diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index f6287a69..514ba1f3 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -37,7 +37,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs id: backport - uses: korthout/backport-action@v3 + uses: korthout/backport-action@v4 with: # See https://github.com/korthout/backport-action#inputs github_token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} diff --git a/all-maintainers.nix b/all-maintainers.nix index 10ddd58e..7bfbdbaa 100644 --- a/all-maintainers.nix +++ b/all-maintainers.nix @@ -361,6 +361,11 @@ email = "nixpkgs@perchun.it"; github = "PerchunPak"; githubId = 68118654; + keys = [ + { + fingerprint = "BBB5 1142 959D 8549 A3D2 F6C5 313F 67D1 EAB7 70F9"; + } + ]; name = "Perchun Pak"; source = "nixpkgs"; }; @@ -2073,7 +2078,7 @@ source = "nixpkgs"; }; shikanime = { - email = "deva.shikanime@protonmail.com"; + email = "william.phetsinorath@shikanime.studio"; github = "shikanime"; githubId = 22115108; name = "William Phetsinorath"; @@ -2311,6 +2316,13 @@ name = "Florian Peter"; source = "nixpkgs"; }; + xavwe = { + email = "git@xavwe.dev"; + github = "xavwe"; + githubId = 125409009; + name = "Xaver Wenhart"; + source = "nixpkgs"; + }; xlambein = { email = "xlambein@gmail.com"; github = "xlambein"; diff --git a/docs/release-notes/rl-2305.md b/docs/release-notes/rl-2305.md index 1358cf71..bdd99100 100644 --- a/docs/release-notes/rl-2305.md +++ b/docs/release-notes/rl-2305.md @@ -57,3 +57,9 @@ changes are only active if the `home.stateVersion` option is set to now default to `true` which is consistent with the default values for those options used by `i3` and `sway`. + +- The [](#opt-programs.swaylock.enable) option now defaults to `false` + and must be explicitly enabled. Previously, it would be implicitly + enabled when `programs.swaylock.settings` was non-empty. Users with + `home.stateVersion` set to earlier versions will continue to get the + old implicit behavior. diff --git a/docs/release-notes/rl-2505.md b/docs/release-notes/rl-2505.md index 977e853c..2ec0f1ad 100644 --- a/docs/release-notes/rl-2505.md +++ b/docs/release-notes/rl-2505.md @@ -27,4 +27,8 @@ The state version in this release includes the changes below. These changes are only active if the `home.stateVersion` option is set to \"25.05\" or later. -- No changes. +- The [](#opt-programs.git.signing.format) option no longer defaults to + `"openpgp"`. Users who use Git signing with GPG should explicitly set + this option to `"openpgp"` to maintain the previous behavior. Users + with `home.stateVersion` set to earlier versions will continue to get + the `"openpgp"` default for backwards compatibility. diff --git a/docs/release-notes/rl-2511.md b/docs/release-notes/rl-2511.md index 8f0c666f..83f4b396 100644 --- a/docs/release-notes/rl-2511.md +++ b/docs/release-notes/rl-2511.md @@ -80,3 +80,10 @@ changes are only active if the `home.stateVersion` option is set to `{ PASSWORD_STORE_DIR = $XDG_DATA_HOME/password-store; }` anymore by its default value. This will revert to the default behaviour of the program, namely `$HOME/.password-store` to be used as the store path. + +- On macOS, [](#opt-targets.darwin.copyApps.enable) is now enabled by + default instead of [](#opt-targets.darwin.linkApps.enable). This means + applications from `home.packages` will be copied to + `~/Applications/Home Manager Apps` rather than symlinked, making them + work properly with Spotlight. Users with `home.stateVersion` set to + earlier versions will continue to use `linkApps` by default. diff --git a/flake.lock b/flake.lock index 84ba9850..767b497b 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1764242076, - "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", + "lastModified": 1766309749, + "narHash": "sha256-3xY8CZ4rSnQ0NqGhMKAy5vgC+2IVK0NoVEzDoOh4DA4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", + "rev": "a6531044f6d0bef691ea18d4d4ce44d0daa6e816", "type": "github" }, "original": { diff --git a/home-manager/po/zh_Hant.po b/home-manager/po/zh_Hant.po index c2fb789b..2bf6463c 100644 --- a/home-manager/po/zh_Hant.po +++ b/home-manager/po/zh_Hant.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Home Manager\n" "Report-Msgid-Bugs-To: https://github.com/nix-community/home-manager/issues\n" "POT-Creation-Date: 2025-07-22 10:59+0200\n" -"PO-Revision-Date: 2025-03-07 18:58+0000\n" -"Last-Translator: 807 \n" +"PO-Revision-Date: 2025-12-04 04:17+0000\n" +"Last-Translator: \"Urocissa Caerulea.Tw\" \n" "Language-Team: Chinese (Traditional Han script) \n" "Language: zh_Hant\n" @@ -17,21 +17,21 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.10.3-dev\n" +"X-Generator: Weblate 5.15-dev\n" #. translators: For example: "home-manager: missing argument for --cores" #: home-manager/home-manager:16 msgid "%s: missing argument for %s" -msgstr "%s: 缺少參數 %s" +msgstr "%s:缺少 %s 的引數" #. translators: For example: "home-manager: --rollback can only be used after switch" #: home-manager/home-manager:22 msgid "%s: %s can only be used after %s" -msgstr "" +msgstr "%s:%s 只能在 %s 之後使用" #: home-manager/home-manager:71 msgid "No configuration file found at %s" -msgstr "未在 %s 處找到配置檔案" +msgstr "在 %s 找不到設定檔" #. translators: The first '%s' specifier will be replaced by either #. 'home.nix' or 'flake.nix'. @@ -41,12 +41,12 @@ msgid "" "Keeping your Home Manager %s in %s is deprecated,\n" "please move it to %s" msgstr "" -"保持你的 Home Manager 在 %s 中,%s 已被拋棄,\n" -"請將它移動到 %s" +"將 Home Manager 的 %s 放在 %s 中, 已經被棄用,\n" +"請改放到 %s" #: home-manager/home-manager:99 msgid "No configuration file found. Please create one at %s" -msgstr "未找到配置檔案。請在 %s 處建立一份" +msgstr "找不到設定檔。請在 %s 建立新的設定檔" #: home-manager/home-manager:114 msgid "Home Manager not found at %s." @@ -57,7 +57,7 @@ msgstr "在 %s 中找不到 Home Manager。" msgid "" "The fallback Home Manager path %s has been deprecated and a file/directory " "was found there." -msgstr "備用的 Home Manager 路徑 %s 已被拋棄,但一個檔案/資料夾在這被找到。" +msgstr "備用的 Home Manager 路徑 %s 已被棄用,且在該處找到了檔案/目錄。" #. translators: This message will be seen by very few users that likely are familiar with English. So feel free to leave this untranslated. #: home-manager/home-manager:125 @@ -80,21 +80,21 @@ msgid "" "\n" " $ rm -r \"%s\"" msgstr "" -"要消除這個警告,請做以下其中一步。\n" +"若要移除此警告,請執行下列其中一項。\n" "\n" -"1. 告訴Home Manager去使用路徑,例如加入\n" +"1. 明確告知 Home Manager 使用該路徑,例如在\n" "\n" " { programs.home-manager.path = \"%s\"; }\n" "\n" -" 到你的配置中。\n" +" 中,加入您的設定。\n" "\n" -" 如果你想要直接引入Home Manager, 請你使用 `path` 參數r\n" +" 如果想直接匯入 Home Manager,可以在呼叫時使用 `path` 參數來指定路徑:\n" "\n" " pkgs.callPackage /path/to/home-manager-package { path = \"%s\"; }\n" "\n" -" 當呼叫 Home Manager 模組。\n" +" 這樣就能正確傳遞 Home Manager 的路徑。\n" "\n" -"2. 刪除無效的路徑\n" +"2. 移除已棄用的路徑。\n" "\n" " $ rm -r \"%s\"" @@ -104,33 +104,33 @@ msgstr "正在進行 Nix 完整性檢查" #: home-manager/home-manager:173 msgid "Could not find suitable profile directory, tried %s and %s" -msgstr "找不到合適的 profile 目錄,已經嘗試 %s 和 %s" +msgstr "找不到合適的設定檔目錄,已嘗試 %s 和 %s" #. translators: Here "flake" is a noun that refers to the Nix Flakes feature. #: home-manager/home-manager:230 msgid "Can't inspect options of a flake configuration" -msgstr "無法檢查 flake 配置中的選項" +msgstr "無法檢查 flake 設定的選項" #: home-manager/home-manager:305 home-manager/home-manager:328 #: home-manager/home-manager:734 home-manager/home-manager:1237 msgid "%s: unknown option '%s'" -msgstr "%s:未知選項 ‘%s’" +msgstr "%s:未知選項 '%s'" #: home-manager/home-manager:310 home-manager/home-manager:1238 msgid "Run '%s --help' for usage help" -msgstr "執行 ‘%s --help’ 獲取用法幫助" +msgstr "執行 '%s --help' 以取得使用說明" #: home-manager/home-manager:336 home-manager/home-manager:441 msgid "The file %s already exists, leaving it unchanged..." -msgstr "檔案 %s 已經存在,不更改它..." +msgstr "檔案 %s 已存在,保持不變..." #: home-manager/home-manager:338 home-manager/home-manager:443 msgid "Creating %s..." -msgstr "創建 %s 中..." +msgstr "正在建立 %s..." #: home-manager/home-manager:487 msgid "Creating initial Home Manager generation..." -msgstr "正在建立初始 Home Manager 世代 ..." +msgstr "正在建立初始 Home Manager 世代..." #. translators: The "%s" specifier will be replaced by a file path. #: home-manager/home-manager:492 @@ -142,12 +142,12 @@ msgid "" "to configure Home Manager. Run 'man home-configuration.nix' to\n" "see all available options." msgstr "" -"全部工作完成了!home-manager 工具現應已安裝,您可以編輯\n" +"全部完成!home-manager 工具現在應該已被安裝,您可以編輯\n" "\n" " %s\n" "\n" -"來配置 Home Manager。執行 ‘man home-configuration.nix’\n" -"來檢視所有可用選項。" +"來設定 Home Manager。執行 'man home-configuration.nix' 時\n" +"可查看所有可用選項。" #. translators: The "%s" specifier will be replaced by a URL. #: home-manager/home-manager:497 @@ -158,16 +158,16 @@ msgid "" "\n" "if the error seems to be the fault of Home Manager." msgstr "" -"啊哦,安裝失敗了!如果感覺是 Home Manager 造成的錯誤,請在下方\n" +"糟糕,安裝失敗了!如果感覺是 Home Manager 所造成的錯誤,請在此連結\n" "\n" " %s\n" "\n" -"處建立 Issue 告知我們。" +"中,建立 Issue 告知我們。" #. translators: Here "flake" is a noun that refers to the Nix Flakes feature. #: home-manager/home-manager:508 msgid "Can't instantiate a flake configuration" -msgstr "無法建立 flake 配置例項" +msgstr "無法實例化 flake 設定" #: home-manager/home-manager:584 msgid "" @@ -177,12 +177,12 @@ msgid_plural "" "There are %d unread and relevant news items.\n" "Read them by running the command \"%s news\"." msgstr[0] "" -"有 %d 條未讀的相關新聞或訊息。\n" -"可執行 “%s news” 命令進行閱讀。" +"有 %d 則未讀且相關的消息項目。\n" +"執行指令 \"%s news\" 來進行確認。" #: home-manager/home-manager:598 msgid "Unknown \"news.display\" setting \"%s\"." -msgstr "未知的 “news.display” 設定項 “%s”。" +msgstr "未知的 \"news.display\" 設定值 \"%s\"。" #: home-manager/home-manager:606 #, sh-format @@ -191,11 +191,11 @@ msgstr "請設定 $EDITOR 或 $VISUAL 環境變數" #: home-manager/home-manager:624 msgid "Cannot run build in read-only directory" -msgstr "無法在唯讀目錄中執行構建" +msgstr "無法在唯讀目錄中執行建置" #: home-manager/home-manager:787 msgid "The configuration did not contain the specialisation \"%s\"" -msgstr "" +msgstr "設定中不包含特化設定 \"%s\"" #: home-manager/home-manager:841 msgid "No generation with ID %s" @@ -203,7 +203,7 @@ msgstr "沒有 ID 為 %s 的世代" #: home-manager/home-manager:843 msgid "Cannot remove the current generation %s" -msgstr "無法移除當前世代 %s" +msgstr "無法移除目前的世代 %s" #: home-manager/home-manager:845 msgid "Removing generation %s" diff --git a/modules/accounts/contacts.nix b/modules/accounts/contacts.nix index 780f6bb5..506b66a1 100644 --- a/modules/accounts/contacts.nix +++ b/modules/accounts/contacts.nix @@ -102,8 +102,8 @@ let }; local = mkOption { - type = types.nullOr (localModule name); - default = null; + type = localModule name; + default = { }; description = '' Local configuration for the contacts. ''; diff --git a/modules/default.nix b/modules/default.nix index 9bc43df1..10e6e7a3 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -57,9 +57,8 @@ let let module = moduleChecks rawModule; in - { - inherit (module) options config; - + module + // { activationPackage = module.config.home.activationPackage; # For backwards compatibility. Please use activationPackage instead. diff --git a/modules/home-environment.nix b/modules/home-environment.nix index a562047b..32c3ce6b 100644 --- a/modules/home-environment.nix +++ b/modules/home-environment.nix @@ -193,6 +193,13 @@ in description = "The user's username."; }; + home.uid = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + example = 1000; + description = "The user's uid."; + }; + home.homeDirectory = mkOption { type = types.path; defaultText = literalExpression '' @@ -570,14 +577,25 @@ in warnings = let hmRelease = config.home.version.release; - nixpkgsRelease = lib.trivial.release; - releaseMismatch = config.home.enableNixpkgsReleaseCheck && hmRelease != nixpkgsRelease; + libRelease = lib.trivial.release; + pkgsRelease = pkgs.lib.trivial.release; + releaseMismatch = hmRelease != libRelease || hmRelease != pkgsRelease; + + versionsSummary = + if libRelease == pkgsRelease then + '' + Home Manager version ${hmRelease} and + Nixpkgs version ${libRelease}.'' + else + '' + Home Manager version: ${hmRelease} + Nixpkgs version used to evaluate Home Manager: ${libRelease} + Nixpkgs version used for packages (`pkgs`): ${pkgsRelease}''; in - lib.optional releaseMismatch '' + lib.optional (config.home.enableNixpkgsReleaseCheck && releaseMismatch) '' You are using - Home Manager version ${hmRelease} and - Nixpkgs version ${nixpkgsRelease}. + ${lib.replaceString "\n" "\n " versionsSummary} Using mismatched versions is likely to cause errors and unexpected behavior. It is therefore highly recommended to use a release of Home @@ -831,6 +849,9 @@ in if [[ ! -v SKIP_SANITY_CHECKS ]]; then checkUsername ${lib.escapeShellArg config.home.username} checkHomeDirectory ${lib.escapeShellArg config.home.homeDirectory} + ${lib.optionalString (config.home.uid != null) '' + checkUid ${toString config.home.uid} + ''} fi ${lib.optionalString config.home.activationGenerateGcRoot '' diff --git a/modules/lib-bash/activation-init.sh b/modules/lib-bash/activation-init.sh index b16d8741..f8fe1103 100755 --- a/modules/lib-bash/activation-init.sh +++ b/modules/lib-bash/activation-init.sh @@ -117,6 +117,17 @@ function checkHomeDirectory() { fi } +function checkUid() { + local expectedUid="$1" + local actualUid + actualUid="$(id -u)" + + if [[ "$actualUid" != "$expectedUid" ]]; then + _iError 'Error: UID is "%s" but we expect "%s"' "$actualUid" "$expectedUid" + exit 1 + fi +} + # Note, the VERBOSE_ECHO variable is deprecated and should not be used inside # the Home Manager project. It is provided here for backwards compatibility. if [[ -v VERBOSE ]]; then diff --git a/modules/misc/news/2025/10/2025-10-15_10-44-58.nix b/modules/misc/news/2025/10/2025-10-15_10-44-58.nix new file mode 100644 index 00000000..94440572 --- /dev/null +++ b/modules/misc/news/2025/10/2025-10-15_10-44-58.nix @@ -0,0 +1,9 @@ +{ + time = "2025-10-14T23:44:58+00:00"; + condition = true; + message = '' + A new module is available: `services.colima` + + Colima is a tool for orchestrating container runtimes under a linux VM. + ''; +} diff --git a/modules/misc/news/2025/11/2025-11-27_08-22-14.nix b/modules/misc/news/2025/11/2025-11-27_08-22-14.nix new file mode 100644 index 00000000..8a10b642 --- /dev/null +++ b/modules/misc/news/2025/11/2025-11-27_08-22-14.nix @@ -0,0 +1,10 @@ +{ pkgs, ... }: +{ + time = "2025-11-27T07:22:14+00:00"; + condition = pkgs.stdenv.hostPlatform.isDarwin; + message = '' + A new module is available: 'programs.infat'. + Infat is a command line tool to set default openers + for file formats and url schemes on macOS. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-01_09-35-38.nix b/modules/misc/news/2025/12/2025-12-01_09-35-38.nix new file mode 100644 index 00000000..40899855 --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-01_09-35-38.nix @@ -0,0 +1,16 @@ +{ config, ... }: + +{ + time = "2025-12-01T12:35:38+00:00"; + condition = config.services.ludusavi.enable; + message = '' + BREAKING CHANGE: + + The `ludusavi` module has changed its default backup and restore path. + The new module implements a mechanism to automatically migrate the backups + to the new path, but if it doesn't work and you can't find your backups in + `ludusavi`, they should be in the old path: ~/\$XDG_STATE_HOME/backups/ludusavi/ + (that means a directory literally called $XDG_STATE_HOME in your home, rather than + the env var expanded). For more info, see pull #8234. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-03_10-14-53.nix b/modules/misc/news/2025/12/2025-12-03_10-14-53.nix new file mode 100644 index 00000000..7d3119ac --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-03_10-14-53.nix @@ -0,0 +1,12 @@ +{ + time = "2025-12-03T13:14:53+00:00"; + condition = true; + message = '' + A new module is available: `programs.calibre` + + Calibre is a powerful and easy to use e-book manager. Users say it’s outstanding + and a must-have. It’ll allow you to do nearly everything and it takes things a + step beyond normal e-book software. It’s also completely free and open source + and great for both casual users and computer experts. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-04_16-34-38.nix b/modules/misc/news/2025/12/2025-12-04_16-34-38.nix new file mode 100644 index 00000000..5c42bb34 --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-04_16-34-38.nix @@ -0,0 +1,12 @@ +{ + time = "2025-12-04T19:34:38+00:00"; + condition = true; + message = '' + A new module is available: `programs.screen` + + GNU Screen is a terminal multiplexer, a software application that can + be used to multiplex several virtual consoles, allowing a user to access + multiple separate login sessions inside a single terminal window, or detach + and reattach sessions from a terminal. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-04_22-50-03.nix b/modules/misc/news/2025/12/2025-12-04_22-50-03.nix new file mode 100644 index 00000000..170b57eb --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-04_22-50-03.nix @@ -0,0 +1,12 @@ +{ pkgs, ... }: + +{ + time = "2025-12-05T01:50:03+00:00"; + condition = pkgs.stdenv.hostPlatform.isLinux; + message = '' + A new module is available: `programs.hyprlauncher` + + Hyprlauncher is a multipurpose and versatile launcher/picker + for hyprland. It’s fast, simple, and provides various modules. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-06_11-03-01.nix b/modules/misc/news/2025/12/2025-12-06_11-03-01.nix new file mode 100644 index 00000000..79cd2651 --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-06_11-03-01.nix @@ -0,0 +1,10 @@ +{ + time = "2025-12-06T10:03:01+00:00"; + condition = true; + message = '' + A new module is available: `programs.npm` + + It allows you manage your npm user configuration (`.npmrc`) + and install a specific version of the package. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-06_11-05-43.nix b/modules/misc/news/2025/12/2025-12-06_11-05-43.nix new file mode 100644 index 00000000..91388980 --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-06_11-05-43.nix @@ -0,0 +1,10 @@ +{ + time = "2025-12-06T10:05:43+00:00"; + condition = true; + message = '' + A new module is available: `programs.ty` + + It allows to manage the configuration and package + of the Python language server `ty` by Astral. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-11_13-04-30.nix b/modules/misc/news/2025/12/2025-12-11_13-04-30.nix new file mode 100644 index 00000000..ddafb6dd --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-11_13-04-30.nix @@ -0,0 +1,32 @@ +{ config, ... }: + +{ + time = "2025-12-11T19:04:30+00:00"; + condition = + let + helixEnabled = config.programs.helix.enable && config.programs.helix.defaultEditor; + kakouneEnabled = config.programs.kakoune.enable && config.programs.kakoune.defaultEditor; + neovimEnabled = config.programs.neovim.enable && config.programs.neovim.defaultEditor; + vimEnabled = config.programs.vim.enable && config.programs.vim.defaultEditor; + emacsEnabled = config.services.emacs.enable && config.services.emacs.defaultEditor; + in + helixEnabled || kakouneEnabled || neovimEnabled || vimEnabled || emacsEnabled; + message = '' + The 'defaultEditor' option now sets both {env}`EDITOR` and {env}`VISUAL` + environment variables. + + Previously, only {env}`EDITOR` was set. The {env}`VISUAL` variable is now + also configured to point to the same editor, which is the expected behavior + for modern terminal editors. + + This change affects the following modules: + - programs.helix + - programs.kakoune + - programs.neovim + - programs.vim + - services.emacs + + No action is required. This change should improve compatibility with tools + that check {env}`VISUAL` before {env}`EDITOR`. + ''; +} diff --git a/modules/misc/news/2025/12/2025-12-12_19-20-28.nix b/modules/misc/news/2025/12/2025-12-12_19-20-28.nix new file mode 100644 index 00000000..6aea4d88 --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-12_19-20-28.nix @@ -0,0 +1,12 @@ +{ config, ... }: + +{ + time = "2025-12-12T19:20:28+00:00"; + condition = config.xsession.windowManager.herbstluftwm.enable; + message = '' + It is now possible to disable the `herbstclient` alias in the autostart + script by setting `xsession.windowManagers.herbsluftwm.enableAlias = false`. + This makes it possible to use the `herbstclient` command in bash functions, + though may cause flickering while the autostart script runs. + ''; +} diff --git a/modules/misc/qt.nix b/modules/misc/qt.nix index 0fe00d77..6bee4217 100644 --- a/modules/misc/qt.nix +++ b/modules/misc/qt.nix @@ -8,6 +8,10 @@ let cfg = config.qt; + qtctFormat = pkgs.formats.ini { + listToValue = values: lib.concatStringsSep ", " values; + }; + # Map platform names to their packages. platformPackages = with pkgs; { gnome = [ @@ -286,7 +290,34 @@ in ''; }; }; - }; + } + // (lib.genAttrs' [ "qt5ct" "qt6ct" ] ( + name: + lib.nameValuePair "${name}Settings" ( + lib.mkOption { + type = lib.types.nullOr qtctFormat.type; + default = null; + example = lib.literalExpression '' + { + Appearance = { + style = "kvantum"; + icon_theme = "Papirus-Dark"; + standar_dialogs = "xdgdesktopportal"; + }; + Fonts = { + fixed = "\"DejaVuSansM Nerd Font Mono,12\""; + general = "\"DejaVu Sans,12\""; + }; + } + ''; + description = '' + Qtct configuration. Writes settings to `${name}/${name}.conf` + file. Lists will be translated to comma-separated strings. + Fonts must be quoted (see example). + ''; + } + ) + )); }; config = @@ -397,5 +428,18 @@ in ] ++ lib.optionals (platformTheme.name != null) [ "QT_QPA_PLATFORMTHEME" ] ++ lib.optionals (cfg.style.name != null) [ "QT_STYLE_OVERRIDE" ]; + + xdg.configFile = + lib.pipe + [ "qt5ct" "qt6ct" ] + [ + (lib.filter (qtct: cfg."${qtct}Settings" != null)) + (lib.flip lib.genAttrs' ( + qtct: + lib.nameValuePair "${qtct}/${qtct}.conf" { + source = qtctFormat.generate "${qtct}-config" cfg."${qtct}Settings"; + } + )) + ]; }; } diff --git a/modules/po/zh_Hant.po b/modules/po/zh_Hant.po index 7b17221b..9c8a0f42 100644 --- a/modules/po/zh_Hant.po +++ b/modules/po/zh_Hant.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Home Manager Modules\n" "Report-Msgid-Bugs-To: https://github.com/nix-community/home-manager/issues\n" "POT-Creation-Date: 2025-07-22 10:59+0200\n" -"PO-Revision-Date: 2025-03-07 18:58+0000\n" -"Last-Translator: 807 \n" +"PO-Revision-Date: 2025-12-04 04:17+0000\n" +"Last-Translator: \"Urocissa Caerulea.Tw\" \n" "Language-Team: Chinese (Traditional Han script) \n" "Language: zh_Hant\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.10.3-dev\n" +"X-Generator: Weblate 5.15-dev\n" #: modules/files.nix:206 msgid "Creating home file links in %s" @@ -25,15 +25,15 @@ msgstr "正在 %s 中建立家目錄檔案連結" #: modules/files.nix:219 msgid "Cleaning up orphan links from %s" -msgstr "正在從 %s 清理孤立連結" +msgstr "正在清理 %s 中的孤立連結" #: modules/home-environment.nix:647 msgid "Creating new profile generation" -msgstr "正在建立新一代的配置文件中" +msgstr "正在建立新的世代設定檔" #: modules/home-environment.nix:650 msgid "No change so reusing latest profile generation" -msgstr "為發生改變,請重新使用新一代的配置文件" +msgstr "沒有變更,將重複使用最新的設定檔世代" #: modules/home-environment.nix:699 msgid "" @@ -50,18 +50,18 @@ msgid "" "\n" "Then try activating your Home Manager configuration again." msgstr "" -"糟糕,Nix 未能安裝您的新 Home Manager 配置文件!\n" +"糟糕,Nix 無法安裝您的新 Home Manager 設定檔!\n" "\n" -"也許這裏和使用 \"%s\" 安裝的包有衝突?\n" -"嘗試運行\n" +"可能與使用 \"%s\" 安裝的套件衝突?\n" +"請嘗試執行\n" "\n" " %s\n" "\n" -"如果有衝突的包,你可以用\n" +"如果有衝突的套件,您可以使用以下指令移除\n" "\n" " %s\n" "\n" -"來移除。然後嘗試再次啟用您的 Home Manager 配置。" +"然後再次嘗試啟用您的 Home Manager 設定。" #: modules/home-environment.nix:735 msgid "Activating %s" @@ -69,27 +69,27 @@ msgstr "正在啟用 %s" #: modules/home-environment.nix:807 msgid "%s: unknown option '%s'" -msgstr "" +msgstr "%s:未知選項 '%s'" #: modules/lib-bash/activation-init.sh:22 msgid "Migrating profile from %s to %s" -msgstr "正在從 %S 配置文件轉移到 %s 中" +msgstr "正在將設定檔從 %s 遷移至 %s" #: modules/lib-bash/activation-init.sh:54 msgid "Could not find suitable profile directory, tried %s and %s" -msgstr "找不到合適的 profile 目錄,已經嘗試 %s 和 %s" +msgstr "找不到合適的設定檔目錄,已嘗試 %s 和 %s" #: modules/lib-bash/activation-init.sh:106 msgid "Error: USER is set to \"%s\" but we expect \"%s\"" -msgstr "錯誤:USER 被設定為 「%s」但我們希望是 「%s」" +msgstr "錯誤:USER 被設定為「%s」,但我們的預期為「%s」" #: modules/lib-bash/activation-init.sh:115 msgid "Error: HOME is set to \"%s\" but we expect \"%s\"" -msgstr "錯誤:HOME 被設定為 「%s」但我們預期得到 「%s」" +msgstr "錯誤:HOME 被設定為「%s」,但我們的預期為「%s」" #: modules/lib-bash/activation-init.sh:132 msgid "Starting Home Manager activation" -msgstr "正在啟動 Home Manager 初始化程式" +msgstr "正在進行 Home Manager 啟用程序" #: modules/lib-bash/activation-init.sh:136 msgid "Sanity checking Nix" @@ -97,19 +97,19 @@ msgstr "正在進行 Nix 完整性檢查" #: modules/lib-bash/activation-init.sh:149 msgid "This is a dry run" -msgstr "這是試運行" +msgstr "這是模擬執行" #: modules/lib-bash/activation-init.sh:153 msgid "This is a live run" -msgstr "這是在實際運行" +msgstr "這是實際執行" #: modules/lib-bash/activation-init.sh:159 msgid "Using Nix version: %s" -msgstr "正在使用的 Nix 版本: %s" +msgstr "使用中的 Nix 版本:%s" #: modules/lib-bash/activation-init.sh:162 msgid "Activation variables:" -msgstr "啟用的變數:" +msgstr "啟用變數:" #~ msgid "Creating profile generation %s" #~ msgstr "正在建立配置檔案世代 %s" diff --git a/modules/programs/calibre.nix b/modules/programs/calibre.nix new file mode 100644 index 00000000..c28a9a8e --- /dev/null +++ b/modules/programs/calibre.nix @@ -0,0 +1,44 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) + types + mkIf + mkEnableOption + mkPackageOption + mkOption + ; + + cfg = config.programs.calibre; +in +{ + meta.maintainers = with lib.hm.maintainers; [ aguirre-matteo ]; + options.programs.calibre = { + enable = mkEnableOption "calibre"; + package = mkPackageOption pkgs "calibre" { nullable = true; }; + plugins = mkOption { + type = with types; listOf path; + default = [ ]; + description = "List of plugins to install for calibre"; + }; + }; + + config = mkIf cfg.enable { + home.packages = mkIf (cfg.package != null) [ cfg.package ]; + xdg.configFile = mkIf (cfg.plugins != [ ]) ( + let + symlinkedPlugins = pkgs.symlinkJoin { + name = "calibre-plugins"; + paths = cfg.plugins; + }; + in + lib.mapAttrs' ( + k: _: lib.nameValuePair "calibre/plugins/${k}" { source = (symlinkedPlugins + "/${k}"); } + ) (builtins.readDir symlinkedPlugins) + ); + }; +} diff --git a/modules/programs/chromium.nix b/modules/programs/chromium.nix index 88b15f68..5344b60a 100644 --- a/modules/programs/chromium.nix +++ b/modules/programs/chromium.nix @@ -40,7 +40,7 @@ let finalPackage = mkOption { inherit visible; - type = types.package; + type = types.nullOr types.package; readOnly = true; description = '' Resulting customized ${name} package @@ -220,15 +220,22 @@ let }; in + lib.mkIf cfg.enable { - programs.${browser}.finalPackage = lib.mkIf (cfg.package != null) ( + assertions = [ + { + assertion = !(cfg.package == null && cfg.commandLineArgs != [ ]); + message = "Cannot set `commandLineArgs` when `package` is null for ${browser}."; + } + ]; + + programs.${browser}.finalPackage = if cfg.commandLineArgs != [ ] then cfg.package.override { commandLineArgs = lib.concatStringsSep " " cfg.commandLineArgs; } else - cfg.package - ); + cfg.package; home.packages = lib.mkIf (cfg.finalPackage != null) [ cfg.finalPackage diff --git a/modules/programs/claude-code.nix b/modules/programs/claude-code.nix index f5b16ac7..8d5a4036 100644 --- a/modules/programs/claude-code.nix +++ b/modules/programs/claude-code.nix @@ -197,6 +197,47 @@ in }; }; + rules = lib.mkOption { + type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path); + default = { }; + description = '' + Modular rule files for Claude Code. + The attribute name becomes the rule filename, and the value is either: + - Inline content as a string + - A path to a file containing the rule content + Rules are stored in .claude/rules/ directory. + All markdown files in .claude/rules/ are automatically loaded as project memory. + ''; + example = lib.literalExpression '' + { + code-style = ''' + # Code Style Guidelines + + - Use consistent formatting + - Follow language conventions + '''; + testing = ''' + # Testing Conventions + + - Write tests for all new features + - Maintain test coverage above 80% + '''; + security = ./rules/security.md; + } + ''; + }; + + rulesDir = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a directory containing rule files for Claude Code. + Rule files from this directory will be symlinked to .claude/rules/. + All markdown files in this directory are automatically loaded as project memory. + ''; + example = lib.literalExpression "./rules"; + }; + agentsDir = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; @@ -325,6 +366,10 @@ in assertion = !(cfg.memory.text != null && cfg.memory.source != null); message = "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`"; } + { + assertion = !(cfg.rules != { } && cfg.rulesDir != null); + message = "Cannot specify both `programs.claude-code.rules` and `programs.claude-code.rulesDir`"; + } { assertion = !(cfg.agents != { } && cfg.agentsDir != null); message = "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`"; @@ -348,7 +393,7 @@ in makeWrapperArgs = lib.flatten ( lib.filter (x: x != [ ]) [ (lib.optional (cfg.mcpServers != { }) [ - "--add-flags" + "--append-flags" "--mcp-config ${jsonFormat.generate "claude-code-mcp-config.json" { inherit (cfg) mcpServers; }}" ]) ] @@ -386,6 +431,11 @@ in if cfg.memory.text != null then { text = cfg.memory.text; } else { source = cfg.memory.source; } ); + ".claude/rules" = lib.mkIf (cfg.rulesDir != null) { + source = cfg.rulesDir; + recursive = true; + }; + ".claude/agents" = lib.mkIf (cfg.agentsDir != null) { source = cfg.agentsDir; recursive = true; @@ -406,6 +456,12 @@ in recursive = true; }; } + // lib.mapAttrs' ( + name: content: + lib.nameValuePair ".claude/rules/${name}.md" ( + if lib.isPath content then { source = content; } else { text = content; } + ) + ) cfg.rules // lib.mapAttrs' ( name: content: lib.nameValuePair ".claude/agents/${name}.md" ( diff --git a/modules/programs/diff-highlight.nix b/modules/programs/diff-highlight.nix index 8bbe178b..1c7ec048 100644 --- a/modules/programs/diff-highlight.nix +++ b/modules/programs/diff-highlight.nix @@ -68,8 +68,15 @@ in in lib.mkMerge [ (mkIf cfg.enable { - # Auto-enable git integration if programs.git.diff-highlight.enable was set to true - programs.diff-highlight.enableGitIntegration = lib.mkIf oldOptionEnabled (lib.mkOverride 1490 true); + assertions = [ + { + assertion = !cfg.enableGitIntegration || config.programs.git.package != null; + message = '' + programs.diff-highlight.enableGitIntegration requires programs.git.package to be set. + Please set programs.git.package to a valid git package. + ''; + } + ]; warnings = lib.optional @@ -77,9 +84,12 @@ in cfg.enableGitIntegration && options.programs.diff-highlight.enableGitIntegration.highestPrio == 1490 ) "`programs.diff-highlight.enableGitIntegration` automatic enablement is deprecated. Please explicitly set `programs.diff-highlight.enableGitIntegration = true`."; + + # Auto-enable git integration if programs.git.diff-highlight.enable was set to true + programs.diff-highlight.enableGitIntegration = lib.mkIf oldOptionEnabled (lib.mkOverride 1490 true); }) - (mkIf (cfg.enable && cfg.enableGitIntegration) { + (mkIf (cfg.enable && cfg.enableGitIntegration && config.programs.git.package != null) { programs.git = { enable = lib.mkDefault true; iniContent = diff --git a/modules/programs/gemini-cli.nix b/modules/programs/gemini-cli.nix index accfdabf..b9b0ca5e 100644 --- a/modules/programs/gemini-cli.nix +++ b/modules/programs/gemini-cli.nix @@ -21,14 +21,19 @@ in settings = lib.mkOption { inherit (jsonFormat) type; default = { }; - example = lib.literalExpression '' - { - "theme": "Default", - "vimMode": true, - "preferredEditor": "nvim", - "autoAccept": true - } - ''; + example = { + ui.theme = "Default"; + general = { + vimMode = true; + preferredEditor = "nvim"; + previewFeatures = true; + }; + ide.enabled = true; + privacy.usageStatisticsEnabled = false; + tools.autoAccept = false; + context.loadMemoryFromIncludeDirectories = true; + security.auth.selectedType = "oauth-personal"; + }; description = "JSON config for gemini-cli"; }; @@ -81,12 +86,12 @@ in }; defaultModel = lib.mkOption { - type = lib.types.str; - default = "gemini-2.5-pro"; + type = lib.types.nullOr lib.types.str; + default = null; example = "gemini-2.5-flash"; description = '' The default model to use for the CLI. - Will be set as $GEMINI_MODEL. + Will be set as $GEMINI_MODEL when configured. ''; }; @@ -138,7 +143,9 @@ in file.".gemini/settings.json" = lib.mkIf (cfg.settings != { }) { source = jsonFormat.generate "gemini-cli-settings.json" cfg.settings; }; - sessionVariables.GEMINI_MODEL = cfg.defaultModel; + sessionVariables = lib.mkIf (cfg.defaultModel != null) { + GEMINI_MODEL = cfg.defaultModel; + }; }; } { diff --git a/modules/programs/git.nix b/modules/programs/git.nix index 3d7bc0d2..b28380f6 100644 --- a/modules/programs/git.nix +++ b/modules/programs/git.nix @@ -41,6 +41,7 @@ in enable = mkEnableOption "Git"; package = lib.mkPackageOption pkgs "git" { + nullable = true; example = "pkgs.gitFull"; extraDescription = '' Use {var}`pkgs.gitFull` @@ -328,7 +329,7 @@ in config = mkIf cfg.enable ( lib.mkMerge [ { - home.packages = [ cfg.package ]; + home.packages = lib.optionals (cfg.package != null) [ cfg.package ]; assertions = [ { @@ -516,7 +517,7 @@ in Type = "oneshot"; ExecStart = let - exe = lib.getExe cfg.package; + exe = if cfg.package != null then lib.getExe cfg.package else "git"; in '' "${exe}" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%i @@ -553,7 +554,7 @@ in launchd.agents = let baseArguments = [ - "${lib.getExe cfg.package}" + "${if cfg.package != null then lib.getExe cfg.package else "git"}" "for-each-repo" "--keep-going" "--config=maintenance.repo" diff --git a/modules/programs/helix.nix b/modules/programs/helix.nix index 2e2f78c9..74b029c0 100644 --- a/modules/programs/helix.nix +++ b/modules/programs/helix.nix @@ -49,7 +49,8 @@ in default = false; description = '' Whether to configure {command}`hx` as the default - editor using the {env}`EDITOR` environment variable. + editor using the {env}`EDITOR` and {env}`VISUAL` + environment variables. ''; }; @@ -225,7 +226,10 @@ in else [ cfg.package ]; - home.sessionVariables = mkIf cfg.defaultEditor { EDITOR = "hx"; }; + home.sessionVariables = mkIf cfg.defaultEditor { + EDITOR = "hx"; + VISUAL = "hx"; + }; xdg.configFile = let diff --git a/modules/programs/hyprpanel/default.nix b/modules/programs/hyprpanel/default.nix index 3c558dc7..f44b8f72 100644 --- a/modules/programs/hyprpanel/default.nix +++ b/modules/programs/hyprpanel/default.nix @@ -9,7 +9,7 @@ let jsonFormat = pkgs.formats.json { }; in { - meta.maintainers = [ lib.maintainers.perchun ]; + meta.maintainers = [ lib.maintainers.PerchunPak ]; imports = [ (lib.mkRemovedOptionModule [ "programs" "hyprpanel" "dontAssertNotificationDaemons " ] '' diff --git a/modules/programs/infat.nix b/modules/programs/infat.nix new file mode 100644 index 00000000..8ec05a67 --- /dev/null +++ b/modules/programs/infat.nix @@ -0,0 +1,81 @@ +{ + lib, + config, + pkgs, + ... +}: +let + cfg = config.programs.infat; + tomlFormat = pkgs.formats.toml { }; + + configDir = + if config.xdg.enable then + config.xdg.configHome + else + "${config.home.homeDirectory}/Library/Application Support"; + + configFile = "${configDir}/infat/config.toml"; +in +{ + meta.maintainers = with lib.maintainers; [ + mirkolenz + ]; + + options = { + programs.infat = { + enable = lib.mkEnableOption "infat"; + package = lib.mkPackageOption pkgs "infat" { nullable = true; }; + settings = lib.mkOption { + type = tomlFormat.type; + default = { }; + example = lib.literalExpression '' + { + extensions = { + md = "TextEdit"; + html = "Safari"; + pdf = "Preview"; + }; + schemes = { + mailto = "Mail"; + web = "Safari"; + }; + types = { + plain-text = "VSCode"; + }; + } + ''; + description = '' + Configuration written to + {file}`$XDG_CONFIG_HOME/infat/config.toml`. + ''; + }; + autoActivate = lib.mkEnableOption "auto-activate infat" // { + default = true; + example = false; + description = '' + Automatically activate infat on startup. + This is useful if you want to use infat as a + default application handler for certain file types. + If you don't want this, set this to false. + This option is only effective if `settings` is set. + ''; + }; + }; + }; + config = lib.mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "programs.infat" pkgs lib.platforms.darwin) + ]; + home = { + packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + file.${configFile} = lib.mkIf (cfg.settings != { }) { + source = tomlFormat.generate "infat-settings.toml" cfg.settings; + }; + activation = lib.mkIf (cfg.settings != { } && cfg.package != null && cfg.autoActivate) { + infat = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + run ${lib.getExe cfg.package} --config "${configFile}" $VERBOSE_ARG + ''; + }; + }; + }; +} diff --git a/modules/programs/kakoune.nix b/modules/programs/kakoune.nix index becc7519..ca8e8a58 100644 --- a/modules/programs/kakoune.nix +++ b/modules/programs/kakoune.nix @@ -705,7 +705,8 @@ in default = false; description = '' Whether to configure {command}`kak` as the default - editor using the {env}`EDITOR` environment variable. + editor using the {env}`EDITOR` and {env}`VISUAL` + environment variables. ''; }; @@ -755,7 +756,10 @@ in programs.kakoune.finalPackage = lib.mkIf (cfg.package != null) kakouneWithPlugins; home.packages = lib.mkIf (cfg.finalPackage != null) [ cfg.finalPackage ]; - home.sessionVariables = mkIf cfg.defaultEditor { EDITOR = "kak"; }; + home.sessionVariables = mkIf cfg.defaultEditor { + EDITOR = "kak"; + VISUAL = "kak"; + }; xdg.configFile = lib.mkMerge [ { "kak/kakrc".source = configFile; } (mkIf (cfg.colorSchemePackage != null) { diff --git a/modules/programs/less.nix b/modules/programs/less.nix index 1a8c5fb7..60a67e24 100644 --- a/modules/programs/less.nix +++ b/modules/programs/less.nix @@ -37,13 +37,17 @@ in options = lib.mkOption { type = with lib.types; - attrsOf (oneOf [ - bool - int - str - ]); - default = { }; - description = "GNU-style options to be set via {env}`$LESS`."; + let + scalar = oneOf [ + bool + int + str + ]; + attrs = attrsOf (either scalar (listOf scalar)); + in + coercedTo attrs (lib.cli.toGNUCommandLine { }) (listOf str); + default = [ ]; + description = "Options to be set via {env}`$LESS`."; example = { RAW-CONTROL-CHARS = true; quiet = true; @@ -58,10 +62,10 @@ in xdg.configFile."lesskey" = lib.mkIf (cfg.config != "") { text = cfg.config; }; - programs.less.config = lib.mkIf (cfg.options != { }) ( + programs.less.config = lib.mkIf (cfg.options != [ ]) ( lib.mkBefore '' #env - LESS = ${lib.cli.toGNUCommandLineShell { } cfg.options} + LESS = ${lib.concatStringsSep " " cfg.options} '' ); }; diff --git a/modules/programs/neovim.nix b/modules/programs/neovim.nix index a234a133..bc3bbac6 100644 --- a/modules/programs/neovim.nix +++ b/modules/programs/neovim.nix @@ -7,121 +7,46 @@ let inherit (lib) + concatMapStringsSep literalExpression mkEnableOption mkIf mkOption - mkRemovedOptionModule + mkPackageOption + optionals types ; cfg = config.programs.neovim; - fileType = - (import ../lib/file-type.nix { - inherit (config.home) homeDirectory; - inherit lib pkgs; - }).fileType; + inherit + ( + (import ../lib/file-type.nix { + inherit (config.home) homeDirectory; + inherit lib pkgs; + }) + ) + fileType + ; jsonFormat = pkgs.formats.json { }; - - pluginWithConfigType = types.submodule { - options = { - config = mkOption { - type = types.nullOr types.lines; - description = "Script to configure this plugin. The scripting language should match type."; - default = null; - }; - - type = mkOption { - type = types.either (types.enum [ - "lua" - "viml" - "teal" - "fennel" - ]) types.str; - description = "Language used in config. Configurations are aggregated per-language."; - default = "viml"; - }; - - optional = mkEnableOption "optional" // { - description = "Don't load by default (load with :packadd)"; - }; - - plugin = lib.mkPackageOption pkgs.vimPlugins "plugin" { - default = null; - example = "pkgs.vimPlugins.nvim-treesitter"; - pkgsText = "pkgs.vimPlugins"; - }; - - runtime = mkOption { - default = { }; - # passing actual "${xdg.configHome}/nvim" as basePath was a bit tricky - # due to how fileType.target is implemented - type = fileType "programs.neovim.plugins._.runtime" "{var}`xdg.configHome/nvim`" "nvim"; - example = literalExpression '' - { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; } - ''; - description = '' - Set of files that have to be linked in nvim config folder. - ''; - }; - }; - }; - - allPlugins = - cfg.plugins - ++ lib.optional cfg.coc.enable { - type = "viml"; - plugin = cfg.coc.package; - config = cfg.coc.pluginConfig; - optional = false; - }; - - luaPackages = cfg.finalPackage.unwrapped.lua.pkgs; - resolvedExtraLuaPackages = cfg.extraLuaPackages luaPackages; - - extraMakeWrapperArgs = lib.optionalString ( - cfg.extraPackages != [ ] - ) ''--suffix PATH : "${lib.makeBinPath cfg.extraPackages}"''; - extraMakeWrapperLuaCArgs = - lib.optionalString (resolvedExtraLuaPackages != [ ]) - ''--suffix LUA_CPATH ";" "${ - lib.concatMapStringsSep ";" luaPackages.getLuaCPath resolvedExtraLuaPackages - }"''; - extraMakeWrapperLuaArgs = - lib.optionalString (resolvedExtraLuaPackages != [ ]) - ''--suffix LUA_PATH ";" "${ - lib.concatMapStringsSep ";" luaPackages.getLuaPath resolvedExtraLuaPackages - }"''; in { meta.maintainers = with lib.maintainers; [ khaneliman ]; - imports = [ - (mkRemovedOptionModule [ - "programs" - "neovim" - "withPython" - ] "Python2 support has been removed from neovim.") - (mkRemovedOptionModule [ - "programs" - "neovim" - "extraPythonPackages" - ] "Python2 support has been removed from neovim.") - (mkRemovedOptionModule [ "programs" "neovim" "configure" ] '' - programs.neovim.configure is deprecated. - Other programs.neovim options can override its settings or ignore them. - Please use the other options at your disposal: - configure.packages.*.opt -> programs.neovim.plugins = [ { plugin = ...; optional = true; }] - configure.packages.*.start -> programs.neovim.plugins = [ { plugin = ...; }] - configure.customRC -> programs.neovim.extraConfig - '') - ]; options = { programs.neovim = { enable = mkEnableOption "Neovim"; + package = mkPackageOption pkgs "neovim" { default = "neovim-unwrapped"; }; + + finalPackage = mkOption { + type = types.package; + readOnly = true; + description = "Resulting customized neovim package."; + }; + + # Aliases viAlias = mkOption { type = types.bool; default = false; @@ -146,6 +71,17 @@ in ''; }; + defaultEditor = mkOption { + type = types.bool; + default = false; + description = '' + Whether to configure {command}`nvim` as the default + editor using the {env}`EDITOR` and {env}`VISUAL` + environment variables. + ''; + }; + + # Providers & Runtimes withNodeJs = mkOption { type = types.bool; default = false; @@ -155,11 +91,12 @@ in ''; }; - withRuby = mkOption { - type = types.nullOr types.bool; - default = true; + withPerl = mkOption { + type = types.bool; + default = false; description = '' - Enable ruby provider. + Enable perl provider. Set to `true` to + use Perl plugins. ''; }; @@ -172,21 +109,16 @@ in ''; }; + withRuby = mkOption { + type = types.nullOr types.bool; + default = true; + description = '' + Enable ruby provider. + ''; + }; + extraPython3Packages = mkOption { - # In case we get a plain list, we need to turn it into a function, - # as expected by the function in nixpkgs. - # The only way to do so is to call `const`, which will ignore its input. - type = - let - fromType = types.listOf types.package; - in - types.coercedTo fromType (lib.flip lib.warn lib.const '' - Assigning a plain list to extraPython3Packages is deprecated. - Please assign a function taking a package set as argument, so - extraPython3Packages = [ pkgs.python3Packages.xxx ]; - should become - extraPython3Packages = ps: [ ps.xxx ]; - '') (types.functionTo fromType); + type = types.functionTo (types.listOf types.package); default = _: [ ]; defaultText = literalExpression "ps: [ ]"; example = literalExpression "pyPkgs: with pyPkgs; [ python-language-server ]"; @@ -198,21 +130,8 @@ in ''; }; - # We get the Lua package from the final package and use its - # Lua packageset to evaluate the function that this option was set to. - # This ensures that we always use the same Lua version as the Neovim package. extraLuaPackages = mkOption { - type = - let - fromType = types.listOf types.package; - in - types.coercedTo fromType (lib.flip lib.warn lib.const '' - Assigning a plain list to extraLuaPackages is deprecated. - Please assign a function taking a package set as argument, so - extraLuaPackages = [ pkgs.lua51Packages.xxx ]; - should become - extraLuaPackages = ps: [ ps.xxx ]; - '') (types.functionTo fromType); + type = types.functionTo (types.listOf types.package); default = _: [ ]; defaultText = literalExpression "ps: [ ]"; example = literalExpression "luaPkgs: with luaPkgs; [ luautf8 ]"; @@ -224,8 +143,34 @@ in ''; }; + # Wrapper Configuration + extraName = mkOption { + type = types.str; + default = ""; + description = '' + Extra name appended to the wrapper package name. + ''; + }; + + autowrapRuntimeDeps = mkOption { + type = types.bool; + default = true; + description = '' + Whether to automatically wrap the binary with the runtime dependencies of the plugins. + ''; + }; + + waylandSupport = mkOption { + type = types.bool; + default = pkgs.stdenv.isLinux; + defaultText = literalExpression "pkgs.stdenv.isLinux"; + description = '' + Whether to enable Wayland clipboard support. + ''; + }; + extraWrapperArgs = mkOption { - type = with types; listOf str; + type = types.listOf types.str; default = [ ]; example = literalExpression '' [ @@ -246,53 +191,14 @@ in ''; }; - generatedConfigViml = mkOption { - type = types.lines; - visible = true; - readOnly = true; - description = '' - Generated vimscript config. - ''; - }; - - generatedConfigs = mkOption { - type = types.attrsOf types.lines; - visible = true; - readOnly = true; - example = literalExpression '' - { - viml = ''' - " Generated by home-manager - map , - '''; - - lua = ''' - -- Generated by home-manager - vim.opt.background = "dark" - '''; - }''; - description = '' - Generated configurations with as key their language (set via type). - ''; - }; - - package = lib.mkPackageOption pkgs "neovim" { default = "neovim-unwrapped"; }; - - finalPackage = mkOption { - type = types.package; - readOnly = true; - description = "Resulting customized neovim package."; - }; - - defaultEditor = mkOption { - type = types.bool; - default = false; - description = '' - Whether to configure {command}`nvim` as the default - editor using the {env}`EDITOR` environment variable. - ''; + extraPackages = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExpression "[ pkgs.shfmt ]"; + description = "Extra packages available to nvim."; }; + # Configuration & Plugins extraConfig = mkOption { type = types.lines; default = ""; @@ -315,37 +221,78 @@ in ''; }; - extraPackages = mkOption { - type = with types; listOf package; - default = [ ]; - example = literalExpression "[ pkgs.shfmt ]"; - description = "Extra packages available to nvim."; - }; + plugins = + let + pluginWithConfigType = types.submodule { + options = { + config = mkOption { + type = types.nullOr types.lines; + description = "Script to configure this plugin. The scripting language should match type."; + default = null; + }; - plugins = mkOption { - type = with types; listOf (either package pluginWithConfigType); - default = [ ]; - example = literalExpression '' - with pkgs.vimPlugins; [ - yankring - vim-nix - { plugin = vim-startify; - config = "let g:startify_change_to_vcs_root = 0"; - } - ] - ''; - description = '' - List of vim plugins to install optionally associated with - configuration to be placed in init.vim. + type = mkOption { + type = types.either (types.enum [ + "lua" + "viml" + "teal" + "fennel" + ]) types.str; + description = "Language used in config. Configurations are aggregated per-language."; + default = "viml"; + }; - This option is mutually exclusive with {var}`configure`. - ''; - }; + optional = mkEnableOption "optional" // { + description = "Don't load by default (load with :packadd)"; + }; + + plugin = mkPackageOption pkgs.vimPlugins "plugin" { + default = null; + example = "pkgs.vimPlugins.nvim-treesitter"; + pkgsText = "pkgs.vimPlugins"; + }; + + runtime = mkOption { + default = { }; + # passing actual "${xdg.configHome}/nvim" as basePath was a bit tricky + # due to how fileType.target is implemented + type = fileType "programs.neovim.plugins._.runtime" "{var}`xdg.configHome/nvim`" "nvim"; + example = literalExpression '' + { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; } + ''; + description = '' + Set of files that have to be linked in nvim config folder. + ''; + }; + }; + }; + + in + mkOption { + type = types.listOf (types.either types.package pluginWithConfigType); + default = [ ]; + example = literalExpression '' + with pkgs.vimPlugins; + [ + yankring + vim-nix + { plugin = vim-startify; + config = "let g:startify_change_to_vcs_root = 0"; + } + ] + ''; + description = '' + List of vim plugins to install optionally associated with + configuration to be placed in init.vim. + + This option is mutually exclusive with {var}`configure`. + ''; + }; coc = { enable = mkEnableOption "Coc"; - package = lib.mkPackageOption pkgs "coc-nvim" { + package = mkPackageOption pkgs "coc-nvim" { default = [ "vimPlugins" "coc-nvim" @@ -375,7 +322,7 @@ in filetypes = [ "haskell" "lhaskell" ]; }; }; - }; + } ''; description = '' Extra configuration lines to add to @@ -392,11 +339,52 @@ in description = "Script to configure CoC. Must be viml."; }; }; + + # Generated / Read-Only + generatedConfigViml = mkOption { + type = types.lines; + visible = true; + readOnly = true; + description = '' + Generated vimscript config. + ''; + }; + + generatedConfigs = mkOption { + type = types.attrsOf types.lines; + visible = true; + readOnly = true; + example = literalExpression '' + { + viml = ''' + " Generated by home-manager + map , + '''; + + lua = ''' + -- Generated by home-manager + vim.opt.background = "dark" + '''; + } + ''; + description = '' + Generated configurations with as key their language (set via type). + ''; + }; }; }; - config = + config = mkIf cfg.enable ( let + allPlugins = + cfg.plugins + ++ lib.optional cfg.coc.enable { + type = "viml"; + plugin = cfg.coc.package; + config = cfg.coc.pluginConfig; + optional = false; + }; + defaultPlugin = { type = "viml"; plugin = null; @@ -412,11 +400,38 @@ in suppressNotVimlConfig = p: if p.type != "viml" then p // { config = null; } else p; + # Lua & Python Package Resolution + luaPackages = cfg.finalPackage.unwrapped.lua.pkgs; + resolvedExtraLuaPackages = cfg.extraLuaPackages luaPackages; + + # Wrapper Arguments Construction + extraMakeWrapperArgs = optionals (cfg.extraPackages != [ ]) [ + "--suffix" + "PATH" + ":" + (lib.makeBinPath cfg.extraPackages) + ]; + + extraMakeWrapperLuaCArgs = optionals (resolvedExtraLuaPackages != [ ]) [ + "--suffix" + "LUA_CPATH" + ";" + (concatMapStringsSep ";" luaPackages.getLuaCPath resolvedExtraLuaPackages) + ]; + + extraMakeWrapperLuaArgs = optionals (resolvedExtraLuaPackages != [ ]) [ + "--suffix" + "LUA_PATH" + ";" + (concatMapStringsSep ";" luaPackages.getLuaPath resolvedExtraLuaPackages) + ]; + neovimConfig = pkgs.neovimUtils.makeNeovimConfig { inherit (cfg) extraPython3Packages withPython3 withRuby + withPerl viAlias vimAlias ; @@ -428,54 +443,63 @@ in wrappedNeovim' = pkgs.wrapNeovimUnstable cfg.package ( neovimConfig // { + inherit (cfg) + extraName + autowrapRuntimeDeps + waylandSupport + withNodeJs + ; wrapperArgs = - (lib.escapeShellArgs (neovimConfig.wrapperArgs ++ cfg.extraWrapperArgs)) - + " " - + extraMakeWrapperArgs - + " " - + extraMakeWrapperLuaCArgs - + " " - + extraMakeWrapperLuaArgs; + neovimConfig.wrapperArgs + ++ cfg.extraWrapperArgs + ++ extraMakeWrapperArgs + ++ extraMakeWrapperLuaCArgs + ++ extraMakeWrapperLuaArgs; wrapRc = false; } ); in - mkIf cfg.enable { + { + programs.neovim = { + generatedConfigViml = neovimConfig.neovimRcContent; - programs.neovim.generatedConfigViml = neovimConfig.neovimRcContent; + generatedConfigs = + let + grouped = builtins.groupBy (x: x.type) pluginsNormalized; + configsOnly = lib.foldl (acc: p: if p.config != null then acc ++ [ p.config ] else acc) [ ]; + in + lib.mapAttrs (_name: vals: lib.concatStringsSep "\n" (configsOnly vals)) grouped; - programs.neovim.generatedConfigs = - let - grouped = lib.lists.groupBy (x: x.type) pluginsNormalized; - configsOnly = lib.foldl (acc: p: if p.config != null then acc ++ [ p.config ] else acc) [ ]; - in - lib.mapAttrs (name: vals: lib.concatStringsSep "\n" (configsOnly vals)) grouped; + finalPackage = wrappedNeovim'; + }; - home.packages = [ cfg.finalPackage ]; + home = { + packages = [ cfg.finalPackage ]; - home.sessionVariables = mkIf cfg.defaultEditor { EDITOR = "nvim"; }; + sessionVariables = mkIf cfg.defaultEditor { + EDITOR = "nvim"; + VISUAL = "nvim"; + }; - home.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; + shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; + }; xdg.configFile = let hasLuaConfig = lib.hasAttr "lua" config.programs.neovim.generatedConfigs; + luaRcContent = + lib.optionalString ( + wrappedNeovim'.initRc != "" + ) "vim.cmd [[source ${pkgs.writeText "nvim-init-home-manager.vim" wrappedNeovim'.initRc}]]\n" + + config.programs.neovim.extraLuaConfig + + lib.optionalString hasLuaConfig config.programs.neovim.generatedConfigs.lua; in lib.mkMerge ( # writes runtime (map (x: x.runtime) pluginsNormalized) ++ [ { - "nvim/init.lua" = - let - luaRcContent = - lib.optionalString ( - wrappedNeovim'.initRc != "" - ) "vim.cmd [[source ${pkgs.writeText "nvim-init-home-manager.vim" wrappedNeovim'.initRc}]]\n" - + config.programs.neovim.extraLuaConfig - + lib.optionalString hasLuaConfig config.programs.neovim.generatedConfigs.lua; - in - mkIf (luaRcContent != "") { text = luaRcContent; }; + "nvim/init.lua" = mkIf (luaRcContent != "") { text = luaRcContent; }; "nvim/coc-settings.json" = mkIf cfg.coc.enable { source = jsonFormat.generate "coc-settings.json" cfg.coc.settings; @@ -483,7 +507,6 @@ in } ] ); - - programs.neovim.finalPackage = wrappedNeovim'; - }; + } + ); } diff --git a/modules/programs/npm.nix b/modules/programs/npm.nix new file mode 100644 index 00000000..015b80b8 --- /dev/null +++ b/modules/programs/npm.nix @@ -0,0 +1,74 @@ +# https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/programs/npm.nix +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.programs.npm; + + xdgConfigHome = lib.removePrefix config.home.homeDirectory config.xdg.configHome; + configFile = if config.home.preferXdgDirectories then "${xdgConfigHome}/npm/npmrc" else ".npmrc"; + + iniFormat = pkgs.formats.ini { + listsAsDuplicateKeys = true; + }; + + toNpmrc = + let + mkLine = lib.generators.mkKeyValueDefault { } "="; + mkLines = k: v: if lib.isList v then map (x: mkLine "${k}[]" x) v else [ (mkLine k v) ]; + in + attrs: lib.concatLines (lib.concatLists (lib.mapAttrsToList mkLines attrs)); +in +{ + meta.maintainers = with lib.maintainers; [ mirkolenz ]; + + options = { + programs.npm = { + enable = lib.mkEnableOption "{command}`npm` user config"; + + package = lib.mkPackageOption pkgs [ "nodejs" ] { + example = "nodejs_24"; + nullable = true; + }; + + settings = lib.mkOption { + type = lib.types.attrsOf iniFormat.lib.types.atom; + description = '' + The user-specific npm configuration. + See and + + for more information. + ''; + default = { + prefix = "\${HOME}/.npm"; + }; + example = lib.literalExpression '' + { + color = true; + include = [ + "dev" + "prod" + ]; + init-license = "MIT"; + prefix = "''${HOME}/.npm"; + } + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + home = { + packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + file.${configFile} = lib.mkIf (cfg.settings != { }) { + text = toNpmrc cfg.settings; + }; + sessionVariables = lib.mkIf (cfg.settings != { }) { + NPM_CONFIG_USERCONFIG = "${config.home.homeDirectory}/${configFile}"; + }; + }; + }; +} diff --git a/modules/programs/parallel.nix b/modules/programs/parallel.nix index 4bb39a24..3ae7581d 100644 --- a/modules/programs/parallel.nix +++ b/modules/programs/parallel.nix @@ -20,7 +20,7 @@ in options.programs.parallel = { enable = mkEnableOption "GNU Parallel"; - package = lib.mkPackageOption pkgs "parallel-full" { }; + package = lib.mkPackageOption pkgs "parallel-full" { nullable = true; }; will-cite = mkOption { type = types.bool; @@ -33,7 +33,7 @@ in config = mkIf cfg.enable { home = { - packages = [ cfg.package ]; + packages = lib.mkIf (cfg.package != null) [ cfg.package ]; file.".parallel/will-cite" = mkIf cfg.will-cite { text = "generated by home manager (programs.parallel.will-cite)"; }; diff --git a/modules/programs/pimsync/default.nix b/modules/programs/pimsync/default.nix index b8edb7df..bf772c8f 100644 --- a/modules/programs/pimsync/default.nix +++ b/modules/programs/pimsync/default.nix @@ -51,7 +51,7 @@ localStorage = calendar: name: acc: { name = "storage"; - params = [ "${name}-local" ]; + params = [ "${if calendar then "calendar" else "contacts"}-${name}-local" ]; children = (attrsToDirectives { inherit (acc.local) path; @@ -63,7 +63,7 @@ remoteStorage = calendar: name: acc: { name = "storage"; - params = [ "${name}-remote" ]; + params = [ "${if calendar then "calendar" else "contacts"}-${name}-remote" ]; children = (attrsToDirectives { inherit (acc.remote) url; @@ -91,8 +91,8 @@ params = lib.singleton "${if calendar then "calendar" else "contacts"}-${name}"; children = (attrsToDirectives { - storage_a = "${name}-local"; - storage_b = "${name}-remote"; + storage_a = "${if calendar then "calendar" else "contacts"}-${name}-local"; + storage_b = "${if calendar then "calendar" else "contacts"}-${name}-remote"; }) ++ acc.pimsync.extraPairDirectives; }; diff --git a/modules/programs/rclone.nix b/modules/programs/rclone.nix index f7560fc2..df1a8a21 100644 --- a/modules/programs/rclone.nix +++ b/modules/programs/rclone.nix @@ -153,7 +153,7 @@ in default = { }; apply = lib.mergeAttrs { vfs-cache-mode = "full"; - cache-dir = "%C"; + cache-dir = "%C/rclone"; }; description = '' An attribute set of option values passed to `rclone mount`. To set diff --git a/modules/programs/screen.nix b/modules/programs/screen.nix new file mode 100644 index 00000000..e3f28924 --- /dev/null +++ b/modules/programs/screen.nix @@ -0,0 +1,55 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) + types + mkIf + mkEnableOption + mkPackageOption + mkOption + ; + + cfg = config.programs.screen; +in +{ + meta.maintainers = with lib.hm.maintainers; [ aguirre-matteo ]; + options.programs.screen = { + enable = mkEnableOption "screen"; + package = mkPackageOption pkgs "screen" { nullable = true; }; + screenrc = mkOption { + type = with types; nullOr (either path lines); + default = null; + example = '' + screen -t rtorrent rtorrent + screen -t irssi irssi + screen -t centerim centerim + screen -t ncmpc ncmpc -c + screen -t bash4 + screen -t bash5 + screen -t bash6 + screen -t bash7 + screen -t bash8 + screen -t bash9 + altscreen on + term screen-256color + bind ',' prev + bind '.' next + ''; + description = '' + Config file for GNU Screen. All the details can be found here: + . + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = mkIf (cfg.package != null) [ cfg.package ]; + home.file.".screenrc" = mkIf (cfg.screenrc != null) { + source = if lib.isPath cfg.screenrc then cfg.screenrc else pkgs.writeText "screenrc" cfg.screenrc; + }; + }; +} diff --git a/modules/programs/sheldon.nix b/modules/programs/sheldon.nix index f1055514..806cd566 100644 --- a/modules/programs/sheldon.nix +++ b/modules/programs/sheldon.nix @@ -52,7 +52,7 @@ in eval "$(sheldon source)" ''; - programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + programs.zsh.initContent = mkIf cfg.enableZshIntegration '' eval "$(sheldon source)" ''; diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix index f73da437..351a42b0 100644 --- a/modules/programs/ssh.nix +++ b/modules/programs/ssh.nix @@ -151,14 +151,7 @@ let identityFile = mkOption { type = with types; either (listOf str) (nullOr str); default = [ ]; - apply = - p: - if p == null then - [ ] - else if lib.isString p then - [ p ] - else - p; + apply = p: if p == null then [ ] else lib.toList p; description = '' Specifies files from which the user identity is read. Identities will be tried in the given order. @@ -168,14 +161,7 @@ let identityAgent = mkOption { type = with types; either (listOf str) (nullOr str); default = [ ]; - apply = - p: - if p == null then - [ ] - else if lib.isString p then - [ p ] - else - p; + apply = p: if p == null then [ ] else lib.toList p; description = '' Specifies the location of the ssh identity agent. ''; @@ -265,14 +251,7 @@ let certificateFile = mkOption { type = with types; either (listOf str) (nullOr str); default = [ ]; - apply = - p: - if p == null then - [ ] - else if lib.isString p then - [ p ] - else - p; + apply = p: if p == null then [ ] else lib.toList p; description = '' Specifies files from which the user certificate is read. ''; @@ -451,7 +430,13 @@ let ++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards ++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards ++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards - ++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions + ++ [ + (lib.generators.toKeyValue { + mkKeyValue = lib.generators.mkKeyValueDefault { } " "; + listsAsDuplicateKeys = true; + indent = " "; + } cf.extraOptions) + ] ); in diff --git a/modules/programs/television.nix b/modules/programs/television.nix index cec1c963..9286ce84 100644 --- a/modules/programs/television.nix +++ b/modules/programs/television.nix @@ -106,13 +106,13 @@ in ]; programs.bash.initExtra = lib.mkIf cfg.enableBashIntegration '' - eval "$(${lib.getExe cfg.package} init bash)" + source ${cfg.package}/share/television/completion.bash ''; programs.zsh.initContent = lib.mkIf cfg.enableZshIntegration '' - eval "$(${lib.getExe cfg.package} init zsh)" + source ${cfg.package}/share/television/completion.zsh ''; programs.fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration '' - ${lib.getExe cfg.package} init fish | source + source ${cfg.package}/share/television/completion.fish ''; }; } diff --git a/modules/programs/thunderbird.nix b/modules/programs/thunderbird.nix index 1fa1e8dc..83b0b2eb 100644 --- a/modules/programs/thunderbird.nix +++ b/modules/programs/thunderbird.nix @@ -9,6 +9,7 @@ let attrValues concatStringsSep filter + flatten length literalExpression mapAttrsToList @@ -918,7 +919,14 @@ in calendarAccounts = getAccountsForProfile name enabledCalendarAccountsWithId; contactAccounts = getAccountsForProfile name enabledContactAccountsWithId; - smtp = filter (a: a.smtp != null) emailAccounts; + accountsSmtp = filter (a: a.smtp != null) emailAccounts; + aliasesSmtp = + let + getAliasesWithSmtp = a: filter (al: builtins.isAttrs al && al.smtp != null) a.aliases; + getAliasesWithId = a: map (al: al // { id = getId a al; }) (getAliasesWithSmtp a); + in + flatten (map getAliasesWithId emailAccounts); + smtp = accountsSmtp ++ aliasesSmtp; feedAccounts = addId (attrValues profile.feedAccounts); diff --git a/modules/programs/ty.nix b/modules/programs/ty.nix new file mode 100644 index 00000000..b278ef7c --- /dev/null +++ b/modules/programs/ty.nix @@ -0,0 +1,51 @@ +{ + pkgs, + config, + lib, + ... +}: +let + inherit (lib) + mkEnableOption + mkPackageOption + mkOption + literalExpression + ; + + tomlFormat = pkgs.formats.toml { }; + cfg = config.programs.ty; +in +{ + meta.maintainers = with lib.maintainers; [ mirkolenz ]; + + options.programs.ty = { + enable = mkEnableOption "ty"; + + package = mkPackageOption pkgs "ty" { nullable = true; }; + + settings = mkOption { + type = tomlFormat.type; + default = { }; + example = literalExpression '' + { + rules.index-out-of-bounds = "ignore"; + } + ''; + description = '' + Configuration written to + {file}`$XDG_CONFIG_HOME/ty/ty.toml`. + See + and + for more information. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + + xdg.configFile."ty/ty.toml" = lib.mkIf (cfg.settings != { }) { + source = tomlFormat.generate "ty-config.toml" cfg.settings; + }; + }; +} diff --git a/modules/programs/vim.nix b/modules/programs/vim.nix index 6773c878..5f919dfa 100644 --- a/modules/programs/vim.nix +++ b/modules/programs/vim.nix @@ -162,7 +162,8 @@ in default = false; description = '' Whether to configure {command}`vim` as the default - editor using the {env}`EDITOR` environment variable. + editor using the {env}`EDITOR` and {env}`VISUAL` + environment variables. ''; }; }; @@ -213,7 +214,10 @@ in home.packages = [ cfg.package ]; - home.sessionVariables = lib.mkIf cfg.defaultEditor { EDITOR = "vim"; }; + home.sessionVariables = lib.mkIf cfg.defaultEditor { + EDITOR = "vim"; + VISUAL = "vim"; + }; programs.vim = { package = vim; diff --git a/modules/services/colima.nix b/modules/services/colima.nix new file mode 100644 index 00000000..da5f83f8 --- /dev/null +++ b/modules/services/colima.nix @@ -0,0 +1,266 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.colima; + yamlFormat = pkgs.formats.yaml { }; +in +{ + meta.maintainers = [ + lib.hm.maintainers.will-lol + ]; + + options.services.colima = { + enable = lib.mkEnableOption "Colima, a container runtime"; + + package = lib.mkPackageOption pkgs "colima" { }; + dockerPackage = lib.mkPackageOption pkgs "docker" { + extraDescription = "Used by colima to activate profiles. Not needed if no profile is set to isActive."; + }; + perlPackage = lib.mkPackageOption pkgs "perl" { + extraDescription = "Used by colima during image download for the shasum command."; + }; + sshPackage = lib.mkPackageOption pkgs "openssh" { + extraDescription = "Used by colima to manage the vm."; + }; + coreutilsPackage = lib.mkPackageOption pkgs "coreutils" { + extraDescription = "Used in various ways by colima."; + }; + curlPackage = lib.mkPackageOption pkgs "curl" { + extraDescription = "Used by colima to donwload images."; + }; + bashPackage = lib.mkPackageOption pkgs "bashNonInteractive" { + extraDescription = "Used by colima's internal scripts."; + }; + + profiles = lib.mkOption { + default = { + default = { + isActive = true; + isService = true; + }; + }; + description = '' + Profiles allow multiple colima configurations. The default profile is active by default. + If you have used colima before, you may need to delete existing configuration using `colima delete` or use a different profile. + + Note that removing a configured profile will not delete the corresponding Colima instance. + You will need to manually run `colima delete ` to remove the instance and release resources. + ''; + example = '' + { + default = { + isActive = true; + isService = true; + }; + rosetta = { + isService = true; + settings.rosetta = true; + }; + powerful = { + settings.cpu = 8; + }; + }; + ''; + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + options = { + name = lib.mkOption { + type = lib.types.str; + default = name; + readOnly = true; + description = "The profile's name."; + }; + + isService = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Whether this profile will run as a service. + ''; + }; + + isActive = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Whether to set this profile as: + - active docker context + - active kubernetes context + - active incus remote + Exactly one or zero profiles should have this option set. + ''; + }; + + logFile = lib.mkOption { + type = lib.types.path; + default = "${config.home.homeDirectory}/.local/state/colima-${name}.log"; + defaultText = lib.literalExpression "\${config.home.homeDirectory}/.local/state/colima-\${name}.log"; + description = "Combined stdout and stderr log file for the Colima service."; + }; + + settings = lib.mkOption { + inherit (yamlFormat) type; + default = { }; + description = "Colima configuration settings, see or run `colima template`."; + example = '' + { + cpu = 2; + disk = 100; + memory = 2; + arch = "host"; + runtime = "docker"; + hostname = null; + kubernetes = { + enabled = false; + version = "v1.33.3+k3s1"; + k3sArgs = [ "--disable=traefik" ]; + port = 0; + }; + autoActivate = true; + network = { + address = false; + mode = "shared"; + interface = "en0"; + preferredRoute = false; + dns = [ ]; + dnsHosts = { + "host.docker.internal" = "host.lima.internal"; + }; + hostAddresses = false; + }; + forwardAgent = false; + docker = { }; + vmType = "qemu"; + portForwarder = "ssh"; + rosetta = false; + binfmt = true; + nestedVirtualization = false; + mountType = "sshfs"; + mountInotify = false; + cpuType = "host"; + provision = [ ]; + sshConfig = true; + sshPort = 0; + mounts = [ ]; + diskImage = ""; + rootDisk = 20; + env = { }; + } + ''; + }; + }; + } + ) + ); + }; + }; + + config = lib.mkIf cfg.enable ({ + assertions = [ + { + assertion = (lib.count (p: p.isActive) (lib.attrValues cfg.profiles)) <= 1; + message = "Only one Colima profile can be active at a time."; + } + ]; + + home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + + home.file = lib.mkMerge ( + lib.mapAttrsToList (profileName: profile: { + ".colima/${profileName}/colima.yaml" = { + source = yamlFormat.generate "colima.yaml" profile.settings; + }; + }) (lib.filterAttrs (name: profile: profile.settings != { }) cfg.profiles) + ); + + programs.docker-cli.settings.currentContext = + let + activeProfile = lib.findFirst (p: p.isActive) null (lib.attrValues cfg.profiles); + in + lib.mkIf (activeProfile != null) ( + if activeProfile.name != "default" then "colima-${activeProfile.name}" else "colima" + ); + + launchd.agents = lib.mkIf pkgs.stdenv.isDarwin ( + lib.mapAttrs' ( + name: profile: + lib.nameValuePair "colima-${name}" { + enable = true; + config = { + ProgramArguments = [ + "${lib.getExe cfg.package}" + "start" + name + "-f" + "--activate=${if profile.isActive then "true" else "false"}" + "--save-config=false" + ]; + KeepAlive = true; + RunAtLoad = true; + EnvironmentVariables.PATH = lib.makeBinPath [ + cfg.package + cfg.perlPackage + cfg.dockerPackage + cfg.sshPackage + cfg.coreutilsPackage + cfg.curlPackage + cfg.bashPackage + pkgs.darwin.DarwinTools + ]; + StandardOutPath = profile.logFile; + StandardErrorPath = profile.logFile; + }; + } + ) (lib.filterAttrs (_: p: p.isService) cfg.profiles) + ); + + systemd.user.services = lib.mkIf pkgs.stdenv.isLinux ( + lib.mapAttrs' ( + name: profile: + lib.nameValuePair "colima-${name}" { + Unit = { + Description = "Colima container runtime (${name} profile)"; + After = [ "network-online.target" ]; + Wants = [ "network-online.target" ]; + }; + Service = { + ExecStart = '' + ${lib.getExe cfg.package} start ${name} \ + -f \ + --activate=${if profile.isActive then "true" else "false"} \ + --save-config=false + ''; + Restart = "always"; + RestartSec = 2; + Environment = [ + "PATH=${ + lib.makeBinPath [ + cfg.package + cfg.perlPackage + cfg.dockerPackage + cfg.sshPackage + cfg.coreutilsPackage + cfg.curlPackage + cfg.bashPackage + ] + }" + ]; + StandardOutput = "append:${profile.logFile}"; + StandardError = "append:${profile.logFile}"; + }; + Install = { + WantedBy = [ "default.target" ]; + }; + } + ) (lib.filterAttrs (_: p: p.isService) cfg.profiles) + ); + }); +} diff --git a/modules/services/emacs.nix b/modules/services/emacs.nix index 117c3b72..a7327199 100644 --- a/modules/services/emacs.nix +++ b/modules/services/emacs.nix @@ -113,7 +113,8 @@ in example = !default; description = '' Whether to configure {command}`emacsclient` as the default - editor using the {env}`EDITOR` environment variable. + editor using the {env}`EDITOR` and {env}`VISUAL` + environment variables. ''; }; }; @@ -121,11 +122,16 @@ in config = mkIf cfg.enable ( lib.mkMerge [ { - home.sessionVariables = mkIf cfg.defaultEditor { - EDITOR = lib.getBin ( - pkgs.writeShellScript "editor" ''exec ${lib.getBin cfg.package}/bin/emacsclient "''${@:---create-frame}"'' - ); - }; + home.sessionVariables = + let + editorBin = lib.getBin ( + pkgs.writeShellScript "editor" ''exec ${lib.getBin cfg.package}/bin/emacsclient "''${@:---create-frame}"'' + ); + in + mkIf cfg.defaultEditor { + EDITOR = editorBin; + VISUAL = editorBin; + }; } (mkIf pkgs.stdenv.isLinux { diff --git a/modules/services/hyprlauncher.nix b/modules/services/hyprlauncher.nix new file mode 100644 index 00000000..1fcc6b53 --- /dev/null +++ b/modules/services/hyprlauncher.nix @@ -0,0 +1,86 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) + mkIf + mkEnableOption + mkPackageOption + mkOption + ; + + cfg = config.services.hyprlauncher; +in +{ + meta.maintainers = with lib.hm.maintainers; [ aguirre-matteo ]; + + options.services.hyprlauncher = { + enable = mkEnableOption "hyprlauncher"; + package = mkPackageOption pkgs "hyprlauncher" { nullable = true; }; + settings = mkOption { + type = + with lib.types; + let + valueType = + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Hyprland configuration value"; + }; + in + valueType; + default = { }; + example = { + general.grab_focus = true; + cache.enabled = true; + ui.window_size = "400 260"; + finders = { + math_prefix = "="; + desktop_icons = true; + }; + }; + description = '' + Configuration settings for hyprlauncher. All the available options can be found here: + + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "services.hyprlauncher" pkgs lib.platforms.linux) + ]; + + home.packages = mkIf (cfg.package != null) [ cfg.package ]; + xdg.configFile."hypr/hyprlauncher.conf" = mkIf (cfg.settings != { }) { + text = lib.hm.generators.toHyprconf { attrs = cfg.settings; }; + }; + systemd.user.services.hyprlauncher = mkIf (cfg.package != null) { + Install.WantedBy = [ config.wayland.systemd.target ]; + Unit = { + Description = "hyprlauncher"; + After = [ config.wayland.systemd.target ]; + PartOf = [ config.wayland.systemd.target ]; + X-Restart-Triggers = lib.mkIf (cfg.settings != { }) [ + "${config.xdg.configFile."hypr/hyprlauncher.conf".source}" + ]; + }; + + Service = { + ExecStart = "${lib.getExe cfg.package} -d"; + Restart = "always"; + RestartSec = "10"; + }; + }; + }; +} diff --git a/modules/services/ludusavi.nix b/modules/services/ludusavi.nix index c301b82b..ab5602dc 100644 --- a/modules/services/ludusavi.nix +++ b/modules/services/ludusavi.nix @@ -49,9 +49,17 @@ in default = { manifest.url = "https://raw.githubusercontent.com/mtkennerly/ludusavi-manifest/master/data/manifest.yaml"; roots = [ ]; - backup.path = "$XDG_STATE_HOME/backups/ludusavi"; - restore.path = "$XDG_STATE_HOME/backups/ludusavi"; + backup.path = "${config.xdg.stateHome}/backups/ludusavi"; + restore.path = "${config.xdg.stateHome}/backups/ludusavi"; }; + defaultText = '' + { + manifest.url = "https://raw.githubusercontent.com/mtkennerly/ludusavi-manifest/master/data/manifest.yaml"; + roots = [ ]; + backup.path = "$XDG_STATE_HOME/backups/ludusavi"; + restore.path = "$XDG_STATE_HOME/backups/ludusavi"; + } + ''; example = { language = "en-US"; theme = "light"; @@ -94,6 +102,24 @@ in Service = { Type = "oneshot"; ExecStart = "${lib.getExe cfg.package} backup --force"; + ExecStartPre = "${pkgs.writeShellScript "ludusavi-migrate-backup" '' + old_base_dir="${config.home.homeDirectory}/\$XDG_STATE_HOME" + old_dir="$old_base_dir/backups/ludusavi" + new_base_dir="${config.xdg.stateHome}/backups" + new_dir="$new_base_dir/ludusavi" + + if [[ -d "$old_base_dir" ]]; then + echo "Migrating old Ludusavi's backup... (See home-manager/#8234)" + if [[ ! -d "$new_base_dir" ]]; then + mkdir -p "$new_base_dir" + fi + + mv "$old_dir" "$new_dir" + rmdir "$old_base_dir/backups" + rmdir "$old_base_dir" + echo "Migration completed successfully." + fi + ''}"; } // lib.optionalAttrs cfg.backupNotification { ExecStartPost = "${lib.getExe pkgs.libnotify} 'Ludusavi' 'Backup completed' -i com.mtkennerly.ludusavi -a 'Ludusavi'"; diff --git a/modules/services/pimsync.nix b/modules/services/pimsync.nix index 99eac8d1..b2cda644 100644 --- a/modules/services/pimsync.nix +++ b/modules/services/pimsync.nix @@ -43,6 +43,7 @@ in Description = "pimsync calendar and contacts synchronization"; PartOf = [ "network-online.target" ]; }; + Install.WantedBy = [ "default.target" ]; Service = { # TODO: make use of the readiness notification Type = "simple"; diff --git a/modules/services/restic.nix b/modules/services/restic.nix index 1fc6e9f7..69c3dbd8 100644 --- a/modules/services/restic.nix +++ b/modules/services/restic.nix @@ -475,7 +475,7 @@ in CacheDirectoryMode = "0700"; PrivateTmp = true; - Environment = mkEnvironment backup ++ [ "RESTIC_CACHE_DIR=%C" ]; + Environment = mkEnvironment backup ++ [ "RESTIC_CACHE_DIR=%C/${serviceName}" ]; ExecStart = lib.optional doBackup backupCmd @@ -591,7 +591,7 @@ in lib.concatLines ]} - RESTIC_CACHE_DIR=$HOME/.cache/${serviceName} + RESTIC_CACHE_DIR=${config.xdg.cacheHome}/${serviceName} PATH=${ lib.pipe environment [ diff --git a/modules/services/snixembed.nix b/modules/services/snixembed.nix index e9b1572e..4f55b200 100644 --- a/modules/services/snixembed.nix +++ b/modules/services/snixembed.nix @@ -7,6 +7,7 @@ let cfg = config.services.snixembed; + waybarCfg = config.programs.waybar; in { meta.maintainers = [ lib.maintainers.DamienCassou ]; @@ -32,6 +33,10 @@ in assertions = [ (lib.hm.assertions.assertPlatform "services.snixembed" pkgs lib.platforms.linux) ]; + warnings = lib.optional waybarCfg.enable '' + snixembed and waybar should not be enabled at the same time. + You may experience inconsistent tray behavior as a result. + ''; systemd.user.services.snixembed = { Install.WantedBy = [ "graphical-session.target" ]; diff --git a/modules/services/ssh-agent.nix b/modules/services/ssh-agent.nix index 35714e2b..8bb68a40 100644 --- a/modules/services/ssh-agent.nix +++ b/modules/services/ssh-agent.nix @@ -84,13 +84,11 @@ in ''; in { - bash.initExtra = lib.mkIf cfg.enableBashIntegration bashIntegration; - - zsh.initContent = lib.mkIf cfg.enableZshIntegration bashIntegration; - - fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration fishIntegration; - - nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration nushellIntegration; + # $SSH_AUTH_SOCK has to be set early since other tools rely on it + bash.profileExtra = lib.mkIf cfg.enableBashIntegration (lib.mkOrder 900 bashIntegration); + fish.shellInit = lib.mkIf cfg.enableFishIntegration (lib.mkOrder 900 fishIntegration); + nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration (lib.mkOrder 900 nushellIntegration); + zsh.envExtra = lib.mkIf cfg.enableZshIntegration (lib.mkOrder 900 bashIntegration); }; } diff --git a/modules/services/swayidle.nix b/modules/services/swayidle.nix index 481dd440..3b96da01 100644 --- a/modules/services/swayidle.nix +++ b/modules/services/swayidle.nix @@ -42,21 +42,30 @@ in }; }; - eventModule = { + eventsModule = { options = { - event = mkOption { - type = types.enum [ - "before-sleep" - "after-resume" - "lock" - "unlock" - ]; - description = "Event name."; + before-sleep = mkOption { + type = types.nullOr types.str; + default = null; + description = "Command to run before suspending."; }; - command = mkOption { - type = types.str; - description = "Command to run when event occurs."; + after-resume = mkOption { + type = types.nullOr types.str; + default = null; + description = "Command to run after resuming."; + }; + + lock = mkOption { + type = types.nullOr types.str; + default = null; + description = "Command to run when the logind session is locked."; + }; + + unlock = mkOption { + type = types.nullOr types.str; + default = null; + description = "Command to run when the logind session is unlocked."; }; }; }; @@ -80,13 +89,31 @@ in }; events = mkOption { - type = with types; listOf (submodule eventModule); - default = [ ]; + type = + with types; + (coercedTo (listOf attrs)) ( + events: + lib.warn + '' + The syntax of services.swayidle.events has changed. While it + previously accepted a list of events, it now accepts an attrset + keyed by the event name. + '' + ( + lib.listToAttrs ( + map (e: { + name = e.event; + value = e.command; + }) events + ) + ) + ) (submodule eventsModule); + default = { }; example = literalExpression '' - [ - { event = "before-sleep"; command = "''${pkgs.swaylock}/bin/swaylock -fF"; } - { event = "lock"; command = "lock"; } - ] + { + "before-sleep" = "''${pkgs.swaylock}/bin/swaylock -fF"; + "lock" = "lock"; + } ''; description = "Run command on occurrence of a event."; }; @@ -144,13 +171,17 @@ in t.resumeCommand ]; - mkEvent = e: [ - e.event - e.command + mkEvent = event: command: [ + event + command ]; + nonemptyEvents = lib.filterAttrs (event: command: command != null) cfg.events; + args = - cfg.extraArgs ++ (lib.concatMap mkTimeout cfg.timeouts) ++ (lib.concatMap mkEvent cfg.events); + cfg.extraArgs + ++ (lib.concatMap mkTimeout cfg.timeouts) + ++ (lib.flatten (lib.mapAttrsToList mkEvent nonemptyEvents)); in "${lib.getExe cfg.package} ${lib.escapeShellArgs args}"; }; diff --git a/modules/services/tldr-update.nix b/modules/services/tldr-update.nix index 4c4e73db..d5fb96a6 100644 --- a/modules/services/tldr-update.nix +++ b/modules/services/tldr-update.nix @@ -8,7 +8,7 @@ let cfg = config.services.tldr-update; in { - meta.maintainers = [ lib.maintainers.perchun ]; + meta.maintainers = [ lib.maintainers.PerchunPak ]; options.services.tldr-update = { enable = lib.mkEnableOption '' diff --git a/modules/services/window-managers/herbstluftwm.nix b/modules/services/window-managers/herbstluftwm.nix index c004564b..3ec19304 100644 --- a/modules/services/window-managers/herbstluftwm.nix +++ b/modules/services/window-managers/herbstluftwm.nix @@ -119,6 +119,24 @@ in {file}`$XDG_CONFIG_HOME/herbstluftwm/autostart`. ''; }; + + enableAlias = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Set an alias for the {command}`herbstclient` command in the + {file}`autostart` script that only stores its arguments and executes + them all at once at the end of the {file}`autostart` script. + + This reduces the amount of flickering you get while all options are + being applied and improves the performance. + + On the other hand, this makes it more difficult to write bash functions + that call {command}`herbstclient`. You can work around this by calling + {command}`command herbstclient` in your functions to still get some of + the benefits of enabling this alias. + ''; + }; }; config = lib.mkIf cfg.enable { @@ -131,11 +149,13 @@ in xsession.windowManager.command = "${cfg.package}/bin/herbstluftwm --locked"; xdg.configFile."herbstluftwm/autostart".source = pkgs.writeShellScript "herbstluftwm-autostart" '' - shopt -s expand_aliases + ${lib.optionalString cfg.enableAlias '' + shopt -s expand_aliases - # shellcheck disable=SC2142 - alias herbstclient='set -- "$@" ";"' - set -- + # shellcheck disable=SC2142 + alias herbstclient='set -- "$@" ";"' + set -- + ''} herbstclient emit_hook reload @@ -169,7 +189,9 @@ in herbstclient unlock - ${cfg.package}/bin/herbstclient chain ";" "$@" + ${lib.optionalString cfg.enableAlias '' + ${cfg.package}/bin/herbstclient chain ";" "$@" + ''} ''; }; } diff --git a/nixos/common.nix b/nixos/common.nix index b5bcfa57..3edecffb 100644 --- a/nixos/common.nix +++ b/nixos/common.nix @@ -2,7 +2,6 @@ # For OS-specific configuration, please edit nixos/default.nix or nix-darwin/default.nix instead. { - options, config, lib, pkgs, @@ -36,7 +35,7 @@ let modules = [ ( - { name, ... }: + { name, options, ... }: { imports = import ../modules/modules.nix { @@ -48,22 +47,31 @@ let ++ cfg.sharedModules; config = { - submoduleSupport.enable = true; - submoduleSupport.externalPackageInstall = cfg.useUserPackages; + submoduleSupport = { + enable = true; + externalPackageInstall = cfg.useUserPackages; + }; - home.username = config.users.users.${name}.name; - home.homeDirectory = config.users.users.${name}.home; + home = { + username = config.users.users.${name}.name; + homeDirectory = config.users.users.${name}.home; + uid = mkIf (options.users.users.${name}.uid.isDefined or false) config.users.users.${name}.uid; + }; - # Forward `nix.enable` from the OS configuration. The - # conditional is to check whether nix-darwin is new enough - # to have the `nix.enable` option; it was previously a - # `mkRemovedOptionModule` error, which we can crudely detect - # by `visible` being set to `false`. - nix.enable = mkIf (options.nix.enable.visible or true) config.nix.enable; + nix = { + # Forward `nix.enable` from the OS configuration. The + # conditional is to check whether nix-darwin is new enough + # to have the `nix.enable` option; it was previously a + # `mkRemovedOptionModule` error, which we can crudely detect + # by `visible` being set to `false`. + enable = mkIf (options.nix.enable.visible or true) config.nix.enable; - # Make activation script use same version of Nix as system as a whole. - # This avoids problems with Nix not being in PATH. - nix.package = config.nix.package; + # Make activation script use same version of Nix as system as a whole. + # This avoids problems with Nix not being in PATH. + # Only set package when nix is enabled to avoid errors when + # nix-darwin has nix.enable = false (e.g., Determinate Nix users). + package = mkIf config.nix.enable config.nix.package; + }; }; } ) @@ -160,30 +168,28 @@ in }; }; - config = ( - lib.mkMerge [ - # Fix potential recursion when configuring home-manager users based on values in users.users #594 - (mkIf (cfg.useUserPackages && cfg.users != { }) { - users.users = (lib.mapAttrs (_username: usercfg: { packages = [ usercfg.home.path ]; }) cfg.users); - environment.pathsToLink = [ "/etc/profile.d" ]; - }) - (mkIf (cfg.users != { }) { - warnings = lib.flatten ( - flip lib.mapAttrsToList cfg.users ( - user: config: flip map config.warnings (warning: "${user} profile: ${warning}") - ) - ); + config = lib.mkMerge [ + # Fix potential recursion when configuring home-manager users based on values in users.users #594 + (mkIf (cfg.useUserPackages && cfg.users != { }) { + users.users = lib.mapAttrs (_username: usercfg: { packages = [ usercfg.home.path ]; }) cfg.users; + environment.pathsToLink = [ "/etc/profile.d" ]; + }) + (mkIf (cfg.users != { }) { + warnings = lib.flatten ( + flip lib.mapAttrsToList cfg.users ( + user: config: flip map config.warnings (warning: "${user} profile: ${warning}") + ) + ); - assertions = lib.flatten ( - flip lib.mapAttrsToList cfg.users ( - user: config: - flip map config.assertions (assertion: { - inherit (assertion) assertion; - message = "${user} profile: ${assertion.message}"; - }) - ) - ); - }) - ] - ); + assertions = lib.flatten ( + flip lib.mapAttrsToList cfg.users ( + user: config: + flip map config.assertions (assertion: { + inherit (assertion) assertion; + message = "${user} profile: ${assertion.message}"; + }) + ) + ); + }) + ]; } diff --git a/tests/darwinScrublist.nix b/tests/darwinScrublist.nix index 128609ae..ec8cafd3 100644 --- a/tests/darwinScrublist.nix +++ b/tests/darwinScrublist.nix @@ -27,6 +27,7 @@ let "broot" "browserpass" "btop" + "calibre" "carapace" "cava" "claude-code" @@ -96,6 +97,7 @@ let "lf" "lieer" "lsd" + "ludusavi" "mbsync" "meli" "mergiraf" diff --git a/tests/integration/standalone/restic.nix b/tests/integration/standalone/restic.nix index aa45acfc..7298facb 100644 --- a/tests/integration/standalone/restic.nix +++ b/tests/integration/standalone/restic.nix @@ -216,7 +216,7 @@ in def make_backup(time): global snapshot_count - machine.succeed(f"timedatectl set-time '{time}'") + machine.succeed(f"date --set='{time}'") systemctl_succeed_as_alice("start restic-backups-prune-me.service") snapshot_count += 1 actual = \ diff --git a/tests/modules/home-environment/default.nix b/tests/modules/home-environment/default.nix index c38ad5ce..c8dc446b 100644 --- a/tests/modules/home-environment/default.nix +++ b/tests/modules/home-environment/default.nix @@ -2,4 +2,7 @@ home-session-path = ./session-path.nix; home-session-search-variables = ./session-search-variables.nix; home-session-variables = ./session-variables.nix; + home-nixpkgs-release-check-pkgs = ./nixpkgs-release-check-pkgs.nix; + home-uid = ./uid.nix; + home-uid-null = ./uid-null.nix; } diff --git a/tests/modules/home-environment/nixpkgs-release-check-pkgs.nix b/tests/modules/home-environment/nixpkgs-release-check-pkgs.nix new file mode 100644 index 00000000..d0ad01c5 --- /dev/null +++ b/tests/modules/home-environment/nixpkgs-release-check-pkgs.nix @@ -0,0 +1,39 @@ +{ lib, ... }: +let + releaseInfo = lib.importJSON ../../../release.json; + hmRelease = releaseInfo.release; + pkgsRelease = ""; +in +{ + test.asserts.warnings.expected = [ + '' + You are using + + Home Manager version: ${hmRelease} + Nixpkgs version used to evaluate Home Manager: ${hmRelease} + Nixpkgs version used for packages (`pkgs`): ${pkgsRelease} + + Using mismatched versions is likely to cause errors and unexpected + behavior. It is therefore highly recommended to use a release of Home + Manager that corresponds with your chosen release of Nixpkgs. + + If you insist then you can disable this warning by adding + + home.enableNixpkgsReleaseCheck = false; + + to your configuration. + '' + ]; + + nixpkgs.overlays = [ + (final: prev: { + lib = prev.lib.extend ( + final: prev: { + trivial = prev.trivial // { + release = pkgsRelease; + }; + } + ); + }) + ]; +} diff --git a/tests/modules/home-environment/uid-null.nix b/tests/modules/home-environment/uid-null.nix new file mode 100644 index 00000000..6f1b915c --- /dev/null +++ b/tests/modules/home-environment/uid-null.nix @@ -0,0 +1,7 @@ +{ + # home.uid defaults to null, so checkUid should not be called in the activation script + + nmt.script = '' + assertFileNotRegex activate "checkUid [0-9]+" + ''; +} diff --git a/tests/modules/home-environment/uid.nix b/tests/modules/home-environment/uid.nix new file mode 100644 index 00000000..06093971 --- /dev/null +++ b/tests/modules/home-environment/uid.nix @@ -0,0 +1,7 @@ +{ + home.uid = 1000; + + nmt.script = '' + assertFileContains activate "checkUid 1000" + ''; +} diff --git a/tests/modules/misc/qt/default.nix b/tests/modules/misc/qt/default.nix index 88ba03f4..bfdfea91 100644 --- a/tests/modules/misc/qt/default.nix +++ b/tests/modules/misc/qt/default.nix @@ -4,4 +4,7 @@ qt-platform-theme-gtk3 = ./qt-platform-theme-gtk3.nix; qt-platform-theme-gnome = ./qt-platform-theme-gnome.nix; qt-platform-theme-kde6-migration = ./qt-platform-theme-kde6-migration.nix; + qt-qt5ct-settings = ./qt-qt5ct-settings.nix; + qt-qt6ct-settings = ./qt-qt6ct-settings.nix; + qt-qtct-settings = ./qt-qtct-settings.nix; } diff --git a/tests/modules/misc/qt/qt-qt5ct-settings.nix b/tests/modules/misc/qt/qt-qt5ct-settings.nix new file mode 100644 index 00000000..152cdbf6 --- /dev/null +++ b/tests/modules/misc/qt/qt-qt5ct-settings.nix @@ -0,0 +1,13 @@ +{ + qt = { + enable = true; + qt5ctSettings = { + test_section.test_option = "test"; + }; + }; + + nmt.script = '' + assertFileExists "home-files/.config/qt5ct/qt5ct.conf" + assertPathNotExists "home-files/.config/qt6ct/qt6ct.conf" + ''; +} diff --git a/tests/modules/misc/qt/qt-qt6ct-settings.nix b/tests/modules/misc/qt/qt-qt6ct-settings.nix new file mode 100644 index 00000000..d363d881 --- /dev/null +++ b/tests/modules/misc/qt/qt-qt6ct-settings.nix @@ -0,0 +1,13 @@ +{ + qt = { + enable = true; + qt6ctSettings = { + test_section.test_option = "test"; + }; + }; + + nmt.script = '' + assertFileExists "home-files/.config/qt6ct/qt6ct.conf" + assertPathNotExists "home-files/.config/qt5ct/qt5ct.conf" + ''; +} diff --git a/tests/modules/misc/qt/qt-qtct-settings.nix b/tests/modules/misc/qt/qt-qtct-settings.nix new file mode 100644 index 00000000..e5805009 --- /dev/null +++ b/tests/modules/misc/qt/qt-qtct-settings.nix @@ -0,0 +1,16 @@ +{ + qt = { + enable = true; + qt5ctSettings = { + test_section.test_option = "test"; + }; + qt6ctSettings = { + test_section.test_option = "test"; + }; + }; + + nmt.script = '' + assertFileExists "home-files/.config/qt5ct/qt5ct.conf" + assertFileExists "home-files/.config/qt6ct/qt6ct.conf" + ''; +} diff --git a/tests/modules/programs/anki/default.nix b/tests/modules/programs/anki/default.nix index a4fd8d9a..baccadfa 100644 --- a/tests/modules/programs/anki/default.nix +++ b/tests/modules/programs/anki/default.nix @@ -1,7 +1,4 @@ -{ lib, pkgs, ... }: - -# Anki is currently marked as broken on Darwin (2025/06/23) -lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { +{ anki-minimal-config = ./minimal-config.nix; anki-full-config = ./full-config.nix; } diff --git a/tests/modules/programs/calibre/default.nix b/tests/modules/programs/calibre/default.nix new file mode 100644 index 00000000..1b21316c --- /dev/null +++ b/tests/modules/programs/calibre/default.nix @@ -0,0 +1 @@ +{ calibre-settings = ./settings.nix; } diff --git a/tests/modules/programs/calibre/plugins/a/a.zip b/tests/modules/programs/calibre/plugins/a/a.zip new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/programs/calibre/plugins/b/b.zip b/tests/modules/programs/calibre/plugins/b/b.zip new file mode 100644 index 00000000..e69de29b diff --git a/tests/modules/programs/calibre/settings.nix b/tests/modules/programs/calibre/settings.nix new file mode 100644 index 00000000..1aae893c --- /dev/null +++ b/tests/modules/programs/calibre/settings.nix @@ -0,0 +1,14 @@ +{ + programs.calibre = { + enable = true; + plugins = [ + ./plugins/a + ./plugins/b + ]; + }; + + nmt.script = '' + assertFileExists home-files/.config/calibre/plugins/a.zip + assertFileExists home-files/.config/calibre/plugins/b.zip + ''; +} diff --git a/tests/modules/programs/cargo/empty-config.nix b/tests/modules/programs/cargo/empty-config.nix index 78aa00f8..63357112 100644 --- a/tests/modules/programs/cargo/empty-config.nix +++ b/tests/modules/programs/cargo/empty-config.nix @@ -1,5 +1,5 @@ { - programs.docker-cli = { + programs.cargo = { settings = { net = { git-fetch-with-cli = true; diff --git a/tests/modules/programs/claude-code/default.nix b/tests/modules/programs/claude-code/default.nix index 7ff4a997..315a1975 100644 --- a/tests/modules/programs/claude-code/default.nix +++ b/tests/modules/programs/claude-code/default.nix @@ -5,6 +5,8 @@ claude-code-assertion = ./assertion.nix; claude-code-memory-management = ./memory-management.nix; claude-code-memory-from-source = ./memory-from-source.nix; + claude-code-rules-dir = ./rules-dir.nix; + claude-code-rules-path = ./rules-path.nix; claude-code-agents-dir = ./agents-dir.nix; claude-code-commands-dir = ./commands-dir.nix; claude-code-hooks-dir = ./hooks-dir.nix; diff --git a/tests/modules/programs/claude-code/expected-mcp-wrapper b/tests/modules/programs/claude-code/expected-mcp-wrapper index 636f0ff9..7a582173 100644 --- a/tests/modules/programs/claude-code/expected-mcp-wrapper +++ b/tests/modules/programs/claude-code/expected-mcp-wrapper @@ -1,2 +1,2 @@ #! /nix/store/00000000000000000000000000000000-bash/bin/bash -e -exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" --mcp-config /nix/store/00000000000000000000000000000000-claude-code-mcp-config.json "$@" +exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" "$@" --mcp-config /nix/store/00000000000000000000000000000000-claude-code-mcp-config.json diff --git a/tests/modules/programs/claude-code/rules-dir.nix b/tests/modules/programs/claude-code/rules-dir.nix new file mode 100644 index 00000000..0716b85b --- /dev/null +++ b/tests/modules/programs/claude-code/rules-dir.nix @@ -0,0 +1,14 @@ +{ + programs.claude-code = { + enable = true; + rulesDir = ./rules; + }; + + nmt.script = '' + assertFileExists home-files/.claude/rules/test-rule.md + assertLinkExists home-files/.claude/rules/test-rule.md + assertFileContent \ + home-files/.claude/rules/test-rule.md \ + ${./rules/test-rule.md} + ''; +} diff --git a/tests/modules/programs/claude-code/rules-path.nix b/tests/modules/programs/claude-code/rules-path.nix new file mode 100644 index 00000000..474280bd --- /dev/null +++ b/tests/modules/programs/claude-code/rules-path.nix @@ -0,0 +1,20 @@ +{ + programs.claude-code = { + enable = true; + rules = { + test-rule = ./test-rule.md; + inline-rule = '' + # Inline Rule + + This is an inline rule for testing. + ''; + }; + }; + + nmt.script = '' + assertFileExists home-files/.claude/rules/test-rule.md + assertFileContent home-files/.claude/rules/test-rule.md \ + ${./test-rule.md} + assertFileExists home-files/.claude/rules/inline-rule.md + ''; +} diff --git a/tests/modules/programs/claude-code/rules/test-rule.md b/tests/modules/programs/claude-code/rules/test-rule.md new file mode 100644 index 00000000..fc873169 --- /dev/null +++ b/tests/modules/programs/claude-code/rules/test-rule.md @@ -0,0 +1,9 @@ +# Test Rule from Directory + +This is a test rule loaded from a directory. +Used to verify rulesDir support functionality. + +## Best Practices + +- Write clean code +- Test thoroughly diff --git a/tests/modules/programs/claude-code/test-rule.md b/tests/modules/programs/claude-code/test-rule.md new file mode 100644 index 00000000..273fad52 --- /dev/null +++ b/tests/modules/programs/claude-code/test-rule.md @@ -0,0 +1,9 @@ +# Test Rule + +This is a test rule loaded from a file path. +Used to verify path support functionality for rules. + +## Guidelines + +- Follow test conventions +- Maintain code quality diff --git a/tests/modules/programs/diff-highlight/default.nix b/tests/modules/programs/diff-highlight/default.nix index 5bdbe52d..8764b8c5 100644 --- a/tests/modules/programs/diff-highlight/default.nix +++ b/tests/modules/programs/diff-highlight/default.nix @@ -2,4 +2,5 @@ diff-highlight-basic = ./diff-highlight-basic.nix; diff-highlight-with-git-integration = ./diff-highlight-with-git-integration.nix; diff-highlight-migration = ./diff-highlight-migration.nix; + diff-highlight-git-package-null-assertion = ./diff-highlight-git-package-null-assertion.nix; } diff --git a/tests/modules/programs/diff-highlight/diff-highlight-git-package-null-assertion.nix b/tests/modules/programs/diff-highlight/diff-highlight-git-package-null-assertion.nix new file mode 100644 index 00000000..52e795bc --- /dev/null +++ b/tests/modules/programs/diff-highlight/diff-highlight-git-package-null-assertion.nix @@ -0,0 +1,18 @@ +{ + programs.diff-highlight = { + enable = true; + enableGitIntegration = true; + }; + + programs.git = { + enable = true; + package = null; + }; + + test.asserts.assertions.expected = [ + '' + programs.diff-highlight.enableGitIntegration requires programs.git.package to be set. + Please set programs.git.package to a valid git package. + '' + ]; +} diff --git a/tests/modules/programs/gemini-cli/context.nix b/tests/modules/programs/gemini-cli/context.nix index f4b547ac..6fbac0ea 100644 --- a/tests/modules/programs/gemini-cli/context.nix +++ b/tests/modules/programs/gemini-cli/context.nix @@ -41,5 +41,9 @@ assertFileExists home-files/.gemini/CONTEXT.md assertFileContent home-files/.gemini/CONTEXT.md \ ${./context-additional.md} + + assertFileExists home-path/etc/profile.d/hm-session-vars.sh + assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh \ + "GEMINI_MODEL" ''; } diff --git a/tests/modules/programs/gemini-cli/settings.nix b/tests/modules/programs/gemini-cli/settings.nix index f78a14f6..80a4344a 100644 --- a/tests/modules/programs/gemini-cli/settings.nix +++ b/tests/modules/programs/gemini-cli/settings.nix @@ -1,6 +1,7 @@ { programs.gemini-cli = { enable = true; + defaultModel = "gemini-2.5-flash"; settings = { theme = "Default"; vimMode = true; @@ -28,5 +29,9 @@ ${./changelog.toml} assertFileContent home-files/.gemini/commands/git/fix.toml \ ${./fix.toml} + + assertFileExists home-path/etc/profile.d/hm-session-vars.sh + assertFileContains home-path/etc/profile.d/hm-session-vars.sh \ + 'export GEMINI_MODEL="gemini-2.5-flash"' ''; } diff --git a/tests/modules/programs/infat/default.nix b/tests/modules/programs/infat/default.nix new file mode 100644 index 00000000..8fdd5ecc --- /dev/null +++ b/tests/modules/programs/infat/default.nix @@ -0,0 +1,6 @@ +{ lib, pkgs, ... }: + +lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin { + infat-example-settings = ./example-settings.nix; + infat-no-settings = ./no-settings.nix; +} diff --git a/tests/modules/programs/infat/example-settings.nix b/tests/modules/programs/infat/example-settings.nix new file mode 100644 index 00000000..a9980d78 --- /dev/null +++ b/tests/modules/programs/infat/example-settings.nix @@ -0,0 +1,39 @@ +{ pkgs, ... }: + +{ + programs.infat = { + enable = true; + settings = { + extensions = { + md = "TextEdit"; + }; + schemes = { + web = "Safari"; + }; + types = { + plain-text = "VSCode"; + }; + }; + }; + + test.stubs.infat = { }; + + nmt.script = + let + expectedConfigPath = "home-files/.config/infat/config.toml"; + expectedConfigContent = pkgs.writeText "infat.config.expected" '' + [extensions] + md = "TextEdit" + + [schemes] + web = "Safari" + + [types] + plain-text = "VSCode" + ''; + in + '' + assertFileExists "${expectedConfigPath}" + assertFileContent "${expectedConfigPath}" "${expectedConfigContent}" + ''; +} diff --git a/tests/modules/programs/infat/no-settings.nix b/tests/modules/programs/infat/no-settings.nix new file mode 100644 index 00000000..fdb970a4 --- /dev/null +++ b/tests/modules/programs/infat/no-settings.nix @@ -0,0 +1,13 @@ +{ + programs.infat.enable = true; + + test.stubs.infat = { }; + + nmt.script = + let + expectedConfigPath = "home-files/.config/infat/config.toml"; + in + '' + assertPathNotExists "${expectedConfigPath}" + ''; +} diff --git a/tests/modules/programs/less/correct-option-order.nix b/tests/modules/programs/less/correct-option-order.nix new file mode 100644 index 00000000..6376898d --- /dev/null +++ b/tests/modules/programs/less/correct-option-order.nix @@ -0,0 +1,30 @@ +{ lib, ... }: + +{ + programs.less = { + enable = true; + options = lib.mkMerge [ + { + quiet = true; + use-color = true; + } + (lib.mkAfter { + color = [ + "HkK" # header: gray + "Mkb" # marks: blue + ]; + }) + (lib.mkOrder 2000 { + prompt = "s%f"; + }) + ]; + }; + + nmt.script = '' + assertFileExists home-files/.config/lesskey + assertFileContent home-files/.config/lesskey ${builtins.toFile "lesskey.expected" '' + #env + LESS = --quiet --use-color --color HkK --color Mkb --prompt s%f + ''} + ''; +} diff --git a/tests/modules/programs/less/custom-options-and-config.nix b/tests/modules/programs/less/custom-options-and-config.nix index 421a55cd..787c8473 100644 --- a/tests/modules/programs/less/custom-options-and-config.nix +++ b/tests/modules/programs/less/custom-options-and-config.nix @@ -12,7 +12,10 @@ in options = { RAW-CONTROL-CHARS = true; quiet = true; - wheel-lines = 3; + wheel-lines = [ + 3 + 1 + ]; }; }; @@ -20,7 +23,7 @@ in assertFileExists home-files/.config/lesskey assertFileContent home-files/.config/lesskey ${builtins.toFile "less.expected" '' #env - LESS = --RAW-CONTROL-CHARS --quiet --wheel-lines 3 + LESS = --RAW-CONTROL-CHARS --quiet --wheel-lines 3 --wheel-lines 1 ${config}''} ''; diff --git a/tests/modules/programs/less/custom-options.nix b/tests/modules/programs/less/custom-options.nix index 2e97f7d6..81abfd96 100644 --- a/tests/modules/programs/less/custom-options.nix +++ b/tests/modules/programs/less/custom-options.nix @@ -4,7 +4,10 @@ options = { RAW-CONTROL-CHARS = true; quiet = true; - wheel-lines = 3; + wheel-lines = [ + 3 + 1 + ]; }; }; @@ -12,7 +15,7 @@ assertFileExists home-files/.config/lesskey assertFileContent home-files/.config/lesskey ${builtins.toFile "lesskey.expected" '' #env - LESS = --RAW-CONTROL-CHARS --quiet --wheel-lines 3 + LESS = --RAW-CONTROL-CHARS --quiet --wheel-lines 3 --wheel-lines 1 ''} ''; } diff --git a/tests/modules/programs/less/default.nix b/tests/modules/programs/less/default.nix index 675c1a6c..08bd2e07 100644 --- a/tests/modules/programs/less/default.nix +++ b/tests/modules/programs/less/default.nix @@ -1,4 +1,5 @@ { + less-correct-option-order = ./correct-option-order.nix; less-custom-config = ./custom-config.nix; less-custom-options = ./custom-options.nix; less-custom-options-and-config = ./custom-options-and-config.nix; diff --git a/tests/modules/programs/neovim/default.nix b/tests/modules/programs/neovim/default.nix index d3224666..0fa15979 100644 --- a/tests/modules/programs/neovim/default.nix +++ b/tests/modules/programs/neovim/default.nix @@ -2,6 +2,7 @@ neovim-plugin-config = ./plugin-config.nix; neovim-coc-config = ./coc-config.nix; neovim-runtime = ./runtime.nix; + neovim-wrapper-args = ./wrapper-args.nix; # waiting for a nixpkgs patch neovim-no-init = ./no-init.nix; diff --git a/tests/modules/programs/neovim/plugin-config.nix b/tests/modules/programs/neovim/plugin-config.nix index 466cede4..5b198ded 100644 --- a/tests/modules/programs/neovim/plugin-config.nix +++ b/tests/modules/programs/neovim/plugin-config.nix @@ -34,15 +34,25 @@ lib.mkIf config.test.enableBig { _module.args.pkgs = lib.mkForce realPkgs; - nmt.script = '' - vimout=$(mktemp) - echo "redir >> /dev/stdout | echo g:hmExtraConfig | echo g:hmPlugins | redir END" \ - | ${pkgs.neovim}/bin/nvim -es -u "$TESTED/home-files/.config/nvim/init.lua" \ - > "$vimout" || true - assertFileContains "$vimout" "HM_EXTRA_CONFIG" - assertFileContains "$vimout" "HM_PLUGINS_CONFIG" + nmt.script = + let + # Force evaluation of generatedConfigs. + luaConfig = config.programs.neovim.generatedConfigs.lua; + vimlConfig = config.programs.neovim.generatedConfigs.viml; + in + '' + vimout=$(mktemp) + echo "redir >> /dev/stdout | echo g:hmExtraConfig | echo g:hmPlugins | redir END" \ + | ${pkgs.neovim}/bin/nvim -es -u "$TESTED/home-files/.config/nvim/init.lua" \ + > "$vimout" || true + assertFileContains "$vimout" "HM_EXTRA_CONFIG" + assertFileContains "$vimout" "HM_PLUGINS_CONFIG" - initLua="$TESTED/home-files/.config/nvim/init.lua" - assertFileContent $(normalizeStorePaths "$initLua") ${./plugin-config.expected} - ''; + initLua="$TESTED/home-files/.config/nvim/init.lua" + assertFileContent $(normalizeStorePaths "$initLua") ${./plugin-config.expected} + + # Verify generatedConfigs evaluated properly (issue #8371) + echo "Lua config length: ${toString (builtins.stringLength luaConfig)}" + echo "Viml config length: ${toString (builtins.stringLength vimlConfig)}" + ''; } diff --git a/tests/modules/programs/neovim/wrapper-args.nix b/tests/modules/programs/neovim/wrapper-args.nix new file mode 100644 index 00000000..6e79d529 --- /dev/null +++ b/tests/modules/programs/neovim/wrapper-args.nix @@ -0,0 +1,83 @@ +{ + lib, + pkgs, + ... +}: + +let + inherit (pkgs.stdenv.hostPlatform) isLinux; + + dummyDep = pkgs.runCommand "dummy-dep" { } '' + mkdir -p $out/bin + echo "echo dummy" > $out/bin/dummy-dep-bin + chmod +x $out/bin/dummy-dep-bin + ''; + + dummyPlugin = pkgs.vimUtils.buildVimPlugin { + pname = "dummy-plugin"; + version = "1.0"; + src = pkgs.writeTextDir "plugin/dummy.vim" "\" dummy"; + runtimeDeps = [ dummyDep ]; + }; +in +{ + imports = [ ./stubs.nix ]; + tests.stubs.wl-clipboard = { }; + + programs.neovim = { + enable = true; + extraName = "-my-suffix"; + withPerl = true; + withPython3 = true; + withRuby = true; + withNodeJs = true; + autowrapRuntimeDeps = true; + waylandSupport = isLinux; + plugins = [ dummyPlugin ]; + }; + + nmt.script = '' + nvimBin="home-path/bin/nvim" + + assertBinaryContains() { + local file="$TESTED/$1" + if [[ $1 == /* ]]; then file="$1"; fi + + if ! grep -a -qF -- "$2" "$file"; then + fail "Expected binary file '$1' to contain '$2' but it did not." + fi + } + + # Ensure the main binary exists + assertFileExists "$nvimBin" + + # 1. extraName: Check if the suffix is in the rplugin manifest path within the wrapper + assertBinaryContains "$nvimBin" "-my-suffix/rplugin.vim" + + # 2. withPerl: Check if nvim-perl binary exists and host prog is set + assertFileExists "home-path/bin/nvim-perl" + assertBinaryContains "$nvimBin" "perl_host_prog=" + + # 3. withPython3: Check if nvim-python3 binary exists and host prog is set + assertFileExists "home-path/bin/nvim-python3" + assertBinaryContains "$nvimBin" "python3_host_prog=" + + # 4. withRuby: Check if nvim-ruby binary exists, GEM_HOME and host prog are set + assertFileExists "home-path/bin/nvim-ruby" + assertBinaryContains "$nvimBin" "GEM_HOME=" + assertBinaryContains "$nvimBin" "ruby_host_prog=" + + # 5. withNodeJs: Check if nvim-node binary exists and host prog is set + assertFileExists "home-path/bin/nvim-node" + assertBinaryContains "$nvimBin" "node_host_prog=" + + # 6. waylandSupport: Check for wl-clipboard path in wrapper's PATH modification + # We check for the store path of wl-clipboard in the current pkgs + ${lib.optionalString isLinux '' + assertBinaryContains "$nvimBin" "wl-clipboard-" + ''} + + # 7. autowrapRuntimeDeps: Check for dummyDep path in wrapper's PATH modification + assertBinaryContains "$nvimBin" "${dummyDep}/bin" + ''; +} diff --git a/tests/modules/programs/npm/default.nix b/tests/modules/programs/npm/default.nix new file mode 100644 index 00000000..d66921ec --- /dev/null +++ b/tests/modules/programs/npm/default.nix @@ -0,0 +1,4 @@ +{ + npm-example-settings = ./example-settings.nix; + npm-no-settings = ./no-settings.nix; +} diff --git a/tests/modules/programs/npm/example-settings.nix b/tests/modules/programs/npm/example-settings.nix new file mode 100644 index 00000000..b8ce0858 --- /dev/null +++ b/tests/modules/programs/npm/example-settings.nix @@ -0,0 +1,36 @@ +{ pkgs, ... }: + +{ + programs.npm = { + enable = true; + settings = { + color = true; + include = [ + "dev" + "prod" + ]; + init-license = "MIT"; + prefix = "\${HOME}/.npm"; + }; + }; + + test.stubs.nodejs = { }; + + nmt.script = + let + configPath = "home-files/.npmrc"; + expectedConfig = pkgs.writeText "npmrc-expected" '' + color=true + include[]=dev + include[]=prod + init-license=MIT + prefix=''${HOME}/.npm + ''; + in + '' + assertFileExists "${configPath}" + assertFileContent "${configPath}" "${expectedConfig}" + assertFileContains home-path/etc/profile.d/hm-session-vars.sh \ + 'export NPM_CONFIG_USERCONFIG="/home/hm-user/.npmrc"' + ''; +} diff --git a/tests/modules/programs/npm/no-settings.nix b/tests/modules/programs/npm/no-settings.nix new file mode 100644 index 00000000..21b0b677 --- /dev/null +++ b/tests/modules/programs/npm/no-settings.nix @@ -0,0 +1,17 @@ +{ ... }: + +{ + programs.npm = { + enable = true; + settings = { }; + }; + + test.stubs.nodejs = { }; + + nmt.script = '' + assertPathNotExists home-files/.npmrc + assertFileExists home-path/etc/profile.d/hm-session-vars.sh + assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh \ + "export NPM_CONFIG_USERCONFIG=" + ''; +} diff --git a/tests/modules/programs/pimsync/basic.nix b/tests/modules/programs/pimsync/basic.nix index 2b9b7434..f029ea1f 100644 --- a/tests/modules/programs/pimsync/basic.nix +++ b/tests/modules/programs/pimsync/basic.nix @@ -1,6 +1,6 @@ { accounts.calendar = { - accounts.caldav = { + accounts.mine = { pimsync.enable = true; remote = { passwordCommand = [ @@ -22,6 +22,22 @@ basePath = ".local/state/calendar"; }; + accounts.contact = { + accounts.mine = { + pimsync.enable = true; + remote = { + passwordCommand = [ + "pass" + "carddav" + ]; + type = "carddav"; + url = "https://carddav.example.com"; + userName = "bob"; + }; + }; + basePath = ".local/state/contact"; + }; + programs.pimsync = { enable = true; settings = [ diff --git a/tests/modules/programs/pimsync/basic.scfg b/tests/modules/programs/pimsync/basic.scfg index e0f6bb22..6d09cbb3 100644 --- a/tests/modules/programs/pimsync/basic.scfg +++ b/tests/modules/programs/pimsync/basic.scfg @@ -1,14 +1,18 @@ -storage caldav-local { - fileext .ics - path /home/hm-user/.local/state/calendar/caldav - type vdir/icalendar -} -storage http-local { +storage calendar-http-local { fileext .ics path /home/hm-user/.local/state/calendar/http type vdir/icalendar } -storage caldav-remote { +storage calendar-mine-local { + fileext .ics + path /home/hm-user/.local/state/calendar/mine + type vdir/icalendar +} +storage calendar-http-remote { + type webcal + url https://example.com/calendar +} +storage calendar-mine-remote { type caldav url https://caldav.example.com username alice @@ -16,16 +20,29 @@ storage caldav-remote { cmd pass caldav } } -storage http-remote { - type webcal - url https://example.com/calendar -} -pair calendar-caldav { - storage_a caldav-local - storage_b caldav-remote -} pair calendar-http { - storage_a http-local - storage_b http-remote + storage_a calendar-http-local + storage_b calendar-http-remote +} +pair calendar-mine { + storage_a calendar-mine-local + storage_b calendar-mine-remote +} +storage contacts-mine-local { + fileext .vcf + path /home/hm-user/.local/state/contact/mine + type vdir/vcard +} +storage contacts-mine-remote { + type carddav + url https://carddav.example.com + username bob + password { + cmd pass carddav + } +} +pair contacts-mine { + storage_a contacts-mine-local + storage_b contacts-mine-remote } status_path /test/dir diff --git a/tests/modules/programs/radicle/basic-configuration.nix b/tests/modules/programs/radicle/basic-configuration.nix index 378cb844..b283e660 100644 --- a/tests/modules/programs/radicle/basic-configuration.nix +++ b/tests/modules/programs/radicle/basic-configuration.nix @@ -1,9 +1,19 @@ -{ config, pkgs, ... }: - { config = { programs.radicle.enable = true; + test.stubs.radicle-node = { + buildScript = '' + mkdir -p "$out/bin" + cat > "$out/bin/rad" << 'EOF' + #!/bin/sh + # Stub rad command that does nothing + exit 0 + EOF + chmod +x "$out/bin/rad" + ''; + }; + nmt.script = '' assertFileContent \ home-files/.radicle/config.json \ diff --git a/tests/modules/programs/screen/default.nix b/tests/modules/programs/screen/default.nix new file mode 100644 index 00000000..574fee86 --- /dev/null +++ b/tests/modules/programs/screen/default.nix @@ -0,0 +1 @@ +{ screen-settings = ./settings.nix; } diff --git a/tests/modules/programs/screen/screenrc b/tests/modules/programs/screen/screenrc new file mode 100644 index 00000000..fb33699d --- /dev/null +++ b/tests/modules/programs/screen/screenrc @@ -0,0 +1,14 @@ +screen -t rtorrent rtorrent +screen -t irssi irssi +screen -t centerim centerim +screen -t ncmpc ncmpc -c +screen -t bash4 +screen -t bash5 +screen -t bash6 +screen -t bash7 +screen -t bash8 +screen -t bash9 +altscreen on +term screen-256color +bind ',' prev +bind '.' next diff --git a/tests/modules/programs/screen/settings.nix b/tests/modules/programs/screen/settings.nix new file mode 100644 index 00000000..45be7ba6 --- /dev/null +++ b/tests/modules/programs/screen/settings.nix @@ -0,0 +1,26 @@ +{ + programs.screen = { + enable = true; + screenrc = '' + screen -t rtorrent rtorrent + screen -t irssi irssi + screen -t centerim centerim + screen -t ncmpc ncmpc -c + screen -t bash4 + screen -t bash5 + screen -t bash6 + screen -t bash7 + screen -t bash8 + screen -t bash9 + altscreen on + term screen-256color + bind ',' prev + bind '.' next + ''; + }; + + nmt.script = '' + assertFileExists home-files/.screenrc + assertFileContent home-files/.screenrc ${./screenrc} + ''; +} diff --git a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf index 1c4c4f86..fc7e4acf 100644 --- a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf +++ b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf @@ -1,7 +1,9 @@ Host dynamicBindAddressWithPort DynamicForward [127.0.0.1]:3000 + Host dynamicBindPathNoPort DynamicForward /run/user/1000/gnupg/S.gpg-agent.extra + diff --git a/tests/modules/programs/ssh/match-blocks-attrs-expected.conf b/tests/modules/programs/ssh/match-blocks-attrs-expected.conf index edff6264..7e488eb8 100644 --- a/tests/modules/programs/ssh/match-blocks-attrs-expected.conf +++ b/tests/modules/programs/ssh/match-blocks-attrs-expected.conf @@ -2,8 +2,10 @@ Host * !github.com Port 516 IdentityFile file1 IdentityFile file2 + Host abc ProxyJump jump-host + Host xyz SetEnv BAR="_bar_ 42" FOO="foo12" ServerAliveInterval 60 @@ -13,8 +15,10 @@ Host xyz RemoteForward [localhost]:8081 [10.0.0.2]:80 RemoteForward /run/user/1000/gnupg/S.gpg-agent.extra /run/user/1000/gnupg/S.gpg-agent DynamicForward [localhost]:2839 + Host ordered Port 1 + diff --git a/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf b/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf index b5f3dad8..6fb60b1e 100644 --- a/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf +++ b/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf @@ -1,9 +1,12 @@ Host * !github.com Port 516 + Host abc Port 2222 + Match host xyz canonical Port 2223 + diff --git a/tests/modules/programs/ssh/old-defaults-expected.conf b/tests/modules/programs/ssh/old-defaults-expected.conf index 65290800..75a34ce4 100644 --- a/tests/modules/programs/ssh/old-defaults-expected.conf +++ b/tests/modules/programs/ssh/old-defaults-expected.conf @@ -11,4 +11,5 @@ Host * ControlMaster no ControlPath ~/.ssh/master-%r@%n:%p ControlPersist no + diff --git a/tests/modules/programs/ssh/old-defaults-extra-config-expected.conf b/tests/modules/programs/ssh/old-defaults-extra-config-expected.conf index e14c44e8..46f398dd 100644 --- a/tests/modules/programs/ssh/old-defaults-extra-config-expected.conf +++ b/tests/modules/programs/ssh/old-defaults-extra-config-expected.conf @@ -11,6 +11,7 @@ Host * ControlMaster no ControlPath ~/.ssh/master-%r@%n:%p ControlPersist no + MyExtraOption no AnotherOption 3 diff --git a/tests/modules/programs/ssh/renamed-options-expected.conf b/tests/modules/programs/ssh/renamed-options-expected.conf index e586953c..f01bc02e 100644 --- a/tests/modules/programs/ssh/renamed-options-expected.conf +++ b/tests/modules/programs/ssh/renamed-options-expected.conf @@ -11,4 +11,5 @@ Host * ControlMaster yes ControlPath ~/.ssh/myfile-%r@%n:%p ControlPersist 10m + diff --git a/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js b/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js index 852a91b2..bd016760 100644 --- a/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js +++ b/tests/modules/programs/thunderbird/thunderbird-expected-first-darwin.js @@ -120,7 +120,7 @@ user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfa user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f.port", 456); user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f.try_ssl", 3); user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f.username", "home.manager"); -user_pref("mail.smtpservers", "smtp_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc,smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f"); +user_pref("mail.smtpservers", "smtp_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc,smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f,smtp_b24ca86ede61ed219e6c87cfec261aae2c72785f812489ea943d114d1f39f55b"); user_pref("privacy.donottrackheader.enabled", true); user_pref("mail.html_compose", false); diff --git a/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js b/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js index 8cbf7be3..bf32de26 100644 --- a/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js +++ b/tests/modules/programs/thunderbird/thunderbird-expected-first-linux.js @@ -120,7 +120,7 @@ user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfa user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f.port", 456); user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f.try_ssl", 3); user_pref("mail.smtpserver.smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f.username", "home.manager"); -user_pref("mail.smtpservers", "smtp_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc,smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f"); +user_pref("mail.smtpservers", "smtp_bcd3ace52bed41febb6cdc2fb1303aebaa573e0d993872da503950901bb6c6fc,smtp_cda3f13b64c1db7d4b58ce07a31304a362d7dcaf14476bfabcca913ae41ada9f,smtp_b24ca86ede61ed219e6c87cfec261aae2c72785f812489ea943d114d1f39f55b"); user_pref("privacy.donottrackheader.enabled", true); user_pref("mail.html_compose", false); diff --git a/tests/modules/programs/ty/default.nix b/tests/modules/programs/ty/default.nix new file mode 100644 index 00000000..be700b67 --- /dev/null +++ b/tests/modules/programs/ty/default.nix @@ -0,0 +1,4 @@ +{ + ty-example-settings = ./example-settings.nix; + ty-no-settings = ./no-settings.nix; +} diff --git a/tests/modules/programs/ty/example-settings.nix b/tests/modules/programs/ty/example-settings.nix new file mode 100644 index 00000000..4d460ead --- /dev/null +++ b/tests/modules/programs/ty/example-settings.nix @@ -0,0 +1,25 @@ +{ pkgs, ... }: + +{ + programs.ty = { + enable = true; + settings = { + rules.index-out-of-bounds = "ignore"; + }; + }; + + test.stubs.ty = { }; + + nmt.script = + let + expectedConfigPath = "home-files/.config/ty/ty.toml"; + expectedConfigContent = pkgs.writeText "ty.config-custom.expected" '' + [rules] + index-out-of-bounds = "ignore" + ''; + in + '' + assertFileExists "${expectedConfigPath}" + assertFileContent "${expectedConfigPath}" "${expectedConfigContent}" + ''; +} diff --git a/tests/modules/programs/ty/no-settings.nix b/tests/modules/programs/ty/no-settings.nix new file mode 100644 index 00000000..23bc04bf --- /dev/null +++ b/tests/modules/programs/ty/no-settings.nix @@ -0,0 +1,17 @@ +{ pkgs, ... }: + +{ + programs.ty = { + enable = true; + }; + + test.stubs.ty = { }; + + nmt.script = + let + expectedConfigPath = "home-files/.config/ty/ty.toml"; + in + '' + assertPathNotExists "${expectedConfigPath}" + ''; +} diff --git a/tests/modules/services/colima/darwin/colima-custom-settings.nix b/tests/modules/services/colima/darwin/colima-custom-settings.nix new file mode 100644 index 00000000..3c908c0a --- /dev/null +++ b/tests/modules/services/colima/darwin/colima-custom-settings.nix @@ -0,0 +1,39 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + services.colima = { + enable = true; + package = config.lib.test.mkStubPackage { + name = "colima"; + outPath = "@colima@"; + }; + dockerPackage = config.lib.test.mkStubPackage { + name = "docker"; + outPath = "@docker@"; + }; + perlPackage = config.lib.test.mkStubPackage { + name = "perl"; + outPath = "@perl@"; + }; + sshPackage = config.lib.test.mkStubPackage { + name = "openssh"; + outPath = "@openssh@"; + }; + + profiles.default.settings = { + cpu = 4; + memory = 8; + kubernetes.enabled = true; + }; + }; + + nmt.script = '' + assertFileExists home-files/.colima/default/colima.yaml + assertFileContent home-files/.colima/default/colima.yaml ${./custom-settings-expected.yaml} + ''; +} diff --git a/tests/modules/services/colima/darwin/colima-default-config.nix b/tests/modules/services/colima/darwin/colima-default-config.nix new file mode 100644 index 00000000..fbc29de8 --- /dev/null +++ b/tests/modules/services/colima/darwin/colima-default-config.nix @@ -0,0 +1,61 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + nixpkgs.overlays = [ + (self: super: { + darwin = super.darwin // { + DarwinTools = config.lib.test.mkStubPackage { + name = "DarwinTools"; + outPath = "@DarwinTools@"; + }; + }; + }) + ]; + + services.colima = { + enable = true; + package = config.lib.test.mkStubPackage { + name = "colima"; + outPath = "@colima@"; + }; + dockerPackage = config.lib.test.mkStubPackage { + name = "docker"; + outPath = "@docker@"; + }; + perlPackage = config.lib.test.mkStubPackage { + name = "perl"; + outPath = "@perl@"; + }; + sshPackage = config.lib.test.mkStubPackage { + name = "openssh"; + outPath = "@openssh@"; + }; + coreutilsPackage = config.lib.test.mkStubPackage { + name = "coreutils"; + outPath = "@coreutils@"; + }; + curlPackage = config.lib.test.mkStubPackage { + name = "curl"; + outPath = "@curl@"; + }; + bashPackage = config.lib.test.mkStubPackage { + name = "bashNonInteractive"; + outPath = "@bashNonInteractive@"; + }; + }; + + nmt.script = '' + assertPathNotExists home-files/.colima/default/colima.yaml + + serviceFile=LaunchAgents/org.nix-community.home.colima-default.plist + + assertFileExists "$serviceFile" + + assertFileContent "$serviceFile" ${./expected-agent.plist} + ''; +} diff --git a/tests/modules/services/colima/darwin/custom-settings-expected.yaml b/tests/modules/services/colima/darwin/custom-settings-expected.yaml new file mode 100644 index 00000000..4a166598 --- /dev/null +++ b/tests/modules/services/colima/darwin/custom-settings-expected.yaml @@ -0,0 +1,4 @@ +cpu: 4 +kubernetes: + enabled: true +memory: 8 diff --git a/tests/modules/services/colima/darwin/default.nix b/tests/modules/services/colima/darwin/default.nix new file mode 100644 index 00000000..1ce901bb --- /dev/null +++ b/tests/modules/services/colima/darwin/default.nix @@ -0,0 +1,4 @@ +{ + "colima-default-config" = ./colima-default-config.nix; + "colima-custom-settings" = ./colima-custom-settings.nix; +} diff --git a/tests/modules/services/colima/darwin/expected-agent.plist b/tests/modules/services/colima/darwin/expected-agent.plist new file mode 100644 index 00000000..e4e43477 --- /dev/null +++ b/tests/modules/services/colima/darwin/expected-agent.plist @@ -0,0 +1,30 @@ + + + + + EnvironmentVariables + + PATH + @colima@/bin:@perl@/bin:@docker@/bin:@openssh@/bin:@coreutils@/bin:@curl@/bin:@bashNonInteractive@/bin:@DarwinTools@/bin + + KeepAlive + + Label + org.nix-community.home.colima-default + ProgramArguments + + @colima@/bin/colima + start + default + -f + --activate=true + --save-config=false + + RunAtLoad + + StandardErrorPath + /home/hm-user/.local/state/colima-default.log + StandardOutPath + /home/hm-user/.local/state/colima-default.log + + \ No newline at end of file diff --git a/tests/modules/services/colima/default.nix b/tests/modules/services/colima/default.nix new file mode 100644 index 00000000..4b1206a2 --- /dev/null +++ b/tests/modules/services/colima/default.nix @@ -0,0 +1,3 @@ +{ lib, pkgs, ... }: +(lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin (import ./darwin/default.nix)) +// (lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux (import ./linux/default.nix)) diff --git a/tests/modules/services/colima/linux/colima-default-config.nix b/tests/modules/services/colima/linux/colima-default-config.nix new file mode 100644 index 00000000..940f997f --- /dev/null +++ b/tests/modules/services/colima/linux/colima-default-config.nix @@ -0,0 +1,50 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + services.colima = { + enable = true; + package = config.lib.test.mkStubPackage { + name = "colima"; + outPath = "@colima@"; + }; + dockerPackage = config.lib.test.mkStubPackage { + name = "docker"; + outPath = "@docker@"; + }; + perlPackage = config.lib.test.mkStubPackage { + name = "perl"; + outPath = "@perl@"; + }; + sshPackage = config.lib.test.mkStubPackage { + name = "openssh"; + outPath = "@openssh@"; + }; + coreutilsPackage = config.lib.test.mkStubPackage { + name = "coreutils"; + outPath = "@coreutils@"; + }; + curlPackage = config.lib.test.mkStubPackage { + name = "curl"; + outPath = "@curl@"; + }; + bashPackage = config.lib.test.mkStubPackage { + name = "bashNonInteractive"; + outPath = "@bashNonInteractive@"; + }; + }; + + nmt.script = '' + assertPathNotExists home-files/.colima/default/colima.yaml + + assertFileExists home-files/.config/systemd/user/colima-default.service + + assertFileContent \ + home-files/.config/systemd/user/colima-default.service \ + ${./expected-service.service} + ''; +} diff --git a/tests/modules/services/colima/linux/default.nix b/tests/modules/services/colima/linux/default.nix new file mode 100644 index 00000000..ee233831 --- /dev/null +++ b/tests/modules/services/colima/linux/default.nix @@ -0,0 +1,3 @@ +{ + "colima-default-config" = ./colima-default-config.nix; +} diff --git a/tests/modules/services/colima/linux/expected-service.service b/tests/modules/services/colima/linux/expected-service.service new file mode 100644 index 00000000..82dacb73 --- /dev/null +++ b/tests/modules/services/colima/linux/expected-service.service @@ -0,0 +1,19 @@ +[Install] +WantedBy=default.target + +[Service] +Environment=PATH=@colima@/bin:@perl@/bin:@docker@/bin:@openssh@/bin:@coreutils@/bin:@curl@/bin:@bashNonInteractive@/bin +ExecStart=@colima@/bin/colima start default \ + -f \ + --activate=true \ + --save-config=false + +Restart=always +RestartSec=2 +StandardError=append:/home/hm-user/.local/state/colima-default.log +StandardOutput=append:/home/hm-user/.local/state/colima-default.log + +[Unit] +After=network-online.target +Description=Colima container runtime (default profile) +Wants=network-online.target diff --git a/tests/modules/services/herbstluftwm/default.nix b/tests/modules/services/herbstluftwm/default.nix index 52d34497..0bad877e 100644 --- a/tests/modules/services/herbstluftwm/default.nix +++ b/tests/modules/services/herbstluftwm/default.nix @@ -3,4 +3,5 @@ lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { herbstluftwm-simple-config = ./herbstluftwm-simple-config.nix; herbstluftwm-no-tags = ./herbstluftwm-no-tags.nix; + herbstluftwm-alias-disabled = ./herbstluftwm-alias-disabled.nix; } diff --git a/tests/modules/services/herbstluftwm/herbstluftwm-alias-disabled-autostart b/tests/modules/services/herbstluftwm/herbstluftwm-alias-disabled-autostart new file mode 100644 index 00000000..7368c7a7 --- /dev/null +++ b/tests/modules/services/herbstluftwm/herbstluftwm-alias-disabled-autostart @@ -0,0 +1,47 @@ +#!/nix/store/00000000000000000000000000000000-bash/bin/bash + + +herbstclient emit_hook reload + +# Reset everything. +herbstclient attr theme.tiling.reset 1 +herbstclient attr theme.floating.reset 1 +herbstclient keyunbind --all +herbstclient mouseunbind --all +herbstclient unrule --all + +herbstclient set always_show_frame true +herbstclient set default_frame_layout max +herbstclient set frame_bg_active_color '#000000' +herbstclient set frame_gap 12 +herbstclient set frame_padding -12 + +for tag in 1 'with space' 'wə1rd#ch@rs'\'''; do + herbstclient add "$tag" +done + +if @herbstluftwm@/bin/herbstclient object_tree tags.by-name.default &>/dev/null; then + herbstclient use 1 + herbstclient merge_tag default 1 +fi + + +herbstclient keybind Mod4-1 use 1 +herbstclient keybind Mod4-2 use 2 +herbstclient keybind Mod4-Alt-Tab cycle -1 +herbstclient keybind Mod4-Tab cycle 1 + +herbstclient mousebind Mod4-B1 move +herbstclient mousebind Mod4-B3 resize + +herbstclient rule focus=on +herbstclient rule windowtype~'_NET_WM_WINDOW_TYPE_(DIALOG|UTILITY|SPLASH)' focus=on pseudotile=on +herbstclient rule class~'[Pp]inentry' instance=pinentry focus=on floating=on floatplacement=center keys_inactive='.*' + +herbstclient use 1 + + +herbstclient unlock + + + diff --git a/tests/modules/services/herbstluftwm/herbstluftwm-alias-disabled.nix b/tests/modules/services/herbstluftwm/herbstluftwm-alias-disabled.nix new file mode 100644 index 00000000..0e9f8f96 --- /dev/null +++ b/tests/modules/services/herbstluftwm/herbstluftwm-alias-disabled.nix @@ -0,0 +1,49 @@ +{ lib, pkgs, ... }: + +{ + xsession.windowManager.herbstluftwm = { + enable = true; + settings = { + always_show_frame = true; + default_frame_layout = "max"; + frame_bg_active_color = "#000000"; + frame_gap = 12; + frame_padding = -12; + }; + keybinds = { + "Mod4-1" = "use 1"; + "Mod4-2" = "use 2"; + "Mod4-Tab" = "cycle 1"; + "Mod4-Alt-Tab" = "cycle -1"; + }; + mousebinds = { + "Mod4-B1" = "move"; + "Mod4-B3" = "resize"; + }; + rules = [ + "focus=on" + "windowtype~'_NET_WM_WINDOW_TYPE_(DIALOG|UTILITY|SPLASH)' focus=on pseudotile=on" + "class~'[Pp]inentry' instance=pinentry focus=on floating=on floatplacement=center keys_inactive='.*'" + ]; + tags = [ + "1" + "with space" + "wə1rd#ch@rs'" + ]; + extraConfig = '' + herbstclient use 1 + ''; + enableAlias = false; + }; + + test.stubs.herbstluftwm = { }; + + nmt.script = '' + autostart=home-files/.config/herbstluftwm/autostart + assertFileExists "$autostart" + assertFileIsExecutable "$autostart" + + normalizedAutostart=$(normalizeStorePaths "$autostart") + assertFileContent "$normalizedAutostart" ${./herbstluftwm-alias-disabled-autostart} + ''; +} diff --git a/tests/modules/services/herbstluftwm/herbstluftwm-no-tags-autostart b/tests/modules/services/herbstluftwm/herbstluftwm-no-tags-autostart index 9e8cd78a..08c35d13 100644 --- a/tests/modules/services/herbstluftwm/herbstluftwm-no-tags-autostart +++ b/tests/modules/services/herbstluftwm/herbstluftwm-no-tags-autostart @@ -5,6 +5,7 @@ shopt -s expand_aliases alias herbstclient='set -- "$@" ";"' set -- + herbstclient emit_hook reload # Reset everything. @@ -30,3 +31,4 @@ herbstclient unlock @herbstluftwm@/bin/herbstclient chain ";" "$@" + diff --git a/tests/modules/services/herbstluftwm/herbstluftwm-simple-config-autostart b/tests/modules/services/herbstluftwm/herbstluftwm-simple-config-autostart index 85ef45b1..0c608a3c 100644 --- a/tests/modules/services/herbstluftwm/herbstluftwm-simple-config-autostart +++ b/tests/modules/services/herbstluftwm/herbstluftwm-simple-config-autostart @@ -5,6 +5,7 @@ shopt -s expand_aliases alias herbstclient='set -- "$@" ";"' set -- + herbstclient emit_hook reload # Reset everything. @@ -49,3 +50,4 @@ herbstclient unlock @herbstluftwm@/bin/herbstclient chain ";" "$@" + diff --git a/tests/modules/services/hyprlauncher/default.nix b/tests/modules/services/hyprlauncher/default.nix new file mode 100644 index 00000000..8b221b45 --- /dev/null +++ b/tests/modules/services/hyprlauncher/default.nix @@ -0,0 +1,5 @@ +{ lib, pkgs, ... }: + +lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { + hyprlauncher-settings = ./settings.nix; +} diff --git a/tests/modules/services/hyprlauncher/hyprlauncher.conf b/tests/modules/services/hyprlauncher/hyprlauncher.conf new file mode 100644 index 00000000..6d6239e1 --- /dev/null +++ b/tests/modules/services/hyprlauncher/hyprlauncher.conf @@ -0,0 +1,16 @@ +cache { + enabled=true +} + +finders { + desktop_icons=true + math_prefix== +} + +general { + grab_focus=true +} + +ui { + window_size=400 260 +} diff --git a/tests/modules/services/hyprlauncher/settings.nix b/tests/modules/services/hyprlauncher/settings.nix new file mode 100644 index 00000000..92db74d8 --- /dev/null +++ b/tests/modules/services/hyprlauncher/settings.nix @@ -0,0 +1,20 @@ +{ + services.hyprlauncher = { + enable = true; + settings = { + general.grab_focus = true; + cache.enabled = true; + ui.window_size = "400 260"; + finders = { + math_prefix = "="; + desktop_icons = true; + }; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/hypr/hyprlauncher.conf + assertFileContent home-files/.config/hypr/hyprlauncher.conf \ + ${./hyprlauncher.conf} + ''; +} diff --git a/tests/modules/services/ludusavi/config.yaml b/tests/modules/services/ludusavi/config.yaml new file mode 100644 index 00000000..ede8d110 --- /dev/null +++ b/tests/modules/services/ludusavi/config.yaml @@ -0,0 +1,9 @@ +backup: + path: ~/.local/state/backups/ludusavi +language: en-US +restore: + path: ~/.local/state/backups/ludusavi +roots: +- path: ~/.local/share/Steam + store: steam +theme: light diff --git a/tests/modules/services/ludusavi/default.nix b/tests/modules/services/ludusavi/default.nix new file mode 100644 index 00000000..2efb726c --- /dev/null +++ b/tests/modules/services/ludusavi/default.nix @@ -0,0 +1 @@ +{ ludusavi-settings = ./settings.nix; } diff --git a/tests/modules/services/ludusavi/settings.nix b/tests/modules/services/ludusavi/settings.nix new file mode 100644 index 00000000..a99975e1 --- /dev/null +++ b/tests/modules/services/ludusavi/settings.nix @@ -0,0 +1,23 @@ +{ + services.ludusavi = { + enable = true; + settings = { + language = "en-US"; + theme = "light"; + roots = [ + { + path = "~/.local/share/Steam"; + store = "steam"; + } + ]; + backup.path = "~/.local/state/backups/ludusavi"; + restore.path = "~/.local/state/backups/ludusavi"; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/ludusavi/config.yaml + assertFileContent home-files/.config/ludusavi/config.yaml \ + ${./config.yaml} + ''; +} diff --git a/tests/modules/services/ssh-agent/darwin/bash-integration.nix b/tests/modules/services/ssh-agent/darwin/bash-integration.nix index b4fb9109..2341462a 100644 --- a/tests/modules/services/ssh-agent/darwin/bash-integration.nix +++ b/tests/modules/services/ssh-agent/darwin/bash-integration.nix @@ -8,7 +8,7 @@ nmt.script = '' assertFileContains \ - home-files/.bashrc \ + home-files/.profile \ 'export SSH_AUTH_SOCK=$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent' ''; } diff --git a/tests/modules/services/swayidle/basic-configuration.nix b/tests/modules/services/swayidle/basic-configuration.nix index b5534c75..5da059b1 100644 --- a/tests/modules/services/swayidle/basic-configuration.nix +++ b/tests/modules/services/swayidle/basic-configuration.nix @@ -19,16 +19,10 @@ resumeCommand = ''swaymsg "output * dpms on"''; } ]; - events = [ - { - event = "before-sleep"; - command = "swaylock -fF"; - } - { - event = "lock"; - command = "swaylock -fF"; - } - ]; + events = { + before-sleep = "swaylock -fF"; + lock = "swaylock -fF"; + }; }; nmt.script = '' diff --git a/tests/modules/services/swayidle/default.nix b/tests/modules/services/swayidle/default.nix index 62db035b..75c88d28 100644 --- a/tests/modules/services/swayidle/default.nix +++ b/tests/modules/services/swayidle/default.nix @@ -2,4 +2,5 @@ lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { swayidle-basic-configuration = ./basic-configuration.nix; + swayidle-legacy-configuration = ./legacy-configuration.nix; } diff --git a/tests/modules/services/swayidle/legacy-configuration.nix b/tests/modules/services/swayidle/legacy-configuration.nix new file mode 100644 index 00000000..e07834fa --- /dev/null +++ b/tests/modules/services/swayidle/legacy-configuration.nix @@ -0,0 +1,51 @@ +{ config, ... }: +{ + services.swayidle = { + enable = true; + package = config.lib.test.mkStubPackage { outPath = "@swayidle@"; }; + events = [ + { + event = "lock"; + command = "swaylock -fF"; + } + { + event = "before-sleep"; + command = "swaylock -fF"; + } + ]; + }; + + test.asserts.evalWarnings.expected = [ + '' + The syntax of services.swayidle.events has changed. While it + previously accepted a list of events, it now accepts an attrset + keyed by the event name. + '' + ]; + + nmt.script = '' + serviceFile=home-files/.config/systemd/user/swayidle.service + + assertFileExists "$serviceFile" + + serviceFileNormalized="$(normalizeStorePaths "$serviceFile")" + + assertFileContent "$serviceFileNormalized" ${builtins.toFile "expected.service" '' + [Install] + WantedBy=graphical-session.target + + [Service] + Environment=PATH=@bash-interactive@/bin + ExecStart=@swayidle@/bin/dummy -w before-sleep 'swaylock -fF' lock 'swaylock -fF' + Restart=always + Type=simple + + [Unit] + After=graphical-session.target + ConditionEnvironment=WAYLAND_DISPLAY + Description=Idle manager for Wayland + Documentation=man:swayidle(1) + PartOf=graphical-session.target + ''} + ''; +}