Compare commits

...
289 Commits
Author SHA1 Message Date
Robert Helgesson f38547881c home-manager: optionally use putter for file management
The Putter tool is a Rust implementation of the file management
component of Home Manager. It takes a JSON manifest that indicates how
files should be symlinked and when the manifest is applied, does its
best to ensure that the file system reflect the manifest.

Putter offers some additional features that Home Manager does not
expose today, such as file copying (potentially recursive) and support
for overriding files within a recursive tree (e.g., inside a symlinked
tree one could ensure that a specific file is copies with specific
permissions).

This is considered highly experimental at the moment and to use Putter
one must set a hidden option.
2026-07-23 10:53:35 +02:00
Eman ResuandRobert Helgesson 3b0e6bbd65 modules: partially apply .nix suffix check
hasSuffix runs some checks on the suffix, that can be done all at once
rather than every single time.
2026-07-23 08:47:55 +02:00
Eman ResuandRobert Helgesson cbfe673271 modules: stop filtering files starting with _
There are no instances of this in-tree.
2026-07-23 08:47:55 +02:00
YethalandAustin Horstman 041a999e8c modules/misc/ssh-auth-sock: correct nushell init 2026-07-22 08:08:47 -05:00
Sebastián EstrellaandAustin Horstman 6d8d615678 herdr: Use the binary outside Nix to reload the config when package is unset 2026-07-21 22:14:27 -05:00
abba1081f5 rclone: add nfsmount mount type
Add a per-mount 'mountType' option ('mount' | 'nfsmount') selecting
which rclone command performs the mount. 'nfsmount' runs rclone's
in-process NFS server and mounts it via the system NFS client, so no
FUSE driver is needed — on macOS this removes the module's documented
macFUSE requirement (and the kernel-extension approval plus Reduced
Security boot policy that come with it).

When 'nfsmount' is selected, 'nfs-cache-type = "disk"' is merged
into the mount's options (user-overridable, mirroring the existing
'vfs-cache-mode' defaulting) so NFS file handles survive rclone
restarts; with the in-memory default, every service restart would
leave the kernel client with stale handles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 22:13:44 -05:00
yaaaarnandAustin Horstman a2a9fec572 senpai: use correct config directory on darwin systems 2026-07-21 22:13:04 -05:00
Matt SchreiberandRobert Helgesson deeb6b7eb7 files: fix unintended subshell expansion 2026-07-21 00:53:36 +02:00
Arnaud VenturiandRobert Helgesson cbcbd8b043 kanshi: Fix scale example values from int to float 2026-07-21 00:24:25 +02:00
JessandRobert Helgesson 7e2199f4bc rclone: set programs.fuse.enable to fix integration tests 2026-07-21 00:23:33 +02:00
DimaniojkandRobert Helgesson 56ccab8918 programs.claude-code: avoid watching the Nix store 2026-07-21 00:19:38 +02:00
AstreaprtclandRobert Helgesson a02190edf9 wlsunset: add duration option
Add an option to set the easing duration if the times have been set
manually.
2026-07-20 23:18:26 +02:00
Eman ResuandRobert Helgesson 411408f523 lib/file-type: partially apply hasPrefix and removePrefix 2026-07-20 23:17:54 +02:00
reducedradiusandRobert Helgesson 7834e3fb36 Translate using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (39 of 39 strings)

Co-authored-by: reducedradius <137701630+flytothehighest@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/zh_Hans/
Translation: Home Manager/Home Manager CLI
2026-07-20 23:00:06 +02:00
Artur SannikovandRobert Helgesson 7d7a2566f6 Translate using Weblate (Italian)
Currently translated at 93.7% (15 of 16 strings)

Translate using Weblate (Italian)

Currently translated at 100.0% (39 of 39 strings)

Translate using Weblate (Russian)

Currently translated at 100.0% (16 of 16 strings)

Translate using Weblate (Russian)

Currently translated at 100.0% (39 of 39 strings)

Co-authored-by: Artur Sannikov <weblate.rbpxz@8shield.net>
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/it/
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/ru/
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/it/
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/ru/
Translation: Home Manager/Home Manager CLI
Translation: Home Manager/Home Manager Modules
2026-07-20 23:00:06 +02:00
greenpsiandMatthieu Coudron e3dea8e479 tirith: zsh initExtra -> initContent
Migrate usage of zsh initExtra to initContent because initExtra is
deprecated.
2026-07-20 13:17:44 +02:00
diniamoandMatthieu Coudron 3139deb8ca maintainers: remove diniamo 2026-07-19 21:31:31 +02:00
Jason BowmanandRobert Helgesson 39411a8e12 syncthing: use localhost authority for unix-socket REST calls
When services.syncthing.guiAddress is a unix socket, curlAddressArgs built
`--unix-socket <sock> http://.<path>`. curl >= 8.21 tightened URL parsing and
rejects the bare-dot authority with "URL rejected: Bad hostname" (exit 3), so
every REST call in the merge script fails. Combined with `--retry 1000
--retry-delay 1 --retry-all-errors`, syncthing-init.service retries until
systemd terminates it at the start timeout and Home Manager activation fails.

Use `localhost` as the placeholder authority; --unix-socket still routes the
request to the socket, and localhost is accepted by all curl versions. Add a
regression test asserting the unix-socket path uses http://localhost.
2026-07-18 22:41:57 +02:00
Sebastián EstrellaandAustin Horstman 460108009c herdr: Automatically reload config 2026-07-18 00:08:44 -05:00
Mike ThaiandAustin Horstman 18f9047340 navi: update url for config file docs 2026-07-18 00:04:03 -05:00
home-manager-ci[bot]andAustin Horstman c71e83ef50 maintainers: update all-maintainers.nix
Automated update of the master maintainers list combining:
- Home Manager specific maintainers from modules/lib/maintainers.nix
- Nixpkgs maintainers referenced in Home Manager modules

**Added:** 2 maintainers
**Removed:** 0 maintainers
**Total:** 305 → 307 maintainers

** Added:** ReeSilva, ratakor

Generated by: lib/python/generate-all-maintainers.py
2026-07-18 00:02:55 -05:00
Austin Horstman 165228b0ef launchd: restore gui domain defaults
User-domain agents are not rediscovered from the user LaunchAgents directory after reboot. Let built-in agents inherit the GUI default while keeping explicit user-domain configuration available.
2026-07-15 10:29:26 -05:00
6dc9e081ba docs: fix cross-page links broken by mdbook migration
The mdBook migration split the manual into one HTML page per section,
but same-page fragment links (e.g. [Standalone setup](#sec-flakes-standalone))
were left pointing at anchors that now live on other pages, so they
silently do nothing when clicked.

Rewrite all cross-page fragment links in docs/manual and
docs/release-notes to page-qualified relative links. Option anchor
links (#opt-*, #nixos-opt-*, #nix-darwin-opt-*) are untouched since
convert-markup.py rewrites those at build time.

Also give the previously empty-label [](#sec-tests) link in the 25.05
release notes a "Tests" label, since the mdBook renderer does not
auto-fill labels from the target heading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:56:06 -05:00
Thierry DelafontaineandMatthieu Coudron a45a7c4514 opencode: use meta.mainProgram to wrap the main binary
Resolves #9640
2026-07-13 19:22:27 +02:00
David ThievonandAustin Horstman 7566825d46 swayimg: support new lua config
Closes: #9479
2026-07-11 21:30:09 -05:00
port22exposedandAustin Horstman ae6f976083 psd: remove aliased packages
aliased packages result in breakage on systems with them disabled and home-manager set to use global nixpkgs
2026-07-11 20:49:16 -05:00
Thierry DelafontaineandAustin Horstman c74f4f6cdd opencode: document plugin option usage in settings description 2026-07-11 19:45:00 -05:00
pancho horrilloandAustin Horstman e473bdcd87 home-cursor: update URL to Freedesktop.org specs
The old one returns a `404 - Page not found` error.

Located the content via archive.org¹ and a quick search yielded the
current URL for the spec.

¹: https://web.archive.org/web/20161024065530/https://specifications.freedesktop.org/icon-theme-spec/latest/ar01s03.html
2026-07-11 18:57:34 -05:00
Rachit Kumar VermaandAustin Horstman 564bc37de5 kitty: fix diff.conf being generated regardless of user config
'cfg.diffConfig' is an implicit submodule with sub-options that carry
their own defaults, so it never actually equals `{ }` - the previous
`cfg.diffConfig != { }` check was always true. This caused
'kitty/diff.conf' to be written (and clobber any existing unmanaged file)
even when the user configured nothing under `programs.kitty.diffConfig`.
2026-07-11 18:56:44 -05:00
csanthiagoandAustin Horstman 0992a29487 television: add extraPackages option with default dependencies
Add an extraPackages option to the television module that wraps the `tv`
binary with additional packages in PATH. Defaults to fd, bat, and ripgrep
which are the dependencies required by television's default channels.

Existing tests updated to set extraPackages = [ ] since they test config
generation, not binary wrapping.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-11 15:00:36 -05:00
Austin Horstman 2afec152df tests/darwinScrublist: add podman
podman pulls in vfkit on darwin, which fails to build on
aarch64-darwin with the current nixpkgs pin (cctools ld crashes
with 'Trace/BPT trap: 5'). The podman darwin tests reference the
real package through the machine watchdog script, establishing a
build dependency. Stub it on darwin test runs.
2026-07-10 23:35:26 -05:00
dependabot[bot]andAustin Horstman d29a1c0f34 flake: bump nixpkgs from 9e92285 to 767b0d3
Bumps [nixpkgs](https://github.com/NixOS/nixpkgs) from `9e92285` to `767b0d3`.
- [Commits](https://github.com/NixOS/nixpkgs/compare/9e92285f211dad236540fd617d7e30e0b99bc0e1...767b0d3ec98a143ad9ed7dfc0d5553510ac27133)

---
updated-dependencies:
- dependency-name: nixpkgs
  dependency-version: 767b0d3ec98a143ad9ed7dfc0d5553510ac27133
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 23:35:26 -05:00
Austin Horstman 3e2af6c9e5 tests/darwinScrublist: add colima, gurk-rs
Both fail to build on aarch64-darwin with the current nixpkgs pin
(cctools ld crashes with 'Trace/BPT trap: 5'): gurk-rs directly, and
vfkit as a colima dependency. Stub them on darwin test runs.
2026-07-10 23:06:34 -05:00
Austin Horstman f938277527 claude-code: tighten module implementation
Remove redundant option machinery, centralize skills shape checks, and use narrower list composition while preserving generated behavior.
2026-07-10 14:19:11 -05:00
Austin Horstman 437ae5be42 claude-code: test plugin collisions
Cover duplicate personal-plugin directory names and collisions between managed plugins and configured skills.
2026-07-10 14:19:11 -05:00
Austin Horstman 5c7199cd3b claude-code: split module files
Move public option declarations and file-entry helpers into dedicated files while keeping module assembly in default.nix. Update the AI labeler for the new module directory.
2026-07-10 14:19:11 -05:00
Austin Horstman 1c029fb0dc claude-code: load plugins without wrapper
Persist managed plugins in Claude Code's personal skills directory so strict-parser subcommands do not receive injected --plugin-dir arguments. Retain version-gated legacy behavior for older and unversioned packages.
2026-07-10 14:19:11 -05:00
Eason20000andAustin Horstman 79e6082586 launchd: treat absent domain as harmless in bootoutAgent
When an agent's launchd.agents.<name>.domain is changed from gui to
user (or the agent is newly installed on a headless macOS system
where the gui/<UID> domain never existed), bootoutAgent attempts to
unload the agent from the old domain first. On such systems, this
fails with: Boot-out failed: 125: Domain does not support specified
action.

The bootoutAgent function only treated "No such process" as a
non-fatal condition (return 2). This change adds the domain-not-exist
error to the same handling path: the agent wasn't running in a domain
that doesn't exist, so we can safely skip the boot-out and proceed
with bootstrap into the new domain.
2026-07-10 08:22:30 -05:00
Austin Horstman 144f4e36d0 files: respect executable for recursive directories
Apply explicit executable settings to recursive directory leaves so directory sources behave like documented file sources.
2026-07-09 12:27:58 -05:00
reesilvaandAustin Horstman f4d01c1d87 posting: add module
posting: add news
2026-07-08 17:33:28 -05:00
reesilvaandAustin Horstman a1fc9a1cc3 maintainers: add reesilva 2026-07-08 17:33:28 -05:00
Austin Horstman 1aac8895fe home-cursor: warn before removing implicit enable
Follow up on home-cursor: remove legacy enable by preserving the implicit enable path that was not covered by the earlier deprecation warning.

Configurations that set home.pointerCursor without top-level enable now continue to work and emit a warning asking users to set home.pointerCursor.enable explicitly.
2026-07-08 10:46:52 -05:00
Austin Horstman cee3f0e7fe yubikey-agent: switch to gui domain for hardware key PIN prompts
yubikey-agent triggers OS-level CCID/PIV PIN entry dialogs when SSH
authentication is requested. These system security prompts require
access to the macOS Aqua session (window server). Running in the user
domain (LimitLoadToSessionType=Background) blocks these prompts,
causing authentication to fail or hang.

Revert to the default gui domain so PIN prompts can render in the
graphical login session.
2026-07-07 22:02:13 -05:00
Austin Horstman 7a62d63707 gpg-agent: switch to gui domain for graphical pinentry support
gpg-agent is commonly configured with graphical pinentry programs
(pinentry-mac, pinentry-gnome3) on macOS that need access to the Aqua
session to display passphrase prompts. Running in the user domain
(LimitLoadToSessionType=Background) prevents these prompts from
rendering, causing passphrase entry to fail silently or hang.

Revert to the default gui domain so the agent runs in the graphical
login session where pinentry can interact with the window server.
2026-07-07 22:02:13 -05:00
Thierry DelafontaineandAustin Horstman 53ebbdc405 proton-pass-agent: switch to gui domain to prevent error accessing keyring
Fixes the following accessing issue:

```
Error accessing credential on keyring: PlatformFailure(Error { code: -25308, message: "User interaction is not allowed." })
```
2026-07-07 16:08:19 -05:00
isabelandAustin Horstman 2ad49968c3 home-cursor: remove legacy enable
its been 482 days since this was merged. users should have had enough
time to migrate
2026-07-07 15:47:40 -05:00
Batuhan Baserdem, Ph.D.andGitHub 3c6f1244ed firefox: allow setting persistent StoreID
Added a new config option programs.firefox.profiles.<name>.storeId.
Setting this option now writes the StoreID both to the profiles.ini
and to the settings."toolkit.profiles.storeID" of the profile.
Firefox has two different profile implementations;
- the old one that home-manager module can declare
- the new "Selectable Profile Service" introduced in 150
The new profiles are managed in a runtime db, and reads from profiles.ini
Migration without database operations is not possible right now,
but when it becomes available, this option will stop home-manager from
clobbering the linked storeIDs.
2026-07-07 15:46:26 -05:00
Thierry DelafontaineandAustin Horstman f09af49406 opencode: assume most recent version when package=null
Resolves #9589
2026-07-07 09:54:30 -05:00
Austin Horstman cd863288d4 codex: stop duplicating hook script deployment
Write path-backed hook bundles only under the active Codex home so XDG mode no longer mirrors scripts into ~/.codex.
2026-07-07 09:53:53 -05:00
RatakorandAustin Horstman 52f93adbac walker: fix systemd dependency cycle with elephant
Signed-off-by: Ratakor <ratakor@disroot.org>
2026-07-07 09:50:48 -05:00
RatakorandAustin Horstman 16acb8c7ff maintainers: add ratakor to walker & elephant
Signed-off-by: Ratakor <ratakor@disroot.org>
2026-07-07 09:50:48 -05:00
RatakorandAustin Horstman 8de7488af2 elephant: fix service unit
Signed-off-by: Ratakor <ratakor@disroot.org>
2026-07-07 09:50:48 -05:00
Saad NadeemandAustin Horstman ed10fd394e clipse: support store paths for theme option 2026-07-07 09:50:12 -05:00
Leon SchwarzäuglandAustin Horstman 63d02d1c19 attic-client: init module 2026-07-06 20:46:24 -05:00
Austin Horstman 0d33882bd4 codex: support path-backed hooks
Allow path-backed hook bundles to provide both hooks.json and supporting scripts through programs.codex.hooks.
2026-07-06 12:20:20 -05:00
Austin Horstman a1645f4077 maintainers: regenerate all-maintainers
Regenerate all-maintainers.nix from module metadata after removing duplicate yarn maintainer entry.
2026-07-04 22:28:41 -05:00
Austin Horstman cf0c5c3e9f maintainers: drop duplicate yarn entry
Remove HM-only yarn maintainer definition and keep module references aligned with nixpkgs maintainer data.
2026-07-04 22:28:41 -05:00
dependabot[bot]andAustin Horstman 40545758d8 flake: bump nixpkgs from 89570f2 to 9e92285
Bumps [nixpkgs](https://github.com/NixOS/nixpkgs) from `89570f2` to `9e92285`.
- [Commits](https://github.com/NixOS/nixpkgs/compare/89570f24e97e614aa34aa9ab1c927b6578a43775...9e92285f211dad236540fd617d7e30e0b99bc0e1)

---
updated-dependencies:
- dependency-name: nixpkgs
  dependency-version: 9e92285f211dad236540fd617d7e30e0b99bc0e1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 22:28:41 -05:00
pancho horrilloandAustin Horstman bfb67e07a3 difftastic: fix jujutsu integration
Don’t append `--color=always` and `--sort-paths` unconditionally, since
`difftastic` doesn’t like repeated arguments, and the user might have
already set them (via `config.programs.difftastic.options`).

To clarify, if `difft` gets called with any parameter repeated, it will
croak:

    difft --color=always --color=always
    error: the argument '--color <WHEN>' cannot be used multiple times

    Usage: difft [OPTIONS] OLD-PATH NEW-PATH

    For more information, try '--help'.

By patching `cfg.options` we ensure that the parameters are defined, and
have the required value, since the user might have selected differently
for their general setup (again, via `config.programs.difftastic.options`).
2026-07-04 22:13:42 -05:00
SigheryandAustin Horstman a7209b6794 syncthing: fix badly escaped ignore patterns test
The previous test would pass no matter what the `ignoreString` was. I
don't know enough Bash/Grep to explain it, but it was easy to test by
just changing the pattern to have `WRONG` instead of `val1`, and that
would still pass the `assertFileContains` test.

I've hopefully fixed it now, by using `lib.escapeShellArg` before
passing it to `assertFileContains`, and I've also added a negative test
case, to ensure that a bad ignore string is _not_ found in the merge
script.
2026-07-04 22:02:21 -05:00
b885baad53 syncthing: add option for ignore patterns
Taken from this nixpkgs commit by @zorrobert:
https://github.com/NixOS/nixpkgs/commit/7a223b26858aaebb44009c8966a1756918e936e3

Syncthing shared folders can be configured to ignore paths. These
changes add support for this functionality.

Co-authored-by: zorrobert <118135271+zorrobert@users.noreply.github.com>
2026-07-03 22:08:35 -05:00
Ben BrownandAustin Horstman b2e4390ff3 kanshi: Add test to check service when settings are unset 2026-07-03 12:27:00 -05:00
Ben BrownandAustin Horstman 1c9ddc707c kanshi: fix check for empty settings
Fixes regression introduced by me in 74b3817.
2026-07-03 12:27:00 -05:00
isabelandAustin Horstman 3a60d513d0 clipse: use rfc 042 style settings 2026-07-03 12:26:19 -05:00
Austin Horstman a4d410db95 fzf: group related module attributes
Group repeated attr prefixes so the fzf module and new tests are easier to scan without changing behavior.
2026-07-02 15:28:15 -05:00
Austin Horstman 0396024ea5 fzf: warn on conflicting ctrl-r bindings
Detect active fzf and history-manager Ctrl-R bindings by their configured values, warn with the winning precedence, and let the normal binding options silence the warning.
2026-07-02 15:28:15 -05:00
Austin Horstman f15a4bf065 fzf: add per-shell widget overrides
Allow each shell integration to override widget commands and options before fzf is sourced, so users can disable or tune bindings only where needed.
2026-07-02 15:28:15 -05:00
Austin Horstman 2d92f5398d fzf: export environment variables to nushell
Pure Nushell sessions do not source hm-session-vars, so feed the same FZF_* values through programs.nushell.environmentVariables before sourcing fzf integration.
2026-07-02 15:28:15 -05:00
Austin Horstman fe3ce0a527 fzf: add historyWidget.command option
Expose FZF_CTRL_R_COMMAND again so users can disable fzf's Ctrl-R binding explicitly, and warn when the configured package is too old to support it.
2026-07-02 15:28:15 -05:00
Austin Horstman cde77509b6 fzf: restructure widget options
Move widget settings under nested option groups while preserving behavior through rename aliases, and centralize FZF environment variable rendering for later reuse.
2026-07-02 15:28:15 -05:00
Austin Horstman 31ca071a7d fzf: remove legacy shell integration fallback
fzf 0.48.0 embeds shell integration scripts, so keep only the supported fzf --bash, --zsh, and --fish paths and assert that integrations use a new enough package.
2026-07-02 15:28:15 -05:00
Austin Horstman 2fca6a7372 atuin: order nushell integration after fzf
Source the Nushell integration at order 2000 so fzf can keep chaining carapace at mkAfter while Atuin still wins Ctrl-R like it does in other shells.

Users who used programs.nushell.extraConfig = lib.mkAfter ... to beat Atuin now need lib.mkOrder 2001 or later.
2026-07-02 15:28:15 -05:00
Benedikt RipsandAustin Horstman f469c79b95 home: document defaults inherited from NixOS/nix-darwin 2026-07-02 10:19:51 -05:00
Benedikt RipsandAustin Horstman 7b64c06f1d home: use lib.literalMD 2026-07-02 10:19:51 -05:00
Benedikt RipsandAustin Horstman 937e85df03 home: check username being non-empty through its type 2026-07-02 10:19:51 -05:00
Benedikt RipsandAustin Horstman c0b7cadd81 home: remove redundant non-emptiness assertion
The option type `path` checks that the path starts with a slash, hence
it can not be empty.
2026-07-02 10:19:51 -05:00
andre4ik3andAustin Horstman ebc87daabc launchd: only use --wait on macOS 26 and later 2026-07-01 22:54:10 -05:00
Ben BrownandAustin Horstman 74b38179ac kanshi: Reload on config change 2026-07-01 15:07:43 -05:00
_andAustin Horstman d32537981c lib: escape backslash in lib.hm.generators.toKDL 2026-06-30 23:49:47 -05:00
Austin Horstman 2f3da3d45f launchd: fix user domain agents
User-domain agents need the Background session type to bootstrap into user/501, and activation should fail rather than silently unloading services when launchctl bootstrap fails.
2026-06-30 23:39:07 -05:00
Amadej KastelicandAustin Horstman 2a37d71bbe herdr: add module 2026-06-30 12:14:44 -05:00
Matej BlagšičandGitHub 5d72a29fc3 hyprpaper: add system target option
Adding option `systemdTarget` to hyprpaper to match other
wayland/hyprland modules. This allows easy fixup of startup dependencies
for the service, depending on system.
2026-06-29 11:13:51 -05:00
Austin Horstman 8f096963ae maintainers: regenerate all-maintainers
Regenerate the combined maintainer list with the lock-aware extractor so migrated nixpkgs maintainers and Home Manager source labels are correct.
2026-06-29 11:12:43 -05:00
Austin Horstman 7042d2b31b lib/nix: use locked nixpkgs for maintainer extraction
Importing <nixpkgs> tied extraction to the caller's NIX_PATH. When that
channel lacks a maintainer referenced through `with lib.maintainers`
(such as sherlock's `_74k1`, which exists in the locked nixpkgs but not
in an older channel), evaluation fails outright and the generated list
varies by machine. Resolve nixpkgs from flake.lock so extraction matches
what Home Manager builds with.

Also classify maintainers by identity (github/name) rather than attribute
name so renamed entries like `_9p4` still match, and correct the
arguments passed to the manual derivation so its maintainer is collected.
2026-06-29 11:12:43 -05:00
Austin Horstman 9eec3ef087 maintainers: remove quoted names in set
Prefer underscore to match nixpkgs.
2026-06-29 11:12:43 -05:00
Austin Horstman 789a35fbde maintainers: remove duplicate maintainers 2026-06-28 22:04:23 -05:00
dependabot[bot]andAustin Horstman 2c661cc960 flake: bump nixpkgs from 3e41b24 to 89570f2
Bumps [nixpkgs](https://github.com/NixOS/nixpkgs) from `3e41b24` to `89570f2`.
- [Commits](https://github.com/NixOS/nixpkgs/compare/3e41b24abd260e8f71dbe2f5737d24122f972158...89570f24e97e614aa34aa9ab1c927b6578a43775)

---
updated-dependencies:
- dependency-name: nixpkgs
  dependency-version: 89570f24e97e614aa34aa9ab1c927b6578a43775
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 22:04:23 -05:00
Felix StuppandGitHub 1cba40f931 vscode: refactor profile paths to use unified profileDir helper
Refactor code to remove duplicated code. The location of the profile directory dependent on the profile name was duplicated over several locations. This PR unifies this into one single helper function profileDir.
2026-06-28 21:50:51 -05:00
Felix StuppandGitHub abeeea1221 vscode: fix userMcp option example syntax
literalExample with JSON code is wrong, changed to direct Nix attr
2026-06-28 21:50:04 -05:00
Jure VarlecandAustin Horstman 4ad9aaae70 generic-linux-gpu: add EGL support for Nvidia 2026-06-28 09:30:28 -05:00
Joshua FergusonandAustin Horstman 8d8a6cc50d gpg: make package optional 2026-06-26 20:08:58 -05:00
Peter BittnerandAustin Horstman 6eba758fee uv: prune Python versions and tools by diff
The prune option uninstalled all managed Python versions (and tools)
and reinstalled them on every activation, which is slow. Instead,
resolve each requested version to the install target uv would produce
and uninstall only the difference; already-correct versions are left
untouched. Resolving to the install target (not every installed match)
also prunes superseded patch releases, which uv retains on upgrade.

Tools are diffed by PEP 503-normalized package name: requested names
are extracted and normalized at build time, and the installed set is
read from uv's tool directory, whose entries are already normalized, so
no uv output is parsed. Pruning to an empty set still removes everything.
2026-06-26 20:08:04 -05:00
Vinicius DeolindoandAustin Horstman 5d320ab301 fzf: source nushell integration after carapace 2026-06-25 16:45:22 -05:00
Austin Horstman 19d7472aca codex: reorganize module files
Move option definitions and helper functions into dedicated files while keeping module assembly in default.nix.
2026-06-25 14:21:12 -05:00
Austin Horstman 2ebe9c0fe7 codex: sanitize plugin cache paths
Use sanitized plugin name and version components for cache targets while preserving manifest names in Codex config.
2026-06-25 14:21:12 -05:00
Austin Horstman 64221f9c87 codex: add lifecycle hooks option
Manage CODEX_HOME/hooks.json directly while covering the generated file in the existing TOML test.
2026-06-25 14:21:12 -05:00
Austin Horstman 1c64122e70 codex: add context override file
Expose AGENTS.override.md declaratively and cover it in the existing TOML generation test.
2026-06-25 14:21:12 -05:00
Austin Horstman 01e23f497c codex: update upstream config reference
Use the current Codex configuration docs URL and snake_case model provider keys in examples and fixtures.
2026-06-25 14:21:12 -05:00
Austin Horstman 9e3e1d95be codex: add maintainer khaneliman 2026-06-25 14:21:12 -05:00
Austin Horstman 3d8f49f360 alacritty: normalize key binding escape sequences
`pkgs.formats.toml` escapes backslashes and cannot emit raw control
characters, so key-binding `chars` written as escape text (e.g.
"\uXXXX" or the "\^[" caret form) ended up as invalid literals in the
generated config. Rewrite each escape to a placeholder before
generation and restore it to "\u" afterward.

Use `builtins.split` so escapes are handled anywhere in the value,
including when repeated or mixed with other text.
2026-06-25 14:20:58 -05:00
085e92b04a github-copilot-cli: preserve args for local-type MCP servers
forCopilotFormat only checked for type == "stdio" to add back args,
missing the case where a server already has type = "local" (e.g. set
directly by a third-party module). This caused args to be absent in the
generated mcp-config.json, failing validation.

Also, running forCopilotFormat as an extraTransform meant it ran inside
transformMcpServer, which then filtered out args = [] in its cleanup
pass. Move forCopilotFormat to run after transformMcpServer so the
empty args list is preserved in the output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 14:16:07 -05:00
Dionisie StratulatandGitHub 3b3ff17621 firefox: legacyUserProfileCustomizations when userChrome is present
When a user configures the userChrome option, automatically enable the
`toolkit.legacyUserProfileCustomizations.stylesheets` preference with
`mkDefault` priority so that the user chrome CSS is actually loaded.
The user can still override this in the settings option if needed.
2026-06-25 14:15:06 -05:00
r-vdpandAustin Horstman 3a70e333f2 fish: extract completion generator from the fish binary
fish 4.8.0 stops installing share/fish/tools/ and embeds the script
in the binary instead. Instead we use `status get-file` to extract it,
which works for fish >= 4.1.

See NixOS/nixpkgs#534907.
2026-06-24 22:36:00 -05:00
Austin Horstman a9232b8b10 launchd: use user domain for background agents
Move non-graphical Darwin services out of the GUI launchd domain so they can be managed from non-Aqua contexts such as SSH sessions.
2026-06-24 20:38:38 -05:00
Austin Horstman dc89b0c0bb launchd: add agent domain option
Allow Darwin LaunchAgents to target either the GUI or user launchd domain while preserving gui as the compatibility default.
2026-06-24 20:38:38 -05:00
Austin Horstman 062581938b claude-code: reject disabled mcp servers instead of dropping
Filtering disabled shared servers out of `.mcp.json` (#9457) collapsed
"present but disabled" and "absent" into the same output, so a server
set
`enabled = false` for OpenCode could no longer remain present in Claude
Code. `.mcp.json` has no per-server `enabled` field, but settings.json
does via `disabledMcpjsonServers`.

Keep disabled servers in `.mcp.json` and list them under
`disabledMcpjsonServers` (merged with any user-set value). This honors
the
disabled intent without dropping the server, and covers both shared and
Claude Code-native servers.
2026-06-23 11:54:25 -05:00
Benedikt RipsandAustin Horstman 1ac720f007 home: document the 'home.profileDirectory' default 2026-06-23 11:11:50 -05:00
ReStrangerandAustin Horstman d6b7bf1695 hyprland-qt-support: add news entry
Signed-off-by: ReStranger <restranger@disroot.org>
2026-06-23 11:10:25 -05:00
ReStrangerandAustin Horstman 16fdc3504e hyprland-qt-support: add test case
Signed-off-by: ReStranger <restranger@disroot.org>
2026-06-23 11:10:25 -05:00
ReStrangerandAustin Horstman 83c0e03687 hyprland-qt-support: init module 2026-06-23 11:10:25 -05:00
Bruno BELANYIandAustin Horstman 4b0b947474 zsh: add 'fastSyntaxHighlighting' 2026-06-23 08:09:29 -05:00
leiserfgandAustin Horstman 471a1d2f84 news: Add devenv entry 2026-06-22 17:14:11 -05:00
leiserfgandAustin Horstman 6cb9da2a4d devenv: init module 2026-06-22 17:14:11 -05:00
leiserfgandAustin Horstman 351959fcdb fzf: Add tests 2026-06-22 15:17:52 -05:00
leiserfgandAustin Horstman cb6bcc43e7 fzf: add nushell integration 2026-06-22 15:17:52 -05:00
mikaeladevandAustin Horstman af11a877f2 nvibrant: add module 2026-06-22 13:55:29 -05:00
Austin Horstman 66eb0fc116 docs: clarify new module news conditions
Clarify that new-module news should remain visible to users who have not enabled the new module yet.
2026-06-22 12:35:03 -05:00
leiserfgandAustin Horstman 979bfee6e1 vicinae: add firefox mesaging host 2026-06-22 08:19:19 -05:00
Austin Horstman d8dac1f668 ci: build docs in buildbot output
GitHub Actions runners lack a reusable /nix/store, so rendering the full
manual, options JSON, and manpages on every docs PR pays that cost
uncached. buildbot has a persistent store, so expose the docs outputs
there instead and leave the GHA docs step at the lightweight maintainers
check.
2026-06-21 23:44:06 -05:00
Austin Horstman f3dc0b8583 docs: update platform module guidance
Program and service modules are auto-imported now, so contributor docs should point platform-specific work at guarded module behavior and tests instead of loadModule.
2026-06-21 22:58:48 -05:00
Austin Horstman db97e414d5 docs: describe embedded module integration
Document the extra module arguments, shared modules hook, and NixOS activation modes so embedded Home Manager setup matches the current integration code.
2026-06-21 22:51:39 -05:00
Austin Horstman e731ff60ec docs: correct test command guidance
Document list and run commands separately, keep test runner usage through the flake wrapper, and avoid stale legacy full-suite commands.
2026-06-21 22:43:01 -05:00
Austin Horstman e45cc6e15a docs: fix manpage path
The generated home-configuration manpage is not compressed, so point contributors at the file that docs.manPages actually creates.
2026-06-21 22:20:43 -05:00
Austin Horstman 8f3bc61192 news: remove duplicate module announcements
Drop sparse duplicate entries for amberol and mpdscribble while keeping the richer platform-gated announcements.
2026-06-21 22:07:39 -05:00
Austin Horstman 5bf17310cf docs: preserve legacy manual links
Generate XHTML-safe redirect shims that route old single-page manual anchors to their mdBook pages.
2026-06-21 22:02:16 -05:00
Thierry DelafontaineandAustin Horstman d1ccd0721e news: add podman registries v2 entry 2026-06-21 09:20:14 -05:00
Thierry DelafontaineandAustin Horstman e3e24bfd0f podman: generate v2 registries.conf
Convert `services.podman.settings.registries` output to v2 format.
Keep legacy `insecure` and `block` inputs, add `registry` entries,
and warn when legacy options are used.

Update podman configuration tests and expected registries.conf output.

Resolves #9501
2026-06-21 09:20:14 -05:00
Austin Horstman 78e7d8b13e ci: checkout base ref for backports
Checkout v7 still treats the pull_request_target default ref as fork PR code, so pin the backport checkout to the trusted base SHA.
2026-06-20 16:06:13 -05:00
Austin Horstman ee6b160bcb ci: avoid fork head checkout in backport
Checkout v7 blocks fork PR head checkouts in pull_request_target workflows, so keep backport on the trusted base checkout and let the backport action fetch PR commits itself.
2026-06-20 16:01:01 -05:00
Anton MosichandAustin Horstman 61157d3c01 pimsync: have type be the first directive in storage blocks
pimsync 0.5.10 introduced a change to config parsing which now mandates
that the type directive is the first one in each storage block.
2026-06-20 15:54:19 -05:00
Austin Horstman 8695ecb389 tests/firefox: avoid building librewolf
Stub the Librewolf package in module tests so shared Firefox logic still runs without evaluating or building the insecure nixpkgs package.
2026-06-20 13:40:22 -05:00
Austin Horstman 8762262054 tests/darwinScrublist: add parallel-full
Scrub the default GNU Parallel package on Darwin so the module test does not build the failing DBD::CSV dependency.
2026-06-20 13:40:22 -05:00
dependabot[bot]andAustin Horstman 72a8bf11c8 flake: bump nixpkgs from ffa10e2 to 3e41b24
Bumps [nixpkgs](https://github.com/NixOS/nixpkgs) from `ffa10e2` to `3e41b24`.
- [Commits](https://github.com/NixOS/nixpkgs/compare/ffa10e26ae11d676b2db836259889f1f571cb14f...3e41b24abd260e8f71dbe2f5737d24122f972158)

---
updated-dependencies:
- dependency-name: nixpkgs
  dependency-version: 3e41b24abd260e8f71dbe2f5737d24122f972158
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-20 13:40:22 -05:00
Juan C NunoandAustin Horstman a4ee5403ca home-manager: Pass extraArgs to nix-instantiate
My NIX_PATH is empty because of the new system.nix NixOS entry point.
I've been relying on -I options more and here they're not being passed
to nix-instantiate.
2026-06-20 12:38:59 -05:00
dependabot[bot]andAustin Horstman c3dfce6c63 ci: bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-20 12:35:00 -05:00
Robert Helgesson 37f21dfa5d tests: fix integration tests
This updates the integration tests so that they pass. Also prevent
auto-fixing some integration test files.
2026-06-20 00:05:51 +02:00
Austin Horstman 153bad8fb5 qt: stop rewriting platformTheme name gtk to gtk2
Setting `qt.platformTheme.name = "gtk"` silently rewrote the
QT_QPA_PLATFORMTHEME session variable to `gtk2` and pulled in the
qtstyleplugins and qt6gtk2 packages, which is not what users asked for
and relies on unmaintained packages. There is no `gtk` Qt platform
theme, so `gtk` now maps to Qt's built-in `gtk3` platform-theme plugin
and installs no extra packages.

Users who want the previous behavior can set
`qt.platformTheme.name = "gtk2"` explicitly, which keeps
QT_QPA_PLATFORMTHEME=gtk2 and installs qtstyleplugins and qt6gtk2. The
`qt.useGtkTheme` -> `qt.platformTheme` migration now maps the legacy
option to `{ name = "gtk2"; }` so existing configs are unaffected.

Fixes #8663
2026-06-19 12:28:04 -05:00
Austin Horstman e061b00885 broot: fix nushell integration br command
broot 1.51 renamed the printed nushell entry point from `br` to `main`,
intended to be loaded with `use` where the command takes the module's
name. Home Manager sources the snippet with `source`, so it began
defining a global `main` instead of `br`, breaking the `br` command in
nushell.

Rewrite the definition line back to `br` for the nushell shell function
so `source` keeps exposing `br` globally. Other shells are unaffected.

Fixes #9392
2026-06-19 10:53:03 -05:00
Austin Horstman 55730a2e34 ssh: document DAG ordering patterns
Expand the settings example so users can discover stable headers and list-shaped DAG helpers for ordered Host and Match blocks.
2026-06-19 10:13:44 -05:00
Austin Horstman fea021792d treefmt: format Python with ruff
Add ruff to the tree formatter so Python sources are kept consistently
formatted by 'nix fmt' and the CI format check. Test fixtures under
tests/modules are excluded since some are intentionally invalid Python.
Reformats the existing lib/python and tests scripts accordingly.
2026-06-19 10:00:25 -05:00
Austin Horstman 6aaf73d18d docs: build manual with mdbook
Switch the HTML manual renderer to mdBook while preserving existing docs package outputs and legacy redirect paths.
2026-06-19 10:00:25 -05:00
Austin Horstman 9b2afbdd47 docs: add contributing pointer
Point contributors at the manual contribution guidelines from the
repository root.
2026-06-19 10:00:10 -05:00
Austin Horstman 0043376f4e docs: clarify module contribution guidance
Document reviewer expectations for new modules so contributors can align option design, news, test organization, and commit structure before review.
2026-06-19 10:00:10 -05:00
mikaeladevandAustin Horstman 43f9f0abc6 misc: move modules spanning multiple files into subdirs
- modules/misc/qt.nix -> modules/misc/qt/default.nix
- modules/misc/gtk.nix -> modules/misc/gtk/default.nix
- modules/misc/news.nix -> modules/misc/news/default.nix
- modules/misc/xdg*.nix -> modules/misc/xdg/...
- modules/misc/nix*.nix -> modules/misc/nix/...
2026-06-19 08:59:16 -05:00
pancho horrilloandAustin Horstman c804fab681 difftastic: fix typo in example
The "dark" value actually applies to the `background` setting (which
I’ve omitted here since "dark" is its default value.)

I’ve given `color` the value "always", which will preserve colorization
when using `less` as a pager.

Extracted from `difft --help`¹:

Options:
...
        --color <WHEN>
            When to use color output.

            [env: DFT_COLOR=]
            [default: auto]
            [possible values: always, auto, never]

        --background <BACKGROUND>
            Set the background brightness. Difftastic will prefer brighter colours on dark backgrounds.

            [env: DFT_BACKGROUND=]
            [default: dark]
            [possible values: dark, light]

¹: which seems to be the authoritative source of documentation, as per
https://difftastic.wilfred.me.uk/usage.html#configuration-options
2026-06-18 23:47:04 -05:00
DrymarchonShaunandGitHub 266211649f hyprland: fix plugin loading when using lua
Move plugin loading to the top of the rendered ~/.config/hypr/hyprland.lua as plugins must be loaded before hl.config can be called with config keys added by the plugin, (binds using dispatchers added by plugins also fail if they are defined before the plugin is loaded)

This also switches over to using the (undocumented?) hl.plugin.load("/path/to/plugin.so") to load plugins, which has solved some strange race condition weirdness caused by using exec_cmd with hyprctl plugin load (I first tried moving renderStartHook to the top of the rendered config, but sometimes it seemed the hyprctl plugin load wouldn't finish loading the plugin before the rest of the config was loaded, causing the same errors about unknown config keys)
2026-06-18 22:39:21 -05:00
Dietrich DarochandAustin Horstman 3abeedcf40 zellij: Avoid starting on dumb terminals
This avoids https://github.com/zellij-org/zellij/issues/3824
2026-06-18 21:57:40 -05:00
0f4a317c06 lib.hm.dag: performance improvements
Avoid repeated attribute lookups and unnecessary intermediate allocations in
DAG helpers. Cache DAG names while normalizing before-edges to improve topoSort
performance on large DAGs.

Co-authored-by: Eman Resu <78693624+quatquatt@users.noreply.github.com>
2026-06-18 21:53:23 -05:00
Benedikt RipsandAustin Horstman c8f0b8ed44 fontconfig: add RFC42-style config file settings 2026-06-18 14:16:03 -05:00
Benedikt RipsandAustin Horstman 4441446672 fontconfig: generate config with pkgs.formats.xml 2026-06-18 14:16:03 -05:00
Benedikt RipsandAustin Horstman 27a3154f42 fontconfig: disallow null as config file source
This brings `fonts.fontconfig.configFile.*.source` in line with
`home.file.*.source` and the like.
2026-06-18 14:16:03 -05:00
Benedikt RipsandAustin Horstman 4ce682641d fontconfig: add 'configFile.<name>.target' option
This brings `fonts.fontconfig.configFile` in line with `home.file` and
the like.
2026-06-18 14:16:03 -05:00
Benedikt RipsandAustin Horstman 4666af39e9 fontconfig: clarify 'configFile.<name>.*' descriptions 2026-06-18 14:16:03 -05:00
Benedikt RipsandAustin Horstman 82432731d3 fontconfig: check warning in legacy default test 2026-06-18 14:16:03 -05:00
Austin Horstman a786067676 mkFirefoxModule: assert managed package for global extensions
Reject globalExtensions when Home Manager has no browser-managed policy
carrier. This follows up the global extension policy work with a clear
failure mode, a regression test, and a news entry so affected users know
how to fix package = null setups.
2026-06-18 12:53:30 -05:00
9f9b23ae21 mkFirefox: add policy to accept added extensions
This commit adds extensions added via
profiles.<name>.globalExtensions to the policy.
In consequence the extensions are added without complaint and don't need
to be enabled manually.

Co-authored-by: khaneliman <khaneliman@users.noreply.github.com>
2026-06-18 12:38:01 -05:00
glmlmandAustin Horstman 8af17160d1 nixgl: preserve __functionArgs when forwarding .override
Forwarding `.override` calls strips the attached metadata, which leads to unexpected behavior in some modules.

Closes #9494
2026-06-18 12:01:25 -05:00
Austin Horstman d456f483f1 codex: add profile config support
Codex 0.134.0 stopped reading profile settings from [profiles.<name>] in config.toml and no longer supports the top-level profile selector.

Add a dedicated programs.codex.profiles option so Home Manager can write CODEX_HOME/<name>.config.toml files that match the current CLI --profile behavior. For Codex 0.134.0 and later, legacy programs.codex.settings.profiles entries are migrated into profile files with a warning, and removed legacy profile keys are omitted from config.toml so strict config loading keeps working. Older Codex versions keep the legacy config shape.
2026-06-18 08:19:47 -05:00
glmlmandGitHub 6274683ee4 mkFirefoxModule: add warning for dropped options on wrapped packages
Warns only if the package lacks a `cfg` override and relevant options are configured.

References
https://github.com/nix-community/home-manager/pull/9486
2026-06-18 08:17:43 -05:00
Austin Horstman 5ccd408ca7 dunst: use DAG ordered generator helper 2026-06-18 07:59:18 -05:00
Austin Horstman b9a29e51c5 opencode: support ordered settings entries
Use the shared DAG-aware JSON generator for programs.opencode.settings so order-sensitive permission rules can be expressed with lib.hm.dag entries.

Add a focused NMT test for last-match-wins permission output and news for existing opencode users.
2026-06-18 07:59:18 -05:00
Austin Horstman 6716d811e5 lib: add DAG-aware format generators
Add reusable Home Manager generator helpers for rendering freeform attrsets that contain lib.hm.dag entries.

This keeps ordering support available to JSON and future generated formats without forcing each module to carry its own DAG sorting boilerplate.
2026-06-18 07:59:18 -05:00
Austin Horstman 652f754686 difftastic: support difftool-only git integration
Splitting git diff tools into the separate programs.difftastic module
made it impossible to register difftastic as a git difftool without also
forcing it as diff.external, since the boolean git.diffToolMode only
toggled between "external" and "external + difftool".

Replace git.diffToolMode with a git.mode enum ("external", "difftool",
"both") so difftastic can be wired as the difftool only, leaving
`git diff` untouched. The deprecated boolean is migrated automatically
via mkChangedOptionModule (true -> "both", false -> "external"),
preserving existing behavior.

Fixes #8592
2026-06-18 07:45:53 -05:00
Fida Waseque ChoudhuryandAustin Horstman 5bcff43156 antigravity-cli: fix mcp validation error for remote/local servers
Do not serialize null/empty fields (command, args, env) for remote servers.
Also fix 'isRemote' check to check for non-null url fields instead of
using the ? operator, which evaluates to true even if the key is null.
2026-06-17 21:59:28 -05:00
csanthiagoandAustin Horstman c187002980 television: add themes option
Add a `themes` option to the television module, allowing users to define
custom themes that are written to `$XDG_CONFIG_HOME/television/themes/`
as individual TOML files. This follows the same pattern used by the
halloy and helix modules.


💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-06-17 19:16:21 -05:00
csanthiagoandAustin Horstman eb0eb170bb television: add csanthiago as maintainer 2026-06-17 19:16:21 -05:00
csanthiagoandAustin Horstman a20c21b2fe maintainers: add csanthiago
💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-06-17 19:16:21 -05:00
Peter BittnerandAustin Horstman c51ac59e59 uv: manage Python versions and tools
Add programs.uv.python.{versions,default,prune} and
programs.uv.tool.{packages,prune} to install uv-managed Python versions
and tools during activation.

Install requests are passed verbatim to uv. Unpinned entries track the
latest release on each activation while pinned ones stay put: Python
requests without a patch component are installed with `uv python install
--upgrade` (exact patches, which uv refuses to upgrade, get a plain
install), and tools are refreshed with `uv tool upgrade <packages>`
scoped to the configured list. Both upgrades are scoped to the managed
entries, so installs Home Manager does not own are left untouched. prune
makes a set declarative by running `uv ... uninstall --all` before
reinstalling the listed entries.

The activation blocks run after linkGeneration so uv sees the freshly
linked uv.toml, and a null programs.uv.package is rejected once the
module emits activation commands.
2026-06-17 15:47:24 -05:00
Peter BittnerandAustin Horstman a69317872e uv: add bittner as a maintainer 2026-06-17 15:47:24 -05:00
Peter BittnerandAustin Horstman bfc003171b maintainers: add bittner 2026-06-17 15:47:24 -05:00
Austin Horstman ea0bf49c9d thunderbird: merge account prefs with mergeAttrsList
The SMTP, identity, and profile pref sets were combined with linear foldl'
of //. mergeAttrsList performs the equivalent right-biased shallow merge and
scales with the account/address count without quadratic recopies.
2026-06-17 13:04:43 -05:00
Austin Horstman 50978d4bf6 neomutt: merge account rc files with mergeAttrsList
Per-account rc files were combined with a linear foldl' of //. mergeAttrsList
is the equivalent shallow merge without the quadratic accumulator recopy.
2026-06-17 13:04:43 -05:00
Austin Horstman b3f454e5c2 zsh: merge plugin home.file entries with mergeAttrsList
Plugin source links were combined with a linear foldl' of //. mergeAttrsList
performs the equivalent shallow merge and scales with the plugin count.
2026-06-17 13:04:43 -05:00
Austin Horstman cbbddc4a6f go: merge package src links with mergeAttrsList
The GOPATH src symlinks were combined with a linear foldl' of //.
mergeAttrsList is the equivalent shallow merge and scales with the package
count without quadratic recopies.
2026-06-17 13:04:43 -05:00
Austin Horstman b06723c4d4 getmail: merge home.file entries with mergeAttrsList
Per-account config files were combined with a linear foldl' of //.
mergeAttrsList performs the equivalent shallow merge without the quadratic
accumulator recopy.
2026-06-17 13:04:43 -05:00
Austin Horstman 9b262cb738 browserpass: merge home.file entries with mergeAttrsList
The native-messaging host files were combined with a linear foldl' of //,
which recopies the accumulator on every step (O(n^2)). mergeAttrsList does
the same right-biased shallow merge with a balanced binary merge.
2026-06-17 13:04:43 -05:00
Austin Horstman 71b4b46f23 thunderbird: use foldl' for profiles.ini merge
profilesIni folded recursiveUpdate over the profile list with the
non-strict foldl, building a thunk chain over the growing accumulator.
Use the strict foldl'.
2026-06-17 13:04:43 -05:00
Austin Horstman b8818d5a65 neovim: collect plugin configs with filter and map
generatedConfigs built its list with a non-strict foldl that appended via
acc ++ [ p.config ], an O(n^2) pattern. Filtering out null configs and
mapping is equivalent, order-preserving, and avoids the repeated list
copies.
2026-06-17 13:04:43 -05:00
Austin Horstman ce68ac6958 lib/generators: short-circuit important-field check with any
toHyprconf' tested every important prefix with a non-strict foldl that
always scanned the full list. lib.any expresses the same any-prefix-matches
intent and stops at the first match. Runs once per attribute in the
Hyprland generator.
2026-06-17 13:04:43 -05:00
Austin Horstman a69e55d05b fusuma: use lib.makeBinPath for service PATH
The local makeBinPath helper reimplemented lib.makeBinPath with a
non-strict foldl accumulator. Use the stdlib function instead: it is
strict and removes the duplicated logic.
2026-06-17 13:04:43 -05:00
K900andAustin Horstman fa4e2f7e10 modules/kdeconnect: stop overriding PATH, remove support for prehistoric versions
Bad. No. Why.
2026-06-17 10:48:18 -05:00
adisbladisandAustin Horstman d2d79394a4 maintainers: remove adisbladis
I haven't even used home-manager in a very long time, much less maintained it.
2026-06-17 10:48:10 -05:00
0xda157andAustin Horstman a2e23ee8f8 helix: make package nullable 2026-06-17 09:32:40 -05:00
Austin Horstman 89fda2248e mise: add usage dependency for shell completions
The generated script calls the separate `usage` CLI at completion time,
so add `pkgs.usage` to `home.packages` whenever mise is installed.
Stub it on Darwin test runs via `tests/darwinScrublist.nix`.
2026-06-17 09:32:24 -05:00
Austin Horstman 524d73a903 mise: add bash completion to bash integration
The bash integration previously only ran `mise activate bash`, so tab
completion for `mise` was never set up. Source the generated completion
script via `mise completion bash --include-bash-completion-lib`; the
flag self-bundles the bash-completion helpers so it also works on
systems whose bash-completion predates `_comp_initialize`.

Fixes #8539
2026-06-17 09:32:24 -05:00
ErinaYipandAustin Horstman 7664e05e24 maintainers: add ErinaYip 2026-06-16 22:42:18 -05:00
ErinaYipandAustin Horstman 2a7b54df12 prismlauncher: add themes option 2026-06-16 22:42:18 -05:00
0xda157andAustin Horstman 45e97d1e88 television: remove da157 from maintainers 2026-06-16 22:32:46 -05:00
Pat L.andGitHub df4e046571 codex: Add support for installing plugins and marketplaces
Install the referenced plugins into $CONFIG_DIR/plugins and add them to
the marketplace file in `~/.agents/plugins/marketplace.json`. The
plugins are then enabled in `$CONFIG_DIR/config.toml`.

Also add support for custom plugin marketplaces via the `marketplaces`
option. Each marketplace entry is a local directory (or fetched package)
that Codex can browse to discover additional plugins. Marketplaces are
registered in `$CONFIG_DIR/config.toml` under [marketplaces.<name>] with
`source_type = "local"` pointing at the provided path. Setting either
`plugins` or `marketplaces` automatically enables the `features.plugins`
flag in the generated config.

Warn that if a derivation is being used as the source of the plugin, the
name of the derivation needs to match the plugin name in the manifest.
2026-06-16 15:35:13 -05:00
Austin Horstman 34dd288e65 antigravity-cli: update skills deployment path
The skills folder deployment path has been updated to
'~/.gemini/antigravity-cli/skills/' to align with the application's
configuration path structure from docs. I think the other path is just a
weird compatibility shim that app supports. strace shows it will search
it, but it also checks the documented path.
2026-06-16 08:06:05 -05:00
Ihar HrachyshkaandAustin Horstman c03e475289 programs/thunderbird: stop writing directory prefs
Thunderbird prefers[1] directory-rel for account local paths and only
falls back to directory when the relative pref is absent. To add, the
generated directory values were home-relative paths while the app
expects absolute paths when reading the setting. Instead of fixing
these, just drop the redundant settings.

[1] https://searchfox.org/comm-central/source/mailnews/base/src/nsMsgIncomingServer.cpp#745
2026-06-15 16:01:52 -05:00
home-manager-ci[bot]andAustin Horstman 2d4f4e2788 maintainers: update all-maintainers.nix
Automated update of the master maintainers list combining:
- Home Manager specific maintainers from modules/lib/maintainers.nix
- Nixpkgs maintainers referenced in Home Manager modules

**Added:** 1 maintainers
**Removed:** 0 maintainers
**Total:** 300 → 301 maintainers

** Added:** superflash41

Generated by: lib/python/generate-all-maintainers.py
2026-06-15 15:53:00 -05:00
glmlmandGitHub 8aec76cc1e mkFirefoxModule: fix unexpected argument error on derivatives
Fix a compilation error when using a wrapper package that does not accept the `cfg` argument in its override.

Closes #9485
2026-06-15 09:26:48 -05:00
Fida Waseque ChoudhuryandAustin Horstman a813a04113 antigravity-cli: fix programs.mcp.servers integration for remote servers
Do not rename url to serverUrl in transformMcpServer's extraTransforms.
This caused the core library to incorrectly default the server type to stdio,
and subsequently erase the serverUrl attribute entirely during cleanup.

The renaming is already handled at the end by the transformMcpServers helper.
2026-06-15 09:25:46 -05:00
Changseo JangandAustin Horstman 1285cd3d68 keepassxc: Support macOS KeePassXC configuration
Refactor configuration for KeePassXC settings based on platform.
2026-06-14 23:23:24 -05:00
Cody ShearerandAustin Horstman ed8263e7d7 voxtype: fix output mode paste
Voxtype requires the command `cat` from
`coreutils` when using the output mode `paste`,
but fails with the current configuration because
it cannot find `cat`.

```
WARN paste (clipboard + keystroke) failed: Text
injection failed: wl-copy exited with error,
trying next
```

This commit adds `coreutils` to the runtime path
to resolve the path to `cat`.
2026-06-14 17:58:52 -05:00
Aguirre MatteoandAustin Horstman 1b7fb5e6b4 maintainers: dump aguirre-matteo from almost all modules 2026-06-14 17:57:38 -05:00
EdontinandAustin Horstman 5b6f573372 zed-editor: fix programs.mcp.servers compatibility
Default args to an empty list on stdio servers as it's a Vec<String>,
and loading the configuration fails if it's missing. This cannot be done
in extraTransforms due to the final filterAttrs.

Remove addType call as ContextServerSettings doesn't use type to
differentiate server types.

Link: https://github.com/zed-industries/zed/blob/601ecb3ee5c16940191818ee7f244837abf6983c/crates/settings_content/src/project.rs#L484
Link: https://github.com/zed-industries/zed/blob/601ecb3ee5c16940191818ee7f244837abf6983c/crates/project/src/project_settings.rs#L182
2026-06-13 10:42:15 -05:00
Tommaso SoncinandAustin Horstman c87a39aa97 Translate using Weblate (Italian)
Currently translated at 100.0% (39 of 39 strings)

Translate using Weblate (Italian)

Currently translated at 93.7% (15 of 16 strings)

Co-authored-by: Tommaso Soncin <soncintommaso@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/it/
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/it/
Translation: Home Manager/Home Manager CLI
Translation: Home Manager/Home Manager Modules
2026-06-12 18:04:56 -05:00
blokykandAustin Horstman 486595d2cf zsh: make sure siteFunction names don't contain slashes 2026-06-11 09:45:14 -05:00
blokykandAustin Horstman 19c41a5a6d zsh: escape siteFunction names 2026-06-11 09:45:14 -05:00
blokykandAustin Horstman d29e0ab7c2 zsh: correctly handle autoloading functions starting with '-' 2026-06-11 09:45:14 -05:00
Austin Horstman bb68a1a2e7 docs: add release notes contributing guidance 2026-06-11 09:05:37 -05:00
toastalandRobert Helgesson 7dbd305f8b darcs: add defaults
This adds support for specifying default values for Darcs commands.
2026-06-11 00:13:36 +02:00
Austin Horstman f96f717a47 vscode: support path literals in userSettings
programs.vscode.profiles.<name>.userSettings accepts either JSON settings or a path to a settings.json file. The update-check defaults were merged into userSettings before deciding whether to generate JSON or link a source file, so path values failed when Nix tried to apply // to them.

Skip those merges for path-like values and use the shared isPathLike helper for VS Code's source-or-generated JSON options so Nix paths, store path strings, and derivation outputs are handled consistently.

Fixes #7726
2026-06-10 13:10:31 -05:00
Austin Horstman 2b394beca7 docs: fix sshAuthSock reference in rl-2605
The link to `#opt-sshAuthSock.enable` broke the manual build on
release-26.05, where the `enable` option does not exist. Drop the
broken reference and the inaccurate "enabled and" wording.
2026-06-10 13:05:30 -05:00
superflash41andAustin Horstman 74887eaee2 kiro-cli: add module 2026-06-10 09:48:41 -05:00
superflash41andAustin Horstman 6cf5b64c12 maintainers: add superflash41 2026-06-10 09:48:41 -05:00
Austin Horstman cadadbc6f4 claude-code: omit unsupported mcp enabled field 2026-06-10 08:38:54 -05:00
MalikandAustin Horstman 1ffdc28076 news: add entry for programs.mcp typed schema 2026-06-09 23:03:52 -05:00
MalikandAustin Horstman ebc55fd103 claude-code, codex, antigravity-cli, github-copilot-cli, opencode, vscode, zed-editor: use mcp lib
- use lib.hm.mcp.transformMcpServer for MCP integration
- Update tests to reflect new merged server configuration
2026-06-09 23:03:52 -05:00
MalikandAustin Horstman 6a66db1360 mcp: extract helper functions into lib/mcp.nix
Extract MCP-related helper functions (renderEnv, mkEnvFilesWrapper,
wrapEnvFilesCommand, addType, transformMcpServer) into a new shared
library module at modules/lib/mcp.nix.
- Add lib.hm.mcp for sharing transformMcpServer logic across consumers
- Add enabled/disabled conflict detection assertion
- Add tests for new library functions
2026-06-09 23:03:52 -05:00
Austin Horstman c30f177760 docs: update release notes for 26.05
Lots of missing information about major changes throughout the last
release.
2026-06-09 22:44:48 -05:00
Austin Horstman bd4277722d tests: add nixpkgs-disabled warning/assertion coverage
Evaluate `nixpkgs-disabled.nix` standalone (the harness always loads the
real `nixpkgs.nix`) to verify the warning and assertion report the offending
`nixpkgs.config`/`nixpkgs.overlays` definition locations.
2026-06-09 17:15:30 -05:00
Austin Horstman d6f3413874 nixpkgs-disabled: show offending definition locations in warning
Surface the files that defined `nixpkgs.config`/`nixpkgs.overlays` in the
`useGlobalPkgs` warning and assertion, so users can find what set them
without inspecting `options.*.files` in the repl.
2026-06-09 17:15:30 -05:00
Austin Horstman 408a6918a0 docs: advise contributors to generate module news entries
The news section told module contributors they did not need to add a
news entry because the merger would create one, contradicting the
earlier instruction to use the create-news-entry generator. Point
contributors at the generator so new modules ship with their own entry.
2026-06-09 16:40:46 -05:00
0xda157andAustin Horstman 8ff67d938c i18n/inputMethod: remove da157 as maintainer 2026-06-09 16:28:30 -05:00
Austin Horstman c58ead12ef voxtype: assert full service file in test
Replace the piecemeal assertFileRegex checks with a single
assertFileContent against the normalized unit, so the test pins the
entire generated service file rather than a handful of lines.
2026-06-09 07:49:19 -05:00
Austin Horstman 449012836e voxtype: restart service when configuration changes
Add the rendered config path to X-Restart-Triggers so the service restarts
whenever the configuration changes.
2026-06-09 07:49:19 -05:00
Austin Horstman df39142474 nushell: remove redundant abbreviations option
Nushell abbreviations are regular config settings, so users can already express them through programs.nushell.settings.abbreviations. Keep test coverage on that native path instead of maintaining a second option.
2026-06-08 13:35:42 -05:00
Austin Horstman e9cbe69850 sshAuthSock: avoid cycles for socket providers
PR #9420 added set-SSH_AUTH_SOCK.service and ordered it before sshAuthSock.systemd.socketProviderUnit. When that provider is a socket unit, systemd default dependencies already order the socket before sockets.target while service defaults place set-SSH_AUTH_SOCK.service after basic.target. That creates a sockets.target -> basic.target -> set-SSH_AUTH_SOCK.service -> provider.socket -> sockets.target cycle.

For gpg-agent this makes systemd drop gpg-agent-ssh.socket, leaving SSH_AUTH_SOCK pointing at the expected S.gpg-agent.ssh path but with no socket listening there.

Only order and install the environment service against non-socket providers. Socket providers still get SSH_AUTH_SOCK imported through default.target.

Fixes #9432.
2026-06-08 12:04:33 -05:00
Perchun PakandAustin Horstman 301871cd96 zellij: add support for plugins 2026-06-08 11:16:17 -05:00
Perchun PakandAustin Horstman ec56189b4f zellij: add myself to maintainers 2026-06-08 11:16:17 -05:00
azahiandAustin Horstman 78da10c4af zed-editor: wrap meta.mainProgram 2026-06-08 10:56:10 -05:00
home-manager-ci[bot]andAustin Horstman 5c34a3c759 maintainers: update all-maintainers.nix
Automated update of the master maintainers list combining:
- Home Manager specific maintainers from modules/lib/maintainers.nix
- Nixpkgs maintainers referenced in Home Manager modules

**Added:** 2 maintainers
**Removed:** 0 maintainers
**Total:** 298 → 300 maintainers

** Added:** kayskayskays, semi710

Generated by: lib/python/generate-all-maintainers.py
2026-06-08 09:37:01 -05:00
Austin Horstman 4fe95527cb pi-coding-agent: scrub package in Darwin tests 2026-06-07 21:22:10 -05:00
Austin Horstman 0c42c7a66f darkman: replace removed python2 in test 2026-06-07 21:22:10 -05:00
Austin Horstman aa14fc7b46 tests: update generated TOML expectations 2026-06-07 21:22:10 -05:00
dependabot[bot]andAustin Horstman e4b8f8c8bf flake: bump nixpkgs from 4100e83 to ffa10e2
Bumps [nixpkgs](https://github.com/NixOS/nixpkgs) from `4100e83` to `ffa10e2`.
- [Commits](https://github.com/NixOS/nixpkgs/compare/4100e830e085863741bc69b156ec4ccd53ab5be0...ffa10e26ae11d676b2db836259889f1f571cb14f)

---
updated-dependencies:
- dependency-name: nixpkgs
  dependency-version: ffa10e26ae11d676b2db836259889f1f571cb14f
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-07 21:22:10 -05:00
Dzming LiandAustin Horstman d4d5d9bb4a nushell: add abbreviations option
Add `programs.nushell.abbreviations`, mirroring `shellAliases`, to
declaratively configure fish-style abbreviations (nushell 0.113.0+).
The set is emitted as `$env.config.abbreviations = { ... }` after the
aliases so it survives a wholesale `$env.config = { ... }` written in
`extraConfig`.

Unlike aliases, abbreviations expand inline in the line editor, so the
expanded command is what ends up in history.
2026-06-07 21:06:57 -05:00
Ben BaldwinandAustin Horstman ddb7082625 lib: separate eval config file
Move the `homeManagerConfiguration` function into its own file to allow
importing without passing an unused `lib` argument.
2026-06-07 21:04:30 -05:00
Austin Horstman b2b7db486e antigravity-cli: robust rename support
Handle module system merge priority resolution issues with suboptions.
2026-06-05 12:15:34 -05:00
Austin Horstman e621f12c02 pi-coding-agent: use lib.hm.strings.isPathLike 2026-06-05 11:39:28 -05:00
Austin Horstman c321a1c8a7 opencode: use lib.hm.strings.isPathLike 2026-06-05 11:39:28 -05:00
Austin Horstman 6d4fb02e34 github-copilot-cli: use lib.hm.strings.isPathLike 2026-06-05 11:39:28 -05:00
Austin Horstman 4b9a995519 codex: use lib.hm.strings.isPathLike 2026-06-05 11:39:28 -05:00
Austin Horstman 09ff165ba6 claude-code: use lib.hm.strings.isPathLike 2026-06-05 11:39:28 -05:00
Austin Horstman 3a647b158e antigravity-cli: use lib.hm.strings.isPathLike 2026-06-05 11:39:28 -05:00
Austin Horstman d33c97ec1b lib/strings: add isPathLike helper 2026-06-05 11:39:28 -05:00
Austin Horstman eaca478c95 antigravity-cli: add migration news 2026-06-05 11:39:28 -05:00
Austin Horstman ff77a72fe5 antigravity-cli: rename gemini-cli module 2026-06-05 11:39:28 -05:00
Austin Horstman 0450574818 flake: gate development outputs to supported systems
Keep the home-manager package exposed for all nixpkgs flakeExposed systems, but only expose formatter, devShell, docs, tests, CI helpers, and legacy test packages on supported Home Manager systems.

This prevents nix flake check --all-systems from forcing unavailable formatter toolchains such as GHC on riscv64-linux and x86_64-freebsd.

Fixes #9389.
2026-06-05 11:33:38 -05:00
Nikhil SinghandAustin Horstman 37e8eef933 pi-coding-agent: add module 2026-06-05 11:13:52 -05:00
Nikhil SinghandAustin Horstman 5fadbec07a maintainers: add semi710 2026-06-05 11:13:52 -05:00
Benedikt RipsandAustin Horstman 447fd9ff62 sshAuthSock: add news entry 2026-06-04 12:20:50 -05:00
Benedikt RipsandAustin Horstman f1d5aa6f69 sshAuthSock: set in systemd
`SSH_AUTH_SOCK` is exported in shells only, which systemd does not
inherit from. With this commit, it is also set in systemd such that
systemd-managed applications can access the SSH agent by declaring
dependencies onto 'sshAuthSock.systemd.socketProviderUnit'.

Closes #7971.
2026-06-04 12:20:50 -05:00
Benedikt RipsandAustin Horstman f4534a4f3c sshAuthSock: add option for initialization in Zsh
Initialization in Zsh defaults to the Bash initialization code since Zsh
is mostly Bash-compatible. This commit adds a dedicated Zsh
initialization code option to make the defaulting behaviour transparent
while also enabling overrides.
2026-06-04 12:20:50 -05:00
Benedikt RipsandAustin Horstman efe95f113a sshAuthSock: use enable flag instead of nullable submodule 2026-06-04 12:20:50 -05:00
Austin Horstman dc641330e2 infat: use deprecation warning helper 2026-06-04 12:20:09 -05:00
Austin Horstman 18b4e1ea6f xdg-user-dirs: assert extraConfig key warning 2026-06-04 12:20:09 -05:00
Austin Horstman 6459fc4aee firefox: assert bookmarks migration warning 2026-06-04 12:20:09 -05:00
Austin Horstman 6a6941ad20 hyprpanel: assert theme name warning 2026-06-04 12:20:09 -05:00
Austin Horstman c81a3d2383 helix: assert languages migration warning 2026-06-04 12:20:09 -05:00
Austin Horstman 9a638c1442 swayidle: assert events migration warning 2026-06-04 12:20:09 -05:00
Austin Horstman ff53fe10e0 qt: assert kde6 migration warning 2026-06-04 12:20:09 -05:00
Austin Horstman 5ded124f91 lib: add deprecated option warning helpers 2026-06-04 12:20:09 -05:00
Austin Horstman 4c5c1e8ba1 alacritty: fix toml escape generation 2026-06-03 14:45:20 -05:00
KaysandAustin Horstman f384af1bec macos-terminal: add module 2026-06-02 08:56:09 -05:00
KaysandAustin Horstman a7c15f4f65 maintainers: add kayskayskays 2026-06-02 08:56:09 -05:00
kinnraiandAustin Horstman 70fc4b4b5e aerospace: add regression test for reload guard 2026-06-02 08:54:53 -05:00
kinnraiandAustin Horstman 58e05d16d7 aerospace: skip config reload when AeroSpace is not running
Avoid failing Home Manager activation when the generated AeroSpace
configuration changes while AeroSpace is not yet running.

The onChange hook can run before a running AeroSpace instance is
available. Probe the running instance with `aerospace list-modes --current`
before calling `reload-config`, and skip the reload if AeroSpace is not
yet running.
2026-06-02 08:54:53 -05:00
Léana 江andAustin Horstman f15c764b14 password-store: improve warning message 2026-06-02 08:23:08 -05:00
Benedikt RipsandAustin Horstman 68227a9363 fontconfig: add test for font discovery 2026-06-02 08:22:25 -05:00
Benedikt RipsandAustin Horstman af1588ad6f fontconfig: add test for default fonts 2026-06-02 08:22:25 -05:00
Benedikt RipsandAustin Horstman c0436bc028 fontconfig: enable config files by default
This commit makes the `fonts.fontconfig.configFile.<name>.enable` option
true by default which matches the behaviour of `home.file` and the like.
2026-06-02 08:22:25 -05:00
Austin Horstman 07c723c3fe zsh: fix oh-my-zsh custom path expansion
Preserve shell-variable paths for programs.zsh.oh-my-zsh.custom while
still escaping values for a double-quoted shell assignment.

This restores runtime expansion for values like $HOME/extra/zsh, which
otherwise became a literal path after the previous escapeShellArg
change.
2026-06-02 08:00:49 -05:00
Austin Horstman a7a4158831 infat: use list for autoActivate extraArgs 2026-06-01 22:28:08 -05:00
Kevin StoneandAustin Horstman 274299642b vscode: make keybindings submodule freeform
vscode supports overrides per platform key combinations at `mac`, `win` and `linux`

Enables `freeformType = jsonFormat` in the keybindings submodule. This enables support for per-platform bindings and any other arbitrary keybinding attributes.

Code pointer: https://github.com/microsoft/vscode/blob/83b7ce68a7c2496ad9a40ba370656050a9096491/src/vs/workbench/services/keybinding/browser/keybindingService.ts#L105-L120
2026-06-01 21:40:49 -05:00
Austin Horstman 26e34a99e4 hyprland: reorganize config generation
Move local render helpers closer to their generated outputs and split xdg.configFile construction into named builders.

Keep luaFiles behavior in the preceding feature commit; this only reduces duplicated predicates and makes the module layout easier to scan.
2026-06-01 21:32:10 -05:00
Austin Horstman a6a13bb0a0 hyprland: add extraLuaFiles option
Allow Lua configs to be split across managed files under XDG_CONFIG_HOME/hypr.

Treat extraLuaFiles attribute names as Lua module names, so dotted names such as lib.helpers write lib/helpers.lua while autoloading with require("lib.helpers").

Add assertions for invalid configType usage, generated hyprland.lua collisions, and duplicate resolved Lua file targets.
2026-06-01 21:32:10 -05:00
Julien GautierandAustin Horstman 5148001968 nix-search-tv: add keybindings and actions
Use the same keybindings as nix-search-tv's author used here:
https://github.com/3timeslazy/nix-search-tv/blob/main/nixpkgs.sh#L26

Also, handle case where the package is null
2026-06-01 21:23:56 -05:00
Austin Horstman c7e4087b4d zsh: rename plugin completions to functions
The option appended directories to fpath, but fpath is zsh's lookup path for all autoloadable functions, not just completion functions.

Rename the option to functions with mkRenamedOptionModule so existing completions users are migrated through the standard Home Manager deprecation path.

Link: https://github.com/nix-community/home-manager/discussions/8701#discussioncomment-16966141
2026-06-01 21:22:13 -05:00
Austin Horstman c7c139f742 zsh: escape double-quoted values
The zsh value renderer emits strings in double quotes so session variables can still expand references such as $HOME. Embedded quotes, backslashes, and backticks were not escaped, which could corrupt generated zsh code or trigger command substitution.

Escape those double-quote-sensitive characters while preserving dollar expansion for existing session-variable semantics.
2026-06-01 21:22:13 -05:00
Austin Horstman 3e17edd5e6 zsh: escape named directory hashes
Named directory hash values are rendered into zsh code. Interpolating them inside double quotes is not enough for values containing quotes, glob syntax, or other shell metacharacters.

Render both the hash name and target with shell escaping so dirHashes entries are emitted as literal zsh arguments.
2026-06-01 21:22:13 -05:00
Austin Horstman d00b16c511 zsh: escape cdpath entries
Zsh cdpath is an array. Rendering entries by joining on spaces breaks paths containing spaces, glob characters, or other shell syntax.

Use the shared shell array formatter so each configured cdpath entry is emitted as a quoted array element.
2026-06-01 21:22:13 -05:00
Austin Horstman 112a3a3783 zsh: add common plugin function paths
Nixpkgs zsh plugin packages often install functions and completions below share/zsh/plugins/<name>, share/zsh/site-functions, or share/zsh/vendor-completions rather than at the package root.

Keep the existing root path and fpath entries, but add those standard subdirectories to fpath when present so plugin-provided functions and completions are discoverable without extra per-plugin configuration.

Link: https://github.com/nix-community/home-manager/discussions/8701#discussioncomment-16966141
2026-06-01 21:22:13 -05:00
Austin Horstman 04ec113f8b zsh: load session vars from zprofile for login shells
Zsh reads .zshenv before system login startup files. On NixOS and macOS those later files can replace or reorder PATH, so home.sessionPath entries loaded from .zshenv may disappear or move behind system paths.

Keep .zshenv coverage for non-login shells, but source Home Manager session variables from .zprofile for login shells so PATH-like values are applied after system login setup.

Fixes #2991
2026-06-01 21:22:13 -05:00
Austin Horstman d0af9b8bf3 claude-code: mark hook scripts executable
Inline hooks are scripts, so write them with the executable bit set. Add NMT assertions covering default and custom config directories.
2026-06-01 16:06:08 -05:00
Mirko LenzandAustin Horstman 5a608a621b infat: allow customization of autoActivate 2026-06-01 14:38:27 -05:00
TofaaandAustin Horstman 8962c2a0f4 Translate using Weblate (Azerbaijani)
Currently translated at 100.0% (39 of 39 strings)

Translate using Weblate (Azerbaijani)

Currently translated at 18.7% (3 of 16 strings)

Add translation using Weblate (Azerbaijani)

Add translation using Weblate (Azerbaijani)

Co-authored-by: Tofaa <tofig.adigozalov.180904@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/az/
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/az/
Translation: Home Manager/Home Manager CLI
Translation: Home Manager/Home Manager Modules
2026-06-01 14:17:00 -05:00
Thierry DelafontaineandAustin Horstman 3d64f2875e podman: fix container config mount on Darwin
On Darwin, `services.podman` mounts `~/.config/containers` into the Fedora CoreOS VM, but this did not work correctly for two reasons:

* `xdg.configFile` creates symlinks into `/nix/store`, which are broken inside the guest.
* The mount target `~/\.config/containers` is not canonical on Fedora CoreOS, so Podman rejects it.

To fix this, we now:

* materialize the generated Podman config files as real files with `runCommand`
* sync them into `~/.config/containers` during activation, between `linkGeneration` and `podmanMachines`
* use the canonical guest path `/var/home/<user>/.config/containers`

Because adding the config directory to the volume mounts overrides the defaults, we also restore the default Podman volumes as the defaults for the  `machines.<machine>.volumes` attribute while still allowing full overrides.

This change does not affect Linux: `xdg.configFile` still produces store symlinks there.

Closes #9327.
2026-06-01 14:13:42 -05:00
randomscientistandAustin Horstman c53d0150e9 prismlauncher: fix string escaping and hygiene 2026-06-01 14:08:48 -05:00
home-manager-ci[bot]andAustin Horstman 16110b65b2 maintainers: update all-maintainers.nix
Automated update of the master maintainers list combining:
- Home Manager specific maintainers from modules/lib/maintainers.nix
- Nixpkgs maintainers referenced in Home Manager modules

**Added:** 1 maintainers
**Removed:** 0 maintainers
**Total:** 297 → 298 maintainers

** Added:** marijanp

Generated by: lib/python/generate-all-maintainers.py
2026-06-01 14:08:07 -05:00
Austin Horstman 7d8127d308 flake: track nixpkgs-unstable 2026-05-29 19:01:27 -05:00
Austin Horstman da8f8fcd69 ci: update release branch 2026-05-29 19:01:27 -05:00
Austin Horstman 948753061a home-manager: prepare 26.11
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
2026-05-29 19:01:27 -05:00
Marijan PetričevićandMatthieu Coudron 61e2c96593 voxtype: initial module implementation 2026-05-28 13:54:55 +02:00
qb114514andRobert Helgesson d17712ec7a Translate using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (16 of 16 strings)

Translate using Weblate (Toki Pona)

Currently translated at 93.7% (15 of 16 strings)

Translate using Weblate (Toki Pona)

Currently translated at 94.8% (37 of 39 strings)

Translate using Weblate (Chinese (Simplified Han script))

Currently translated at 97.4% (38 of 39 strings)

Co-authored-by: qb114514 <GNUqb114514@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/tok/
Translate-URL: https://hosted.weblate.org/projects/home-manager/cli/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/tok/
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/zh_Hans/
Translation: Home Manager/Home Manager CLI
Translation: Home Manager/Home Manager Modules
2026-05-28 12:23:23 +02:00
Tomas RiveraandRobert Helgesson 874ae4cccf Translate using Weblate (Spanish)
Currently translated at 100.0% (16 of 16 strings)

Co-authored-by: Tomas Rivera <tomas.riveral@icloud.com>
Translate-URL: https://hosted.weblate.org/projects/home-manager/modules/es/
Translation: Home Manager/Home Manager Modules
2026-05-28 12:23:23 +02:00
671 changed files with 17416 additions and 3810 deletions
+2 -2
View File
@@ -24,8 +24,8 @@ Also make sure to read the guidelines found at
- [ ] Code formatted with `nix fmt` or
`nix-shell -A dev --run treefmt`.
- [ ] Code tested through `nix run .#tests -- test-all` or
`nix-shell --pure tests -A run.all`.
- [ ] Code tested through `nix build .#test-all`
or a targeted `nix run .#tests -- <pattern>`.
- [ ] Test cases updated/added. See [example](https://github.com/nix-community/home-manager/commit/f3fbb50b68df20da47f9b0def5607857fcc0d021#diff-b61a6d542f9036550ba9c401c80f00ef).
+2 -2
View File
@@ -9,7 +9,7 @@ updates:
prefix: "ci:"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "release-25.11"
target-branch: "release-26.05"
schedule:
interval: "weekly"
commit-message:
@@ -23,7 +23,7 @@ updates:
prefix: "flake:"
- package-ecosystem: "nix"
directory: "/"
target-branch: "release-25.11"
target-branch: "release-26.05"
schedule:
interval: "weekly"
commit-message:
+2 -2
View File
@@ -7,10 +7,10 @@
- modules/programs/aiac.nix
- modules/programs/aichat.nix
- modules/programs/aider-chat.nix
- modules/programs/claude-code.nix
- modules/programs/claude-code/**/*
- modules/programs/codex.nix
- modules/programs/fabric-ai.nix
- modules/programs/gemini-cli.nix
- modules/programs/antigravity-cli.nix
- modules/programs/mcp.nix
- modules/programs/mistral-vibe.nix
- modules/programs/mods.nix
+2 -2
View File
@@ -31,10 +31,10 @@ jobs:
with:
app-id: ${{ vars.CI_APP_ID }}
private-key: ${{ secrets.CI_APP_PRIVATE_KEY }}
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ github.event.pull_request.base.sha }}
- name: Create backport PRs
id: backport
uses: korthout/backport-action@v4
+2 -2
View File
@@ -12,10 +12,10 @@ jobs:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
nix_path: nixpkgs=channel:nixpkgs-unstable
- uses: cachix/cachix-action@v17
with:
name: nix-community
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
app-id: ${{ vars.CI_APP_ID }}
private-key: ${{ secrets.CI_APP_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.base.sha }}
- name: Get Nixpkgs revision from flake.lock
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
parse: ${{ steps.changes.outputs.parse }}
tests: ${{ steps.changes.outputs.tests }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
id: changes
with:
@@ -54,7 +54,7 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Get Nixpkgs revision from flake.lock
id: get-nixpkgs
run: |
+1 -1
View File
@@ -48,7 +48,7 @@ jobs:
echo "email=$id+$name@users.noreply.github.com"
} >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
token: ${{ steps.app-token.outputs.token || github.token }}
- name: Get Nixpkgs revision from flake.lock
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
if: github.repository_owner == 'nix-community'
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Get Nixpkgs revision from flake.lock
id: get-nixpkgs
run: |
+8
View File
@@ -0,0 +1,8 @@
# Contributing
Home Manager contribution guidelines live in the manual:
- [`docs/manual/contributing.md`](docs/manual/contributing.md)
- <https://nix-community.github.io/home-manager/#ch-contributing>
Read those docs before changing modules, tests, news entries, or release notes.
+5 -1
View File
@@ -11,9 +11,13 @@ list *pattern:
test *pattern:
nix run .#tests -- {{pattern}}
# List integration tests
list_integration_tests:
nix run .#tests -- -t -l
# Run integration tests
integration_tests:
nix run .#tests -- -t -l
nix run .#tests -- -t integration-test-
# Create a news entry
create-news-entry:
+2 -2
View File
@@ -125,7 +125,7 @@ Reference commits:
- Update the nixpkgs input to track the corresponding stable branch
- Example: For `release-25.11`, change from:
```nix
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
```
to:
```nix
@@ -135,7 +135,7 @@ Reference commits:
- Commit the flake.nix and flake.lock changes
**Note**: The release branch should track the stable NixOS release channel
(e.g., `nixos-25.11`), while master continues to track `nixos-unstable`.
(e.g., `nixos-25.11`), while master continues to track `nixpkgs-unstable`.
#### Step 3.5: On master - Update CI for the New Release Branch
+1 -1
View File
@@ -1,5 +1,5 @@
.PHONY: all all-tests test test-install format
NIXPKGS_REV := nixos-unstable
NIXPKGS_REV := nixpkgs-unstable
NIX_PATH := nixpkgs=https://github.com/NixOS/nixpkgs/archive/${NIXPKGS_REV}.tar.gz
all: all-tests test-install
+4 -3
View File
@@ -25,8 +25,9 @@ If you would like to contribute to Home Manager, then please have a look at
Releases
--------
Home Manager is developed against `nixos-unstable` branch, which often causes
it to contain tweaks for changes/packages not yet released in stable [NixOS][].
Home Manager is developed against the `nixpkgs-unstable` branch, which often
causes it to contain tweaks for changes/packages not yet released in stable
[NixOS][].
To avoid breaking users' configurations, Home Manager is released in branches
corresponding to NixOS releases (e.g. `release-26.05`). These branches get
fixes, but usually not new modules. If you need a module to be backported, then
@@ -49,7 +50,7 @@ dconf store and cannot tell whether a configuration that is about to be
overwritten was from a previous Home Manager generation or from manual
configuration.
Home Manager targets [NixOS][] unstable and NixOS version 26.05 (the current
Home Manager targets `nixpkgs-unstable` and NixOS version 26.05 (the current
stable version), it may or may not work on other Linux distributions and NixOS
versions.
+103 -31
View File
@@ -26,7 +26,7 @@
github = "0xdsqr";
githubId = 99584622;
name = "Dave Dennis";
source = "nixpkgs";
source = "home-manager";
};
"3ulalia" = {
email = "3ulalia@proton.me";
@@ -39,8 +39,14 @@
email = "git.t@betsumei.com";
github = "74k1";
githubId = 49000471;
keys = [
{
fingerprint = "46F3 422F 63A3 1369 7EAB 83D5 1CF1 55F7 6F21 3503";
}
];
matrix = "@74k1:matrix.org";
name = "Tim";
source = "home-manager";
source = "nixpkgs";
};
"9p4" = {
email = "vcs@ersei.net";
@@ -75,14 +81,14 @@
github = "AndreasMager";
githubId = 5646732;
name = "Andreas Mager";
source = "nixpkgs";
source = "home-manager";
};
Austreelis = {
email = "github@accounts.austreelis.net";
github = "Austreelis";
githubId = 56743515;
name = "Morgane Austreelis";
source = "nixpkgs";
source = "home-manager";
};
CarlosLoboxyz = {
email = "86011416+CarlosLoboxyz@users.noreply.github.com";
@@ -119,6 +125,13 @@
name = "Denis Kaynar";
source = "home-manager";
};
ErinaYip = {
email = "erinayip@outlook.com";
github = "ErinaYip";
githubId = 227132255;
name = "erina";
source = "home-manager";
};
Eveeifyeve = {
email = "eveeg1971@gmail.com";
github = "Eveeifyeve";
@@ -132,7 +145,7 @@
github = "Fendse";
githubId = 46252070;
name = "Sara Johnsson";
source = "nixpkgs";
source = "home-manager";
};
Flameopathic = {
email = "flameopathic@gmail.com";
@@ -195,7 +208,7 @@
githubId = 80165193;
matrix = "@janik0:matrix.org";
name = "Janik";
source = "nixpkgs";
source = "home-manager";
};
JasmineLowen = {
email = "robwalter96@gmail.com";
@@ -221,7 +234,7 @@
github = "Joker9944";
githubId = 9194199;
name = "Felix von Arx";
source = "nixpkgs";
source = "home-manager";
};
JustinLovinger = {
email = "git@justinlovinger.com";
@@ -298,7 +311,7 @@
github = "MForster";
githubId = 4067975;
name = "Michael Forster";
source = "nixpkgs";
source = "home-manager";
};
Misterio77 = {
email = "eu@misterio.me";
@@ -377,12 +390,25 @@
name = "Philipp Mildenberger";
source = "nixpkgs";
};
ReStranger = {
github = "ReStranger";
githubId = 69393944;
name = "ReStranger";
source = "nixpkgs";
};
ReeSilva = {
email = "reesilva@proton.me";
github = "ReeSilva";
githubId = 3605032;
name = "Renato da Silva";
source = "home-manager";
};
Rosuavio = {
email = "RosarioPulella@gmail.com";
github = "Rosuavio";
githubId = 7164552;
name = "Rosario Pulella";
source = "nixpkgs";
source = "home-manager";
};
S0AndS0 = {
email = "S0AndS0@digital-mercenaries.com";
@@ -522,14 +548,6 @@
name = "David Chocholatý";
source = "nixpkgs";
};
adisbladis = {
email = "adisbladis@gmail.com";
github = "adisbladis";
githubId = 63286;
matrix = "@adis:blad.is";
name = "Adam Hose";
source = "nixpkgs";
};
afresquet = {
email = "alvarofresquet@gmail.com";
github = "afresquet";
@@ -579,6 +597,13 @@
name = "Alin";
source = "nixpkgs";
};
amadejkastelic = {
email = "amadejkastelic7@gmail.com";
github = "amadejkastelic";
githubId = 26391003;
name = "Amadej Kastelic";
source = "nixpkgs";
};
ambroisie = {
email = "bruno.nixpkgs@belanyi.fr";
github = "ambroisie";
@@ -646,7 +671,7 @@
github = "b1kku";
githubId = 77858854;
name = "Bikku";
source = "nixpkgs";
source = "home-manager";
};
bamhm182 = {
email = "bamhm182@gmail.com";
@@ -686,6 +711,13 @@
name = "Filippo Berto";
source = "nixpkgs";
};
bittner = {
email = "peter@painless.software";
github = "bittner";
githubId = 665072;
name = "Peter Bittner";
source = "home-manager";
};
bjpbakker = {
email = "bart@thesoftwarecraft.com";
github = "bjpbakker";
@@ -849,7 +881,7 @@
github = "ckgxrg-salt";
githubId = 165614491;
name = "ckgxrg";
source = "nixpkgs";
source = "home-manager";
};
cmacrae = {
email = "hi@cmacr.ae";
@@ -878,6 +910,13 @@
name = "Phillip Cloud";
source = "nixpkgs";
};
csanthiago = {
email = "git@csanthiago.dev";
github = "csanthiago";
githubId = 8346803;
name = "csanthiago";
source = "home-manager";
};
cwyc = {
email = "hello@cwyc.page";
github = "cwyc";
@@ -958,13 +997,6 @@
name = "Robin Stumm";
source = "nixpkgs";
};
diniamo = {
email = "diniamo53@gmail.com";
github = "diniamo";
githubId = 55629891;
name = "diniamo";
source = "nixpkgs";
};
donovanglover = {
github = "donovanglover";
githubId = 2374245;
@@ -1439,7 +1471,7 @@
github = "kaleocheng";
githubId = 7939352;
name = "Kaleo Cheng";
source = "nixpkgs";
source = "home-manager";
};
kalhauge = {
email = "kalhauge@users.noreply.github.com";
@@ -1467,6 +1499,13 @@
name = "Nikolaos Karaolidis";
source = "nixpkgs";
};
kayskayskays = {
email = "115312476+kayskayskays@users.noreply.github.com";
github = "kayskayskays";
githubId = 115312476;
name = "Kays";
source = "home-manager";
};
khaneliman = {
email = "khaneliman12@gmail.com";
github = "khaneliman";
@@ -1618,6 +1657,13 @@
name = "Manaiki Laut";
source = "nixpkgs";
};
marijanp = {
email = "marijan.petricevic94@gmail.com";
github = "marijanp";
githubId = 13599169;
name = "Marijan Petričević";
source = "nixpkgs";
};
mateusauler = {
email = "mateus@auler.dev";
github = "mateusauler";
@@ -1719,7 +1765,7 @@
github = "miku4k";
githubId = 89653242;
name = "Miku B";
source = "nixpkgs";
source = "home-manager";
};
minijackson = {
email = "minijackson@riseup.net";
@@ -1818,7 +1864,7 @@
github = "natecox";
githubId = 2782695;
name = "Nate Cox";
source = "nixpkgs";
source = "home-manager";
};
nbp = {
email = "nixos@nbp.name";
@@ -2039,6 +2085,18 @@
name = "Rasmus Kirk";
source = "home-manager";
};
ratakor = {
email = "ratakor@disroot.org";
github = "ratakor";
githubId = 45130910;
keys = [
{
fingerprint = "B60F 8F80 D6CD C5D2 58CF 14C3 241B 1CBE 567B 287E";
}
];
name = "Ratakor";
source = "nixpkgs";
};
ravenjoad = {
email = "raven@hallsby.com";
github = "ravenjoad";
@@ -2150,7 +2208,7 @@
github = "s0racat";
githubId = 125882337;
name = "soracat";
source = "nixpkgs";
source = "home-manager";
};
sableseyler = {
email = "sable@seyleri.us";
@@ -2171,6 +2229,13 @@
name = "Seong Yong-ju";
source = "nixpkgs";
};
semi710 = {
email = "nik.singh710@gmail.com";
github = "semi710";
githubId = 60490474;
name = "Nikhil Singh";
source = "nixpkgs";
};
shikanime = {
email = "william.phetsinorath@shikanime.studio";
github = "shikanime";
@@ -2250,6 +2315,13 @@
name = "Sumner Evans";
source = "nixpkgs";
};
superflash41 = {
email = "saymon.nicho@pucp.edu.pe";
github = "superflash41";
githubId = 102434258;
name = "Saymon Nicho";
source = "home-manager";
};
surfaceflinger = {
email = "nat@nekopon.pl";
github = "surfaceflinger";
@@ -2425,7 +2497,7 @@
source = "nixpkgs";
};
yaaaarn = {
email = "30006414+yaaaarn@users.noreply.github.com";
email = "nix@yarncat.moe";
github = "yaaaarn";
githubId = 30006414;
name = "yarncat";
+9 -3
View File
@@ -253,9 +253,15 @@ let
# Generate the HTML manual pages
home-manager-manual = pkgs.callPackage ./home-manager-manual.nix {
home-manager-options = {
home-manager = hmOptionsDocs.optionsJSON;
nixos = nixosOptionsDocs.optionsJSON;
nix-darwin = nixDarwinOptionsDocs.optionsJSON;
home-manager = {
json = hmOptionsDocs.optionsJSON;
};
nixos = {
json = nixosOptionsDocs.optionsJSON;
};
nix-darwin = {
json = nixDarwinOptionsDocs.optionsJSON;
};
};
inherit revision;
};
+4 -4
View File
@@ -2,16 +2,16 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1773821835,
"narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=",
"lastModified": 1779877693,
"narHash": "sha256-NOF9NAREhxr50bbBfVcVOq+ArCMSoe8dP79Pk2uyARk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c06b4ae3d6599a672a6210b7021d699c351eebda",
"rev": "4100e830e085863741bc69b156ec4ccd53ab5be0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
+1 -1
View File
@@ -2,7 +2,7 @@
description = "Support developing Home Manager documentation";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
scss-reset = {
url = "github:andreymatin/scss-reset/1.4.2";
flake = false;
+53 -39
View File
@@ -1,63 +1,75 @@
{
stdenv,
lib,
documentation-highlighter,
callPackage,
mdbook,
python3,
revision,
home-manager-options,
nixos-render-docs,
}:
let
outputPath = "share/doc/home-manager";
mdbookOptions = callPackage ./mdbook/options.nix {
manpageUrls = ./manual/manpage-urls.json;
inherit revision;
optionDocs = {
home-manager = {
title = "Home Manager Configuration Options";
path = "home-manager";
prefix = "opt-";
json = "${home-manager-options.home-manager.json}";
};
nixos = {
title = "NixOS Configuration Options";
path = "nixos";
prefix = "nixos-opt-";
json = "${home-manager-options.nixos.json}";
};
nix-darwin = {
title = "nix-darwin Configuration Options";
path = "nix-darwin";
prefix = "nix-darwin-opt-";
json = "${home-manager-options.nix-darwin.json}";
};
};
};
in
stdenv.mkDerivation {
name = "home-manager-manual";
nativeBuildInputs = [ nixos-render-docs ];
nativeBuildInputs = [
mdbook
python3
];
src = ./manual;
src = ./.;
buildPhase = ''
mkdir -p out/{highlightjs,media}
runHook preBuild
cp -t out/highlightjs \
${documentation-highlighter}/highlight.pack.js \
${documentation-highlighter}/LICENSE \
${documentation-highlighter}/mono-blue.css \
${documentation-highlighter}/loader.js
mkdir -p source
python3 ${./mdbook/convert-markup.py} "$src/manual" source
python3 ${./mdbook/convert-markup.py} \
--base-depth 1 \
"$src/release-notes" \
source/release-notes
substituteInPlace ./options.md \
--subst-var-by \
OPTIONS_JSON \
${home-manager-options.home-manager}/share/doc/nixos/options.json
cp -r ${mdbookOptions}/options source/options
substituteInPlace ./nixos-options.md \
--subst-var-by \
OPTIONS_JSON \
${home-manager-options.nixos}/share/doc/nixos/options.json
python3 ${./mdbook/substitute-summary.py} \
source/SUMMARY.md \
${mdbookOptions}/summary/home-manager.md \
${mdbookOptions}/summary/nixos.md \
${mdbookOptions}/summary/nix-darwin.md
substituteInPlace ./nix-darwin-options.md \
--subst-var-by \
OPTIONS_JSON \
${home-manager-options.nix-darwin}/share/doc/nixos/options.json
mdbook build source --dest-dir book
cp ${./options.html} out/options.html
mkdir -p out
cp -r book/* out/
cp ${./static/style.css} out/style.css
cp ${./static/anchor-links.js} out/anchor-links.js
python3 ${./mdbook/legacy-redirects.py} source out
cp -r ${./release-notes} release-notes
nixos-render-docs manual html \
--manpage-urls ./manpage-urls.json \
--revision ${lib.trivial.revisionWithDefault revision} \
--stylesheet style.css \
--script highlightjs/highlight.pack.js \
--script highlightjs/loader.js \
--script anchor-links.js \
--toc-depth 1 \
--section-toc-depth 1 \
manual.md \
out/index.xhtml
runHook postBuild
'';
installPhase = ''
@@ -69,7 +81,9 @@ stdenv.mkDerivation {
echo "doc manual $dest index.html" >> $out/nix-support/hydra-build-products
'';
passthru = { inherit home-manager-options; };
passthru = {
inherit home-manager-options mdbookOptions;
};
meta = {
maintainers = [ lib.maintainers.considerate ];
+7 -2
View File
@@ -25,9 +25,14 @@ let
if [[ ! -v BROWSER || -z $BROWSER ]]; then
echo "$0: unable to start a web browser; please set \$BROWSER"
exit 1
else
exec "$BROWSER" "${html}/share/doc/${pathName}/index.xhtml"
fi
manualPath="${html}/share/doc/${pathName}/index.html"
if [[ ! -e $manualPath ]]; then
manualPath="${html}/share/doc/${pathName}/index.xhtml"
fi
exec "$BROWSER" "$manualPath"
'';
desktopItem = makeDesktopItem {
+64
View File
@@ -0,0 +1,64 @@
# Summary
- [Preface](preface.md)
- [Introduction to Home Manager](introduction.md)
- [Installing Home Manager](installation.md)
- [Standalone installation](installation/standalone.md)
- [NixOS module](installation/nixos.md)
- [nix-darwin module](installation/nix-darwin.md)
- [Using Home Manager](usage.md)
- [Configuration Example](usage/configuration.md)
- [Rollbacks](usage/rollbacks.md)
- [Keeping your ~ safe from harm](usage/dotfiles.md)
- [Graphical services](usage/graphical.md)
- [GPU on non-NixOS systems](usage/gpu-non-nixos.md)
- [Modular Services](usage/modular-services.md)
- [Updating](usage/updating.md)
- [Upgrading to a new Home Manager release](usage/upgrading.md)
- [Nix Flakes](nix-flakes.md)
- [Prerequisites](nix-flakes/prerequisites.md)
- [Standalone setup](nix-flakes/standalone.md)
- [NixOS module](nix-flakes/nixos.md)
- [nix-darwin module](nix-flakes/nix-darwin.md)
- [flake-parts module](nix-flakes/flake-parts.md)
- [Writing Home Manager Modules](writing-modules.md)
- [Option Types](writing-modules/types.md)
- [Contributing](contributing.md)
- [Getting started](contributing/getting-started.md)
- [Guidelines](contributing/guidelines.md)
- [Release Notes](contributing/release-notes.md)
- [News](contributing/news.md)
- [Tests](contributing/tests.md)
- [Home Manager Internals](internals.md)
- [Activation](internals/activation.md)
- [Third-Party Tools and Extensions](3rd-party.md)
- [Module Collections](3rd-party/collections.md)
- [Frequently Asked Questions (FAQ)](faq.md)
- [Why is there a collision error when switching generation?](faq/collision.md)
- [Why are the session variables not set?](faq/session-variables.md)
- [How to set up a configuration for multiple users/machines?](faq/multiple-users-machines.md)
- [Why do I get an error message about `ca.desrt.dconf` or `dconf.service`?](faq/ca-desrt-dconf.md)
- [How do I install packages from Nixpkgs unstable?](faq/unstable.md)
- [How do I change the package used by a module?](faq/change-package-module.md)
- [Options](options.md)
@HOME_MANAGER_OPTIONS@
@NIXOS_OPTIONS@
@NIX_DARWIN_OPTIONS@
- [Release Notes](release-notes/release-notes.md)
- [Release 26.11](release-notes/rl-2611.md)
- [Release 26.05](release-notes/rl-2605.md)
- [Release 25.11](release-notes/rl-2511.md)
- [Release 25.05](release-notes/rl-2505.md)
- [Release 24.11](release-notes/rl-2411.md)
- [Release 24.05](release-notes/rl-2405.md)
- [Release 23.11](release-notes/rl-2311.md)
- [Release 23.05](release-notes/rl-2305.md)
- [Release 22.11](release-notes/rl-2211.md)
- [Release 22.05](release-notes/rl-2205.md)
- [Release 21.11](release-notes/rl-2111.md)
- [Release 21.05](release-notes/rl-2105.md)
- [Release 20.09](release-notes/rl-2009.md)
- [Release 20.03](release-notes/rl-2003.md)
- [Release 19.09](release-notes/rl-1909.md)
- [Release 19.03](release-notes/rl-1903.md)
- [Release 18.09](release-notes/rl-1809.md)
+11
View File
@@ -0,0 +1,11 @@
[book]
title = "Home Manager Manual"
language = "en"
src = "."
[output.html]
git-repository-url = "https://github.com/nix-community/home-manager"
[output.html.fold]
enable = true
level = 0
+3 -2
View File
@@ -3,8 +3,8 @@
Contributions to Home Manager are very welcome. To make the process as
smooth as possible for both you and the Home Manager maintainers we
provide some guidelines that we ask you to follow. See [Getting
started](#sec-contrib-getting-started) for information on how to set up
a suitable development environment and [Guidelines](#sec-guidelines) for
started](contributing/getting-started.md#sec-contrib-getting-started) for information on how to set up
a suitable development environment and [Guidelines](contributing/guidelines.md#sec-guidelines) for
the actual guidelines.
This text is mainly directed at those who would like to make code
@@ -22,6 +22,7 @@ configuration.
```{=include=} sections
contributing/getting-started.md
contributing/guidelines.md
contributing/release-notes.md
contributing/news.md
contributing/tests.md
```
+2 -2
View File
@@ -8,7 +8,7 @@ how to do this.
Once you have a fork of Home Manager you should create a branch starting
at the most recent `master` branch. Give your branch a reasonably
descriptive name. Commit your changes to this branch and when you are
happy with the result and it fulfills [Guidelines](#sec-guidelines) then
happy with the result and it fulfills [Guidelines](guidelines.md#sec-guidelines) then
push the branch to GitHub and [create a pull
request](https://help.github.com/articles/creating-a-pull-request/).
@@ -21,7 +21,7 @@ the `home-manager` command use it by either
$ home-manager -I home-manager=$HOME/devel/home-manager
```
or, if using [flakes](#sec-flakes-standalone):
or, if using [flakes](../nix-flakes/standalone.md#sec-flakes-standalone):
``` shell
$ home-manager --override-input home-manager ~/devel/home-manager
+51 -8
View File
@@ -58,11 +58,38 @@ YAML, INI, TOML, or even a plain list of key/value pairs then consider
using a `settings` option as described in [Nix RFC
42](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md).
These guidelines describe the minimum option design requirements. Before
submitting a module, compare it against the upstream documentation or
source code and verify that the generated files, services, environment
variables, and command line arguments all match the upstream behavior
you intend to expose.
If a module installs a package, try to make the package option nullable,
for example
``` nix
package = lib.mkPackageOption pkgs "xdg-terminal-exec" { nullable = true; };
```
This lets users keep installation outside Home Manager, for example via
`apt` or because the program is built into macOS, while still using the
module for configuration. Keeping the package non-nullable is fine when
the enabled behavior structurally requires the executable or when
package-less support would make the module significantly more complex.
Avoid generating files for empty settings, null packages, or optional
features that are not configured.
If upstream does not use XDG paths by default but supports changing the
configuration location with an environment variable, for example
`FOO_HOME`, expose a `configDir` option and use it to respect
`home.preferXdgDirectories`.
## Add relevant tests {#sec-guidelines-add-tests}
If at all possible, make sure to add new tests and expand existing tests
so that your change will keep working in the future. See
[Tests](#sec-tests) for more information about the Home Manager test
[Tests](tests.md#sec-tests) for more information about the Home Manager test
suite.
All contributed code *must* pass the test suite.
@@ -81,7 +108,7 @@ Manager Git repository:
``` shell
$ nix-build -A docs.html
$ xdg-open ./result/share/doc/home-manager/index.xhtml
$ xdg-open ./result/share/doc/home-manager/index.html
```
When you have made changes to a module, it is a good idea to check that
@@ -89,7 +116,7 @@ the man page version of the module options looks good:
``` shell
$ nix-build -A docs.manPages
$ man ./result/share/man/man5/home-configuration.nix.5.gz
$ man ./result/share/man/man5/home-configuration.nix.5
```
## Module Auto-importing {#sec-module-auto-importing}
@@ -155,10 +182,17 @@ consistent commit message format as described in
If your contribution includes a change that should be communicated to
users of Home Manager then you can add a news entry. The entry must be
formatted as described in [News](#sec-news).
formatted as described in [News](news.md#sec-news).
When new modules are added a news entry should be included.
News entries and release notes serve different purposes. Release notes
should be updated separately when a change affects users migrating
between stable releases, such as state version default changes, required
migration steps, or broad behavior changes. See
[Release Notes](release-notes.md#sec-contributing-release-notes) and [News](news.md#sec-news)
for more details.
## Use conditional modules and news {#sec-guidelines-conditional-modules}
Home Manager includes a number of modules that are only usable on some
@@ -166,12 +200,16 @@ of the supported platforms. The most common example of platform specific
modules are those that define systemd user services, which only works on
Linux systems.
If you add a module that is platform specific then make sure to include
a condition in the `loadModule` function call. This will make the module
accessible only on systems where the condition evaluates to `true`.
If you add a module that is platform specific then make sure the module
guards platform-specific configuration with an appropriate condition, for
example `pkgs.stdenv.hostPlatform.isLinux` or
`pkgs.stdenv.hostPlatform.isDarwin`. Modules in `modules/programs/` and
`modules/services/` are auto-imported, so the platform condition should live
in the module behavior and in any platform-specific tests rather than in a
separate module discovery call.
Similarly, if you are adding a news entry then it should be shown only
to users that may find it relevant, see [News](#sec-news) for a
to users that may find it relevant, see [News](news.md#sec-news) for a
description of conditional news.
## Mind the license {#sec-guidelines-licensing}
@@ -194,6 +232,11 @@ that is, each commit should make sense in isolation. In particular, you
will be asked to amend any commit that introduces syntax errors or
similar problems even if they are fixed in a later commit.
Keep commits atomic and separated by concern. For example, a new
maintainer entry should be a separate first commit, and a shared module
should be committed separately from integrations in existing modules.
Pull requests should not include merge commits or fixup commits.
The commit messages should follow the [seven
rules](https://chris.beams.io/posts/git-commit/#seven-rules), except for
\"Capitalize the subject line\". We also ask you to include the affected
+23 -6
View File
@@ -6,8 +6,17 @@ associated news entry. In general, a news entry should only be added for
truly noteworthy news. For example, a bug fix or new option does
generally not need a news entry.
Release notes and news entries serve different purposes. A news entry is
shown during Home Manager activation and is useful for day-to-day
communication about noteworthy changes, such as a new module, a new
feature, or a specific deprecation. Release notes are read from the
website documentation and should summarize what users need to know before
or during a stable-release upgrade. See
[Release Notes](release-notes.md#sec-contributing-release-notes) for guidance on changes
that affect stable-release upgrades.
If you do have a change worthy of a news entry then please add one in
[`news.nix`](https://github.com/nix-community/home-manager/blob/master/modules/misc/news.nix)
[`news`](https://github.com/nix-community/home-manager/blob/master/modules/misc/news)
but you should follow some basic guidelines:
- Use the included news entry generator to create a news entry file:
@@ -25,9 +34,12 @@ but you should follow some basic guidelines:
This will create a new file inside the `modules/misc/news` directory
with some placeholder information that you can edit.
- The entry condition should be as specific as possible. For example,
if you are changing or deprecating a specific option then you could
restrict the news to those users who actually use this option.
- The entry condition should be as specific as possible for changes
affecting existing functionality. For example, if you are changing
or deprecating a specific option then you could restrict the news to
those users who actually use this option. Prefer a targeted
condition over skipping useful news only to avoid notifying
unaffected users.
- Wrap the news message so that it will fit in the typical terminal,
that is, at most 80 characters wide. Ideally a bit less.
@@ -53,6 +65,10 @@ but you should follow some basic guidelines:
A new module is available: 'services.foo'.
Since this news announces newly available functionality, its
condition should not require `config.services.foo.enable`; otherwise
users who may want the new module will not see the news.
If the module is platform specific, e.g., a service module using
systemd, then a condition like
@@ -60,5 +76,6 @@ but you should follow some basic guidelines:
condition = hostPlatform.isLinux;
```
should be added. If you contribute a module then you don't need to
add this entry, the merger will create an entry for you.
should be added to avoid showing the news on unsupported platforms.
Use the `create-news-entry` generator described above to scaffold
this entry as part of your contribution.
+24
View File
@@ -0,0 +1,24 @@
# Release Notes {#sec-contributing-release-notes}
Home Manager release notes are published as part of the website documentation in
`docs/release-notes/rl-*.md`. They are intended for users migrating from one
stable release to another.
A change should be mentioned in the release notes when it affects migration
between stable releases. This includes changes that require user action or
awareness during an upgrade, such as:
- breaking changes;
- default changes guarded by `home.stateVersion`;
- required migration steps;
- broad behavior changes across platform or integration boundaries;
- compatibility changes that can affect existing configurations.
Do not use the presence or absence of a news entry to decide whether a change
belongs in the release notes. A change can require a news entry, a release note,
both, or neither.
When preparing a release, review the commits since the previous stable release
and update the upcoming release note file with the migration impact. Prefer
concise user-facing descriptions that name the affected option paths and explain
required action.
+24 -5
View File
@@ -54,6 +54,13 @@ The `default.nix` file should list all test cases:
}
```
Prefer keeping related assertions in as few test files as practical.
Exercising several cases in one evaluation keeps the test suite cheaper
to evaluate and reduces maintenance burden. Split cases into separate
files when they need incompatible module configuration, platform
conditions, expected assertion failures, or otherwise cannot share one
evaluation.
### Common NMT Assertions {#sec-tests-assertions}
NMT provides several assertion functions:
@@ -162,6 +169,12 @@ For cross-platform modules that have packages which need to be stubbed on Darwin
add the package names to `tests/darwinScrublist.nix` to prevent build failures
during cross-platform test runs.
On Linux, packages are automatically scrubbed by the test infrastructure,
so tests should normally use the module's default package. Use
`test.stubs` or `config.lib.test.mkStubPackage` only when the automatic
scrubbing does not model the behavior that the test needs, such as a
package with additional files or a non-default executable layout.
## Using the tests command {#sec-tests-command}
Home Manager provides a convenient `tests` command for discovering and running tests:
@@ -179,16 +192,22 @@ $ nix run .#tests -- alacritty
# Run a specific test
$ nix run .#tests -- test-alacritty-empty-settings
# Run integration tests
# List integration tests
$ nix run .#tests -- -t -l
# Run all integration tests
$ nix run .#tests -- -t integration-test-
# Interactive test selection (requires fzf)
$ python3 tests/tests.py -i
$ nix run .#tests -- -i
# Pass additional nix build flags
$ nix run .#tests -- alacritty -- --verbose
```
Integration tests are only exposed on Linux. On other platforms, integration
test discovery may report no matching tests.
## Manual test commands {#sec-tests-manual}
For advanced usage or CI environments, you can also run tests manually using nix build commands.
@@ -202,7 +221,7 @@ $ nix-build --pure --option allow-import-from-derivation false tests -A build.al
in the project root. List all test cases through
``` shell
$ nix-build --pure tests --option allow-import-from-derivation false -A list
$ nix run .#tests -- -l
```
and run an individual test, for example `alacritty-empty-settings`,
@@ -217,13 +236,13 @@ and may cause failures. To run against the Nixpkgs from the `flake.lock` file,
use instead e.g.
``` shell
$ nix build --option allow-import-from-derivation false .#test-all
$ nix build .#test-all
```
or
``` shell
$ nix build --option allow-import-from-derivation false .#test-alacritty-empty-settings
$ nix build .#test-alacritty-empty-settings
```
Some tests may be marked with `enableLegacyIfd`, those may be run by run with e.g.
+1 -1
View File
@@ -31,7 +31,7 @@ this package. This can typically be done in two ways.
should work OK. With flakes, pass the unstable package set as
described in
[How do I install packages from Nixpkgs unstable?](#_how_do_i_install_packages_from_nixpkgs_unstable)
[How do I install packages from Nixpkgs unstable?](unstable.md#_how_do_i_install_packages_from_nixpkgs_unstable)
and then use the extra module argument:
``` nix
+4 -4
View File
@@ -6,26 +6,26 @@ Home Manager can be used in three primary ways:
NixOS and Darwin, this is the only available choice. It is also
recommended for people on NixOS or Darwin that want to manage their
home directory independently of the system as a whole. See
[Standalone installation](#sec-install-standalone) for instructions
[Standalone installation](installation/standalone.md#sec-install-standalone) for instructions
on how to perform this installation.
2. As a module within a NixOS system configuration. This allows the
user profiles to be built together with the system when running
`nixos-rebuild`. See [NixOS module](#sec-install-nixos-module) for a
`nixos-rebuild`. See [NixOS module](installation/nixos.md#sec-install-nixos-module) for a
description of this setup.
3. As a module within a
[nix-darwin](https://github.com/nix-darwin/nix-darwin/) system
configuration. This allows the user profiles to be built together
with the system when running `darwin-rebuild`. See [nix-darwin
module](#sec-install-nix-darwin-module) for a description of this
module](installation/nix-darwin.md#sec-install-nix-darwin-module) for a description of this
setup.
:::{.note}
In this chapter we describe how to install Home Manager in the standard
way using channels. If you prefer to use [Nix
Flakes](https://wiki.nixos.org/wiki/Flakes) then please see the instructions
in [nix flakes](#ch-nix-flakes).
in [nix flakes](nix-flakes.md#ch-nix-flakes).
:::
```{=include=} sections
+17 -4
View File
@@ -52,6 +52,9 @@ home-manager.users.eve = { pkgs, ... }: {
and after a `darwin-rebuild switch` the user eve's environment should
include a basic Bash configuration and the packages atool and httpie.
Home Manager activation runs as part of nix-darwin activation for each
configured user.
If you do not plan on having Home Manager manage your shell
configuration then you must add either
@@ -103,13 +106,23 @@ Nixpkgs.
:::
:::{.note}
Home Manager will pass `osConfig` as a module argument to any modules
you create. This contains the system's nix-darwin configuration.
Home Manager passes extra module arguments to each
`home-manager.users.<name>` module:
``` nix
{ lib, pkgs, osConfig, ... }:
{ lib, pkgs, osConfig, darwinConfig, osClass, modulesPath, ... }:
```
Here `osConfig` contains the system's nix-darwin configuration and
`darwinConfig` is a Darwin-specific alias for the same value. The `lib`
argument is Home Manager's extended library. You can pass additional module
arguments with `home-manager.extraSpecialArgs`.
:::
Once installed you can see [Using Home Manager](#ch-usage) for a more detailed
:::{.note}
Use `home-manager.sharedModules` to add Home Manager modules to every user
declared under `home-manager.users`.
:::
Once installed you can see [Using Home Manager](../usage.md#ch-usage) for a more detailed
description of Home Manager and how to use it.
+25 -6
View File
@@ -85,12 +85,21 @@ httpie.
:::{.note}
If `nixos-rebuild switch` does not result in the environment you expect,
you can take a look at the output of the Home Manager activation script
output using
then the service to inspect depends on the activation mode.
By default, Home Manager activates each configured user during boot and
system rebuilds through a NixOS system service:
``` shell
$ systemctl status "home-manager-$USER.service"
```
If `home-manager.startAsUserService = true` is set, Home Manager instead
activates through the user's systemd service:
``` shell
$ systemctl --user status home-manager.service
```
:::
If you do not plan on having Home Manager manage your shell
@@ -144,13 +153,23 @@ Nixpkgs.
:::
:::{.note}
Home Manager will pass `osConfig` as a module argument to any modules
you create. This contains the system's NixOS configuration.
Home Manager passes extra module arguments to each
`home-manager.users.<name>` module:
``` nix
{ lib, pkgs, osConfig, ... }:
{ lib, pkgs, osConfig, nixosConfig, osClass, modulesPath, ... }:
```
Here `osConfig` contains the system's NixOS configuration and `nixosConfig`
is a NixOS-specific alias for the same value. The `lib` argument is Home
Manager's extended library. You can pass additional module arguments with
`home-manager.extraSpecialArgs`.
:::
Once installed you can see [Using Home Manager](#ch-usage) for a more detailed
:::{.note}
Use `home-manager.sharedModules` to add Home Manager modules to every user
declared under `home-manager.users`.
:::
Once installed you can see [Using Home Manager](../usage.md#ch-usage) for a more detailed
description of Home Manager and how to use it.
+1 -1
View File
@@ -71,5 +71,5 @@ checkout of the repository then you can use the
[home-manager.path](#opt-programs.home-manager.path) option to specify the absolute
path to the repository.
Once installed you can see [Using Home Manager](#ch-usage) for a more detailed
Once installed you can see [Using Home Manager](../usage.md#ch-usage) for a more detailed
description of Home Manager and how to use it.
+1 -1
View File
@@ -1,6 +1,6 @@
# Home Manager Manual {#home-manager-manual}
## Version 26.05
## Version 26.11 (unstable)
```{=include=} preface
+3 -3
View File
@@ -12,18 +12,18 @@ flake in three ways:
NixOS and Darwin, this is the only available choice. It is also
recommended for people on NixOS or Darwin that want to manage their
home directory independently of the system as a whole. See
[Standalone setup](#sec-flakes-standalone) for instructions on how
[Standalone setup](nix-flakes/standalone.md#sec-flakes-standalone) for instructions on how
to perform this installation.
2. As a module within a NixOS system configuration. This allows the
user profiles to be built together with the system when running
`nixos-rebuild`. See [NixOS module](#sec-flakes-nixos-module) for a
`nixos-rebuild`. See [NixOS module](nix-flakes/nixos.md#sec-flakes-nixos-module) for a
description of this setup.
3. As a module within a [nix-darwin](https://github.com/nix-darwin/nix-darwin/)
system configuration. This allows the user profiles to be built
together with the system when running `darwin-rebuild`. See
[nix-darwin module](#sec-flakes-nix-darwin-module) for a
[nix-darwin module](nix-flakes/nix-darwin.md#sec-flakes-nix-darwin-module) for a
description of this setup.
Advanced users may want Home Manager's `nixpkgs` input to follow the
+1 -1
View File
@@ -8,7 +8,7 @@ Manager's flake module, `flakeModules.home-manager`.
description = "flake-parts configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
home-manager.url = "github:nix-community/home-manager";
flake-parts.url = "github:hercules-ci/flake-parts";
};
+7 -3
View File
@@ -8,7 +8,7 @@ to that of NixOS. The `flake.nix` would be:
description = "Darwin configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
darwin.url = "github:nix-darwin/nix-darwin";
darwin.inputs.nixpkgs.follows = "nixpkgs";
home-manager.url = "github:nix-community/home-manager";
@@ -45,8 +45,12 @@ module argument from within the module graph itself. For values that originate
outside the module graph, such as flake inputs, prefer
`home-manager.extraSpecialArgs`.
and it is also rebuilt with the nix-darwin generations. The rebuild
command here may be `darwin-rebuild switch --flake ~/.config/darwin`.
Use `home-manager.sharedModules` for Home Manager modules or settings that
should be imported by every user declared in `home-manager.users`.
The Home Manager configuration is also rebuilt with the nix-darwin
generations. The rebuild command here may be
`darwin-rebuild switch --flake ~/.config/darwin`.
You can use the above `flake.nix` as a template in `~/.config/darwin` by
+4 -1
View File
@@ -8,7 +8,7 @@ be as follows:
description = "NixOS configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
home-manager.url = "github:nix-community/home-manager";
};
@@ -43,6 +43,9 @@ module argument from within the module graph itself. For values that originate
outside the module graph, such as flake inputs, prefer
`home-manager.extraSpecialArgs`.
Use `home-manager.sharedModules` for Home Manager modules or settings that
should be imported by every user declared in `home-manager.users`.
The Home Manager configuration is then part of the NixOS configuration
and is automatically rebuilt with the system when using the appropriate
command for the system, such as
+1 -1
View File
@@ -33,5 +33,5 @@
Unlike the channel-based setup, `home.nix` will be evaluated when
the flake is built, so it must be present before bootstrap of Home
Manager from the flake. See [Configuration Example](#sec-usage-configuration) for
Manager from the flake. See [Configuration Example](../usage/configuration.md#sec-usage-configuration) for
introduction about writing a Home Manager configuration.
+1 -1
View File
@@ -27,7 +27,7 @@ imported Home Manager modules, use `extraSpecialArgs` in the call to
``` nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
home-manager.url = "github:nix-community/home-manager";
};
+1 -1
View File
@@ -43,7 +43,7 @@ option will state the expected type, for
[programs.emacs.enable](#opt-programs.emacs.enable) you will see "Type: boolean". You
there also find information about the default value and a description of
the option. You can find the complete option documentation in
[Home Manager Configuration Options](#ch-options) or directly in the terminal by running
[Home Manager Configuration Options](options.md#ch-options) or directly in the terminal by running
``` shell
man home-configuration.nix
+1 -1
View File
@@ -39,7 +39,7 @@ This error is about a file that Home Manager wants to manage as a
symbolic link in your home directory. It is separate from package
profile collisions, which usually mention `installPackages` or a
`collision between .../bin/...` path. For package collisions, see
[Why is there a collision error when switching generation?](#_why_is_there_a_collision_error_when_switching_generation).
[Why is there a collision error when switching generation?](../faq/collision.md#_why_is_there_a_collision_error_when_switching_generation).
## Resolving file collisions {#sec-usage-dotfiles-collisions}
+1 -1
View File
@@ -8,7 +8,7 @@ without changing release branches.
If you want to move from one release branch to another, such as
`release-25.05` to `release-25.11`, or between a release branch and
`master`, see
[Upgrading to a new Home Manager release](#sec-upgrade-release).
[Upgrading to a new Home Manager release](upgrading.md#sec-upgrade-release).
## Flake-Based Configurations {#sec-updating-flakes}
+5 -5
View File
@@ -16,10 +16,10 @@ use Home Manager's `master` branch.
Home Manager follows NixOS release cycles and provides corresponding branches:
- **release-<version>**: Stable branch for the matching NixOS or
- **release-\<version\>**: Stable branch for the matching NixOS or
Nixpkgs release, such as `release-25.11`.
- **master**: Development branch (tracks nixos-unstable)
- **master**: Development branch (tracks nixpkgs-unstable)
:::{.note}
Use the Home Manager branch that matches the Nixpkgs branch used to
@@ -61,7 +61,7 @@ instead:
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
home-manager = {
url = "github:nix-community/home-manager";
@@ -169,7 +169,7 @@ affected configuration.
### Common Issues {#sec-upgrade-release-state-troubleshooting-common-issues}
Check the [Home Manager Release Notes](#ch-release-notes) for breaking changes.
Check the [Home Manager Release Notes](../release-notes/release-notes.md#ch-release-notes) for breaking changes.
1. **Version Mismatch Warning**: If you see warnings about version
mismatches, ensure your Home Manager branch matches the Nixpkgs
@@ -199,4 +199,4 @@ the expected release branch.
## Additional Resources {#sec-upgrade-release-resources}
- [NixOS Stable Release Notes](https://nixos.org/manual/nixos/stable/release-notes)
- [Home Manager Release Notes](#ch-release-notes)
- [Home Manager Release Notes](../release-notes/release-notes.md#ch-release-notes)
+231
View File
@@ -0,0 +1,231 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import html
import re
import shutil
import sys
from pathlib import Path
SIMPLE_ROLES = (
"command",
"component",
"description",
"file",
"index",
"system",
"type",
)
HEADING_ANCHOR = re.compile(r"^(#{1,6}\s+)(.*)\s+\{#([^}]+)\}\s*$")
INLINE_ANCHOR = re.compile(r"\[\]\{#([^}]+)\}")
OPTION_ROLE = re.compile(r"(?<![$`])\{option\}`([^`]*)`")
SIMPLE_ROLE = re.compile(r"(?<![$`])\{(" + "|".join(SIMPLE_ROLES) + r")\}`([^`]*)`")
OPTION_LINK = re.compile(
r"\[(?P<label>[^\]]*)\]\(#(?P<anchor>(?:opt|nixos-opt|nix-darwin-opt)-[^)]+)\)"
)
LEFTOVER_ROLE = re.compile(
r"(?<![$`])\{(" + "|".join(("option", *SIMPLE_ROLES)) + r")\}`[^`]*`"
)
FENCE = re.compile(r"^\s*(`{3,})(.*)$")
FENCE_CLOSE = re.compile(r"^\s*`{3,}\s*$")
ADMONITION_OPEN = re.compile(r"^\s*:::\s*\{\.(note|warning|example)\}\s*$")
ADMONITION_CLOSE = re.compile(r"^\s*:::\s*$")
DEEP_SPLIT_NAMESPACES = {"programs", "services"}
def option_target(anchor: str, current_file: Path, base_depth: int) -> str:
if anchor.startswith("nix-darwin-opt-"):
option = anchor.removeprefix("nix-darwin-opt-")
option = option.replace("<", "_").replace(">", "_")
anchor = f"nix-darwin-opt-{option}"
base = "options/nix-darwin"
elif anchor.startswith("nixos-opt-"):
option = anchor.removeprefix("nixos-opt-")
option = option.replace("<", "_").replace(">", "_")
anchor = f"nixos-opt-{option}"
base = "options/nixos"
else:
option = anchor.removeprefix("opt-")
option = option.replace("<", "_").replace(">", "_")
anchor = f"opt-{option}"
base = "options/home-manager"
page_parts = option_page_parts(option)
prefix = "../" * (base_depth + len(current_file.parent.parts))
return f"{prefix}{base}/{'/'.join(page_parts)}.md#{anchor}"
def option_page_parts(option_name: str) -> list[str]:
parts = option_name.split(".")
namespace = parts[0]
if namespace in DEEP_SPLIT_NAMESPACES and len(parts) > 1:
return parts[:2]
return [namespace]
def option_label(anchor: str) -> str:
if anchor.startswith("nix-darwin-opt-"):
return anchor.removeprefix("nix-darwin-opt-")
if anchor.startswith("nixos-opt-"):
return anchor.removeprefix("nixos-opt-")
return anchor.removeprefix("opt-")
def markdown_label(value: str) -> str:
return value.replace("<", "&lt;").replace(">", "&gt;")
def convert_inline(line: str, current_file: Path, base_depth: int) -> str:
line = line.replace("index.xhtml", "index.html")
line = INLINE_ANCHOR.sub(
lambda match: f'<a id="{html.escape(match.group(1), quote=True)}"></a>',
line,
)
line = OPTION_ROLE.sub(
lambda match: (
f"[{markdown_label(match.group(1))}]"
f"({option_target(f'opt-{match.group(1)}', current_file, base_depth)})"
),
line,
)
line = OPTION_LINK.sub(
lambda match: (
f"[{markdown_label(match.group('label') or option_label(match.group('anchor')))}]"
f"({option_target(match.group('anchor'), current_file, base_depth)})"
),
line,
)
return SIMPLE_ROLE.sub(lambda match: f"`{match.group(2)}`", line)
def convert_heading(line: str, current_file: Path, base_depth: int) -> str:
match = HEADING_ANCHOR.match(line)
if match is None:
return convert_inline(line, current_file, base_depth)
prefix, title, anchor = match.groups()
return (
f'<a id="{html.escape(anchor, quote=True)}"></a>\n'
f"{prefix}{convert_inline(title, current_file, base_depth)}"
)
def is_include_fence(line: str) -> tuple[bool, str]:
match = FENCE.match(line)
if match is None:
return False, ""
info = match.group(2).strip()
return info.startswith(("{=include=}", "include")), match.group(1)
def convert_markdown(
text: str,
source: Path,
current_file: Path,
base_depth: int,
) -> str:
output: list[str] = []
in_code_fence = False
code_fence = ""
in_include = False
include_fence = ""
in_admonition = False
for raw_line in text.splitlines(keepends=True):
line = raw_line[:-1] if raw_line.endswith("\n") else raw_line
newline = "\n" if raw_line.endswith("\n") else ""
if in_include:
if FENCE_CLOSE.match(line) and len(line.strip()) >= len(include_fence):
in_include = False
include_fence = ""
continue
if in_code_fence:
output.append(raw_line)
if FENCE_CLOSE.match(line) and len(line.strip()) >= len(code_fence):
in_code_fence = False
code_fence = ""
continue
include, fence = is_include_fence(line)
if include:
in_include = True
include_fence = fence
continue
fence_match = FENCE.match(line)
if fence_match is not None:
in_code_fence = True
code_fence = fence_match.group(1)
output.append(raw_line)
continue
if in_admonition:
if ADMONITION_CLOSE.match(line):
in_admonition = False
continue
converted = convert_inline(line, current_file, base_depth)
output.append(f"> {converted}{newline}" if converted else ">\n")
continue
admonition = ADMONITION_OPEN.match(line)
if admonition is not None:
in_admonition = True
output.append(f"> **{admonition.group(1).title()}**\n")
continue
output.append(convert_heading(line, current_file, base_depth) + newline)
if in_include:
raise ValueError(f"{source}: unterminated include block")
converted = "".join(output)
if LEFTOVER_ROLE.search(converted):
raise ValueError(f"{source}: unconverted NixOS-render-docs role remains")
if "```{=include=}" in converted:
raise ValueError(f"{source}: unconverted include block remains")
return converted
def convert_tree(source: Path, destination: Path, base_depth: int) -> None:
for path in source.rglob("*"):
if not path.is_file():
continue
target = destination / path.relative_to(source)
target.parent.mkdir(parents=True, exist_ok=True)
if path.suffix == ".md":
text = path.read_text(encoding="utf-8")
target.write_text(
convert_markdown(text, path, path.relative_to(source), base_depth),
encoding="utf-8",
)
else:
shutil.copy2(path, target)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--base-depth", type=int, default=0)
parser.add_argument("source", type=Path)
parser.add_argument("destination", type=Path)
args = parser.parse_args()
if not args.source.is_dir():
print(f"missing source directory: {args.source}", file=sys.stderr)
return 1
args.destination.mkdir(parents=True, exist_ok=True)
convert_tree(args.source, args.destination, args.base_depth)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
import html
import json
import re
import sys
from pathlib import Path
ANCHOR = re.compile(r'<a id="([^"]+)"></a>')
SPECIAL_PAGES = {
Path("manual.md"): "index.html",
Path("preface.md"): "index.html",
Path("options.md"): "options/home-manager/index.html",
Path("nixos-options.md"): "options/nixos/index.html",
Path("nix-darwin-options.md"): "options/nix-darwin/index.html",
}
OPTION_PREFIXES = {
"opt-": "options/home-manager",
"nixos-opt-": "options/nixos",
"nix-darwin-opt-": "options/nix-darwin",
}
ROUTER = """
(function () {
var h = location.hash || "", a = h.slice(1), t = __DEFAULT__;
var m = __ANCHORS__, p = __PREFIXES__;
function o(a) {
for (var k in p) {
if (a.indexOf(k) !== 0) continue;
var s = a.slice(k.length).replace(/[<>]/g, "_").split("."), q = s[0];
if ((q === "programs" || q === "services") && s.length > 1) q += "/" + s[1];
return p[k] + "/" + q + ".html";
}
return null;
}
if (a) t = Object.prototype.hasOwnProperty.call(m, a) ? m[a] : o(a) || t;
if (t === null) return;
var n = t + (location.search || "") + h;
var c = location.pathname.split("/").pop() + location.search + h;
if (n !== c) location.replace(n);
}());
""".strip()
def page_for(source, path):
relative = path.relative_to(source)
return SPECIAL_PAGES.get(relative, relative.with_suffix(".html").as_posix())
def anchors_for(source, roots):
anchors = {}
for root in roots:
files = [root] if root.is_file() else sorted(root.rglob("*.md"))
for path in files:
page = page_for(source, path)
for anchor in ANCHOR.findall(path.read_text(encoding="utf-8")):
anchors.setdefault(anchor, page)
return anchors
def script(default, anchors):
js = (
ROUTER.replace("__DEFAULT__", json.dumps(default))
.replace(
"__ANCHORS__", json.dumps(anchors, sort_keys=True, separators=(",", ":"))
)
.replace("__PREFIXES__", json.dumps(OPTION_PREFIXES, separators=(",", ":")))
)
return "\n".join(
[
" <script>",
" //<![CDATA[",
*[f" {line}" for line in js.splitlines()],
" //]]>",
" </script>",
]
)
def write_redirect(output, name, destination, anchors=None):
escaped = html.escape(destination, quote=True)
(output / name).write_text(
f"""<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta charset="utf-8" />
{script(destination, anchors or {})}
<meta http-equiv="refresh" content="0; url={escaped}" />
<link rel="canonical" href="{escaped}" />
<title>Redirecting...</title>
</head>
<body>
<p>Redirecting to <a href="{escaped}">{escaped}</a>.</p>
</body>
</html>
""",
encoding="utf-8",
)
def inject_root_router(output, anchors):
index = output / "index.html"
text = index.read_text(encoding="utf-8")
index.write_text(text.replace("<head>", f"<head>\n{script(None, anchors)}", 1))
def main():
source, output = map(Path, sys.argv[1:])
manual_roots = [
path
for path in source.iterdir()
if path.name != "options" and (path.is_dir() or path.suffix == ".md")
]
manual_anchors = anchors_for(source, manual_roots)
release_anchors = anchors_for(source, [source / "release-notes"])
inject_root_router(output, manual_anchors)
for name, destination, anchors in [
("index.xhtml", "index.html", manual_anchors),
("options.html", "options/home-manager/index.html", {}),
("options.xhtml", "options/home-manager/index.html", {}),
("nixos-options.xhtml", "options/nixos/index.html", {}),
("nix-darwin-options.xhtml", "options/nix-darwin/index.html", {}),
("release-notes.xhtml", "release-notes/release-notes.html", release_anchors),
]:
write_redirect(output, name, destination, anchors)
if __name__ == "__main__":
main()
+23
View File
@@ -0,0 +1,23 @@
{
pkgs,
optionDocs,
manpageUrls,
revision,
}:
pkgs.runCommand "home-manager-mdbook-options"
{
nativeBuildInputs = [
pkgs.buildPackages.nixos-render-docs
pkgs.buildPackages.python3
];
optionDocsJson = builtins.toJSON optionDocs;
passAsFile = [ "optionDocsJson" ];
}
''
python3 ${./render-options.py} \
"$optionDocsJsonPath" \
${manpageUrls} \
${revision} \
"$out"
''
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import re
import subprocess
import sys
from pathlib import Path
OPTION_LINK = re.compile(
r"\[(?P<label>[^\]]*)\]\(#(?P<anchor>(?:opt|nixos-opt|nix-darwin-opt)-[^)]+)\)"
)
OPTION_HREF = re.compile(r'href="#(?P<anchor>(?:opt|nixos-opt|nix-darwin-opt)-[^"]+)"')
DEEP_SPLIT_NAMESPACES = {"programs", "services"}
def option_label(anchor: str) -> str:
if anchor.startswith("nix-darwin-opt-"):
return anchor.removeprefix("nix-darwin-opt-")
if anchor.startswith("nixos-opt-"):
return anchor.removeprefix("nixos-opt-")
return anchor.removeprefix("opt-")
def option_target(anchor: str, current_file: Path) -> str:
if anchor.startswith("nix-darwin-opt-"):
option = anchor.removeprefix("nix-darwin-opt-")
base = "options/nix-darwin"
elif anchor.startswith("nixos-opt-"):
option = anchor.removeprefix("nixos-opt-")
base = "options/nixos"
else:
option = anchor.removeprefix("opt-")
base = "options/home-manager"
page_parts = option_page_parts(option)
prefix = "../" * len(current_file.parent.parts)
return f"{prefix}{base}/{'/'.join(page_parts)}.md#{anchor}"
def rewrite_option_links(text: str, current_file: Path) -> str:
text = OPTION_LINK.sub(
lambda match: (
f"[{match.group('label') or option_label(match.group('anchor'))}]"
f"({option_target(match.group('anchor'), current_file)})"
),
text,
)
return OPTION_HREF.sub(
lambda match: f'href="{option_target(match.group("anchor"), current_file)}"',
text,
)
def namespace_for(option_name: str) -> str:
return option_name.split(".", 1)[0]
def option_page_parts(option_name: str) -> list[str]:
parts = option_name.split(".")
namespace = parts[0]
if namespace in DEEP_SPLIT_NAMESPACES and len(parts) > 1:
return parts[:2]
return [namespace]
def option_group_for(option_name: str) -> str:
return "/".join(option_page_parts(option_name))
def render_namespace(
options: dict[str, object],
destination: Path,
manpage_urls: Path,
revision: str,
anchor_prefix: str,
current_file: Path,
) -> None:
destination.parent.mkdir(parents=True, exist_ok=True)
source = destination.with_suffix(".json")
source.write_text(json.dumps(options, sort_keys=True), encoding="utf-8")
subprocess.run(
[
"nixos-render-docs",
"options",
"commonmark",
"--manpage-urls",
str(manpage_urls),
"--revision",
revision,
"--anchor-style",
"legacy",
"--anchor-prefix",
anchor_prefix,
str(source),
str(destination),
],
check=True,
)
source.unlink()
destination.write_text(
rewrite_option_links(destination.read_text(encoding="utf-8"), current_file),
encoding="utf-8",
)
def write_doc(
name: str,
doc: dict[str, str],
manpage_urls: Path,
revision: str,
output: Path,
) -> None:
options_json = Path(doc["json"]) / "share/doc/nixos/options.json"
options = json.loads(options_json.read_text(encoding="utf-8"))
grouped: dict[str, dict[str, object]] = {}
for option_name, option in options.items():
group = option_group_for(option_name)
grouped.setdefault(group, {})[option_name] = option
doc_dir = output / "options" / doc["path"]
doc_dir.mkdir(parents=True, exist_ok=True)
groups = sorted(grouped)
namespaces = sorted({group.split("/", 1)[0] for group in groups})
nested = {
namespace: sorted(
group for group in groups if group.startswith(f"{namespace}/")
)
for namespace in namespaces
}
index = [
f"# {doc['title']}\n",
"\n",
"Generated from Home Manager option definitions.\n",
"\n",
"## Namespaces\n",
"\n",
]
for namespace in namespaces:
if nested[namespace]:
index.append(f"- [{namespace}]({namespace}/index.md)\n")
else:
index.append(f"- [{namespace}]({namespace}.md)\n")
(doc_dir / "index.md").write_text("".join(index), encoding="utf-8")
summary_dir = output / "summary"
summary_dir.mkdir(parents=True, exist_ok=True)
summary = [f" - [{doc['title']}](options/{doc['path']}/index.md)\n"]
for namespace in namespaces:
if nested[namespace]:
namespace_index = doc_dir / namespace / "index.md"
namespace_index.parent.mkdir(parents=True, exist_ok=True)
namespace_index.write_text(
"".join(
[
f"# {namespace}\n",
"\n",
"## Modules\n",
"\n",
*(
f"- [{group.split('/', 1)[1]}]({group.split('/', 1)[1]}.md)\n"
for group in nested[namespace]
),
]
),
encoding="utf-8",
)
summary.append(
f" - [{namespace}](options/{doc['path']}/{namespace}/index.md)\n"
)
for group in nested[namespace]:
module_name = group.split("/", 1)[1]
summary.append(
f" - [{module_name}](options/{doc['path']}/{namespace}/{module_name}.md)\n"
)
else:
summary.append(
f" - [{namespace}](options/{doc['path']}/{namespace}.md)\n"
)
for group in groups:
page = doc_dir / f"{group}.md"
current_file = Path("options") / doc["path"] / f"{group}.md"
render_namespace(
grouped[group],
page,
manpage_urls,
revision,
doc["prefix"],
current_file,
)
(summary_dir / f"{name}.md").write_text("".join(summary), encoding="utf-8")
def main() -> int:
if len(sys.argv) != 5:
print(
"usage: render-options.py OPTION_DOCS MANPAGE_URLS REVISION OUT",
file=sys.stderr,
)
return 1
option_docs = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
manpage_urls = Path(sys.argv[2])
revision = sys.argv[3]
output = Path(sys.argv[4])
for name, doc in option_docs.items():
write_doc(name, doc, manpage_urls, revision, output)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
from __future__ import annotations
import sys
from pathlib import Path
def main() -> int:
if len(sys.argv) != 5:
print(
"usage: substitute-summary.py SUMMARY HM_SUMMARY NIXOS_SUMMARY DARWIN_SUMMARY",
file=sys.stderr,
)
return 1
summary = Path(sys.argv[1])
replacements = {
"@HOME_MANAGER_OPTIONS@": Path(sys.argv[2]).read_text(encoding="utf-8"),
"@NIXOS_OPTIONS@": Path(sys.argv[3]).read_text(encoding="utf-8"),
"@NIX_DARWIN_OPTIONS@": Path(sys.argv[4]).read_text(encoding="utf-8"),
}
text = summary.read_text(encoding="utf-8")
for needle, replacement in replacements.items():
text = text.replace(needle, replacement.rstrip())
summary.write_text(text, encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+3 -2
View File
@@ -1,9 +1,10 @@
# Release Notes {#ch-release-notes}
This section lists the release notes for stable versions of Home Manager
and the current unstable version.
This section lists the release notes for stable versions of Home Manager and the
current unstable version.
```{=include=} chapters
rl-2611.md
rl-2605.md
rl-2511.md
rl-2505.md
+1 -1
View File
@@ -36,7 +36,7 @@ This release has the following notable changes:
optionally also activate it. The recommended installation method for
a standalone Home Manager setup with Nix flakes uses this new
command. The standard installation method remains the same but uses
the new command internally. See [sec-flakes-standalone](#sec-flakes-standalone) for
the new command internally. See [sec-flakes-standalone](../nix-flakes/standalone.md#sec-flakes-standalone) for
more.
## State Version Changes {#sec-release-23.05-state-version-changes}
+1 -1
View File
@@ -19,7 +19,7 @@ This release has the following notable changes:
- The Home Manager test suite has been removed from the main Nix Flake
since it caused unnecessary evaluations and downloads. Instead the
tests are available through a Nix Flake file inside the `tests`
directory. See [](#sec-tests) for example commands.
directory. See [Tests](../contributing/tests.md#sec-tests) for example commands.
## State Version Changes {#sec-release-25.05-state-version-changes}
+2 -2
View File
@@ -16,7 +16,7 @@ This release has the following notable changes:
term removed all together. If you have developed tooling that
directly call the generated activation script, then you are
encouraged to adapt to the new behavior. See the
[Activation](#sec-internals-activation) section in the manual for
[Activation](../internals/activation.md#sec-internals-activation) section in the manual for
details on how to call the activation script.
- The `home-manager switch` command now offers a `--rollback` option.
@@ -25,7 +25,7 @@ This release has the following notable changes:
previously possible to accomplish this by manually activating an old
generation, it always created a new profile generation. The new
behavior mirrors the behavior of `nixos-rebuild switch --rollback`.
See the [Rollbacks](#sec-usage-rollbacks) section for more.
See the [Rollbacks](../usage/rollbacks.md#sec-usage-rollbacks) section for more.
- The `home-manager switch` command now offers a
`--specialisation NAME` option. When given, the switch activates the
+154 -36
View File
@@ -6,41 +6,141 @@ The 26.05 release branch became stable in May, 2026.
This release has the following notable changes:
- WezTerm now supports declarative configuration via
[](#opt-programs.wezterm.settings). Settings are expressed as a Nix
attribute set and serialized to Lua using `lib.generators.toLua`.
Raw Lua expressions such as `wezterm.font` and `wezterm.action.*`
can be embedded using `lib.generators.mkLuaInline`. The existing
[](#opt-programs.wezterm.extraConfig) option remains fully supported
and can be combined with [](#opt-programs.wezterm.settings).
- Home Manager now supports RFC 42-style SSH configuration through
[](#opt-programs.ssh.settings). The legacy `programs.ssh.matchBlocks` format
is deprecated and migrated automatically.
- The [](#opt-programs.anki.uiScale) option now expects a value in the
range 1.02.0, previously it erroneously expected values in the
range `0.01.0`.
- The new `home-manager.startAsUserService` option runs user activation on
demand at login instead of doing all setup during system boot, which helps on
systems where home directories are mounted later (for example, pam_mount).
- New options were added for `services.podman.useDefaultMachine` and podman
machine management on Darwin.
- The [](#opt-programs.rclone.enable) module now supports launchd-backed config,
mount, and serve agents on Darwin. The new
`programs.rclone.remotes.<name>.serve` option can manage `rclone serve`
sidecar services on both Linux and Darwin. On non-systemd platforms,
`programs.rclone.requiresUnit` is read-only and always `null` because there is
no systemd ordering unit to depend on.
- The Syncthing credentials flow now uses `services.syncthing.guiCredentials`
instead of the removed `services.syncthing.passwordFile` option.
- Firefox extensions are now managed per-profile through the
`programs.firefox.profiles.<name>.extensions.packages` and
`programs.firefox.profiles.<name>.extensions.settings` options. The top-level
`programs.firefox.extensions = [ ... ]` list was removed; migrate it to
`programs.firefox.profiles.<name>.extensions.packages`.
- Thunderbird now supports `accounts.email.accounts.<name>.ews`, the
`outlook.office365.com-ews` account flavor, declarative language packs, and
enterprise policies. Thunderbird accounts using the `outlook.office365.com`
flavor now default IMAP and SMTP authentication to OAuth2 when the account
does not set an authentication method explicitly.
- A new `systemd.user.packages` option provides a user-level equivalent of
`systemd.packages` for installing unit files from packages.
- WezTerm now supports declarative configuration via
[](#opt-programs.wezterm.settings). Settings are expressed as a Nix attribute
set and serialized to Lua using `lib.generators.toLua`. Raw Lua expressions
such as `wezterm.font` and `wezterm.action.*` can be embedded using
`lib.generators.mkLuaInline`. The existing
[](#opt-programs.wezterm.extraConfig) option remains fully supported and can
be combined with [](#opt-programs.wezterm.settings).
- The [](#opt-programs.anki.uiScale) option now expects a value in the range
1.02.0, previously it erroneously expected values in the range `0.01.0`.
Anki sync options moved from `programs.anki.sync.*` to
`programs.anki.profiles."User 1".sync.*`, and
`programs.anki.sync.passwordFile` is migrated to
`programs.anki.profiles."User 1".sync.keyFile`.
- New [](#opt-home.services) namespace for nixpkgs
[modular services](https://nixos.org/manual/nixos/unstable/#modular-services).
Service modules shipped with packages (e.g.
`pkgs.<name>.passthru.services.default`) drop in unchanged and are
lifted to user systemd units. See
[Modular Services](#sec-usage-modular-services) for details.
`pkgs.<name>.passthru.services.default`) drop in unchanged and are lifted to
user systemd units. See [Modular Services](../usage/modular-services.md#sec-usage-modular-services) for
details.
- Dedicated modules were added for several VSCode forks (`programs.cursor`,
`programs.vscodium`, `programs.windsurf`, `programs.kiro`, and
`programs.antigravity`), built from a shared `mkVscodeModule` factory. Each
fork can now be configured independently and simultaneously. Users who
previously selected a fork by setting `programs.vscode.package` should migrate
to the matching module; the `programs.vscode.pname` option was removed.
- The AI coding-assistant modules ([](#opt-programs.claude-code.enable),
[](#opt-programs.codex.enable), and [](#opt-programs.opencode.enable)) now
share a unified `context` option for providing global instructions. The
previous per-module options (`claude-code.memory`,
`codex.custom-instructions`, and `opencode.rules`) are deprecated and migrated
automatically. MCP server configuration via `programs.mcp.servers` was also
extended to more assistant and editor modules through an
`enableMcpIntegration` option.
- The `programs.man` module was split so the man viewer can be selected through
the [](#opt-programs.man.man-db.enable) and
[](#opt-programs.man.mandoc.enable) submodules, which are mutually exclusive.
- The `programs.neovim.extraLuaConfig` option was renamed to
[](#opt-programs.neovim.initLua). Home Manager now writes generated Lua
initialization to {file}`$XDG_CONFIG_HOME/nvim/init.lua` by default. If you
manage that file outside Home Manager, set
[](#opt-programs.neovim.sideloadInitLua) to `true` to load the generated Lua
through the Neovim wrapper instead.
- A new `sshAuthSock` module manages the `SSH_AUTH_SOCK`
environment variable. It is implicitly configured by the
SSH-agent-providing modules (e.g. `services.ssh-agent`, `services.gpg-agent`).
The old `services.ssh-agent.enable{Bash,Zsh,Fish,Nushell}Integration` options
were removed; shell initialization is now provided through `sshAuthSock`.
- A new [](#opt-services.pipewire.enable) module provides client-side
configuration for PipeWire, WirePlumber, and the PulseAudio/JACK compatibility
layers. It does not install or manage the PipeWire daemon itself.
- The `services.swww` module was renamed to [](#opt-services.awww.enable)
following the upstream project rename. The `enable`, `package`, and
`extraArgs` options are migrated automatically from `services.swww.*` to
`services.awww.*`.
- Several modules dropped their freeform `extraConfig` escape hatches in favor
of RFC 42-style `settings`: `programs.aerospace.extraConfig` (and
`userSettings`) and `programs.aria2.extraConfig` were removed.
`programs.mise.settings` was renamed to `programs.mise.globalConfig.settings`.
The `programs.eww.configDir` and
`programs.eww.enable{Bash,Zsh,Fish}Integration` options were removed; use
[](#opt-programs.eww.yuckConfig) and [](#opt-programs.eww.scssConfig) for
declarative Eww config files. The `programs.niriswitcher` options were also
removed.
- Darwin integration received several behavior changes. Home Manager launchd
agents now wait for `/nix/store` before starting and use
`launchctl bootout --wait` when replacing agents. nix-darwin activations now
propagate dry-run mode into Home Manager user activations. On Darwin, Home
Manager also exports `TERMINFO_DIRS` so terminfo entries from Home
Manager-installed packages are visible to shells.
- The [](#opt-nix.assumeXdg) option was added for installations where Nix is
configured to use XDG base directories outside Home Manager. Home Manager also
detects `osConfig.nix.settings.use-xdg-base-directories` when deciding where
to place declarative channel entries.
## State Version Changes {#sec-release-26.05-state-version-changes}
The state version in this release includes the changes below. These
changes are only active if the `home.stateVersion` option is set to
\"26.05\" or later.
The state version in this release includes the changes below. These changes are
only active if the `home.stateVersion` option is set to \"26.05\" or later.
- The [](#opt-gtk.gtk4.theme) option does not mirror
[](#opt-gtk.theme) by default anymore.
- The [](#opt-gtk.gtk4.theme) option does not mirror [](#opt-gtk.theme) by
default anymore.
- The [](#opt-programs.zsh.dotDir) option now defaults to the XDG
configuration directory (usually `~/.config/zsh`) when
[](#opt-xdg.enable) is true.
- The [](#opt-programs.zsh.dotDir) option now defaults to the XDG configuration
directory (usually `~/.config/zsh`) when [](#opt-xdg.enable) is true.
- The [](#opt-programs.yazi.shellWrapperName) option now defaults to
`y` instead of `yy`. For users with older `home.stateVersion` values,
the legacy default `yy` is retained.
- The [](#opt-programs.yazi.shellWrapperName) option now defaults to `y` instead
of `yy`.
- The [](#opt-xdg.userDirs.setSessionVariables) option now defaults to `false`
instead of `true`.
@@ -51,21 +151,39 @@ changes are only active if the `home.stateVersion` option is set to
`DESKTOP`. Home Manager 26.05 introduced a warning when the `XDG_<name>_DIR`
form is used.
- The [](#opt-programs.man.package) option now defaults to `null` on
Darwin because the GNU `man` from nixpkgs ships `apropos`/`man -k`
and `whatis`/`man -f` binaries that don't work on Darwin. Nix-installed
manual pages still work with macOS's built-in `man` via
- The [](#opt-programs.man.package) option now defaults to `null` on Darwin
because the GNU `man` from nixpkgs ships `apropos`/`man -k` and
`whatis`/`man -f` binaries that don't work on Darwin. Nix-installed manual
pages still work with macOS's built-in `man` via
[](#opt-home.extraOutputsToInstall).
- The options [](#opt-programs.neovim.withPython3) and
[](#opt-programs.neovim.withRuby) now default to `false` following nixpkgs
[](#opt-programs.neovim.withRuby) now default to `false` following nixpkgs.
- Neovim plugin `config` fragments are assumed to be Lua by default through
`programs.neovim.plugins.<name>.type = "lua"`, instead of the previous `viml`
default.
- On Linux, `programs.firefox.configPath` now defaults to
`"${config.xdg.configHome}/mozilla/firefox"` for `home.stateVersion = "26.05"` and later.
Older state versions keep the legacy default of `".mozilla/firefox"`.
`"${config.xdg.configHome}/mozilla/firefox"`, instead of the legacy
`".mozilla/firefox"`.
- The [](#opt-wayland.windowManager.hyprland.configType) option now
defaults to `"lua"` for `home.stateVersion = "26.05"` and later.
Older state versions keep the legacy `"hyprlang"` default. Set
`wayland.windowManager.hyprland.configType = "hyprlang"` to keep
generating {file}`$XDG_CONFIG_HOME/hypr/hyprland.conf`.
- The [](#opt-wayland.windowManager.hyprland.configType) option now defaults to
`"lua"`, instead of the legacy `"hyprlang"`. Set
`wayland.windowManager.hyprland.configType = "hyprlang"` to keep generating
{file}`$XDG_CONFIG_HOME/hypr/hyprland.conf`.
- The [](#opt-programs.docker-cli.configDir) option now defaults to
`"${config.xdg.configHome}/docker"` when [](#opt-xdg.enable) is true, instead
of `~/.docker`. Similarly, [](#opt-services.colima.profiles) now sets
`$DOCKER_HOST` for the default context.
- The [](#opt-services.home-manager.autoUpgrade.preSwitchCommands) option now
defaults to an empty list. Previously, when
[](#opt-services.home-manager.autoUpgrade.useFlake) was `true`, it defaulted
to `[ "nix flake update" ]`. Set `preSwitchCommands = [ "nix flake update" ]`
to keep updating flake inputs before each automatic switch.
- The [](#opt-programs.mergiraf.enableGitIntegration) and
[](#opt-programs.mergiraf.enableJujutsuIntegration) options now default to
`false`, so the integrations must be enabled explicitly.
+35
View File
@@ -0,0 +1,35 @@
# Release 26.11 {#sec-release-26.11}
This is the current unstable branch and the information in this
section is therefore not final.
## Highlights {#sec-release-26.11-highlights}
This release has the following notable changes:
- The [](#opt-programs.uv.enable) module can now install uv-managed Python
versions and tools through the new `programs.uv.python.versions`,
`programs.uv.python.default`, and `programs.uv.tool.packages` options. Unpinned
entries track the latest release on each activation while pinned ones stay put,
and `programs.uv.python.prune` / `programs.uv.tool.prune` make the managed set
fully declarative by removing versions and tools that are no longer listed.
- On Darwin, Home Manager launchd agents now support
[](#opt-launchd.agents._name_.domain) to choose either the user's GUI or
background launchd domain. Agents use the GUI domain by default. Set the
domain to `user` for agents that should run without an active graphical login
session.
## State Version Changes {#sec-release-26.11-state-version-changes}
The state version in this release includes the changes below. These
changes are only active if the `home.stateVersion` option is set to
"26.11" or later.
- The KDL options under `programs.zellij` will now correctly escape backslashes
in string values. For example, the Nix string `"\\"` will now correctly
generate the KDL string `"\\"`. Previously, this would have generated `"\"`,
which is invalid KDL. The following options are affected:
- `programs.zellij.settings`
- `programs.zellij.themes`
- `programs.zellij.layouts`
Generated
+4 -4
View File
@@ -2,16 +2,16 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1779508470,
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
"lastModified": 1783604885,
"narHash": "sha256-tzMgSkV7kljEkqIjlgV6F+n+xD+/a35Db8bs7a4BFAo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "29916453413845e54a65b8a1cf996842300cd299",
"rev": "767b0d3ec98a143ad9ed7dfc0d5553510ac27133",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
+38 -13
View File
@@ -1,7 +1,7 @@
{
description = "Home Manager for Nix";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs =
{
@@ -45,15 +45,34 @@
}
// (
let
forAllPkgs =
f:
nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (system: f nixpkgs.legacyPackages.${system});
supportedSystems = [
"aarch64-darwin"
"aarch64-linux"
"i686-linux"
"x86_64-darwin"
"x86_64-linux"
];
forSystems = systems: f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
forAllPkgs = forSystems nixpkgs.lib.systems.flakeExposed;
forSupportedPkgs = forSystems supportedSystems;
forCI = nixpkgs.lib.genAttrs [
"aarch64-darwin"
"x86_64-linux"
];
releaseInfo = nixpkgs.lib.importJSON ./release.json;
docsFor =
pkgs:
import ./docs {
inherit pkgs;
inherit (releaseInfo) release isReleaseBranch;
};
testChunks =
system:
let
@@ -156,12 +175,13 @@
lib.mapAttrs' renameTestPkg tests;
in
{
formatter = forAllPkgs (pkgs: pkgs.callPackage ./home-manager/formatter.nix { });
formatter = forSupportedPkgs (pkgs: pkgs.callPackage ./home-manager/formatter.nix { });
# TODO: increase buildbot testing scope
buildbot = forCI (
system:
let
docs = docsFor nixpkgs.legacyPackages.${system};
allIntegrationTests = integrationTests system;
workingIntegrationTests = nixpkgs.lib.filterAttrs (
name: _:
@@ -171,22 +191,27 @@
]
) allIntegrationTests;
in
(testChunks system) // workingIntegrationTests
(testChunks system)
// workingIntegrationTests
// {
docs-html = docs.manual.html;
docs-json = docs.options.json;
docs-jsonModuleMaintainers = docs.jsonModuleMaintainers;
docs-manpages = docs.manPages;
}
);
packages = forAllPkgs (
pkgs:
let
releaseInfo = nixpkgs.lib.importJSON ./release.json;
docs = import ./docs {
inherit pkgs;
inherit (releaseInfo) release isReleaseBranch;
};
docs = docsFor pkgs;
hmPkg = pkgs.callPackage ./home-manager { path = "${self}"; };
in
{
default = hmPkg;
home-manager = hmPkg;
}
// nixpkgs.lib.optionalAttrs (nixpkgs.lib.elem pkgs.stdenv.hostPlatform.system supportedSystems) {
ci-parse = pkgs.callPackage ./ci/parse.nix { nix = pkgs.nixVersions.latest; };
ci-parse-lix = pkgs.callPackage ./ci/parse.nix {
@@ -212,11 +237,11 @@
}
);
devShells = forAllPkgs (pkgs: {
devShells = forSupportedPkgs (pkgs: {
default = pkgs.callPackage ./home-manager/devShell.nix { };
});
legacyPackages = forAllPkgs (
legacyPackages = forSupportedPkgs (
pkgs:
let
inherit (pkgs.stdenv.hostPlatform) system;
+1
View File
@@ -18,6 +18,7 @@ pkgs.treefmt.withConfig {
deadnix
keep-sorted
nixf-diagnose
ruff
];
settings = pkgs.lib.importTOML ../treefmt.toml;
}
+3 -2
View File
@@ -291,6 +291,7 @@ function doInspectOption() {
for path in "${paths[@]}"; do
nix-instantiate --eval --json \
--arg nixos "$modulesExpr" \
"${extraArgs[@]}" \
--argstr path "$path" \
--arg recursive "$recursive" \
"$NIXOS_OPTION_NIX" \
@@ -313,7 +314,7 @@ function doInit() {
fi
local homeManagerUrl="github:nix-community/home-manager"
local nixpkgsUrl="github:nixos/nixpkgs/nixos-unstable"
local nixpkgsUrl="github:nixos/nixpkgs/nixpkgs-unstable"
while (( $# > 0 )); do
local opt="$1"
@@ -1279,7 +1280,7 @@ while [[ $# -gt 0 ]]; do
export VERBOSE=1
;;
--version)
echo 26.05-pre
echo 26.11-pre
exit 0
;;
*)
+261
View File
@@ -0,0 +1,261 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Home Manager contributors
# This file is distributed under the same license as the Home Manager package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: Home Manager\n"
"Report-Msgid-Bugs-To: https://github.com/nix-community/home-manager/issues\n"
"POT-Creation-Date: 2026-01-21 09:53+0100\n"
"PO-Revision-Date: 2026-05-31 21:01+0000\n"
"Last-Translator: Tofaa <tofig.adigozalov.180904@gmail.com>\n"
"Language-Team: Azerbaijani <https://hosted.weblate.org/projects/home-manager/"
"cli/az/>\n"
"Language: az\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.6.dev0\n"
#. translators: For example: "home-manager: missing argument for --cores"
#: home-manager/home-manager:16
msgid "%s: missing argument for %s"
msgstr "%s: {s} üçün tələb olunan arqument verilməyib"
#. 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 "%s: %s ancaq %s'dən sonra işlənə bilər"
#: home-manager/home-manager:71
msgid "No configuration file found at %s"
msgstr "%s-də konfiqurasiya faylı tapılmadı"
#. translators: The first '%s' specifier will be replaced by either
#. 'home.nix' or 'flake.nix'.
#: home-manager/home-manager:88 home-manager/home-manager:92
#: home-manager/home-manager:191
msgid ""
"Keeping your Home Manager %s in %s is deprecated,\n"
"please move it to %s"
msgstr ""
"Home Manager %s faylının %s daxilində saxlanılması artıq dəstəklənmir.\n"
"Zəhmət olmasa onu %s ünvanına köçürün"
#: home-manager/home-manager:99
msgid "No configuration file found. Please create one at %s"
msgstr "Konfiqurasiya faylı tapılmadı. Zəhmət olmasa fayli %s'də yaradin"
#: home-manager/home-manager:114
msgid "Home Manager not found at %s."
msgstr "Home Manager %s'də tapılmadı."
#. 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:122
msgid ""
"The fallback Home Manager path %s has been deprecated and a file/directory "
"was found there."
msgstr ""
"Ehtiyat Home Manager yolu %s artıq köhnəlmişdir və həmin ünvanda fayl və ya "
"qovluq tapıldı."
#. 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
msgid ""
"To remove this warning, do one of the following.\n"
"\n"
"1. Explicitly tell Home Manager to use the path, for example by adding\n"
"\n"
" { programs.home-manager.path = \"%s\"; }\n"
"\n"
" to your configuration.\n"
"\n"
" If you import Home Manager directly, you can use the `path` parameter\n"
"\n"
" pkgs.callPackage /path/to/home-manager-package { path = \"%s\"; }\n"
"\n"
" when calling the Home Manager package.\n"
"\n"
"2. Remove the deprecated path.\n"
"\n"
" $ rm -r \"%s\""
msgstr ""
"Bu xəbərdarlığı aradan qaldırmaq üçün aşağıdakılardan birini edin.\n"
"\n"
"1. Home Manager-ə bu yolu istifadə etməsini açıq şəkildə bildirin. Məsələn, "
"konfiqurasiyanıza aşağıdakı sətri əlavə edin:\n"
"\n"
"{ programs.home-manager.path = \"%s\"; }\n"
"\n"
" Əgər Home Manager-i birbaşa import edirsinizsə, Home Manager paketini\n"
" çağırarkən `path` parametrindən istifadə edə bilərsiniz:\n"
"\n"
" pkgs.callPackage /path/to/home-manager-package { path = \"%s\"; }\n"
"\n"
"2. Köhnəlmiş yolu silin.\n"
"\n"
" $ rm -r \"%s\""
#: home-manager/home-manager:153
msgid "Sanity checking Nix"
msgstr "Nix yoxlanılır"
#: home-manager/home-manager:173
msgid "Could not find suitable profile directory, tried %s and %s"
msgstr "Uyğun profil qovluğu tapılmadı; %s və %s yolları yoxlanıldı"
#. 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 konfiqurasiyasının seçimlərinə baxış keçirmək mümkün deyil"
#: home-manager/home-manager:340 home-manager/home-manager:363
#: home-manager/home-manager:769 home-manager/home-manager:1289
msgid "%s: unknown option '%s'"
msgstr "%s: bilinməyən seçim '%s'"
#: home-manager/home-manager:345 home-manager/home-manager:1290
msgid "Run '%s --help' for usage help"
msgstr "İstifadə köməyi üçün '%s --help' işlədin"
#: home-manager/home-manager:371 home-manager/home-manager:476
msgid "The file %s already exists, leaving it unchanged..."
msgstr "%s faylı artıq mövcuddur, dəyişdirilmədən saxlanılır..."
#: home-manager/home-manager:373 home-manager/home-manager:478
msgid "Creating %s..."
msgstr "%s yaradılır..."
#: home-manager/home-manager:522
msgid "Creating initial Home Manager generation..."
msgstr "Ilkin Home Manager nəsili yaradılır..."
#. translators: The "%s" specifier will be replaced by a file path.
#: home-manager/home-manager:527
msgid ""
"All done! The home-manager tool should now be installed and you can edit\n"
"\n"
" %s\n"
"\n"
"to configure Home Manager. Run 'man home-configuration.nix' to\n"
"see all available options."
msgstr ""
"Hər şey tamamlandı! İndi home-manager aləti quraşdırılıb və Home Manager-i "
"konfiqurasiya etmək üçün %s faylını redaktə edə bilərsiniz.\n"
"\n"
"Mövcud bütün seçimləri görmək üçün 'man home-configuration.nix' əmrini icra "
"edin."
#. translators: The "%s" specifier will be replaced by a URL.
#: home-manager/home-manager:532
msgid ""
"Uh oh, the installation failed! Please create an issue at\n"
"\n"
" %s\n"
"\n"
"if the error seems to be the fault of Home Manager."
msgstr ""
"Quraşdırma uğursuz oldu! Əgər xəta Home Manager-in səbəbindən baş veribsə, "
"%s ünvanında issue yaradın."
#. translators: Here "flake" is a noun that refers to the Nix Flakes feature.
#: home-manager/home-manager:543
msgid "Can't instantiate a flake configuration"
msgstr "Flake konfiqurasiyasını yaratmaq mümkün deyil"
#: home-manager/home-manager:619
msgid ""
"There is %d unread and relevant news item.\n"
"Read it by running the command \"%s news\"."
msgid_plural ""
"There are %d unread and relevant news items.\n"
"Read them by running the command \"%s news\"."
msgstr[0] ""
"Oxunmamış və uyğun %d xəbər var.\n"
"\n"
"Onu oxumaq üçün \"%s news\" əmrini icra edin."
msgstr[1] ""
"Oxunmamış və uyğun %d xəbər var.\n"
"\n"
"Onları oxumaq üçün \"%s news\" əmrini icra edin."
#: home-manager/home-manager:633
msgid "Unknown \"news.display\" setting \"%s\"."
msgstr "Naməlum \"news.display\" ayarı: \"%s\"."
#: home-manager/home-manager:641
#, sh-format
msgid "Please set the $EDITOR or $VISUAL environment variable"
msgstr "Zəhmət olmasa $EDITOR və ya $VISUAL mühit dəyişənini təyin edin"
#: home-manager/home-manager:659
msgid "Cannot run build in read-only directory"
msgstr "Yalnız oxuma rejimində qovluqda build icra etmək mümkün deyil"
#: home-manager/home-manager:822
msgid "The configuration does not contain the specialisation \"%s\""
msgstr "Konfiqurasiya \"%s\" specialisation-ını ehtiva etmir"
#: home-manager/home-manager:876
msgid "No generation with ID %s"
msgstr "%s ID-li nəsil mövcud deyil"
#: home-manager/home-manager:878
msgid "Cannot remove the current generation %s"
msgstr "Cari nəsli (%s) silmək mümkün deyil"
#: home-manager/home-manager:880
msgid "Removing generation %s"
msgstr "%s nəsli silinir"
#: home-manager/home-manager:901
msgid "No generations to expire"
msgstr "Müddəti bitəcək heç bir nəsil yoxdur"
#: home-manager/home-manager:912
msgid "No home-manager packages seem to be installed."
msgstr "Heç bir home-manager paketi quraşdırılmış görünmür."
#: home-manager/home-manager:997
msgid "Unknown argument %s"
msgstr "Naməlum arqument: %s"
#: home-manager/home-manager:1022
msgid "This will remove Home Manager from your system."
msgstr "Bu, Home Manager-i sisteminizdən siləcək."
#: home-manager/home-manager:1025
msgid "This is a dry run, nothing will actually be uninstalled."
msgstr "Bu sınaq icrasıdır, heç nə faktiki olaraq silinməyəcək."
#: home-manager/home-manager:1029
msgid "Really uninstall Home Manager?"
msgstr "Home Manager-i həqiqətən silmək istəyirsiniz?"
#: home-manager/home-manager:1035
msgid "Switching to empty Home Manager configuration..."
msgstr "Boş Home Manager konfiqurasiyasına keçid edilir..."
#: home-manager/home-manager:1050
msgid "Yay!"
msgstr "Əla!"
#: home-manager/home-manager:1055
msgid "Home Manager is uninstalled but your home.nix is left untouched."
msgstr "Home Manager silindi, lakin home.nix faylınız dəyişdirilmədi."
#: home-manager/home-manager:1334
msgid "expire-generations expects one argument, got %d."
msgstr "expire-generations bir arqument gözləyir, lakin %d arqument verilib."
#: home-manager/home-manager:1359
msgid "Unknown command: %s"
msgstr "Naməlum əmr: %s"
#: home-manager/install.nix:21
msgid "This derivation is not buildable, please run it using nix-shell."
msgstr ""
"Bu derivasiya build edilə bilən deyil, zəhmət olmasa onu nix-shell ilə icra "
"edin."
+5 -5
View File
@@ -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: 2026-01-21 09:53+0100\n"
"PO-Revision-Date: 2026-03-31 16:16+0000\n"
"Last-Translator: Lorenzo Bevilacqua <lorenzobevilacqua02@gmail.com>\n"
"PO-Revision-Date: 2026-07-19 08:01+0000\n"
"Last-Translator: Artur Sannikov <weblate.rbpxz@8shield.net>\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/home-manager/cli/"
"it/>\n"
"Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.17-dev\n"
"X-Generator: Weblate 2026.8.dev0\n"
#. translators: For example: "home-manager: missing argument for --cores"
#: home-manager/home-manager:16
@@ -104,7 +104,7 @@ msgstr ""
#: home-manager/home-manager:153
msgid "Sanity checking Nix"
msgstr "Controllando Nix"
msgstr "Verifica di correttezza di Nix"
#: home-manager/home-manager:173
msgid "Could not find suitable profile directory, tried %s and %s"
@@ -204,7 +204,7 @@ msgstr "Impossibile eseguire la build in una cartella in sola lettura"
#: home-manager/home-manager:822
msgid "The configuration does not contain the specialisation \"%s\""
msgstr ""
msgstr "La configurazione non contiene la specializzazione \"%s\""
#: home-manager/home-manager:876
msgid "No generation with ID %s"
+4 -4
View File
@@ -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: 2026-01-21 09:53+0100\n"
"PO-Revision-Date: 2026-03-31 16:16+0000\n"
"Last-Translator: NikSne <commits@niksne.ru>\n"
"PO-Revision-Date: 2026-07-19 08:01+0000\n"
"Last-Translator: Artur Sannikov <weblate.rbpxz@8shield.net>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/home-manager/cli/"
"ru/>\n"
"Language: ru\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Weblate 5.17-dev\n"
"X-Generator: Weblate 2026.8.dev0\n"
#. translators: For example: "home-manager: missing argument for --cores"
#: home-manager/home-manager:16
@@ -205,7 +205,7 @@ msgstr "Не могу начать сборку в директории, защ
#: home-manager/home-manager:822
msgid "The configuration does not contain the specialisation \"%s\""
msgstr ""
msgstr "В конфигурации отсутствует специализация «%s»"
#: home-manager/home-manager:876
msgid "No generation with ID %s"
+5 -4
View File
@@ -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: 2026-01-21 09:53+0100\n"
"PO-Revision-Date: 2026-05-15 00:11+0000\n"
"Last-Translator: soweli Emi <emi@alchemi.dev>\n"
"PO-Revision-Date: 2026-05-27 11:11+0000\n"
"Last-Translator: qb114514 <GNUqb114514@outlook.com>\n"
"Language-Team: Toki Pona <https://hosted.weblate.org/projects/home-manager/"
"cli/tok/>\n"
"Language: tok\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 2026.5.dev0\n"
"X-Generator: Weblate 2026.6.dev0\n"
#. translators: For example: "home-manager: missing argument for --cores"
#: home-manager/home-manager:16
@@ -185,8 +185,9 @@ msgid "Cannot run build in read-only directory"
msgstr "ma ni li ken ala ante. mi ken ala pali \"build\" lon ma ni"
#: home-manager/home-manager:822
#, fuzzy
msgid "The configuration does not contain the specialisation \"%s\""
msgstr ""
msgstr "lipu lawa jo ala lipu Specialisation \"%s\""
#: home-manager/home-manager:876
msgid "No generation with ID %s"
+6 -5
View File
@@ -8,8 +8,9 @@ msgstr ""
"Project-Id-Version: Home Manager\n"
"Report-Msgid-Bugs-To: https://github.com/nix-community/home-manager/issues\n"
"POT-Creation-Date: 2026-01-21 09:53+0100\n"
"PO-Revision-Date: 2026-03-31 16:13+0000\n"
"Last-Translator: Zexin Yuan <yuan.zx@outlook.com>\n"
"PO-Revision-Date: 2026-07-19 08:01+0000\n"
"Last-Translator: reducedradius "
"<137701630+flytothehighest@users.noreply.github.com>\n"
"Language-Team: Chinese (Simplified Han script) <https://hosted.weblate.org/"
"projects/home-manager/cli/zh_Hans/>\n"
"Language: zh_Hans\n"
@@ -17,7 +18,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.17-dev\n"
"X-Generator: Weblate 2026.8.dev0\n"
#. translators: For example: "home-manager: missing argument for --cores"
#: home-manager/home-manager:16
@@ -27,7 +28,7 @@ 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"
@@ -194,7 +195,7 @@ msgstr "无法在只读目录中运行构建"
#: home-manager/home-manager:822
msgid "The configuration does not contain the specialisation \"%s\""
msgstr ""
msgstr "配置不包含专门化的 “%s”"
#: home-manager/home-manager:876
msgid "No generation with ID %s"
+1 -34
View File
@@ -2,38 +2,5 @@
{
inherit ((import ../modules/lib/stdlib-extended.nix lib)) hm;
homeManagerConfiguration =
{
check ? true,
extraSpecialArgs ? { },
lib ? pkgs.lib,
modules ? [ ],
pkgs,
minimal ? false,
}:
import ../modules {
inherit
check
extraSpecialArgs
lib
pkgs
minimal
;
configuration = {
imports = modules ++ [
{
programs.home-manager.path = builtins.path {
path = ../.;
name = "source";
};
}
];
nixpkgs = {
config = lib.mkDefault pkgs.config;
inherit (pkgs) overlays;
};
};
};
homeManagerConfiguration = import ./eval-config.nix;
}
+33
View File
@@ -0,0 +1,33 @@
{
check ? true,
extraSpecialArgs ? { },
lib ? pkgs.lib,
modules ? [ ],
pkgs,
minimal ? false,
}:
import ../modules {
inherit
check
extraSpecialArgs
lib
pkgs
minimal
;
configuration = {
imports = modules ++ [
{
programs.home-manager.path = builtins.path {
path = ../.;
name = "source";
};
}
];
nixpkgs = {
config = lib.mkDefault pkgs.config;
inherit (pkgs) overlays;
};
};
}
+35 -20
View File
@@ -1,7 +1,14 @@
# Extract maintainers from Home Manager modules using meta.maintainers
# This script evaluates all Home Manager modules and extracts the merged maintainer information
let
pkgs = import <nixpkgs> { };
flakeLock = builtins.fromJSON (builtins.readFile ../../flake.lock);
nixpkgsNodeName = flakeLock.nodes.root.inputs.nixpkgs;
nixpkgsLocked = flakeLock.nodes.${nixpkgsNodeName}.locked;
pkgs = import (fetchTarball {
url = "https://github.com/${nixpkgsLocked.owner}/${nixpkgsLocked.repo}/archive/${nixpkgsLocked.rev}.tar.gz";
sha256 = nixpkgsLocked.narHash;
}) { };
lib = import ../../modules/lib/stdlib-extended.nix pkgs.lib;
releaseInfo = pkgs.lib.importJSON ../../release.json;
@@ -25,18 +32,23 @@ let
file:
let
fileContent = import file;
# Arguments expected by docs/home-manager-manual.nix.
evaluated =
if lib.isFunction fileContent then
fileContent {
inherit (pkgs) stdenv lib;
documentation-highlighter = { };
inherit lib;
inherit (pkgs)
callPackage
mdbook
python3
stdenv
;
revision = "unknown";
home-manager-options = {
home-manager = { };
nixos = { };
nix-darwin = { };
};
nixos-render-docs = { };
}
else
fileContent;
@@ -59,27 +71,30 @@ let
allMaintainerObjects = extractMaintainerObjects maintainers ++ additionalMaintainerObjects;
allMaintainerNames = lib.filter (name: name != null) (
map (maintainer: maintainer.github or maintainer.name or null) allMaintainerObjects
);
getMaintainerName = maintainer: maintainer.github or maintainer.name or null;
allMaintainerNames = lib.filter (name: name != null) (map getMaintainerName allMaintainerObjects);
hmMaintainers = import ../../modules/lib/maintainers.nix;
hmMaintainerNames = lib.attrNames hmMaintainers;
hmMaintainerNames = lib.filter (name: name != null) (
map getMaintainerName (lib.attrValues hmMaintainers)
);
maintainerDetails = lib.pipe allMaintainerObjects [
(lib.filter (obj: (obj.github or obj.name or null) != null))
(map (obj: {
name = obj.github or obj.name;
value = obj // {
source =
if categorizedMaintainers.home-manager ? ${obj.github} then
"home-manager"
else if categorizedMaintainers.nixpkgs ? ${obj.github} then
"nixpkgs"
else
throw "${obj.github} is neither a home-manager or nixpkgs maintainer";
};
}))
(map (
obj:
let
maintainerName = obj.github or obj.name;
in
{
name = maintainerName;
value = obj // {
source =
if categorizedMaintainers.home-manager ? ${maintainerName} then "home-manager" else "nixpkgs";
};
}
))
lib.listToAttrs
];
+32 -17
View File
@@ -17,52 +17,67 @@ def main():
print("🔍 Checking for duplicate maintainers between HM and nixpkgs...")
# Get home-manager maintainers
hm_result = subprocess.run(['nix', 'eval', '--file', 'modules/lib/maintainers.nix', '--json'],
capture_output=True, text=True, check=True)
hm_result = subprocess.run(
["nix", "eval", "--file", "modules/lib/maintainers.nix", "--json"],
capture_output=True,
text=True,
check=True,
)
hm_maintainers = json.loads(hm_result.stdout)
hm_github_users = set()
for name, data in hm_maintainers.items():
if 'github' in data:
hm_github_users.add(data['github'])
if "github" in data:
hm_github_users.add(data["github"])
# Read nixpkgs revision from flake.lock to ensure consistency between local and CI
flake_lock_path = Path('flake.lock')
with open(flake_lock_path, 'r') as f:
flake_lock_path = Path("flake.lock")
with open(flake_lock_path, "r") as f:
flake_lock = json.load(f)
nixpkgs_rev = flake_lock['nodes']['nixpkgs']['locked']['rev']
nixpkgs_rev = flake_lock["nodes"]["nixpkgs"]["locked"]["rev"]
print(f"📌 Using nixpkgs from flake.lock: {nixpkgs_rev[:7]}")
# Get nixpkgs maintainers from the locked revision
nixpkgs_result = subprocess.run(
['nix', 'eval', f'github:NixOS/nixpkgs/{nixpkgs_rev}#lib.maintainers', '--json'],
capture_output=True, text=True, check=True
[
"nix",
"eval",
f"github:NixOS/nixpkgs/{nixpkgs_rev}#lib.maintainers",
"--json",
],
capture_output=True,
text=True,
check=True,
)
nixpkgs_maintainers = json.loads(nixpkgs_result.stdout)
nixpkgs_github_users = set()
for name, data in nixpkgs_maintainers.items():
if isinstance(data, dict) and 'github' in data:
nixpkgs_github_users.add(data['github'])
if isinstance(data, dict) and "github" in data:
nixpkgs_github_users.add(data["github"])
# Find duplicates
duplicates = hm_github_users.intersection(nixpkgs_github_users)
if duplicates:
print(f'❌ Found {len(duplicates)} duplicate maintainers between HM and nixpkgs:')
print(
f"❌ Found {len(duplicates)} duplicate maintainers between HM and nixpkgs:"
)
for github_user in sorted(duplicates):
# Find the HM attribute name for this github user
hm_attr = None
for attr_name, data in hm_maintainers.items():
if data.get('github') == github_user:
if data.get("github") == github_user:
hm_attr = attr_name
break
print(f' - {github_user} (HM attribute: {hm_attr})')
print(f" - {github_user} (HM attribute: {hm_attr})")
print()
print('These maintainers should be removed from HM maintainers file to avoid duplication.')
print('They can be referenced directly from nixpkgs instead.')
print(
"These maintainers should be removed from HM maintainers file to avoid duplication."
)
print("They can be referenced directly from nixpkgs instead.")
sys.exit(1)
else:
print('✅ No duplicate maintainers found')
print("✅ No duplicate maintainers found")
if __name__ == "__main__":
+4 -1
View File
@@ -16,6 +16,7 @@ from pathlib import Path
class NixEvalError(Exception):
"""Custom exception for errors during Nix evaluation."""
pass
@@ -39,7 +40,9 @@ def run_nix_eval(nix_file: Path, *args: str) -> str:
)
return result.stdout.strip()
except FileNotFoundError:
logging.error("'nix-instantiate' command not found. Is Nix installed and in your PATH?")
logging.error(
"'nix-instantiate' command not found. Is Nix installed and in your PATH?"
)
raise NixEvalError("'nix-instantiate' not found")
except subprocess.CalledProcessError as e:
logging.error(f"Nix evaluation failed with exit code {e.returncode}")
+18 -11
View File
@@ -40,6 +40,7 @@ def get_project_root() -> Path:
# Assumes this script is at: <root>/flake/dev/generate-all-maintainers/
return Path(__file__).parent.parent.parent.parent.resolve()
class MetaMaintainerGenerator:
"""Generates maintainers list using meta.maintainers from Home Manager evaluation."""
@@ -54,9 +55,12 @@ class MetaMaintainerGenerator:
print("🔍 Extracting maintainers using meta.maintainers...")
try:
result = subprocess.run([
"nix", "eval", "--file", str(self.extractor_script), "--json"
], capture_output=True, text=True, timeout=60)
result = subprocess.run(
["nix", "eval", "--file", str(self.extractor_script), "--json"],
capture_output=True,
text=True,
timeout=60,
)
if result.returncode == 0:
data = json.loads(result.stdout)
@@ -87,7 +91,7 @@ class MetaMaintainerGenerator:
print(f"🏠 Home Manager maintainers: {len(hm_maintainers)}")
print(f"📦 Nixpkgs maintainers: {len(nixpkgs_maintainers)}")
with open(self.output_file, 'w') as f:
with open(self.output_file, "w") as f:
f.write(
inspect.cleandoc("""
# Home Manager all maintainers list.
@@ -114,9 +118,12 @@ class MetaMaintainerGenerator:
def validate_generated_file(self) -> bool:
"""Validate the generated Nix file syntax."""
try:
result = subprocess.run([
'nix-instantiate', '--eval', str(self.output_file), '--strict'
], capture_output=True, text=True, timeout=10)
result = subprocess.run(
["nix-instantiate", "--eval", str(self.output_file), "--strict"],
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0:
print("✅ Generated file has valid Nix syntax")
@@ -148,16 +155,16 @@ def main():
description="Generate Home Manager all-maintainers.nix using meta.maintainers"
)
parser.add_argument(
'--root',
"--root",
type=Path,
default=None,
help='Path to Home Manager root (default: auto-detect)'
help="Path to Home Manager root (default: auto-detect)",
)
parser.add_argument(
'--output',
"--output",
type=Path,
default=None,
help='Output file path (default: <root>/all-maintainers.nix)'
help="Output file path (default: <root>/all-maintainers.nix)",
)
args = parser.parse_args()
+108 -41
View File
@@ -60,6 +60,7 @@ query($owner: String!, $repo: String!, $prNumber: Int!) {
class GHError(Exception):
"""Custom exception for errors related to 'gh' CLI commands."""
pass
@@ -94,15 +95,28 @@ def get_manual_reviewer_actions(
tuple: (manually_requested, manually_removed) sets of usernames
"""
try:
result = run_gh_command([
"api", "graphql",
"-f", f"query={MANUAL_REVIEW_REQUEST_QUERY}",
"-F", f"owner={owner}",
"-F", f"repo={repo}",
"-F", f"prNumber={pr_number}",
])
result = run_gh_command(
[
"api",
"graphql",
"-f",
f"query={MANUAL_REVIEW_REQUEST_QUERY}",
"-F",
f"owner={owner}",
"-F",
f"repo={repo}",
"-F",
f"prNumber={pr_number}",
]
)
data = json.loads(result.stdout)
nodes = data.get("data", {}).get("repository", {}).get("pullRequest", {}).get("timelineItems", {}).get("nodes", [])
nodes = (
data.get("data", {})
.get("repository", {})
.get("pullRequest", {})
.get("timelineItems", {})
.get("nodes", [])
)
manually_requested = set()
manually_removed = set()
@@ -143,7 +157,15 @@ def get_users_from_gh(args: list[str], error_message: str) -> set[str]:
def get_pending_reviewers(pr_number: int) -> set[str]:
"""Gets the set of currently pending reviewers for a PR."""
return get_users_from_gh(
["pr", "view", str(pr_number), "--json", "reviewRequests", "--jq", ".reviewRequests[].login"],
[
"pr",
"view",
str(pr_number),
"--json",
"reviewRequests",
"--jq",
".reviewRequests[].login",
],
"Error getting pending reviewers",
)
@@ -151,7 +173,12 @@ def get_pending_reviewers(pr_number: int) -> set[str]:
def get_past_reviewers(owner: str, repo: str, pr_number: int) -> set[str]:
"""Gets the set of users who have already reviewed the PR."""
return get_users_from_gh(
["api", f"repos/{owner}/{repo}/pulls/{pr_number}/reviews", "--jq", ".[].user.login"],
[
"api",
f"repos/{owner}/{repo}/pulls/{pr_number}/reviews",
"--jq",
".[].user.login",
],
"Error getting past reviewers",
)
@@ -162,17 +189,14 @@ def is_collaborator(owner: str, repo: str, username: str) -> bool:
Handles 404 as a non-collaborator, while other errors are raised.
"""
result = run_gh_command(
["api", f"repos/{owner}/{repo}/collaborators/{username}"],
check=False
["api", f"repos/{owner}/{repo}/collaborators/{username}"], check=False
)
if result.returncode == 0:
return True
if "HTTP 404" in result.stderr:
logging.error(
"'%s' is not a collaborator in this repository.", username
)
logging.error("'%s' is not a collaborator in this repository.", username)
return False
else:
logging.error(
@@ -195,22 +219,32 @@ def update_reviewers(
if reviewers_to_add:
logging.info("Requesting reviews from: %s", ", ".join(reviewers_to_add))
try:
run_gh_command([
"pr", "edit", str(pr_number),
"--add-reviewer", ",".join(reviewers_to_add)
])
run_gh_command(
[
"pr",
"edit",
str(pr_number),
"--add-reviewer",
",".join(reviewers_to_add),
]
)
except GHError as e:
logging.error("Failed to add reviewers: %s", e)
if reviewers_to_remove and owner and repo:
logging.info("Removing review requests from: %s", ", ".join(reviewers_to_remove))
logging.info(
"Removing review requests from: %s", ", ".join(reviewers_to_remove)
)
payload = json.dumps({"reviewers": list(reviewers_to_remove)})
try:
run_gh_command(
[
"api", "--method", "DELETE",
"api",
"--method",
"DELETE",
f"repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers",
"--input", "-",
"--input",
"-",
],
input_data=payload,
)
@@ -220,15 +254,33 @@ def update_reviewers(
def main() -> None:
"""Main function to handle command-line arguments and manage reviewers."""
parser = argparse.ArgumentParser(description="Manage pull request reviewers for Home Manager.")
parser = argparse.ArgumentParser(
description="Manage pull request reviewers for Home Manager."
)
parser.add_argument("--owner", required=True, help="Repository owner.")
parser.add_argument("--repo", required=True, help="Repository name.")
parser.add_argument("--pr-number", type=int, required=True, help="Pull request number.")
parser.add_argument(
"--pr-number", type=int, required=True, help="Pull request number."
)
parser.add_argument("--pr-author", required=True, help="PR author's username.")
parser.add_argument("--current-maintainers", default="", help="Space-separated list of maintainers for the changed files.")
parser.add_argument("--changed-files", default="", help="Newline-separated list of changed files.")
parser.add_argument("--bot-user-name", default="", help="Bot user name to distinguish manual vs automated review requests.")
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without making actual changes.")
parser.add_argument(
"--current-maintainers",
default="",
help="Space-separated list of maintainers for the changed files.",
)
parser.add_argument(
"--changed-files", default="", help="Newline-separated list of changed files."
)
parser.add_argument(
"--bot-user-name",
default="",
help="Bot user name to distinguish manual vs automated review requests.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be done without making actual changes.",
)
args = parser.parse_args()
no_changed_files = not args.changed_files.strip()
@@ -237,13 +289,15 @@ def main() -> None:
maintainers: set[str] = set(args.current_maintainers.split())
pending_reviewers = get_pending_reviewers(args.pr_number)
past_reviewers = get_past_reviewers(args.owner, args.repo, args.pr_number)
manually_requested, manually_removed = get_manual_reviewer_actions(args.owner, args.repo, args.pr_number, args.bot_user_name)
manually_requested, manually_removed = get_manual_reviewer_actions(
args.owner, args.repo, args.pr_number, args.bot_user_name
)
logging.info("File Maintainers: %s", ' '.join(maintainers) or "None")
logging.info("Pending Reviewers: %s", ' '.join(pending_reviewers) or "None")
logging.info("Past Reviewers: %s", ' '.join(past_reviewers) or "None")
logging.info("Manually Requested: %s", ' '.join(manually_requested) or "None")
logging.info("Manually Removed: %s", ' '.join(manually_removed) or "None")
logging.info("File Maintainers: %s", " ".join(maintainers) or "None")
logging.info("Pending Reviewers: %s", " ".join(pending_reviewers) or "None")
logging.info("Past Reviewers: %s", " ".join(past_reviewers) or "None")
logging.info("Manually Requested: %s", " ".join(manually_requested) or "None")
logging.info("Manually Removed: %s", " ".join(manually_removed) or "None")
# --- 2. Determine reviewers to remove ---
reviewers_to_remove: set[str] = set()
@@ -257,13 +311,15 @@ def main() -> None:
if reviewers_to_remove:
if args.dry_run:
logging.info("DRY RUN: Would remove reviewers: %s", ", ".join(reviewers_to_remove))
logging.info(
"DRY RUN: Would remove reviewers: %s", ", ".join(reviewers_to_remove)
)
else:
update_reviewers(
args.pr_number,
owner=args.owner,
repo=args.repo,
reviewers_to_remove=reviewers_to_remove
reviewers_to_remove=reviewers_to_remove,
)
else:
logging.info("No reviewers to remove.")
@@ -278,20 +334,29 @@ def main() -> None:
MAX_MAINTAINERS_THRESHOLD,
)
else:
users_to_exclude = {args.pr_author} | past_reviewers | pending_reviewers | manually_removed
users_to_exclude = (
{args.pr_author} | past_reviewers | pending_reviewers | manually_removed
)
potential_reviewers = maintainers - users_to_exclude
reviewers_to_add = {
user for user in potential_reviewers if is_collaborator(args.owner, args.repo, user)
user
for user in potential_reviewers
if is_collaborator(args.owner, args.repo, user)
}
non_collaborators = potential_reviewers - reviewers_to_add
if non_collaborators:
logging.warning("Ignoring non-collaborators: %s", ", ".join(non_collaborators))
logging.warning(
"Ignoring non-collaborators: %s", ", ".join(non_collaborators)
)
manually_removed_maintainers = reviewers_to_add & manually_removed
if manually_removed_maintainers:
logging.info("Not re-adding manually removed maintainers: %s", ", ".join(manually_removed_maintainers))
logging.info(
"Not re-adding manually removed maintainers: %s",
", ".join(manually_removed_maintainers),
)
reviewers_to_add -= manually_removed
if len(reviewers_to_add) > MAX_REVIEWERS:
@@ -304,7 +369,9 @@ def main() -> None:
if reviewers_to_add:
if args.dry_run:
logging.info("DRY RUN: Would add reviewers: %s", ", ".join(reviewers_to_add))
logging.info(
"DRY RUN: Would add reviewers: %s", ", ".join(reviewers_to_add)
)
else:
update_reviewers(args.pr_number, reviewers_to_add=reviewers_to_add)
else:
+18 -12
View File
@@ -14,32 +14,38 @@ import sys
def main():
print("🔍 Validating maintainer entries...")
result = subprocess.run(['nix', 'eval', '--file', 'modules/lib/maintainers.nix', '--json'],
capture_output=True, text=True, check=True)
result = subprocess.run(
["nix", "eval", "--file", "modules/lib/maintainers.nix", "--json"],
capture_output=True,
text=True,
check=True,
)
maintainers = json.loads(result.stdout)
errors = []
for name, data in maintainers.items():
if 'github' not in data:
if "github" not in data:
errors.append(f'{name}: Missing required field "github"')
if 'githubId' not in data:
if "githubId" not in data:
errors.append(f'{name}: Missing required field "githubId"')
if 'githubId' in data:
github_id = data['githubId']
if "githubId" in data:
github_id = data["githubId"]
if not isinstance(github_id, int):
errors.append(f'{name}: githubId must be a number, not a string: {github_id} (type: {type(github_id).__name__})')
errors.append(
f"{name}: githubId must be a number, not a string: {github_id} (type: {type(github_id).__name__})"
)
elif github_id <= 0:
errors.append(f'{name}: githubId must be positive: {github_id}')
errors.append(f"{name}: githubId must be positive: {github_id}")
if errors:
print('❌ Validation errors found:')
print("❌ Validation errors found:")
for error in errors:
print(f' - {error}')
print(f" - {error}")
sys.exit(1)
else:
print('✅ All maintainer entries are valid')
print(f'✅ Validated {len(maintainers)} maintainer entries')
print("✅ All maintainer entries are valid")
print(f"✅ Validated {len(maintainers)} maintainer entries")
if __name__ == "__main__":
+6 -2
View File
@@ -13,8 +13,12 @@ def main():
print("🔍 Validating maintainers.nix syntax...")
try:
subprocess.run(['nix', 'eval', '--file', 'modules/lib/maintainers.nix', '--json'],
capture_output=True, text=True, check=True)
subprocess.run(
["nix", "eval", "--file", "modules/lib/maintainers.nix", "--json"],
capture_output=True,
text=True,
check=True,
)
print("✅ Valid Nix syntax")
except subprocess.CalledProcessError:
print("❌ Invalid Nix syntax")
+10 -74
View File
@@ -13,12 +13,10 @@ let
mkIf
mkMerge
mkDefault
mkAliasOptionModule
types
literalExpression
escapeShellArg
hm
getAttrFromPath
any
optional
;
@@ -140,46 +138,10 @@ in
{
meta.maintainers = [ lib.maintainers.league ];
imports = [
(mkAliasOptionModule
[ "xsession" "pointerCursor" "package" ]
[
"home"
"pointerCursor"
"package"
]
)
(mkAliasOptionModule
[ "xsession" "pointerCursor" "name" ]
[
"home"
"pointerCursor"
"name"
]
)
(mkAliasOptionModule
[ "xsession" "pointerCursor" "size" ]
[
"home"
"pointerCursor"
"size"
]
)
(mkAliasOptionModule
[ "xsession" "pointerCursor" "defaultCursor" ]
[
"home"
"pointerCursor"
"x11"
"defaultCursor"
]
)
];
options = {
home.pointerCursor = mkOption {
type = types.nullOr pointerCursorModule;
default = null;
type = pointerCursorModule;
default = { };
description = ''
Cursor configuration.
@@ -204,9 +166,10 @@ in
let
# Check if enable option was explicitly defined by the user
enableDefined = any (x: x ? enable) opts.definitions;
pointerCursorDefined = opts.highestPrio != (lib.mkOptionDefault { }).priority;
# Determine if cursor configuration should be enabled
enable = if enableDefined then cfg.enable else cfg != null;
enable = if enableDefined then cfg.enable else pointerCursorDefined;
in
mkMerge [
(mkIf enable (mkMerge [
@@ -240,7 +203,7 @@ in
(mkIf cfg.dotIcons.enable {
# Add symlink of cursor icon directory to $HOME/.icons, needed for
# backwards compatibility with some applications. See:
# https://specifications.freedesktop.org/icon-theme-spec/latest/ar01s03.html
# https://specifications.freedesktop.org/icon-theme/latest/#directory_layout
home.file.".icons/default/index.theme".source =
"${defaultIndexThemePackage}/share/icons/default/index.theme";
home.file.".icons/${cfg.name}".source = "${cfg.package}/share/icons/${cfg.name}";
@@ -285,39 +248,12 @@ in
]))
{
warnings =
(optional
(any
(
x:
getAttrFromPath (
[
"xsession"
"pointerCursor"
]
++ [ x ]
++ [ "isDefined" ]
) options
)
[
"package"
"name"
"size"
"defaultCursor"
]
)
''
The option `xsession.pointerCursor` has been merged into `home.pointerCursor` and will be removed
in the future. Please change to set `home.pointerCursor` directly and enable `home.pointerCursor.x11.enable`
to generate x11 specific cursor configurations. You can refer to the documentation for more details.
''
)
++ (optional (opts.highestPrio != (lib.mkOptionDefault { }).priority && cfg == null) ''
Setting home.pointerCursor to null is deprecated.
Please update your configuration to explicitly set:
warnings = optional (pointerCursorDefined && !enableDefined) ''
Relying on `home.pointerCursor` to enable cursor config generation is deprecated.
Please update your configuration to explicitly set:
home.pointerCursor.enable = false;
'');
home.pointerCursor.enable = ${lib.boolToString enable};
'';
}
];
}
+218 -67
View File
@@ -55,6 +55,8 @@ let
name = sourceName;
};
putterStatePath = "${config.xdg.stateHome}/home-manager/putter-state.json";
in
{
@@ -88,11 +90,44 @@ in
'';
};
home.fileActivator = lib.mkOption {
type =
with lib.types;
enum [
"legacy"
"putter"
];
default = "legacy";
example = "putter";
visible = false;
description = ''
The tooling to use to place files during activation.
The legacy option (currently the default) is the built-in tooling that
is very robust, but is limited in future potential.
The putter option is a new external tool that may replace the legacy
alternative in the future. It is not as hardened as the legacy
alternative but will allow future features such as file copying.
This option should be considered experimental and is therefore hidden
from documentation at this time.
'';
};
home-files = lib.mkOption {
type = lib.types.package;
internal = true;
description = "Package to contain all home files";
};
home.internal = {
filePutterConfig = lib.mkOption {
type = lib.types.package;
internal = true;
description = "Putter configuration.";
};
};
};
config = {
@@ -149,21 +184,29 @@ in
storeDir = lib.escapeShellArg builtins.storeDir;
check = pkgs.replaceVars ./files/check-link-targets.sh {
legacyCheckScript = pkgs.replaceVars ./files/check-link-targets.sh {
inherit (config.lib.bash) initHomeManagerLib;
inherit forcedPaths storeDir;
};
in
''
function checkNewGenCollision() {
local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${check} "$newGenFiles" {} +
}
checkNewGenCollision || exit 1
''
legacyCheckLinkTargets = ''
function checkNewGenCollision() {
local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${legacyCheckScript} "$newGenFiles" {} +
}
checkNewGenCollision || exit 1
'';
putterCheckLinkTargets = ''
${lib.getExe pkgs.putter} check $VERBOSE_ARG \
--state-file "${putterStatePath}" \
${config.home.internal.filePutterConfig}
'';
in
if config.home.fileActivator == "putter" then putterCheckLinkTargets else legacyCheckLinkTargets
);
# This activation script will
@@ -186,7 +229,9 @@ in
# source and target generation.
home.activation.linkGeneration = lib.hm.dag.entryAfter [ "writeBoundary" ] (
let
link = pkgs.writeShellScript "link" ''
storeDir = lib.escapeShellArg builtins.storeDir;
legacyLink = pkgs.writeShellScript "link" ''
${config.lib.bash.initHomeManagerLib}
newGenFiles="$1"
@@ -196,8 +241,8 @@ in
targetPath="$HOME/$relativePath"
if [[ -e "$targetPath" && ! -L "$targetPath" ]] ; then
if [[ -n "$HOME_MANAGER_BACKUP_COMMAND" ]] ; then
verboseEcho "Running $HOME_MANAGER_BACKUP_COMMAND $targetPath."
run $HOME_MANAGER_BACKUP_COMMAND "$targetPath" || errorEcho "Running `$HOME_MANAGER_BACKUP_COMMAND` on '$targetPath' failed."
verboseEcho "Running '$HOME_MANAGER_BACKUP_COMMAND' on '$targetPath'."
run $HOME_MANAGER_BACKUP_COMMAND "$targetPath" || errorEcho "Running '$HOME_MANAGER_BACKUP_COMMAND' on '$targetPath' failed."
elif [[ -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
# The target exists, back it up
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
@@ -220,12 +265,12 @@ in
done
'';
cleanup = pkgs.writeShellScript "cleanup" ''
legacyCleanup = pkgs.writeShellScript "cleanup" ''
${config.lib.bash.initHomeManagerLib}
# A symbolic link whose target path matches this pattern will be
# considered part of a Home Manager generation.
homeFilePattern="$(readlink -e ${lib.escapeShellArg builtins.storeDir})/*-home-manager-files/*"
homeFilePattern="$(readlink -e ${storeDir})/*-home-manager-files/*"
newGenFiles="$1"
shift 1
@@ -256,38 +301,86 @@ in
fi
done
'';
# If Putter is not enabled, then generate a fake state file to allow
# switching to Putter in the future.
putterCompatState =
let
putter = import ./lib/putter.nix { inherit lib; };
manifest = putter.mkPutterCompatState {
sourceBaseDirectory = config.home-files;
targetBaseDirectory = config.home.homeDirectory;
fileEntries = cfg;
};
in
pkgs.writeText "hm-putter-state.json" manifest;
# This activation script will
#
# 1. Remove files from the old generation that are not in the new
# generation.
#
# 2. Symlink files from the new generation into $HOME.
#
# This order is needed to ensure that we always know which links
# belong to which generation. Specifically, if we're moving from
# generation A to generation B having sets of home file links FA
# and FB, respectively then cleaning before linking produces state
# transitions similar to
#
# FA → FA ∩ FB → (FA ∩ FB) FB = FB
#
# and a failure during the intermediate state FA ∩ FB will not
# result in lost links because this set of links are in both the
# source and target generation.
legacyLinkGeneration = ''
function linkNewGen() {
_i "Creating home file links in %s" "$HOME"
local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${legacyLink} "$newGenFiles" {} +
# Copy in the Putter compatible state file. This is to allow a later
# switchover to Putter.
run install -Dp -m600 $VERBOSE_ARG ${
lib.escapeShellArgs [
putterCompatState
putterStatePath
]
}
}
function cleanOldGen() {
if [[ ! -v oldGenPath || ! -e "$oldGenPath/home-files" ]] ; then
return
fi
_i "Cleaning up orphan links from %s" "$HOME"
local newGenFiles oldGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
# Apply the cleanup script on each leaf in the old
# generation. The find command below will print the
# relative path of the entry.
find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \
| xargs -0 bash ${legacyCleanup} "$newGenFiles"
}
cleanOldGen
linkNewGen
'';
putterLinkGeneration = ''
${lib.getExe pkgs.putter} apply $VERBOSE_ARG ''${DRY_RUN:+--dry-run} \
--state-file "${putterStatePath}" \
${config.home.internal.filePutterConfig}
'';
in
''
function linkNewGen() {
_i "Creating home file links in %s" "$HOME"
local newGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
find "$newGenFiles" \( -type f -or -type l \) \
-exec bash ${link} "$newGenFiles" {} +
}
function cleanOldGen() {
if [[ ! -v oldGenPath || ! -e "$oldGenPath/home-files" ]] ; then
return
fi
_i "Cleaning up orphan links from %s" "$HOME"
local newGenFiles oldGenFiles
newGenFiles="$(readlink -e "$newGenPath/home-files")"
oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
# Apply the cleanup script on each leaf in the old
# generation. The find command below will print the
# relative path of the entry.
find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \
| xargs -0 bash ${cleanup} "$newGenFiles"
}
cleanOldGen
linkNewGen
''
if config.home.fileActivator == "putter" then putterLinkGeneration else legacyLinkGeneration
);
home.activation.checkFilesChanged = lib.hm.dag.entryBefore [ "linkGeneration" ] (
@@ -334,6 +427,18 @@ in
'') (lib.filter (v: v.onChange != "") cfg)
);
home.internal.filePutterConfig =
let
putter = import ./lib/putter.nix { inherit lib; };
manifest = putter.mkPutterManifest {
inherit putterStatePath;
sourceBaseDirectory = config.home-files;
targetBaseDirectory = config.home.homeDirectory;
fileEntries = cfg;
};
in
pkgs.writeText "hm-putter.json" manifest;
# Symlink directories and files that have the right execute bit.
# Copy files that need their execute bit changed.
home-files =
@@ -354,6 +459,63 @@ in
# contain the recursion root, not the visited files.
declare -A seenTargets
function setExecutableBit() {
local target="$1"
local executable="$2"
if [[ $executable == inherit ]]; then
# Don't change file mode if it should match the source.
:
elif [[ $executable ]]; then
chmod +x "$target"
else
chmod -x "$target"
fi
}
function insertFileEntry() {
local source="$1"
local target="$2"
local executable="$3"
local isExecutable
[[ -x $source ]] && isExecutable=1 || isExecutable=""
# Link the file into the home file directory if possible,
# i.e., if the executable bit of the source is the same we
# expect for the target. Otherwise, we copy the file and
# set the executable bit to the expected value.
if [[ $executable == inherit || $isExecutable == $executable ]]; then
ln -s "$source" "$target"
else
cp "$source" "$target"
setExecutableBit "$target" "$executable"
fi
}
function setLinkedFileExecutableBit() {
local target="$1"
local executable="$2"
local isExecutable
if [[ -d $target || ! -e $target ]]; then
return
fi
[[ -x $target ]] && isExecutable=1 || isExecutable=""
if [[ $executable == inherit || $isExecutable == $executable ]]; then
return
fi
local tmp
tmp="$(mktemp "$target.XXXXXX")"
cp "$target" "$tmp"
setExecutableBit "$tmp" "$executable"
mv -f "$tmp" "$target"
}
function insertFile() {
local source="$1"
local relTarget="$2"
@@ -409,30 +571,19 @@ in
else
lndir -silent "$source" "$target"
fi
if [[ $executable != inherit ]]; then
local linkedFile
while IFS= read -r -d "" linkedFile; do
setLinkedFileExecutableBit "$linkedFile" "$executable"
done < <(find "$target" \( -type f -or -type l \) -print0)
fi
else
ln -s "$source" "$target"
fi
else
[[ -x $source ]] && isExecutable=1 || isExecutable=""
# Link the file into the home file directory if possible,
# i.e., if the executable bit of the source is the same we
# expect for the target. Otherwise, we copy the file and
# set the executable bit to the expected value.
if [[ $executable == inherit || $isExecutable == $executable ]]; then
ln -s "$source" "$target"
else
cp "$source" "$target"
if [[ $executable == inherit ]]; then
# Don't change file mode if it should match the source.
:
elif [[ $executable ]]; then
chmod +x "$target"
else
chmod -x "$target"
fi
fi
insertFileEntry "$source" "$target" "$executable"
fi
}
''
+1 -1
View File
@@ -32,7 +32,7 @@ for sourcePath in "$@" ; do
warnEcho "Existing file '$targetPath' is in the way of '$sourcePath', will be skipped since they are the same"
elif [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_COMMAND" ]] ; then
# Next, try to run the custom backup command. Assume this always succeeds.
verboseEcho "Existing file '$targetPath' exists and differs from '$sourcePath'. `$HOME_MANAGER_BACKUP_COMMAND` will be used to backup the file."
verboseEcho "Existing file '$targetPath' exists and differs from '$sourcePath'. '$HOME_MANAGER_BACKUP_COMMAND' will be used to backup the file."
elif [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
# Next, try to move the file to a backup location if configured and possible
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
+38 -32
View File
@@ -184,38 +184,62 @@ in
options = {
home.username = mkOption {
type = types.str;
defaultText = literalExpression ''
"$USER" for state version < 20.09,
undefined for state version 20.09
type = types.nonEmptyStr;
defaultText = lib.literalMD ''
`$USER`, for state version < 20.09,
undefined, otherwise.
'';
example = "jane.doe";
description = "The user's username.";
description = ''
The user's username.
If Home Manager is installed as a NixOS or nix-darwin submodule, it is
set to `osConfig.users.users.<name>.name`.
'';
};
home.uid = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 1000;
description = "The user's uid.";
description = ''
The user's uid.
If Home Manager is installed as a NixOS or nix-darwin submodule, it is
set to `osConfig.users.users.<name>.uid`.
'';
};
home.homeDirectory = mkOption {
type = types.path;
defaultText = literalExpression ''
"$HOME" for state version < 20.09,
undefined for state version 20.09
defaultText = lib.literalMD ''
`$HOME`, for state version < 20.09
undefined, otherwise.
'';
apply = toString;
example = "/home/jane.doe";
description = "The user's home directory. Must be an absolute path.";
description = ''
The user's home directory. Must be an absolute path.
If Home Manager is installed as a NixOS or nix-darwin submodule, it is
set to `osConfig.users.users.<name>.home`.
'';
};
home.profileDirectory = mkOption {
type = types.path;
defaultText = literalExpression ''
"''${home.homeDirectory}/.nix-profile" or
"/etc/profiles/per-user/''${home.username}"
default =
if config.submoduleSupport.enable && config.submoduleSupport.externalPackageInstall then
"/etc/profiles/per-user/${cfg.username}"
else if config.nix.useXdg then
"${config.xdg.stateHome}/nix/profile"
else
cfg.homeDirectory + "/.nix-profile";
defaultText = lib.literalMD ''
- `/etc/profiles/per-user/''${home.username}`, if Home Manager is installed as
a submodule and `home-manager.useUserPackages` is enabled, else
- `''${xdg.stateHome}/nix/profile`, if `nix.useXdg` is enabled, else
- `''${home.homeDirectory}/.nix-profile`.
'';
readOnly = true;
description = ''
@@ -569,17 +593,6 @@ in
};
config = {
assertions = [
{
assertion = config.home.username != "";
message = "Username could not be determined";
}
{
assertion = config.home.homeDirectory != "";
message = "Home directory could not be determined";
}
];
warnings =
let
hmRelease = config.home.version.release;
@@ -621,14 +634,6 @@ in
lib.mkDefault (builtins.getEnv "HOME")
);
home.profileDirectory =
if config.submoduleSupport.enable && config.submoduleSupport.externalPackageInstall then
"/etc/profiles/per-user/${cfg.username}"
else if config.nix.useXdg then
"${config.xdg.stateHome}/nix/profile"
else
cfg.homeDirectory + "/.nix-profile";
programs.bash.shellAliases = cfg.shellAliases;
programs.zsh.shellAliases = cfg.shellAliases;
programs.fish.shellAliases = cfg.shellAliases;
@@ -912,6 +917,7 @@ in
--subst-var-by GENERATION_DIR $out
ln -s ${config.home-files} $out/home-files
ln -s ${config.home.internal.filePutterConfig} $out/putter.json
ln -s ${cfg.path} $out/home-path
cp "$extraDependenciesPath" "$out/extra-dependencies"
-1
View File
@@ -46,7 +46,6 @@ let
in
{
meta.maintainers = with lib.maintainers; [
da157
kranzes
];
+260 -38
View File
@@ -14,10 +14,26 @@ let
dstDir = "${config.home.homeDirectory}/Library/LaunchAgents";
launchdConfig =
{ name, ... }:
{ name, config, ... }:
{
options = {
enable = lib.mkEnableOption name;
domain = lib.mkOption {
type = lib.types.enum [
"gui"
"user"
];
default = "gui";
example = "user";
description = ''
The launchd domain to bootstrap this agent into.
The `gui` domain is appropriate for agents that need the user's
Aqua session, such as window managers, hotkey daemons, and other
graphical tools. The `user` domain is appropriate for background
services that do not require a graphical login session.
'';
};
config = lib.mkOption {
type = lib.types.submodule (import ./launchd.nix);
default = { };
@@ -39,9 +55,14 @@ let
};
};
config = {
config.Label = lib.mkDefault "${labelPrefix}${name}";
};
config = lib.mkMerge [
{
config.Label = lib.mkDefault "${labelPrefix}${name}";
}
(lib.mkIf (config.domain == "user") {
config.LimitLoadToSessionType = lib.mkDefault "Background";
})
];
};
# mutateConfig calls /bin/sh with /bin/wait4path to wait for /nix/store before
@@ -74,6 +95,13 @@ let
_n: v: lib.nameValuePair "${v.config.Label}.plist" (toAgent v.config)
) (lib.filterAttrs (_n: v: v.enable) cfg.agents);
agentDomains = lib.mapAttrs' (
_n: v:
lib.nameValuePair "${v.config.Label}.domain" (
pkgs.writeText "${v.config.Label}.domain" "${v.domain}\n"
)
) (lib.filterAttrs (_n: v: v.enable) cfg.agents);
agentsDrv = pkgs.runCommand "home-manager-agents" { } ''
mkdir -p "$out"
@@ -87,6 +115,20 @@ let
ln -s "$src" "$out/$dest"
done
'';
agentDomainsDrv = pkgs.runCommand "home-manager-agent-domains" { } ''
mkdir -p "$out"
declare -A domains
domains=(${
lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "['${name}']='${value}'") agentDomains)
})
for dest in "''${!domains[@]}"; do
src="''${domains[$dest]}"
ln -s "$src" "$out/$dest"
done
'';
in
{
meta.maintainers = with lib.maintainers; [
@@ -129,6 +171,7 @@ in
(lib.mkIf isDarwin {
home.extraBuilderCommands = ''
ln -s "${agentsDrv}" $out/LaunchAgents
ln -s "${agentDomainsDrv}" $out/LaunchAgentDomains
'';
# NOTE: Launch Agent configurations can't be symlinked from the Nix store
@@ -136,9 +179,53 @@ in
home.activation.setupLaunchAgents =
lib.hm.dag.entryAfter [ "writeBoundary" ] # Bash
''
# Get the OS version to see if we can use --wait
version="$(/usr/bin/sw_vers --productVersion | cut -d. -f 1)"
# Disable errexit to ensure we process all agents even if some fail
set +e
readAgentDomain() {
local domainsDir="$1"
local agentName="$2"
local domainFile="$domainsDir/$agentName.domain"
if [[ -n "$domainsDir" && -f "$domainFile" ]]; then
local domainName
domainName="$(<"$domainFile")"
case "$domainName" in
gui|user)
printf '%s\n' "$domainName"
;;
*)
printf 'gui\n'
;;
esac
else
printf 'gui\n'
fi
}
resolveDomain() {
local domainName="$1"
case "$domainName" in
gui)
printf 'gui/%s\n' "$UID"
;;
user)
printf 'user/%s\n' "$UID"
;;
esac
}
agentIsLoaded() {
local domain="$1"
local agentName="$2"
run /bin/launchctl print "$domain/$agentName" >/dev/null 2>&1
}
# Stop an agent if it's running
bootoutAgent() {
local domain="$1"
@@ -146,21 +233,37 @@ in
verboseEcho "Stopping agent '$domain/$agentName'..."
local bootout_output
bootout_output=$(run /bin/launchctl bootout --wait "$domain/$agentName" 2>&1) || {
# Only show warning if it's not the common "No such process" error
if [[ "$bootout_output" != *"No such process"* ]]; then
warnEcho "Failed to stop agent '$domain/$agentName': $bootout_output"
else
verboseEcho "Agent '$domain/$agentName' was not running"
if [[ "$version" -ge "26" ]]; then
if bootout_output=$(run /bin/launchctl bootout --wait "$domain/$agentName" 2>&1); then
# --wait already makes sure it was unloaded (BUT we can only use it in >=26)
return 0
fi
}
else
if bootout_output=$(run /bin/launchctl bootout "$domain/$agentName" 2>&1); then
# Otherwise on <26 give the system a moment to fully unload the agent
run sleep 1
return 0
fi
fi
# At this point we know exit code is not 0 because otherwise we
# would have returned by now
# Only show warning if it's not a common harmless error
if [[ "$bootout_output" != *"No such process"* && "$bootout_output" != *"Domain does not support specified action"* ]]; then
warnEcho "Failed to stop agent '$domain/$agentName': $bootout_output"
return 1
else
verboseEcho "Agent '$domain/$agentName' was not running"
return 2
fi
}
installAndBootstrapAgent() {
installAgentFile() {
local srcPath="$1"
local dstPath="$2"
local domain="$3"
local agentName="$4"
local agentName="$3"
verboseEcho "Installing agent file to $dstPath"
if ! run install -Dm444 -T "$srcPath" "$dstPath"; then
@@ -168,11 +271,17 @@ in
return 1
fi
return 0
}
bootstrapAgent() {
local domain="$1"
local dstPath="$2"
local agentName="$3"
verboseEcho "Starting agent '$domain/$agentName'"
local bootstrap_output
bootstrap_output=$(run /bin/launchctl bootstrap "$domain" "$dstPath" 2>&1) || {
local error_code=$?
if [[ "$bootstrap_output" == *"Bootstrap failed: 5: Input/output error"* ]]; then
errorEcho "Failed to start agent '$domain/$agentName' with I/O error (code 5)"
errorEcho "This typically happens when the agent wasn't unloaded before attempting to bootstrap the new agent."
@@ -187,42 +296,120 @@ in
return 0
}
restoreAgent() {
local oldSrcPath="$1"
local dstPath="$2"
local oldDomain="$3"
local agentName="$4"
if [[ ! -f "$oldSrcPath" ]]; then
warnEcho "Cannot restore previous agent '$oldDomain/$agentName', old file '$oldSrcPath' is missing"
return 1
fi
warnEcho "Restoring previous agent '$oldDomain/$agentName'"
installAgentFile "$oldSrcPath" "$dstPath" "$agentName" \
&& bootstrapAgent "$oldDomain" "$dstPath" "$agentName"
}
processAgent() {
local srcPath="$1"
local dstDir="$2"
local domain="$3"
local oldDir="$3"
local oldDomainsDir="$4"
local newDomainsDir="$5"
local agentFile="''${srcPath##*/}"
local agentName="''${agentFile%.plist}"
local dstPath="$dstDir/$agentFile"
local oldSrcPath=""
local oldDomainName
local newDomainName
local oldDomain
local newDomain
local oldAgentBootedOut=0
# Skip if unchanged
if cmp -s "$srcPath" "$dstPath"; then
verboseEcho "Agent '$agentName' is already up-to-date"
return 0
oldDomainName="$(readAgentDomain "$oldDomainsDir" "$agentName")"
newDomainName="$(readAgentDomain "$newDomainsDir" "$agentName")"
oldDomain="$(resolveDomain "$oldDomainName")"
newDomain="$(resolveDomain "$newDomainName")"
if [[ -n "$oldDir" ]]; then
oldSrcPath="$oldDir/$agentFile"
fi
verboseEcho "Processing agent '$agentName'"
# Skip if unchanged
if cmp -s "$srcPath" "$dstPath" && [[ "$oldDomainName" == "$newDomainName" ]]; then
if agentIsLoaded "$newDomain" "$agentName"; then
verboseEcho "Agent '$newDomain/$agentName' is already up-to-date"
return 0
else
verboseEcho "Agent '$newDomain/$agentName' is up-to-date but not loaded"
fi
fi
verboseEcho "Processing agent '$newDomain/$agentName'"
# Stop/Unload agent if it's already running
if [[ -f "$dstPath" ]]; then
bootoutAgent "$domain" "$agentName"
bootoutAgent "$oldDomain" "$agentName"
case "$?" in
0)
oldAgentBootedOut=1
;;
2)
;;
*)
return 1
;;
esac
fi
installAndBootstrapAgent "$srcPath" "$dstPath" "$domain" "$agentName"
# Note: We continue processing even if this agent fails
return 0
if [[ "$oldDomainName" != "$newDomainName" ]]; then
bootoutAgent "$newDomain" "$agentName"
case "$?" in
0|2)
;;
*)
if [[ "$oldAgentBootedOut" -eq 1 ]]; then
restoreAgent "$oldSrcPath" "$dstPath" "$oldDomain" "$agentName"
fi
return 1
;;
esac
fi
if ! installAgentFile "$srcPath" "$dstPath" "$agentName"; then
if [[ "$oldAgentBootedOut" -eq 1 ]]; then
restoreAgent "$oldSrcPath" "$dstPath" "$oldDomain" "$agentName"
fi
return 1
fi
if bootstrapAgent "$newDomain" "$dstPath" "$agentName"; then
return 0
fi
if [[ "$oldAgentBootedOut" -eq 1 ]]; then
restoreAgent "$oldSrcPath" "$dstPath" "$oldDomain" "$agentName"
fi
return 1
}
removeAgent() {
local srcPath="$1"
local dstDir="$2"
local newDir="$3"
local domain="$4"
local oldDomainsDir="$4"
local agentFile="''${srcPath##*/}"
local agentName="''${agentFile%.plist}"
local dstPath="$dstDir/$agentFile"
local domainName
local domain
domainName="$(readAgentDomain "$oldDomainsDir" "$agentName")"
domain="$(resolveDomain "$domainName")"
if [[ -e "$newDir/$agentFile" ]]; then
verboseEcho "Agent '$agentName' still exists in new generation, skipping cleanup"
@@ -241,23 +428,32 @@ in
# Stop and remove the agent
bootoutAgent "$domain" "$agentName"
case "$?" in
0|2)
;;
*)
return 1
;;
esac
verboseEcho "Removing agent file '$dstPath'"
if run rm -f $VERBOSE_ARG "$dstPath"; then
verboseEcho "Successfully removed agent file for '$agentName'"
else
warnEcho "Failed to remove agent file '$dstPath'"
return 1
fi
return 0
}
setupLaunchAgents() {
local oldDir newDir dstDir domain
local oldDir newDir oldDomainsDir newDomainsDir dstDir launchdStatus
newDir="$(readlink -m "$newGenPath/LaunchAgents")"
newDomainsDir="$(readlink -m "$newGenPath/LaunchAgentDomains")"
dstDir=${lib.escapeShellArg dstDir}
domain="gui/$UID"
launchdStatus=0
if [[ -n "''${oldGenPath:-}" ]]; then
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")"
@@ -265,34 +461,60 @@ in
verboseEcho "No previous LaunchAgents directory found"
oldDir=""
fi
oldDomainsDir="$(readlink -m "$oldGenPath/LaunchAgentDomains")"
if [[ ! -d "$oldDomainsDir" ]]; then
oldDomainsDir=""
fi
else
oldDir=""
oldDomainsDir=""
fi
verboseEcho "Setting up LaunchAgents in $dstDir"
[[ -d "$dstDir" ]] || run mkdir -p "$dstDir"
if [[ ! -d "$dstDir" ]] && ! run mkdir -p "$dstDir"; then
errorEcho "Failed to create LaunchAgents directory '$dstDir'"
return 1
fi
verboseEcho "Processing new/updated LaunchAgents..."
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
processAgent "$srcPath" "$dstDir" "$domain"
done
while IFS= read -r srcPath; do
processAgent "$srcPath" "$dstDir" "$oldDir" "$oldDomainsDir" "$newDomainsDir" \
|| launchdStatus=1
done < <(find -L "$newDir" -maxdepth 1 -name '*.plist' -type f)
# Skip cleanup if there's no previous generation
if [[ -z "$oldDir" || ! -d "$oldDir" ]]; then
if [[ "$launchdStatus" -ne 0 ]]; then
errorEcho "Failed to activate one or more LaunchAgents"
fi
verboseEcho "LaunchAgents setup complete"
return
return "$launchdStatus"
fi
verboseEcho "Cleaning up removed LaunchAgents..."
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
removeAgent "$srcPath" "$dstDir" "$newDir" "$domain"
done
while IFS= read -r srcPath; do
removeAgent "$srcPath" "$dstDir" "$newDir" "$oldDomainsDir" \
|| launchdStatus=1
done < <(find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f)
if [[ "$launchdStatus" -ne 0 ]]; then
errorEcho "Failed to activate one or more LaunchAgents"
fi
return "$launchdStatus"
}
setupLaunchAgents
launchdStatus=0
setupLaunchAgents || launchdStatus=$?
# Restore errexit
set -e
if [[ "$launchdStatus" -ne 0 ]]; then
exit "$launchdStatus"
fi
'';
})
];
+40 -24
View File
@@ -12,20 +12,32 @@
let
inherit (lib)
all
filterAttrs
attrNames
attrValues
elem
filter
head
hm
isAttrs
mapAttrs
length
tail
toposort
;
inherit (hm.dag)
empty
entriesBetween
entryAfter
entryBetween
isEntry
;
in
{
empty = { };
isEntry = e: e ? data && e ? after && e ? before;
isDag = dag: builtins.isAttrs dag && all hm.dag.isEntry (builtins.attrValues dag);
isDag = dag: isAttrs dag && all isEntry (attrValues dag);
# Takes an attribute set containing entries built by entryAnywhere,
# entryAfter, and entryBefore to a topologically sorted list of
@@ -84,16 +96,19 @@ in
# }
# true
topoSort =
let
before = a: b: elem a.name b.after;
in
dag:
let
dagBefore = dag: name: builtins.attrNames (filterAttrs (_n: v: builtins.elem name v.before) dag);
names = attrNames dag;
dagBefore = name: filter (n: elem name dag.${n}.before) names;
normalizedDag = mapAttrs (n: v: {
name = n;
inherit (v) data;
after = v.after ++ dagBefore dag n;
after = v.after ++ dagBefore n;
}) dag;
before = a: b: builtins.elem a.name b.after;
sorted = toposort before (builtins.attrValues normalizedDag);
sorted = toposort before (attrValues normalizedDag);
in
if sorted ? result then
{
@@ -108,10 +123,10 @@ in
entryBetween = before: after: data: { inherit data before after; };
# Create a DAG entry with no particular dependency information.
entryAnywhere = hm.dag.entryBetween [ ] [ ];
entryAnywhere = entryBetween [ ] [ ];
entryAfter = hm.dag.entryBetween [ ];
entryBefore = before: hm.dag.entryBetween before [ ];
entryAfter = entryBetween [ ];
entryBefore = before: entryBetween before [ ];
# Given a list of entries, this function places them in order within the DAG.
# Each entry is labeled "${tag}-${entry index}" and other DAG entries can be
@@ -124,24 +139,25 @@ in
let
go =
i: before: after: entries:
let
name = "${tag}-${toString i}";
in
if entries == [ ] then
hm.dag.empty
else if length entries == 1 then
{
"${name}" = hm.dag.entryBetween before after (head entries);
}
empty
else
{
"${name}" = hm.dag.entryAfter after (head entries);
}
// go (i + 1) before [ name ] (tail entries);
let
name = "${tag}-${toString i}";
in
if length entries == 1 then
{
"${name}" = entryBetween before after (head entries);
}
else
{
"${name}" = entryAfter after (head entries);
}
// go (i + 1) before [ name ] (tail entries);
in
go 0;
entriesAnywhere = tag: hm.dag.entriesBetween tag [ ] [ ];
entriesAfter = tag: hm.dag.entriesBetween tag [ ];
entriesBefore = tag: before: hm.dag.entriesBetween tag before [ ];
entriesAnywhere = tag: entriesBetween tag [ ] [ ];
entriesAfter = tag: entriesBetween tag [ ];
entriesBefore = tag: before: entriesBetween tag before [ ];
}
+1
View File
@@ -12,6 +12,7 @@ rec {
git = import ./git.nix { inherit lib; };
gvariant = import ./gvariant.nix { inherit lib; };
maintainers = import ./maintainers.nix;
mcp = import ./mcp.nix { inherit lib; };
strings = import ./strings.nix { inherit lib; };
types = import ./types.nix { inherit gvariant lib; };
+42
View File
@@ -1,5 +1,47 @@
{ lib }:
{
/*
Builds a standard warning for an option value shape that is deprecated.
Example:
mkDeprecatedOptionValueWarning {
option = [ "programs" "example" "settings" ];
old = "a list";
replacement = "`programs.example.settings.items`";
}
=> Using `programs.example.settings` as a list is deprecated and will be
removed in a future release. Please use `programs.example.settings.items`
instead.
*/
mkDeprecatedOptionValueWarning =
{
option,
old,
replacement,
details ? "",
}:
''
Using `${lib.showOption option}` as ${old} is deprecated and will be
removed in a future release. Please use ${replacement} instead.
''
+ lib.optionalString (details != "") ''
${details}
'';
# Builds a standard warning for an option value that has been renamed.
mkDeprecatedOptionValueRenameWarning =
{
option,
old,
replacement,
}:
''
The value ${old} for `${lib.showOption option}` is deprecated and will be
removed in a future release. Please use ${replacement} instead.
'';
/*
Returns a function that maps
[
+4 -2
View File
@@ -15,6 +15,8 @@ let
removePrefix
types
;
isAbsolutePath = hasPrefix "/";
removeHomePrefix = removePrefix (homeDirectory + "/");
in
{
# Constructs a type suitable for a `home.file` like option. The
@@ -46,9 +48,9 @@ in
apply =
p:
let
absPath = if hasPrefix "/" p then p else "${basePath}/${p}";
absPath = if isAbsolutePath p then p else "${basePath}/${p}";
in
removePrefix (homeDirectory + "/") absPath;
removeHomePrefix absPath;
defaultText = literalExpression "name";
description = ''
Path to target file relative to ${basePathDesc}.
+699 -5
View File
@@ -1,6 +1,684 @@
{ lib }:
let
isDAGEntryWithOrdering' =
entry: lib.hm.dag.isEntry entry && (entry.after != [ ] || entry.before != [ ]);
hasDAGEntryWithOrdering' = attrs: lib.any isDAGEntryWithOrdering' (lib.attrValues attrs);
sortDAGEntries' =
{
cycleErrorMessage ? "Dependency cycle in DAG entries",
}:
attrs:
let
dag = lib.mapAttrs (
_: entry: if lib.hm.dag.isEntry entry then entry else lib.hm.dag.entryAnywhere entry
) attrs;
sortedDag = lib.hm.dag.topoSort dag;
entries = sortedDag.result or (abort "${cycleErrorMessage}: ${builtins.toJSON sortedDag}");
in
map (entry: {
inherit (entry) name;
value = entry.data;
}) entries;
# Freeform generators can use this to support DAG ordering while preserving
# entryAnywhere-shaped plain values beside ordered DAG entries.
toDAGOrderedAttrs' =
{
cycleErrorMessage ? "Dependency cycle in DAG entries",
}:
attrs:
if hasDAGEntryWithOrdering' attrs then
sortDAGEntries' { inherit cycleErrorMessage; } (
lib.mapAttrs (
_: entry: if isDAGEntryWithOrdering' entry then entry else lib.hm.dag.entryAnywhere entry
) attrs
)
else
lib.mapAttrsToList (name: value: { inherit name value; }) attrs;
toDAGOrderedText' =
{
renderAttrs,
renderList,
renderValue,
cycleErrorMessage ? "Dependency cycle in DAG entries",
}:
let
render =
value:
if lib.isDerivation value || builtins.isPath value then
renderValue value
else if builtins.isAttrs value then
renderAttrs (
map (entry: entry // { value = render entry.value; }) (
toDAGOrderedAttrs' { inherit cycleErrorMessage; } value
)
)
else if builtins.isList value then
renderList (map render value)
else
renderValue value;
in
render;
toDAGOrderedJsonText' =
{
cycleErrorMessage ? "Dependency cycle in DAG entries",
}:
toDAGOrderedText' {
inherit cycleErrorMessage;
renderValue = builtins.toJSON;
renderList = values: "[${lib.concatStringsSep "," values}]";
renderAttrs =
attrs:
let
renderAttr = entry: "${builtins.toJSON entry.name}:${entry.value}";
in
"{${lib.concatMapStringsSep "," renderAttr attrs}}";
};
toDAGOrderedKeyValue' =
{
mkKeyValue ? lib.generators.mkKeyValueDefault { } "=",
listsAsDuplicateKeys ? false,
listToValue ? null,
indent ? "",
cycleErrorMessage ? "Dependency cycle in DAG entries",
}:
assert listsAsDuplicateKeys -> listToValue == null;
attrs:
let
normalizeValue =
value: if listToValue != null && builtins.isList value then listToValue value else value;
mkLine = name: value: indent + mkKeyValue name value + "\n";
mkLines =
if listsAsDuplicateKeys then
name: value: map (mkLine name) (if builtins.isList value then value else [ value ])
else
name: value: [ (mkLine name (normalizeValue value)) ];
in
lib.concatStrings (
lib.concatMap (entry: mkLines entry.name entry.value) (
toDAGOrderedAttrs' { inherit cycleErrorMessage; } attrs
)
);
toDAGOrderedINI' =
{
mkSectionName ? (name: lib.escape [ "[" "]" ] name),
mkKeyValue ? lib.generators.mkKeyValueDefault { } "=",
listsAsDuplicateKeys ? false,
listToValue ? null,
cycleErrorMessage ? "Dependency cycle in INI sections",
}:
assert listsAsDuplicateKeys -> listToValue == null;
attrsOfAttrs:
let
mkSection =
section:
''
[${mkSectionName section.name}]
''
+ toDAGOrderedKeyValue' {
inherit
cycleErrorMessage
listToValue
listsAsDuplicateKeys
mkKeyValue
;
} section.value;
in
lib.concatStringsSep "\n" (
map mkSection (toDAGOrderedAttrs' { inherit cycleErrorMessage; } attrsOfAttrs)
);
mkDAGOrderedFormat' =
{
pkgs,
format,
generator,
nativeBuildInputs ? [ ],
buildCommand ? ''
cp "$valuePath" "$out"
'',
cycleErrorMessage ? null,
}:
format
// {
generate =
name: value:
pkgs.runCommandLocal name {
inherit nativeBuildInputs;
value = generator {
cycleErrorMessage =
if cycleErrorMessage == null then "Dependency cycle in ${name}" else cycleErrorMessage;
} value;
# Nix does not populate $valuePath when __structuredAttrs is true.
passAsFile = [ "value" ];
} buildCommand;
};
in
{
/**
Returns whether a value is a Home Manager DAG entry with ordering metadata.
# Inputs
`entry`
: 1\. Function argument
# Type
```
isDAGEntryWithOrdering :: Any -> Bool
```
*/
isDAGEntryWithOrdering = isDAGEntryWithOrdering';
/**
Returns whether an attribute set contains any Home Manager DAG entry with
ordering metadata.
# Inputs
`attrs`
: 1\. Function argument
# Type
```
hasDAGEntryWithOrdering :: AttrSet -> Bool
```
*/
hasDAGEntryWithOrdering = hasDAGEntryWithOrdering';
/**
Topologically sort an attribute set that contains Home Manager DAG entries.
Plain sibling values are treated as unordered DAG entries.
# Inputs
`options`
: Function options
`cycleErrorMessage` (string; optional)
: Message prefix to use when a dependency cycle is detected.
`attrs`
: Attribute set to sort
# Type
```
sortDAGEntries :: { cycleErrorMessage ? String } -> AttrSet -> [ { name :: String; value :: Any; } ]
```
# Examples
:::{.example}
## `lib.hm.generators.sortDAGEntries` usage example
```nix
lib.hm.generators.sortDAGEntries { } {
after = lib.hm.dag.entryAfter [ "before" ] "2";
before = "1";
}
=> [
{ name = "before"; value = "1"; }
{ name = "after"; value = "2"; }
]
```
:::
*/
sortDAGEntries = sortDAGEntries';
/**
Convert an attribute set to a list of name/value pairs, using topological
ordering when any value is a Home Manager DAG entry with non-empty ordering
metadata.
This is intended for freeform generators that should support optional DAG
ordering while preserving entryAnywhere-shaped plain values.
# Inputs
`options`
: Function options
`cycleErrorMessage` (string; optional)
: Message prefix to use when a dependency cycle is detected.
`attrs`
: Attribute set to convert
# Type
```
toDAGOrderedAttrs :: { cycleErrorMessage ? String } -> AttrSet -> [ { name :: String; value :: Any; } ]
```
*/
toDAGOrderedAttrs = toDAGOrderedAttrs';
/**
Render Nix values to text using caller-provided render functions while
preserving ordering from nested Home Manager DAG entries.
# Inputs
`options`
: Function options
`renderAttrs` (function)
: Render ordered attribute entries. Receives a list of
`{ name, value }` entries where `value` is already rendered text.
`renderList` (function)
: Render list values. Receives rendered element strings.
`renderValue` (function)
: Render scalar values, paths, and derivations.
`cycleErrorMessage` (string; optional)
: Message prefix to use when a dependency cycle is detected.
`value`
: Value to render
# Type
```
toDAGOrderedText :: { renderAttrs :: Function; renderList :: Function; renderValue :: Function; cycleErrorMessage ? String; } -> Any -> String
```
*/
toDAGOrderedText = toDAGOrderedText';
/**
Generate a key-value-style config file from an attribute set, preserving
ordering from Home Manager DAG entries.
# Inputs
`options`
: Function options
`mkKeyValue` (function; optional)
: Format a setting line from name and value.
`listsAsDuplicateKeys` (boolean; optional)
: Render list values as duplicate keys.
`listToValue` (function or null; optional)
: Convert list values to scalar values before rendering.
`indent` (string; optional)
: Initial indentation level.
`cycleErrorMessage` (string; optional)
: Message prefix to use when a dependency cycle is detected.
`attrs`
: Attribute set to render
# Type
```
toDAGOrderedKeyValue :: { mkKeyValue ? Function; listsAsDuplicateKeys ? Bool; listToValue ? NullOr Function; indent ? String; cycleErrorMessage ? String; } -> AttrSet -> String
```
*/
toDAGOrderedKeyValue = toDAGOrderedKeyValue';
/**
Generate an INI-style config file from an attribute set of sections while
preserving ordering from Home Manager DAG entries.
# Inputs
`options`
: Function options
`mkSectionName` (function; optional)
: Format a section name.
`mkKeyValue` (function; optional)
: Format a setting line from name and value.
`listsAsDuplicateKeys` (boolean; optional)
: Render list values as duplicate keys.
`listToValue` (function or null; optional)
: Convert list values to scalar values before rendering.
`cycleErrorMessage` (string; optional)
: Message prefix to use when a dependency cycle is detected.
`attrsOfAttrs`
: Attribute set of sections to render
# Type
```
toDAGOrderedINI :: { mkSectionName ? Function; mkKeyValue ? Function; listsAsDuplicateKeys ? Bool; listToValue ? NullOr Function; cycleErrorMessage ? String; } -> AttrSet -> String
```
*/
toDAGOrderedINI = toDAGOrderedINI';
/**
Wrap a `pkgs.formats` format so its `generate` function can render
Home Manager DAG entries in order.
# Inputs
`options`
: Function options
`pkgs` (attribute set)
: Package set used to create the generated derivation.
`format` (attribute set)
: Existing format value, for example `pkgs.formats.json { }`.
`generator` (function)
: Text renderer. Receives `{ cycleErrorMessage }` and returns a function
from value to rendered text.
`nativeBuildInputs` (list; optional)
: Build inputs needed by `buildCommand`.
`buildCommand` (string; optional)
: Builder script. Defaults to copying rendered text to `$out`.
`cycleErrorMessage` (string or null; optional)
: Message prefix to use when a dependency cycle is detected. When `null`,
the generated file name is used.
# Type
```
mkDAGOrderedFormat :: { pkgs :: AttrSet; format :: AttrSet; generator :: Function; nativeBuildInputs ? [Derivation]; buildCommand ? String; cycleErrorMessage ? NullOr String; } -> AttrSet
```
*/
mkDAGOrderedFormat = mkDAGOrderedFormat';
/**
Create a JSON format whose `generate` function renders nested ordered Home
Manager DAG entries in order.
# Inputs
`options`
: Function options
`pkgs` (attribute set)
: Package set used to create the generated derivation.
`jsonFormat` (attribute set; optional)
: Existing JSON format value. Defaults to `pkgs.formats.json { }`.
`cycleErrorMessage` (string or null; optional)
: Message prefix to use when a dependency cycle is detected. When `null`,
the generated file name is used.
# Type
```
mkDAGOrderedJsonFormat :: { pkgs :: AttrSet; jsonFormat ? AttrSet; cycleErrorMessage ? NullOr String; } -> AttrSet
```
*/
mkDAGOrderedJsonFormat =
{
pkgs,
jsonFormat ? pkgs.formats.json { },
cycleErrorMessage ? null,
}:
mkDAGOrderedFormat' {
inherit
pkgs
cycleErrorMessage
;
format = jsonFormat;
generator = { cycleErrorMessage }: toDAGOrderedJsonText' { inherit cycleErrorMessage; };
nativeBuildInputs = [ pkgs.buildPackages.jq ];
buildCommand = ''
jq . "$valuePath" > "$out"
'';
};
/**
Create a YAML format whose `generate` function renders nested ordered Home
Manager DAG entries in order.
# Inputs
`options`
: Function options
`pkgs` (attribute set)
: Package set used to create the generated derivation.
`yamlFormat` (attribute set; optional)
: Existing YAML format value. Defaults to `pkgs.formats.yaml { }`.
`cycleErrorMessage` (string or null; optional)
: Message prefix to use when a dependency cycle is detected. When `null`,
the generated file name is used.
# Type
```
mkDAGOrderedYamlFormat :: { pkgs :: AttrSet; yamlFormat ? AttrSet; cycleErrorMessage ? NullOr String; } -> AttrSet
```
*/
mkDAGOrderedYamlFormat =
{
pkgs,
yamlFormat ? pkgs.formats.yaml { },
cycleErrorMessage ? null,
}:
mkDAGOrderedFormat' {
inherit
pkgs
cycleErrorMessage
;
format = yamlFormat;
generator = { cycleErrorMessage }: toDAGOrderedJsonText' { inherit cycleErrorMessage; };
nativeBuildInputs = [ pkgs.buildPackages.remarshal ];
buildCommand = ''
json2yaml "$valuePath" "$out"
'';
};
/**
Create a TOML format whose `generate` function renders nested ordered Home
Manager DAG entries in order.
# Inputs
`options`
: Function options
`pkgs` (attribute set)
: Package set used to create the generated derivation.
`tomlFormat` (attribute set; optional)
: Existing TOML format value. Defaults to `pkgs.formats.toml { }`.
`cycleErrorMessage` (string or null; optional)
: Message prefix to use when a dependency cycle is detected. When `null`,
the generated file name is used.
# Type
```
mkDAGOrderedTomlFormat :: { pkgs :: AttrSet; tomlFormat ? AttrSet; cycleErrorMessage ? NullOr String; } -> AttrSet
```
*/
mkDAGOrderedTomlFormat =
{
pkgs,
tomlFormat ? pkgs.formats.toml { },
cycleErrorMessage ? null,
}:
mkDAGOrderedFormat' {
inherit
pkgs
cycleErrorMessage
;
format = tomlFormat;
generator = { cycleErrorMessage }: toDAGOrderedJsonText' { inherit cycleErrorMessage; };
nativeBuildInputs = [ pkgs.buildPackages.remarshal ];
buildCommand = ''
json2toml "$valuePath" "$out"
'';
};
/**
Create an INI format whose `generate` function renders ordered Home Manager
DAG entries in order.
# Inputs
`options`
: Function options
`pkgs` (attribute set)
: Package set used to create the generated derivation.
`iniFormat` (attribute set; optional)
: Existing INI format value. Defaults to `pkgs.formats.ini { ... }`.
`listsAsDuplicateKeys`, `listToValue`, `atomsCoercedToLists`
: Format options matching `pkgs.formats.ini`.
`mkSectionName`, `mkKeyValue`
: Format options for rendering section and setting lines.
`cycleErrorMessage` (string or null; optional)
: Message prefix to use when a dependency cycle is detected. When `null`,
the generated file name is used.
# Type
```
mkDAGOrderedIniFormat :: { pkgs :: AttrSet; iniFormat ? AttrSet; mkSectionName ? Function; mkKeyValue ? Function; listsAsDuplicateKeys ? Bool; listToValue ? NullOr Function; atomsCoercedToLists ? NullOr Bool; cycleErrorMessage ? NullOr String; } -> AttrSet
```
*/
mkDAGOrderedIniFormat =
{
pkgs,
mkSectionName ? (name: lib.escape [ "[" "]" ] name),
mkKeyValue ? lib.generators.mkKeyValueDefault { } "=",
listsAsDuplicateKeys ? false,
listToValue ? null,
atomsCoercedToLists ? null,
iniFormat ? pkgs.formats.ini {
inherit
atomsCoercedToLists
listsAsDuplicateKeys
listToValue
;
},
cycleErrorMessage ? null,
}:
assert listsAsDuplicateKeys -> listToValue == null;
mkDAGOrderedFormat' {
inherit
pkgs
cycleErrorMessage
;
format = iniFormat;
generator =
{ cycleErrorMessage }:
toDAGOrderedINI' {
inherit
cycleErrorMessage
listToValue
listsAsDuplicateKeys
mkKeyValue
mkSectionName
;
};
};
/**
Create a key-value format whose `generate` function renders ordered Home
Manager DAG entries in order.
# Inputs
`options`
: Function options
`pkgs` (attribute set)
: Package set used to create the generated derivation.
`keyValueFormat` (attribute set; optional)
: Existing key-value format value. Defaults to `pkgs.formats.keyValue`.
`listsAsDuplicateKeys`, `listToValue`
: Format options matching `pkgs.formats.keyValue`.
`mkKeyValue`
: Format option for rendering setting lines.
`cycleErrorMessage` (string or null; optional)
: Message prefix to use when a dependency cycle is detected. When `null`,
the generated file name is used.
# Type
```
mkDAGOrderedKeyValueFormat :: { pkgs :: AttrSet; keyValueFormat ? AttrSet; mkKeyValue ? Function; listsAsDuplicateKeys ? Bool; listToValue ? NullOr Function; cycleErrorMessage ? NullOr String; } -> AttrSet
```
*/
mkDAGOrderedKeyValueFormat =
{
pkgs,
mkKeyValue ? lib.generators.mkKeyValueDefault { } "=",
listsAsDuplicateKeys ? false,
listToValue ? null,
keyValueFormat ? pkgs.formats.keyValue {
inherit listsAsDuplicateKeys listToValue;
},
cycleErrorMessage ? null,
}:
assert listsAsDuplicateKeys -> listToValue == null;
mkDAGOrderedFormat' {
inherit
pkgs
cycleErrorMessage
;
format = keyValueFormat;
generator =
{ cycleErrorMessage }:
toDAGOrderedKeyValue' {
inherit
cycleErrorMessage
listToValue
listsAsDuplicateKeys
mkKeyValue
;
};
};
toHyprconf =
{
attrs,
@@ -10,11 +688,11 @@
let
inherit (lib)
all
any
concatMapStringsSep
concatStrings
concatStringsSep
filterAttrs
foldl
generators
hasPrefix
isAttrs
@@ -29,8 +707,7 @@
toHyprconf' =
indent: attrs:
let
isImportantField =
n: _: foldl (acc: prev: if hasPrefix prev n then true else acc) false importantPrefixes;
isImportantField = n: _: any (prev: hasPrefix prev n) importantPrefixes;
importantFields = filterAttrs isImportantField attrs;
withoutImportantFields = fields: removeAttrs fields (attrNames importantFields);
@@ -67,7 +744,9 @@
toHyprconf' initialIndent attrs;
toKDL =
_:
{
escapeBackslashes ? false,
}:
let
inherit (lib)
concatStringsSep
@@ -89,7 +768,22 @@
stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines));
# String -> String
sanitizeString = replaceStrings [ "\n" ''"'' ] [ "\\n" ''\"'' ];
sanitizeString =
replaceStrings
(
[
"\n"
''"''
]
++ (lib.optional escapeBackslashes "\\")
)
(
[
"\\n"
''\"''
]
++ (lib.optional escapeBackslashes "\\\\")
);
# OneOf [Int Float String Bool Null] -> String
literalValueToString =
+43 -19
View File
@@ -7,30 +7,18 @@
# [1] https://github.com/NixOS/nixpkgs/blob/737449404589e4a80b3fa99ecbc6d4d803c1f6dc/maintainers/maintainer-list.nix#LC1
{
# keep-sorted start case=no numeric=no block=yes
"3ulalia" = {
_3ulalia = {
name = "Eulalia del Sol";
email = "3ulalia@proton.me";
github = "3ulalia";
githubId = 179992797;
};
"74k1" = {
name = "Tim";
email = "git.t@betsumei.com";
github = "74k1";
githubId = 49000471;
};
"9p4" = {
_9p4 = {
name = "9p4";
email = "vcs@ersei.net";
github = "9p4";
githubId = 17993169;
};
"will-lol" = {
name = "William Bradshaw";
email = "will.bradshaw50@gmail.com";
github = "will-lol";
githubId = 65345408;
};
aabccd021 = {
name = "Muhamad Abdurahman";
email = "aabccd021@gmail.com";
@@ -91,6 +79,12 @@
github = "b1kku";
githubId = 77858854;
};
bittner = {
name = "Peter Bittner";
email = "peter@painless.software";
github = "bittner";
githubId = 665072;
};
bjpbakker = {
email = "bart@thesoftwarecraft.com";
github = "bjpbakker";
@@ -115,6 +109,12 @@
github = "ckgxrg-salt";
githubId = 165614491;
};
csanthiago = {
name = "csanthiago";
email = "git@csanthiago.dev";
github = "csanthiago";
githubId = 8346803;
};
d-dervishi = {
email = "david.dervishi@epfl.ch";
github = "d-dervishi";
@@ -175,6 +175,12 @@
github = "epixtm";
githubId = 168025607;
};
ErinaYip = {
name = "erina";
email = "erinayip@outlook.com";
github = "ErinaYip";
githubId = 227132255;
};
exzombie = {
name = "Jure Varlec";
email = "jure@varlec.si";
@@ -302,6 +308,12 @@
github = "kalhauge";
githubId = 1182166;
};
kayskayskays = {
name = "Kays";
email = "115312476+kayskayskays@users.noreply.github.com";
github = "kayskayskays";
githubId = 115312476;
};
kmaasrud = {
name = "Knut Magnus Aasrud";
email = "km@aasrud.com";
@@ -505,6 +517,12 @@
}
];
};
reesilva = {
name = "Renato da Silva";
email = "reesilva@proton.me";
github = "ReeSilva";
githubId = 3605032;
};
rosuavio = {
name = "Rosario Pulella";
email = "RosarioPulella@gmail.com";
@@ -540,11 +558,17 @@
github = "SunOfLife1";
githubId = 30405063;
};
yarn = {
name = "yarncat";
email = "30006414+yaaaarn@users.noreply.github.com";
github = "yaaaarn";
githubId = 30006414;
superflash41 = {
name = "Saymon Nicho";
email = "saymon.nicho@pucp.edu.pe";
github = "superflash41";
githubId = 102434258;
};
will-lol = {
name = "William Bradshaw";
email = "will.bradshaw50@gmail.com";
github = "will-lol";
githubId = 65345408;
};
zorrobert = {
name = "zorrobert";
+210
View File
@@ -0,0 +1,210 @@
{ lib }:
let
/*
Checks whether a value is a file reference submodule.
A file reference has the shape `{ file = "/path/to/file"; }` and is used
to indicate that an environment variable's value should be read from a file
at runtime (e.g. via sops-nix or systemd credentials).
Type: isFileRef :: Value -> Bool
*/
isFileRef = value: lib.isAttrs value && value ? file;
/*
Filters an `env` attrset to contain only literal (non-file-ref) values.
This is useful when a transform needs to produce two separate outputs:
one with the literal values and one with the file paths (for wrapping).
Type: literalEnv :: AttrSet -> AttrSet
Example:
literalEnv { API_KEY = "secret"; TOKEN.file = "/run/secrets/token"; }
=> { API_KEY = "secret"; }
*/
literalEnv = env: lib.filterAttrs (_: value: !isFileRef value) env;
/*
Extracts only the file paths from file-ref entries in an `env` attrset.
Returns a flat `attrsOf str` mapping variable names to their file paths.
Non-file-ref entries are excluded.
Type: fileRefEnv :: AttrSet -> AttrSet
Example:
fileRefEnv { API_KEY = "secret"; TOKEN.file = "/run/secrets/token"; }
=> { TOKEN = "/run/secrets/token"; }
*/
fileRefEnv = env: lib.mapAttrs (_: value: value.file) (lib.filterAttrs (_: isFileRef) env);
/*
Render an `env` attrset (literals + file refs) as a flat `attrsOf str`
by mapping file refs through `mkFileRef path` and leaving literals untouched.
Type: renderEnv :: (String -> String) -> AttrSet -> AttrSet
Example:
renderEnv (path: "{file:${path}}") {
API_KEY = "literal";
SESSION_TOKEN.file = "/run/secrets/token";
}
=> { API_KEY = "literal"; SESSION_TOKEN = "{file:/run/secrets/token}"; }
*/
renderEnv =
mkFileRef: env:
lib.mapAttrs (_: value: if isFileRef value then mkFileRef value.file else value) env;
/*
Wrap a local MCP server command in a shell script that reads file-backed
env values into the environment before executing the original process.
Compatible with file-based secret managers such as sops-nix or systemd
credentials. Reads file refs from `server.env`.
Type: mkEnvFilesWrapper :: { pkgs, name, server } -> Derivation
*/
mkEnvFilesWrapper =
{
pkgs,
name,
server,
}:
let
files = fileRefEnv (server.env or { });
in
pkgs.writeShellScript "mcp-${name}-wrapper" ''
${lib.concatStrings (
lib.mapAttrsToList (var: path: ''
if ${var}=$(cat ${lib.escapeShellArg path}); then
export ${var}
else
printf '[${name} wrapper ] Failed to read env var %s from %s\n' \
${lib.escapeShellArg var} \
${lib.escapeShellArg path} >&2
fi
'') files
)}
exec ${lib.escapeShellArgs ([ server.command ] ++ (server.args or [ ]))}
'';
/*
extraTransform factory: when the server has file refs in `env` and a
local `command`, replace `command` with a wrapper script that reads
those files at startup, reset `args` to `[ ]`, and drop the file refs
from `env` (leaving only literal entries).
When there is nothing to wrap, returns the server unchanged.
Type: wrapEnvFilesCommand :: { pkgs, name } -> AttrSet -> AttrSet
*/
wrapEnvFilesCommand =
{ pkgs, name }:
server:
let
files = fileRefEnv (server.env or { });
needsWrapping = files != { };
in
server
// lib.optionalAttrs needsWrapping {
command = mkEnvFilesWrapper { inherit pkgs name server; };
args = [ ];
env = literalEnv (server.env or { });
};
/*
Resolve the effective `enabled` state from a server config that may
have either an `enabled` or a `disabled` field (but not both).
Returns `null` if neither field is present, allowing callers to omit
the attribute from the output via the empty-value filter.
Type: resolveEnabled :: AttrSet -> Null | Bool
*/
resolveEnabled =
server:
let
hasEnabled = server ? enabled && server.enabled != null;
hasDisabled = server ? disabled && server.disabled != null;
in
if hasEnabled then
server.enabled
else if hasDisabled then
!server.disabled
else
null;
/*
extraTransform that adds `type = "stdio" | "http"` based on whether the
server has a `url`.
Type: addType :: AttrSet -> AttrSet
*/
addType =
server:
if server ? type then
server
else
server
// {
type = if server ? url && server.url != null then "http" else "stdio";
};
in
{
inherit
renderEnv
mkEnvFilesWrapper
wrapEnvFilesCommand
addType
;
/*
Normalise an MCP server attribute set for consumption by a client
program. Performs only universal steps:
1. Resolve `enabled` from optional `enabled`/`disabled` fields.
2. Apply each function in `extraTransforms` to the server attrs, in
order. Transforms run before `mkFileRef`, `exclude` and the
empty-value filter, so a transform can still read attrs that will
be excluded and operate on the raw env shape (with file refs).
3. Render any remaining file refs in `env` through `mkFileRef path`.
Callers that consume file refs themselves (e.g. via
`wrapEnvFilesCommand`) will have stripped them in an
extraTransform, so `mkFileRef` is a no-op for them.
4. Remove `disabled` and any keys listed in `exclude`.
5. Filter `null`, `[]`, and `{}` values.
Type: transformMcpServer ::
{ server, exclude?, extraTransforms?, mkFileRef? }
-> AttrSet
*/
transformMcpServer =
{
server,
exclude ? [ ],
extraTransforms ? [ ],
mkFileRef ? (path: "{file:${path}}"),
}:
let
enabled = resolveEnabled server;
normalised = server // {
inherit enabled;
url = if (server.url or null) != null then server.url else server.serverUrl or null;
};
transformed = lib.foldl' (acc: transform: transform acc) normalised extraTransforms;
withRenderedEnv =
transformed
// lib.optionalAttrs (transformed ? env) {
env = renderEnv mkFileRef transformed.env;
};
cleaned = removeAttrs withRenderedEnv (
[
"disabled"
"serverUrl"
]
++ exclude
);
in
lib.filterAttrs (_: value: value != null && value != [ ] && value != { }) cleaned;
}
+82
View File
@@ -0,0 +1,82 @@
# Contains some handy functions for generating Putter file manifests.
{ lib }:
let
inherit (lib)
filter
hasPrefix
optionalAttrs
;
in
{
# Converts a Home Manager style list of file specifications into a Putter
# configuration.
#
# Note, the interface of this function is not considered stable, it may change
# as the needs of Home Manager change.
mkPutterManifest =
{
putterStatePath,
sourceBaseDirectory,
targetBaseDirectory,
fileEntries,
}:
let
# Create a Putter entry for the given file.
mkEntry =
f:
{
source = "${sourceBaseDirectory}/${f.target}";
# source = "${f.source}";
target = (if hasPrefix "/" f.target then "" else "${targetBaseDirectory}/") + f.target;
}
// optionalAttrs f.force {
collision.resolution = "force";
}
// optionalAttrs f.recursive {
action.type = "recursive_symlink";
};
putterJson = {
version = "1";
state = putterStatePath;
files = map mkEntry (filter (f: f.enable) fileEntries);
};
putterJsonText = builtins.toJSON putterJson;
in
putterJsonText;
# Create a putter state file to allow compatibility between legacy and putter
# managed files.
#
# Note, the interface of this function is not considered stable, it may change
# as the needs of Home Manager change.
mkPutterCompatState =
{
sourceBaseDirectory,
targetBaseDirectory,
fileEntries,
}:
let
mkEntry = f: {
name = (if hasPrefix "/" f.target then "" else "${targetBaseDirectory}/") + f.target;
value = {
source = "${sourceBaseDirectory}/${f.target}";
modified = {
secs_since_epoch = 0;
nanos_since_epoch = 0;
};
};
};
stateJsonText = builtins.toJSON {
version = "1";
files = lib.listToAttrs (map mkEntry (filter (f: f.enable) fileEntries));
};
in
stateJsonText;
}
+7
View File
@@ -87,4 +87,11 @@ rec {
# Returns true for strings like `SCREAMING_SNAKE_CASE`, `SOME_CONSTANT`.
# Must be all uppercase letters/numbers, with words separated by single underscores.
isScreamingSnakeCase = str: builtins.match "^[A-Z0-9]+(_[A-Z0-9]+)*$" str != null;
# Check if the content is path-like (a path, a Nix store path string, or a derivation).
isPathLike =
content:
lib.isPath content
|| (builtins.isString content && lib.hasPrefix "${builtins.storeDir}/" content)
|| lib.isDerivation content;
}
+1 -1
View File
@@ -7,7 +7,7 @@ rec {
if builtins.isBool v then
if v then "true" else "false"
else if builtins.isString v then
''"${v}"''
''"${lib.escape [ "\\" "\"" "`" ] v}"''
else if builtins.isList v then
let
shell = import ./shell.nix { inherit lib; };
+206 -124
View File
@@ -14,38 +14,141 @@ let
cfg = config.fonts.fontconfig;
inherit (config.home) profileDirectory;
xml = pkgs.formats.xml { };
globalConfig = config;
fontConfigFileType = lib.types.submodule (
{ name, ... }:
let
noteContentOptionsExclusivity = ''
Note that {option}`source` will be derived from {option}`settings` and {option}`text` if they are
set; with override priorities being preserved. As a consequence, exactly
one of {option}`settings`, {option}`text`, and {option}`source` must be set with highest priority.
'';
in
{
config,
name,
options,
...
}:
{
options = {
enable = lib.mkEnableOption "Whether this font config file should be generated.";
text = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Verbatim contents of the config file. If this option is null then the 'source' option must be set.";
};
source = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Config file to source. Alternatively, use the 'text' option instead.";
enable = lib.mkOption {
description = ''
Whether this file should be generated. This option allows specific
files to be disabled.
'';
type = lib.types.bool;
inherit
(lib.hm.deprecations.mkStateVersionOptionDefault {
inherit (globalConfig.home) stateVersion;
since = "26.11";
optionPath = [
"fonts"
"fontconfig"
"configFile"
name
"enable"
];
legacy.value = false;
current.value = true;
})
default
defaultText
;
};
label = lib.mkOption {
type = lib.types.str;
default = name;
defaultText = "<name>";
description = "Label to use for the name of the config file.";
default = name;
defaultText = lib.literalExpression "<name>";
type = lib.types.str;
};
priority = lib.mkOption {
type = lib.types.ints.between 0 99;
default = 90;
description = ''
Determines the order in which configs are loaded.
Must be a value within the range of 0-99, where priority 0 is the highest priority and 99 is the lowest.
Determines the order in which configuration files are loaded.
Must be a value within the range of 0-99, where priority 0 is the
highest priority and 99 is the lowest.
'';
default = 90;
type = lib.types.ints.between 0 99;
};
target = lib.mkOption {
description = ''
Path to the target file relative to
{file}`''${config.xdg.configHome}/fontconfig/conf.d/`.
'';
default = "${toString config.priority}-hm-${config.label}.conf";
defaultText = ''"''${toString config.priority}-hm-''${config.label}.conf"'';
type = lib.types.nonEmptyStr;
};
settings = lib.mkOption {
inherit (xml) type;
default = { };
example = {
description = "Enable antialiasing";
match = {
"@target" = "font";
edit = {
"@mode" = "assign";
"@name" = "antialias";
bool = "true";
};
};
};
description = ''
Structured attrs of settings in the format of `pkgs.format.xml {}`. The
settings are implicitly wrapped inside the `<fontconfig>` tag, i.e. as
`{ fontconfig = <settings>; }`.
''
+ noteContentOptionsExclusivity;
};
text = lib.mkOption {
description = ''
Verbatim contents of the config file.
''
+ noteContentOptionsExclusivity;
default = null;
type = with lib.types; nullOr lines;
};
source = lib.mkOption {
description = ''
Path to the source file.
''
+ noteContentOptionsExclusivity;
type =
let
thisOption = lib.showOption [
"fonts"
"fontconfig"
"configFile"
name
"source"
];
abortOnNull =
x:
if x != null then
x
else
abort ''
Setting '${thisOption}' to null is not
required anymore and disallowed. You can safely remove this definition.
'';
in
with lib.types;
coercedTo (nullOr path) abortOnNull path;
};
};
config.source =
let
fileName = lib.hm.strings.storeFileName "hm-fontconfig-${config.label}.xml";
mkSettingsFile = settings: xml.generate fileName { fontconfig = settings; };
mkTextFile = pkgs.writeText fileName;
in
lib.mkMerge [
(lib.mkIf (config.settings != { }) (lib.mkDerivedConfig options.settings mkSettingsFile))
(lib.mkIf (config.text != null) (lib.mkDerivedConfig options.text mkTextFile))
];
}
);
@@ -175,13 +278,12 @@ in
type = lib.types.attrsOf fontConfigFileType;
default = { };
description = ''
Extra font config files that will be added to `~/.config/fontconfig/conf.d/`.
Files are named like `fontconfig/conf.d/{priority}-{label}.conf`.
Config files that will be installed to {file}`~/.config/fontconfig/conf.d/`.
'';
example = {
tamzen = {
tamzen-disable-antialiasing = {
enable = true;
label = "tamzen-disable-antialiasing";
priority = 90;
text = ''
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
@@ -198,13 +300,12 @@ in
</match>
</fontconfig>
'';
priority = 90;
}; # => conf.d/90-tamzen-disable-antialiasing.conf
};
commit-mono-options = {
enable = true;
source = "./resources/fontconfig/commit-mono.conf";
priority = 80;
}; # => conf.d/80-commit-mono-options.conf
source = "./resources/fontconfig/commit-mono.conf";
};
};
};
@@ -248,112 +349,93 @@ in
fi
'';
fonts.fontconfig.configFile =
let
mkFontconfigConf = conf: ''
<?xml version='1.0'?>
<!-- Generated by Home Manager. -->
<!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
<fontconfig>
${conf}
</fontconfig>
'';
in
{
fonts = {
fonts.fontconfig.configFile = {
fonts =
let
mkInclude = path: {
"@ignore_missing" = "yes";
"#text" = path;
};
in
{
enable = true;
priority = 10;
source = null; # Set the source as null explicitly so that it cannot be overwritten by mistake by a user
text = mkFontconfigConf ''
<description>Add fonts in the Nix user profile</description>
<include ignore_missing="yes">${config.home.path}/etc/fonts/conf.d</include>
<include ignore_missing="yes">${config.home.path}/etc/fonts/fonts.conf</include>
<dir>${config.home.path}/lib/X11/fonts</dir>
<dir>${config.home.path}/share/fonts</dir>
<dir>${profileDirectory}/lib/X11/fonts</dir>
<dir>${profileDirectory}/share/fonts</dir>
<cachedir>${config.home.path}/lib/fontconfig/cache</cachedir>
'';
settings = {
description = "Add fonts in the Nix user profile";
include = map mkInclude [
"${config.home.path}/etc/fonts/conf.d"
"${config.home.path}/etc/fonts/fonts.conf"
];
dir = [
"${config.home.path}/lib/X11/fonts"
"${config.home.path}/share/fonts"
"${config.home.profileDirectory}/lib/X11/fonts"
"${config.home.profileDirectory}/share/fonts"
];
cachedir = "${config.home.path}/lib/fontconfig/cache";
};
};
rendering =
let
set =
name: value:
let
xmlValue =
if builtins.isBool value then
"<bool>${lib.boolToString value}</bool>"
else if builtins.isString value then
"<const>${value}</const>"
else
throw "expected bool or string but got ${builtins.typeOf value}: ${toString value}";
in
''
<match target="font">
<edit mode="assign" name="${name}">
${xmlValue}
</edit>
</match>
'';
content =
lib.optional (cfg.antialiasing != null) (set "antialias" cfg.antialiasing)
++ lib.optionals (cfg.hinting != null) [
(set "hinting" true)
(set "hintstyle" ("hint" + cfg.hinting))
]
++ lib.optional (cfg.subpixelRendering != null) (
set "rgba" (builtins.replaceStrings [ "ertical-" ] [ "" ] cfg.subpixelRendering)
);
in
{
enable = builtins.length content > 0;
priority = 10;
source = null;
text = mkFontconfigConf (
lib.concatStrings ([ "<description>Set the rendering mode</description>\n" ] ++ content)
rendering =
let
toXmlValue =
value:
if lib.isBool value then
{ bool = lib.boolToString value; }
else if lib.isString value then
{ const = value; }
else
throw "expected bool or string but got ${lib.typeOf value}: ${toString value}";
assign =
name: value:
toXmlValue value
// {
"@mode" = "assign";
"@name" = name;
};
content =
lib.optional (cfg.antialiasing != null) (assign "antialias" cfg.antialiasing)
++ lib.optionals (cfg.hinting != null) [
(assign "hinting" true)
(assign "hintstyle" ("hint" + cfg.hinting))
]
++ lib.optional (cfg.subpixelRendering != null) (
assign "rgba" (lib.replaceStrings [ "ertical-" ] [ "" ] cfg.subpixelRendering)
);
in
{
enable = lib.length content > 0;
priority = 10;
settings = {
description = "Set the rendering mode";
match = {
"@target" = "font";
edit = content;
};
};
default-fonts =
let
genDefault =
fonts: name:
lib.optionalString (fonts != [ ]) ''
<alias binding="same">
<family>${name}</family>
<prefer>
${lib.concatStringsSep "" (
map (font: ''
<family>${font}</family>
'') fonts
)}
</prefer>
</alias>
'';
in
{
enable = true;
priority = 52;
source = null;
text = mkFontconfigConf ''
<!-- Default fonts -->
${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
${genDefault cfg.defaultFonts.serif "serif"}
${genDefault cfg.defaultFonts.monospace "monospace"}
${genDefault cfg.defaultFonts.emoji "emoji"}
'';
};
default-fonts =
let
filterNonEmpty = lib.filterAttrs (_: fonts: fonts != [ ]);
mkAlias = name: fonts: {
"@binding" = "same";
family = if name == "sansSerif" then "sans-serif" else name;
prefer.family = fonts;
};
};
in
{
enable = true;
priority = 52;
settings = {
description = "Set default fonts";
alias = lib.mapAttrsToList mkAlias (filterNonEmpty cfg.defaultFonts);
};
};
};
xdg.configFile = lib.mapAttrs' (
_name: config:
lib.nameValuePair "fontconfig/conf.d/${toString config.priority}-hm-${config.label}.conf" {
inherit (config) enable text;
source = lib.mkIf (config.source != null) config.source;
lib.nameValuePair "fontconfig/conf.d/${config.target}" {
inherit (config) enable source;
}
) cfg.configFile;
};
@@ -13,7 +13,7 @@ let
cfg3 = config.gtk.gtk3;
cfg4 = config.gtk.gtk4;
gtkLib = import ./gtk/lib.nix { inherit lib; };
gtkLib = import ./lib.nix { inherit lib; };
themeType = gtkLib.mkThemeType {
typeName = "theme";
@@ -41,9 +41,9 @@ in
];
imports = [
./gtk/gtk2.nix
./gtk/gtk3.nix
./gtk/gtk4.nix
./gtk2.nix
./gtk3.nix
./gtk4.nix
];
options.gtk = {
@@ -1,10 +0,0 @@
{ pkgs, ... }:
{
time = "2024-03-28T17:02:19+00:00";
condition = pkgs.stdenv.hostPlatform.isLinux;
message = ''
A new module is available: 'services.amberol'.
'';
}
@@ -1,10 +0,0 @@
{
time = "2025-01-02T11:21:19+00:00";
condition = true;
message = ''
A new module is available: 'services.mpdscribble'.
A MPD client which submits information about tracks being played to a
scrobbler (e.g. last.fm)
'';
}
@@ -1,7 +1,7 @@
{ config, ... }:
{
time = "2026-03-13T03:09:51+00:00";
condition = config.programs.gemini-cli.enable;
condition = config.programs.antigravity-cli.enable;
message = ''
The `programs.gemini-cli.policies` option has been added to support configuring
the Gemini CLI policy engine.
@@ -1,7 +1,7 @@
{ config, ... }:
{
time = "2026-03-31T17:22:36+00:00";
condition = config.programs.gemini-cli.enable;
condition = config.programs.antigravity-cli.enable;
message = ''
The `programs.gemini-cli.enableMcpIntegration` and `programs.gemini-cli.skills`
options have been added to support configuring the Gemini CLI MCP servers and
@@ -5,7 +5,7 @@
config.programs.claude-code.enable
|| config.programs.codex.enable
|| config.programs.opencode.enable
|| config.programs.gemini-cli.enable;
|| config.programs.antigravity-cli.enable;
message = ''
The Claude Code, Codex, and OpenCode modules now expose a canonical
`context` option for configuring their global assistant instructions.
@@ -0,0 +1,14 @@
{
pkgs,
...
}:
{
time = "2026-04-15T11:57:57+00:00";
condition = pkgs.stdenv.hostPlatform.isDarwin;
message = ''
A new module is available: `programs.macos-terminal`.
This module allows for configuration of preferences for the macOS
Terminal.app application.
'';
}
@@ -0,0 +1,18 @@
{ config, lib, ... }:
{
time = "2026-04-26T12:42:03+00:00";
condition =
let
extraConfigFiles = lib.removeAttrs config.fonts.fontconfig.configFile [
"fonts"
"rendering"
"default-fonts"
];
in
config.fonts.fontconfig.enable && extraConfigFiles != { };
message = ''
There is a new `fonts.fontconfig.configFile.<name>.settings` option to
define Fontconfig configuration files via a structured attrs in the
format of `pkgs.formats.xml {}`.
'';
}
@@ -0,0 +1,15 @@
{ config, ... }:
{
time = "2026-05-14T00:00:00+00:00";
condition = config.wayland.windowManager.hyprland.enable;
message = ''
A new option `wayland.windowManager.hyprland.extraLuaFiles` is available for
managing additional Lua files under `$XDG_CONFIG_HOME/hypr` when using
`wayland.windowManager.hyprland.configType = "lua"`.
Files can be provided as paths or strings. Attribute names are treated as
Lua module names, so `lib.helpers` writes `lib/helpers.lua`, and can be
automatically loaded from the generated `hyprland.lua` with `require(...)`.
'';
}
@@ -0,0 +1,18 @@
{ config, ... }:
{
time = "2026-05-18T17:00:00+00:00";
condition = config.programs.zsh.enable;
message = ''
The `programs.zsh` module now sources Home Manager session variables from
`.zprofile` for login shells. Non-login shells continue to source them from
`.zshenv`.
As a result, Home Manager now always writes `.zprofile` when
`programs.zsh.enable` is set, instead of only writing it when
`programs.zsh.profileExtra` is configured.
This keeps `home.sessionPath` entries from being overwritten or reordered
by system login startup files such as NixOS' `/etc/zprofile` or macOS'
`path_helper`.
'';
}

Some files were not shown because too many files have changed in this diff Show More