From 5e090cc936f668260ebcc398eb810f0b50806db0 Mon Sep 17 00:00:00 2001 From: Guz Date: Fri, 22 Aug 2025 17:06:40 -0300 Subject: [PATCH] fix: run spaces script just on settings change (#96) * fix(hm-module,spaces): missing columns when Zen is opened before the spaces table is created Appearently, opening Zen before the spaces service runs could cause the table not having the theme columns. Because of this, the CREATE TABLE IF NOT EXISTS query would not run, but the columns would still not be present. This commit adds the column via a bash script plus an ALTER TABLE query, mimicking more what Zen does in it JavaScript code. * style(hm-module): run nix fmt on hm-module * refactor(hm-module,spaces): move bash scripts and sql queries directly to service executable This was made to reduce the amount of derivations being build to just one per-profile. Also, this helps make all the logic of the spaces activation be into a single executable for debugging and error handling. * feat(hm-module,spaces): error handling for locked database Provide a useful error message to the user if the service fails due to opened Zen browser instance. * feat(hm-module,spaces): implement places.sqlite updating via home.file.* script This fixed the problem of the systemd service being rerun on every home-manager rebuild. Now the places.sqlite updating script just runs when something needs to be updated on it. We use home.file.* to store the script on the home directory and take advantage of the home.file.*.onChange option to run it. * feat(hm-module,spaces)!: change type of colors values to integer between 0 and 255 * docs: change bold headers to markdown headers to add support #links * docs: [work]spaces options documentation and example --- .github/README.md | 252 ++++++++++++++-------- hm-module.nix | 522 ++++++++++++++++++++++++++-------------------- 2 files changed, 464 insertions(+), 310 deletions(-) diff --git a/.github/README.md b/.github/README.md index 20778e9..a7dbade 100644 --- a/.github/README.md +++ b/.github/README.md @@ -7,10 +7,11 @@ This is a nix flake for the Zen browser. - Linux and MacOS support - Available for _x86_64_ and _aarch64_ - Support for _twilight_ and _beta_ -- Policies can be modified via Home Manager and unwrapped package override +- [Policies can be modified via Home Manager and unwrapped package override](#policies) - Fast & Automatic updates via GitHub Actions - Browser update checks are disabled by default - The default twilight version is reliable and reproducible +- [Declarative \[Work\]Spaces (including themes, icons, containers)](#spaces) ## Installation @@ -123,94 +124,177 @@ experiment with other program options and help with further documentation. } ``` +### Policies + - `policies` (attrsOf anything): You can also modify the **extensions** and **preferences** from here. - **Some common policies:** +#### Some common policies - ```nix - { - programs.zen-browser.policies = { - AutofillAddressEnabled = true; - AutofillCreditCardEnabled = false; - DisableAppUpdate = true; - DisableFeedbackCommands = true; - DisableFirefoxStudies = true; - DisablePocket = true; - DisableTelemetry = true; - DontCheckDefaultBrowser = true; - NoDefaultBookmarks = true; - OfferToSaveLogins = false; - EnableTrackingProtection = { - Value = true; - Locked = true; - Cryptomining = true; - Fingerprinting = true; +```nix +{ + programs.zen-browser.policies = { + AutofillAddressEnabled = true; + AutofillCreditCardEnabled = false; + DisableAppUpdate = true; + DisableFeedbackCommands = true; + DisableFirefoxStudies = true; + DisablePocket = true; + DisableTelemetry = true; + DontCheckDefaultBrowser = true; + NoDefaultBookmarks = true; + OfferToSaveLogins = false; + EnableTrackingProtection = { + Value = true; + Locked = true; + Cryptomining = true; + Fingerprinting = true; + }; + }; +} +``` + +For more policies [read this](https://mozilla.github.io/policy-templates/). + +#### Preferences + +```nix +{ + programs.zen-browser.policies = let + mkLockedAttrs = builtins.mapAttrs (_: value: { + Value = value; + Status = "locked"; + }); + in { + Preferences = mkLockedAttrs { + "browser.tabs.warnOnClose" = false; + # and so on... + }; + }; +} +``` + +##### Zen-specific preferences + +Check [this comment](https://github.com/0xc000022070/zen-browser-flake/issues/59#issuecomment-2964607780). + +#### Extensions + +```nix +{ + programs.zen-browser.policies = let + mkExtensionSettings = builtins.mapAttrs (_: pluginId: { + install_url = "https://addons.mozilla.org/firefox/downloads/latest/${pluginId}/latest.xpi"; + installation_mode = "force_installed"; + }); + in { + ExtensionSettings = mkExtensionSettings { + "wappalyzer@crunchlabz.com" = "wappalyzer"; + "{85860b32-02a8-431a-b2b1-40fbd64c9c69}" = "github-file-icons"; + }; + }; +} +``` + +To setup your own extensions you should: + + 1. [Go to Add-ons for Firefox](https://addons.mozilla.org/en-US/firefox/). + 2. Go to the page of the extension that you want to declare. + 3. Go to "_See all versions_". + 4. Copy the link from any button to "Download file". + 5. Exec **wget** with the output of this command: + + ```bash + echo "" \ + | sed -E 's|https://addons.mozilla.org/firefox/downloads/file/[0-9]+/([^/]+)-[^/]+\.xpi|\1|' \ + | tr '_' '-' \ + | awk '{print "https://addons.mozilla.org/firefox/downloads/latest/" $1 "/latest.xpi"}' + ``` + + 6. Run `unzip -*.xpi -d my-extension && cd my-extension`. + 7. Run `cat manifest.json | jq -r '.browser_specific_settings.gecko.id'` and use the result + for the _entry key_. + 8. Don't forget to add the `install_url` and set `installation_mode` to `force_installed`. + +### Spaces + +> [!WARNING] +> Spaces declaration may change your rebuild experience with Home Manager. Due to limitations +> on how Zen handles spaces, the updating of them is done via a activation script on your +> `home-manager-.service`. This may cause the service to fail, to prevent this, +> it is recommended to close your Zen browser instance before rebuilding. + +- `profiles.*.spaces` (attrsOf submodule): Declare profile's \[work\]spaces. + - `name` (string) Name of space, defaults to submodule/attribute name. + - `id` (string) **Required.** UUID v4 of space. **Changing this after a rebuild will re-create the space as + a new one,** losing opened tabs, groups, etc. If `spacesForce` is true, the space with the previous UUID will be deleted. + - `position` (unsigned integer) Position/order of space in the left bar. + - `icon` (null or (string or path)) Emoji, URI or file path for icon to be used as space icon. + - `container` (null or unsigned integer) Container ID to be used as default in space. + - `theme.type` (nullOr string) Type of theme, defaults to "gradient". + - `theme.color` (listOf submodule) List of JSON colors to be used as theme: + - `red` (integer between 0 and 255) Red value of color (first value of "c" array in JSON object). + - `green` (integer between 0 and 255) Green value of color (second value of "c" array in JSON object). + - `blue` (integer between 0 and 255) Blue value of color (third value of "c" array in JSON object). + - `custom` (boolean) Is custom color ("isCustom" in JSON object). + - `algorithm` (enum of "complementary", "floating" or "analogous") color algorithm (defaults to "floating"). + - `lightness` (integer) Lightness of color. + - `position.x` (integer) X Position of color in gradient picker on Zen browser. + - `position.y` (integer) Y Position of color in gradient picker on Zen browser. + - `type` (enum of "undefined" or "explicit-lightness") Type of color (default to "undefined"). + - `theme.opacity` (null or float) Opacity of theme (defaults to 0.5). + - `theme.rotation` (null or integer) Rotation of theme gradient (defaults to null). + - `theme.texture` (null or float) Amount of texture of theme (defaults to 0.0). +- `profiles.*.spacesForce` (boolean) Whether to delete existing spaces not declared in the configuration. + Recommended to make spaces fully declarative (defaults to false). + +```nix +{ + programs.zen-browser = { + enable = true; + profiles."default" = { + containersForce = true; + containers = { + Personal = { + color = "purple"; + icon = "fingerprint"; + id = 1; + }; + Work = { + color = "blue"; + icon = "briefcase"; + id = 2; + }; + Shopping = { + color = "yellow"; + icon = "dollarsign"; + id = 3; + }; + }; + spacesForce = true; + spaces = let + containers = config.programs.zen-browser.profiles."default".containers; + in { + "Space" = { + id = "c6de089c-410d-4206-961d-ab11f988d40a"; + position = 1000; + }; + "Work" = { + id = "cdd10fab-4fc5-494b-9041-325e5759195b"; + icon = "chrome://browser/skin/zen-icons/selectable/star-2.svg"; + container = containers."Work".id; + position = 2000; + }; + "Shopping" = { + id = "78aabdad-8aae-4fe0-8ff0-2a0c6c4ccc24"; + icon = "💸"; + container = containers."Shopping".id; + position = 3000; + }; }; }; - } - ``` - - For more policies [read this](https://mozilla.github.io/policy-templates/). - - **Preferences:** - - ```nix - { - programs.zen-browser.policies = let - mkLockedAttrs = builtins.mapAttrs (_: value: { - Value = value; - Status = "locked"; - }); - in { - Preferences = mkLockedAttrs { - "browser.tabs.warnOnClose" = false; - # and so on... - }; - }; - } - ``` - - **Zen-specific preferences:** - - Check [this comment](https://github.com/0xc000022070/zen-browser-flake/issues/59#issuecomment-2964607780). - - **Extensions:** - - ```nix - { - programs.zen-browser.policies = let - mkExtensionSettings = builtins.mapAttrs (_: pluginId: { - install_url = "https://addons.mozilla.org/firefox/downloads/latest/${pluginId}/latest.xpi"; - installation_mode = "force_installed"; - }); - in { - ExtensionSettings = mkExtensionSettings { - "wappalyzer@crunchlabz.com" = "wappalyzer"; - "{85860b32-02a8-431a-b2b1-40fbd64c9c69}" = "github-file-icons"; - }; - }; - } - ``` - - To setup your own extensions you should: - - 1. [Go to Add-ons for Firefox](https://addons.mozilla.org/en-US/firefox/). - 2. Go to the page of the extension that you want to declare. - 3. Go to "_See all versions_". - 4. Copy the link from any button to "Download file". - 5. Exec **wget** with the output of this command: - - ```bash - echo "" \ - | sed -E 's|https://addons.mozilla.org/firefox/downloads/file/[0-9]+/([^/]+)-[^/]+\.xpi|\1|' \ - | tr '_' '-' \ - | awk '{print "https://addons.mozilla.org/firefox/downloads/latest/" $1 "/latest.xpi"}' - ``` - - 6. Run `unzip -*.xpi -d my-extension && cd my-extension`. - 7. Run `cat manifest.json | jq -r '.browser_specific_settings.gecko.id'` and use the result - for the _entry key_. - 8. Don't forget to add the `install_url` and set `installation_mode` to `force_installed`. + }; +} +``` ## 1Password diff --git a/hm-module.nix b/hm-module.nix index e988079..12da9ee 100644 --- a/hm-module.nix +++ b/hm-module.nix @@ -29,11 +29,13 @@ linuxConfigPath = ".zen"; darwinConfigPath = "Library/Application Support/Zen"; - configPath = "${config.home.homeDirectory}/${( - if pkgs.stdenv.isDarwin - then darwinConfigPath - else linuxConfigPath - )}"; + configPath = "${ + ( + if pkgs.stdenv.isDarwin + then darwinConfigPath + else linuxConfigPath + ) + }"; mkFirefoxModule = import "${home-manager.outPath}/modules/programs/firefox/mkFirefoxModule.nix"; in { @@ -59,113 +61,134 @@ in { options = setAttrByPath modulePath { profiles = mkOption { type = with types; - attrsOf (submodule ({...}: { - options = { - spacesForce = mkOption { - type = bool; - description = "Whether delete existing spaces not declared in the configuration."; - default = false; - }; - spaces = mkOption { - type = attrsOf (submodule ({name, ...}: { - options = { - name = mkOption { - type = str; - description = "Name of the space."; - default = name; - }; - id = mkOption { - type = str; - description = "REQUIRED. Unique Version 4 UUID for space."; - }; - position = mkOption { - type = ints.unsigned; - description = "Position of space in the left bar."; - default = 1000; - }; - icon = mkOption { - type = nullOr (either str path); - description = "Emoji or icon URI to be used as space icon."; - apply = v: - if isPath v - then "file://${v}" - else v; - default = null; - }; - container = mkOption { - type = nullOr ints.unsigned; - description = "Container ID to be used in space"; - default = null; - }; - theme.type = mkOption { - type = nullOr str; - default = "gradient"; - }; - theme.colors = mkOption { - type = nullOr (listOf (submodule ({...}: { - options = { - red = mkOption { - type = int; - default = 0; - }; - green = mkOption { - type = int; - default = 0; - }; - blue = mkOption { - type = int; - default = 0; - }; - custom = mkOption { - type = bool; - default = false; - }; - algorithm = mkOption { - type = enum ["complementary" "floating" "analogous"]; - default = "floating"; - }; - primary = mkOption { - type = bool; - default = true; - }; - lightness = mkOption { - type = int; - default = 0; - }; - position.x = mkOption { - type = int; - default = 0; - }; - position.y = mkOption { - type = int; - default = 0; - }; - type = mkOption { - type = enum ["undefined" "explicit-lightness"]; - default = "undefined"; - }; - }; - }))); - default = []; - }; - theme.opacity = mkOption { - type = nullOr float; - default = 0.5; - }; - theme.rotation = mkOption { - type = nullOr int; - default = null; - }; - theme.texture = mkOption { - type = nullOr float; - default = 0.0; - }; + attrsOf ( + submodule ( + {...}: { + options = { + spacesForce = mkOption { + type = bool; + description = "Whether to delete existing spaces not declared in the configuration."; + default = false; }; - })); - default = {}; - }; - }; - })); + spaces = mkOption { + type = attrsOf ( + submodule ( + {name, ...}: { + options = { + name = mkOption { + type = str; + description = "Name of the space."; + default = name; + }; + id = mkOption { + type = str; + description = "REQUIRED. Unique Version 4 UUID for space."; + }; + position = mkOption { + type = ints.unsigned; + description = "Position of space in the left bar."; + default = 1000; + }; + icon = mkOption { + type = nullOr (either str path); + description = "Emoji or icon URI to be used as space icon."; + apply = v: + if isPath v + then "file://${v}" + else v; + default = null; + }; + container = mkOption { + type = nullOr ints.unsigned; + description = "Container ID to be used in space"; + default = null; + }; + theme.type = mkOption { + type = nullOr str; + default = "gradient"; + }; + theme.colors = mkOption { + type = nullOr ( + listOf ( + submodule ( + {...}: { + options = { + red = mkOption { + type = ints.between 0 255; + default = 0; + }; + green = mkOption { + type = ints.between 0 255; + default = 0; + }; + blue = mkOption { + type = ints.between 0 255; + default = 0; + }; + custom = mkOption { + type = bool; + default = false; + }; + algorithm = mkOption { + type = enum [ + "complementary" + "floating" + "analogous" + ]; + default = "floating"; + }; + primary = mkOption { + type = bool; + default = true; + }; + lightness = mkOption { + type = int; + default = 0; + }; + position.x = mkOption { + type = int; + default = 0; + }; + position.y = mkOption { + type = int; + default = 0; + }; + type = mkOption { + type = enum [ + "undefined" + "explicit-lightness" + ]; + default = "undefined"; + }; + }; + } + ) + ) + ); + default = []; + }; + theme.opacity = mkOption { + type = nullOr float; + default = 0.5; + }; + theme.rotation = mkOption { + type = nullOr int; + default = null; + }; + theme.texture = mkOption { + type = nullOr float; + default = 0.0; + }; + }; + } + ) + ); + default = {}; + }; + }; + } + ) + ); }; }; @@ -187,140 +210,187 @@ in { }; }; - systemd.user.services."zen-browser-spaces-activation" = let + home.file = let + inherit + (builtins) + isNull + toJSON + toString + ; inherit (lib) - attrByPath - concatMapAttrsStringSep - concatMapStringsSep concatStringsSep - elemAt + concatMapStringsSep + concatMapAttrsStringSep filterAttrs getExe getExe' - isStringLike - lists + mapAttrs' mapAttrsToList + nameValuePair optionalString pipe ; + in (mapAttrs' (profileName: profile: let + sqlite3 = getExe' pkgs.sqlite "sqlite3"; + scriptFile = "${configPath}/${profileName}/places_update.sh"; + placesFile = "${config.home.homeDirectory}/${configPath}/${profileName}/places.sqlite"; - hasSpaces = pipe cfg.profiles [ - (mapAttrsToList (n: v: v.spaces != {})) - (lists.any (v: v)) - ]; + insertSpaces = '' + # Reference: https://github.com/zen-browser/desktop/blob/4e2dfd8a138fd28767bb4799a3ca9d8aab80430e/src/zen/workspaces/ZenWorkspacesStorage.mjs#L25-L55 + ${sqlite3} "${placesFile}" "${ + concatStringsSep " " [ + "CREATE TABLE IF NOT EXISTS zen_workspaces (" + "id INTEGER PRIMARY KEY," + "uuid TEXT UNIQUE NOT NULL," + "name TEXT NOT NULL," + "icon TEXT," + "container_id INTEGER," + "position INTEGER NOT NULL DEFAULT 0," + "created_at INTEGER NOT NULL," + "updated_at INTEGER NOT NULL" + ");" + ] + }" || exit 1 - # Reference: https://github.com/zen-browser/desktop/blob/4e2dfd8a138fd28767bb4799a3ca9d8aab80430e/src/zen/workspaces/ZenWorkspacesStorage.mjs#L25-L55 - initSpacesTable = pkgs.writeText "init.sql" '' - CREATE TABLE IF NOT EXISTS zen_workspaces ( - id INTEGER PRIMARY KEY, - uuid TEXT UNIQUE NOT NULL, - name TEXT NOT NULL, - icon TEXT, - container_id INTEGER, - position INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, + columns=($(${sqlite3} "${placesFile}" "SELECT name FROM pragma_table_info('zen_workspaces');")) + if [[ ! "''${columns[@]}" =~ "theme_type" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_workspaces ADD COLUMN theme_type TEXT;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "theme_colors" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_workspaces ADD COLUMN theme_colors TEXT;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "theme_opacity" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_workspaces ADD COLUMN theme_opacity REAL;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "theme_rotation" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_workspaces ADD COLUMN theme_rotation INTEGER;" || exit 1 + fi + if [[ ! "''${columns[@]}" =~ "theme_texture" ]]; then + ${sqlite3} "${placesFile}" "ALTER TABLE zen_workspaces ADD COLUMN theme_texture REAL;" || exit 1 + fi - theme_type TEXT, - theme_colors TEXT, - theme_opacity REALj - theme_rotation INTEGER, - theme_texture REAL - ) - ''; + # Reference: https://github.com/zen-browser/desktop/blob/4e2dfd8a138fd28767bb4799a3ca9d8aab80430e/src/zen/workspaces/ZenWorkspacesStorage.mjs#L141-L149 + ${sqlite3} "${placesFile}" "${ + (concatStringsSep " " [ + "INSERT OR REPLACE INTO zen_workspaces (" + "uuid," + "name," + "icon," + "container_id," + "position," - # Source: https://github.com/zen-browser/desktop/blob/4e2dfd8a138fd28767bb4799a3ca9d8aab80430e/src/zen/workspaces/ZenWorkspacesStorage.mjs#L141-L149 - updateSpacesTable = spaces: - pkgs.writeText "insert.sql" '' - INSERT OR REPLACE INTO zen_workspaces ( - uuid, - name, - icon, - container_id, - "position", + "theme_type," + "theme_colors," + "theme_opacity," + "theme_rotation," + "theme_texture," - theme_type, - theme_colors, - theme_opacity, - theme_rotation, - theme_texture, - - created_at, - updated_at - ) VALUES ${pipe spaces [ - (mapAttrsToList (_: space: [ - "{${space.id}}" - space.name - (attrByPath ["icon"] null space) - (attrByPath ["container"] null space) - (attrByPath ["position"] 0 space) - (attrByPath ["theme" "type"] "gradient" space) - (map (color: { - inherit (color) algorithm lightness position type; - c = [color.red color.green color.blue]; - isCustom = color.custom; - isPrimary = color.primary; - }) (attrByPath ["theme" "colors"] [] space)) - (attrByPath ["theme" "opacity"] 0.5 space) - (attrByPath ["theme" "rotation"] null space) - (attrByPath ["theme" "texture"] 0.0 space) - ])) - (map (row: - map ( - v: - with builtins; - if isStringLike v - then "'${v}'" - else if (isList v) || (isAttrs v) - then "'${toJSON v}'" - else if isNull v - then "NULL" - else toString v + "created_at," + "updated_at" + ") VALUES " + ]) + + (pipe profile.spaces [ + (mapAttrsToList (_: s: [ + "'{${s.id}}'" + "'${s.name}'" + ( + if isNull s.icon + then "NULL" + else "'${s.icon}'" ) - row)) - (map (row: - row - ++ [ - "COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = ${elemAt row 0}), strftime('%s', 'now'))" - "strftime('%s', 'now')" - ])) + ( + if isNull s.container + then "NULL" + else toString s.container + ) + (toString s.position) + ( + if isNull s.theme.type + then "NULL" + else "'${s.theme.type}'" + ) + ( + if isNull s.theme.colors + then "NULL" + else "'${toJSON (map (c: { + inherit (c) algorithm lightness position type; + c = [c.red c.green c.blue]; + isCustom = c.custom; + isPrimary = c.primary; + }) + s.theme.colors)}'" + ) + ( + if isNull s.theme.opacity + then "NULL" + else toString s.theme.opacity + ) + ( + if isNull s.theme.rotation + then "NULL" + else toString s.theme.rotation + ) + ( + if isNull s.theme.texture + then "NULL" + else toString s.theme.texture + ) + "COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = '{${s.id}}'), strftime('%s', 'now'))" + "strftime('%s', 'now')" + ])) (map (row: concatStringsSep "," row)) (concatMapStringsSep "," (row: "(${row})")) - ]} - ''; + ]) + }" || exit 1 + ''; - filterSpacesTable = spaces: - pkgs.writeText "filter.sql" '' - DELETE FROM zen_workspaces ${ - if spaces != {} - then "WHERE " - else "" - }${ - concatMapAttrsStringSep " AND " (n: v: "NOT uuid = '{${v.id}}'") spaces - } - ''; + deleteSpaces = '' + ${sqlite3} "${placesFile}" "DELETE FROM zen_workspaces ${ + if profile.spaces != {} + then "WHERE " + else "" + }${concatMapAttrsStringSep " AND " (_: s: "NOT uuid = '{${s.id}}'") profile.spaces}" || exit 1 + ''; in - mkIf hasSpaces { - Install.WantedBy = ["default.target"]; - Unit.After = ["home-manager-${config.home.username}.service"]; - Service = { - Type = "oneshot"; - ExecStart = let - sqlite3 = getExe' pkgs.sqlite "sqlite3"; - in - pipe cfg.profiles [ - (filterAttrs (_: v: v.spaces != {})) - (mapAttrsToList (n: v: (pkgs.writeShellScriptBin "zen-browser-spaces-${n}" '' - mkdir -p "${configPath}/${n}" - ${sqlite3} "${configPath}/${n}/places.sqlite" ".read ${initSpacesTable}" - ${sqlite3} "${configPath}/${n}/places.sqlite" ".read ${updateSpacesTable v.spaces}" - ${optionalString v.spacesForce - ''${sqlite3} "${configPath}/${n}/places.sqlite" ".read ${filterSpacesTable v.spaces}"''} - ''))) - (list: map (pkg: getExe pkg) list) - ]; - }; - }; + nameValuePair scriptFile { + source = getExe (pkgs.writeShellScriptBin "places_update_${profileName}" '' + # This file is generated by Zen browser Home Manager module, please to not change it since it + # will be overridden and executed on every rebuild of the home environment. + + function update_spaces() { + ${optionalString (profile.spaces != {}) insertSpaces} + ${optionalString (profile.spacesForce) deleteSpaces} + } + + error="$(update_spaces 2>&1 1>/dev/null)" + if [[ "$?" -ne 0 ]]; then + if [[ "$error" == *"database is locked"* ]]; then + echo "$error" + + YELLOW="\033[1;33m" + NC="\033[0m" + echo -e "zen-update-places:''${YELLOW} Atempted to update the \"zen_workspaces\" table with values declared in \"programs.zen.profiles.\"${profileName}\".spaces\".''${NC}" + echo -e "zen-update-places:''${YELLOW} Failed to update \"${placesFile}\" due to a Zen browser instance for profile \"${profileName}\" being opened, please close''${NC}" + echo -e "zen-update-places:''${YELLOW} Zen browser and rebuild the home environment to rerun \"home-manager-${config.home.username}.service\" and update places.sqlite.''${NC}" + else + echo "$error" + fi + exit 1 + else + exit 0 + fi + ''); + onChange = '' + ${config.home.homeDirectory}/${scriptFile} + if [[ "$?" -ne 0 ]]; then + RED="\033[0;31m" + NC="\033[0m" + echo -e "zen-update-places:''${RED} Failed to update places.sqlite file for Zen browser \"${profileName}\" profile.''${NC}" + fi + ''; + executable = true; + force = true; + }) (filterAttrs (_: profile: profile.spaces != {} || profile.spacesForce) cfg.profiles)); }; }