From 465ea1f994a4e2b498b847c35caa205af7b261df Mon Sep 17 00:00:00 2001 From: Donovan Glover Date: Wed, 24 Jul 2024 05:37:28 +0000 Subject: [PATCH 01/17] swayosd: avoid restarting too quickly Should fix an issue where swayosd.service would stop without starting again after restarting too quickly. Triggered by ending a Hyprland session and logging in with tuigreet. Related: https://github.com/nix-community/home-manager/pull/4316 --- modules/services/swayosd.nix | 3 +++ tests/modules/services/swayosd/swayosd.nix | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules/services/swayosd.nix b/modules/services/swayosd.nix index 79ca7f5e..869b4872 100644 --- a/modules/services/swayosd.nix +++ b/modules/services/swayosd.nix @@ -60,6 +60,8 @@ in { After = [ "graphical-session.target" ]; ConditionEnvironment = "WAYLAND_DISPLAY"; Documentation = "man:swayosd(1)"; + StartLimitBurst = 5; + StartLimitIntervalSec = 10; }; Service = { @@ -71,6 +73,7 @@ in { + (optionalString (cfg.topMargin != null) " --top-margin ${toString cfg.topMargin}"); Restart = "always"; + RestartSec = "2s"; }; Install = { WantedBy = [ "graphical-session.target" ]; }; diff --git a/tests/modules/services/swayosd/swayosd.nix b/tests/modules/services/swayosd/swayosd.nix index 88a7c536..1bc7d535 100644 --- a/tests/modules/services/swayosd/swayosd.nix +++ b/tests/modules/services/swayosd/swayosd.nix @@ -23,6 +23,7 @@ [Service] ExecStart=@swayosd@/bin/swayosd-server --display DISPLAY --style '/etc/xdg/swayosd/style.css' --top-margin 0.100000 Restart=always + RestartSec=2s Type=simple [Unit] @@ -31,6 +32,8 @@ Description=Volume/backlight OSD indicator Documentation=man:swayosd(1) PartOf=graphical-session.target + StartLimitBurst=5 + StartLimitIntervalSec=10 '' } ''; From af70fc502a15d7e1e4c5a4c4fc8e06c2ec561e0c Mon Sep 17 00:00:00 2001 From: home-manager-bot <106474382+home-manager-bot@users.noreply.github.com> Date: Wed, 24 Jul 2024 08:55:10 +0200 Subject: [PATCH 02/17] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/1d9c2c9b3e71b9ee663d11c5d298727dace8d374?narHash=sha256-8MUgifkJ7lkZs3u99UDZMB4kbOxvMEXQZ31FO3SopZ0%3D' (2024-07-19) → 'github:NixOS/nixpkgs/68c9ed8bbed9dfce253cc91560bf9043297ef2fe?narHash=sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8%3D' (2024-07-21) Co-authored-by: github-actions[bot] --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 685f6f60..5e97fc2c 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1721379653, - "narHash": "sha256-8MUgifkJ7lkZs3u99UDZMB4kbOxvMEXQZ31FO3SopZ0=", + "lastModified": 1721562059, + "narHash": "sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1d9c2c9b3e71b9ee663d11c5d298727dace8d374", + "rev": "68c9ed8bbed9dfce253cc91560bf9043297ef2fe", "type": "github" }, "original": { From 304a011325b7ac7b8c9950333cd215a7aa146b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20Dro=C5=BCak?= Date: Wed, 27 Mar 2024 19:18:49 +0100 Subject: [PATCH 03/17] firefox: add languagePacks option Port the programs.firefox.languagePacks option from NixOS --- modules/programs/firefox.nix | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/programs/firefox.nix b/modules/programs/firefox.nix index 1370d4c2..3bee6e0b 100644 --- a/modules/programs/firefox.nix +++ b/modules/programs/firefox.nix @@ -262,6 +262,18 @@ in { ''; }; + languagePacks = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + The language packs to install. Available language codes can be found + on the releases page: + `https://releases.mozilla.org/pub/firefox/releases/''${version}/linux-x86_64/xpi/`, + replacing `''${version}` with the version of Firefox you have. + ''; + example = [ "en-GB" "de" ]; + }; + nativeMessagingHosts = mkOption { type = types.listOf types.package; default = [ ]; @@ -736,6 +748,14 @@ in { message = "Container id must be smaller than 4294967294 (2^32 - 2)"; }) + { + assertion = cfg.languagePacks == [ ] || cfg.package != null; + message = '' + 'programs.firefox.languagePacks' requires 'programs.firefox.package' + to be set to a non-null value. + ''; + } + (mkNoDuplicateAssertion cfg.profiles "profile") ] ++ (mapAttrsToList (_: profile: mkNoDuplicateAssertion profile.containers "container") @@ -750,6 +770,15 @@ in { programs.firefox.finalPackage = wrapPackage cfg.package; + programs.firefox.policies = { + ExtensionSettings = listToAttrs (map (lang: + nameValuePair "langpack-${lang}@firefox.mozilla.org" { + installation_mode = "normal_installed"; + install_url = + "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi"; + }) cfg.languagePacks); + }; + home.packages = lib.optional (cfg.finalPackage != null) cfg.finalPackage; home.file = mkMerge ([{ From bc2b96acda50229bc99925dde5c8e561e90b0b00 Mon Sep 17 00:00:00 2001 From: Masum Reza <50095635+JohnRTitor@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:58:33 +0530 Subject: [PATCH 04/17] zsh: add programs.zsh.autosuggestions.strategy option (#5396) defaults to history --- modules/programs/zsh.nix | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/programs/zsh.nix b/modules/programs/zsh.nix index 3ec73f30..11d77a16 100644 --- a/modules/programs/zsh.nix +++ b/modules/programs/zsh.nix @@ -398,6 +398,22 @@ in {manpage}`zshzle(1)` for syntax. ''; }; + + strategy = mkOption { + type = types.listOf (types.enum [ "history" "completion" "match_prev_cmd" ]); + default = [ "history" ]; + description = '' + `ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated. + The strategies in the array are tried successively until a suggestion is found. + There are currently three built-in strategies to choose from: + + - `history`: Chooses the most recent match from history. + - `completion`: Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module) + - `match_prev_cmd`: Like `history`, but chooses the most recent match whose preceding history item matches + the most recently executed command. Note that this strategy won't work as expected with ZSH options that + don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. + ''; + }; }; history = mkOption { @@ -610,6 +626,7 @@ in (optionalString cfg.autosuggestion.enable '' source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh + ZSH_AUTOSUGGEST_STRATEGY=(${concatStringsSep " " cfg.autosuggestion.strategy}) '') (optionalString (cfg.autosuggestion.enable && cfg.autosuggestion.highlight != null) '' ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="${cfg.autosuggestion.highlight}" From 180158b46ea02f1c8f794367f122e8f2a2e49cf8 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Sat, 27 Jul 2024 09:08:30 +0200 Subject: [PATCH 05/17] mcfly: add settings option --- modules/programs/mcfly.nix | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/modules/programs/mcfly.nix b/modules/programs/mcfly.nix index 450a2738..d0dddba2 100644 --- a/modules/programs/mcfly.nix +++ b/modules/programs/mcfly.nix @@ -5,6 +5,8 @@ let cfg = config.programs.mcfly; + tomlFormat = pkgs.formats.toml { }; + bashIntegration = '' eval "$(${getExe pkgs.mcfly} init bash)" '' + optionalString cfg.fzf.enable '' @@ -40,6 +42,37 @@ in { options.programs.mcfly = { enable = mkEnableOption "mcfly"; + settings = mkOption { + type = tomlFormat.type; + default = { }; + example = literalExpression '' + { + colors = { + menubar = { + bg = "black"; + fg = "red"; + }; + darkmode = { + prompt = "cyan"; + timing = "yellow"; + results_selection_fg = "cyan"; + results_selection_bg = "black"; + results_selection_hl = "red"; + }; + }; + } + ''; + description = '' + Settings written to {file}`~/.config/mcfly/config.toml`. + + Note, if your McFly database is currently in {file}`~/.mcfly`, + then this option has no effect. + Move the database to {file}`$XDG_DATA_DIR/mcfly/history.db` and + remove {file}`~/.mcfly` to make the settings take effect. See + . + ''; + }; + keyScheme = mkOption { type = types.enum [ "emacs" "vim" ]; default = "emacs"; @@ -105,6 +138,11 @@ in { { home.packages = [ pkgs.mcfly ] ++ optional cfg.fzf.enable pkgs.mcfly-fzf; + # Oddly enough, McFly expects this in the data path, not in config. + xdg.dataFile."mcfly/config.toml" = mkIf (cfg.settings != { }) { + source = tomlFormat.generate "mcfly-config.toml" cfg.settings; + }; + programs.bash.initExtra = mkIf cfg.enableBashIntegration bashIntegration; programs.zsh.initExtra = mkIf cfg.enableZshIntegration zshIntegration; From 975b83ca560d17db51a66cb2b0dc0e44213eab27 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Sat, 27 Jul 2024 09:35:13 +0200 Subject: [PATCH 06/17] treewide: fix eval after Nixpkgs maintainer changes --- modules/config/home-cursor.nix | 2 +- modules/lib/maintainers.nix | 5 +++++ modules/misc/nix.nix | 2 +- modules/programs/bottom.nix | 2 +- modules/programs/watson.nix | 2 +- modules/services/fnott.nix | 2 +- modules/services/window-managers/i3-sway/swaynag.nix | 2 +- 7 files changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/config/home-cursor.nix b/modules/config/home-cursor.nix index c696908d..76996048 100644 --- a/modules/config/home-cursor.nix +++ b/modules/config/home-cursor.nix @@ -67,7 +67,7 @@ let }; in { - meta.maintainers = [ maintainers.polykernel maintainers.league ]; + meta.maintainers = [ hm.maintainers.polykernel maintainers.league ]; imports = [ (mkAliasOptionModule [ "xsession" "pointerCursor" "package" ] [ diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index f1a81815..6ad7bd18 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -445,6 +445,11 @@ github = "podocarp"; githubId = 10473184; }; + polykernel = { + github = "polykernel"; + githubId = 81340136; + name = "polykernel"; + }; mainrs = { name = "mainrs"; email = "5113257+mainrs@users.noreply.github.com"; diff --git a/modules/misc/nix.nix b/modules/misc/nix.nix index 2daa56b2..668d44a1 100644 --- a/modules/misc/nix.nix +++ b/modules/misc/nix.nix @@ -311,5 +311,5 @@ in { }) ]); - meta.maintainers = [ maintainers.polykernel ]; + meta.maintainers = [ lib.hm.maintainers.polykernel ]; } diff --git a/modules/programs/bottom.nix b/modules/programs/bottom.nix index 810307b1..91d172ad 100644 --- a/modules/programs/bottom.nix +++ b/modules/programs/bottom.nix @@ -56,5 +56,5 @@ in { }; }; - meta.maintainers = [ maintainers.polykernel ]; + meta.maintainers = [ hm.maintainers.polykernel ]; } diff --git a/modules/programs/watson.nix b/modules/programs/watson.nix index b30dec09..5d0f4b7b 100644 --- a/modules/programs/watson.nix +++ b/modules/programs/watson.nix @@ -14,7 +14,7 @@ let config.xdg.configHome; in { - meta.maintainers = [ maintainers.polykernel ]; + meta.maintainers = [ hm.maintainers.polykernel ]; options.programs.watson = { enable = mkEnableOption "watson, a wonderful CLI to track your time"; diff --git a/modules/services/fnott.nix b/modules/services/fnott.nix index 3d2d1dd8..a8d7a637 100644 --- a/modules/services/fnott.nix +++ b/modules/services/fnott.nix @@ -9,7 +9,7 @@ let iniFormat = pkgs.formats.ini { }; in { - meta.maintainers = with maintainers; [ polykernel ]; + meta.maintainers = [ hm.maintainers.polykernel ]; options = { services.fnott = { diff --git a/modules/services/window-managers/i3-sway/swaynag.nix b/modules/services/window-managers/i3-sway/swaynag.nix index fcdedd3d..c3f4d535 100644 --- a/modules/services/window-managers/i3-sway/swaynag.nix +++ b/modules/services/window-managers/i3-sway/swaynag.nix @@ -14,7 +14,7 @@ let }; in attrsOf confAtom; in { - meta.maintainers = with maintainers; [ polykernel ]; + meta.maintainers = [ hm.maintainers.polykernel ]; options = { wayland.windowManager.sway.swaynag = { From d0240a064db3987eb4d5204cf2400bc4452d9922 Mon Sep 17 00:00:00 2001 From: Tomodachi94 Date: Wed, 24 Jul 2024 16:36:17 -0700 Subject: [PATCH 07/17] gnome-terminal: update package name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `pkgs.gnome.gnome-terminal` package was moved to `pkgs.gnome-terminal`. The former is now a deprecated alias that throws a warning whenever a configuration enabling the module is used: ``` The ‘gnome.gnome-terminal’ was moved to top-level. Please use ‘pkgs.gnome-terminal’ directly. ``` Related: https://github.com/NixOS/nixpkgs/pull/319659 Related: https://github.com/nix-community/home-manager/pull/5611 --- modules/programs/gnome-terminal.nix | 2 +- tests/modules/programs/gnome-terminal/bad-profile-name.nix | 5 ++--- tests/modules/programs/gnome-terminal/gnome-terminal-1.nix | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/programs/gnome-terminal.nix b/modules/programs/gnome-terminal.nix index 83cfc027..dafadb7e 100644 --- a/modules/programs/gnome-terminal.nix +++ b/modules/programs/gnome-terminal.nix @@ -316,7 +316,7 @@ in { }) ]; - home.packages = [ pkgs.gnome.gnome-terminal ]; + home.packages = [ pkgs.gnome-terminal ]; dconf.settings = let dconfPath = "org/gnome/terminal/legacy"; in { diff --git a/tests/modules/programs/gnome-terminal/bad-profile-name.nix b/tests/modules/programs/gnome-terminal/bad-profile-name.nix index ebd85440..308b9306 100644 --- a/tests/modules/programs/gnome-terminal/bad-profile-name.nix +++ b/tests/modules/programs/gnome-terminal/bad-profile-name.nix @@ -13,9 +13,8 @@ }; }; - nixpkgs.overlays = [ - (self: super: { gnome.gnome-terminal = config.lib.test.mkStubPackage { }; }) - ]; + nixpkgs.overlays = + [ (self: super: { gnome-terminal = config.lib.test.mkStubPackage { }; }) ]; test.stubs.dconf = { }; diff --git a/tests/modules/programs/gnome-terminal/gnome-terminal-1.nix b/tests/modules/programs/gnome-terminal/gnome-terminal-1.nix index bb58983f..6dab6a2b 100644 --- a/tests/modules/programs/gnome-terminal/gnome-terminal-1.nix +++ b/tests/modules/programs/gnome-terminal/gnome-terminal-1.nix @@ -49,9 +49,7 @@ with lib; }; nixpkgs.overlays = [ - (self: super: { - gnome.gnome-terminal = config.lib.test.mkStubPackage { }; - }) + (self: super: { gnome-terminal = config.lib.test.mkStubPackage { }; }) ]; test.stubs.dconf = { }; From cd520fbd31934deaa1cda11297a99ef1fa369dbf Mon Sep 17 00:00:00 2001 From: polykernel <81340136+polykernel@users.noreply.github.com> Date: Sun, 28 Jul 2024 11:58:33 -0400 Subject: [PATCH 08/17] maintainers: remove polykernel --- modules/config/home-cursor.nix | 2 +- modules/lib/maintainers.nix | 5 ----- modules/misc/nix.nix | 2 +- modules/programs/bottom.nix | 2 +- modules/programs/watson.nix | 2 +- modules/services/fnott.nix | 2 +- modules/services/window-managers/i3-sway/swaynag.nix | 2 +- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/config/home-cursor.nix b/modules/config/home-cursor.nix index 76996048..cb9a2ab2 100644 --- a/modules/config/home-cursor.nix +++ b/modules/config/home-cursor.nix @@ -67,7 +67,7 @@ let }; in { - meta.maintainers = [ hm.maintainers.polykernel maintainers.league ]; + meta.maintainers = [ maintainers.league ]; imports = [ (mkAliasOptionModule [ "xsession" "pointerCursor" "package" ] [ diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index 6ad7bd18..f1a81815 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -445,11 +445,6 @@ github = "podocarp"; githubId = 10473184; }; - polykernel = { - github = "polykernel"; - githubId = 81340136; - name = "polykernel"; - }; mainrs = { name = "mainrs"; email = "5113257+mainrs@users.noreply.github.com"; diff --git a/modules/misc/nix.nix b/modules/misc/nix.nix index 668d44a1..652d3eb8 100644 --- a/modules/misc/nix.nix +++ b/modules/misc/nix.nix @@ -311,5 +311,5 @@ in { }) ]); - meta.maintainers = [ lib.hm.maintainers.polykernel ]; + meta.maintainers = [ ]; } diff --git a/modules/programs/bottom.nix b/modules/programs/bottom.nix index 91d172ad..fd18fe0d 100644 --- a/modules/programs/bottom.nix +++ b/modules/programs/bottom.nix @@ -56,5 +56,5 @@ in { }; }; - meta.maintainers = [ hm.maintainers.polykernel ]; + meta.maintainers = [ ]; } diff --git a/modules/programs/watson.nix b/modules/programs/watson.nix index 5d0f4b7b..c842c519 100644 --- a/modules/programs/watson.nix +++ b/modules/programs/watson.nix @@ -14,7 +14,7 @@ let config.xdg.configHome; in { - meta.maintainers = [ hm.maintainers.polykernel ]; + meta.maintainers = [ ]; options.programs.watson = { enable = mkEnableOption "watson, a wonderful CLI to track your time"; diff --git a/modules/services/fnott.nix b/modules/services/fnott.nix index a8d7a637..ecbd0fd1 100644 --- a/modules/services/fnott.nix +++ b/modules/services/fnott.nix @@ -9,7 +9,7 @@ let iniFormat = pkgs.formats.ini { }; in { - meta.maintainers = [ hm.maintainers.polykernel ]; + meta.maintainers = [ ]; options = { services.fnott = { diff --git a/modules/services/window-managers/i3-sway/swaynag.nix b/modules/services/window-managers/i3-sway/swaynag.nix index c3f4d535..924521f7 100644 --- a/modules/services/window-managers/i3-sway/swaynag.nix +++ b/modules/services/window-managers/i3-sway/swaynag.nix @@ -14,7 +14,7 @@ let }; in attrsOf confAtom; in { - meta.maintainers = [ hm.maintainers.polykernel ]; + meta.maintainers = [ ]; options = { wayland.windowManager.sway.swaynag = { From 9fdadb1cb65093015fc8cd65a592c983b490c75c Mon Sep 17 00:00:00 2001 From: home-manager-bot <106474382+home-manager-bot@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:43:01 +0200 Subject: [PATCH 09/17] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/68c9ed8bbed9dfce253cc91560bf9043297ef2fe?narHash=sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8%3D' (2024-07-21) → 'github:NixOS/nixpkgs/b73c2221a46c13557b1b3be9c2070cc42cf01eb3?narHash=sha256-QOS0ykELUmPbrrUGmegAUlpmUFznDQeR4q7rFhl8eQg%3D' (2024-07-27) Co-authored-by: github-actions[bot] --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 5e97fc2c..3ba1333a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1721562059, - "narHash": "sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8=", + "lastModified": 1722062969, + "narHash": "sha256-QOS0ykELUmPbrrUGmegAUlpmUFznDQeR4q7rFhl8eQg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "68c9ed8bbed9dfce253cc91560bf9043297ef2fe", + "rev": "b73c2221a46c13557b1b3be9c2070cc42cf01eb3", "type": "github" }, "original": { From ea72cf548fafb2876232a3ae22fcc03d5fb354de Mon Sep 17 00:00:00 2001 From: Milo Moisson Date: Sun, 28 Jul 2024 22:54:29 +0200 Subject: [PATCH 10/17] jujutsu: support darwin guidelines for config placement Follow up to #5207, fixing jujutsu module on darwin targets. --- modules/programs/jujutsu.nix | 7 ++++++- tests/modules/programs/jujutsu/empty-config.nix | 9 ++++++--- tests/modules/programs/jujutsu/example-config.nix | 12 +++++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/modules/programs/jujutsu.nix b/modules/programs/jujutsu.nix index a21c7108..f0e1b425 100644 --- a/modules/programs/jujutsu.nix +++ b/modules/programs/jujutsu.nix @@ -7,6 +7,11 @@ let cfg = config.programs.jujutsu; tomlFormat = pkgs.formats.toml { }; + configDir = if pkgs.stdenv.isDarwin then + "Library/Application Support" + else + config.xdg.configHome; + in { meta.maintainers = [ maintainers.shikanime ]; @@ -51,7 +56,7 @@ in { config = mkIf cfg.enable { home.packages = [ cfg.package ]; - xdg.configFile."jj/config.toml" = mkIf (cfg.settings != { }) { + home.file."${configDir}/jj/config.toml" = mkIf (cfg.settings != { }) { source = tomlFormat.generate "jujutsu-config" (cfg.settings // optionalAttrs (cfg.ediff) (let emacsDiffScript = pkgs.writeShellScriptBin "emacs-ediff" '' diff --git a/tests/modules/programs/jujutsu/empty-config.nix b/tests/modules/programs/jujutsu/empty-config.nix index 10dd1258..55b5d87e 100644 --- a/tests/modules/programs/jujutsu/empty-config.nix +++ b/tests/modules/programs/jujutsu/empty-config.nix @@ -1,11 +1,14 @@ -{ ... }: +{ pkgs, ... }: -{ +let + configDir = + if pkgs.stdenv.isDarwin then "Library/Application Support" else ".config"; +in { programs.jujutsu.enable = true; test.stubs.jujutsu = { }; nmt.script = '' - assertPathNotExists home-files/.config/jj/config.toml + assertPathNotExists 'home-files/${configDir}/jj/config.toml' ''; } diff --git a/tests/modules/programs/jujutsu/example-config.nix b/tests/modules/programs/jujutsu/example-config.nix index d83b552b..02918778 100644 --- a/tests/modules/programs/jujutsu/example-config.nix +++ b/tests/modules/programs/jujutsu/example-config.nix @@ -1,6 +1,9 @@ -{ config, ... }: +{ pkgs, config, ... }: -{ +let + configDir = + if pkgs.stdenv.isDarwin then "Library/Application Support" else ".config"; +in { programs.jujutsu = { enable = true; package = config.lib.test.mkStubPackage { }; @@ -13,9 +16,8 @@ }; nmt.script = '' - assertFileExists home-files/.config/jj/config.toml - assertFileContent \ - home-files/.config/jj/config.toml \ + assertFileExists 'home-files/${configDir}/jj/config.toml' + assertFileContent 'home-files/${configDir}/jj/config.toml' \ ${ builtins.toFile "expected.toml" '' [user] From a11cfcd0a18fdf6257808da631a956800af764bf Mon Sep 17 00:00:00 2001 From: nikitax44 <49244351+nikitax44@users.noreply.github.com> Date: Mon, 29 Jul 2024 02:00:57 +0500 Subject: [PATCH 11/17] micro: add package option --- modules/programs/micro.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/programs/micro.nix b/modules/programs/micro.nix index 0093e25f..bf34f8e6 100644 --- a/modules/programs/micro.nix +++ b/modules/programs/micro.nix @@ -15,6 +15,8 @@ in { programs.micro = { enable = mkEnableOption "micro, a terminal-based text editor"; + package = mkPackageOption pkgs "micro" { }; + settings = mkOption { type = jsonFormat.type; default = { }; @@ -35,7 +37,7 @@ in { }; config = mkIf cfg.enable { - home.packages = [ pkgs.micro ]; + home.packages = [ cfg.package ]; xdg.configFile."micro/settings.json".source = jsonFormat.generate "micro-settings" cfg.settings; From 587fcca66e9d11c8e2357053c096a8a727c120ab Mon Sep 17 00:00:00 2001 From: Jean Sidharta Date: Sun, 28 Jul 2024 21:37:02 +0000 Subject: [PATCH 12/17] eww: add terminal integration options Use eww's shell-completions command to generate completions for bash, zsh and fish. --- modules/programs/eww.nix | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/modules/programs/eww.nix b/modules/programs/eww.nix index 3d178f94..75a10922 100644 --- a/modules/programs/eww.nix +++ b/modules/programs/eww.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.programs.eww; + ewwCmd = "${cfg.package}/bin/eww"; in { meta.maintainers = [ hm.maintainers.mainrs ]; @@ -30,10 +31,40 @@ in { {file}`$XDG_CONFIG_HOME/eww`. ''; }; + + enableBashIntegration = mkEnableOption "Bash integration" // { + default = true; + }; + + enableZshIntegration = mkEnableOption "Zsh integration" // { + default = true; + }; + + enableFishIntegration = mkEnableOption "Fish integration" // { + default = true; + }; }; config = mkIf cfg.enable { home.packages = [ cfg.package ]; xdg.configFile."eww".source = cfg.configDir; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + if [[ $TERM != "dumb" ]]; then + eval "$(${ewwCmd} shell-completions --shell bash)" + fi + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + if [[ $TERM != "dumb" ]]; then + eval "$(${ewwCmd} shell-completions --shell zsh)" + fi + ''; + + programs.fish.interactiveShellInit = mkIf cfg.enableFishIntegration '' + if test "$TERM" != "dumb" + eval "$(${ewwCmd} shell-completions --shell fish)" + end + ''; }; } From 792757f643cedc13f02098d8ed506d82e19ec1da Mon Sep 17 00:00:00 2001 From: bricked Date: Wed, 13 Mar 2024 00:20:41 +0100 Subject: [PATCH 13/17] firefox: support firefox derivatives Adds support for Firefox forks by introducing methods that create generic configs and options. Additional configs and options can be added in separate modules. --- modules/lib/maintainers.nix | 6 + modules/programs/firefox.nix | 981 +--------------- modules/programs/firefox/mkFirefoxModule.nix | 1017 +++++++++++++++++ tests/default.nix | 2 +- .../firefox/container-id-out-of-range.nix | 37 +- tests/modules/programs/firefox/default.nix | 9 - .../firefox/deprecated-native-messenger.nix | 27 +- .../firefox/duplicate-container-ids.nix | 53 +- .../firefox/duplicate-profile-ids.nix | 35 +- tests/modules/programs/firefox/firefox.nix | 11 + tests/modules/programs/firefox/policies.nix | 31 +- .../programs/firefox/profile-settings.nix | 301 ++--- .../firefox/setup-firefox-mock-overlay.nix | 28 +- .../programs/firefox/state-version-19_09.nix | 21 +- 14 files changed, 1356 insertions(+), 1203 deletions(-) create mode 100644 modules/programs/firefox/mkFirefoxModule.nix delete mode 100644 tests/modules/programs/firefox/default.nix create mode 100644 tests/modules/programs/firefox/firefox.nix diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index f1a81815..9793d5c3 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -49,6 +49,12 @@ github = "bertof"; githubId = 9915675; }; + bricked = { + name = "Bricked"; + email = "hello@bricked.dev"; + github = "brckd"; + githubId = 92804487; + }; CarlosLoboxyz = { name = "Carlos Lobo"; email = "86011416+CarlosLoboxyz@users.noreply.github.com"; diff --git a/modules/programs/firefox.nix b/modules/programs/firefox.nix index 3bee6e0b..ac85990f 100644 --- a/modules/programs/firefox.nix +++ b/modules/programs/firefox.nix @@ -1,974 +1,51 @@ -{ config, lib, pkgs, ... }: +{ lib, ... }: with lib; let - inherit (pkgs.stdenv.hostPlatform) isDarwin; + modulePath = [ "programs" "firefox" ]; - cfg = config.programs.firefox; + moduleName = concatStringsSep "." modulePath; - jsonFormat = pkgs.formats.json { }; - - mozillaConfigPath = - if isDarwin then "Library/Application Support/Mozilla" else ".mozilla"; - - firefoxConfigPath = if isDarwin then - "Library/Application Support/Firefox" - else - "${mozillaConfigPath}/firefox"; - - profilesPath = - if isDarwin then "${firefoxConfigPath}/Profiles" else firefoxConfigPath; - - nativeMessagingHostsPath = if isDarwin then - "${mozillaConfigPath}/NativeMessagingHosts" - else - "${mozillaConfigPath}/native-messaging-hosts"; - - nativeMessagingHostsJoined = pkgs.symlinkJoin { - name = "ff_native-messaging-hosts"; - paths = [ - # Link a .keep file to keep the directory around - (pkgs.writeTextDir "lib/mozilla/native-messaging-hosts/.keep" "") - # Link package configured native messaging hosts (entire Firefox actually) - cfg.finalPackage - ] - # Link user configured native messaging hosts - ++ cfg.nativeMessagingHosts; - }; - - # The extensions path shared by all profiles; will not be supported - # by future Firefox versions. - extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"; - - profiles = flip mapAttrs' cfg.profiles (_: profile: - nameValuePair "Profile${toString profile.id}" { - Name = profile.name; - Path = if isDarwin then "Profiles/${profile.path}" else profile.path; - IsRelative = 1; - Default = if profile.isDefault then 1 else 0; - }) // { - General = { StartWithLastProfile = 1; }; - }; - - profilesIni = generators.toINI { } profiles; - - userPrefValue = pref: - builtins.toJSON (if isBool pref || isInt pref || isString pref then - pref - else - builtins.toJSON pref); - - mkUserJs = prefs: extraPrefs: bookmarks: - let - prefs' = lib.optionalAttrs ([ ] != bookmarks) { - "browser.bookmarks.file" = toString (firefoxBookmarksFile bookmarks); - "browser.places.importBookmarksHTML" = true; - } // prefs; - in '' - // Generated by Home Manager. - - ${concatStrings (mapAttrsToList (name: value: '' - user_pref("${name}", ${userPrefValue value}); - '') prefs')} - - ${extraPrefs} - ''; - - mkContainersJson = containers: - let - containerToIdentity = _: container: { - userContextId = container.id; - name = container.name; - icon = container.icon; - color = container.color; - public = true; - }; - in '' - ${builtins.toJSON { - version = 4; - lastUserContextId = - elemAt (mapAttrsToList (_: container: container.id) containers) 0; - identities = mapAttrsToList containerToIdentity containers ++ [ - { - userContextId = 4294967294; # 2^32 - 2 - name = "userContextIdInternal.thumbnail"; - icon = ""; - color = ""; - accessKey = ""; - public = false; - } - { - userContextId = 4294967295; # 2^32 - 1 - name = "userContextIdInternal.webextStorageLocal"; - icon = ""; - color = ""; - accessKey = ""; - public = false; - } - ]; - }} - ''; - - firefoxBookmarksFile = bookmarks: - let - indent = level: - lib.concatStringsSep "" (map (lib.const " ") (lib.range 1 level)); - - bookmarkToHTML = indentLevel: bookmark: - '' - ${indent indentLevel}
${escapeXML bookmark.name}''; - - directoryToHTML = indentLevel: directory: '' - ${indent indentLevel}
${ - if directory.toolbar then - '' -

Bookmarks Toolbar'' - else - ''

${escapeXML directory.name}'' - }

- ${indent indentLevel}

- ${allItemsToHTML (indentLevel + 1) directory.bookmarks} - ${indent indentLevel}

''; - - itemToHTMLOrRecurse = indentLevel: item: - if item ? "url" then - bookmarkToHTML indentLevel item - else - directoryToHTML indentLevel item; - - allItemsToHTML = indentLevel: bookmarks: - lib.concatStringsSep "\n" - (map (itemToHTMLOrRecurse indentLevel) bookmarks); - - bookmarkEntries = allItemsToHTML 1 bookmarks; - in pkgs.writeText "firefox-bookmarks.html" '' - - - - Bookmarks -

Bookmarks Menu

-

- ${bookmarkEntries} -

- ''; - - mkNoDuplicateAssertion = entities: entityKind: - (let - # Return an attribute set with entity IDs as keys and a list of - # entity names with corresponding ID as value. An ID is present in - # the result only if more than one entity has it. The argument - # entities is a list of AttrSet of one id/name pair. - findDuplicateIds = entities: - filterAttrs (_entityId: entityNames: length entityNames != 1) - (zipAttrs entities); - - duplicates = findDuplicateIds (mapAttrsToList - (entityName: entity: { "${toString entity.id}" = entityName; }) - entities); - - mkMsg = entityId: entityNames: - " - ID ${entityId} is used by " + concatStringsSep ", " entityNames; - in { - assertion = duplicates == { }; - message = '' - Must not have a Firefox ${entityKind} with an existing ID but - '' + concatStringsSep "\n" (mapAttrsToList mkMsg duplicates); - }); - - wrapPackage = package: - let - # The configuration expected by the Firefox wrapper. - fcfg = { enableGnomeExtensions = cfg.enableGnomeExtensions; }; - - # A bit of hackery to force a config into the wrapper. - browserName = - package.browserName or (builtins.parseDrvName package.name).name; - - # The configuration expected by the Firefox wrapper builder. - bcfg = setAttrByPath [ browserName ] fcfg; - - in if package == null then - null - else if isDarwin then - package - else if versionAtLeast config.home.stateVersion "19.09" then - package.override (old: { - cfg = old.cfg or { } // fcfg; - extraPolicies = (old.extraPolicies or { }) // cfg.policies; - }) - else - (pkgs.wrapFirefox.override { config = bcfg; }) package { }; + mkFirefoxModule = import ./firefox/mkFirefoxModule.nix; in { - meta.maintainers = [ maintainers.rycee maintainers.kira-bruneau ]; + meta.maintainers = + [ maintainers.rycee maintainers.kira-bruneau hm.maintainers.bricked ]; imports = [ - (mkRemovedOptionModule [ "programs" "firefox" "extensions" ] '' + (mkFirefoxModule { + inherit modulePath; + name = "Firefox"; + wrappedPackageName = "firefox"; + unwrappedPackageName = "firefox-unwrapped"; + visible = true; + + platforms.linux = rec { + vendorPath = ".mozilla"; + configPath = "${vendorPath}/firefox"; + }; + platforms.darwin = { + vendorPath = "Library/Application Support/Mozilla"; + configPath = "Library/Application Support/Firefox"; + }; + }) + + (mkRemovedOptionModule (modulePath ++ [ "extensions" ]) '' Extensions are now managed per-profile. That is, change from - programs.firefox.extensions = [ foo bar ]; + ${moduleName}.extensions = [ foo bar ]; to - programs.firefox.profiles.myprofile.extensions = [ foo bar ];'') - (mkRemovedOptionModule [ "programs" "firefox" "enableAdobeFlash" ] + ${moduleName}.profiles.myprofile.extensions = [ foo bar ];'') + (mkRemovedOptionModule (modulePath ++ [ "enableAdobeFlash" ]) "Support for this option has been removed.") - (mkRemovedOptionModule [ "programs" "firefox" "enableGoogleTalk" ] + (mkRemovedOptionModule (modulePath ++ [ "enableGoogleTalk" ]) "Support for this option has been removed.") - (mkRemovedOptionModule [ "programs" "firefox" "enableIcedTea" ] + (mkRemovedOptionModule (modulePath ++ [ "enableIcedTea" ]) "Support for this option has been removed.") ]; - - options = { - programs.firefox = { - enable = mkEnableOption "Firefox"; - - package = mkOption { - type = with types; nullOr package; - default = if versionAtLeast config.home.stateVersion "19.09" then - pkgs.firefox - else - pkgs.firefox-unwrapped; - defaultText = literalExpression "pkgs.firefox"; - example = literalExpression '' - pkgs.firefox.override { - # See nixpkgs' firefox/wrapper.nix to check which options you can use - nativeMessagingHosts = [ - # Gnome shell native connector - pkgs.gnome-browser-connector - # Tridactyl native connector - pkgs.tridactyl-native - ]; - } - ''; - description = '' - The Firefox package to use. If state version ≥ 19.09 then - this should be a wrapped Firefox package. For earlier state - versions it should be an unwrapped Firefox package. - Set to `null` to disable installing Firefox. - ''; - }; - - languagePacks = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - The language packs to install. Available language codes can be found - on the releases page: - `https://releases.mozilla.org/pub/firefox/releases/''${version}/linux-x86_64/xpi/`, - replacing `''${version}` with the version of Firefox you have. - ''; - example = [ "en-GB" "de" ]; - }; - - nativeMessagingHosts = mkOption { - type = types.listOf types.package; - default = [ ]; - description = '' - Additional packages containing native messaging hosts that should be - made available to Firefox extensions. - ''; - }; - - finalPackage = mkOption { - type = with types; nullOr package; - readOnly = true; - description = "Resulting Firefox package."; - }; - - policies = mkOption { - type = types.attrsOf jsonFormat.type; - default = { }; - description = - "[See list of policies](https://mozilla.github.io/policy-templates/)."; - example = { - DefaultDownloadDirectory = "\${home}/Downloads"; - BlockAboutConfig = true; - }; - }; - - profiles = mkOption { - type = types.attrsOf (types.submodule ({ config, name, ... }: { - options = { - name = mkOption { - type = types.str; - default = name; - description = "Profile name."; - }; - - id = mkOption { - type = types.ints.unsigned; - default = 0; - description = '' - Profile ID. This should be set to a unique number per profile. - ''; - }; - - settings = mkOption { - type = types.attrsOf (jsonFormat.type // { - description = - "Firefox preference (int, bool, string, and also attrs, list, float as a JSON string)"; - }); - default = { }; - example = literalExpression '' - { - "browser.startup.homepage" = "https://nixos.org"; - "browser.search.region" = "GB"; - "browser.search.isUS" = false; - "distribution.searchplugins.defaultLocale" = "en-GB"; - "general.useragent.locale" = "en-GB"; - "browser.bookmarks.showMobileBookmarks" = true; - "browser.newtabpage.pinned" = [{ - title = "NixOS"; - url = "https://nixos.org"; - }]; - } - ''; - description = '' - Attribute set of Firefox preferences. - - Firefox only supports int, bool, and string types for - preferences, but home-manager will automatically - convert all other JSON-compatible values into strings. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = '' - Extra preferences to add to {file}`user.js`. - ''; - }; - - userChrome = mkOption { - type = types.lines; - default = ""; - description = "Custom Firefox user chrome CSS."; - example = '' - /* Hide tab bar in FF Quantum */ - @-moz-document url("chrome://browser/content/browser.xul") { - #TabsToolbar { - visibility: collapse !important; - margin-bottom: 21px !important; - } - - #sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header { - visibility: collapse !important; - } - } - ''; - }; - - userContent = mkOption { - type = types.lines; - default = ""; - description = "Custom Firefox user content CSS."; - example = '' - /* Hide scrollbar in FF Quantum */ - *{scrollbar-width:none !important} - ''; - }; - - bookmarks = mkOption { - type = let - bookmarkSubmodule = types.submodule ({ config, name, ... }: { - options = { - name = mkOption { - type = types.str; - default = name; - description = "Bookmark name."; - }; - - tags = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "Bookmark tags."; - }; - - keyword = mkOption { - type = types.nullOr types.str; - default = null; - description = "Bookmark search keyword."; - }; - - url = mkOption { - type = types.str; - description = "Bookmark url, use %s for search terms."; - }; - }; - }) // { - description = "bookmark submodule"; - }; - - bookmarkType = types.addCheck bookmarkSubmodule (x: x ? "url"); - - directoryType = types.submodule ({ config, name, ... }: { - options = { - name = mkOption { - type = types.str; - default = name; - description = "Directory name."; - }; - - bookmarks = mkOption { - type = types.listOf nodeType; - default = [ ]; - description = "Bookmarks within directory."; - }; - - toolbar = mkOption { - type = types.bool; - default = false; - description = '' - Make this the toolbar directory. Note, this does _not_ - mean that this directory will be added to the toolbar, - this directory _is_ the toolbar. - ''; - }; - }; - }) // { - description = "directory submodule"; - }; - - nodeType = types.either bookmarkType directoryType; - in with types; - coercedTo (attrsOf nodeType) attrValues (listOf nodeType); - default = [ ]; - example = literalExpression '' - [ - { - name = "wikipedia"; - tags = [ "wiki" ]; - keyword = "wiki"; - url = "https://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go"; - } - { - name = "kernel.org"; - url = "https://www.kernel.org"; - } - { - name = "Nix sites"; - toolbar = true; - bookmarks = [ - { - name = "homepage"; - url = "https://nixos.org/"; - } - { - name = "wiki"; - tags = [ "wiki" "nix" ]; - url = "https://wiki.nixos.org/"; - } - ]; - } - ] - ''; - description = '' - Preloaded bookmarks. Note, this may silently overwrite any - previously existing bookmarks! - ''; - }; - - path = mkOption { - type = types.str; - default = name; - description = "Profile path."; - }; - - isDefault = mkOption { - type = types.bool; - default = config.id == 0; - defaultText = "true if profile ID is 0"; - description = "Whether this is a default profile."; - }; - - search = { - force = mkOption { - type = with types; bool; - default = false; - description = '' - Whether to force replace the existing search - configuration. This is recommended since Firefox will - replace the symlink for the search configuration on every - launch, but note that you'll lose any existing - configuration by enabling this. - ''; - }; - - default = mkOption { - type = with types; nullOr str; - default = null; - example = "DuckDuckGo"; - description = '' - The default search engine used in the address bar and search bar. - ''; - }; - - privateDefault = mkOption { - type = with types; nullOr str; - default = null; - example = "DuckDuckGo"; - description = '' - The default search engine used in the Private Browsing. - ''; - }; - - order = mkOption { - type = with types; uniq (listOf str); - default = [ ]; - example = [ "DuckDuckGo" "Google" ]; - description = '' - The order the search engines are listed in. Any engines - that aren't included in this list will be listed after - these in an unspecified order. - ''; - }; - - engines = mkOption { - type = with types; attrsOf (attrsOf jsonFormat.type); - default = { }; - example = literalExpression '' - { - "Nix Packages" = { - urls = [{ - template = "https://search.nixos.org/packages"; - params = [ - { name = "type"; value = "packages"; } - { name = "query"; value = "{searchTerms}"; } - ]; - }]; - - icon = "''${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; - definedAliases = [ "@np" ]; - }; - - "NixOS Wiki" = { - urls = [{ template = "https://wiki.nixos.org/index.php?search={searchTerms}"; }]; - iconUpdateURL = "https://wiki.nixos.org/favicon.png"; - updateInterval = 24 * 60 * 60 * 1000; # every day - definedAliases = [ "@nw" ]; - }; - - "Bing".metaData.hidden = true; - "Google".metaData.alias = "@g"; # builtin engines only support specifying one additional alias - } - ''; - description = '' - Attribute set of search engine configurations. Engines - that only have {var}`metaData` specified will - be treated as builtin to Firefox. - - See [SearchEngine.jsm](https://searchfox.org/mozilla-central/rev/669329e284f8e8e2bb28090617192ca9b4ef3380/toolkit/components/search/SearchEngine.jsm#1138-1177) - in Firefox's source for available options. We maintain a - mapping to let you specify all options in the referenced - link without underscores, but it may fall out of date with - future options. - - Note, {var}`icon` is also a special option - added by Home Manager to make it convenient to specify - absolute icon paths. - ''; - }; - }; - - containersForce = mkOption { - type = types.bool; - default = false; - description = '' - Whether to force replace the existing containers - configuration. This is recommended since Firefox will - replace the symlink on every launch, but note that you'll - lose any existing configuration by enabling this. - ''; - }; - - containers = mkOption { - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { - name = mkOption { - type = types.str; - default = name; - description = "Container name, e.g., shopping."; - }; - - id = mkOption { - type = types.ints.unsigned; - default = 0; - description = '' - Container ID. This should be set to a unique number per container in this profile. - ''; - }; - - # List of colors at - # https://searchfox.org/mozilla-central/rev/5ad226c7379b0564c76dc3b54b44985356f94c5a/toolkit/components/extensions/parent/ext-contextualIdentities.js#32 - color = mkOption { - type = types.enum [ - "blue" - "turquoise" - "green" - "yellow" - "orange" - "red" - "pink" - "purple" - "toolbar" - ]; - default = "pink"; - description = "Container color."; - }; - - icon = mkOption { - type = types.enum [ - "briefcase" - "cart" - "circle" - "dollar" - "fence" - "fingerprint" - "gift" - "vacation" - "food" - "fruit" - "pet" - "tree" - "chill" - ]; - default = "fruit"; - description = "Container icon."; - }; - }; - })); - default = { }; - example = { - "shopping" = { - id = 1; - color = "blue"; - icon = "cart"; - }; - "dangerous" = { - id = 2; - color = "red"; - icon = "fruit"; - }; - }; - description = '' - Attribute set of container configurations. See - [Multi-Account - Containers](https://support.mozilla.org/en-US/kb/containers) - for more information. - ''; - }; - - extensions = mkOption { - type = types.listOf types.package; - default = [ ]; - example = literalExpression '' - with pkgs.nur.repos.rycee.firefox-addons; [ - privacy-badger - ] - ''; - description = '' - List of Firefox add-on packages to install for this profile. - Some pre-packaged add-ons are accessible from the - [Nix User Repository](https://github.com/nix-community/NUR). - Once you have NUR installed run - - ```console - $ nix-env -f '' -qaP -A nur.repos.rycee.firefox-addons - ``` - - to list the available Firefox add-ons. - - Note that it is necessary to manually enable these extensions - inside Firefox after the first installation. - - To automatically enable extensions add - `"extensions.autoDisableScopes" = 0;` - to - [{option}`programs.firefox.profiles..settings`](#opt-programs.firefox.profiles._name_.settings) - ''; - }; - - }; - })); - default = { }; - description = "Attribute set of Firefox profiles."; - }; - - enableGnomeExtensions = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the GNOME Shell native host connector. Note, you - also need to set the NixOS option - `services.gnome.gnome-browser-connector.enable` to - `true`. - ''; - }; - }; - }; - - config = mkIf cfg.enable { - assertions = [ - (let - defaults = - catAttrs "name" (filter (a: a.isDefault) (attrValues cfg.profiles)); - in { - assertion = cfg.profiles == { } || length defaults == 1; - message = "Must have exactly one default Firefox profile but found " - + toString (length defaults) + optionalString (length defaults > 1) - (", namely " + concatStringsSep ", " defaults); - }) - - (let - getContainers = profiles: - flatten - (mapAttrsToList (_: value: (attrValues value.containers)) profiles); - - findInvalidContainerIds = profiles: - filter (container: container.id >= 4294967294) - (getContainers profiles); - in { - assertion = cfg.profiles == { } - || length (findInvalidContainerIds cfg.profiles) == 0; - message = "Container id must be smaller than 4294967294 (2^32 - 2)"; - }) - - { - assertion = cfg.languagePacks == [ ] || cfg.package != null; - message = '' - 'programs.firefox.languagePacks' requires 'programs.firefox.package' - to be set to a non-null value. - ''; - } - - (mkNoDuplicateAssertion cfg.profiles "profile") - ] ++ (mapAttrsToList - (_: profile: mkNoDuplicateAssertion profile.containers "container") - cfg.profiles); - - warnings = optional (cfg.enableGnomeExtensions or false) '' - Using 'programs.firefox.enableGnomeExtensions' has been deprecated and - will be removed in the future. Please change to overriding the package - configuration using 'programs.firefox.package' instead. You can refer to - its example for how to do this. - ''; - - programs.firefox.finalPackage = wrapPackage cfg.package; - - programs.firefox.policies = { - ExtensionSettings = listToAttrs (map (lang: - nameValuePair "langpack-${lang}@firefox.mozilla.org" { - installation_mode = "normal_installed"; - install_url = - "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi"; - }) cfg.languagePacks); - }; - - home.packages = lib.optional (cfg.finalPackage != null) cfg.finalPackage; - - home.file = mkMerge ([{ - "${firefoxConfigPath}/profiles.ini" = - mkIf (cfg.profiles != { }) { text = profilesIni; }; - - "${nativeMessagingHostsPath}" = { - source = - "${nativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts"; - recursive = true; - }; - }] ++ flip mapAttrsToList cfg.profiles (_: profile: { - "${profilesPath}/${profile.path}/.keep".text = ""; - - "${profilesPath}/${profile.path}/chrome/userChrome.css" = - mkIf (profile.userChrome != "") { text = profile.userChrome; }; - - "${profilesPath}/${profile.path}/chrome/userContent.css" = - mkIf (profile.userContent != "") { text = profile.userContent; }; - - "${profilesPath}/${profile.path}/user.js" = mkIf (profile.settings != { } - || profile.extraConfig != "" || profile.bookmarks != [ ]) { - text = - mkUserJs profile.settings profile.extraConfig profile.bookmarks; - }; - - "${profilesPath}/${profile.path}/containers.json" = - mkIf (profile.containers != { }) { - force = profile.containersForce; - text = mkContainersJson profile.containers; - }; - - "${profilesPath}/${profile.path}/search.json.mozlz4" = mkIf - (profile.search.default != null || profile.search.privateDefault != null - || profile.search.order != [ ] || profile.search.engines != { }) { - force = profile.search.force; - source = let - settings = { - version = 6; - engines = let - # Map of nice field names to internal field names. - # This is intended to be exhaustive and should be - # updated at every version bump. - internalFieldNames = (genAttrs [ - "name" - "isAppProvided" - "loadPath" - "hasPreferredIcon" - "updateInterval" - "updateURL" - "iconUpdateURL" - "iconURL" - "iconMapObj" - "metaData" - "orderHint" - "definedAliases" - "urls" - ] (name: "_${name}")) // { - searchForm = "__searchForm"; - }; - - processCustomEngineInput = input: - (removeAttrs input [ "icon" ]) - // optionalAttrs (input ? icon) { - # Convenience to specify absolute path to icon - iconURL = "file://${input.icon}"; - } // (optionalAttrs (input ? iconUpdateURL) { - # Convenience to default iconURL to iconUpdateURL so - # the icon is immediately downloaded from the URL - iconURL = input.iconURL or input.iconUpdateURL; - } // { - # Required for custom engine configurations, loadPaths - # are unique identifiers that are generally formatted - # like: [source]/path/to/engine.xml - loadPath = '' - [home-manager]/programs.firefox.profiles.${profile.name}.search.engines."${ - replaceStrings [ "\\" ] [ "\\\\" ] input.name - }"''; - }); - - processEngineInput = name: input: - let - requiredInput = { - inherit name; - isAppProvided = input.isAppProvided or removeAttrs input - [ "metaData" ] == { }; - metaData = input.metaData or { }; - }; - in if requiredInput.isAppProvided then - requiredInput - else - processCustomEngineInput (input // requiredInput); - - buildEngineConfig = name: input: - mapAttrs' (name: value: { - name = internalFieldNames.${name} or name; - inherit value; - }) (processEngineInput name input); - - sortEngineConfigs = configs: - let - buildEngineConfigWithOrder = order: name: - let - config = configs.${name} or { - _name = name; - _isAppProvided = true; - _metaData = { }; - }; - in config // { - _metaData = config._metaData // { inherit order; }; - }; - - engineConfigsWithoutOrder = - attrValues (removeAttrs configs profile.search.order); - - sortedEngineConfigs = - (imap buildEngineConfigWithOrder profile.search.order) - ++ engineConfigsWithoutOrder; - in sortedEngineConfigs; - - engineInput = profile.search.engines // { - # Infer profile.search.default as an app provided - # engine if it's not in profile.search.engines - ${profile.search.default} = - profile.search.engines.${profile.search.default} or { }; - } // { - ${profile.search.privateDefault} = - profile.search.engines.${profile.search.privateDefault} or { }; - }; - in sortEngineConfigs (mapAttrs buildEngineConfig engineInput); - - metaData = optionalAttrs (profile.search.default != null) { - current = profile.search.default; - hash = "@hash@"; - } // optionalAttrs (profile.search.privateDefault != null) { - private = profile.search.privateDefault; - privateHash = "@privateHash@"; - } // { - useSavedOrder = profile.search.order != [ ]; - }; - }; - - # Home Manager doesn't circumvent user consent and isn't acting - # maliciously. We're modifying the search outside of Firefox, but - # a claim by Mozilla to remove this would be very anti-user, and - # is unlikely to be an issue for our use case. - disclaimer = appName: - "By modifying this file, I agree that I am doing so " - + "only within ${appName} itself, using official, user-driven search " - + "engine selection processes, and in a way which does not circumvent " - + "user consent. I acknowledge that any attempt to change this file " - + "from outside of ${appName} is a malicious act, and will be responded " - + "to accordingly."; - - salt = if profile.search.default != null then - profile.path + profile.search.default + disclaimer "Firefox" - else - null; - - privateSalt = if profile.search.privateDefault != null then - profile.path + profile.search.privateDefault - + disclaimer "Firefox" - else - null; - in pkgs.runCommand "search.json.mozlz4" { - nativeBuildInputs = with pkgs; [ mozlz4a openssl ]; - json = builtins.toJSON settings; - inherit salt privateSalt; - } '' - if [[ -n $salt ]]; then - export hash=$(echo -n "$salt" | openssl dgst -sha256 -binary | base64) - export privateHash=$(echo -n "$privateSalt" | openssl dgst -sha256 -binary | base64) - mozlz4a <(substituteStream json search.json.in --subst-var hash --subst-var privateHash) "$out" - else - mozlz4a <(echo "$json") "$out" - fi - ''; - }; - - "${profilesPath}/${profile.path}/extensions" = - mkIf (profile.extensions != [ ]) { - source = let - extensionsEnvPkg = pkgs.buildEnv { - name = "hm-firefox-extensions"; - paths = profile.extensions; - }; - in "${extensionsEnvPkg}/share/mozilla/${extensionPath}"; - recursive = true; - force = true; - }; - })); - }; } diff --git a/modules/programs/firefox/mkFirefoxModule.nix b/modules/programs/firefox/mkFirefoxModule.nix new file mode 100644 index 00000000..32ab4b47 --- /dev/null +++ b/modules/programs/firefox/mkFirefoxModule.nix @@ -0,0 +1,1017 @@ +{ modulePath, name, description ? null, wrappedPackageName ? null +, unwrappedPackageName ? null, platforms, visible ? false }: + +{ config, lib, pkgs, ... }: + +with lib; + +let + + inherit (pkgs.stdenv.hostPlatform) isDarwin; + + moduleName = concatStringsSep "." modulePath; + + cfg = getAttrFromPath modulePath config; + + jsonFormat = pkgs.formats.json { }; + + supportedPlatforms = flatten (attrVals (attrNames platforms) lib.platforms); + + isWrapped = versionAtLeast config.home.stateVersion "19.09" + && wrappedPackageName != null; + + defaultPackageName = + if isWrapped then wrappedPackageName else unwrappedPackageName; + + packageName = if wrappedPackageName != null then + wrappedPackageName + else + unwrappedPackageName; + + profilesPath = + if isDarwin then "${cfg.configPath}/Profiles" else cfg.configPath; + + nativeMessagingHostsPath = if isDarwin then + "${cfg.vendorPath}/NativeMessagingHosts" + else + "${cfg.vendorPath}/native-messaging-hosts"; + + nativeMessagingHostsJoined = pkgs.symlinkJoin { + name = "ff_native-messaging-hosts"; + paths = [ + # Link a .keep file to keep the directory around + (pkgs.writeTextDir "lib/mozilla/native-messaging-hosts/.keep" "") + # Link package configured native messaging hosts (entire browser actually) + cfg.finalPackage + ] + # Link user configured native messaging hosts + ++ cfg.nativeMessagingHosts; + }; + + # The extensions path shared by all profiles; will not be supported + # by future browser versions. + extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"; + + profiles = flip mapAttrs' cfg.profiles (_: profile: + nameValuePair "Profile${toString profile.id}" { + Name = profile.name; + Path = if isDarwin then "Profiles/${profile.path}" else profile.path; + IsRelative = 1; + Default = if profile.isDefault then 1 else 0; + }) // { + General = { + StartWithLastProfile = 1; + Version = 2; + }; + }; + + profilesIni = generators.toINI { } profiles; + + userPrefValue = pref: + builtins.toJSON (if isBool pref || isInt pref || isString pref then + pref + else + builtins.toJSON pref); + + mkUserJs = prefs: extraPrefs: bookmarks: + let + prefs' = lib.optionalAttrs ([ ] != bookmarks) { + "browser.bookmarks.file" = toString (browserBookmarksFile bookmarks); + "browser.places.importBookmarksHTML" = true; + } // prefs; + in '' + // Generated by Home Manager. + + ${concatStrings (mapAttrsToList (name: value: '' + user_pref("${name}", ${userPrefValue value}); + '') prefs')} + + ${extraPrefs} + ''; + + mkContainersJson = containers: + let + containerToIdentity = _: container: { + userContextId = container.id; + name = container.name; + icon = container.icon; + color = container.color; + public = true; + }; + in '' + ${builtins.toJSON { + version = 4; + lastUserContextId = + elemAt (mapAttrsToList (_: container: container.id) containers) 0; + identities = mapAttrsToList containerToIdentity containers ++ [ + { + userContextId = 4294967294; # 2^32 - 2 + name = "userContextIdInternal.thumbnail"; + icon = ""; + color = ""; + accessKey = ""; + public = false; + } + { + userContextId = 4294967295; # 2^32 - 1 + name = "userContextIdInternal.webextStorageLocal"; + icon = ""; + color = ""; + accessKey = ""; + public = false; + } + ]; + }} + ''; + + browserBookmarksFile = bookmarks: + let + indent = level: + lib.concatStringsSep "" (map (lib.const " ") (lib.range 1 level)); + + bookmarkToHTML = indentLevel: bookmark: + '' + ${indent indentLevel}
${escapeXML bookmark.name}''; + + directoryToHTML = indentLevel: directory: '' + ${indent indentLevel}
${ + if directory.toolbar then + '' +

Bookmarks Toolbar'' + else + ''

${escapeXML directory.name}'' + }

+ ${indent indentLevel}

+ ${allItemsToHTML (indentLevel + 1) directory.bookmarks} + ${indent indentLevel}

''; + + itemToHTMLOrRecurse = indentLevel: item: + if item ? "url" then + bookmarkToHTML indentLevel item + else + directoryToHTML indentLevel item; + + allItemsToHTML = indentLevel: bookmarks: + lib.concatStringsSep "\n" + (map (itemToHTMLOrRecurse indentLevel) bookmarks); + + bookmarkEntries = allItemsToHTML 1 bookmarks; + in pkgs.writeText "${packageName}-bookmarks.html" '' + + + + Bookmarks +

Bookmarks Menu

+

+ ${bookmarkEntries} +

+ ''; + + mkNoDuplicateAssertion = entities: entityKind: + (let + # Return an attribute set with entity IDs as keys and a list of + # entity names with corresponding ID as value. An ID is present in + # the result only if more than one entity has it. The argument + # entities is a list of AttrSet of one id/name pair. + findDuplicateIds = entities: + filterAttrs (_entityId: entityNames: length entityNames != 1) + (zipAttrs entities); + + duplicates = findDuplicateIds (mapAttrsToList + (entityName: entity: { "${toString entity.id}" = entityName; }) + entities); + + mkMsg = entityId: entityNames: + " - ID ${entityId} is used by " + concatStringsSep ", " entityNames; + in { + assertion = duplicates == { }; + message = '' + Must not have a ${name} ${entityKind} with an existing ID but + '' + concatStringsSep "\n" (mapAttrsToList mkMsg duplicates); + }); + + wrapPackage = package: + let + # The configuration expected by the Firefox wrapper. + fcfg = { enableGnomeExtensions = cfg.enableGnomeExtensions; }; + + # A bit of hackery to force a config into the wrapper. + browserName = + package.browserName or (builtins.parseDrvName package.name).name; + + # The configuration expected by the Firefox wrapper builder. + bcfg = setAttrByPath [ browserName ] fcfg; + + in if package == null then + null + else if isDarwin then + package + else if isWrapped then + package.override (old: { + cfg = old.cfg or { } // fcfg; + extraPolicies = (old.extraPolicies or { }) // cfg.policies; + }) + else + (pkgs.wrapFirefox.override { config = bcfg; }) package { }; + +in { + options = setAttrByPath modulePath { + enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to enable ${name}.${ + optionalString (description != null) " ${description}" + } + ${optionalString (!visible) + "See `programs.firefox` for more configuration options."} + ''; + }; + + package = mkOption { + inherit visible; + type = with types; nullOr package; + default = pkgs.${defaultPackageName}; + defaultText = literalExpression "pkgs.${packageName}"; + example = literalExpression '' + pkgs.${packageName}.override { + # See nixpkgs' firefox/wrapper.nix to check which options you can use + nativeMessagingHosts = [ + # Gnome shell native connector + pkgs.gnome-browser-connector + # Tridactyl native connector + pkgs.tridactyl-native + ]; + } + ''; + description = '' + The ${name} package to use. If state version ≥ 19.09 then + this should be a wrapped ${name} package. For earlier state + versions it should be an unwrapped ${name} package. + Set to `null` to disable installing ${name}. + ''; + }; + + languagePacks = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + The language packs to install. Available language codes can be found + on the releases page: + `https://releases.mozilla.org/pub/firefox/releases/''${version}/linux-x86_64/xpi/`, + replacing `''${version}` with the version of Firefox you have. + ''; + example = [ "en-GB" "de" ]; + }; + + name = mkOption { + internal = true; + type = types.str; + default = name; + example = "Firefox"; + description = "The name of the browser."; + }; + + wrappedPackageName = mkOption { + internal = true; + type = with types; nullOr str; + default = wrappedPackageName; + description = "Name of the wrapped browser package."; + }; + + vendorPath = mkOption { + internal = true; + type = with types; nullOr str; + default = with platforms; + if isDarwin then + darwin.vendorPath or null + else + linux.vendorPath or null; + example = ".mozilla"; + description = + "Directory containing the native messaging hosts directory."; + }; + + configPath = mkOption { + internal = true; + type = types.str; + default = with platforms; + if isDarwin then darwin.configPath else linux.configPath; + example = ".mozilla/firefox"; + description = "Directory containing the ${name} configuration files."; + }; + + nativeMessagingHosts = optionalAttrs (cfg.vendorPath != null) (mkOption { + inherit visible; + type = types.listOf types.package; + default = [ ]; + description = '' + Additional packages containing native messaging hosts that should be + made available to ${name} extensions. + ''; + }); + + finalPackage = mkOption { + inherit visible; + type = with types; nullOr package; + readOnly = true; + description = "Resulting ${cfg.name} package."; + }; + + policies = optionalAttrs (unwrappedPackageName != null) (mkOption { + inherit visible; + type = types.attrsOf jsonFormat.type; + default = { }; + description = + "[See list of policies](https://mozilla.github.io/policy-templates/)."; + example = { + DefaultDownloadDirectory = "\${home}/Downloads"; + BlockAboutConfig = true; + }; + }); + + profiles = mkOption { + inherit visible; + type = types.attrsOf (types.submodule ({ config, name, ... }: { + options = { + name = mkOption { + type = types.str; + default = name; + description = "Profile name."; + }; + + id = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + Profile ID. This should be set to a unique number per profile. + ''; + }; + + settings = mkOption { + type = types.attrsOf (jsonFormat.type // { + description = + "${name} preference (int, bool, string, and also attrs, list, float as a JSON string)"; + }); + default = { }; + example = literalExpression '' + { + "browser.startup.homepage" = "https://nixos.org"; + "browser.search.region" = "GB"; + "browser.search.isUS" = false; + "distribution.searchplugins.defaultLocale" = "en-GB"; + "general.useragent.locale" = "en-GB"; + "browser.bookmarks.showMobileBookmarks" = true; + "browser.newtabpage.pinned" = [{ + title = "NixOS"; + url = "https://nixos.org"; + }]; + } + ''; + description = '' + Attribute set of ${name} preferences. + + ${name} only supports int, bool, and string types for + preferences, but home-manager will automatically + convert all other JSON-compatible values into strings. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra preferences to add to {file}`user.js`. + ''; + }; + + userChrome = mkOption { + type = types.lines; + default = ""; + description = "Custom ${name} user chrome CSS."; + example = '' + /* Hide tab bar in FF Quantum */ + @-moz-document url("chrome://browser/content/browser.xul") { + #TabsToolbar { + visibility: collapse !important; + margin-bottom: 21px !important; + } + + #sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header { + visibility: collapse !important; + } + } + ''; + }; + + userContent = mkOption { + type = types.lines; + default = ""; + description = "Custom ${name} user content CSS."; + example = '' + /* Hide scrollbar in FF Quantum */ + *{scrollbar-width:none !important} + ''; + }; + + bookmarks = mkOption { + type = let + bookmarkSubmodule = types.submodule ({ config, name, ... }: { + options = { + name = mkOption { + type = types.str; + default = name; + description = "Bookmark name."; + }; + + tags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Bookmark tags."; + }; + + keyword = mkOption { + type = types.nullOr types.str; + default = null; + description = "Bookmark search keyword."; + }; + + url = mkOption { + type = types.str; + description = "Bookmark url, use %s for search terms."; + }; + }; + }) // { + description = "bookmark submodule"; + }; + + bookmarkType = types.addCheck bookmarkSubmodule (x: x ? "url"); + + directoryType = types.submodule ({ config, name, ... }: { + options = { + name = mkOption { + type = types.str; + default = name; + description = "Directory name."; + }; + + bookmarks = mkOption { + type = types.listOf nodeType; + default = [ ]; + description = "Bookmarks within directory."; + }; + + toolbar = mkOption { + type = types.bool; + default = false; + description = '' + Make this the toolbar directory. Note, this does _not_ + mean that this directory will be added to the toolbar, + this directory _is_ the toolbar. + ''; + }; + }; + }) // { + description = "directory submodule"; + }; + + nodeType = types.either bookmarkType directoryType; + in with types; + coercedTo (attrsOf nodeType) attrValues (listOf nodeType); + default = [ ]; + example = literalExpression '' + [ + { + name = "wikipedia"; + tags = [ "wiki" ]; + keyword = "wiki"; + url = "https://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go"; + } + { + name = "kernel.org"; + url = "https://www.kernel.org"; + } + { + name = "Nix sites"; + toolbar = true; + bookmarks = [ + { + name = "homepage"; + url = "https://nixos.org/"; + } + { + name = "wiki"; + tags = [ "wiki" "nix" ]; + url = "https://wiki.nixos.org/"; + } + ]; + } + ] + ''; + description = '' + Preloaded bookmarks. Note, this may silently overwrite any + previously existing bookmarks! + ''; + }; + + path = mkOption { + type = types.str; + default = name; + description = "Profile path."; + }; + + isDefault = mkOption { + type = types.bool; + default = config.id == 0; + defaultText = "true if profile ID is 0"; + description = "Whether this is a default profile."; + }; + + search = { + force = mkOption { + type = with types; bool; + default = false; + description = '' + Whether to force replace the existing search + configuration. This is recommended since ${name} will + replace the symlink for the search configuration on every + launch, but note that you'll lose any existing + configuration by enabling this. + ''; + }; + + default = mkOption { + type = with types; nullOr str; + default = null; + example = "DuckDuckGo"; + description = '' + The default search engine used in the address bar and search bar. + ''; + }; + + privateDefault = mkOption { + type = with types; nullOr str; + default = null; + example = "DuckDuckGo"; + description = '' + The default search engine used in the Private Browsing. + ''; + }; + + order = mkOption { + type = with types; uniq (listOf str); + default = [ ]; + example = [ "DuckDuckGo" "Google" ]; + description = '' + The order the search engines are listed in. Any engines + that aren't included in this list will be listed after + these in an unspecified order. + ''; + }; + + engines = mkOption { + type = with types; attrsOf (attrsOf jsonFormat.type); + default = { }; + example = literalExpression '' + { + "Nix Packages" = { + urls = [{ + template = "https://search.nixos.org/packages"; + params = [ + { name = "type"; value = "packages"; } + { name = "query"; value = "{searchTerms}"; } + ]; + }]; + + icon = "''${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; + definedAliases = [ "@np" ]; + }; + + "NixOS Wiki" = { + urls = [{ template = "https://wiki.nixos.org/index.php?search={searchTerms}"; }]; + iconUpdateURL = "https://wiki.nixos.org/favicon.png"; + updateInterval = 24 * 60 * 60 * 1000; # every day + definedAliases = [ "@nw" ]; + }; + + "Bing".metaData.hidden = true; + "Google".metaData.alias = "@g"; # builtin engines only support specifying one additional alias + } + ''; + description = '' + Attribute set of search engine configurations. Engines + that only have {var}`metaData` specified will + be treated as builtin to ${name}. + + See [SearchEngine.jsm](https://searchfox.org/mozilla-central/rev/669329e284f8e8e2bb28090617192ca9b4ef3380/toolkit/components/search/SearchEngine.jsm#1138-1177) + in Firefox's source for available options. We maintain a + mapping to let you specify all options in the referenced + link without underscores, but it may fall out of date with + future options. + + Note, {var}`icon` is also a special option + added by Home Manager to make it convenient to specify + absolute icon paths. + ''; + }; + }; + + containersForce = mkOption { + type = types.bool; + default = false; + description = '' + Whether to force replace the existing containers configuration. + This is recommended since Firefox will replace the symlink on + every launch, but note that you'll lose any existing configuration + by enabling this. + ''; + }; + + containers = mkOption { + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + name = mkOption { + type = types.str; + default = name; + description = "Container name, e.g., shopping."; + }; + + id = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + Container ID. This should be set to a unique number per container in this profile. + ''; + }; + + # List of colors at + # https://searchfox.org/mozilla-central/rev/5ad226c7379b0564c76dc3b54b44985356f94c5a/toolkit/components/extensions/parent/ext-contextualIdentities.js#32 + color = mkOption { + type = types.enum [ + "blue" + "turquoise" + "green" + "yellow" + "orange" + "red" + "pink" + "purple" + "toolbar" + ]; + default = "pink"; + description = "Container color."; + }; + + icon = mkOption { + type = types.enum [ + "briefcase" + "cart" + "circle" + "dollar" + "fence" + "fingerprint" + "gift" + "vacation" + "food" + "fruit" + "pet" + "tree" + "chill" + ]; + default = "fruit"; + description = "Container icon."; + }; + }; + })); + default = { }; + example = { + "shopping" = { + id = 1; + color = "blue"; + icon = "cart"; + }; + "dangerous" = { + id = 2; + color = "red"; + icon = "fruit"; + }; + }; + description = '' + Attribute set of container configurations. See + [Multi-Account + Containers](https://support.mozilla.org/en-US/kb/containers) + for more information. + ''; + }; + + extensions = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExpression '' + with pkgs.nur.repos.rycee.firefox-addons; [ + privacy-badger + ] + ''; + description = '' + List of ${name} add-on packages to install for this profile. + Some pre-packaged add-ons are accessible from the + [Nix User Repository](https://github.com/nix-community/NUR). + Once you have NUR installed run + + ```console + $ nix-env -f '' -qaP -A nur.repos.rycee.firefox-addons + ``` + + to list the available ${name} add-ons. + + Note that it is necessary to manually enable these extensions + inside ${name} after the first installation. + + To automatically enable extensions add + `"extensions.autoDisableScopes" = 0;` + to + [{option}`${moduleName}.profiles..settings`](#opt-${moduleName}.profiles._name_.settings) + ''; + }; + + }; + })); + default = { }; + description = "Attribute set of ${name} profiles."; + }; + + enableGnomeExtensions = mkOption { + inherit visible; + type = types.bool; + default = false; + description = '' + Whether to enable the GNOME Shell native host connector. Note, you + also need to set the NixOS option + `services.gnome.gnome-browser-connector.enable` to + `true`. + ''; + }; + }; + + config = mkIf cfg.enable ({ + assertions = [ + (hm.assertions.assertPlatform moduleName pkgs supportedPlatforms) + + (let + defaults = + catAttrs "name" (filter (a: a.isDefault) (attrValues cfg.profiles)); + in { + assertion = cfg.profiles == { } || length defaults == 1; + message = "Must have exactly one default ${cfg.name} profile but found " + + toString (length defaults) + optionalString (length defaults > 1) + (", namely " + concatStringsSep ", " defaults); + }) + + (let + getContainers = profiles: + flatten + (mapAttrsToList (_: value: (attrValues value.containers)) profiles); + + findInvalidContainerIds = profiles: + filter (container: container.id >= 4294967294) + (getContainers profiles); + in { + assertion = cfg.profiles == { } + || length (findInvalidContainerIds cfg.profiles) == 0; + message = "Container id must be smaller than 4294967294 (2^32 - 2)"; + }) + + { + assertion = cfg.languagePacks == [ ] || cfg.package != null; + message = '' + 'programs.firefox.languagePacks' requires 'programs.firefox.package' + to be set to a non-null value. + ''; + } + + (mkNoDuplicateAssertion cfg.profiles "profile") + ] ++ (mapAttrsToList + (_: profile: mkNoDuplicateAssertion profile.containers "container") + cfg.profiles); + + warnings = optional (cfg.enableGnomeExtensions or false) '' + Using '${moduleName}.enableGnomeExtensions' has been deprecated and + will be removed in the future. Please change to overriding the package + configuration using '${moduleName}.package' instead. You can refer to + its example for how to do this. + ''; + + programs.firefox.policies = { + ExtensionSettings = listToAttrs (map (lang: + nameValuePair "langpack-${lang}@firefox.mozilla.org" { + installation_mode = "normal_installed"; + install_url = + "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi"; + }) cfg.languagePacks); + }; + + home.packages = lib.optional (cfg.finalPackage != null) cfg.finalPackage; + + home.file = mkMerge ([{ + "${cfg.configPath}/profiles.ini" = + mkIf (cfg.profiles != { }) { text = profilesIni; }; + }] ++ optional (cfg.vendorPath != null) { + "${nativeMessagingHostsPath}" = { + source = + "${nativeMessagingHostsJoined}/lib/mozilla/native-messaging-hosts"; + recursive = true; + }; + } ++ flip mapAttrsToList cfg.profiles (_: profile: { + "${profilesPath}/${profile.path}/.keep".text = ""; + + "${profilesPath}/${profile.path}/chrome/userChrome.css" = + mkIf (profile.userChrome != "") { text = profile.userChrome; }; + + "${profilesPath}/${profile.path}/chrome/userContent.css" = + mkIf (profile.userContent != "") { text = profile.userContent; }; + + "${profilesPath}/${profile.path}/user.js" = mkIf (profile.settings != { } + || profile.extraConfig != "" || profile.bookmarks != [ ]) { + text = + mkUserJs profile.settings profile.extraConfig profile.bookmarks; + }; + + "${profilesPath}/${profile.path}/containers.json" = + mkIf (profile.containers != { }) { + text = mkContainersJson profile.containers; + force = profile.containersForce; + }; + + "${profilesPath}/${profile.path}/search.json.mozlz4" = mkIf + (profile.search.default != null || profile.search.privateDefault != null + || profile.search.order != [ ] || profile.search.engines != { }) { + force = profile.search.force; + source = let + settings = { + version = 6; + engines = let + # Map of nice field names to internal field names. + # This is intended to be exhaustive and should be + # updated at every version bump. + internalFieldNames = (genAttrs [ + "name" + "isAppProvided" + "loadPath" + "hasPreferredIcon" + "updateInterval" + "updateURL" + "iconUpdateURL" + "iconURL" + "iconMapObj" + "metaData" + "orderHint" + "definedAliases" + "urls" + ] (name: "_${name}")) // { + searchForm = "__searchForm"; + }; + + processCustomEngineInput = input: + (removeAttrs input [ "icon" ]) + // optionalAttrs (input ? icon) { + # Convenience to specify absolute path to icon + iconURL = "file://${input.icon}"; + } // (optionalAttrs (input ? iconUpdateURL) { + # Convenience to default iconURL to iconUpdateURL so + # the icon is immediately downloaded from the URL + iconURL = input.iconURL or input.iconUpdateURL; + } // { + # Required for custom engine configurations, loadPaths + # are unique identifiers that are generally formatted + # like: [source]/path/to/engine.xml + loadPath = '' + [home-manager]/${moduleName}.profiles.${profile.name}.search.engines."${ + replaceStrings [ "\\" ] [ "\\\\" ] input.name + }"''; + }); + + processEngineInput = name: input: + let + requiredInput = { + inherit name; + isAppProvided = input.isAppProvided or removeAttrs input + [ "metaData" ] == { }; + metaData = input.metaData or { }; + }; + in if requiredInput.isAppProvided then + requiredInput + else + processCustomEngineInput (input // requiredInput); + + buildEngineConfig = name: input: + mapAttrs' (name: value: { + name = internalFieldNames.${name} or name; + inherit value; + }) (processEngineInput name input); + + sortEngineConfigs = configs: + let + buildEngineConfigWithOrder = order: name: + let + config = configs.${name} or { + _name = name; + _isAppProvided = true; + _metaData = { }; + }; + in config // { + _metaData = config._metaData // { inherit order; }; + }; + + engineConfigsWithoutOrder = + attrValues (removeAttrs configs profile.search.order); + + sortedEngineConfigs = + (imap buildEngineConfigWithOrder profile.search.order) + ++ engineConfigsWithoutOrder; + in sortedEngineConfigs; + + engineInput = profile.search.engines // { + # Infer profile.search.default as an app provided + # engine if it's not in profile.search.engines + ${profile.search.default} = + profile.search.engines.${profile.search.default} or { }; + } // { + ${profile.search.privateDefault} = + profile.search.engines.${profile.search.privateDefault} or { }; + }; + in sortEngineConfigs (mapAttrs buildEngineConfig engineInput); + + metaData = optionalAttrs (profile.search.default != null) { + current = profile.search.default; + hash = "@hash@"; + } // optionalAttrs (profile.search.privateDefault != null) { + private = profile.search.privateDefault; + privateHash = "@privateHash@"; + } // { + useSavedOrder = profile.search.order != [ ]; + }; + }; + + # Home Manager doesn't circumvent user consent and isn't acting + # maliciously. We're modifying the search outside of the browser, but + # a claim by Mozilla to remove this would be very anti-user, and + # is unlikely to be an issue for our use case. + disclaimer = appName: + "By modifying this file, I agree that I am doing so " + + "only within ${appName} itself, using official, user-driven search " + + "engine selection processes, and in a way which does not circumvent " + + "user consent. I acknowledge that any attempt to change this file " + + "from outside of ${appName} is a malicious act, and will be responded " + + "to accordingly."; + + salt = if profile.search.default != null then + profile.path + profile.search.default + disclaimer cfg.name + else + null; + + privateSalt = if profile.search.privateDefault != null then + profile.path + profile.search.privateDefault + + disclaimer cfg.name + else + null; + in pkgs.runCommand "search.json.mozlz4" { + nativeBuildInputs = with pkgs; [ mozlz4a openssl ]; + json = builtins.toJSON settings; + inherit salt privateSalt; + } '' + if [[ -n $salt ]]; then + export hash=$(echo -n "$salt" | openssl dgst -sha256 -binary | base64) + export privateHash=$(echo -n "$privateSalt" | openssl dgst -sha256 -binary | base64) + mozlz4a <(substituteStream json search.json.in --subst-var hash --subst-var privateHash) "$out" + else + mozlz4a <(echo "$json") "$out" + fi + ''; + }; + + "${profilesPath}/${profile.path}/extensions" = + mkIf (profile.extensions != [ ]) { + source = let + extensionsEnvPkg = pkgs.buildEnv { + name = "hm-firefox-extensions"; + paths = profile.extensions; + }; + in "${extensionsEnvPkg}/share/mozilla/${extensionPath}"; + recursive = true; + force = true; + }; + })); + } // setAttrByPath modulePath { finalPackage = wrapPackage cfg.package; }); +} + diff --git a/tests/default.nix b/tests/default.nix index 28ce4f64..1c143716 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -189,7 +189,7 @@ in import nmtSrc { ./modules/programs/bemenu ./modules/programs/borgmatic ./modules/programs/boxxy - ./modules/programs/firefox + ./modules/programs/firefox/firefox.nix ./modules/programs/foot ./modules/programs/freetube ./modules/programs/fuzzel diff --git a/tests/modules/programs/firefox/container-id-out-of-range.nix b/tests/modules/programs/firefox/container-id-out-of-range.nix index 5dbeaedd..2ea08e88 100644 --- a/tests/modules/programs/firefox/container-id-out-of-range.nix +++ b/tests/modules/programs/firefox/container-id-out-of-range.nix @@ -1,27 +1,32 @@ +modulePath: { config, lib, ... }: -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +with lib; - config = lib.mkIf config.test.enableBig { +let + + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig ({ test.asserts.assertions.expected = [ "Container id must be smaller than 4294967294 (2^32 - 2)" ]; + } // setAttrByPath modulePath { + enable = true; - programs.firefox = { - enable = true; + profiles.my-profile = { + isDefault = true; + id = 1; - profiles.my-profile = { - isDefault = true; - id = 1; - - containers = { - "shopping" = { - id = 4294967294; - color = "blue"; - icon = "circle"; - }; + containers = { + "shopping" = { + id = 4294967294; + color = "blue"; + icon = "circle"; }; }; }; - }; + }); } diff --git a/tests/modules/programs/firefox/default.nix b/tests/modules/programs/firefox/default.nix deleted file mode 100644 index 1cd462f2..00000000 --- a/tests/modules/programs/firefox/default.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - firefox-profile-settings = ./profile-settings.nix; - firefox-state-version-19_09 = ./state-version-19_09.nix; - firefox-deprecated-native-messenger = ./deprecated-native-messenger.nix; - firefox-duplicate-profile-ids = ./duplicate-profile-ids.nix; - firefox-duplicate-container-ids = ./duplicate-container-ids.nix; - firefox-container-id-out-of-range = ./container-id-out-of-range.nix; - firefox-policies = ./policies.nix; -} diff --git a/tests/modules/programs/firefox/deprecated-native-messenger.nix b/tests/modules/programs/firefox/deprecated-native-messenger.nix index db70d405..87423aba 100644 --- a/tests/modules/programs/firefox/deprecated-native-messenger.nix +++ b/tests/modules/programs/firefox/deprecated-native-messenger.nix @@ -1,21 +1,26 @@ -{ config, lib, pkgs, ... }: +modulePath: +{ config, lib, ... }: with lib; -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +let - config = lib.mkIf config.test.enableBig { - programs.firefox = { - enable = true; - enableGnomeExtensions = true; - }; + moduleName = concatStringsSep "." modulePath; + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig (setAttrByPath modulePath { + enable = true; + enableGnomeExtensions = true; + } // { test.asserts.warnings.expected = ['' - Using 'programs.firefox.enableGnomeExtensions' has been deprecated and + Using '${moduleName}.enableGnomeExtensions' has been deprecated and will be removed in the future. Please change to overriding the package - configuration using 'programs.firefox.package' instead. You can refer to + configuration using '${moduleName}.package' instead. You can refer to its example for how to do this. '']; - }; + }); } diff --git a/tests/modules/programs/firefox/duplicate-container-ids.nix b/tests/modules/programs/firefox/duplicate-container-ids.nix index fce91fa0..2ad99b4b 100644 --- a/tests/modules/programs/firefox/duplicate-container-ids.nix +++ b/tests/modules/programs/firefox/duplicate-container-ids.nix @@ -1,35 +1,42 @@ +modulePath: { config, lib, ... }: -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +with lib; - config = lib.mkIf config.test.enableBig { +let + + cfg = getAttrFromPath modulePath config; + + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig ({ test.asserts.assertions.expected = ['' - Must not have a Firefox container with an existing ID but + Must not have a ${cfg.name} container with an existing ID but - ID 9 is used by dangerous, shopping'']; + } // setAttrByPath modulePath { + enable = true; - programs.firefox = { - enable = true; + profiles = { + my-profile = { + isDefault = true; + id = 1; - profiles = { - my-profile = { - isDefault = true; - id = 1; - - containers = { - "shopping" = { - id = 9; - color = "blue"; - icon = "circle"; - }; - "dangerous" = { - id = 9; - color = "red"; - icon = "circle"; - }; + containers = { + "shopping" = { + id = 9; + color = "blue"; + icon = "circle"; + }; + "dangerous" = { + id = 9; + color = "red"; + icon = "circle"; }; }; }; }; - }; + }); } diff --git a/tests/modules/programs/firefox/duplicate-profile-ids.nix b/tests/modules/programs/firefox/duplicate-profile-ids.nix index 9b0b7c06..ad946af1 100644 --- a/tests/modules/programs/firefox/duplicate-profile-ids.nix +++ b/tests/modules/programs/firefox/duplicate-profile-ids.nix @@ -1,23 +1,30 @@ +modulePath: { config, lib, ... }: -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +with lib; - config = lib.mkIf config.test.enableBig { +let + + cfg = getAttrFromPath modulePath config; + + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig ({ test.asserts.assertions.expected = ['' - Must not have a Firefox profile with an existing ID but + Must not have a ${cfg.name} profile with an existing ID but - ID 1 is used by first, second'']; + } // setAttrByPath modulePath { + enable = true; - programs.firefox = { - enable = true; - - profiles = { - first = { - isDefault = true; - id = 1; - }; - second = { id = 1; }; + profiles = { + first = { + isDefault = true; + id = 1; }; + second = { id = 1; }; }; - }; + }); } diff --git a/tests/modules/programs/firefox/firefox.nix b/tests/modules/programs/firefox/firefox.nix new file mode 100644 index 00000000..6598d6ec --- /dev/null +++ b/tests/modules/programs/firefox/firefox.nix @@ -0,0 +1,11 @@ +let name = "firefox"; + +in builtins.mapAttrs (test: module: import module [ "programs" name ]) { + "${name}-profile-settings" = ./profile-settings.nix; + "${name}-state-version-19_09" = ./state-version-19_09.nix; + "${name}-deprecated-native-messenger" = ./deprecated-native-messenger.nix; + "${name}-duplicate-profile-ids" = ./duplicate-profile-ids.nix; + "${name}-duplicate-container-ids" = ./duplicate-container-ids.nix; + "${name}-container-id-out-of-range" = ./container-id-out-of-range.nix; + "${name}-policies" = ./policies.nix; +} diff --git a/tests/modules/programs/firefox/policies.nix b/tests/modules/programs/firefox/policies.nix index 7b503d3d..5e02a191 100644 --- a/tests/modules/programs/firefox/policies.nix +++ b/tests/modules/programs/firefox/policies.nix @@ -1,22 +1,29 @@ +modulePath: { config, lib, pkgs, ... }: -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +with lib; - config = lib.mkIf config.test.enableBig { +let + + cfg = getAttrFromPath modulePath config; + + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig ({ home.stateVersion = "23.05"; - - programs.firefox = { - enable = true; - policies = { BlockAboutConfig = true; }; - package = pkgs.firefox.override { - extraPolicies = { DownloadDirectory = "/foo"; }; - }; + } // setAttrByPath modulePath { + enable = true; + policies = { BlockAboutConfig = true; }; + package = pkgs.${cfg.wrappedPackageName}.override { + extraPolicies = { DownloadDirectory = "/foo"; }; }; - + }) // { nmt.script = '' jq=${lib.getExe pkgs.jq} - config_file="${config.programs.firefox.finalPackage}/lib/firefox/distribution/policies.json" + config_file="${cfg.finalPackage}/lib/${cfg.wrappedPackageName}/distribution/policies.json" assertFileExists "$config_file" diff --git a/tests/modules/programs/firefox/profile-settings.nix b/tests/modules/programs/firefox/profile-settings.nix index 8b781552..d7776eb4 100644 --- a/tests/modules/programs/firefox/profile-settings.nix +++ b/tests/modules/programs/firefox/profile-settings.nix @@ -1,177 +1,184 @@ +modulePath: { config, lib, pkgs, ... }: -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +with lib; - config = lib.mkIf config.test.enableBig { - programs.firefox = { - enable = true; - profiles.basic.isDefault = true; +let - profiles.test = { - id = 1; - settings = { - "general.smoothScroll" = false; - "browser.newtabpage.pinned" = [{ - title = "NixOS"; - url = "https://nixos.org"; + cfg = getAttrFromPath modulePath config; + + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = mkIf config.test.enableBig (setAttrByPath modulePath { + enable = true; + profiles.basic.isDefault = true; + + profiles.test = { + id = 1; + settings = { + "general.smoothScroll" = false; + "browser.newtabpage.pinned" = [{ + title = "NixOS"; + url = "https://nixos.org"; + }]; + }; + }; + + profiles.bookmarks = { + id = 2; + settings = { "general.smoothScroll" = false; }; + bookmarks = [ + { + toolbar = true; + bookmarks = [{ + name = "Home Manager"; + url = "https://wiki.nixos.org/wiki/Home_Manager"; }]; - }; - }; + } + { + name = "wikipedia"; + tags = [ "wiki" ]; + keyword = "wiki"; + url = "https://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go"; + } + { + name = "kernel.org"; + url = "https://www.kernel.org"; + } + { + name = "Nix sites"; + bookmarks = [ + { + name = "homepage"; + url = "https://nixos.org/"; + } + { + name = "wiki"; + tags = [ "wiki" "nix" ]; + url = "https://wiki.nixos.org/"; + } + { + name = "Nix sites"; + bookmarks = [ + { + name = "homepage"; + url = "https://nixos.org/"; + } + { + name = "wiki"; + url = "https://wiki.nixos.org/"; + } + ]; + } + ]; + } + ]; + }; - profiles.bookmarks = { - id = 2; - settings = { "general.smoothScroll" = false; }; - bookmarks = [ - { - toolbar = true; - bookmarks = [{ - name = "Home Manager"; - url = "https://wiki.nixos.org/wiki/Home_Manager"; + profiles.search = { + id = 3; + search = { + force = true; + default = "Google"; + privateDefault = "DuckDuckGo"; + order = [ "Nix Packages" "NixOS Wiki" ]; + engines = { + "Nix Packages" = { + urls = [{ + template = "https://search.nixos.org/packages"; + params = [ + { + name = "type"; + value = "packages"; + } + { + name = "query"; + value = "{searchTerms}"; + } + ]; }]; - } - { - name = "wikipedia"; - tags = [ "wiki" ]; - keyword = "wiki"; - url = - "https://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go"; - } - { - name = "kernel.org"; - url = "https://www.kernel.org"; - } - { - name = "Nix sites"; - bookmarks = [ - { - name = "homepage"; - url = "https://nixos.org/"; - } - { - name = "wiki"; - tags = [ "wiki" "nix" ]; - url = "https://wiki.nixos.org/"; - } - { - name = "Nix sites"; - bookmarks = [ - { - name = "homepage"; - url = "https://nixos.org/"; - } - { - name = "wiki"; - url = "https://wiki.nixos.org/"; - } - ]; - } - ]; - } - ]; - }; - profiles.search = { - id = 3; - search = { - force = true; - default = "Google"; - privateDefault = "DuckDuckGo"; - order = [ "Nix Packages" "NixOS Wiki" ]; - engines = { - "Nix Packages" = { - urls = [{ - template = "https://search.nixos.org/packages"; - params = [ - { - name = "type"; - value = "packages"; - } - { - name = "query"; - value = "{searchTerms}"; - } - ]; - }]; + icon = + "/run/current-system/sw/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; - icon = - "/run/current-system/sw/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; - - definedAliases = [ "@np" ]; - }; - - "NixOS Wiki" = { - urls = [{ - template = - "https://wiki.nixos.org/index.php?search={searchTerms}"; - }]; - iconUpdateURL = "https://wiki.nixos.org/favicon.png"; - updateInterval = 24 * 60 * 60 * 1000; - definedAliases = [ "@nw" ]; - }; - - "Bing".metaData.hidden = true; - "Google".metaData.alias = "@g"; + definedAliases = [ "@np" ]; }; + + "NixOS Wiki" = { + urls = [{ + template = + "https://wiki.nixos.org/index.php?search={searchTerms}"; + }]; + iconUpdateURL = "https://wiki.nixos.org/favicon.png"; + updateInterval = 24 * 60 * 60 * 1000; + definedAliases = [ "@nw" ]; + }; + + "Bing".metaData.hidden = true; + "Google".metaData.alias = "@g"; }; }; + }; - profiles.searchWithoutDefault = { - id = 4; - search = { - force = true; - order = [ "Google" "Nix Packages" ]; - engines = { - "Nix Packages" = { - urls = [{ - template = "https://search.nixos.org/packages"; - params = [ - { - name = "type"; - value = "packages"; - } - { - name = "query"; - value = "{searchTerms}"; - } - ]; - }]; + profiles.searchWithoutDefault = { + id = 4; + search = { + force = true; + order = [ "Google" "Nix Packages" ]; + engines = { + "Nix Packages" = { + urls = [{ + template = "https://search.nixos.org/packages"; + params = [ + { + name = "type"; + value = "packages"; + } + { + name = "query"; + value = "{searchTerms}"; + } + ]; + }]; - definedAliases = [ "@np" ]; - }; - }; - }; - }; - - profiles.containers = { - id = 5; - containers = { - "shopping" = { - id = 6; - icon = "circle"; - color = "yellow"; + definedAliases = [ "@np" ]; }; }; }; }; + profiles.containers = { + id = 5; + containers = { + "shopping" = { + id = 6; + icon = "circle"; + color = "yellow"; + }; + }; + }; + } // { + nmt.script = '' assertFileRegex \ - home-path/bin/firefox \ + home-path/bin/${cfg.wrappedPackageName} \ MOZ_APP_LAUNCHER - assertDirectoryExists home-files/.mozilla/firefox/basic + assertDirectoryExists home-files/${cfg.configPath}/basic assertFileContent \ - home-files/.mozilla/firefox/test/user.js \ + home-files/${cfg.configPath}/test/user.js \ ${./profile-settings-expected-user.js} assertFileContent \ - home-files/.mozilla/firefox/containers/containers.json \ + home-files/${cfg.configPath}/containers/containers.json \ ${./profile-settings-expected-containers.json} bookmarksUserJs=$(normalizeStorePaths \ - home-files/.mozilla/firefox/bookmarks/user.js) + home-files/${cfg.configPath}/bookmarks/user.js) assertFileContent \ $bookmarksUserJs \ @@ -179,7 +186,7 @@ bookmarksFile="$(sed -n \ '/browser.bookmarks.file/ {s|^.*\(/nix/store[^"]*\).*|\1|;p}' \ - $TESTED/home-files/.mozilla/firefox/bookmarks/user.js)" + $TESTED/home-files/${cfg.configPath}/bookmarks/user.js)" assertFileContent \ $bookmarksFile \ @@ -197,12 +204,12 @@ } assertFirefoxSearchContent \ - home-files/.mozilla/firefox/search/search.json.mozlz4 \ + home-files/${cfg.configPath}/search/search.json.mozlz4 \ ${./profile-settings-expected-search.json} assertFirefoxSearchContent \ - home-files/.mozilla/firefox/searchWithoutDefault/search.json.mozlz4 \ + home-files/${cfg.configPath}/searchWithoutDefault/search.json.mozlz4 \ ${./profile-settings-expected-search-without-default.json} ''; - }; + }); } diff --git a/tests/modules/programs/firefox/setup-firefox-mock-overlay.nix b/tests/modules/programs/firefox/setup-firefox-mock-overlay.nix index 0f0d182a..ecbd492f 100644 --- a/tests/modules/programs/firefox/setup-firefox-mock-overlay.nix +++ b/tests/modules/programs/firefox/setup-firefox-mock-overlay.nix @@ -1,16 +1,24 @@ -{ pkgs, ... }: +modulePath: +{ config, lib, pkgs, ... }: -{ +with lib; + +let + + cfg = getAttrFromPath modulePath config; + +in { nixpkgs.overlays = [ (self: super: { - firefox-unwrapped = pkgs.runCommandLocal "firefox-0" { - meta.description = "I pretend to be Firefox"; - passthru.gtk3 = null; - } '' - mkdir -p "$out"/{bin,lib} - touch "$out/bin/firefox" - chmod 755 "$out/bin/firefox" - ''; + "${cfg.wrappedPackageName}-unwrapped" = + pkgs.runCommandLocal "${cfg.wrappedPackageName}-0" { + meta.description = "I pretend to be ${cfg.name}"; + passthru.gtk3 = null; + } '' + mkdir -p "$out"/{bin,lib} + touch "$out/bin/${cfg.wrappedPackageName}" + chmod 755 "$out/bin/${cfg.wrappedPackageName}" + ''; chrome-gnome-shell = pkgs.runCommandLocal "dummy-chrome-gnome-shell" { } '' diff --git a/tests/modules/programs/firefox/state-version-19_09.nix b/tests/modules/programs/firefox/state-version-19_09.nix index c4c75970..475705c3 100644 --- a/tests/modules/programs/firefox/state-version-19_09.nix +++ b/tests/modules/programs/firefox/state-version-19_09.nix @@ -1,19 +1,24 @@ +modulePath: { config, lib, pkgs, ... }: with lib; -{ - imports = [ ./setup-firefox-mock-overlay.nix ]; +let - config = lib.mkIf config.test.enableBig { + cfg = getAttrFromPath modulePath config; + + firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath; + +in { + imports = [ firefoxMockOverlay ]; + + config = lib.mkIf config.test.enableBig ({ home.stateVersion = "19.09"; - - programs.firefox.enable = true; - + } // setAttrByPath modulePath { enable = true; } // { nmt.script = '' assertFileRegex \ - home-path/bin/firefox \ + home-path/bin/${cfg.wrappedPackageName} \ MOZ_APP_LAUNCHER ''; - }; + }); } From 89670e27e101b9b30f5900fc1eb6530258d316b1 Mon Sep 17 00:00:00 2001 From: Gaurav Juvekar Date: Sun, 28 Jul 2024 20:42:18 -0700 Subject: [PATCH 14/17] home-manager: ignore hostname -f lookup errors `hostname -f` could fail depending on the resolver. Discard any stderr and test for the exit status before using the value for flake attribute lookup. I was unable to repro the exact bad exit status in #5665. With - nscd disabled, - nsswitch.conf pointing to 'files', - hostname entry removed from /etc/hosts `hostname -f` from inetutils-2.5 fell back to showing just the nodename from `uname(2)`. Injecting an empty string into the `(struct utsname).nodename` field of `uname(2)` using strace still exited with empty output and 0 exit-status. Fixes #5665 --- home-manager/home-manager | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/home-manager/home-manager b/home-manager/home-manager index 5bf796b4..ab4de538 100644 --- a/home-manager/home-manager +++ b/home-manager/home-manager @@ -198,9 +198,19 @@ function setFlakeAttribute() { ;; *) local name="$USER" + + local hostnameArray=() + # FQDN lookup can fail depending on the resolver. + local fqdn + fqdn="$(hostname -f 2> /dev/null)" + if [[ $? -eq 0 ]]; then + hostnameArray+=( "$USER@$fqdn" ) + fi # Check FQDN, long, and short hostnames; long first to preserve # pre-existing behaviour in case both happen to be defined. - for n in "$USER@$(hostname -f)" "$USER@$(hostname)" "$USER@$(hostname -s)"; do + hostnameArray+=( "$USER@$(hostname)" "$USER@$(hostname -s)" ) + + for n in "${hostnameArray[@]}"; do if [[ "$(nix eval "$flake#homeConfigurations" --apply "x: x ? \"$n\"")" == "true" ]]; then name="$n" if [[ -v VERBOSE ]]; then From db40fead89db185dfd863aed5dad1d675f82a3fc Mon Sep 17 00:00:00 2001 From: Ninja3047 <1284324+Ninja3047@users.noreply.github.com> Date: Mon, 22 Jul 2024 21:51:02 -0400 Subject: [PATCH 15/17] nix-gc: call nix-collect-garbage in a shell script This will match the behavior in the upstream service which allows the user to set options to something that uses shell syntax. --- modules/services/nix-gc.nix | 5 +++-- tests/modules/services/nix-gc/basic.nix | 2 +- tests/modules/services/nix-gc/expected.service | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/services/nix-gc.nix b/modules/services/nix-gc.nix index 652c575a..0f477204 100644 --- a/modules/services/nix-gc.nix +++ b/modules/services/nix-gc.nix @@ -110,9 +110,10 @@ in { systemd.user.services.nix-gc = { Unit = { Description = "Nix Garbage Collector"; }; Service = { - ExecStart = "${nixPackage}/bin/nix-collect-garbage ${ + ExecStart = toString (pkgs.writeShellScript "nix-gc" '' + exec "${nixPackage}/bin/nix-collect-garbage ${ lib.optionalString (cfg.options != null) cfg.options - }"; + }"''); }; }; systemd.user.timers.nix-gc = { diff --git a/tests/modules/services/nix-gc/basic.nix b/tests/modules/services/nix-gc/basic.nix index d6511a17..507c9598 100644 --- a/tests/modules/services/nix-gc/basic.nix +++ b/tests/modules/services/nix-gc/basic.nix @@ -4,7 +4,7 @@ nix.gc = { automatic = true; frequency = "monthly"; - options = "--delete-older-than 30d"; + options = "--delete-older-than 30d --max-freed $((64 * 1024**3))"; }; test.stubs.nix = { name = "nix"; }; diff --git a/tests/modules/services/nix-gc/expected.service b/tests/modules/services/nix-gc/expected.service index 4aafd6af..73c0355c 100644 --- a/tests/modules/services/nix-gc/expected.service +++ b/tests/modules/services/nix-gc/expected.service @@ -1,5 +1,5 @@ [Service] -ExecStart=@nix@/bin/nix-collect-garbage --delete-older-than 30d +ExecStart=/nix/store/00000000000000000000000000000000-nix-gc [Unit] Description=Nix Garbage Collector From d34aaf7b3b4c98f2aefe0429b8946f2bcd36a59a Mon Sep 17 00:00:00 2001 From: Ninja3047 <1284324+Ninja3047@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:45:50 -0400 Subject: [PATCH 16/17] nix-gc: set service type to oneshot --- modules/services/nix-gc.nix | 1 + tests/modules/services/nix-gc/expected.service | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/services/nix-gc.nix b/modules/services/nix-gc.nix index 0f477204..fe7e4669 100644 --- a/modules/services/nix-gc.nix +++ b/modules/services/nix-gc.nix @@ -110,6 +110,7 @@ in { systemd.user.services.nix-gc = { Unit = { Description = "Nix Garbage Collector"; }; Service = { + Type = "oneshot"; ExecStart = toString (pkgs.writeShellScript "nix-gc" '' exec "${nixPackage}/bin/nix-collect-garbage ${ lib.optionalString (cfg.options != null) cfg.options diff --git a/tests/modules/services/nix-gc/expected.service b/tests/modules/services/nix-gc/expected.service index 73c0355c..47bca989 100644 --- a/tests/modules/services/nix-gc/expected.service +++ b/tests/modules/services/nix-gc/expected.service @@ -1,5 +1,6 @@ [Service] ExecStart=/nix/store/00000000000000000000000000000000-nix-gc +Type=oneshot [Unit] Description=Nix Garbage Collector From 4fcd54df7cbb1d79cbe81209909ee8514d6b17a4 Mon Sep 17 00:00:00 2001 From: Jakub Nowak Date: Tue, 30 Jul 2024 08:33:10 +0200 Subject: [PATCH 17/17] firefox: fix userChrome example Example CSS wasn't valid for Firefox 69+. --- modules/programs/firefox/mkFirefoxModule.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/programs/firefox/mkFirefoxModule.nix b/modules/programs/firefox/mkFirefoxModule.nix index 32ab4b47..f22af019 100644 --- a/modules/programs/firefox/mkFirefoxModule.nix +++ b/modules/programs/firefox/mkFirefoxModule.nix @@ -402,7 +402,7 @@ in { description = "Custom ${name} user chrome CSS."; example = '' /* Hide tab bar in FF Quantum */ - @-moz-document url("chrome://browser/content/browser.xul") { + @-moz-document url(chrome://browser/content/browser.xul), url(chrome://browser/content/browser.xhtml) { #TabsToolbar { visibility: collapse !important; margin-bottom: 21px !important;