diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ee1781ea2c6e..5967e4f43db3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,7 @@ * @rycee +/flake.nix @bqv @kisik21 + /modules/home-environment.nix @rycee /modules/misc/dconf.nix @gnidorah @rycee @@ -11,6 +13,9 @@ /modules/misc/news.nix @rycee +/modules/misc/numlock.nix @evanjs +/tests/modules/misc/numlock @evanjs + /modules/misc/pam.nix @rycee /tests/modules/misc/pam @rycee @@ -28,6 +33,9 @@ /modules/programs/aria2.nix @JustinLovinger +/modules/programs/autojump.nix @evanjs +/tests/modules/programs/autojump @evanjs + /modules/programs/autorandr.nix @uvNikita /modules/programs/bash.nix @rycee @@ -48,6 +56,9 @@ /modules/programs/firefox.nix @rycee +/modules/programs/gh.nix @Gerschtli +/tests/modules/programs/gh @Gerschtli + /modules/programs/git.nix @rycee /modules/programs/gnome-terminal.nix @rycee @@ -58,6 +69,8 @@ /modules/programs/i3status.nix @JustinLovinger +/modules/programs/i3status-rust.nix @workflow + /modules/programs/keychain.nix @marsam /modules/programs/lesspipe.nix @rycee @@ -71,8 +84,19 @@ /modules/programs/matplotlib.nix @rprospero +/modules/programs/mbsync.nix @KarlJoad +/tests/modules/programs/mbsync @KarlJoad + +/modules/programs/mcfly.nix @marsam + /modules/programs/mpv.nix @tadeokondrak +/modules/programs/mu.nix @KarlJoad + +/modules/programs/ncmpcpp.nix @olmokramer +/tests/modules/programs/ncmpcpp @olmokramer +/tests/modules/programs/ncmpcpp-linux @olmokramer + /modules/programs/ne.nix @cwyc /tests/modules/programs/ne @cwyc @@ -103,12 +127,19 @@ /modules/programs/texlive.nix @rycee +/modules/programs/waybar.nix @berbiche +/tests/modules/programs/waybar @berbiche + /modules/programs/z-lua.nix @marsam /modules/programs/zathura.nix @rprospero /modules/programs/zoxide.nix @marsam +/modules/programs/zsh/prezto.nix @NickHu + +/modules/services/caffeine.nix @uvNikita + /modules/services/cbatticon.nix @pmiddend /modules/services/clipmenu.nix @DamienCassou @@ -124,6 +155,8 @@ /modules/services/fluidsynth.nix @Valodim +/modules/services/gammastep.nix @petabyteboy + /modules/services/gnome-keyring.nix @rycee /modules/services/gpg-agent.nix @rycee @@ -134,6 +167,9 @@ /modules/services/imapnotify.nix @nickhu +/modules/services/kanshi.nix @nurelin +/tests/modules/services/kanshi @nurelin + /modules/services/kdeconnect.nix @adisbladis /modules/services/keepassx.nix @rycee @@ -180,6 +216,9 @@ /modules/services/window-managers/i3-sway/sway.nix @alexarice +/modules/services/wlsunset.nix @matrss +/tests/modules/services/wlsunset @matrss + /modules/services/xcape.nix @nickhu /modules/services/xembed-sni-proxy.nix @rycee diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 75c91499a582..905f975e85de 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,18 @@ + + ### Issue description @@ -25,7 +25,7 @@ Also make sure to read the guidelines found at - [ ] Code tested through `nix-shell --pure tests -A run.all`. -- [ ] Test cases updated/added. See [example](https://github.com/rycee/home-manager/commit/f3fbb50b68df20da47f9b0def5607857fcc0d021#diff-b61a6d542f9036550ba9c401c80f00ef). +- [ ] Test cases updated/added. See [example](https://github.com/nix-community/home-manager/commit/f3fbb50b68df20da47f9b0def5607857fcc0d021#diff-b61a6d542f9036550ba9c401c80f00ef). - [ ] Commit messages are formatted like @@ -35,10 +35,10 @@ Also make sure to read the guidelines found at {long description} ``` - See [CONTRIBUTING](https://github.com/rycee/home-manager/blob/master/doc/contributing.adoc#sec-commit-style) for more information and [recent commit messages](https://github.com/rycee/home-manager/commits/master) for examples. + See [CONTRIBUTING](https://github.com/nix-community/home-manager/blob/master/doc/contributing.adoc#sec-commit-style) for more information and [recent commit messages](https://github.com/nix-community/home-manager/commits/master) for examples. - If this PR adds a new module - - [ ] Added myself as module maintainer. See [example](https://github.com/rycee/home-manager/blob/068ff76a10e95820f886ac46957edcff4e44621d/modules/programs/lesspipe.nix#L6). + - [ ] Added myself as module maintainer. See [example](https://github.com/nix-community/home-manager/blob/068ff76a10e95820f886ac46957edcff4e44621d/modules/programs/lesspipe.nix#L6). - [ ] Added myself and the module files to `.github/CODEOWNERS`. diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml new file mode 100644 index 000000000000..aa175b253a0c --- /dev/null +++ b/.github/workflows/github_pages.yml @@ -0,0 +1,28 @@ +name: GitHub Pages +on: + push: + branches: + - master +jobs: + publish: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: cachix/install-nix-action@v12 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v8 + with: + name: nix-community + signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' + - run: | + nix-build -A docs.html + cp -r result/share/doc/home-manager public + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12be93e2973f..7572016cb9a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,8 +11,13 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v10 + - uses: cachix/install-nix-action@v12 with: nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v8 + with: + name: nix-community + signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' + - run: ./format -c - run: nix-shell . -A install - - run: nix-shell --pure --max-jobs 4 tests -A run.all + - run: nix-shell --pure tests -A run.all diff --git a/.gitignore b/.gitignore index d6944e3ddc11..3526db718920 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/flake.lock /result* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2c5c5a67ffea..34085a57e2ac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,7 @@ image: nixos/nix:latest variables: - # Pinned 2020-03-28. - NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/05f0934825c2a0750d4888c4735f9420c906b388.tar.gz" + NIX_PATH: "nixpkgs=channel:nixos-unstable" stages: - test diff --git a/LICENSE b/LICENSE index 122b9b4a56f0..2db3938bbebd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017-2020 Robert Helgesson and Home Manager contributors +Copyright (c) 2017-2020 Home Manager contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 874ae2b4b34e..a47f26bc648c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ will write to your dconf store and cannot tell whether a configuration that it is about to be overwrite was from a previous Home Manager generation or from manual configuration. -Home Manager targets [NixOS][] unstable and NixOS version 20.03 (the +Home Manager targets [NixOS][] unstable and NixOS version 20.09 (the current stable version), it may or may not work on other Linux distributions and NixOS versions. @@ -61,21 +61,23 @@ Currently the easiest way to install Home Manager is as follows: control this option using the [`nix.allowedUsers`][nixosAllowedUsers] system option. + Note that Nix 2.4 (`nixUnstable`) is not yet supported. + 2. Add the appropriate Home Manager channel. Typically this is ```console - $ nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager + $ nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager $ nix-channel --update ``` if you are following Nixpkgs master or an unstable channel and ```console - $ nix-channel --add https://github.com/rycee/home-manager/archive/release-20.03.tar.gz home-manager + $ nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager $ nix-channel --update ``` - if you follow a Nixpkgs version 20.03 channel. + if you follow a Nixpkgs version 20.09 channel. On NixOS you may need to log out and back in for the channel to become available. On non-NixOS you may have to add @@ -129,8 +131,8 @@ configuration generations. As an example, let us expand the initial configuration file from the installation above to install the htop and fortune packages, install -Emacs with a few extra packages enabled, install Firefox with the -IcedTea plugin enabled, and enable the user gpg-agent service. +Emacs with a few extra packages enabled, install Firefox with +smooth scrolling enabled, and enable the user gpg-agent service. To satisfy the above setup we should elaborate the `~/.config/nixpkgs/home.nix` file as follows: @@ -305,6 +307,41 @@ in your system configuration and in your Home Manager configuration. +Nix Flakes +---------- + +Home Manager includes a `flake.nix` file for compatibility with [Nix Flakes][] +for those that wish to use it as a module. A bare-minimum `flake.nix` would be +as follows: + +```nix +{ + description = "NixOS configuration"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + home-manager.url = "github:nix-community/home-manager"; + }; + + outputs = { home-manager, nixpkgs, ... }: { + nixosConfigurations = { + hostname = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ./configuration.nix + home-manager.nixosModules.home-manager + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.users.user = import ./home.nix; + } + ]; + }; + }; + }; +} +``` + Releases -------- @@ -312,7 +349,7 @@ Home Manager is developed against `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-20.03`). These branches get fixes, but usually not new +(e.g. `release-20.09`). These branches get fixes, but usually not new modules. If you need a module to be backported, then feel free to open an issue. @@ -323,10 +360,11 @@ an issue. [nixAllowedUsers]: https://nixos.org/nix/manual/#conf-allowed-users [nixosAllowedUsers]: https://nixos.org/nixos/manual/options.html#opt-nix.allowedUsers [Z shell]: http://zsh.sourceforge.net/ -[manual]: https://rycee.gitlab.io/home-manager/ -[configuration options]: https://rycee.gitlab.io/home-manager/options.html +[manual]: https://nix-community.github.io/home-manager/ +[configuration options]: https://nix-community.github.io/home-manager/options.html [#home-manager]: https://webchat.freenode.net/?url=irc%3A%2F%2Firc.freenode.net%2Fhome-manager [freenode]: https://freenode.net/ [channel logs]: https://logs.nix.samueldr.com/home-manager/ [samueldr]: https://github.com/samueldr/ [Nix Pills]: https://nixos.org/nixos/nix-pills/ +[Nix Flakes]: https://nixos.wiki/wiki/Flakes diff --git a/doc/contributing.adoc b/doc/contributing.adoc index a6c7a3278e73..dd68f296f785 100644 --- a/doc/contributing.adoc +++ b/doc/contributing.adoc @@ -1,14 +1,14 @@ [[ch-contributing]] == Contributing -:open-issues: https://github.com/rycee/home-manager/issues -:new-issue: https://github.com/rycee/home-manager/issues/new +:open-issues: https://github.com/nix-community/home-manager/issues +:new-issue: https://github.com/nix-community/home-manager/issues/new :fork-a-repo: https://help.github.com/articles/fork-a-repo/ :create-a-pull-request: https://help.github.com/articles/creating-a-pull-request/ :seven-rules: https://chris.beams.io/posts/git-commit/#seven-rules -:news-nix: https://github.com/rycee/home-manager/blob/master/modules/misc/news.nix +:news-nix: https://github.com/nix-community/home-manager/blob/master/modules/misc/news.nix :nixfmt: https://github.com/serokell/nixfmt/ -:example-commit-message: https://github.com/rycee/home-manager/commit/69f8e47e9e74c8d3d060ca22e18246b7f7d988ef +:example-commit-message: https://github.com/nix-community/home-manager/commit/69f8e47e9e74c8d3d060ca22e18246b7f7d988ef 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 <> for information on how to set up a suitable development environment and <> for the actual guidelines. diff --git a/doc/default.nix b/doc/default.nix index 88e9756140e8..6b8e229aae1e 100644 --- a/doc/default.nix +++ b/doc/default.nix @@ -33,7 +33,7 @@ let } ++ [ scrubbedPkgsModule ]; moduleRootPaths = [ ./.. ]; mkModuleUrl = path: - "https://github.com/rycee/home-manager/blob/master/${path}#blob-path"; + "https://github.com/nix-community/home-manager/blob/master/${path}#blob-path"; channelName = "home-manager"; docBook.id = "home-manager-options"; }; diff --git a/doc/installation.adoc b/doc/installation.adoc new file mode 100644 index 000000000000..ef97b96b239d --- /dev/null +++ b/doc/installation.adoc @@ -0,0 +1,260 @@ +[[ch-installation]] +== Installing Home Manager + +:nix-darwin: https://github.com/LnL7/nix-darwin/ + +Home Manager can be used in three primary ways: + +1. Using the standalone `home-manager` tool. For platforms other than +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 independent of the system as a whole. See +<> 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 <> for a description of +this setup. + +3. As a module within a {nix-darwin}[nix-darwin] system configuration. +This allows the user profiles to be built together with the system +when running `darwin-rebuild`. See <> +for a description of this setup. + +[[sec-install-standalone]] +=== Standalone installation + +:nix-allowed-users: https://nixos.org/nix/manual/#conf-allowed-users +:nixos-allowed-users: https://nixos.org/nixos/manual/options.html#opt-nix.allowedUsers + +1. Make sure you have a working Nix installation. Specifically, make +sure that your user is able to build and install Nix packages. For +example, you should be able to successfully run a command like +`nix-instantiate '' -A hello` without having to switch to the +root user. For a multi-user install of Nix this means that your user +must be covered by the {nix-allowed-users}[`allowed-users`] Nix +option. On NixOS you can control this option using the +{nixos-allowed-users}[`nix.allowedUsers`] system option. + +2. Add the Home Manager channel that you wish to follow. This is done +by running ++ +[source,console] +---- +$ nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager +$ nix-channel --update +---- ++ +if you are following Nixpkgs master or an unstable channel and ++ +[source,console] +---- +$ nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager +$ nix-channel --update +---- ++ +if you follow a Nixpkgs version 20.09 channel. ++ +On NixOS you may need to log out and back in for the channel to become +available. On non-NixOS you may have to add ++ +[source,bash] +export NIX_PATH=$HOME/.nix-defexpr/channels${NIX_PATH:+:}$NIX_PATH ++ +to your shell (see +https://github.com/NixOS/nix/issues/2033[nix#2033]). + +3. Run the Home Manager installation command and create the first Home +Manager generation: ++ +[source,console] +$ nix-shell '' -A install ++ +Once finished, Home Manager should be active and available in your +user environment. + +4. If you do not plan on having Home Manager manage your shell +configuration then you must source the ++ +[source,bash] +$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh ++ +file in your shell configuration. Unfortunately, we currently only +support POSIX.2-like shells such as +https://www.gnu.org/software/bash/[Bash] or +http://zsh.sourceforge.net/[Z shell]. ++ +For example, if you use Bash then add ++ +[source,bash] +---- +. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh" +---- ++ +to your `~/.profile` file. + +If instead of using channels you want to run Home Manager from a Git +checkout of the repository then you can use the +<> option to specify the absolute path +to the repository. + +[[sec-install-nixos-module]] +=== NixOS module + +Home Manager provides a NixOS module that allows you to prepare user +environments directly from the system configuration file, which often +is more convenient than using the `home-manager` tool. It also opens +up additional possibilities, for example, to automatically configure +user environments in NixOS declarative containers or on systems +deployed through NixOps. + +To make the NixOS module available for use you must `import` it into +your system configuration. This is most conveniently done by adding a +Home Manager channel, for example + +[source,console] +---- +# nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager +# nix-channel --update +---- + +if you are following Nixpkgs master or an unstable channel and + +[source,console] +---- +# nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager +# nix-channel --update +---- + +if you follow a Nixpkgs version 20.09 channel. + +It is then possible to add + +[source,nix] +imports = [ ]; + +to your system `configuration.nix` file, which will introduce a new +NixOS option called `home-manager.users` whose type is an attribute +set that maps user names to Home Manager configurations. + +For example, a NixOS configuration may include the lines + +[source,nix] +---- +users.users.eve.isNormalUser = true; +home-manager.users.eve = { pkgs, ... }: { + home.packages = [ pkgs.atool pkgs.httpie ]; + programs.bash.enable = true; +}; +---- + +and after a `nixos-rebuild switch` the user eve's environment should +include a basic Bash configuration and the packages atool and httpie. + +[NOTE] +==== +By default packages will be installed to `$HOME/.nix-profile` but they +can be installed to `/etc/profiles` if + +[source,nix] +home-manager.useUserPackages = true; + +is added to the system configuration. This is necessary if, for +example, you wish to use `nixos-rebuild build-vm`. This option may +become the default value in the future. +==== + +[NOTE] +==== +By default, Home Manager uses a private `pkgs` instance that is +configured via the `home-manager.users..nixpkgs` options. To +instead use the global `pkgs` that is configured via the system level +`nixpkgs` options, set + +[source,nix] +home-manager.useGlobalPkgs = true; + +This saves an extra Nixpkgs evaluation, adds consistency, and removes +the dependency on `NIX_PATH`, which is otherwise used for importing +Nixpkgs. +==== + +[[sec-install-nix-darwin-module]] +=== nix-darwin module + +Home Manager provides a module that allows you to prepare user +environments directly from the {nix-darwin}[nix-darwin] configuration +file, which often is more convenient than using the `home-manager` +tool. + +To make the NixOS module available for use you must `import` it into +your system configuration. This is most conveniently done by adding a +Home Manager channel, for example + +[source,console] +---- +# nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager +# nix-channel --update +---- + +if you are following Nixpkgs master or an unstable channel and + +[source,console] +---- +# nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager +# nix-channel --update +---- + +if you follow a Nixpkgs version 20.09 channel. + +It is then possible to add + +[source,nix] +imports = [ ]; + +to your nix-darwin `configuration.nix` file, which will introduce a +new NixOS option called `home-manager` whose type is an attribute set +that maps user names to Home Manager configurations. + +For example, a nix-darwin configuration may include the lines + +[source,nix] +---- +home-manager.users.eve = { pkgs, ... }: { + home.packages = [ pkgs.atool pkgs.httpie ]; + programs.bash.enable = true; +}; +---- + +and after a `darwin-rebuild --switch` the user eve's environment +should include a basic Bash configuration and the packages atool and +httpie. + +[NOTE] +==== +By default user packages will not be ignored in favor of +`environment.systemPackages`, but they will be intalled to +`/etc/profiles/per-user/$USERNAME` if + +[source,nix] +home-manager.useUserPackages = true; + +is added to the nix-darwin configuration. This option may become the +default value in the future. +==== + +[NOTE] +==== +By default, Home Manager uses a private `pkgs` instance that is +configured via the `home-manager.users..nixpkgs` options. To +instead use the global `pkgs` that is configured via the system level +`nixpkgs` options, set + +[source,nix] +home-manager.useGlobalPkgs = true; + +This saves an extra Nixpkgs evaluation, adds consistency, and removes +the dependency on `NIX_PATH`, which is otherwise used for importing +Nixpkgs. +==== diff --git a/doc/installation.xml b/doc/installation.xml deleted file mode 100644 index 4c5d1f735bb7..000000000000 --- a/doc/installation.xml +++ /dev/null @@ -1,317 +0,0 @@ - - Installing Home Manager - - Home Manager can be used in three primary ways: - - - - Using the standalone home-manager tool. For platforms - other than 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 independent of the system as a whole. See - for instructions on how to - perform this installation. - - - - - 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 - for a description of this - setup. - - - - - As a module within a - nix-darwin - system configuration. This allows the user profiles to be built together - with the system when running darwin-rebuild. See - for a description of this - setup. - - - - -
- Standalone installation - - - - - Make sure you have a working Nix installation. Specifically, make - sure that your user is able to build and install Nix packages. - For example, you should be able to successfully run a command - like nix-instantiate '<nixpkgs>' -A hello - without having to switch to the root user. For a multi-user - install of Nix this means that your user must be covered by the - allowed-users - Nix option. On NixOS you can control this option using the - nix.allowedUsers - system option. - - - - - Add the Home Manager channel that you wish to follow. This is done by - running - - -$ nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager -$ nix-channel --update - - - if you are following Nixpkgs master or an unstable channel and - - -$ nix-channel --add https://github.com/rycee/home-manager/archive/release-20.03.tar.gz home-manager -$ nix-channel --update - - - if you follow a Nixpkgs version 20.03 channel. - - - On NixOS you may need to log out and back in for the channel to become - available. On non-NixOS you may have to add - -export NIX_PATH=$HOME/.nix-defexpr/channels${NIX_PATH:+:}$NIX_PATH - - to your shell (see - nix#2033). - - - - - Run the Home Manager installation command and create the first Home - Manager generation: - - -$ nix-shell '<home-manager>' -A install - - - Once finished, Home Manager should be active and available in your user - environment. - - - - - If you do not plan on having Home Manager manage your shell configuration - then you must source the - - -$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh - - - file in your shell configuration. Unfortunately, we currently only support - POSIX.2-like shells such as - Bash or - Z shell. - - - For example, if you use Bash then add - - -. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh" - - - to your ~/.profile file. - - - - - - If instead of using channels you want to run Home Manager from a Git - checkout of the repository then you can use the - programs.home-manager.path option to specify the absolute - path to the repository. - -
-
- NixOS module - - - Home Manager provides a NixOS module that allows you to prepare user - environments directly from the system configuration file, which often is - more convenient than using the home-manager tool. It also - opens up additional possibilities, for example, to automatically configure - user environments in NixOS declarative containers or on systems deployed - through NixOps. - - - - To make the NixOS module available for use you must - it into your system configuration. This is most conveniently done by adding - a Home Manager channel, for example - - - -# nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager -# nix-channel --update - - - - if you are following Nixpkgs master or an unstable channel and - - - -# nix-channel --add https://github.com/rycee/home-manager/archive/release-20.03.tar.gz home-manager -# nix-channel --update - - - - if you follow a Nixpkgs version 20.03 channel. - - - - It is then possible to add - - - -imports = [ <home-manager/nixos> ]; - - - - to your system configuration.nix file, which will - introduce a new NixOS option called - whose type is an attribute set that maps user names to Home Manager - configurations. - - - - For example, a NixOS configuration may include the lines - - - -users.users.eve.isNormalUser = true; -home-manager.users.eve = { pkgs, ... }: { - home.packages = [ pkgs.atool pkgs.httpie ]; - programs.bash.enable = true; -}; - - - - and after a nixos-rebuild switch the user eve's - environment should include a basic Bash configuration and the packages atool - and httpie. - - - - - By default packages will be installed to - $HOME/.nix-profile but they can be installed to - /etc/profiles if - - -home-manager.useUserPackages = true; - - - is added to the system configuration. This is necessary if, for example, - you wish to use nixos-rebuild build-vm. This option may - become the default value in the future. - - - - - - By default, Home Manager uses a private pkgs instance - that is configured via the options. - To instead use the global pkgs that is configured via - the system level options, set - - -home-manager.useGlobalPkgs = true; - - - This saves an extra Nixpkgs evaluation, adds consistency, and removes the - dependency on NIX_PATH, which is otherwise used for - importing Nixpkgs. - - -
-
- nix-darwin module - - - Home Manager provides a module that allows you to prepare user - environments directly from the nix-darwin configuration file, which often is - more convenient than using the home-manager tool. - - - - To make the NixOS module available for use you must - it into your system configuration. This is most conveniently done by adding - a Home Manager channel, for example - - - -# nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager -# nix-channel --update - - - - if you are following Nixpkgs master or an unstable channel and - - - -# nix-channel --add https://github.com/rycee/home-manager/archive/release-20.03.tar.gz home-manager -# nix-channel --update - - - - if you follow a Nixpkgs version 20.03 channel. - - - - It is then possible to add - - - -imports = [ <home-manager/nix-darwin> ]; - - - - to your nix-darwin configuration.nix file, which will - introduce a new NixOS option called whose type - is an attribute set that maps user names to Home Manager configurations. - - - - For example, a nix-darwin configuration may include the lines - - - -home-manager.users.eve = { pkgs, ... }: { - home.packages = [ pkgs.atool pkgs.httpie ]; - programs.bash.enable = true; -}; - - - - and after a darwin-rebuild --switch the user eve's - environment should include a basic Bash configuration and the packages atool - and httpie. - - - - - By default user packages will not be ignored in favor of - , but they will be intalled to - if - - - -home-manager.useUserPackages = true; - - - - is added to the nix-darwin configuration. This option may become the default - value in the future. - - -
-
diff --git a/doc/man-home-manager.xml b/doc/man-home-manager.xml index 90f58063ea95..3bfe023026a2 100644 --- a/doc/man-home-manager.xml +++ b/doc/man-home-manager.xml @@ -115,7 +115,16 @@ - --max-jobs number + + + -j + + + + --max-jobs + + + number @@ -417,6 +426,9 @@ + + + @@ -512,7 +524,7 @@ Please report any bugs on the project + xlink:href="https://github.com/nix-community/home-manager/issues">project issue tracker. diff --git a/doc/manual.xml b/doc/manual.xml index ff355d6ce1ee..314d7c10a961 100644 --- a/doc/manual.xml +++ b/doc/manual.xml @@ -20,7 +20,7 @@ are hosted courtesy of samueldr. If your problem is caused by a bug in Home Manager then it should be reported on the - Home Manager issue tracker. + Home Manager issue tracker. diff --git a/doc/release-notes/release-notes.adoc b/doc/release-notes/release-notes.adoc index 9a98e3850c3b..e89e588fe1e5 100644 --- a/doc/release-notes/release-notes.adoc +++ b/doc/release-notes/release-notes.adoc @@ -6,6 +6,8 @@ This section lists the release notes for stable versions of Home Manager and the :leveloffset: 1 +include::rl-2103.adoc[] + include::rl-2009.adoc[] include::rl-2003.adoc[] diff --git a/doc/release-notes/rl-1903.adoc b/doc/release-notes/rl-1903.adoc index 6dfdc67f5bf0..1cba4235d60d 100644 --- a/doc/release-notes/rl-1903.adoc +++ b/doc/release-notes/rl-1903.adoc @@ -5,7 +5,7 @@ The 19.03 release branch became the stable branch in April, 2019. [[sec-release-19.03-highlights]] === Highlights -:opt-home-file-source: opt-home.file._name__.source +:opt-home-file-source: opt-home.file._name_.source This release has the following notable changes: diff --git a/doc/release-notes/rl-2009.adoc b/doc/release-notes/rl-2009.adoc index 0562e046d8d1..a3de0260c7d1 100644 --- a/doc/release-notes/rl-2009.adoc +++ b/doc/release-notes/rl-2009.adoc @@ -1,8 +1,7 @@ [[sec-release-20.09]] -== Release 20.09 (unreleased) +== Release 20.09 -This is the current unstable branch and the information in this -section is therefore not final. +The 20.09 release branch became the stable branch in late September, 2020. [[sec-release-20.09-highlights]] === Highlights @@ -43,3 +42,55 @@ $ nix-shell '' -A install will automatically include these options, when necessary. -- + +* Git's `smtpEncryption` option is now set to `tls` only if both <> and <> are `true`. If only <> is `true`, `ssl` is used instead. + +* The `nixpkgs` module no longer references ``. Before it would do so when building the `pkgs` module argument. Starting with state version 20.09, the `pkgs` argument is instead built from the same Nixpkgs that was used to initialize the Home Manager modules. This is useful, for example, when using Home Manager within a Nix Flake. If you want to keep using `` with state version ≥ 20.09 then add ++ +[source,nix] +_module.args.pkgsPath = ; ++ +to your Home Manager configuration. + +* The options `wayland.windowManager.sway.config.bars` and `opt-xsession.windowManager.i3.config.bars` have been changed so that most of the suboptions are now nullable and default to `null`. The default for these two options has been changed to manually set the old defaults for each suboption. The overall effect is that if the `bars` options is not set, then the default remains the same. On the other hand, something like: ++ +-- +[source,nix] +---- +bars = [ { + command = "waybar"; +} ]; +---- +will now create the config: +.... +bar { + swaybar_command waybar +} +.... +instead of +.... +bar { + + font pango:monospace 8 + mode dock + hidden_state hide + position bottom + status_command /nix/store/h7s6i9q1z5fxrlyyw5ls8vqxhf5bcs5a-i3status-2.13/bin/i3status + swaybar_command waybar + workspace_buttons yes + strip_workspace_numbers no + tray_output primary + colors { + background #000000 + statusline #ffffff + separator #666666 + focused_workspace #4c7899 #285577 #ffffff + active_workspace #333333 #5f676a #ffffff + inactive_workspace #333333 #222222 #888888 + urgent_workspace #2f343a #900000 #ffffff + binding_mode #2f343a #900000 #ffffff + } + +} +.... +-- diff --git a/doc/release-notes/rl-2103.adoc b/doc/release-notes/rl-2103.adoc new file mode 100644 index 000000000000..7d5b55d49a2b --- /dev/null +++ b/doc/release-notes/rl-2103.adoc @@ -0,0 +1,42 @@ +[[sec-release-21.03]] +== Release 21.03 + +This is the current unstable branch and the information in this +section is therefore not final. + +[[sec-release-21.03-highlights]] +=== Highlights + +This release has the following notable changes: + +* The <> option is now a list rather than an +attribute set. To migrate, move the keys of the attrset into the list +items' `invocation` keys. For example, ++ +[source,nix] +---- +programs.broot.verbs = { + "p" = { execution = ":parent"; }; +}; +---- ++ +becomes ++ +[source,nix] +---- +programs.broot.verbs = [ + { + invocation = "p"; + execution = ":parent"; + } +]; +---- + +[[sec-release-21.03-state-version-changes]] +=== 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 +"21.03" or later. + +* Nothing has happened. diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000000..2d53d603a61f --- /dev/null +++ b/flake.nix @@ -0,0 +1,23 @@ +{ + description = "Home Manager for Nix"; + + outputs = { self, nixpkgs }: rec { + nixosModules.home-manager = import ./nixos; + + darwinModules.home-manager = import ./nix-darwin; + + lib = { + homeManagerConfiguration = { configuration, system, homeDirectory + , username + , pkgs ? builtins.getAttr system nixpkgs.outputs.legacyPackages + , check ? true }@args: + import ./modules { + inherit pkgs check; + configuration = { ... }: { + imports = [ configuration ]; + home = { inherit homeDirectory username; }; + }; + }; + }; + }; +} diff --git a/format b/format index 8c771b6db7d3..7d48cb8a111b 100755 --- a/format +++ b/format @@ -36,10 +36,8 @@ find . -name '*.nix' \ ! -path ./modules/programs/firefox.nix \ ! -path ./modules/programs/gpg.nix \ ! -path ./modules/programs/lesspipe.nix \ - ! -path ./modules/programs/neovim.nix \ ! -path ./modules/programs/ssh.nix \ ! -path ./modules/programs/tmux.nix \ - ! -path ./modules/programs/vscode.nix \ ! -path ./modules/programs/zsh.nix \ ! -path ./modules/services/gpg-agent.nix \ ! -path ./modules/services/kbfs.nix \ diff --git a/home-manager/completion.bash b/home-manager/completion.bash index e9f04578916a..d5b48258da25 100644 --- a/home-manager/completion.bash +++ b/home-manager/completion.bash @@ -285,7 +285,8 @@ _home-manager_completions () #--------------------------# local Options - Options=( "-f" "--file" "-b" "-A" "-I" "-h" "--help" "-n" "--dry-run" "-v" "--verbose" "--show-trace" ) + Options=( "-f" "--file" "-b" "-A" "-I" "-h" "--help" "-n" "--dry-run" "-v" "--verbose" "--show-trace" \ + "-j" "--max-jobs" ) # ^ « home-manager »'s options. diff --git a/home-manager/home-manager b/home-manager/home-manager index 6903c7bfeebc..632198386bb3 100644 --- a/home-manager/home-manager +++ b/home-manager/home-manager @@ -165,7 +165,11 @@ function doEdit() { setConfigFile - exec "$EDITOR" "$HOME_MANAGER_CONFIG" + # Don't quote $EDITOR in order to support values including options, e.g., + # "code --wait". + # + # shellcheck disable=2086 + exec $EDITOR "$HOME_MANAGER_CONFIG" } function doBuild() { @@ -368,6 +372,7 @@ function doUninstall() { HOME_MANAGER_CONFIG="$(mktemp --tmpdir home-manager.XXXXXXXXXX)" echo "{ lib, ... }: { home.file = lib.mkForce {}; }" > "$HOME_MANAGER_CONFIG" doSwitch + $DRY_RUN_CMD nix-env -e home-manager-path || true rm "$HOME_MANAGER_CONFIG" $DRY_RUN_CMD rm $VERBOSE_ARG -r \ "${XDG_DATA_HOME:-$HOME/.local/share}/home-manager" @@ -419,7 +424,7 @@ function doHelp() { echo " --cores NUM" echo " --keep-failed" echo " --keep-going" - echo " --max-jobs NUM" + echo " -j, --max-jobs NUM" echo " --option NAME VALUE" echo " --show-trace" echo " --(no-)substitute" @@ -496,7 +501,7 @@ while [[ $# -gt 0 ]]; do PASSTHROUGH_OPTS+=("$opt" "$1" "$2") shift 2 ;; - --max-jobs|--cores) + -j|--max-jobs|--cores) PASSTHROUGH_OPTS+=("$opt" "$1") shift ;; @@ -507,6 +512,10 @@ while [[ $# -gt 0 ]]; do -v|--verbose) export VERBOSE=1 ;; + --version) + echo 21.03 + exit 0 + ;; *) case $COMMAND in expire-generations|remove-generations) diff --git a/home-manager/install.nix b/home-manager/install.nix index 87252730e6fd..01697eb77b3a 100644 --- a/home-manager/install.nix +++ b/home-manager/install.nix @@ -45,7 +45,7 @@ runCommand "home-manager-install" { # You can update Home Manager without changing this value. See # the Home Manager release notes for a list of state version # changes in each release. - home.stateVersion = "20.09"; + home.stateVersion = "21.03"; } EOF fi @@ -71,7 +71,7 @@ runCommand "home-manager-install" { Uh oh, the installation failed! Please create an issue at - https://github.com/rycee/home-manager/issues + https://github.com/nix-community/home-manager/issues if the error seems to be the fault of Home Manager. EOF diff --git a/modules/files.nix b/modules/files.nix index e0ad8facac19..09ecf715497e 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -316,12 +316,15 @@ in } '' + concatStrings ( mapAttrsToList (n: v: '' - insertFile "${sourceStorePath v}" \ - "${v.target}" \ - "${if v.executable == null - then "inherit" - else builtins.toString v.executable}" \ - "${builtins.toString v.recursive}" + insertFile ${ + escapeShellArgs [ + (sourceStorePath v) + v.target + (if v.executable == null + then "inherit" + else toString v.executable) + (toString v.recursive) + ]} '') cfg )); }; diff --git a/modules/home-environment.nix b/modules/home-environment.nix index 209df4ffcfef..e5b6cc3a6ed8 100644 --- a/modules/home-environment.nix +++ b/modules/home-environment.nix @@ -196,6 +196,7 @@ in "$HOME" for state version < 20.09, undefined for state version ≥ 20.09 ''; + apply = toString; example = "/home/jane.doe"; description = "The user's home directory. Must be an absolute path."; }; @@ -263,6 +264,17 @@ in ''; }; + home.sessionPath = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ + ".git/safe/../../bin" + "\${xdg.configHome}/emacs/bin" + "~/.local/bin" + ]; + description = "Extra directories to add to PATH."; + }; + home.sessionVariablesExtra = mkOption { type = types.lines; default = ""; @@ -445,6 +457,8 @@ in export __HM_SESS_VARS_SOURCED=1 ${config.lib.shell.exportAll cfg.sessionVariables} + '' + lib.optionalString (cfg.sessionPath != [ ]) '' + export PATH="$PATH''${PATH:+:}${concatStringsSep ":" cfg.sessionPath}" '' + cfg.sessionVariablesExtra; } ) diff --git a/modules/lib/file-type.nix b/modules/lib/file-type.nix index f61a24ca67d3..56a3a1286a01 100644 --- a/modules/lib/file-type.nix +++ b/modules/lib/file-type.nix @@ -11,7 +11,7 @@ with lib; # Arguments: # - basePathDesc docbook compatible description of the base path # - basePath the file base path - fileType = basePathDesc: basePath: types.loaOf (types.submodule ( + fileType = basePathDesc: basePath: types.attrsOf (types.submodule ( { name, config, ... }: { options = { target = mkOption { @@ -32,7 +32,7 @@ with lib; type = types.nullOr types.lines; description = '' Text of the file. If this option is null then - home.file.<name?>.source + home.file.<name?>.source must be set. ''; }; @@ -41,7 +41,7 @@ with lib; type = types.path; description = '' Path of the source file or directory. If - home.file.<name?>.text + home.file.<name?>.text is non-null then this option will automatically point to a file containing that text. ''; diff --git a/modules/lib/gvariant.nix b/modules/lib/gvariant.nix index 3bfa18cb4db8..92aa7d98371a 100644 --- a/modules/lib/gvariant.nix +++ b/modules/lib/gvariant.nix @@ -123,7 +123,7 @@ in rec { mkString = v: mkPrimitive type.string v // { - __toString = self: "'${escape [ "'" ] self.value}'"; + __toString = self: "'${escape [ "'" "\\" ] self.value}'"; }; mkObjectpath = v: diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index 49d14ef06383..715b5b0bbc50 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -25,4 +25,16 @@ github = "cwyc"; githubId = 16950437; }; + olmokramer = { + name = "Olmo Kramer"; + email = "olmokramer@users.noreply.github.com"; + github = "olmokramer"; + githubId = 3612514; + }; + matrss = { + name = "Matthias Riße"; + email = "matrss@users.noreply.github.com"; + github = "matrss"; + githubId = 9308656; + }; } diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 10b0611e4987..4dbf7325a0dc 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -452,7 +452,7 @@ in { time = "2017-12-11T17:23:12+00:00"; - condition = config.home.activation ? reloadSystemD; + condition = config.home.activation ? reloadSystemd; message = '' The Boolean option 'systemd.user.startServices' is now available. When enabled the current naive systemd unit @@ -508,7 +508,7 @@ in configuration you can add imports = [ - "''${builtins.fetchTarball https://github.com/rycee/home-manager/archive/master.tar.gz}/nixos" + "''${builtins.fetchTarball https://github.com/nix-community/home-manager/archive/master.tar.gz}/nixos" ]; to your 'configuration.nix' file. This will introduce a new @@ -1363,7 +1363,7 @@ in To learn more, see the installation section of the manual - https://rycee.gitlab.io/home-manager/#sec-install-nixos-module + https://nix-community.github.io/home-manager/#sec-install-nixos-module ''; } @@ -1621,7 +1621,155 @@ in } { - time = "2020-08-07T17:32:36+00:00"; + time = "2020-08-13T22:15:27+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'programs.waybar' + ''; + } + + { + time = "2020-08-14T22:44:20+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.kanshi' + ''; + } + + { + time = "2020-08-25T22:14:01+00:00"; + message = '' + A new module is available: 'programs.mcfly' + ''; + } + + { + time = "2020-09-01T18:38:18+00:00"; + message = '' + A new module is available: 'programs.ncmpcpp' + ''; + } + + { + time = "2020-09-11T10:06:47+00:00"; + condition = hostPlatform.isLinux && config.targets.genericLinux.enable; + message = '' + A new option 'targets.genericLinux.extraXdgDataDirs' is available + to setup the user environment with the OS's data files. + + This is useful for example to get Bash completion for + 'systemctl' which shouldn't be installed through Home Manager. + + This is also useful to have non Home Manager applications + available in menus. + ''; + } + + { + time = "2020-09-09T06:54:59+00:00"; + condition = config.programs.man.enable; + message = '' + A new option 'programs.man.generateCaches' was added to + support the apropos command. + ''; + } + + { + time = "2020-09-22T21:03:28+00:00"; + message = '' + A new module is available: 'programs.pet'. + ''; + } + + { + time = "2020-09-29T21:21:44+00:00"; + message = '' + A new module is available: 'programs.mu'. + ''; + } + + { + time = "2020-10-08T21:28:16+00:00"; + message = '' + A new module is available: 'programs.autojump' + + The option `programs.bash.enableAutojump` is deprecated and this new + module should be used instead. + ''; + } + + { + time = "2020-10-12T00:12:23+00:00"; + condition = config.programs.zsh.enable; + message = '' + A new zsh submodule is available: 'programs.zsh.prezto'. + ''; + } + + { + time = "2020-10-22T21:10:38+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.gammastep'. + ''; + } + + { + time = "2020-10-22T21:30:42+00:00"; + message = '' + A new module is available: 'programs.gh'. + ''; + } + + { + time = "2020-11-01T11:17:02+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.caffeine'. + ''; + } + + { + time = "2020-11-05T22:59:21+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'programs.i3status-rust'. + ''; + } + + { + time = "2020-11-14T13:02:40+00:00"; + condition = config.programs.broot.enable; + message = '' + The 'programs.broot.verbs' option is now a list rather than an + attribute set. To migrate, move the keys of the attrset into the + list items' 'invocation' keys. For example, + + programs.broot.verbs = { + "p" = { execution = ":parent"; }; + }; + + becomes + + programs.broot.verbs = [ + { + invocation = "p"; + execution = ":parent"; + } + ]; + ''; + } + + { + time = "2020-12-01T20:46:14+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.wlsunset'. + ''; + } + + { + time = "2020-12-05T17:32:36+00:00"; condition = config.services.polybar.enable; message = '' The polybar configuration can now be written in a more nix-friendly format. diff --git a/modules/misc/nixpkgs.nix b/modules/misc/nixpkgs.nix index 7b0904a5f20a..511dbec10b21 100644 --- a/modules/misc/nixpkgs.nix +++ b/modules/misc/nixpkgs.nix @@ -1,6 +1,6 @@ # Adapted from Nixpkgs. -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, pkgsPath, ... }: with lib; @@ -49,7 +49,7 @@ let merge = lib.mergeOneOption; }; - _pkgs = import ( + _pkgs = import pkgsPath ( filterAttrs (n: v: v != null) config.nixpkgs ); diff --git a/modules/misc/numlock.nix b/modules/misc/numlock.nix index 199dd317daa3..c823f6dbdd21 100644 --- a/modules/misc/numlock.nix +++ b/modules/misc/numlock.nix @@ -7,6 +7,8 @@ let cfg = config.xsession.numlock; in { + meta.maintainers = [ maintainers.evanjs ]; + options = { xsession.numlock.enable = mkEnableOption "Num Lock"; }; config = mkIf cfg.enable { diff --git a/modules/misc/version.nix b/modules/misc/version.nix index fbeb3ec539ad..9c742f4847cd 100644 --- a/modules/misc/version.nix +++ b/modules/misc/version.nix @@ -5,7 +5,7 @@ with lib; { options = { home.stateVersion = mkOption { - type = types.enum [ "18.09" "19.03" "19.09" "20.03" "20.09" ]; + type = types.enum [ "18.09" "19.03" "19.09" "20.03" "20.09" "21.03" ]; default = "18.09"; description = '' It is occasionally necessary for Home Manager to change diff --git a/modules/misc/xdg-mime.nix b/modules/misc/xdg-mime.nix index f41e3160f66b..5999e1299c98 100644 --- a/modules/misc/xdg-mime.nix +++ b/modules/misc/xdg-mime.nix @@ -27,6 +27,14 @@ in { home.packages = [ # Explicitly install package to provide basic mime types. pkgs.shared-mime-info + + # Make sure the target directories will be real directories. + (pkgs.runCommandLocal "dummy-xdg-mime-dirs1" { } '' + mkdir -p $out/share/{applications,mime/packages} + '') + (pkgs.runCommandLocal "dummy-xdg-mime-dirs2" { } '' + mkdir -p $out/share/{applications,mime/packages} + '') ]; home.extraProfileCommands = '' diff --git a/modules/misc/xdg-user-dirs.nix b/modules/misc/xdg-user-dirs.nix index 854eaf83026e..a1db6b115a1a 100644 --- a/modules/misc/xdg-user-dirs.nix +++ b/modules/misc/xdg-user-dirs.nix @@ -89,16 +89,21 @@ in { }; config = mkIf cfg.enable { - xdg.configFile."user-dirs.dirs".text = generators.toKeyValue { } ({ - XDG_DESKTOP_DIR = cfg.desktop; - XDG_DOCUMENTS_DIR = cfg.documents; - XDG_DOWNLOAD_DIR = cfg.download; - XDG_MUSIC_DIR = cfg.music; - XDG_PICTURES_DIR = cfg.pictures; - XDG_PUBLICSHARE_DIR = cfg.publicShare; - XDG_TEMPLATES_DIR = cfg.templates; - XDG_VIDEOS_DIR = cfg.videos; - } // cfg.extraConfig); + xdg.configFile."user-dirs.dirs".text = let + options = { + XDG_DESKTOP_DIR = cfg.desktop; + XDG_DOCUMENTS_DIR = cfg.documents; + XDG_DOWNLOAD_DIR = cfg.download; + XDG_MUSIC_DIR = cfg.music; + XDG_PICTURES_DIR = cfg.pictures; + XDG_PUBLICSHARE_DIR = cfg.publicShare; + XDG_TEMPLATES_DIR = cfg.templates; + XDG_VIDEOS_DIR = cfg.videos; + } // cfg.extraConfig; + + # For some reason, these need to be wrapped with quotes to be valid. + wrapped = mapAttrs (_: value: ''"${value}"'') options; + in generators.toKeyValue { } wrapped; xdg.configFile."user-dirs.conf".text = "enabled=False"; }; diff --git a/modules/misc/xdg.nix b/modules/misc/xdg.nix index 7420e8e92b3c..f207d7d353f7 100644 --- a/modules/misc/xdg.nix +++ b/modules/misc/xdg.nix @@ -101,11 +101,13 @@ in { home.file = mkMerge [ - cfg.configFile - cfg.dataFile - { - "${config.xdg.cacheHome}/.keep".text = ""; - } + (mapAttrs' + (name: file: nameValuePair "${config.xdg.configHome}/${name}" file) + cfg.configFile) + (mapAttrs' + (name: file: nameValuePair "${config.xdg.dataHome}/${name}" file) + cfg.dataFile) + { "${config.xdg.cacheHome}/.keep".text = ""; } ]; } ]; diff --git a/modules/modules.nix b/modules/modules.nix index 76ff5aaa8c0c..b599fe99099a 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -49,13 +49,14 @@ let (loadModule ./programs/alot.nix { }) (loadModule ./programs/aria2.nix { }) (loadModule ./programs/astroid.nix { }) + (loadModule ./programs/autojump.nix { }) (loadModule ./programs/autorandr.nix { }) (loadModule ./programs/bash.nix { }) (loadModule ./programs/bat.nix { }) (loadModule ./programs/beets.nix { }) (loadModule ./programs/broot.nix { }) (loadModule ./programs/browserpass.nix { }) - (loadModule ./programs/chromium.nix { condition = hostPlatform.isLinux; }) + (loadModule ./programs/chromium.nix { }) (loadModule ./programs/command-not-found/command-not-found.nix { }) (loadModule ./programs/dircolors.nix { }) (loadModule ./programs/direnv.nix { }) @@ -66,6 +67,7 @@ let (loadModule ./programs/fish.nix { }) (loadModule ./programs/fzf.nix { }) (loadModule ./programs/getmail.nix { condition = hostPlatform.isLinux; }) + (loadModule ./programs/gh.nix { }) (loadModule ./programs/git.nix { }) (loadModule ./programs/gnome-terminal.nix { }) (loadModule ./programs/go.nix { }) @@ -73,6 +75,7 @@ let (loadModule ./programs/home-manager.nix { }) (loadModule ./programs/htop.nix { }) (loadModule ./programs/i3status.nix { }) + (loadModule ./programs/i3status-rust.nix { condition = hostPlatform.isLinux; }) (loadModule ./programs/info.nix { }) (loadModule ./programs/irssi.nix { }) (loadModule ./programs/lieer.nix { }) @@ -86,9 +89,12 @@ let (loadModule ./programs/man.nix { }) (loadModule ./programs/matplotlib.nix { }) (loadModule ./programs/mbsync.nix { }) + (loadModule ./programs/mcfly.nix { }) (loadModule ./programs/mercurial.nix { }) (loadModule ./programs/mpv.nix { }) (loadModule ./programs/msmtp.nix { }) + (loadModule ./programs/mu.nix { }) + (loadModule ./programs/ncmpcpp.nix { }) (loadModule ./programs/ne.nix { }) (loadModule ./programs/neomutt.nix { }) (loadModule ./programs/neovim.nix { }) @@ -101,6 +107,7 @@ let (loadModule ./programs/opam.nix { }) (loadModule ./programs/password-store.nix { }) (loadModule ./programs/pazi.nix { }) + (loadModule ./programs/pet.nix { }) (loadModule ./programs/pidgin.nix { }) (loadModule ./programs/powerline-go.nix { }) (loadModule ./programs/qutebrowser.nix { }) @@ -118,12 +125,15 @@ let (loadModule ./programs/vim.nix { }) (loadModule ./programs/vscode.nix { }) (loadModule ./programs/vscode/haskell.nix { }) + (loadModule ./programs/waybar.nix { condition = hostPlatform.isLinux; }) (loadModule ./programs/z-lua.nix { }) (loadModule ./programs/zathura.nix { }) (loadModule ./programs/zoxide.nix { }) (loadModule ./programs/zplug.nix { }) (loadModule ./programs/zsh.nix { }) + (loadModule ./programs/zsh/prezto.nix { }) (loadModule ./services/blueman-applet.nix { }) + (loadModule ./services/caffeine.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/cbatticon.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/clipmenu.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/compton.nix { }) @@ -133,12 +143,14 @@ let (loadModule ./services/emacs.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/flameshot.nix { }) (loadModule ./services/fluidsynth.nix { condition = hostPlatform.isLinux; }) + (loadModule ./services/gammastep.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/getmail.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/gnome-keyring.nix { }) (loadModule ./services/gpg-agent.nix { }) (loadModule ./services/grobi.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/hound.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/imapnotify.nix { condition = hostPlatform.isLinux; }) + (loadModule ./services/kanshi.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/kbfs.nix { }) (loadModule ./services/kdeconnect.nix { }) (loadModule ./services/keepassx.nix { }) @@ -180,11 +192,13 @@ let (loadModule ./services/window-managers/i3-sway/i3.nix { }) (loadModule ./services/window-managers/i3-sway/sway.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/window-managers/xmonad.nix { }) + (loadModule ./services/wlsunset.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/xcape.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/xembed-sni-proxy.nix { condition = hostPlatform.isLinux; }) (loadModule ./services/xscreensaver.nix { }) (loadModule ./services/xsuspender.nix { condition = hostPlatform.isLinux; }) (loadModule ./systemd.nix { }) + (loadModule ./targets/darwin.nix { condition = hostPlatform.isDarwin; }) (loadModule ./targets/generic-linux.nix { condition = hostPlatform.isLinux; }) (loadModule ./xcursor.nix { }) (loadModule ./xresources.nix { }) @@ -195,9 +209,14 @@ let modules = map (getAttr "file") (filter (getAttr "condition") allModules); - pkgsModule = { + pkgsModule = { config, ... }: { config = { _module.args.baseModules = modules; + _module.args.pkgsPath = lib.mkDefault ( + if versionAtLeast config.home.stateVersion "20.09" then + pkgs.path + else + ); _module.args.pkgs = lib.mkDefault pkgs; _module.check = check; lib = lib.hm; diff --git a/modules/programs/alacritty.nix b/modules/programs/alacritty.nix index ea908f2b0566..9c3e8e75dc40 100644 --- a/modules/programs/alacritty.nix +++ b/modules/programs/alacritty.nix @@ -3,9 +3,8 @@ with lib; let - cfg = config.programs.alacritty; - + yamlFormat = pkgs.formats.yaml { }; in { options = { programs.alacritty = { @@ -19,7 +18,7 @@ in { }; settings = mkOption { - type = types.attrs; + type = yamlFormat.type; default = { }; example = literalExample '' { @@ -51,6 +50,11 @@ in { home.packages = [ cfg.package ]; xdg.configFile."alacritty/alacritty.yml" = mkIf (cfg.settings != { }) { + # TODO: Replace by the generate function but need to figure out how to + # handle the escaping first. + # + # source = yamlFormat.generate "alacritty.yml" cfg.settings; + text = replaceStrings [ "\\\\" ] [ "\\" ] (builtins.toJSON cfg.settings); }; diff --git a/modules/programs/astroid-accounts.nix b/modules/programs/astroid-accounts.nix index 17544ff78996..fb803867efbe 100644 --- a/modules/programs/astroid-accounts.nix +++ b/modules/programs/astroid-accounts.nix @@ -16,7 +16,7 @@ with lib; }; extraConfig = mkOption { - type = types.attrs; + type = types.attrsOf types.anything; default = { }; example = { select_query = ""; }; description = '' diff --git a/modules/programs/astroid.nix b/modules/programs/astroid.nix index af12b10edbbf..8af18f16c1dd 100644 --- a/modules/programs/astroid.nix +++ b/modules/programs/astroid.nix @@ -7,6 +7,8 @@ let cfg = config.programs.astroid; + jsonFormat = pkgs.formats.json { }; + astroidAccounts = filterAttrs (n: v: v.astroid.enable) config.accounts.email.accounts; @@ -36,19 +38,18 @@ let } // astroid.extraConfig; # See https://github.com/astroidmail/astroid/wiki/Configuration-Reference - configFile = mailAccounts: - let - template = fromJSON (readFile ./astroid-config-template.json); - astroidConfig = foldl' recursiveUpdate template [ - { - astroid.notmuch_config = "${config.xdg.configHome}/notmuch/notmuchrc"; - accounts = mapAttrs (n: accountAttr) astroidAccounts; - crypto.gpg.path = "${pkgs.gnupg}/bin/gpg"; - } - cfg.extraConfig - cfg.externalEditor - ]; - in builtins.toJSON astroidConfig; + finalConfig = let + template = fromJSON (readFile ./astroid-config-template.json); + astroidConfig = foldl' recursiveUpdate template [ + { + astroid.notmuch_config = "${config.xdg.configHome}/notmuch/notmuchrc"; + accounts = mapAttrs (n: accountAttr) astroidAccounts; + crypto.gpg.path = "${pkgs.gnupg}/bin/gpg"; + } + cfg.extraConfig + cfg.externalEditor + ]; + in astroidConfig; in { options = { @@ -90,9 +91,13 @@ in { }; extraConfig = mkOption { - type = types.attrs; + type = jsonFormat.type; default = { }; - example = { poll.interval = 0; }; + example = literalExample '' + { + poll.interval = 0; + } + ''; description = '' JSON config that will override the default Astroid configuration. ''; @@ -107,13 +112,8 @@ in { config = mkIf cfg.enable { home.packages = [ pkgs.astroid ]; - xdg.configFile."astroid/config".source = pkgs.runCommand "out.json" { - json = configFile astroidAccounts; - preferLocalBuild = true; - allowSubstitutes = false; - } '' - echo -n "$json" | ${pkgs.jq}/bin/jq . > $out - ''; + xdg.configFile."astroid/config".source = + jsonFormat.generate "astroid-config" finalConfig; xdg.configFile."astroid/poll.sh" = { executable = true; diff --git a/modules/programs/autojump.nix b/modules/programs/autojump.nix new file mode 100644 index 000000000000..db3bdaf593c0 --- /dev/null +++ b/modules/programs/autojump.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.autojump; + package = pkgs.autojump; + +in { + meta.maintainers = [ maintainers.evanjs ]; + + options.programs.autojump = { + enable = mkEnableOption "autojump"; + + enableBashIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Bash integration. + ''; + }; + + enableZshIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Zsh integration. + ''; + }; + + enableFishIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Fish integration. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ package ]; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration (mkBefore '' + . ${package}/share/autojump/autojump.bash + ''); + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + . ${package}/share/autojump/autojump.zsh + ''; + + programs.fish.promptInit = mkIf cfg.enableFishIntegration '' + . ${package}/share/autojump/autojump.fish + ''; + }; +} diff --git a/modules/programs/bash.nix b/modules/programs/bash.nix index 5758cbc11958..6338f5e4a59e 100644 --- a/modules/programs/bash.nix +++ b/modules/programs/bash.nix @@ -11,6 +11,14 @@ in { meta.maintainers = [ maintainers.rycee ]; + imports = [ + (mkRenamedOptionModule [ "programs" "bash" "enableAutojump" ] [ + "programs" + "autojump" + "enable" + ]) + ]; + options = { programs.bash = { enable = mkEnableOption "GNU Bourne-Again SHell"; @@ -94,12 +102,6 @@ in ''; }; - enableAutojump = mkOption { - default = false; - type = types.bool; - description = "Enable the autojump navigation tool."; - }; - profileExtra = mkOption { default = ""; type = types.lines; @@ -177,9 +179,6 @@ in ${aliasesStr} ${cfg.initExtra} - - ${optionalString cfg.enableAutojump - ". ${pkgs.autojump}/share/autojump/autojump.bash"} fi ''; @@ -216,9 +215,6 @@ in ${cfg.logoutExtra} ''; }; - - home.packages = - optional (cfg.enableAutojump) pkgs.autojump; } ); } diff --git a/modules/programs/beets.nix b/modules/programs/beets.nix index 1a45bbea1c70..6eb183dd1e07 100644 --- a/modules/programs/beets.nix +++ b/modules/programs/beets.nix @@ -6,6 +6,8 @@ let cfg = config.programs.beets; + yamlFormat = pkgs.formats.yaml { }; + in { meta.maintainers = [ maintainers.rycee ]; @@ -39,7 +41,7 @@ in { }; settings = mkOption { - type = types.attrs; + type = yamlFormat.type; default = { }; description = '' Configuration written to @@ -52,7 +54,7 @@ in { config = mkIf cfg.enable { home.packages = [ cfg.package ]; - xdg.configFile."beets/config.yaml".text = - builtins.toJSON config.programs.beets.settings; + xdg.configFile."beets/config.yaml".source = + yamlFormat.generate "beets-config" cfg.settings; }; } diff --git a/modules/programs/broot.nix b/modules/programs/broot.nix index 6951e035d32a..e711e8ae255f 100644 --- a/modules/programs/broot.nix +++ b/modules/programs/broot.nix @@ -18,8 +18,7 @@ let ''; brootConf = { - verbs = - mapAttrsToList (name: value: value // { invocation = name; }) cfg.verbs; + verbs = cfg.verbs; skin = cfg.skin; }; @@ -54,41 +53,60 @@ in { }; verbs = mkOption { - type = with types; attrsOf (attrsOf (either bool str)); - default = { - "p" = { execution = ":parent"; }; - "edit" = { + type = with types; listOf (attrsOf (either bool str)); + default = [ + { + invocation = "p"; + execution = ":parent"; + } + { + invocation = "edit"; shortcut = "e"; execution = "$EDITOR {file}"; - }; - "create {subpath}" = { execution = "$EDITOR {directory}/{subpath}"; }; - "view" = { execution = "less {file}"; }; - }; - example = literalExample '' + } + { + invocation = "create {subpath}"; + execution = "$EDITOR {directory}/{subpath}"; + } { - "p" = { execution = ":parent"; }; - "edit" = { shortcut = "e"; execution = "$EDITOR {file}" ; }; - "create {subpath}" = { execution = "$EDITOR {directory}/{subpath}"; }; - "view" = { execution = "less {file}"; }; - "blop {name}\\.{type}" = { + invocation = "view"; + execution = "less {file}"; + } + ]; + example = literalExample '' + [ + { invocation = "p"; execution = ":parent"; } + { invocation = "edit"; shortcut = "e"; execution = "$EDITOR {file}" ; } + { invocation = "create {subpath}"; execution = "$EDITOR {directory}/{subpath}"; } + { invocation = "view"; execution = "less {file}"; } + { + invocation = "blop {name}\\.{type}"; execution = "/bin/mkdir {parent}/{type} && /usr/bin/nvim {parent}/{type}/{name}.{type}"; from_shell = true; - }; - } + } + ] ''; description = '' - Define new verbs. The attribute name indicates how the verb is - called by the user, with placeholders for arguments. + Define new verbs. For more information, see + . The possible attributes are: + + invocation (optional) + how the verb is called by the user, with placeholders for arguments + execution (mandatory) how the verb is executed + + key (optional) + a keyboard key triggering execution + shortcut (optional) an alternate way to call the verb (without diff --git a/modules/programs/direnv.nix b/modules/programs/direnv.nix index 1d1374b8e260..92a6e6688538 100644 --- a/modules/programs/direnv.nix +++ b/modules/programs/direnv.nix @@ -5,16 +5,8 @@ with lib; let cfg = config.programs.direnv; - configFile = config: - pkgs.runCommand "config.toml" { - buildInputs = [ pkgs.remarshal ]; - preferLocalBuild = true; - allowSubstitutes = false; - } '' - remarshal -if json -of toml \ - < ${pkgs.writeText "config.json" (builtins.toJSON config)} \ - > $out - ''; + + tomlFormat = pkgs.formats.toml { }; in { meta.maintainers = [ maintainers.rycee ]; @@ -23,7 +15,7 @@ in { enable = mkEnableOption "direnv, the environment switcher"; config = mkOption { - type = types.attrs; + type = tomlFormat.type; default = { }; description = '' Configuration written to @@ -80,8 +72,9 @@ in { config = mkIf cfg.enable { home.packages = [ pkgs.direnv ]; - xdg.configFile."direnv/config.toml" = - mkIf (cfg.config != { }) { source = configFile cfg.config; }; + xdg.configFile."direnv/config.toml" = mkIf (cfg.config != { }) { + source = tomlFormat.generate "direnv-config" cfg.config; + }; xdg.configFile."direnv/direnvrc" = let text = concatStringsSep "\n" (optional (cfg.stdlib != "") cfg.stdlib diff --git a/modules/programs/feh.nix b/modules/programs/feh.nix index b1b33697e958..e098342b53c7 100644 --- a/modules/programs/feh.nix +++ b/modules/programs/feh.nix @@ -6,8 +6,22 @@ let cfg = config.programs.feh; - disableBinding = func: key: func; - enableBinding = func: key: "${func} ${toString key}"; + bindingsOf = t: with types; attrsOf (nullOr (either t (listOf t))); + + renderBindings = bindings: + let + enabled = filterAttrs (n: v: v != null) bindings; + disabled = filterAttrs (n: v: v == null) bindings; + render = mapAttrsToList renderBinding; + in concatStringsSep "\n" (render disabled ++ render enabled); + + renderBinding = func: key: + if key == null then + func + else if isList key then + concatStringsSep " " ([ func ] ++ map toString key) + else + "${func} ${toString key}"; in { options.programs.feh = { @@ -15,14 +29,16 @@ in { buttons = mkOption { default = { }; - type = with types; attrsOf (nullOr (either str int)); + type = with types; bindingsOf (either str int); example = { zoom_in = 4; zoom_out = "C-4"; + prev_img = [ 3 "C-3" ]; }; description = '' Override feh's default mouse button mapping. If you want to disable an - action, set its value to null. + action, set its value to null. If you want to bind multiple buttons to + an action, set its value to a list. See for default bindings and available commands. ''; @@ -30,14 +46,16 @@ in { keybindings = mkOption { default = { }; - type = types.attrsOf (types.nullOr types.str); + type = bindingsOf types.str; example = { zoom_in = "plus"; zoom_out = "minus"; + prev_img = [ "h" "Left" ]; }; description = '' Override feh's default keybindings. If you want to disable a keybinding - set its value to null. + set its value to null. If you want to bind multiple keys to an action, + set its value to a list. See for default bindings and available commands. ''; @@ -53,18 +71,11 @@ in { home.packages = [ pkgs.feh ]; - xdg.configFile."feh/buttons".text = '' - ${concatStringsSep "\n" (mapAttrsToList disableBinding - (filterAttrs (n: v: v == null) cfg.buttons))} - ${concatStringsSep "\n" (mapAttrsToList enableBinding - (filterAttrs (n: v: v != null) cfg.buttons))} - ''; + xdg.configFile."feh/buttons" = + mkIf (cfg.buttons != { }) { text = renderBindings cfg.buttons + "\n"; }; - xdg.configFile."feh/keys".text = '' - ${concatStringsSep "\n" (mapAttrsToList disableBinding - (filterAttrs (n: v: v == null) cfg.keybindings))} - ${concatStringsSep "\n" (mapAttrsToList enableBinding - (filterAttrs (n: v: v != null) cfg.keybindings))} - ''; + xdg.configFile."feh/keys" = mkIf (cfg.keybindings != { }) { + text = renderBindings cfg.keybindings + "\n"; + }; }; } diff --git a/modules/programs/firefox.nix b/modules/programs/firefox.nix index d5003f59edc6..eafeef47f312 100644 --- a/modules/programs/firefox.nix +++ b/modules/programs/firefox.nix @@ -220,6 +220,17 @@ in default = false; description = "Whether to enable the unfree Adobe Flash plugin."; }; + + enableGnomeExtensions = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the GNOME Shell native host connector. Note, you + also need to set the NixOS option + services.gnome3.chrome-gnome-shell.enable to + true. + ''; + }; }; }; @@ -262,6 +273,7 @@ in # The configuration expected by the Firefox wrapper. fcfg = { enableAdobeFlash = cfg.enableAdobeFlash; + enableGnomeExtensions = cfg.enableGnomeExtensions; }; # A bit of hackery to force a config into the wrapper. diff --git a/modules/programs/gh.nix b/modules/programs/gh.nix new file mode 100644 index 000000000000..41d6aa1dec14 --- /dev/null +++ b/modules/programs/gh.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.gh; + +in { + meta.maintainers = [ maintainers.gerschtli ]; + + options.programs.gh = { + enable = mkEnableOption "GitHub CLI tool"; + + aliases = mkOption { + type = with types; attrsOf str; + default = { }; + example = literalExample '' + { + co = "pr checkout"; + pv = "pr view"; + } + ''; + description = '' + Aliases that allow you to create nicknames for gh commands. + ''; + }; + + editor = mkOption { + type = types.str; + default = ""; + description = '' + The editor that gh should run when creating issues, pull requests, etc. + If blank, will refer to environment. + ''; + }; + + gitProtocol = mkOption { + type = types.enum [ "https" "ssh" ]; + default = "https"; + description = '' + The protocol to use when performing Git operations. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.gitAndTools.gh ]; + + xdg.configFile."gh/config.yml".text = + builtins.toJSON { inherit (cfg) aliases editor gitProtocol; }; + }; +} diff --git a/modules/programs/git.nix b/modules/programs/git.nix index a174fa0c4eec..78346c6abb45 100644 --- a/modules/programs/git.nix +++ b/modules/programs/git.nix @@ -101,7 +101,7 @@ let }; contents = mkOption { - type = types.attrs; + type = types.attrsOf types.anything; default = { }; description = '' Configuration to include. If empty then a path must be given. @@ -277,7 +277,14 @@ in { genIdentity = name: account: with account; nameValuePair "sendemail.${name}" ({ - smtpEncryption = if smtp.tls.enable then "tls" else ""; + smtpEncryption = if smtp.tls.enable then + (if smtp.tls.useStartTls + || versionOlder config.home.stateVersion "20.09" then + "tls" + else + "ssl") + else + ""; smtpServer = smtp.host; smtpUser = userName; from = address; diff --git a/modules/programs/gnome-terminal.nix b/modules/programs/gnome-terminal.nix index 373ff756bb60..dec2a10c59e0 100644 --- a/modules/programs/gnome-terminal.nix +++ b/modules/programs/gnome-terminal.nix @@ -6,6 +6,14 @@ let cfg = config.programs.gnome-terminal; + eraseBinding = types.enum [ + "auto" + "ascii-backspace" + "ascii-delete" + "delete-sequence" + "tty" + ]; + backForeSubModule = types.submodule ({ ... }: { options = { foreground = mkOption { @@ -122,6 +130,104 @@ let The number of scrollback lines to keep, null for infinite. ''; }; + + customCommand = mkOption { + default = null; + type = types.nullOr types.str; + description = '' + The command to use to start the shell, or null for default shell. + ''; + }; + + loginShell = mkOption { + default = false; + type = types.bool; + description = "Run command as a login shell."; + }; + + backspaceBinding = mkOption { + default = "ascii-delete"; + type = eraseBinding; + description = '' + Which string the terminal should send to an application when the user + presses the Backspace key. + + + + auto + + Attempt to determine the right value from the terminal's IO settings. + + + + ascii-backspace + + Send an ASCII backspace character (0x08). + + + + ascii-delete + + Send an ASCII delete character (0x7F). + + + + delete-sequence + + Send the @7 control sequence. + + + + tty + + Send terminal’s erase setting. + + + + ''; + }; + + deleteBinding = mkOption { + default = "delete-sequence"; + type = eraseBinding; + description = '' + Which string the terminal should send to an application when the user + presses the Delete key. + + + + auto + + Send the @7 control sequence. + + + + ascii-backspace + + Send an ASCII backspace character (0x08). + + + + ascii-delete + + Send an ASCII delete character (0x7F). + + + + delete-sequence + + Send the @7 control sequence. + + + + tty + + Send terminal’s erase setting. + + + + ''; + }; }; }); @@ -132,7 +238,15 @@ let scrollback-lines = pcfg.scrollbackLines; cursor-shape = pcfg.cursorShape; cursor-blink-mode = pcfg.cursorBlinkMode; - } // (if (pcfg.font == null) then { + login-shell = pcfg.loginShell; + backspace-binding = pcfg.backspaceBinding; + delete-binding = pcfg.deleteBinding; + } // (if (pcfg.customCommand != null) then { + use-custom-command = true; + custom-command = pcfg.customCommand; + } else { + use-custom-command = false; + }) // (if (pcfg.font == null) then { use-system-font = true; } else { use-system-font = false; diff --git a/modules/programs/htop.nix b/modules/programs/htop.nix index 849660405348..1fb397cdc38d 100644 --- a/modules/programs/htop.nix +++ b/modules/programs/htop.nix @@ -61,6 +61,9 @@ let CGROUP = 112; OOM = 113; IO_PRIORITY = 114; + M_PSS = 118; + M_SWAP = 119; + M_PSSWP = 120; }; # Mapping from names to defaults @@ -76,16 +79,32 @@ let Hostname = 2; AllCPUs = 1; AllCPUs2 = 1; + AllCPUs4 = 1; LeftCPUs = 1; RightCPUs = 1; + Right = 1; + CPUs = 1; LeftCPUs2 = 1; RightCPUs2 = 1; + LeftCPUs4 = 1; + RightCPUs4 = 1; Blank = 2; + PressureStallCPUSome = 2; + PressureStallIOSome = 2; + PressureStallIOFull = 2; + PressureStallMemorySome = 2; + PressureStallMemoryFull = 2; + ZFSARC = 2; + ZFSCARC = 2; CPU = 1; "CPU(1)" = 1; "CPU(2)" = 1; "CPU(3)" = 1; "CPU(4)" = 1; + "CPU(5)" = 1; + "CPU(6)" = 1; + "CPU(7)" = 1; + "CPU(8)" = 1; }; singleMeterType = let @@ -268,6 +287,18 @@ in { description = "Count CPUs from 0 instead of 1."; }; + showCpuUsage = mkOption { + type = types.bool; + default = false; + description = "Show CPU usage frequency."; + }; + + showCpuFrequency = mkOption { + type = types.bool; + default = false; + description = "Show CPU frequency."; + }; + updateProcessNames = mkOption { type = types.bool; default = false; @@ -287,6 +318,12 @@ in { description = "Which color scheme to use."; }; + enableMouse = mkOption { + type = types.bool; + default = true; + description = "Enable mouse support."; + }; + delay = mkOption { type = types.int; default = 15; @@ -328,6 +365,11 @@ in { type = meterType; }; + vimMode = mkOption { + type = types.bool; + default = false; + description = "Vim key bindings."; + }; }; config = mkIf cfg.enable { @@ -357,14 +399,18 @@ in { header_margin=${bool cfg.headerMargin} detailed_cpu_time=${bool cfg.detailedCpuTime} cpu_count_from_zero=${bool cfg.cpuCountFromZero} + show_cpu_usage=${bool cfg.showCpuUsage} + show_cpu_frequency=${bool cfg.showCpuFrequency} update_process_names=${bool cfg.updateProcessNames} account_guest_in_cpu_meter=${bool cfg.accountGuestInCpuMeter} color_scheme=${toString cfg.colorScheme} + enable_mouse=${bool cfg.enableMouse} delay=${toString cfg.delay} left_meters=${list leftMeters} left_meter_modes=${list leftModes} right_meters=${list rightMeters} right_meter_modes=${list rightModes} + vim_mode=${bool cfg.vimMode} ''; }; } diff --git a/modules/programs/i3status-rust.nix b/modules/programs/i3status-rust.nix new file mode 100644 index 000000000000..5f44c818db05 --- /dev/null +++ b/modules/programs/i3status-rust.nix @@ -0,0 +1,265 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.i3status-rust; + + restartI3 = '' + i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.* + if [ -S $i3Socket ]; then + echo "Reloading i3" + $DRY_RUN_CMD ${config.xsession.windowManager.i3.package}/bin/i3-msg -s $i3Socket restart 1>/dev/null + fi + ''; + + settingsFormat = pkgs.formats.toml { }; + +in { + meta.maintainers = [ maintainers.farlion ]; + + options.programs.i3status-rust = { + enable = mkEnableOption "a replacement for i3-status written in Rust"; + + bars = mkOption { + type = types.attrsOf (types.submodule { + options = { + + blocks = mkOption { + type = settingsFormat.type; + default = [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "memory"; + display_type = "memory"; + format_mem = "{Mup}%"; + format_swap = "{SUp}%"; + } + { + block = "cpu"; + interval = 1; + } + { + block = "load"; + interval = 1; + format = "{1m}"; + } + { block = "sound"; } + { + block = "time"; + interval = 60; + format = "%a %d/%m %R"; + } + ]; + description = '' + Configuration blocks to add to i3status-rust + config. See + + for block options. + ''; + example = literalExample '' + [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "sound"; + format = "{output_name} {volume}%"; + on_click = "pavucontrol --tab=3"; + mappings = { + "alsa_output.pci-0000_00_1f.3.analog-stereo" = ""; + "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = "" + }; + } + ]; + ''; + }; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + description = '' + Any extra options to add to i3status-rust + config. + ''; + example = literalExample '' + { + theme = { + name = "solarized-dark"; + overrides = { + idle_bg = "#123456"; + idle_fg = "#abcdef"; + }; + }; + } + ''; + }; + + icons = mkOption { + type = types.str; + default = "none"; + description = '' + The icons set to use. See + + for a list of available icon sets. + ''; + example = "awesome5"; + }; + + theme = mkOption { + type = types.str; + default = "plain"; + description = '' + The theme to use. See + + for a list of available themes. + ''; + example = "gruvbox-dark"; + }; + }; + }); + + default = { + default = { + blocks = [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "memory"; + display_type = "memory"; + format_mem = "{Mup}%"; + format_swap = "{SUp}%"; + } + { + block = "cpu"; + interval = 1; + } + { + block = "load"; + interval = 1; + format = "{1m}"; + } + { block = "sound"; } + { + block = "time"; + interval = 60; + format = "%a %d/%m %R"; + } + ]; + }; + }; + description = '' + Attribute set of i3status-rust bars, each with their own configuration. + Each bar name generates a config file suffixed with + the bar's name from the attribute set, like so: + config-name.toml. + + This way, multiple config files can be generated, such as for having a + top and a bottom bar. + + See + + i3status-rust + 1 + + for options. + ''; + example = literalExample '' + bottom = { + blocks = [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "memory"; + display_type = "memory"; + format_mem = "{Mup}%"; + format_swap = "{SUp}%"; + } + { + block = "cpu"; + interval = 1; + } + { + block = "load"; + interval = 1; + format = "{1m}"; + } + { block = "sound"; } + { + block = "time"; + interval = 60; + format = "%a %d/%m %R"; + } + ]; + settings = { + theme = { + name = "solarized-dark"; + overrides = { + idle_bg = "#123456"; + idle_fg = "#abcdef"; + }; + }; + }; + icons = "awesome5"; + theme = "gruvbox-dark"; + }; + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.i3status-rust; + defaultText = literalExample "pkgs.i3status-rust"; + description = "Package providing i3status-rust"; + }; + + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + xdg.configFile = mapAttrs' (cfgFileSuffix: cfg: + nameValuePair ("i3status-rust/config-${cfgFileSuffix}.toml") ({ + onChange = mkIf config.xsession.windowManager.i3.enable restartI3; + + source = settingsFormat.generate ("config-${cfgFileSuffix}.toml") ({ + theme = cfg.theme; + icons = cfg.icons; + block = cfg.blocks; + } // cfg.settings); + })) cfg.bars; + }; +} diff --git a/modules/programs/info.nix b/modules/programs/info.nix index 9e4a5d4aaffa..a7d2692b5155 100644 --- a/modules/programs/info.nix +++ b/modules/programs/info.nix @@ -1,22 +1,21 @@ -# info.nix -- install texinfo, set INFOPATH, create `dir` file +# info.nix -- install texinfo and create `dir` file # This is a helper for the GNU info documentation system. By default, # the `info` command (and the Info subsystem within Emacs) gives easy # access to the info files stored system-wide, but not info files in # your ~/.nix-profile. -# We set $INFOPATH to include `/run/current-system/sw/share/info` and -# `~/.nix-profile/share/info` but it's not enough. Although info can -# then find files when you explicitly ask for them, it doesn't show -# them to you in the table of contents on startup. To do that requires -# a `dir` file. NixOS keeps the system-wide `dir` file up to date, but -# ignores home-installed packages. +# Specifically, although info can then find files when you explicitly +# ask for them, it doesn't show them to you in the table of contents +# on startup. To do that requires a `dir` file. NixOS keeps the +# system-wide `dir` file up to date, but ignores files installed in +# user profiles. -# So this module contains an activation script that generates the -# `dir` for your home profile. Then when you start info (and both -# `dir` files are in your $INFOPATH), it will *merge* the contents of -# the two files, showing you a unified table of contents for all -# packages. This is really nice. +# This module contains extra profile commands that generate the `dir` +# for your home profile. Then when you start info (and both `dir` +# files are in your $INFOPATH), it will *merge* the contents of the +# two files, showing you a unified table of contents for all packages. +# This is really nice. { config, lib, pkgs, ... }: @@ -26,50 +25,39 @@ let cfg = config.programs.info; - # Indexes info files found in this location - homeInfoPath = "${config.home.profileDirectory}/share/info"; - # Installs this package -- the interactive just means that it # includes the curses `info` program. We also use `install-info` # from this package in the activation script. infoPkg = pkgs.texinfoInteractive; in { - options = { - programs.info = { - enable = mkEnableOption "GNU Info"; + imports = [ + (mkRemovedOptionModule [ "programs" "info" "homeInfoDirLocation" ] '' + The `dir` file is now generated as part of the Home Manager profile and + will no longer be placed in your home directory. + '') + ]; - homeInfoDirLocation = mkOption { - default = "\${XDG_CACHE_HOME:-$HOME/.cache}/info"; - description = '' - Directory in which to store the info dir - file within your home. - ''; - }; - }; - }; + options.programs.info.enable = mkEnableOption "GNU Info"; config = mkIf cfg.enable { - home.sessionVariables.INFOPATH = - "${cfg.homeInfoDirLocation}\${INFOPATH:+:}\${INFOPATH}"; + home.packages = [ + infoPkg - home.activation.createHomeInfoDir = - hm.dag.entryAfter [ "installPackages" ] '' - oPATH=$PATH - export PATH="${lib.makeBinPath [ pkgs.gzip ]}''${PATH:+:}$PATH" - $DRY_RUN_CMD mkdir -p "${cfg.homeInfoDirLocation}" - $DRY_RUN_CMD rm -f "${cfg.homeInfoDirLocation}/dir" - if [[ -d "${homeInfoPath}" ]]; then - find -L "${homeInfoPath}" \( -name '*.info' -o -name '*.info.gz' \) \ - -exec $DRY_RUN_CMD ${infoPkg}/bin/install-info '{}' \ - "${cfg.homeInfoDirLocation}/dir" \; - fi - export PATH="$oPATH" - unset oPATH - ''; - - home.packages = [ infoPkg ]; + # Make sure the target directory is a real directory. + (pkgs.runCommandLocal "dummy-info-dir1" { } "mkdir -p $out/share/info") + (pkgs.runCommandLocal "dummy-info-dir2" { } "mkdir -p $out/share/info") + ]; home.extraOutputsToInstall = [ "info" ]; + + home.extraProfileCommands = let infoPath = "$out/share/info"; + in '' + if [[ -w "${infoPath}" && ! -e "${infoPath}/dir" ]]; then + PATH="${lib.makeBinPath [ pkgs.gzip infoPkg ]}''${PATH:+:}$PATH" \ + find -L "${infoPath}" \( -name '*.info' -o -name '*.info.gz' \) \ + -exec install-info '{}' "${infoPath}/dir" ';' + fi + ''; }; } diff --git a/modules/programs/kakoune.nix b/modules/programs/kakoune.nix index d2fd8eb10a1e..6db311a13767 100644 --- a/modules/programs/kakoune.nix +++ b/modules/programs/kakoune.nix @@ -489,6 +489,10 @@ let }; }; + kakouneWithPlugins = pkgs.wrapKakoune pkgs.kakoune-unwrapped { + configure = { plugins = cfg.plugins; }; + }; + configFile = let wrapOptions = with cfg.config.wrapLines; concatStrings [ @@ -506,12 +510,22 @@ let ]; showWhitespaceOptions = with cfg.config.showWhitespace; - concatStrings [ - (optionalString (tab != null) " -tab ${tab}") - (optionalString (tabStop != null) " -tabpad ${tabStop}") - (optionalString (space != null) " -spc ${space}") - (optionalString (nonBreakingSpace != null) " -nbsp ${nonBreakingSpace}") - (optionalString (lineFeed != null) " -lf ${lineFeed}") + let + quoteSep = sep: + if sep == "'" then + ''"'"'' + else if lib.strings.stringLength sep == 1 then + "'${sep}'" + else + sep; # backwards compat, in case sep == "' '", etc. + + in concatStrings [ + (optionalString (tab != null) " -tab ${quoteSep tab}") + (optionalString (tabStop != null) " -tabpad ${quoteSep tabStop}") + (optionalString (space != null) " -spc ${quoteSep space}") + (optionalString (nonBreakingSpace != null) + " -nbsp ${quoteSep nonBreakingSpace}") + (optionalString (lineFeed != null) " -lf ${quoteSep lineFeed}") ]; uiOptions = with cfg.config.ui; @@ -624,11 +638,22 @@ in { ~/.config/kak/kakrc. ''; }; + + plugins = mkOption { + type = with types; listOf package; + default = [ ]; + example = literalExample "[ pkgs.kakounePlugins.kak-fzf ]"; + description = '' + List of kakoune plugins to install. To get a list of + supported plugins run: + nix-env -f '<nixpkgs>' -qaP -A kakounePlugins. + ''; + }; }; }; config = mkIf cfg.enable { - home.packages = [ pkgs.kakoune ]; + home.packages = [ kakouneWithPlugins ]; xdg.configFile."kak/kakrc".source = configFile; }; } diff --git a/modules/programs/man.nix b/modules/programs/man.nix index 0ed376780d4d..b235b02fe2d2 100644 --- a/modules/programs/man.nix +++ b/modules/programs/man.nix @@ -4,19 +4,69 @@ with lib; { options = { - programs.man.enable = mkOption { - type = types.bool; - default = true; - description = '' - Whether to enable manual pages and the man - command. This also includes "man" outputs of all - home.packages. - ''; + programs.man = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable manual pages and the man + command. This also includes "man" outputs of all + home.packages. + ''; + }; + + generateCaches = mkOption { + type = types.bool; + default = false; + description = '' + Whether to generate the manual page index caches using + + mandb + 8 + . This allows searching for a page or + keyword using utilities like + apropos + 1 + . + + This feature is disabled by default because it slows down + building. If you don't mind waiting a few more seconds when + Home Manager builds a new generation, you may safely enable + this option. + ''; + }; }; }; config = mkIf config.programs.man.enable { home.packages = [ pkgs.man ]; home.extraOutputsToInstall = [ "man" ]; + + # This is mostly copy/pasted/adapted from NixOS' documentation.nix. + home.file = mkIf config.programs.man.generateCaches { + ".manpath".text = let + # Generate a directory containing installed packages' manpages. + manualPages = pkgs.buildEnv { + name = "man-paths"; + paths = config.home.packages; + pathsToLink = [ "/share/man" ]; + extraOutputsToInstall = [ "man" ]; + ignoreCollisions = true; + }; + + # Generate a database of all manpages in ${manualPages}. + manualCache = pkgs.runCommandLocal "man-cache" { } '' + # Generate a temporary man.conf so mandb knows where to + # write cache files. + echo "MANDB_MAP ${manualPages}/share/man $out" > man.conf + + # Run mandb to generate cache files: + ${pkgs.man-db}/bin/mandb -C man.conf --no-straycats --create \ + ${manualPages}/share/man + ''; + in '' + MANDB_MAP ${config.home.profileDirectory}/share/man ${manualCache} + ''; + }; }; } diff --git a/modules/programs/matplotlib.nix b/modules/programs/matplotlib.nix index da80c1167707..0d4e48c953f4 100644 --- a/modules/programs/matplotlib.nix +++ b/modules/programs/matplotlib.nix @@ -23,7 +23,7 @@ in { config = mkOption { default = { }; - type = types.attrs; + type = types.attrsOf types.anything; description = '' Add terms to the matplotlibrc file to control the default matplotlib behavior. diff --git a/modules/programs/mbsync-accounts.nix b/modules/programs/mbsync-accounts.nix index 4de1965fe3f6..c1bd551fa09e 100644 --- a/modules/programs/mbsync-accounts.nix +++ b/modules/programs/mbsync-accounts.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +{ config, lib, ... }: with lib; @@ -6,6 +6,110 @@ let extraConfigType = with lib.types; attrsOf (either (either str int) bool); + perAccountGroups = { name, config, ... }: { + options = { + name = mkOption { + type = types.str; + # Make value of name the same as the name used with the dot prefix + default = name; + readOnly = true; + description = '' + The name of this group for this account. These names are different than + some others, because they will hide channel names that are the same. + ''; + }; + + channels = mkOption { + type = types.attrsOf (types.submodule channel); + default = { }; + description = '' + List of channels that should be grouped together into this group. When + performing a synchronization, the groups are synchronized, rather than + the individual channels. + + Using these channels and then grouping them together allows for you to + define the maildir hierarchy as you see fit. + ''; + }; + }; + }; + + # Options for configuring channel(s) that will be composed together into a group. + channel = { name, config, ... }: { + options = { + name = mkOption { + type = types.str; + default = name; + readOnly = true; + description = '' + The unique name for THIS channel in THIS group. The group will refer to + this channel by this name. + + In addition, you can manually sync just this channel by specifying this + name to mbsync on the command line. + ''; + }; + + masterPattern = mkOption { + type = types.str; + default = ""; + example = "[Gmail]/Sent Mail"; + description = '' + IMAP4 patterns for which mailboxes on the remote mail server to sync. + If Patterns are specified, masterPattern + is interpreted as a prefix which is not matched against the patterns, + and is not affected by mailbox list overrides. + + If this is left as the default, then mbsync will default to the pattern + INBOX. + ''; + }; + + slavePattern = mkOption { + type = types.str; + default = ""; + example = "Sent"; + description = '' + Name for where mail coming from the master mail server will end up + locally. The mailbox specified by the master's pattern will be placed + in this directory. + + If this is left as the default, then mbsync will default to the pattern + INBOX. + ''; + }; + + patterns = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "INBOX" ]; + description = '' + Instead of synchronizing just the mailboxes that + match the masterPattern, use it as a prefix which is + not matched against the patterns, and is not affected by mailbox list + overrides. + ''; + }; + + extraConfig = mkOption { + type = extraConfigType; + default = { }; + example = '' + { + Create = "both"; + CopyArrivalDate = "yes"; + MaxMessages = 10000; + MaxSize = "1m"; + } + ''; + description = '' + Extra configuration lines to add to THIS channel's + configuration. + ''; + }; + }; + }; + in { options.mbsync = { enable = mkEnableOption "synchronization using mbsync"; @@ -58,6 +162,23 @@ in { ''; }; + groups = mkOption { + type = types.attrsOf (types.submodule perAccountGroups); + default = { }; + # The default cannot actually be empty, but contains an attribute set where + # the channels set is empty. If a group is specified, then a name is given, + # creating the attribute set. + description = '' + Some email providers (Gmail) have a different directory hierarchy for + synchronized email messages. Namely, when using mbsync without specifying + a set of channels into a group, all synchronized directories end up beneath + the [Gmail]/ directory. + + This option allows you to specify a group, and subsequently channels that + will allow you to sync your mail into an arbitrary hierarchy. + ''; + }; + extraConfig.channel = mkOption { type = extraConfigType; default = { }; diff --git a/modules/programs/mbsync.nix b/modules/programs/mbsync.nix index f2814b393d06..f9713da3aef3 100644 --- a/modules/programs/mbsync.nix +++ b/modules/programs/mbsync.nix @@ -69,23 +69,98 @@ let Inbox = "${maildir.absPath}/${folders.inbox}"; SubFolders = "Verbatim"; } // optionalAttrs (mbsync.flatten != null) { Flatten = mbsync.flatten; } - // mbsync.extraConfig.local) + "\n" + genSection "Channel ${name}" ({ - Master = ":${name}-remote:"; - Slave = ":${name}-local:"; - Patterns = mbsync.patterns; - Create = masterSlaveMapping.${mbsync.create}; - Remove = masterSlaveMapping.${mbsync.remove}; - Expunge = masterSlaveMapping.${mbsync.expunge}; - SyncState = "*"; - } // mbsync.extraConfig.channel) + "\n"; + // mbsync.extraConfig.local) + "\n" + genChannels account; + + genChannels = account: + with account; + if mbsync.groups == { } then + genAccountWideChannel account + else + genGroupChannelConfig name mbsync.groups + "\n" + + genAccountGroups mbsync.groups; + + # Used when no channels are specified for this account. This will create a + # single channel for the entire account that is then further refined within + # the Group for synchronization. + genAccountWideChannel = account: + with account; + genSection "Channel ${name}" ({ + Master = ":${name}-remote:"; + Slave = ":${name}-local:"; + Patterns = mbsync.patterns; + Create = masterSlaveMapping.${mbsync.create}; + Remove = masterSlaveMapping.${mbsync.remove}; + Expunge = masterSlaveMapping.${mbsync.expunge}; + SyncState = "*"; + } // mbsync.extraConfig.channel) + "\n"; + + # Given the attr set of groups, return a string of channels that will direct + # mail to the proper directories, according to the pattern used in channel's + # master pattern definition. + genGroupChannelConfig = storeName: groups: + let + # Given the name of the group this channel is part of and the channel + # itself, generate the string for the desired configuration. + genChannelString = groupName: channel: + let + escapeValue = escape [ ''\"'' ]; + hasSpace = v: builtins.match ".* .*" v != null; + # Given a list of patterns, will return the string requested. + # Only prints if the pattern is NOT the empty list, the default. + genChannelPatterns = patterns: + if (length patterns) != 0 then + "Pattern " + concatStringsSep " " + (map (pat: if hasSpace pat then escapeValue pat else pat) + patterns) + "\n" + else + ""; + in genSection "Channel ${groupName}-${channel.name}" ({ + Master = ":${storeName}-remote:${channel.masterPattern}"; + Slave = ":${storeName}-local:${channel.slavePattern}"; + } // channel.extraConfig) + genChannelPatterns channel.patterns; + # Given the group name, and a attr set of channels within that group, + # Generate a list of strings for each channels' configuration. + genChannelStrings = groupName: channels: + optionals (channels != { }) + (mapAttrsToList (channelName: info: genChannelString groupName info) + channels); + # Given a group, return a string that configures all the channels within + # the group. + genGroupsChannels = group: + concatStringsSep "\n" (genChannelStrings group.name group.channels); + # Generate all channel configurations for all groups for this account. + in concatStringsSep "\n" (filter (s: s != "") + (mapAttrsToList (name: group: genGroupsChannels group) groups)); + + # Given the attr set of groups, return a string which maps channels to groups + genAccountGroups = groups: + let + # Given the name of the group and the attribute set of channels, make + # make "Channel -" for each channel to list os strings + genChannelStrings = groupName: channels: + mapAttrsToList (name: info: "Channel ${groupName}-${name}") channels; + # Take in 1 group, if the group has channels specified, construct the + # "Group " header and each of the channels. + genGroupChannelString = group: + flatten (optionals (group.channels != { }) ([ "Group ${group.name}" ] + ++ (genChannelStrings group.name group.channels))); + # Given set of groups, generates list of strings, where each string is one + # of the groups and its consituent channels. + genGroupsStrings = mapAttrsToList (name: info: + concatStringsSep "\n" (genGroupChannelString groups.${name})) groups; + in concatStringsSep "\n\n" (filter (s: s != "") + genGroupsStrings) # filter for the cases of empty groups + + "\n"; # Put all strings together. genGroupConfig = name: channels: let genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}"; - in concatStringsSep "\n" + in "\n" + concatStringsSep "\n" ([ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels); in { + meta.maintainers = [ maintainers.KarlJoad ]; + options = { programs.mbsync = { enable = mkEnableOption "mbsync IMAP4 and Maildir mailbox synchronizer"; @@ -150,11 +225,20 @@ in { home.file.".mbsyncrc".text = let accountsConfig = map genAccountConfig mbsyncAccounts; - groupsConfig = mapAttrsToList genGroupConfig cfg.groups; - in concatStringsSep "\n" (['' + # Only generate this kind of Group configuration if there are ANY accounts + # that do NOT have a per-account groups/channels option(s) specified. + groupsConfig = + if any (account: account.mbsync.groups == { }) mbsyncAccounts then + mapAttrsToList genGroupConfig cfg.groups + else + [ ]; + in '' # Generated by Home Manager. - ''] ++ optional (cfg.extraConfig != "") cfg.extraConfig ++ accountsConfig - ++ groupsConfig) + "\n"; + + '' + + concatStringsSep "\n" (optional (cfg.extraConfig != "") cfg.extraConfig) + + concatStringsSep "\n\n" accountsConfig + + concatStringsSep "\n" groupsConfig; home.activation = mkIf (mbsyncAccounts != [ ]) { createMaildir = diff --git a/modules/programs/mcfly.nix b/modules/programs/mcfly.nix new file mode 100644 index 000000000000..1206f9da5660 --- /dev/null +++ b/modules/programs/mcfly.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + + cfg = config.programs.mcfly; + +in { + meta.maintainers = [ maintainers.marsam ]; + + options.programs.mcfly = { + enable = mkEnableOption "mcfly"; + + keyScheme = mkOption { + type = types.enum [ "emacs" "vim" ]; + default = "emacs"; + description = '' + Key scheme to use. + ''; + }; + + enableLightTheme = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable light mode theme. + ''; + }; + + enableBashIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Bash integration. + ''; + }; + + enableZshIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Zsh integration. + ''; + }; + + enableFishIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Fish integration. + ''; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + { + home.packages = [ pkgs.mcfly ]; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + source "${pkgs.mcfly}/share/mcfly/mcfly.bash" + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + source "${pkgs.mcfly}/share/mcfly/mcfly.zsh" + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + source "${pkgs.mcfly}/share/mcfly/mcfly.fish" + if status is-interactive + mcfly_key_bindings + end + ''; + + home.sessionVariables.MCFLY_KEY_SCHEME = cfg.keyScheme; + } + + (mkIf cfg.enableLightTheme { home.sessionVariables.MCFLY_LIGHT = "TRUE"; }) + ]); +} diff --git a/modules/programs/mercurial.nix b/modules/programs/mercurial.nix index 8e9a3befbaf1..2fc6e0076aea 100644 --- a/modules/programs/mercurial.nix +++ b/modules/programs/mercurial.nix @@ -6,6 +6,8 @@ let cfg = config.programs.mercurial; + iniFormat = pkgs.formats.ini { }; + in { options = { @@ -30,19 +32,19 @@ in { }; aliases = mkOption { - type = types.attrs; + type = types.attrsOf types.anything; default = { }; description = "Mercurial aliases to define."; }; extraConfig = mkOption { - type = types.either types.attrs types.lines; + type = types.either (types.attrsOf types.anything) types.lines; default = { }; description = "Additional configuration to add."; }; iniContent = mkOption { - type = types.attrsOf types.attrs; + type = iniFormat.type; internal = true; }; @@ -71,7 +73,8 @@ in { username = cfg.userName + " <" + cfg.userEmail + ">"; }; - xdg.configFile."hg/hgrc".text = generators.toINI { } cfg.iniContent; + xdg.configFile."hg/hgrc".source = + iniFormat.generate "hgrc" cfg.iniContent; } (mkIf (cfg.ignores != [ ] || cfg.ignoresRegexp != [ ]) { diff --git a/modules/programs/mpv.nix b/modules/programs/mpv.nix index a5b0517fe0a7..5901f9d08b8b 100644 --- a/modules/programs/mpv.nix +++ b/modules/programs/mpv.nix @@ -8,7 +8,8 @@ let cfg = config.programs.mpv; mpvOption = with types; either str (either int (either bool float)); - mpvOptions = with types; attrsOf mpvOption; + mpvOptionDup = with types; either mpvOption (listOf mpvOption); + mpvOptions = with types; attrsOf mpvOptionDup; mpvProfiles = with types; attrsOf mpvOptions; mpvBindings = with types; attrsOf str; @@ -22,28 +23,46 @@ let string = option; }.${typeOf option}; - renderOptions = options: - concatStringsSep "\n" (mapAttrsToList (name: value: - let - rendered = renderOption value; - length = toString (stringLength rendered); - in "${name}=%${length}%${rendered}") options); + renderOptionValue = value: + let + rendered = renderOption value; + length = toString (stringLength rendered); + in "%${length}%${rendered}"; - renderProfiles = profiles: - concatStringsSep "\n" (mapAttrsToList (name: value: '' - [${name}] - ${renderOptions value} - '') profiles); + renderOptions = generators.toKeyValue { + mkKeyValue = + generators.mkKeyValueDefault { mkValueString = renderOptionValue; } "="; + listsAsDuplicateKeys = true; + }; + + renderProfiles = generators.toINI { + mkKeyValue = + generators.mkKeyValueDefault { mkValueString = renderOptionValue; } "="; + listsAsDuplicateKeys = true; + }; renderBindings = bindings: concatStringsSep "\n" (mapAttrsToList (name: value: "${name} ${value}") bindings); + mpvPackage = if cfg.scripts == [ ] then + pkgs.mpv + else + pkgs.wrapMpv pkgs.mpv-unwrapped { scripts = cfg.scripts; }; + in { options = { programs.mpv = { enable = mkEnableOption "mpv"; + package = mkOption { + type = types.package; + readOnly = true; + description = '' + Resulting mpv package. + ''; + }; + scripts = mkOption { type = with types; listOf (either package str); default = [ ]; @@ -121,12 +140,8 @@ in { config = mkIf cfg.enable (mkMerge [ { - home.packages = [ - (if cfg.scripts == [ ] then - pkgs.mpv - else - pkgs.wrapMpv pkgs.mpv-unwrapped { scripts = cfg.scripts; }) - ]; + home.packages = [ mpvPackage ]; + programs.mpv.package = mpvPackage; } (mkIf (cfg.config != { } || cfg.profiles != { }) { xdg.configFile."mpv/mpv.conf".text = '' diff --git a/modules/programs/mu.nix b/modules/programs/mu.nix new file mode 100644 index 000000000000..18c3993a158d --- /dev/null +++ b/modules/programs/mu.nix @@ -0,0 +1,57 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.mu; + + # Used to generate command line arguments that mu can operate with. + genCmdMaildir = path: "--maildir=" + path; + + # Takes the list of accounts with mu.enable = true, and generates a + # command-line flag for initializing the mu database. + myAddresses = let + # List of account sets where mu.enable = true. + muAccounts = + filter (a: a.mu.enable) (attrValues config.accounts.email.accounts); + addrs = map (a: a.address) muAccounts; + # Prefix --my-address= to each account's address with mu.enable. + addMyAddress = map (addr: "--my-address=" + addr) addrs; + in concatStringsSep " " addMyAddress; + +in { + meta.maintainers = [ maintainers.KarlJoad ]; + + options = { + programs.mu = { + enable = mkEnableOption "mu, a maildir indexer and searcher"; + + # No options/config file present for mu, and program author will not be + # adding one soon. See https://github.com/djcb/mu/issues/882 for more + # information about this. + }; + + accounts.email.accounts = mkOption { + type = with types; + attrsOf + (submodule { options.mu.enable = mkEnableOption "mu indexing"; }); + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.mu ]; + + home.activation.runMuInit = let + maildirOption = genCmdMaildir config.accounts.email.maildirBasePath; + dbLocation = config.xdg.cacheHome + "/mu"; + in hm.dag.entryAfter [ "writeBoundary" ] '' + # If the database directory exists, then `mu init` should NOT be run. + # In theory, mu is the only thing that creates that directory, and it is + # only created during the initial index. + if [[ ! -d "${dbLocation}" ]]; then + $DRY_RUN_CMD mu init ${maildirOption} $VERBOSE_ARG; + fi + ''; + }; +} diff --git a/modules/programs/ncmpcpp.nix b/modules/programs/ncmpcpp.nix new file mode 100644 index 000000000000..a39baab6ca5a --- /dev/null +++ b/modules/programs/ncmpcpp.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.ncmpcpp; + + renderSettings = settings: + concatStringsSep "\n" (mapAttrsToList renderSetting settings); + + renderSetting = name: value: "${name}=${renderValue value}"; + + renderValue = option: + { + int = toString option; + bool = if option then "yes" else "no"; + string = option; + }.${builtins.typeOf option}; + + renderBindings = bindings: concatStringsSep "\n" (map renderBinding bindings); + + renderBinding = { key, command }: + concatStringsSep "\n " ([ ''def_key "${key}"'' ] ++ maybeWrapList command); + + maybeWrapList = xs: if isList xs then xs else [ xs ]; + + valueType = with types; oneOf [ bool int str ]; + + bindingType = types.submodule ({ name, config, ... }: { + options = { + key = mkOption { + type = types.str; + description = "Key to bind."; + example = "j"; + }; + + command = mkOption { + type = with types; either str (listOf str); + description = "Command or sequence of commands to be executed."; + example = "scroll_down"; + }; + }; + }); + +in { + meta.maintainers = with maintainers; [ olmokramer ]; + + options.programs.ncmpcpp = { + enable = + mkEnableOption "ncmpcpp - an ncurses Music Player Daemon (MPD) client"; + + package = mkOption { + type = types.package; + default = pkgs.ncmpcpp; + defaultText = literalExample "pkgs.ncmpcpp"; + description = '' + Package providing the ncmpcpp command. + ''; + example = + literalExample "pkgs.ncmpcpp.override { visualizerSupport = true; }"; + }; + + mpdMusicDir = mkOption { + type = types.nullOr types.path; + default = let mpdCfg = config.services.mpd; + in if pkgs.stdenv.hostPlatform.isLinux && mpdCfg.enable then + mpdCfg.musicDirectory + else + null; + defaultText = literalExample '' + if pkgs.stdenv.hostPlatform.isLinux && config.services.mpd.enable then + config.services.mpd.musicDirectory + else + null + ''; + description = '' + Value of the mpd_music_dir setting. On Linux platforms the + value of services.mpd.musicDirectory is used as the + default if services.mpd.enable is + true. + ''; + example = "~/music"; + }; + + settings = mkOption { + type = types.attrsOf valueType; + default = { }; + description = '' + Attribute set from name of a setting to its value. For available options + see + + ncmpcpp + 1 + . + ''; + example = { ncmpcpp_directory = "~/.local/share/ncmpcpp"; }; + }; + + bindings = mkOption { + type = types.listOf bindingType; + default = [ ]; + description = "List of keybindings."; + example = literalExample '' + [ + { key = "j"; command = "scroll_down"; } + { key = "k"; command = "scroll_up"; } + { key = "J"; command = [ "select_item" "scroll_down" ]; } + { key = "K"; command = [ "select_item" "scroll_up" ]; } + ] + ''; + }; + }; + + config = mkIf cfg.enable { + warnings = mkIf (cfg.settings ? mpd_music_dir && cfg.mpdMusicDir != null) [ + ("programs.ncmpcpp.settings.mpd_music_dir will be overridden by" + + " programs.ncmpcpp.mpdMusicDir.") + ]; + + home.packages = [ cfg.package ]; + + xdg.configFile = { + "ncmpcpp/config" = let + settings = cfg.settings // optionalAttrs (cfg.mpdMusicDir != null) { + mpd_music_dir = toString cfg.mpdMusicDir; + }; + in mkIf (settings != { }) { text = renderSettings settings + "\n"; }; + + "ncmpcpp/bindings" = mkIf (cfg.bindings != [ ]) { + text = renderBindings cfg.bindings + "\n"; + }; + }; + }; +} diff --git a/modules/programs/neomutt.nix b/modules/programs/neomutt.nix index f2a6bbfff089..d990f02eaca5 100644 --- a/modules/programs/neomutt.nix +++ b/modules/programs/neomutt.nix @@ -124,7 +124,7 @@ let let folderHook = mapAttrsToList setOption (genCommonFolderHooks account // { folder = "'${account.maildir.absPath}'"; - }) ++ optional (neomutt.extraConfig != "") neomutt.extraConfig; + }); in '' ${concatStringsSep "\n" folderHook} ''; diff --git a/modules/programs/neovim.nix b/modules/programs/neovim.nix index 858f5576ad14..c0ee24cf1a26 100644 --- a/modules/programs/neovim.nix +++ b/modules/programs/neovim.nix @@ -9,32 +9,53 @@ let extraPythonPackageType = mkOptionType { name = "extra-python-packages"; description = "python packages in python.withPackages format"; - check = with types; (x: if isFunction x - then isList (x pkgs.pythonPackages) - else false); + check = with types; + (x: if isFunction x then isList (x pkgs.pythonPackages) else false); merge = mergeOneOption; }; extraPython3PackageType = mkOptionType { name = "extra-python3-packages"; description = "python3 packages in python.withPackages format"; - check = with types; (x: if isFunction x - then isList (x pkgs.python3Packages) - else false); + check = with types; + (x: if isFunction x then isList (x pkgs.python3Packages) else false); merge = mergeOneOption; }; - moduleConfigure = - optionalAttrs (cfg.extraConfig != "") { - customRC = cfg.extraConfig; - } - // optionalAttrs (cfg.plugins != []) { - packages.home-manager.start = cfg.plugins; + pluginWithConfigType = types.submodule { + options = { + plugin = mkOption { + type = types.package; + description = "vim plugin"; + }; + config = mkOption { + type = types.lines; + description = "vimscript for this plugin to be placed in init.vim"; + default = ""; + }; }; + }; -in + # A function to get the configuration string (if any) from an element of 'plugins' + pluginConfig = p: + if builtins.hasAttr "plugin" p && builtins.hasAttr "config" p then '' + " ${p.plugin.pname} {{{ + ${p.config} + " }}} + '' else + ""; + + moduleConfigure = optionalAttrs (cfg.extraConfig != "" + || (lib.filter (hasAttr "config") cfg.plugins) != [ ]) { + customRC = cfg.extraConfig + + pkgs.lib.concatMapStrings pluginConfig cfg.plugins; + } // optionalAttrs (cfg.plugins != [ ]) { + packages.home-manager.start = map (x: x.plugin or x) cfg.plugins; + }; + extraMakeWrapperArgs = lib.optionalString (cfg.extraPackages != [ ]) + ''--prefix PATH : "${lib.makeBinPath cfg.extraPackages}"''; -{ +in { options = { programs.neovim = { enable = mkEnableOption "Neovim"; @@ -83,7 +104,7 @@ in extraPythonPackages = mkOption { type = with types; either extraPythonPackageType (listOf package); - default = (_: []); + default = (_: [ ]); defaultText = "ps: []"; example = literalExample "(ps: with ps; [ pandas jedi ])"; description = '' @@ -111,7 +132,7 @@ in extraPython3Packages = mkOption { type = with types; either extraPython3PackageType (listOf package); - default = (_: []); + default = (_: [ ]); defaultText = "ps: []"; example = literalExample "(ps: with ps; [ python-language-server ])"; description = '' @@ -135,8 +156,8 @@ in }; configure = mkOption { - type = types.attrs; - default = {}; + type = types.attrsOf types.anything; + default = { }; example = literalExample '' configure = { customRC = $'''' @@ -177,12 +198,28 @@ in ''; }; - plugins = mkOption { + extraPackages = mkOption { type = with types; listOf package; default = [ ]; - example = literalExample "[ pkgs.vimPlugins.yankring ]"; + example = "[ pkgs.shfmt ]"; + description = "Extra packages available to nvim."; + }; + + plugins = mkOption { + type = with types; listOf (either package pluginWithConfigType); + default = [ ]; + example = literalExample '' + with pkgs.vimPlugins; [ + yankring + vim-nix + { plugin = vim-startify; + config = "let g:startify_change_to_vcs_root = 0"; + } + ] + ''; description = '' - List of vim plugins to install. + List of vim plugins to install optionally associated with + configuration to be placed in init.vim. @@ -193,27 +230,25 @@ in }; config = mkIf cfg.enable { - assertions = [ - { - assertion = cfg.configure == { } || moduleConfigure == { }; - message = "The programs.neovim option configure is mutually exclusive" - + " with extraConfig and plugins."; - } - ]; + assertions = [{ + assertion = cfg.configure == { } || moduleConfigure == { }; + message = "The programs.neovim option configure is mutually exclusive" + + " with extraConfig and plugins."; + }]; home.packages = [ cfg.finalPackage ]; programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package { inherit (cfg) - extraPython3Packages withPython3 - extraPythonPackages withPython + extraPython3Packages withPython3 extraPythonPackages withPython withNodeJs withRuby viAlias vimAlias; + extraMakeWrapperArgs = extraMakeWrapperArgs; configure = cfg.configure // moduleConfigure; }; programs.bash.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; programs.fish.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; - programs.zsh.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; + programs.zsh.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; }; } diff --git a/modules/programs/notmuch-accounts.nix b/modules/programs/notmuch-accounts.nix deleted file mode 100644 index fd4a811d73d3..000000000000 --- a/modules/programs/notmuch-accounts.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ lib, ... }: - -{ - options.notmuch = { enable = lib.mkEnableOption "notmuch indexing"; }; -} diff --git a/modules/programs/notmuch.nix b/modules/programs/notmuch.nix index 9070d7556716..b93cc6a5ed8e 100644 --- a/modules/programs/notmuch.nix +++ b/modules/programs/notmuch.nix @@ -145,7 +145,10 @@ in { }; accounts.email.accounts = mkOption { - type = with types; attrsOf (submodule (import ./notmuch-accounts.nix)); + type = with types; + attrsOf (submodule { + options.notmuch.enable = mkEnableOption "notmuch indexing"; + }); }; }; diff --git a/modules/programs/pet.nix b/modules/programs/pet.nix new file mode 100644 index 000000000000..0da205dab9f9 --- /dev/null +++ b/modules/programs/pet.nix @@ -0,0 +1,88 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.pet; + + format = pkgs.formats.toml { }; + + snippetType = types.submodule { + options = { + description = mkOption { + type = types.str; + default = ""; + example = "Count the number of commits in the current branch"; + description = '' + Description of the snippet. + ''; + }; + + command = mkOption { + type = types.str; + default = ""; + example = "git rev-list --count HEAD"; + description = '' + The command. + ''; + }; + + output = mkOption { + type = types.str; + default = ""; + example = "473"; + description = '' + Example output of the command. + ''; + }; + }; + }; + +in { + options.programs.pet = { + enable = mkEnableOption "pet"; + + settings = mkOption { + type = format.type; + default = { }; + description = '' + Settings written to config.toml. See the pet + documentation for details. + ''; + }; + + selectcmdPackage = mkOption { + type = types.package; + default = pkgs.fzf; + defaultText = literalExample "pkgs.fzf"; + description = '' + The package needed for the settings.selectcmd. + ''; + }; + + snippets = mkOption { + type = types.listOf snippetType; + default = [ ]; + description = '' + The snippets. + ''; + }; + }; + + config = mkIf cfg.enable { + programs.pet.settings = { + selectcmd = mkDefault "fzf"; + snippetfile = config.xdg.configHome + "/pet/snippet.toml"; + }; + + home.packages = [ pkgs.pet cfg.selectcmdPackage ]; + + xdg.configFile = { + "pet/config.toml".source = + format.generate "config.toml" { General = cfg.settings; }; + "pet/snippet.toml".source = + format.generate "snippet.toml" { snippets = cfg.snippets; }; + }; + }; +} diff --git a/modules/programs/powerline-go.nix b/modules/programs/powerline-go.nix index a4cd233cf70e..8f5db8f260e1 100644 --- a/modules/programs/powerline-go.nix +++ b/modules/programs/powerline-go.nix @@ -106,17 +106,38 @@ in { }; }; - config = mkIf (cfg.enable && config.programs.bash.enable) { - programs.bash.initExtra = '' - function _update_ps1() { - local old_exit_status=$? - PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $old_exit_status ${commandLineArguments})" + config = { + programs.bash.initExtra = + mkIf (cfg.enable && config.programs.bash.enable) '' + function _update_ps1() { + local old_exit_status=$? + PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $old_exit_status ${commandLineArguments})" + ${cfg.extraUpdatePS1} + return $old_exit_status + } + + if [ "$TERM" != "linux" ]; then + PROMPT_COMMAND="_update_ps1;$PROMPT_COMMAND" + fi + ''; + + programs.zsh.initExtra = mkIf (cfg.enable && config.programs.zsh.enable) '' + function powerline_precmd() { + PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $? -shell zsh ${commandLineArguments})" ${cfg.extraUpdatePS1} - return $old_exit_status + } + + function install_powerline_precmd() { + for s in "$\{precmd_functions[@]}"; do + if [ "$s" = "powerline_precmd" ]; then + return + fi + done + precmd_functions+=(powerline_precmd) } if [ "$TERM" != "linux" ]; then - PROMPT_COMMAND="_update_ps1;$PROMPT_COMMAND" + install_powerline_precmd fi ''; }; diff --git a/modules/programs/qutebrowser.nix b/modules/programs/qutebrowser.nix index 798363fb1879..282861d90f89 100644 --- a/modules/programs/qutebrowser.nix +++ b/modules/programs/qutebrowser.nix @@ -78,7 +78,7 @@ in { }; settings = mkOption { - type = types.attrs; + type = types.attrsOf types.anything; default = { }; description = '' Options to add to qutebrowser config.py file. diff --git a/modules/programs/skim.nix b/modules/programs/skim.nix index c90fe1b1a35c..40377054bf99 100644 --- a/modules/programs/skim.nix +++ b/modules/programs/skim.nix @@ -91,6 +91,14 @@ in { Whether to enable Zsh integration. ''; }; + + enableFishIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Fish integration. + ''; + }; }; config = mkIf cfg.enable { @@ -120,5 +128,9 @@ in { . ${pkgs.skim}/share/skim/key-bindings.zsh fi ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + source ${pkgs.skim}/share/skim/key-bindings.fish && skim_key_bindings + ''; }; } diff --git a/modules/programs/taskwarrior.nix b/modules/programs/taskwarrior.nix index cf95511f8efb..6a887e0f5b18 100644 --- a/modules/programs/taskwarrior.nix +++ b/modules/programs/taskwarrior.nix @@ -40,7 +40,7 @@ in { enable = mkEnableOption "Task Warrior"; config = mkOption { - type = types.attrs; + type = types.attrsOf types.anything; default = { }; example = literalExample '' { diff --git a/modules/programs/tmux.nix b/modules/programs/tmux.nix index a71c302ac6fb..e28145da125b 100644 --- a/modules/programs/tmux.nix +++ b/modules/programs/tmux.nix @@ -27,6 +27,7 @@ let defaultResize = 5; defaultShortcut = "b"; defaultTerminal = "screen"; + defaultShell = null; boolToStr = value: if value then "on" else "off"; @@ -41,7 +42,10 @@ let set -g default-terminal "${cfg.terminal}" set -g base-index ${toString cfg.baseIndex} setw -g pane-base-index ${toString cfg.baseIndex} - + ${optionalString (cfg.shell != null) '' + # We need to set default-shell before calling new-session + set -g default-shell "${cfg.shell}" + ''} ${optionalString cfg.newSession "new-session"} ${optionalString cfg.reverseSplit '' @@ -250,6 +254,13 @@ in description = "Set the $TERM variable."; }; + shell = mkOption { + default = defaultShell; + example = "\${pkgs.zsh}/bin/zsh"; + type = with types; nullOr str; + description = "Set the default-shell tmux variable."; + }; + secureSocket = mkOption { default = pkgs.stdenv.isLinux; type = types.bool; diff --git a/modules/programs/urxvt.nix b/modules/programs/urxvt.nix index e4c72bfe2726..5eb3d90d7924 100644 --- a/modules/programs/urxvt.nix +++ b/modules/programs/urxvt.nix @@ -124,7 +124,7 @@ in { extraConfig = mkOption { default = { }; - type = types.attrs; + type = types.attrsOf types.anything; description = "Additional configuration to add."; example = { "shading" = 15; }; }; diff --git a/modules/programs/vscode.nix b/modules/programs/vscode.nix index 8e8fba777ce3..5f4400257e82 100644 --- a/modules/programs/vscode.nix +++ b/modules/programs/vscode.nix @@ -8,6 +8,8 @@ let vscodePname = cfg.package.pname; + jsonFormat = pkgs.formats.json { }; + configDir = { "vscode" = "Code"; "vscode-insiders" = "Code - Insiders"; @@ -20,17 +22,18 @@ let "vscodium" = "vscode-oss"; }.${vscodePname}; - configFilePath = - if pkgs.stdenv.hostPlatform.isDarwin then - "Library/Application Support/${configDir}/User/settings.json" - else - "${config.xdg.configHome}/${configDir}/User/settings.json"; + userDir = if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/${configDir}/User" + else + "${config.xdg.configHome}/${configDir}/User"; + + configFilePath = "${userDir}/settings.json"; + keybindingsFilePath = "${userDir}/keybindings.json"; # TODO: On Darwin where are the extensions? extensionPath = ".${extensionDir}/extensions"; -in -{ +in { options = { programs.vscode = { enable = mkEnableOption "Visual Studio Code"; @@ -45,8 +48,8 @@ in }; userSettings = mkOption { - type = types.attrs; - default = {}; + type = jsonFormat.type; + default = { }; example = literalExample '' { "update.channel" = "none"; @@ -59,9 +62,48 @@ in ''; }; + keybindings = mkOption { + type = types.listOf (types.submodule { + options = { + key = mkOption { + type = types.str; + example = "ctrl+c"; + description = "The key or key-combination to bind."; + }; + + command = mkOption { + type = types.str; + example = "editor.action.clipboardCopyAction"; + description = "The VS Code command to execute."; + }; + + when = mkOption { + type = types.str; + default = ""; + example = "textInputFocus"; + description = "Optional context filter."; + }; + }; + }); + default = [ ]; + example = literalExample '' + [ + { + key = "ctrl+c"; + command = "editor.action.clipboardCopyAction"; + when = "textInputFocus"; + } + ] + ''; + description = '' + Keybindings written to Visual Studio Code's + keybindings.json. + ''; + }; + extensions = mkOption { type = types.listOf types.package; - default = []; + default = [ ]; example = literalExample "[ pkgs.vscode-extensions.bbenoist.Nix ]"; description = '' The extensions Visual Studio Code should be started with. @@ -75,25 +117,21 @@ in home.packages = [ cfg.package ]; # Adapted from https://discourse.nixos.org/t/vscode-extensions-setup/1801/2 - home.file = - let - subDir = "share/vscode/extensions"; - toPaths = path: - # Links every dir in path to the extension path. - mapAttrsToList (k: _: - { - "${extensionPath}/${k}".source = "${path}/${subDir}/${k}"; - }) (builtins.readDir (path + "/${subDir}")); - toSymlink = concatMap toPaths cfg.extensions; - in - foldr - (a: b: a // b) - { - "${configFilePath}" = - mkIf (cfg.userSettings != {}) { - text = builtins.toJSON cfg.userSettings; - }; - } - toSymlink; + home.file = let + subDir = "share/vscode/extensions"; + toPaths = path: + # Links every dir in path to the extension path. + mapAttrsToList + (k: _: { "${extensionPath}/${k}".source = "${path}/${subDir}/${k}"; }) + (builtins.readDir (path + "/${subDir}")); + toSymlink = concatMap toPaths cfg.extensions; + in foldr (a: b: a // b) { + "${configFilePath}" = mkIf (cfg.userSettings != { }) { + source = jsonFormat.generate "vscode-user-settings" cfg.userSettings; + }; + "${keybindingsFilePath}" = mkIf (cfg.keybindings != [ ]) { + source = jsonFormat.generate "vscode-keybindings" cfg.keybindings; + }; + } toSymlink; }; } diff --git a/modules/programs/waybar.nix b/modules/programs/waybar.nix new file mode 100644 index 000000000000..a2b8aa0f8aaf --- /dev/null +++ b/modules/programs/waybar.nix @@ -0,0 +1,373 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) + attrByPath attrNames concatMap concatMapStringsSep elem filter filterAttrs + flip foldl' hasPrefix mergeAttrs optionalAttrs stringLength subtractLists + types unique; + inherit (lib.options) literalExample mkEnableOption mkOption; + inherit (lib.modules) mkIf mkMerge; + + cfg = config.programs.waybar; + + # Used when generating warnings + modulesPath = "programs.waybar.settings.[].modules"; + + jsonFormat = pkgs.formats.json { }; + + # Taken from + defaultModuleNames = [ + "sway/mode" + "sway/workspaces" + "sway/window" + "sway/language" + "wlr/taskbar" + "river/tags" + "idle_inhibitor" + "memory" + "cpu" + "clock" + "disk" + "tray" + "network" + "backlight" + "pulseaudio" + "mpd" + "sndio" + "temperature" + "bluetooth" + "battery" + ]; + + isValidCustomModuleName = x: + elem x defaultModuleNames || (hasPrefix "custom/" x && stringLength x > 7); + + margins = let + mkMargin = name: { + "margin-${name}" = mkOption { + type = types.nullOr types.int; + default = null; + example = 10; + description = "Margins value without unit."; + }; + }; + margins = map mkMargin [ "top" "left" "bottom" "right" ]; + in foldl' mergeAttrs { } margins; + + waybarBarConfig = with lib.types; + submodule { + options = { + layer = mkOption { + type = nullOr (enum [ "top" "bottom" ]); + default = null; + description = '' + Decide if the bar is displayed in front ("top") + of the windows or behind ("bottom"). + ''; + example = "top"; + }; + + output = mkOption { + type = nullOr (either str (listOf str)); + default = null; + example = literalExample '' + [ "DP-1" "!DP-2" "!DP-3" ] + ''; + description = '' + Specifies on which screen this bar will be displayed. + Exclamation mark(!) can be used to exclude specific output. + ''; + }; + + position = mkOption { + type = nullOr (enum [ "top" "bottom" "left" "right" ]); + default = null; + example = "right"; + description = "Bar position relative to the output."; + }; + + height = mkOption { + type = nullOr ints.unsigned; + default = null; + example = 5; + description = + "Height to be used by the bar if possible. Leave blank for a dynamic value."; + }; + + width = mkOption { + type = nullOr ints.unsigned; + default = null; + example = 5; + description = + "Width to be used by the bar if possible. Leave blank for a dynamic value."; + }; + + modules-left = mkOption { + type = listOf str; + default = [ ]; + description = "Modules that will be displayed on the left."; + example = literalExample '' + [ "sway/workspaces" "sway/mode" "wlr/taskbar" ] + ''; + }; + + modules-center = mkOption { + type = listOf str; + default = [ ]; + description = "Modules that will be displayed in the center."; + example = literalExample '' + [ "sway/window" ] + ''; + }; + + modules-right = mkOption { + type = listOf str; + default = [ ]; + description = "Modules that will be displayed on the right."; + example = literalExample '' + [ "mpd" "custom/mymodule#with-css-id" "temperature" ] + ''; + }; + + modules = mkOption { + type = jsonFormat.type; + default = { }; + description = "Modules configuration."; + example = literalExample '' + { + "sway/window" = { + max-length = 50; + }; + "clock" = { + format-alt = "{:%a, %d. %b %H:%M}"; + }; + } + ''; + }; + + margin = mkOption { + type = nullOr str; + default = null; + description = "Margins value using the CSS format without units."; + example = "20 5"; + }; + + inherit (margins) margin-top margin-left margin-bottom margin-right; + + name = mkOption { + type = nullOr str; + default = null; + description = + "Optional name added as a CSS class, for styling multiple waybars."; + example = "waybar-1"; + }; + + gtk-layer-shell = mkOption { + type = nullOr bool; + default = null; + example = false; + description = + "Option to disable the use of gtk-layer-shell for popups."; + }; + }; + }; +in { + meta.maintainers = with lib.maintainers; [ berbiche ]; + + options.programs.waybar = with lib.types; { + enable = mkEnableOption "Waybar"; + + package = mkOption { + type = package; + default = pkgs.waybar; + defaultText = "pkgs.waybar"; + description = '' + Waybar package to use. Set to null to use the default module. + ''; + }; + + settings = mkOption { + type = listOf waybarBarConfig; + default = [ ]; + description = '' + Configuration for Waybar, see + for supported values. + ''; + example = literalExample '' + [ + { + layer = "top"; + position = "top"; + height = 30; + output = [ + "eDP-1" + "HDMI-A-1" + ]; + modules-left = [ "sway/workspaces" "sway/mode" "wlr/taskbar" ]; + modules-center = [ "sway/window" "custom/hello-from-waybar" ]; + modules-right = [ "mpd" "custom/mymodule#with-css-id" "temperature" ]; + modules = { + "sway/workspaces" = { + disable-scroll = true; + all-outputs = true; + }; + "custom/hello-from-waybar" = { + format = "hello {}"; + max-length = 40; + interval = "once"; + exec = pkgs.writeShellScript "hello-from-waybar" ''' + echo "from within waybar" + '''; + }; + }; + } + ] + ''; + }; + + systemd.enable = mkEnableOption "Waybar systemd integration"; + + style = mkOption { + type = nullOr str; + default = null; + description = '' + CSS style of the bar. + See + for the documentation. + ''; + example = '' + * { + border: none; + border-radius: 0; + font-family: Source Code Pro; + } + window#waybar { + background: #16191C; + color: #AAB2BF; + } + #workspaces button { + padding: 0 5px; + } + ''; + }; + }; + + config = let + writePrettyJSON = jsonFormat.generate; + + configSource = let + # Removes nulls because Waybar ignores them for most values + removeNulls = filterAttrs (_: v: v != null); + + # Makes the actual valid configuration Waybar accepts + # (strips our custom settings before converting to JSON) + makeConfiguration = configuration: + let + # The "modules" option is not valid in the JSON + # as its descendants have to live at the top-level + settingsWithoutModules = removeAttrs configuration [ "modules" ]; + settingsModules = + optionalAttrs (configuration.modules != { }) configuration.modules; + in removeNulls (settingsWithoutModules // settingsModules); + # The clean list of configurations + finalConfiguration = map makeConfiguration cfg.settings; + in writePrettyJSON "waybar-config.json" finalConfiguration; + + # + # Warnings are generated based on the following things: + # 1. A `module` is referenced in any of `modules-{left,center,right}` that is neither + # a default module name nor defined in `modules`. + # 2. A `module` is defined in `modules` but is not referenced in either of + # `modules-{left,center,right}`. + # 3. A custom `module` configuration is defined in `modules` but has an invalid name + # for a custom module (i.e. not "custom/my-module-name"). + # + warnings = let + mkUnreferencedModuleWarning = name: + "The module '${name}' defined in '${modulesPath}' is not referenced " + + "in either `modules-left`, `modules-center` or `modules-right` of Waybar's options"; + mkUndefinedModuleWarning = settings: name: + let + # Locations where the module is undefined (a combination modules-{left,center,right}) + locations = flip filter [ "left" "center" "right" ] + (x: elem name settings."modules-${x}"); + mkPath = loc: "'${modulesPath}-${loc}'"; + # The modules-{left,center,right} configuration that includes + # an undefined module + path = concatMapStringsSep " and " mkPath locations; + in "The module '${name}' defined in ${path} is neither " + + "a default module or a custom module declared in '${modulesPath}'"; + mkInvalidModuleNameWarning = name: + "The custom module '${name}' defined in '${modulesPath}' is not a valid " + + "module name. A custom module's name must start with 'custom/' " + + "like 'custom/mymodule' for instance"; + + allFaultyModules = flip map cfg.settings (settings: + let + allModules = unique + (concatMap (x: attrByPath [ "modules-${x}" ] [ ] settings) [ + "left" + "center" + "right" + ]); + declaredModules = attrNames settings.modules; + # Modules declared in `modules` but not referenced in `modules-{left,center,right}` + unreferencedModules = subtractLists allModules declaredModules; + # Modules listed in modules-{left,center,right} that are not default modules + nonDefaultModules = subtractLists defaultModuleNames allModules; + # Modules referenced in `modules-{left,center,right}` but not declared in `modules` + undefinedModules = subtractLists declaredModules nonDefaultModules; + # Check for invalid module names + invalidModuleNames = + filter (m: !isValidCustomModuleName m) declaredModules; + in { + # The Waybar bar configuration (since config.settings is a list) + inherit settings; + undef = undefinedModules; + unref = unreferencedModules; + invalidName = invalidModuleNames; + }); + + allWarnings = flip concatMap allFaultyModules + ({ settings, undef, unref, invalidName }: + let + unreferenced = map mkUnreferencedModuleWarning unref; + undefined = map (mkUndefinedModuleWarning settings) undef; + invalid = map mkInvalidModuleNameWarning invalidName; + in undefined ++ unreferenced ++ invalid); + in allWarnings; + + in mkIf cfg.enable (mkMerge [ + { home.packages = [ cfg.package ]; } + (mkIf (cfg.settings != [ ]) { + # Generate warnings about defined but unreferenced modules + inherit warnings; + + xdg.configFile."waybar/config".source = configSource; + }) + (mkIf (cfg.style != null) { + xdg.configFile."waybar/style.css".text = cfg.style; + }) + (mkIf cfg.systemd.enable { + systemd.user.services.waybar = { + Unit = { + Description = + "Highly customizable Wayland bar for Sway and Wlroots based compositors."; + Documentation = "https://github.com/Alexays/Waybar/wiki"; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + Type = "dbus"; + BusName = "fr.arouillard.waybar"; + ExecStart = "${cfg.package}/bin/waybar"; + Restart = "always"; + RestartSec = "1sec"; + }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + }; + }) + ]); +} diff --git a/modules/programs/zathura.nix b/modules/programs/zathura.nix index d9f3c1af1fd6..64a77cb3bee1 100644 --- a/modules/programs/zathura.nix +++ b/modules/programs/zathura.nix @@ -20,6 +20,13 @@ in { Zathura, a highly customizable and functional document viewer focused on keyboard interaction''; + package = mkOption { + type = types.package; + default = pkgs.zathura; + defaultText = "pkgs.zathura"; + description = "The Zathura package to use"; + }; + options = mkOption { default = { }; type = with types; attrsOf (either str (either bool int)); @@ -49,7 +56,7 @@ in { }; config = mkIf cfg.enable { - home.packages = [ pkgs.zathura ]; + home.packages = [ cfg.package ]; xdg.configFile."zathura/zathurarc".text = concatStringsSep "\n" ([ ] ++ optional (cfg.extraConfig != "") cfg.extraConfig diff --git a/modules/programs/zplug.nix b/modules/programs/zplug.nix index 0df395292a75..6cb5e98e3135 100644 --- a/modules/programs/zplug.nix +++ b/modules/programs/zplug.nix @@ -45,11 +45,14 @@ in { optionalString (plugin.tags != [ ]) '' ${concatStrings (map (tag: ", ${tag}") plugin.tags)} '' - } + } '') cfg.plugins)} ''} - zplug install + if ! zplug check; then + zplug install + fi + zplug load ''; diff --git a/modules/programs/zsh.nix b/modules/programs/zsh.nix index 5199e51e9728..9e3c517d23ed 100644 --- a/modules/programs/zsh.nix +++ b/modules/programs/zsh.nix @@ -22,6 +22,10 @@ let mapAttrsToList (k: v: "alias -g ${k}=${lib.escapeShellArg v}") cfg.shellGlobalAliases ); + dirHashesStr = concatStringsSep "\n" ( + mapAttrsToList (k: v: ''hash -d ${k}="${v}"'') cfg.dirHashes + ); + zdotdir = "$HOME/" + cfg.dotDir; bindkeyCommands = { @@ -185,6 +189,14 @@ in type = types.nullOr types.bool; }; + cdpath = mkOption { + default = []; + description = '' + List of paths to autocomplete calls to `cd`. + ''; + type = types.listOf types.str; + }; + dotDir = mkOption { default = null; example = ".config/zsh"; @@ -226,6 +238,21 @@ in type = types.attrsOf types.str; }; + dirHashes = mkOption { + default = {}; + example = literalExample '' + { + docs = "$HOME/Documents"; + vids = "$HOME/Videos"; + dl = "$HOME/Downloads"; + } + ''; + description = '' + An attribute set that adds to named directory hash table. + ''; + type = types.attrsOf types.str; + }; + enableCompletion = mkOption { default = true; description = '' @@ -275,6 +302,12 @@ in description = "Extra commands that should be added to .zshrc."; }; + initExtraFirst = mkOption { + default = ""; + type = types.lines; + description = "Commands that should be added to top of .zshrc."; + }; + envExtra = mkOption { default = ""; type = types.lines; @@ -390,8 +423,14 @@ in ++ optional cfg.oh-my-zsh.enable oh-my-zsh; home.file."${relToDotDir ".zshrc"}".text = '' + ${cfg.initExtraFirst} + typeset -U path cdpath fpath manpath + ${optionalString (cfg.cdpath != []) '' + cdpath+=(${concatStringsSep " " cfg.cdpath}) + ''} + for profile in ''${(z)NIX_PROFILES}; do fpath+=($profile/share/zsh/site-functions $profile/share/zsh/$ZSH_VERSION/functions $profile/share/zsh/vendor-completions) done @@ -412,10 +451,10 @@ in fpath+="$HOME/${pluginsDir}/${plugin.name}" '') cfg.plugins)} - # Oh-My-Zsh calls compinit during initialization, + # Oh-My-Zsh/Prezto calls compinit during initialization, # calling it twice causes sight start up slowdown # as all $fpath entries will be traversed again. - ${optionalString (cfg.enableCompletion && !cfg.oh-my-zsh.enable) + ${optionalString (cfg.enableCompletion && !cfg.oh-my-zsh.enable && !cfg.prezto.enable) "autoload -U compinit && compinit" } @@ -443,6 +482,9 @@ in source $ZSH/oh-my-zsh.sh ''} + ${optionalString cfg.prezto.enable + (builtins.readFile "${pkgs.zsh-prezto}/runcoms/zshrc")} + ${concatStrings (map (plugin: '' if [ -f "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}" ]; then source "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}" @@ -450,7 +492,7 @@ in '') cfg.plugins)} # History options should be set in .zshrc and after oh-my-zsh sourcing. - # See https://github.com/rycee/home-manager/issues/177. + # See https://github.com/nix-community/home-manager/issues/177. HISTSIZE="${toString cfg.history.size}" SAVEHIST="${toString cfg.history.save}" ${if versionAtLeast config.home.stateVersion "20.03" @@ -473,12 +515,15 @@ in # Global Aliases ${globalAliasesStr} + + # Named Directory Hashes + ${dirHashesStr} ''; } (mkIf cfg.oh-my-zsh.enable { # Make sure we create a cache directory since some plugins expect it to exist - # See: https://github.com/rycee/home-manager/issues/761 + # See: https://github.com/nix-community/home-manager/issues/761 home.file."${config.xdg.cacheHome}/oh-my-zsh/.keep".text = ""; }) diff --git a/modules/programs/zsh/prezto.nix b/modules/programs/zsh/prezto.nix new file mode 100644 index 000000000000..1bd1be584326 --- /dev/null +++ b/modules/programs/zsh/prezto.nix @@ -0,0 +1,543 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + + cfg = config.programs.zsh.prezto; + + relToDotDir = file: + (optionalString (config.programs.zsh.dotDir != null) + (config.programs.zsh.dotDir + "/")) + file; + + preztoModule = types.submodule { + options = { + enable = mkEnableOption "prezto"; + + caseSensitive = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = + "Set case-sensitivity for completion, history lookup, etc."; + }; + + color = mkOption { + type = types.nullOr types.bool; + default = true; + example = false; + description = "Color output (auto set to 'no' on dumb terminals)"; + }; + + pmoduleDirs = mkOption { + type = types.listOf types.path; + default = [ ]; + example = [ "$HOME/.zprezto-contrib" ]; + description = "Add additional directories to load prezto modules from"; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional configuration to add to .zpreztorc. + ''; + }; + + extraModules = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "attr" "stat" ]; + description = "Set the Zsh modules to load (man zshmodules)."; + }; + + extraFunctions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "zargs" "zmv" ]; + description = "Set the Zsh functions to load (man zshcontrib)."; + }; + + pmodules = mkOption { + type = types.listOf types.str; + default = [ + "environment" + "terminal" + "editor" + "history" + "directory" + "spectrum" + "utility" + "completion" + "prompt" + ]; + description = + "Set the Prezto modules to load (browse modules). The order matters."; + }; + + autosuggestions.color = mkOption { + type = types.nullOr types.str; + default = null; + example = "fg=blue"; + description = "Set the query found color."; + }; + + completions.ignoredHosts = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "0.0.0.0" "127.0.0.1" ]; + description = + "Set the entries to ignore in static */etc/hosts* for host completion."; + }; + + editor = { + keymap = mkOption { + type = types.nullOr (types.enum [ "emacs" "vi" ]); + default = "emacs"; + example = "vi"; + description = "Set the key mapping style to 'emacs' or 'vi'."; + }; + + dotExpansion = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = "Auto convert .... to ../.."; + }; + + promptContext = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = "Allow the zsh prompt context to be shown."; + }; + }; + + git.submoduleIgnore = mkOption { + type = types.nullOr (types.enum [ "dirty" "untracked" "all" "none" ]); + default = null; + example = "all"; + description = + "Ignore submodules when they are 'dirty', 'untracked', 'all', or 'none'."; + }; + + gnuUtility.prefix = mkOption { + type = types.nullOr types.str; + default = null; + example = "g"; + description = "Set the command prefix on non-GNU systems."; + }; + + historySubstring = { + foundColor = mkOption { + type = types.nullOr types.str; + default = null; + example = "fg=blue"; + description = "Set the query found color."; + }; + + notFoundColor = mkOption { + type = types.nullOr types.str; + default = null; + example = "fg=red"; + description = "Set the query not found color."; + }; + + globbingFlags = mkOption { + type = types.nullOr types.str; + default = null; + description = "Set the search globbing flags."; + }; + }; + + macOS.dashKeyword = mkOption { + type = types.nullOr types.str; + default = null; + example = "manpages"; + description = + "Set the keyword used by `mand` to open man pages in Dash.app"; + }; + + prompt = { + theme = mkOption { + type = types.nullOr types.str; + default = "sorin"; + example = "pure"; + description = '' + Set the prompt theme to load. Setting it to 'random' + loads a random theme. Auto set to 'off' on dumb terminals.''; + }; + + pwdLength = mkOption { + type = types.nullOr (types.enum [ "short" "long" "full" ]); + default = null; + example = "short"; + description = '' + Set the working directory prompt display length. By + default, it is set to 'short'. Set it to 'long' (without '~' expansion) for + longer or 'full' (with '~' expansion) for even longer prompt display.''; + }; + + showReturnVal = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = '' + Set the prompt to display the return code along with an + indicator for non-zero return codes. This is not supported by all prompts.''; + }; + }; + + python = { + virtualenvAutoSwitch = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = "Auto switch to Python virtualenv on directory change."; + }; + + virtualenvInitialize = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = + "Automatically initialize virtualenvwrapper if pre-requisites are met."; + }; + }; + + ruby.chrubyAutoSwitch = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = "Auto switch the Ruby version on directory change."; + }; + + screen = { + autoStartLocal = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = + "Auto start a session when Zsh is launched in a local terminal."; + }; + + autoStartRemote = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = + "Auto start a session when Zsh is launched in a SSH connection."; + }; + }; + + ssh.identities = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "id_rsa" "id_rsa2" "id_github" ]; + description = "Set the SSH identities to load into the agent."; + }; + + syntaxHighlighting = { + highlighters = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "main" "brackets" "pattern" "line" "cursor" "root" ]; + description = '' + Set syntax highlighters. By default, only the main + highlighter is enabled.''; + }; + + styles = mkOption { + type = types.attrsOf types.str; + default = { }; + example = { + builtin = "bg=blue"; + command = "bg=blue"; + function = "bg=blue"; + }; + description = "Set syntax highlighting styles."; + }; + + pattern = mkOption { + type = types.attrsOf types.str; + default = { }; + example = { "rm*-rf*" = "fg=white,bold,bg=red"; }; + description = "Set syntax pattern styles."; + }; + }; + + terminal = { + autoTitle = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = "Auto set the tab and window titles."; + }; + + windowTitleFormat = mkOption { + type = types.nullOr types.str; + default = null; + example = "%n@%m: %s"; + description = "Set the window title format."; + }; + + tabTitleFormat = mkOption { + type = types.nullOr types.str; + default = null; + example = "%m: %s"; + description = "Set the tab title format."; + }; + + multiplexerTitleFormat = mkOption { + type = types.nullOr types.str; + default = null; + example = "%s"; + description = "Set the multiplexer title format."; + }; + }; + + tmux = { + autoStartLocal = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = + "Auto start a session when Zsh is launched in a local terminal."; + }; + + autoStartRemote = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = + "Auto start a session when Zsh is launched in a SSH connection."; + }; + + itermIntegration = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = "Integrate with iTerm2."; + }; + + defaultSessionName = mkOption { + type = types.nullOr types.str; + default = null; + example = "YOUR DEFAULT SESSION NAME"; + description = "Set the default session name."; + }; + }; + + utility.safeOps = mkOption { + type = types.nullOr types.bool; + default = null; + example = true; + description = '' + Enabled safe options. This aliases cp, ln, mv and rm so + that they prompt before deleting or overwriting files. Set to 'no' to disable + this safer behavior.''; + }; + }; + }; + +in { + options = { + programs.zsh = { + prezto = mkOption { + type = preztoModule; + default = { }; + description = "Options to configure prezto."; + }; + }; + }; + config = mkIf cfg.enable (mkMerge [{ + home.file."${relToDotDir ".zprofile"}".text = + builtins.readFile "${pkgs.zsh-prezto}/runcoms/zprofile"; + home.file."${relToDotDir ".zlogin"}".text = + builtins.readFile "${pkgs.zsh-prezto}/runcoms/zlogin"; + home.file."${relToDotDir ".zlogout"}".text = + builtins.readFile "${pkgs.zsh-prezto}/runcoms/zlogout"; + home.packages = with pkgs; [ zsh-prezto ]; + + home.file."${relToDotDir ".zshenv"}".text = + (builtins.readFile "${pkgs.zsh-prezto}/runcoms/zshenv"); + home.file."${relToDotDir ".zpreztorc"}".text = '' + # Generated by Nix + ${optionalString (cfg.caseSensitive != null) '' + zstyle ':prezto:*:*' case-sensitive '${ + if cfg.caseSensitive then "yes" else "no" + }' + ''} + ${optionalString (cfg.color != null) '' + zstyle ':prezto:*:*' color '${if cfg.color then "yes" else "no"}' + ''} + ${optionalString (cfg.pmoduleDirs != [ ]) '' + zstyle ':prezto:load' pmodule-dirs ${ + builtins.concatStringsSep " " cfg.pmoduleDirs + } + ''} + ${optionalString (cfg.extraModules != [ ]) '' + zstyle ':prezto:load' zmodule ${ + strings.concatMapStringsSep " " strings.escapeShellArg + cfg.extraModules + } + ''} + ${optionalString (cfg.extraFunctions != [ ]) '' + zstyle ':prezto:load' zfunction ${ + strings.concatMapStringsSep " " strings.escapeShellArg + cfg.extraFunctions + } + ''} + ${optionalString (cfg.pmodules != [ ]) '' + zstyle ':prezto:load' pmodule \ + ${ + strings.concatMapStringsSep " \\\n " strings.escapeShellArg + cfg.pmodules + } + ''} + ${optionalString (cfg.autosuggestions.color != null) '' + zstyle ':prezto:module:autosuggestions:color' found '${cfg.autosuggestions.color}' + ''} + ${optionalString (cfg.completions.ignoredHosts != [ ]) '' + zstyle ':prezto:module:completion:*:hosts' etc-host-ignores \ + ${ + strings.concatMapStringsSep " " strings.escapeShellArg + cfg.completions.ignoredHosts + } + ''} + ${optionalString (cfg.editor.keymap != null) '' + zstyle ':prezto:module:editor' key-bindings '${cfg.editor.keymap}' + ''} + ${optionalString (cfg.editor.dotExpansion != null) '' + zstyle ':prezto:module:editor' dot-expansion '${ + if cfg.editor.dotExpansion then "yes" else "no" + }' + ''} + ${optionalString (cfg.editor.promptContext != null) '' + zstyle ':prezto:module:editor' ps-context '${ + if cfg.editor.promptContext then "yes" else "no" + }' + ''} + ${optionalString (cfg.git.submoduleIgnore != null) '' + zstyle ':prezto:module:git:status:ignore' submodules '${cfg.git.submoduleIgnore}' + ''} + ${optionalString (cfg.gnuUtility.prefix != null) '' + zstyle ':prezto:module:gnu-utility' prefix '${cfg.gnuUtility.prefix}' + ''} + ${optionalString (cfg.historySubstring.foundColor != null) '' + zstyle ':prezto:module:history-substring-search:color' found '${cfg.historySubstring.foundColor}' + ''} + ${optionalString (cfg.historySubstring.notFoundColor != null) '' + zstyle ':prezto:module:history-substring-search:color' not-found '${cfg.historySubstring.notFoundColor}' + ''} + ${optionalString (cfg.historySubstring.globbingFlags != null) '' + zstyle ':prezto:module:history-substring-search:color' globbing-flags '${cfg.historySubstring.globbingFlags}' + ''} + ${optionalString (cfg.macOS.dashKeyword != null) '' + zstyle ':prezto:module:osx:man' dash-keyword '${cfg.macOS.dashKeyword}' + ''} + ${optionalString (cfg.prompt.theme != null) '' + zstyle ':prezto:module:prompt' theme '${cfg.prompt.theme}' + ''} + ${optionalString (cfg.prompt.pwdLength != null) '' + zstyle ':prezto:module:prompt' pwd-length '${cfg.prompt.pwdLength}' + ''} + ${optionalString (cfg.prompt.showReturnVal != null) '' + zstyle ':prezto:module:prompt' show-return-val '${cfg.prompt.showReturnVal}' + ''} + ${optionalString (cfg.python.virtualenvAutoSwitch != null) '' + zstyle ':prezto:module:python:virtualenv' auto-switch '${ + if cfg.python.virtualenvAutoSwitch then "yes" else "no" + }' + ''} + ${optionalString (cfg.python.virtualenvInitialize != null) '' + zstyle ':prezto:module:python:virtualenv' initialize '${ + if cfg.python.virtualenvInitialize then "yes" else "no" + }' + ''} + ${optionalString (cfg.ruby.chrubyAutoSwitch != null) '' + zstyle ':prezto:module:ruby:chruby' auto-switch '${ + if cfg.ruby.chrubyAutoSwitch then "yes" else "no" + }' + ''} + ${optionalString (cfg.screen.autoStartLocal != null) '' + zstyle ':prezto:module:screen:auto-start' local '${ + if cfg.screen.autoStartLocal then "yes" else "no" + }' + ''} + ${optionalString (cfg.screen.autoStartRemote != null) '' + zstyle ':prezto:module:screen:auto-start' remote '${ + if cfg.screen.autoStartRemote then "yes" else "no" + }' + ''} + ${optionalString (cfg.ssh.identities != [ ]) '' + zstyle ':prezto:module:ssh:load' identities \ + ${ + strings.concatMapStringsSep " " strings.escapeShellArg + cfg.ssh.identities + } + ''} + ${optionalString (cfg.syntaxHighlighting.highlighters != [ ]) '' + zstyle ':prezto:module:syntax-highlighting' highlighters \ + ${ + strings.concatMapStringsSep " \\\n " strings.escapeShellArg + cfg.syntaxHighlighting.highlighters + } + ''} + ${optionalString (cfg.syntaxHighlighting.styles != { }) '' + zstyle ':prezto:module:syntax-highlighting' styles \ + ${ + builtins.concatStringsSep " \\\n" (attrsets.mapAttrsToList + (k: v: strings.escapeShellArg k + " " + strings.escapeShellArg v) + cfg.syntaxHighlighting.styles) + } + ''} + ${optionalString (cfg.syntaxHighlighting.pattern != { }) '' + zstyle ':prezto:module:syntax-highlighting' pattern \ + ${ + builtins.concatStringsSep " \\\n" (attrsets.mapAttrsToList + (k: v: strings.escapeShellArg k + " " + strings.escapeShellArg v) + cfg.syntaxHighlighting.pattern) + } + ''} + ${optionalString (cfg.terminal.autoTitle != null) '' + zstyle ':prezto:module:terminal' auto-title '${ + if cfg.terminal.autoTitle then "yes" else "no" + }' + ''} + ${optionalString (cfg.terminal.windowTitleFormat != null) '' + zstyle ':prezto:module:terminal:window-title' format '${cfg.terminal.windowTitleFormat}' + ''} + ${optionalString (cfg.terminal.tabTitleFormat != null) '' + zstyle ':prezto:module:terminal:tab-title' format '${cfg.terminal.tabTitleFormat}' + ''} + ${optionalString (cfg.terminal.multiplexerTitleFormat != null) '' + zstyle ':prezto:module:terminal:multiplexer-title' format '${cfg.terminal.multiplexerTitleFormat}' + ''} + ${optionalString (cfg.tmux.autoStartLocal != null) '' + zstyle ':prezto:module:tmux:auto-start' local '${ + if cfg.tmux.autoStartLocal then "yes" else "no" + }' + ''} + ${optionalString (cfg.tmux.autoStartRemote != null) '' + zstyle ':prezto:module:tmux:auto-start' remote '${ + if cfg.tmux.autoStartRemote then "yes" else "no" + }' + ''} + ${optionalString (cfg.tmux.itermIntegration != null) '' + zstyle ':prezto:module:tmux:iterm' integrate '${ + if cfg.tmux.itermIntegration then "yes" else "no" + }' + ''} + ${optionalString (cfg.tmux.defaultSessionName != null) '' + zstyle ':prezto:module:tmux:session' name '${cfg.tmux.defaultSessionName}' + ''} + ${optionalString (cfg.utility.safeOps != null) '' + zstyle ':prezto:module:utility' safe-ops '${ + if cfg.utility.safeOps then "yes" else "no" + }' + ''} + ${cfg.extraConfig} + ''; + }]); +} diff --git a/modules/services/caffeine.nix b/modules/services/caffeine.nix new file mode 100644 index 000000000000..bb24a0e05273 --- /dev/null +++ b/modules/services/caffeine.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.caffeine; + +in { + meta.maintainers = [ maintainers.uvnikita ]; + + options = { + services.caffeine = { enable = mkEnableOption "Caffeine service"; }; + }; + + config = mkIf cfg.enable { + systemd.user.services.caffeine = { + Unit = { Description = "caffeine"; }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + + Service = { + Restart = "on-failure"; + PrivateTmp = true; + ProtectSystem = "full"; + ProtectHome = "yes"; + Type = "exec"; + Slice = "session.slice"; + ExecStart = "${pkgs.caffeine-ng}/bin/caffeine"; + }; + }; + }; +} diff --git a/modules/services/dwm-status.nix b/modules/services/dwm-status.nix index 7a19e5e5fc9a..a0c2a7243698 100644 --- a/modules/services/dwm-status.nix +++ b/modules/services/dwm-status.nix @@ -5,11 +5,13 @@ with lib; let cfg = config.services.dwm-status; + jsonFormat = pkgs.formats.json { }; + features = [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]; - configText = builtins.toJSON ({ inherit (cfg) order; } // cfg.extraConfig); + finalConfig = { inherit (cfg) order; } // cfg.extraConfig; - configFile = pkgs.writeText "dwm-status.json" configText; + configFile = jsonFormat.generate "dwm-status.json" finalConfig; in { options = { @@ -30,7 +32,7 @@ in { }; extraConfig = mkOption { - type = types.attrs; + type = jsonFormat.type; default = { }; example = literalExample '' { diff --git a/modules/services/emacs.nix b/modules/services/emacs.nix index a73b750c5137..560d9a6c4b8b 100644 --- a/modules/services/emacs.nix +++ b/modules/services/emacs.nix @@ -6,28 +6,28 @@ let cfg = config.services.emacs; emacsCfg = config.programs.emacs; - emacsBinPath = "${emacsCfg.finalPackage}/bin"; - emacsVersion = getVersion emacsCfg.finalPackage; + emacsBinPath = "${cfg.package}/bin"; + emacsVersion = getVersion cfg.package; # Adapted from upstream emacs.desktop - clientDesktopItem = pkgs.makeDesktopItem rec { - name = "emacsclient"; - desktopName = "Emacs Client"; - genericName = "Text Editor"; - comment = "Edit text"; - mimeType = - "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;"; - exec = "${emacsBinPath}/emacsclient ${ - concatStringsSep " " cfg.client.arguments - } %F"; - icon = "emacs"; - type = "Application"; - terminal = "false"; - categories = "Utility;TextEditor;"; - extraEntries = '' - StartupWMClass=Emacs - ''; - }; + clientDesktopItem = pkgs.writeTextDir "share/applications/emacsclient.desktop" + (generators.toINI { } { + "Desktop Entry" = { + Type = "Application"; + Exec = "${emacsBinPath}/emacsclient ${ + concatStringsSep " " cfg.client.arguments + } %F"; + Terminal = false; + Name = "Emacs Client"; + Icon = "emacs"; + Comment = "Edit text"; + GenericName = "Text Editor"; + MimeType = + "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;"; + Categories = "Utility;TextEditor;"; + StartupWMClass = "Emacs"; + }; + }); # Match the default socket path for the Emacs version so emacsclient continues # to work without wrapping it. It might be worthwhile to allow customizing the @@ -51,6 +51,16 @@ in { options.services.emacs = { enable = mkEnableOption "the Emacs daemon"; + package = mkOption { + type = types.package; + default = if emacsCfg.enable then emacsCfg.finalPackage else pkgs.emacs; + defaultText = literalExample '' + if config.programs.emacs.enable then config.programs.emacs.finalPackage + else pkgs.emacs + ''; + description = "The Emacs package to use."; + }; + client = { enable = mkEnableOption "generation of Emacs client desktop file"; arguments = mkOption { @@ -72,18 +82,11 @@ in { config = mkIf cfg.enable (mkMerge [ { - assertions = [ - { - assertion = emacsCfg.enable; - message = "The Emacs service module requires" - + " 'programs.emacs.enable = true'."; - } - { - assertion = cfg.socketActivation.enable - -> versionAtLeast emacsVersion "26"; - message = "Socket activation requires Emacs 26 or newer."; - } - ]; + assertions = [{ + assertion = cfg.socketActivation.enable + -> versionAtLeast emacsVersion "26"; + message = "Socket activation requires Emacs 26 or newer."; + }]; systemd.user.services.emacs = { Unit = { @@ -119,7 +122,7 @@ in { Install = { WantedBy = [ "default.target" ]; }; }; - home.packages = optional cfg.client.enable clientDesktopItem; + home.packages = optional cfg.client.enable (hiPrio clientDesktopItem); } (mkIf cfg.socketActivation.enable { diff --git a/modules/services/gammastep.nix b/modules/services/gammastep.nix new file mode 100644 index 000000000000..7740c462cb0b --- /dev/null +++ b/modules/services/gammastep.nix @@ -0,0 +1,166 @@ +# Adapted from Nixpkgs. + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.gammastep; + +in { + meta.maintainers = [ maintainers.petabyteboy ]; + + options.services.gammastep = { + enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Enable Gammastep to change your screen's colour temperature depending on + the time of day. + ''; + }; + + latitude = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Your current latitude, between -90.0 and + 90.0. Must be provided along with + longitude. + ''; + }; + + longitude = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Your current longitude, between -180.0 and + 180.0. Must be provided along with + latitude. + ''; + }; + + provider = mkOption { + type = types.enum [ "manual" "geoclue2" ]; + default = "manual"; + description = '' + The location provider to use for determining your location. If set to + manual you must also provide latitude/longitude. + If set to geoclue2, you must also enable the global + geoclue2 service. + ''; + }; + + temperature = { + day = mkOption { + type = types.int; + default = 5500; + description = '' + Colour temperature to use during the day, between + 1000 and 25000 K. + ''; + }; + + night = mkOption { + type = types.int; + default = 3700; + description = '' + Colour temperature to use at night, between + 1000 and 25000 K. + ''; + }; + }; + + brightness = { + day = mkOption { + type = types.str; + default = "1"; + description = '' + Screen brightness to apply during the day, + between 0.1 and 1.0. + ''; + }; + + night = mkOption { + type = types.str; + default = "1"; + description = '' + Screen brightness to apply during the night, + between 0.1 and 1.0. + ''; + }; + }; + + package = mkOption { + type = types.package; + default = pkgs.gammastep; + defaultText = literalExample "pkgs.gammastep"; + description = '' + gammastep derivation to use. + ''; + }; + + tray = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Start the gammastep-indicator tray applet. + ''; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "-v" "-m randr" ]; + description = '' + Additional command-line arguments to pass to + gammastep. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [{ + assertion = cfg.provider == "manual" -> cfg.latitude != null + && cfg.longitude != null; + message = "Must provide services.gammastep.latitude and" + + " services.gammastep.latitude when" + + " services.gammastep.provider is set to \"manual\"."; + }]; + + systemd.user.services.gammastep = { + Unit = { + Description = "Gammastep colour temperature adjuster"; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + + Service = { + ExecStart = let + providerString = if cfg.provider == "manual" then + "${cfg.latitude}:${cfg.longitude}" + else + cfg.provider; + + args = [ + "-l ${providerString}" + "-t ${toString cfg.temperature.day}:${ + toString cfg.temperature.night + }" + "-b ${toString cfg.brightness.day}:${toString cfg.brightness.night}" + ] ++ cfg.extraOptions; + + command = if cfg.tray then "gammastep-indicator" else "gammastep"; + in "${cfg.package}/bin/${command} ${concatStringsSep " " args}"; + RestartSec = 3; + Restart = "always"; + }; + }; + }; + +} diff --git a/modules/services/hound.nix b/modules/services/hound.nix index 00589f3405fc..07b5d4765388 100644 --- a/modules/services/hound.nix +++ b/modules/services/hound.nix @@ -6,12 +6,14 @@ let cfg = config.services.hound; - configFile = pkgs.writeText "hound-config.json" (builtins.toJSON { + jsonFormat = pkgs.formats.json { }; + + configFile = jsonFormat.generate "hound-config.json" { max-concurrent-indexers = cfg.maxConcurrentIndexers; dbpath = cfg.databasePath; repos = cfg.repositories; health-check-url = "/healthz"; - }); + }; houndOptions = [ "--addr ${cfg.listenAddress}" "--conf ${configFile}" ]; @@ -41,7 +43,7 @@ in { }; repositories = mkOption { - type = types.attrsOf (types.uniq types.attrs); + type = types.attrsOf jsonFormat.type; default = { }; example = literalExample '' { diff --git a/modules/services/kanshi.nix b/modules/services/kanshi.nix new file mode 100644 index 000000000000..4e5e5f104e65 --- /dev/null +++ b/modules/services/kanshi.nix @@ -0,0 +1,194 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.kanshi; + + outputModule = types.submodule { + options = { + + criteria = mkOption { + type = types.str; + description = '' + The criteria can either be an output name, an output description or "*". + The latter can be used to match any output. + + On + + sway + 1 + , + output names and descriptions can be obtained via + swaymsg -t get_outputs. + ''; + }; + + status = mkOption { + type = types.nullOr (types.enum [ "enable" "disable" ]); + default = null; + description = '' + Enables or disables the specified output. + ''; + }; + + mode = mkOption { + type = types.nullOr types.str; + default = null; + example = "1920x1080@60Hz"; + description = '' + <width>x<height>[@<rate>[Hz]] + + Configures the specified output to use the specified mode. + Modes are a combination of width and height (in pixels) and + a refresh rate (in Hz) that your display can be configured to use. + ''; + }; + + position = mkOption { + type = types.nullOr types.str; + default = null; + example = "1600,0"; + description = '' + <x>,<y> + + Places the output at the specified position in the global coordinates + space. + ''; + }; + + scale = mkOption { + type = types.nullOr types.float; + default = null; + example = 2; + description = '' + Scales the output by the specified scale factor. + ''; + }; + + transform = mkOption { + type = types.nullOr (types.enum [ + "normal" + "90" + "180" + "270" + "flipped" + "flipped-90" + "flipped-180" + "flipped-270" + ]); + default = null; + description = '' + Sets the output transform. + ''; + }; + }; + }; + + outputStr = { criteria, status, mode, position, scale, transform, ... }: + ''output "${criteria}"'' + optionalString (status != null) " ${status}" + + optionalString (mode != null) " mode ${mode}" + + optionalString (position != null) " position ${position}" + + optionalString (scale != null) " scale ${toString scale}" + + optionalString (transform != null) " transform ${transform}"; + + profileModule = types.submodule { + options = { + outputs = mkOption { + type = types.listOf outputModule; + default = [ ]; + description = '' + Outputs configuration. + ''; + }; + + exec = mkOption { + type = types.nullOr types.str; + default = null; + example = + "\${pkg.sway}/bin/swaymsg workspace 1, move workspace to eDP-1"; + description = '' + Command executed after the profile is succesfully applied. + ''; + }; + }; + }; + + profileStr = name: + { outputs, exec, ... }: + '' + profile ${name} { + ${concatStringsSep "\n " (map outputStr outputs)} + '' + optionalString (exec != null) " exec ${exec}\n" + '' + } + ''; +in { + + meta.maintainers = [ maintainers.nurelin ]; + + options.services.kanshi = { + enable = mkEnableOption + "kanshi, a Wayland daemon that automatically configures outputs"; + + package = mkOption { + type = types.package; + default = pkgs.kanshi; + defaultText = literalExample "pkgs.kanshi"; + description = '' + kanshi derivation to use. + ''; + }; + + profiles = mkOption { + type = types.attrsOf profileModule; + default = { }; + description = '' + List of profiles. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration lines to append to the kanshi + configuration file. + ''; + }; + + systemdTarget = mkOption { + type = types.str; + default = "sway-session.target"; + description = '' + Systemd target to bind to. + ''; + }; + }; + + config = mkIf cfg.enable { + + xdg.configFile."kanshi/config".text = '' + ${concatStringsSep "\n" (mapAttrsToList profileStr cfg.profiles)} + ${cfg.extraConfig} + ''; + + systemd.user.services.kanshi = { + Unit = { + Description = "Dynamic output configuration"; + Documentation = "man:kanshi(1)"; + PartOf = cfg.systemdTarget; + Requires = cfg.systemdTarget; + After = cfg.systemdTarget; + }; + + Service = { + Type = "simple"; + ExecStart = "${cfg.package}/bin/kanshi"; + Restart = "always"; + }; + + Install = { WantedBy = [ cfg.systemdTarget ]; }; + }; + }; +} diff --git a/modules/services/mpd.nix b/modules/services/mpd.nix index 2aa1cd3a9fe5..a6ed0a483120 100644 --- a/modules/services/mpd.nix +++ b/modules/services/mpd.nix @@ -41,8 +41,17 @@ in { ''; }; - musicDirectory = mkOption { - type = types.path; + package = mkOption { + type = types.package; + default = pkgs.mpd; + defaultText = "pkgs.mpd"; + description = '' + The MPD package to run. + ''; + }; + + musicDirectory = mkOption { + type = with types; either path str; default = "${config.home.homeDirectory}/music"; defaultText = "$HOME/music"; apply = toString; # Prevent copies to Nix store. @@ -87,7 +96,14 @@ in { ''; }; - network = { + network = { + startWhenNeeded = mkOption { + type = types.bool; + default = false; + description = '' + Enable systemd socket activation. + ''; + }; listenAddress = mkOption { type = types.str; @@ -134,17 +150,34 @@ in { Description = "Music Player Daemon"; }; - Install = { + Install = mkIf (!cfg.network.startWhenNeeded) { WantedBy = [ "default.target" ]; }; Service = { Environment = "PATH=${config.home.profileDirectory}/bin"; - ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}"; + ExecStart = "${cfg.package}/bin/mpd --no-daemon ${mpdConf}"; Type = "notify"; ExecStartPre = ''${pkgs.bash}/bin/bash -c "${pkgs.coreutils}/bin/mkdir -p '${cfg.dataDir}' '${cfg.playlistDirectory}'"''; }; }; + systemd.user.sockets.mpd = mkIf cfg.network.startWhenNeeded { + Socket = { + ListenStream = let + listen = + if cfg.network.listenAddress == "any" + then toString cfg.network.port + else "${cfg.network.listenAddress}:${toString cfg.network.port}"; + in [ listen "%t/mpd/socket" ]; + + Backlog = 5; + KeepAlive = true; + }; + + Install = { + WantedBy = [ "sockets.target" ]; + }; + }; }; } diff --git a/modules/services/parcellite.nix b/modules/services/parcellite.nix index ce04238613b0..cf1a7fe890f8 100644 --- a/modules/services/parcellite.nix +++ b/modules/services/parcellite.nix @@ -5,17 +5,24 @@ with lib; let cfg = config.services.parcellite; - package = pkgs.parcellite; in { meta.maintainers = [ maintainers.gleber ]; - options = { - services.parcellite = { enable = mkEnableOption "Parcellite"; }; + options.services.parcellite = { + enable = mkEnableOption "Parcellite"; + + package = mkOption { + type = types.package; + default = pkgs.parcellite; + defaultText = literalExample "pkgs.parcellite"; + example = literalExample "pkgs.clipit"; + description = "Parcellite derivation to use."; + }; }; config = mkIf cfg.enable { - home.packages = [ package ]; + home.packages = [ cfg.package ]; systemd.user.services.parcellite = { Unit = { @@ -27,7 +34,7 @@ in { Install = { WantedBy = [ "graphical-session.target" ]; }; Service = { - ExecStart = "${package}/bin/parcellite"; + ExecStart = "${cfg.package}/bin/${cfg.package.pname}"; Restart = "on-abort"; }; }; diff --git a/modules/services/pulseeffects.nix b/modules/services/pulseeffects.nix index af6cebe1281c..445b1c0a1f87 100644 --- a/modules/services/pulseeffects.nix +++ b/modules/services/pulseeffects.nix @@ -36,10 +36,13 @@ in { systemd.user.services.pulseeffects = { Unit = { Description = "Pulseeffects daemon"; - Requires = [ "pulseaudio.service" "dbus.service" ]; - After = [ "graphical-session.target" ]; + Requires = [ "dbus.service" ]; + After = [ "graphical-session-pre.target" ]; + PartOf = [ "graphical-session.target" "pulseaudio.service" ]; }; + Install = { WantedBy = [ "graphical-session.target" ]; }; + Service = { ExecStart = "${pkgs.pulseeffects}/bin/pulseeffects --gapplication-service ${presetOpts}"; diff --git a/modules/services/unison.nix b/modules/services/unison.nix index 93c59e8fd626..a9cf23fb66e9 100644 --- a/modules/services/unison.nix +++ b/modules/services/unison.nix @@ -60,7 +60,7 @@ let }; }; - serialiseArg = key: val: "-${key}=${escapeShellArg val}"; + serialiseArg = key: val: escapeShellArg "-${key}=${escape [ "=" ] val}"; serialiseArgs = args: concatStringsSep " " (mapAttrsToList serialiseArg args); diff --git a/modules/services/window-managers/i3-sway/i3.nix b/modules/services/window-managers/i3-sway/i3.nix index 540025055c30..37f6e8bd8302 100644 --- a/modules/services/window-managers/i3-sway/i3.nix +++ b/modules/services/window-managers/i3-sway/i3.nix @@ -7,7 +7,7 @@ let cfg = config.xsession.windowManager.i3; commonOptions = import ./lib/options.nix { - inherit lib cfg pkgs; + inherit config lib cfg pkgs; moduleName = "i3"; isGaps = cfg.package == pkgs.i3-gaps; }; @@ -193,6 +193,21 @@ let else "") + "\n" + cfg.extraConfig); + # Validates the i3 configuration + checkI3Config = + pkgs.runCommandLocal "i3-config" { buildInputs = [ cfg.package ]; } '' + # We have to make sure the wrapper does not start a dbus session + export DBUS_SESSION_BUS_ADDRESS=1 + + # A zero exit code means i3 succesfully validated the configuration + i3 -c ${configFile} -C -d all || { + echo "i3 configuration validation failed" + echo "For a verbose log of the failure, run 'i3 -c ${configFile} -C -d all'" + exit 1 + }; + cp ${configFile} $out + ''; + in { options = { xsession.windowManager.i3 = { @@ -229,7 +244,7 @@ in { home.packages = [ cfg.package ]; xsession.windowManager.command = "${cfg.package}/bin/i3"; xdg.configFile."i3/config" = { - source = configFile; + source = checkI3Config; onChange = '' i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.* if [ -S $i3Socket ]; then @@ -250,7 +265,7 @@ in { warnings = [ ("'xsession.windowManager.i3.config.startup.*.workspace' is deprecated, " + "use 'xsession.windowManager.i3.config.assigns' instead." - + "See https://github.com/rycee/home-manager/issues/265.") + + "See https://github.com/nix-community/home-manager/issues/265.") ]; }) ]); diff --git a/modules/services/window-managers/i3-sway/lib/functions.nix b/modules/services/window-managers/i3-sway/lib/functions.nix index ea2938e6f6e5..9391e6e92fc8 100644 --- a/modules/services/window-managers/i3-sway/lib/functions.nix +++ b/modules/services/window-managers/i3-sway/lib/functions.nix @@ -41,31 +41,66 @@ rec { barStr = { id, fonts, mode, hiddenState, position, workspaceButtons , workspaceNumbers, command, statusCommand, colors, trayOutput, extraConfig - , ... }: '' + , ... }: + let colorsNotNull = lib.filterAttrs (n: v: v != null) colors != { }; + in '' bar { ${optionalString (id != null) "id ${id}"} - font pango:${concatStringsSep ", " fonts} - mode ${mode} - hidden_state ${hiddenState} - position ${position} + ${ + optionalString (fonts != [ ]) + "font pango:${concatStringsSep ", " fonts}" + } + ${optionalString (mode != null) "mode ${mode}"} + ${optionalString (hiddenState != null) "hidden_state ${hiddenState}"} + ${optionalString (position != null) "position ${position}"} ${ optionalString (statusCommand != null) "status_command ${statusCommand}" } ${moduleName}bar_command ${command} - workspace_buttons ${if workspaceButtons then "yes" else "no"} - strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"} - tray_output ${trayOutput} - colors { - background ${colors.background} - statusline ${colors.statusline} - separator ${colors.separator} - focused_workspace ${barColorSetStr colors.focusedWorkspace} - active_workspace ${barColorSetStr colors.activeWorkspace} - inactive_workspace ${barColorSetStr colors.inactiveWorkspace} - urgent_workspace ${barColorSetStr colors.urgentWorkspace} - binding_mode ${barColorSetStr colors.bindingMode} + ${ + optionalString (workspaceButtons != null) + "workspace_buttons ${if workspaceButtons then "yes" else "no"}" + } + ${ + optionalString (workspaceNumbers != null) + "strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"}" } + ${optionalString (trayOutput != null) "tray_output ${trayOutput}"} + ${optionalString colorsNotNull "colors {"} + ${ + optionalString (colors.background != null) + "background ${colors.background}" + } + ${ + optionalString (colors.statusline != null) + "statusline ${colors.statusline}" + } + ${ + optionalString (colors.separator != null) + "separator ${colors.separator}" + } + ${ + optionalString (colors.focusedWorkspace != null) + "focused_workspace ${barColorSetStr colors.focusedWorkspace}" + } + ${ + optionalString (colors.activeWorkspace != null) + "active_workspace ${barColorSetStr colors.activeWorkspace}" + } + ${ + optionalString (colors.inactiveWorkspace != null) + "inactive_workspace ${barColorSetStr colors.inactiveWorkspace}" + } + ${ + optionalString (colors.urgentWorkspace != null) + "urgent_workspace ${barColorSetStr colors.urgentWorkspace}" + } + ${ + optionalString (colors.bindingMode != null) + "binding_mode ${barColorSetStr colors.bindingMode}" + } + ${optionalString colorsNotNull "}"} ${extraConfig} } ''; diff --git a/modules/services/window-managers/i3-sway/lib/options.nix b/modules/services/window-managers/i3-sway/lib/options.nix index b5216f8ca1c8..f1d0436a5e25 100644 --- a/modules/services/window-managers/i3-sway/lib/options.nix +++ b/modules/services/window-managers/i3-sway/lib/options.nix @@ -1,4 +1,5 @@ -{ lib, moduleName, cfg, pkgs, capitalModuleName ? moduleName, isGaps ? true }: +{ config, lib, moduleName, cfg, pkgs, capitalModuleName ? moduleName +, isGaps ? true }: with lib; @@ -41,7 +42,7 @@ let description = '' Launch application on a particular workspace. DEPRECATED: Use xsession.windowManager.i3.config.assigns - instead. See . + instead. See . ''; }; }; @@ -49,8 +50,23 @@ let }; barModule = types.submodule { - options = { - inherit fonts; + options = let + versionAtLeast2009 = versionAtLeast config.home.stateVersion "20.09"; + mkNullableOption = { type, default, ... }@args: + mkOption (args // optionalAttrs versionAtLeast2009 { + type = types.nullOr type; + default = null; + example = default; + } // { + defaultText = literalExample '' + ${ + if isString default then default else "See code" + } for state version < 20.09, + null for state version ≥ 20.09 + ''; + }); + in { + fonts = fonts // optionalAttrs versionAtLeast2009 { default = [ ]; }; extraConfig = mkOption { type = types.lines; @@ -68,31 +84,31 @@ let ''; }; - mode = mkOption { + mode = mkNullableOption { type = types.enum [ "dock" "hide" "invisible" ]; default = "dock"; description = "Bar visibility mode."; }; - hiddenState = mkOption { + hiddenState = mkNullableOption { type = types.enum [ "hide" "show" ]; default = "hide"; description = "The default bar mode when 'bar.mode' == 'hide'."; }; - position = mkOption { + position = mkNullableOption { type = types.enum [ "top" "bottom" ]; default = "bottom"; description = "The edge of the screen ${moduleName}bar should show up."; }; - workspaceButtons = mkOption { + workspaceButtons = mkNullableOption { type = types.bool; default = true; description = "Whether workspace buttons should be shown or not."; }; - workspaceNumbers = mkOption { + workspaceNumbers = mkNullableOption { type = types.bool; default = true; description = @@ -112,32 +128,34 @@ let statusCommand = mkOption { type = types.nullOr types.str; - default = "${pkgs.i3status}/bin/i3status"; + default = + if versionAtLeast2009 then null else "${pkgs.i3status}/bin/i3status"; + example = "i3status"; description = "Command that will be used to get status lines."; }; colors = mkOption { type = types.submodule { options = { - background = mkOption { + background = mkNullableOption { type = types.str; default = "#000000"; description = "Background color of the bar."; }; - statusline = mkOption { + statusline = mkNullableOption { type = types.str; default = "#ffffff"; description = "Text color to be used for the statusline."; }; - separator = mkOption { + separator = mkNullableOption { type = types.str; default = "#666666"; description = "Text color to be used for the separator."; }; - focusedWorkspace = mkOption { + focusedWorkspace = mkNullableOption { type = barColorSetModule; default = { border = "#4c7899"; @@ -149,7 +167,7 @@ let ''; }; - activeWorkspace = mkOption { + activeWorkspace = mkNullableOption { type = barColorSetModule; default = { border = "#333333"; @@ -161,7 +179,7 @@ let ''; }; - inactiveWorkspace = mkOption { + inactiveWorkspace = mkNullableOption { type = barColorSetModule; default = { border = "#333333"; @@ -174,7 +192,7 @@ let ''; }; - urgentWorkspace = mkOption { + urgentWorkspace = mkNullableOption { type = barColorSetModule; default = { border = "#2f343a"; @@ -187,7 +205,7 @@ let ''; }; - bindingMode = mkOption { + bindingMode = mkNullableOption { type = barColorSetModule; default = { border = "#2f343a"; @@ -210,7 +228,7 @@ let ''; }; - trayOutput = mkOption { + trayOutput = mkNullableOption { type = types.str; default = "primary"; description = "Where to output tray."; @@ -573,7 +591,47 @@ in { bars = mkOption { type = types.listOf barModule; - default = [ { } ]; + default = if versionAtLeast config.home.stateVersion "20.09" then [{ + mode = "dock"; + hiddenState = "hide"; + position = "bottom"; + workspaceButtons = true; + workspaceNumbers = true; + statusCommand = "${pkgs.i3status}/bin/i3status"; + fonts = [ "monospace 8" ]; + trayOutput = "primary"; + colors = { + background = "#000000"; + statusline = "#ffffff"; + separator = "#666666"; + focusedWorkspace = { + border = "#4c7899"; + background = "#285577"; + text = "#ffffff"; + }; + activeWorkspace = { + border = "#333333"; + background = "#5f676a"; + text = "#ffffff"; + }; + inactiveWorkspace = { + border = "#333333"; + background = "#222222"; + text = "#888888"; + }; + urgentWorkspace = { + border = "#2f343a"; + background = "#900000"; + text = "#ffffff"; + }; + bindingMode = { + border = "#2f343a"; + background = "#900000"; + text = "#ffffff"; + }; + }; + }] else + [ { } ]; description = '' ${capitalModuleName} bars settings blocks. Set to empty list to remove bars completely. ''; @@ -587,13 +645,22 @@ in { See . ''; - example = literalExample '' - [ - { command = "systemctl --user restart polybar"; always = true; notification = false; } - { command = "dropbox start"; notification = false; } - { command = "firefox"; workspace = "1: web"; } - ]; - ''; + example = if moduleName == "i3" then + literalExample '' + [ + { command = "systemctl --user restart polybar"; always = true; notification = false; } + { command = "dropbox start"; notification = false; } + { command = "firefox"; workspace = "1: web"; } + ]; + '' + else + literalExample '' + [ + { command = "systemctl --user restart waybar"; always = true; } + { command = "dropbox start"; } + { command = "firefox"; } + ] + ''; }; gaps = mkOption { diff --git a/modules/services/window-managers/i3-sway/sway.nix b/modules/services/window-managers/i3-sway/sway.nix index 83c8afd6c46e..14d82d267121 100644 --- a/modules/services/window-managers/i3-sway/sway.nix +++ b/modules/services/window-managers/i3-sway/sway.nix @@ -7,7 +7,7 @@ let cfg = config.wayland.windowManager.sway; commonOptions = import ./lib/options.nix { - inherit lib cfg pkgs; + inherit config lib cfg pkgs; moduleName = "sway"; capitalModuleName = "Sway"; }; @@ -396,7 +396,7 @@ in { xdg.configFile."sway/config" = { source = configFile; onChange = '' - swaySocket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/sway-ipc.$UID.$(${pkgs.procps}/bin/pgrep -x sway).sock + swaySocket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/sway-ipc.$UID.$(${pkgs.procps}/bin/pgrep -x sway || ${pkgs.coreutils}/bin/true).sock if [ -S $swaySocket ]; then echo "Reloading sway" $DRY_RUN_CMD ${pkgs.sway}/bin/swaymsg -s $swaySocket reload diff --git a/modules/services/wlsunset.nix b/modules/services/wlsunset.nix new file mode 100644 index 000000000000..084dbdb7c3d6 --- /dev/null +++ b/modules/services/wlsunset.nix @@ -0,0 +1,97 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.services.wlsunset; + +in { + meta.maintainers = [ maintainers.matrss ]; + + options.services.wlsunset = { + enable = mkEnableOption "Whether to enable wlsunset."; + + package = mkOption { + type = types.package; + default = pkgs.wlsunset; + defaultText = "pkgs.wlsunset"; + description = '' + wlsunset derivation to use. + ''; + }; + + latitude = mkOption { + type = types.str; + description = '' + Your current latitude, between -90.0 and + 90.0. + ''; + }; + + longitude = mkOption { + type = types.str; + description = '' + Your current longitude, between -180.0 and + 180.0. + ''; + }; + + temperature = { + day = mkOption { + type = types.int; + default = 6500; + description = '' + Colour temperature to use during the day, in Kelvin (K). + This value must be greater than temperature.night. + ''; + }; + + night = mkOption { + type = types.int; + default = 4000; + description = '' + Colour temperature to use during the night, in Kelvin (K). + This value must be smaller than temperature.day. + ''; + }; + }; + + gamma = mkOption { + type = types.str; + default = "1.0"; + description = '' + Gamma value to use. + ''; + }; + + systemdTarget = mkOption { + type = types.str; + default = "graphical-session.target"; + description = '' + Systemd target to bind to. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.user.services.wlsunset = { + Unit = { + Description = "Day/night gamma adjustments for Wayland compositors."; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + ExecStart = let + args = [ + "-l ${cfg.latitude}" + "-L ${cfg.longitude}" + "-t ${toString cfg.temperature.night}" + "-T ${toString cfg.temperature.day}" + "-g ${cfg.gamma}" + ]; + in "${cfg.package}/bin/wlsunset ${concatStringsSep " " args}"; + }; + + Install = { WantedBy = [ cfg.systemdTarget ]; }; + }; + }; +} diff --git a/modules/services/xsuspender.nix b/modules/services/xsuspender.nix index 2eb40f5dd340..7d855f05d51f 100644 --- a/modules/services/xsuspender.nix +++ b/modules/services/xsuspender.nix @@ -6,6 +6,8 @@ let cfg = config.services.xsuspender; + iniFormat = pkgs.formats.ini { }; + xsuspenderOptions = types.submodule { options = { matchWmClassContains = mkOption { @@ -139,7 +141,7 @@ in { }; iniContent = mkOption { - type = types.attrsOf types.attrs; + type = iniFormat.type; internal = true; }; }; @@ -170,7 +172,8 @@ in { # To make the xsuspender tool available. home.packages = [ pkgs.xsuspender ]; - xdg.configFile."xsuspender.conf".text = generators.toINI { } cfg.iniContent; + xdg.configFile."xsuspender.conf".source = + iniFormat.generate "xsuspender.conf" cfg.iniContent; systemd.user.services.xsuspender = { Unit = { diff --git a/modules/systemd.nix b/modules/systemd.nix index dcb1a29570de..755aac25c26d 100644 --- a/modules/systemd.nix +++ b/modules/systemd.nix @@ -7,6 +7,7 @@ let cfg = config.systemd.user; enabled = cfg.services != {} + || cfg.slices != {} || cfg.sockets != {} || cfg.targets != {} || cfg.timers != {} @@ -14,6 +15,7 @@ let || cfg.sessionVariables != {}; toSystemdIni = generators.toINI { + listsAsDuplicateKeys = true; mkKeyValue = key: value: let value' = @@ -88,7 +90,7 @@ let ${type} = { … }; - } + }; }; ''; @@ -124,6 +126,13 @@ in example = unitExample "Service"; }; + slices = mkOption { + default = {}; + type = unitType "slices"; + description = unitDescription "slices"; + example = unitExample "Slices"; + }; + sockets = mkOption { default = {}; type = unitType "socket"; @@ -196,7 +205,8 @@ in let names = concatStringsSep ", " ( attrNames ( - cfg.services // cfg.sockets // cfg.targets // cfg.timers // cfg.paths // cfg.sessionVariables + cfg.services // cfg.slices // cfg.sockets // cfg.targets + // cfg.timers // cfg.paths // cfg.sessionVariables ) ); in @@ -212,6 +222,8 @@ in (listToAttrs ( (buildServices "service" cfg.services) ++ + (buildServices "slices" cfg.slices) + ++ (buildServices "socket" cfg.sockets) ++ (buildServices "target" cfg.targets) @@ -228,7 +240,7 @@ in # running this from the NixOS module then XDG_RUNTIME_DIR is not # set and systemd commands will fail. We'll therefore have to # set it ourselves in that case. - home.activation.reloadSystemD = hm.dag.entryAfter ["linkGeneration"] ( + home.activation.reloadSystemd = hm.dag.entryAfter ["linkGeneration"] ( let autoReloadCmd = '' ${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \ diff --git a/modules/targets/darwin.nix b/modules/targets/darwin.nix new file mode 100644 index 000000000000..118321e8f117 --- /dev/null +++ b/modules/targets/darwin.nix @@ -0,0 +1,16 @@ +{ config, lib, pkgs, ... }: + +{ + # Disabled for now due to conflicting behavior with nix-darwin. See + # https://github.com/nix-community/home-manager/issues/1341#issuecomment-687286866 + config = lib.mkIf (false && pkgs.stdenv.hostPlatform.isDarwin) { + # Install MacOS applications to the user environment. + home.file."Applications/Home Manager Apps".source = let + apps = pkgs.buildEnv { + name = "home-manager-applications"; + paths = config.home.packages; + pathsToLink = "/Applications"; + }; + in "${apps}/Applications"; + }; +} diff --git a/modules/targets/generic-linux.nix b/modules/targets/generic-linux.nix index bdb27926fbcf..cf6b2bd87a94 100644 --- a/modules/targets/generic-linux.nix +++ b/modules/targets/generic-linux.nix @@ -7,19 +7,31 @@ let profileDirectory = config.home.profileDirectory; in { - options.targets.genericLinux.enable = mkEnableOption "" // { - description = '' - Whether to enable settings that make Home Manager work better on - GNU/Linux distributions other than NixOS. - ''; + options.targets.genericLinux = { + enable = mkEnableOption "" // { + description = '' + Whether to enable settings that make Home Manager work better on + GNU/Linux distributions other than NixOS. + ''; + }; + + extraXdgDataDirs = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "/usr/share" "/usr/local/share" ]; + description = '' + List of directory names to add to XDG_DATA_DIRS. + ''; + }; }; config = mkIf config.targets.genericLinux.enable { home.sessionVariables = let profiles = [ "\${NIX_STATE_DIR:-/nix/var/nix}/profiles/default" profileDirectory ]; - dataDirs = - concatStringsSep ":" (map (profile: "${profile}/share") profiles); + dataDirs = concatStringsSep ":" + (map (profile: "${profile}/share") profiles + ++ config.targets.genericLinux.extraXdgDataDirs); in { XDG_DATA_DIRS = "${dataDirs}\${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS"; }; home.sessionVariablesExtra = '' @@ -27,7 +39,7 @@ in { ''; # We need to source both nix.sh and hm-session-vars.sh as noted in - # https://github.com/rycee/home-manager/pull/797#issuecomment-544783247 + # https://github.com/nix-community/home-manager/pull/797#issuecomment-544783247 programs.bash.initExtra = '' . "${pkgs.nix}/etc/profile.d/nix.sh" . "${profileDirectory}/etc/profile.d/hm-session-vars.sh" diff --git a/nix-darwin/default.nix b/nix-darwin/default.nix index ce6105d4b121..12d02174332d 100644 --- a/nix-darwin/default.nix +++ b/nix-darwin/default.nix @@ -10,11 +10,12 @@ let hmModule = types.submoduleWith { specialArgs = { lib = extendedLib; }; - modules = [( - {name, ...}: { + modules = [ + ({ name, ... }: { imports = import ../modules/modules.nix { inherit pkgs; lib = extendedLib; + useNixpkgsModule = !cfg.useGlobalPkgs; }; config = { @@ -24,8 +25,8 @@ let home.username = config.users.users.${name}.name; home.homeDirectory = config.users.users.${name}.home; }; - } - )]; + }) + ]; }; in @@ -38,6 +39,24 @@ in option. ''; + useGlobalPkgs = mkEnableOption '' + using the system configuration's pkgs + argument in Home Manager. This disables the Home Manager + options + ''; + + backupFileExtension = mkOption { + type = types.nullOr types.str; + default = null; + example = "backup"; + description = '' + On activation move existing files by appending the given + file extension rather than exiting with an error. + ''; + }; + + verbose = mkEnableOption "verbose output on activation"; + users = mkOption { type = types.attrsOf hmModule; default = {}; @@ -77,7 +96,12 @@ in system.activationScripts.postActivation.text = concatStringsSep "\n" (mapAttrsToList (username: usercfg: '' echo Activating home-manager configuration for ${username} - sudo -u ${username} -i ${usercfg.home.activationPackage}/activate + sudo -u ${username} -i ${pkgs.writeShellScript "activation-${username}" '' + ${lib.optionalString (cfg.backupFileExtension != null) + "export HOME_MANAGER_BACKUP_EXT=${lib.escapeShellArg cfg.backupFileExtension}"} + ${lib.optionalString cfg.verbose "export VERBOSE=1"} + exec ${usercfg.home.activationPackage}/activate + ''} '') cfg.users); }; } diff --git a/nixos/default.nix b/nixos/default.nix index 9aedf618c51b..e5ca61c02f68 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -9,7 +9,10 @@ let extendedLib = import ../modules/lib/stdlib-extended.nix pkgs.lib; hmModule = types.submoduleWith { - specialArgs = { lib = extendedLib; }; + specialArgs = { + lib = extendedLib; + nixosConfig = config; + }; modules = [ ({ name, ... }: { imports = import ../modules/modules.nix { diff --git a/tests/default.nix b/tests/default.nix index 53c221bcf694..1ce1b5a27ca3 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -7,8 +7,8 @@ let nmt = pkgs.fetchFromGitLab { owner = "rycee"; repo = "nmt"; - rev = "8e130d655ec396ce165763c95bbf4ac429810ca8"; - sha256 = "1jbljr06kg1ycdn24hj8xap16axq11rhb6hm4949fz48n57pwwps"; + rev = "26af5c54c88695ed73be93a9eae6b71f2d76d04a"; + sha256 = "1m10cwjh63qkz2rgnm0wk16pxh52lp8i9kjfv6cfhbzl5df4q95p"; }; modules = import ../modules/modules.nix { @@ -41,53 +41,66 @@ import nmt { ./modules/programs/alacritty ./modules/programs/alot ./modules/programs/aria2 + ./modules/programs/autojump ./modules/programs/bash ./modules/programs/browserpass ./modules/programs/dircolors ./modules/programs/direnv + ./modules/programs/feh ./modules/programs/fish + ./modules/programs/gh ./modules/programs/git ./modules/programs/gpg ./modules/programs/i3status ./modules/programs/kakoune ./modules/programs/lf ./modules/programs/lieer + ./modules/programs/man ./modules/programs/mbsync + ./modules/programs/ncmpcpp ./modules/programs/ne ./modules/programs/neomutt ./modules/programs/newsboat ./modules/programs/nushell + ./modules/programs/powerline-go ./modules/programs/qutebrowser ./modules/programs/readline - ./modules/programs/powerline-go ./modules/programs/ssh ./modules/programs/starship ./modules/programs/texlive ./modules/programs/tmux + ./modules/programs/vscode ./modules/programs/zplug ./modules/programs/zsh ./modules/xresources + ] ++ lib.optionals pkgs.stdenv.hostPlatform.isDarwin [ + ./modules/targets-darwin ] ++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [ - ./meta # Suffices to run on one platform. ./modules/misc/debug + ./modules/misc/numlock ./modules/misc/pam ./modules/misc/xdg ./modules/misc/xsession ./modules/programs/abook ./modules/programs/autorandr - ./modules/services/dropbox - ./modules/services/emacs - ./modules/services/dropbox ./modules/programs/firefox ./modules/programs/getmail - ./modules/services/lieer + ./modules/programs/i3status-rust + ./modules/programs/ncmpcpp-linux + ./modules/programs/neovim # Broken package dependency on Darwin. ./modules/programs/rofi + ./modules/programs/waybar + ./modules/services/dropbox + ./modules/services/emacs + ./modules/services/fluidsynth + ./modules/services/kanshi + ./modules/services/lieer ./modules/services/polybar ./modules/services/sxhkd - ./modules/services/fluidsynth ./modules/services/window-managers/i3 ./modules/services/window-managers/sway + ./modules/services/wlsunset ./modules/systemd - ./modules/targets + ./modules/targets-linux ]); } diff --git a/tests/lib/types/gvariant-merge.nix b/tests/lib/types/gvariant-merge.nix index fce50e0da3cf..867534c1f149 100644 --- a/tests/lib/types/gvariant-merge.nix +++ b/tests/lib/types/gvariant-merge.nix @@ -26,6 +26,7 @@ in { { string = "foo"; } { string = "foo"; } + { escapedString = "' \\"; } { tuple = mkTuple [ 1 [ "foo" ] ]; } @@ -46,6 +47,7 @@ in { bool = true emptyArray1 = @as [] emptyArray2 = @as [] + escapedString = '\' \\' float = 3.140000 int = 42 list = @as ['one','two'] diff --git a/tests/meta/default.nix b/tests/meta/default.nix deleted file mode 100644 index 0f02fcfb4532..000000000000 --- a/tests/meta/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ meta-formatting = ./formatting.nix; } diff --git a/tests/meta/formatting.nix b/tests/meta/formatting.nix deleted file mode 100644 index 2d5800c53cd3..000000000000 --- a/tests/meta/formatting.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - pinnedNixpkgs = builtins.fetchTarball { - url = - "https://github.com/NixOS/nixpkgs/archive/05f0934825c2a0750d4888c4735f9420c906b388.tar.gz"; - sha256 = "1g8c2w0661qn89ajp44znmwfmghbbiygvdzq0rzlvlpdiz28v6gy"; - }; - - pinnedPkgs = import pinnedNixpkgs { }; - -in { - config = { - nmt.script = '' - PATH="${with pinnedPkgs; lib.makeBinPath [ findutils nixfmt ]}:$PATH" - cd ${../..} - if ! ${pkgs.runtimeShell} format -c; then - fail "${'' - Expected source code to be formatted with nixfmt but it was not. - This error can be resolved by running the './format' in the project root directory.''}" - fi - ''; - }; -} diff --git a/tests/modules/accounts/email-test-accounts.nix b/tests/modules/accounts/email-test-accounts.nix index 9c9c90cf8fe2..9a4e0b8e72f0 100644 --- a/tests/modules/accounts/email-test-accounts.nix +++ b/tests/modules/accounts/email-test-accounts.nix @@ -21,6 +21,7 @@ passwordCommand = "password-command 2"; imap.host = "imap.example.org"; smtp.host = "smtp.example.org"; + smtp.tls.useStartTls = true; }; }; }; diff --git a/tests/modules/files/default.nix b/tests/modules/files/default.nix index 77743a760dc2..6f1ef24b8103 100644 --- a/tests/modules/files/default.nix +++ b/tests/modules/files/default.nix @@ -3,5 +3,6 @@ files-hidden-source = ./hidden-source.nix; files-out-of-store-symlink = ./out-of-store-symlink.nix; files-source-with-spaces = ./source-with-spaces.nix; + files-target-with-shellvar = ./target-with-shellvar.nix; files-text = ./text.nix; } diff --git a/tests/modules/files/target-with-shellvar.nix b/tests/modules/files/target-with-shellvar.nix new file mode 100644 index 000000000000..c54946eb9eba --- /dev/null +++ b/tests/modules/files/target-with-shellvar.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + home.file."$HOME/$FOO/bar baz".text = "blah"; + + nmt.script = '' + assertFileExists 'home-files/$HOME/$FOO/bar baz'; + assertFileContent 'home-files/$HOME/$FOO/bar baz' \ + ${pkgs.writeText "expected" "blah"} + ''; + }; +} diff --git a/tests/modules/home-environment/default.nix b/tests/modules/home-environment/default.nix index 2a1201a2f0a8..e76e248a1643 100644 --- a/tests/modules/home-environment/default.nix +++ b/tests/modules/home-environment/default.nix @@ -1,3 +1,4 @@ { home-session-variables = ./session-variables.nix; + home-session-path = ./session-path.nix; } diff --git a/tests/modules/home-environment/session-path.nix b/tests/modules/home-environment/session-path.nix new file mode 100644 index 000000000000..3b40059eac9a --- /dev/null +++ b/tests/modules/home-environment/session-path.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ({ ... }: { config.home.sessionPath = [ "foo" ]; }) + ({ ... }: { config.home.sessionPath = [ "bar" "baz" ]; }) + ]; + + nmt.script = '' + assertFileExists home-path/etc/profile.d/hm-session-vars.sh + assertFileContent \ + home-path/etc/profile.d/hm-session-vars.sh \ + ${ + pkgs.writeText "session-path-expected.txt" '' + # Only source this once. + if [ -n "$__HM_SESS_VARS_SOURCED" ]; then return; fi + export __HM_SESS_VARS_SOURCED=1 + + export XDG_CACHE_HOME="/home/hm-user/.cache" + export XDG_CONFIG_HOME="/home/hm-user/.config" + export XDG_DATA_HOME="/home/hm-user/.local/share" + export PATH="$PATH''${PATH:+:}bar:baz:foo" + '' + } + ''; + +} diff --git a/tests/modules/misc/numlock/default.nix b/tests/modules/misc/numlock/default.nix new file mode 100644 index 000000000000..47ca563fec04 --- /dev/null +++ b/tests/modules/misc/numlock/default.nix @@ -0,0 +1 @@ +{ numlock = ./numlock.nix; } diff --git a/tests/modules/misc/numlock/numlock.nix b/tests/modules/misc/numlock/numlock.nix new file mode 100644 index 000000000000..aa468c212c3e --- /dev/null +++ b/tests/modules/misc/numlock/numlock.nix @@ -0,0 +1,18 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + xsession.numlock.enable = true; + + nixpkgs.overlays = [ + (self: super: { numlockx = pkgs.writeScriptBin "dummy-numlockx" ""; }) + ]; + + nmt.script = '' + serviceFile=home-files/.config/systemd/user/numlockx.service + assertFileExists $serviceFile + ''; + }; +} diff --git a/tests/modules/misc/xdg/default.nix b/tests/modules/misc/xdg/default.nix index e5d015759d52..813a25202fac 100644 --- a/tests/modules/misc/xdg/default.nix +++ b/tests/modules/misc/xdg/default.nix @@ -1 +1,4 @@ -{ xdg-mime-apps-basics = ./mime-apps-basics.nix; } +{ + xdg-mime-apps-basics = ./mime-apps-basics.nix; + xdg-file-attr-names = ./file-attr-names.nix; +} diff --git a/tests/modules/misc/xdg/file-attr-names.nix b/tests/modules/misc/xdg/file-attr-names.nix new file mode 100644 index 000000000000..0aa8fcffe6f1 --- /dev/null +++ b/tests/modules/misc/xdg/file-attr-names.nix @@ -0,0 +1,26 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + xdg.configFile.test.text = "config"; + xdg.dataFile.test.text = "data"; + home.file.test.text = "home"; + + nmt.script = '' + assertFileExists home-files/.config/test + assertFileExists home-files/.local/share/test + assertFileExists home-files/test + assertFileContent \ + home-files/.config/test \ + ${builtins.toFile "test" "config"} + assertFileContent \ + home-files/.local/share/test \ + ${builtins.toFile "test" "data"} + assertFileContent \ + home-files/test \ + ${builtins.toFile "test" "home"} + ''; + }; +} diff --git a/tests/modules/programs/alacritty/default.nix b/tests/modules/programs/alacritty/default.nix index f63e033d8461..3ccd9a91f33a 100644 --- a/tests/modules/programs/alacritty/default.nix +++ b/tests/modules/programs/alacritty/default.nix @@ -1,4 +1,5 @@ { alacritty-example-settings = ./example-settings.nix; alacritty-empty-settings = ./empty-settings.nix; + alacritty-merging-settings = ./settings-merging.nix; } diff --git a/tests/modules/programs/alacritty/settings-merging-expected.yml b/tests/modules/programs/alacritty/settings-merging-expected.yml new file mode 100644 index 000000000000..49d92a614e88 --- /dev/null +++ b/tests/modules/programs/alacritty/settings-merging-expected.yml @@ -0,0 +1 @@ +{"font":{"bold":{"family":"SFMono"},"normal":{"family":"SFMono"}},"key_bindings":[{"chars":"\x0c","key":"K","mods":"Control"}],"window":{"dimensions":{"columns":200,"lines":3}}} \ No newline at end of file diff --git a/tests/modules/programs/alacritty/settings-merging.nix b/tests/modules/programs/alacritty/settings-merging.nix new file mode 100644 index 000000000000..1b8559d69de8 --- /dev/null +++ b/tests/modules/programs/alacritty/settings-merging.nix @@ -0,0 +1,39 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.alacritty = { + enable = true; + package = pkgs.writeScriptBin "dummy-alacritty" ""; + + settings = { + window.dimensions = { + lines = 3; + columns = 200; + }; + + key_bindings = [{ + key = "K"; + mods = "Control"; + chars = "\\x0c"; + }]; + + font = let + defaultFont = + lib.mkMerge [ (lib.mkIf true "SFMono") (lib.mkIf false "Iosevka") ]; + in { + normal.family = defaultFont; + bold.family = defaultFont; + }; + }; + }; + + nmt.script = '' + assertFileContent \ + home-files/.config/alacritty/alacritty.yml \ + ${./settings-merging-expected.yml} + ''; + }; +} diff --git a/tests/modules/programs/autojump/default-settings.nix b/tests/modules/programs/autojump/default-settings.nix new file mode 100644 index 000000000000..2f6670272722 --- /dev/null +++ b/tests/modules/programs/autojump/default-settings.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.autojump.enable = true; + + nmt.script = '' + assertFileExists home-path/bin/autojump + ''; + }; +} diff --git a/tests/modules/programs/autojump/default.nix b/tests/modules/programs/autojump/default.nix new file mode 100644 index 000000000000..eaaaba3b4b28 --- /dev/null +++ b/tests/modules/programs/autojump/default.nix @@ -0,0 +1 @@ +{ autojump = ./default-settings.nix; } diff --git a/tests/modules/programs/feh/default.nix b/tests/modules/programs/feh/default.nix new file mode 100644 index 000000000000..48bab8ab6e79 --- /dev/null +++ b/tests/modules/programs/feh/default.nix @@ -0,0 +1,4 @@ +{ + feh-empty-config = ./feh-empty-settings.nix; + feh-bindings = ./feh-bindings.nix; +} diff --git a/tests/modules/programs/feh/feh-bindings-expected-buttons b/tests/modules/programs/feh/feh-bindings-expected-buttons new file mode 100644 index 000000000000..f285ada64f26 --- /dev/null +++ b/tests/modules/programs/feh/feh-bindings-expected-buttons @@ -0,0 +1,4 @@ +zoom_in +next_img C-4 +prev_img 3 C-3 +zoom_out 4 diff --git a/tests/modules/programs/feh/feh-bindings-expected-keys b/tests/modules/programs/feh/feh-bindings-expected-keys new file mode 100644 index 000000000000..ad558e6aa7ce --- /dev/null +++ b/tests/modules/programs/feh/feh-bindings-expected-keys @@ -0,0 +1,3 @@ +zoom_in +prev_img h Left +zoom_out minus diff --git a/tests/modules/programs/feh/feh-bindings.nix b/tests/modules/programs/feh/feh-bindings.nix new file mode 100644 index 000000000000..f6b9e5b6e944 --- /dev/null +++ b/tests/modules/programs/feh/feh-bindings.nix @@ -0,0 +1,33 @@ +{ pkgs, ... }: + +{ + config = { + programs.feh.enable = true; + + programs.feh.buttons = { + zoom_in = null; + zoom_out = 4; + next_img = "C-4"; + prev_img = [ 3 "C-3" ]; + }; + + programs.feh.keybindings = { + zoom_in = null; + zoom_out = "minus"; + prev_img = [ "h" "Left" ]; + }; + + nixpkgs.overlays = + [ (self: super: { feh = pkgs.writeScriptBin "dummy-feh" ""; }) ]; + + nmt.script = '' + assertFileContent \ + home-files/.config/feh/buttons \ + ${./feh-bindings-expected-buttons} + + assertFileContent \ + home-files/.config/feh/keys \ + ${./feh-bindings-expected-keys} + ''; + }; +} diff --git a/tests/modules/programs/feh/feh-empty-settings.nix b/tests/modules/programs/feh/feh-empty-settings.nix new file mode 100644 index 000000000000..ad0d15153d2a --- /dev/null +++ b/tests/modules/programs/feh/feh-empty-settings.nix @@ -0,0 +1,15 @@ +{ pkgs, ... }: + +{ + config = { + programs.feh.enable = true; + + nixpkgs.overlays = + [ (self: super: { feh = pkgs.writeScriptBin "dummy-feh" ""; }) ]; + + nmt.script = '' + assertPathNotExists home-files/.config/feh/buttons + assertPathNotExists home-files/.config/feh/keys + ''; + }; +} diff --git a/tests/modules/programs/gh/config-file.nix b/tests/modules/programs/gh/config-file.nix new file mode 100644 index 000000000000..0c0186942052 --- /dev/null +++ b/tests/modules/programs/gh/config-file.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +{ + config = { + programs.gh = { + enable = true; + aliases = { co = "pr checkout"; }; + editor = "vim"; + }; + + nixpkgs.overlays = [ + (self: super: { + gitAndTools = super.gitAndTools // { + gh = pkgs.writeScriptBin "dummy-gh" ""; + }; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/gh/config.yml + assertFileContent home-files/.config/gh/config.yml ${ + builtins.toFile "config-file.yml" '' + {"aliases":{"co":"pr checkout"},"editor":"vim","gitProtocol":"https"}'' + } + ''; + }; +} diff --git a/tests/modules/programs/gh/default.nix b/tests/modules/programs/gh/default.nix new file mode 100644 index 000000000000..680e8b2756a6 --- /dev/null +++ b/tests/modules/programs/gh/default.nix @@ -0,0 +1 @@ +{ gh-config-file = ./config-file.nix; } diff --git a/tests/modules/programs/git/git-expected.conf b/tests/modules/programs/git/git-expected.conf index c3e534af0586..fe258a0d3be1 100644 --- a/tests/modules/programs/git/git-expected.conf +++ b/tests/modules/programs/git/git-expected.conf @@ -7,7 +7,7 @@ gpgSign = true [core] - pager = "@deltaCommand@" + pager = "@delta@/bin/delta" [delta] features = "decorations" @@ -41,7 +41,7 @@ program = "path-to-gpg" [interactive] - diffFilter = "@deltaCommand@ --color-only" + diffFilter = "@delta@/bin/delta --color-only" [user] email = "user@example.org" diff --git a/tests/modules/programs/git/git-with-email-expected.conf b/tests/modules/programs/git/git-with-email-expected.conf index 44035a23c47e..f48b7c33334d 100644 --- a/tests/modules/programs/git/git-with-email-expected.conf +++ b/tests/modules/programs/git/git-with-email-expected.conf @@ -6,7 +6,7 @@ [sendemail "hm@example.com"] from = "hm@example.com" - smtpEncryption = "tls" + smtpEncryption = "ssl" smtpServer = "smtp.example.com" smtpUser = "home.manager" diff --git a/tests/modules/programs/git/git-with-email.nix b/tests/modules/programs/git/git-with-email.nix index ca577eef4d33..49089a97a0c1 100644 --- a/tests/modules/programs/git/git-with-email.nix +++ b/tests/modules/programs/git/git-with-email.nix @@ -13,10 +13,12 @@ with lib; userName = "H. M. Test"; }; + home.stateVersion = "20.09"; + nmt.script = '' function assertGitConfig() { local value - value=$(${pkgs.git}/bin/git config \ + value=$(${pkgs.gitMinimal}/bin/git config \ --file $TESTED/home-files/.config/git/config \ --get $1) if [[ $value != $2 ]]; then diff --git a/tests/modules/programs/git/git.nix b/tests/modules/programs/git/git.nix index feefff54b613..981d1934821b 100644 --- a/tests/modules/programs/git/git.nix +++ b/tests/modules/programs/git/git.nix @@ -14,9 +14,6 @@ let substituteExpected = path: pkgs.substituteAll { src = path; - - deltaCommand = "${pkgs.gitAndTools.delta}/bin/delta"; - git_include_path = pkgs.writeText "contents" (builtins.readFile ./git-expected-include.conf); }; @@ -82,6 +79,17 @@ in { } ]; + nixpkgs.overlays = [ + (self: super: { + git-lfs = pkgs.writeScriptBin "dummy-git-lfs" ""; + gitAndTools = super.gitAndTools // { + delta = pkgs.writeScriptBin "dummy-delta" "" // { + outPath = "@delta@"; + }; + }; + }) + ]; + nmt.script = '' assertFileExists home-files/.config/git/config assertFileContent home-files/.config/git/config ${ diff --git a/tests/modules/programs/i3status-rust/default.nix b/tests/modules/programs/i3status-rust/default.nix new file mode 100644 index 000000000000..50d8c7b853c1 --- /dev/null +++ b/tests/modules/programs/i3status-rust/default.nix @@ -0,0 +1,6 @@ +{ + i3status-rust-with-default = ./with-default.nix; + i3status-rust-with-custom = ./with-custom.nix; + i3status-rust-with-extra-settings = ./with-extra-settings.nix; + i3status-rust-with-multiple-bars = ./with-multiple-bars.nix; +} diff --git a/tests/modules/programs/i3status-rust/with-custom.nix b/tests/modules/programs/i3status-rust/with-custom.nix new file mode 100644 index 000000000000..4ced6add57ba --- /dev/null +++ b/tests/modules/programs/i3status-rust/with-custom.nix @@ -0,0 +1,186 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.i3status-rust = { + enable = true; + bars = { + custom = { + blocks = [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "memory"; + display_type = "memory"; + format_mem = "{Mug}GB ({Mup}%)"; + format_swap = "{SUp}%"; + } + { + block = "cpu"; + interval = 1; + format = "{barchart}"; + } + { + block = "load"; + interval = 1; + format = "{1m} {5m}"; + } + { + block = "temperature"; + collapsed = true; + interval = 10; + format = "{min}° min, {max}° max, {average}° avg"; + chip = "*-isa-*"; + } + { + block = "networkmanager"; + ap_format = "{ssid} @ {strength}%"; + on_click = "kcmshell5 kcm_networkmanagement"; + } + { + block = "net"; + device = "enp9s0u2u1u2c2"; + speed_up = true; + interval = 5; + } + { + block = "speedtest"; + bytes = true; + } + { + block = "xrandr"; + interval = + 6000; # Because running the commands causes screen lag, see https://github.com/greshake/i3status-rust/issues/668 + } + { + block = "sound"; + format = "{output_name} {volume}%"; + on_click = "pavucontrol --tab=3"; + mappings = { + "alsa_output.pci-0000_00_1f.3.analog-stereo" = ""; + "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = ""; + }; + } + { + block = "music"; + player = "spotify"; + buttons = [ "play" "prev" "next" ]; + on_collapsed_click = "i3-msg '[class=Spotify] focus'"; + } + { + block = "time"; + interval = 60; + format = "%a %d.%m %R"; + } + { block = "battery"; } + ]; + + icons = "awesome5"; + + theme = "gruvbox-dark"; + }; + }; + }; + + nixpkgs.overlays = [ + (self: super: { + i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" ""; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/i3status-rust/config-custom.toml + assertFileContent home-files/.config/i3status-rust/config-custom.toml \ + ${ + pkgs.writeText "i3status-rust-expected-config" '' + icons = "awesome5" + theme = "gruvbox-dark" + [[block]] + alert = 10 + alias = "/" + block = "disk_space" + info_type = "available" + interval = 60 + path = "/" + unit = "GB" + warning = 20 + + [[block]] + block = "memory" + display_type = "memory" + format_mem = "{Mug}GB ({Mup}%)" + format_swap = "{SUp}%" + + [[block]] + block = "cpu" + format = "{barchart}" + interval = 1 + + [[block]] + block = "load" + format = "{1m} {5m}" + interval = 1 + + [[block]] + block = "temperature" + chip = "*-isa-*" + collapsed = true + format = "{min}° min, {max}° max, {average}° avg" + interval = 10 + + [[block]] + ap_format = "{ssid} @ {strength}%" + block = "networkmanager" + on_click = "kcmshell5 kcm_networkmanagement" + + [[block]] + block = "net" + device = "enp9s0u2u1u2c2" + interval = 5 + speed_up = true + + [[block]] + block = "speedtest" + bytes = true + + [[block]] + block = "xrandr" + interval = 6000 + + [[block]] + block = "sound" + format = "{output_name} {volume}%" + on_click = "pavucontrol --tab=3" + + [block.mappings] + "alsa_output.pci-0000_00_1f.3.analog-stereo" = "" + "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = "" + + [[block]] + block = "music" + buttons = ["play", "prev", "next"] + on_collapsed_click = "i3-msg '[class=Spotify] focus'" + player = "spotify" + + [[block]] + block = "time" + format = "%a %d.%m %R" + interval = 60 + + [[block]] + block = "battery" + '' + } + ''; + }; +} diff --git a/tests/modules/programs/i3status-rust/with-default.nix b/tests/modules/programs/i3status-rust/with-default.nix new file mode 100644 index 000000000000..b62c248c8a15 --- /dev/null +++ b/tests/modules/programs/i3status-rust/with-default.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.i3status-rust = { enable = true; }; + + nixpkgs.overlays = [ + (self: super: { + i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" ""; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/i3status-rust/config-default.toml + assertFileContent home-files/.config/i3status-rust/config-default.toml \ + ${ + pkgs.writeText "i3status-rust-expected-config" '' + icons = "none" + theme = "plain" + [[block]] + alert = 10 + alias = "/" + block = "disk_space" + info_type = "available" + interval = 60 + path = "/" + unit = "GB" + warning = 20 + + [[block]] + block = "memory" + display_type = "memory" + format_mem = "{Mup}%" + format_swap = "{SUp}%" + + [[block]] + block = "cpu" + interval = 1 + + [[block]] + block = "load" + format = "{1m}" + interval = 1 + + [[block]] + block = "sound" + + [[block]] + block = "time" + format = "%a %d/%m %R" + interval = 60 + '' + } + ''; + }; +} diff --git a/tests/modules/programs/i3status-rust/with-extra-settings.nix b/tests/modules/programs/i3status-rust/with-extra-settings.nix new file mode 100644 index 000000000000..16f3428a0fba --- /dev/null +++ b/tests/modules/programs/i3status-rust/with-extra-settings.nix @@ -0,0 +1,202 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.i3status-rust = { + enable = true; + bars = { + extra-settings = { + blocks = [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "memory"; + display_type = "memory"; + format_mem = "{Mug}GB ({Mup}%)"; + format_swap = "{SUp}%"; + } + { + block = "cpu"; + interval = 1; + format = "{barchart}"; + } + { + block = "load"; + interval = 1; + format = "{1m} {5m}"; + } + { + block = "temperature"; + collapsed = true; + interval = 10; + format = "{min}° min, {max}° max, {average}° avg"; + chip = "*-isa-*"; + } + { + block = "networkmanager"; + ap_format = "{ssid} @ {strength}%"; + on_click = "kcmshell5 kcm_networkmanagement"; + } + { + block = "net"; + device = "enp9s0u2u1u2c2"; + speed_up = true; + interval = 5; + } + { + block = "speedtest"; + bytes = true; + } + { + block = "xrandr"; + interval = + 6000; # Because running the commands causes screen lag, see https://github.com/greshake/i3status-rust/issues/668 + } + { + block = "sound"; + format = "{output_name} {volume}%"; + on_click = "pavucontrol --tab=3"; + mappings = { + "alsa_output.pci-0000_00_1f.3.analog-stereo" = ""; + "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = ""; + }; + } + { + block = "music"; + player = "spotify"; + buttons = [ "play" "prev" "next" ]; + on_collapsed_click = "i3-msg '[class=Spotify] focus'"; + } + { + block = "time"; + interval = 60; + format = "%a %d.%m %R"; + } + { block = "battery"; } + ]; + + icons = "awesome5"; + + settings = { + theme = { + name = "solarized-dark"; + overrides = { + idle_bg = "#123456"; + idle_fg = "#abcdef"; + }; + }; + }; + + theme = "gruvbox-dark"; + }; + }; + }; + + nixpkgs.overlays = [ + (self: super: { + i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" ""; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/i3status-rust/config-extra-settings.toml + assertFileContent home-files/.config/i3status-rust/config-extra-settings.toml \ + ${ + pkgs.writeText "i3status-rust-expected-config" '' + icons = "awesome5" + [[block]] + alert = 10 + alias = "/" + block = "disk_space" + info_type = "available" + interval = 60 + path = "/" + unit = "GB" + warning = 20 + + [[block]] + block = "memory" + display_type = "memory" + format_mem = "{Mug}GB ({Mup}%)" + format_swap = "{SUp}%" + + [[block]] + block = "cpu" + format = "{barchart}" + interval = 1 + + [[block]] + block = "load" + format = "{1m} {5m}" + interval = 1 + + [[block]] + block = "temperature" + chip = "*-isa-*" + collapsed = true + format = "{min}° min, {max}° max, {average}° avg" + interval = 10 + + [[block]] + ap_format = "{ssid} @ {strength}%" + block = "networkmanager" + on_click = "kcmshell5 kcm_networkmanagement" + + [[block]] + block = "net" + device = "enp9s0u2u1u2c2" + interval = 5 + speed_up = true + + [[block]] + block = "speedtest" + bytes = true + + [[block]] + block = "xrandr" + interval = 6000 + + [[block]] + block = "sound" + format = "{output_name} {volume}%" + on_click = "pavucontrol --tab=3" + + [block.mappings] + "alsa_output.pci-0000_00_1f.3.analog-stereo" = "" + "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = "" + + [[block]] + block = "music" + buttons = ["play", "prev", "next"] + on_collapsed_click = "i3-msg '[class=Spotify] focus'" + player = "spotify" + + [[block]] + block = "time" + format = "%a %d.%m %R" + interval = 60 + + [[block]] + block = "battery" + + [theme] + name = "solarized-dark" + + [theme.overrides] + idle_bg = "#123456" + idle_fg = "#abcdef" + '' + } + ''; + }; +} diff --git a/tests/modules/programs/i3status-rust/with-multiple-bars.nix b/tests/modules/programs/i3status-rust/with-multiple-bars.nix new file mode 100644 index 000000000000..9cbbe2854a2d --- /dev/null +++ b/tests/modules/programs/i3status-rust/with-multiple-bars.nix @@ -0,0 +1,106 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.i3status-rust = { + enable = true; + + bars = { + + top = { + blocks = [ + { + block = "disk_space"; + path = "/"; + alias = "/"; + info_type = "available"; + unit = "GB"; + interval = 60; + warning = 20.0; + alert = 10.0; + } + { + block = "memory"; + display_type = "memory"; + format_mem = "{Mug}GB ({Mup}%)"; + format_swap = "{SUp}%"; + } + ]; + }; + + bottom = { + blocks = [ + { + block = "cpu"; + interval = 1; + format = "{barchart}"; + } + { + block = "load"; + interval = 1; + format = "{1m} {5m}"; + } + ]; + icons = "awesome5"; + + theme = "gruvbox-dark"; + }; + + }; + + }; + + nixpkgs.overlays = [ + (self: super: { + i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" ""; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/i3status-rust/config-top.toml + assertFileContent home-files/.config/i3status-rust/config-top.toml \ + ${ + pkgs.writeText "i3status-rust-expected-config" '' + icons = "none" + theme = "plain" + [[block]] + alert = 10 + alias = "/" + block = "disk_space" + info_type = "available" + interval = 60 + path = "/" + unit = "GB" + warning = 20 + + [[block]] + block = "memory" + display_type = "memory" + format_mem = "{Mug}GB ({Mup}%)" + format_swap = "{SUp}%" + '' + } + + assertFileExists home-files/.config/i3status-rust/config-bottom.toml + assertFileContent \ + home-files/.config/i3status-rust/config-bottom.toml \ + ${ + pkgs.writeText "i3status-rust-expected-config" '' + icons = "awesome5" + theme = "gruvbox-dark" + [[block]] + block = "cpu" + format = "{barchart}" + interval = 1 + + [[block]] + block = "load" + format = "{1m} {5m}" + interval = 1 + '' + } + ''; + }; +} diff --git a/tests/modules/programs/kakoune/default.nix b/tests/modules/programs/kakoune/default.nix index 9d88abf49490..1e6e077df1f8 100644 --- a/tests/modules/programs/kakoune/default.nix +++ b/tests/modules/programs/kakoune/default.nix @@ -1 +1,7 @@ -{ kakoune-whitespace-highlighter = ./whitespace-highlighter.nix; } +{ + kakoune-no-plugins = ./no-plugins.nix; + kakoune-use-plugins = ./use-plugins.nix; + kakoune-whitespace-highlighter = ./whitespace-highlighter.nix; + kakoune-whitespace-highlighter-corner-cases = + ./whitespace-highlighter-corner-cases.nix; +} diff --git a/tests/modules/programs/kakoune/no-plugins.nix b/tests/modules/programs/kakoune/no-plugins.nix new file mode 100644 index 000000000000..67a7f30f5234 --- /dev/null +++ b/tests/modules/programs/kakoune/no-plugins.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.kakoune = { enable = true; }; + + nmt.script = '' + assertPathNotExists home-path/share/kak/autoload/plugins + ''; + }; +} diff --git a/tests/modules/programs/kakoune/use-plugins.nix b/tests/modules/programs/kakoune/use-plugins.nix new file mode 100644 index 000000000000..9d44483276fc --- /dev/null +++ b/tests/modules/programs/kakoune/use-plugins.nix @@ -0,0 +1,16 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.kakoune = { + enable = true; + plugins = [ pkgs.kakounePlugins.kak-prelude ]; + }; + + nmt.script = '' + assertDirectoryNotEmpty home-path/share/kak/autoload/plugins + ''; + }; +} diff --git a/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix b/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix new file mode 100644 index 000000000000..142aaac8fe37 --- /dev/null +++ b/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.kakoune = { + enable = true; + config.showWhitespace = { + enable = true; + lineFeed = ''"''; + space = " "; + nonBreakingSpace = "' '"; # backwards compat + tab = "'"; + # tabStop = + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/kak/kakrc + assertFileContains home-files/.config/kak/kakrc \ + "add-highlighter global/ show-whitespaces -tab \"'\" -spc ' ' -nbsp ' ' -lf '\"'" + ''; + }; +} diff --git a/tests/modules/programs/kakoune/whitespace-highlighter.nix b/tests/modules/programs/kakoune/whitespace-highlighter.nix index 3ce02e8f5afc..514c26a118b2 100644 --- a/tests/modules/programs/kakoune/whitespace-highlighter.nix +++ b/tests/modules/programs/kakoune/whitespace-highlighter.nix @@ -16,17 +16,10 @@ with lib; }; }; - nmt.script = let - lineStart = - "^add-highlighter\\s\\+global\\/\\?\\s\\+show-whitespaces\\s\\+" - + "\\(-\\w\\+\\s\\+.\\s\\+\\)*"; - in '' + nmt.script = '' assertFileExists home-files/.config/kak/kakrc - assertFileRegex home-files/.config/kak/kakrc '${lineStart}-lf\s\+1\b' - assertFileRegex home-files/.config/kak/kakrc '${lineStart}-spc\s\+2\b' - assertFileRegex home-files/.config/kak/kakrc '${lineStart}-nbsp\s\+3\b' - assertFileRegex home-files/.config/kak/kakrc '${lineStart}-tab\s\+4\b' - assertFileRegex home-files/.config/kak/kakrc '${lineStart}-tabpad\s\+5\b' + assertFileContains home-files/.config/kak/kakrc \ + "add-highlighter global/ show-whitespaces -tab '4' -tabpad '5' -spc '2' -nbsp '3' -lf '1'" ''; }; } diff --git a/tests/modules/programs/man/apropos.nix b/tests/modules/programs/man/apropos.nix new file mode 100644 index 000000000000..a8ec35ead920 --- /dev/null +++ b/tests/modules/programs/man/apropos.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.man = { + enable = true; + generateCaches = true; + }; + + nmt.script = '' + assertFileExists home-files/.manpath + + CACHE_DIR=$(cat $TESTED/home-files/.manpath | cut --delimiter=' ' --fields=3) + + if [[ ! -f "$CACHE_DIR/index.bt" ]]; then + fail "Expected man cache files to exist (in $CACHE_DIR) but they were not found." + fi + ''; + }; +} diff --git a/tests/modules/programs/man/default.nix b/tests/modules/programs/man/default.nix new file mode 100644 index 000000000000..2e9d340f98d4 --- /dev/null +++ b/tests/modules/programs/man/default.nix @@ -0,0 +1,4 @@ +{ + man-apropos = ./apropos.nix; + man-no-manpath = ./no-manpath.nix; +} diff --git a/tests/modules/programs/man/no-manpath.nix b/tests/modules/programs/man/no-manpath.nix new file mode 100644 index 000000000000..1b8cfb40c6eb --- /dev/null +++ b/tests/modules/programs/man/no-manpath.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.man = { enable = true; }; + + nmt.script = '' + assertPathNotExists home-files/.manpath + ''; + }; +} diff --git a/tests/modules/programs/mbsync/mbsync-expected.conf b/tests/modules/programs/mbsync/mbsync-expected.conf index f1ca79fe7380..6722960476b5 100644 --- a/tests/modules/programs/mbsync/mbsync-expected.conf +++ b/tests/modules/programs/mbsync/mbsync-expected.conf @@ -15,14 +15,41 @@ Inbox /home/hm-user/Mail/hm-account/Inbox Path /home/hm-user/Mail/hm-account/ SubFolders Verbatim -Channel hm-account -Create None -Expunge None +Channel emptyChannels-empty1 Master :hm-account-remote: -Patterns * -Remove None Slave :hm-account-local: -SyncState * + +Channel emptyChannels-empty2 +Master :hm-account-remote: +Slave :hm-account-local: + +Channel hm-account-earlierPatternMatch +Master :hm-account-remote:Label +Slave :hm-account-local:SomethingUnderLabel +Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?" + +Channel hm-account-inbox +Master :hm-account-remote:Inbox +Slave :hm-account-local:Inbox + +Channel hm-account-patternMatch +Master :hm-account-remote:Label +Slave :hm-account-local:SomethingUnderLabel +Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?" + +Channel hm-account-strangeHostBoxName +Master ":hm-account-remote:[Weird]/Label Mess" +Slave :hm-account-local:[AnotherWeird]/Label + +Group emptyChannels +Channel emptyChannels-empty1 +Channel emptyChannels-empty2 + +Group hm-account +Channel hm-account-earlierPatternMatch +Channel hm-account-inbox +Channel hm-account-patternMatch +Channel hm-account-strangeHostBoxName IMAPAccount hm@example.com @@ -40,16 +67,14 @@ Inbox /home/hm-user/Mail/hm@example.com/Inbox Path /home/hm-user/Mail/hm@example.com/ SubFolders Verbatim -Channel hm@example.com -Create None -Expunge None -Master :hm@example.com-remote: -Patterns * -Remove None -Slave :hm@example.com-local: -SyncState * +Channel inboxes-inbox1 +Master :hm@example.com-remote:Inbox1 +Slave :hm@example.com-local:Inboxes +Channel inboxes-inbox2 +Master :hm@example.com-remote:Inbox2 +Slave :hm@example.com-local:Inboxes Group inboxes -Channel hm-account:Inbox -Channel hm@example.com:Inbox1,Inbox2 +Channel inboxes-inbox1 +Channel inboxes-inbox2 diff --git a/tests/modules/programs/mbsync/mbsync.nix b/tests/modules/programs/mbsync/mbsync.nix index fa8e28cb4a6e..a6e555cd4c08 100644 --- a/tests/modules/programs/mbsync/mbsync.nix +++ b/tests/modules/programs/mbsync/mbsync.nix @@ -8,6 +8,10 @@ with lib; config = { programs.mbsync = { enable = true; + # programs.mbsync.groups and + # accounts.email.accounts..mbsync.groups should NOT be used at the + # same time. + # If they are, then the new version will take precendence. groups.inboxes = { "hm@example.com" = [ "Inbox1" "Inbox2" ]; hm-account = [ "Inbox" ]; @@ -15,9 +19,60 @@ with lib; }; accounts.email.accounts = { - "hm@example.com".mbsync = { enable = true; }; + "hm@example.com".mbsync = { + enable = true; + groups.inboxes = { + channels = { + inbox1 = { + masterPattern = "Inbox1"; + slavePattern = "Inboxes"; + }; + inbox2 = { + masterPattern = "Inbox2"; + slavePattern = "Inboxes"; + }; + }; + }; + }; - hm-account.mbsync = { enable = true; }; + hm-account.mbsync = { + enable = true; + groups.hm-account = { + channels.earlierPatternMatch = { + masterPattern = "Label"; + slavePattern = "SomethingUnderLabel"; + patterns = [ + "ThingUnderLabel" + "!NotThisMaildirThough" + ''"[Weird] Label?"'' + ]; + }; + channels.inbox = { + masterPattern = "Inbox"; + slavePattern = "Inbox"; + }; + channels.strangeHostBoxName = { + masterPattern = "[Weird]/Label Mess"; + slavePattern = "[AnotherWeird]/Label"; + }; + channels.patternMatch = { + masterPattern = "Label"; + slavePattern = "SomethingUnderLabel"; + patterns = [ + "ThingUnderLabel" + "!NotThisMaildirThough" + ''"[Weird] Label?"'' + ]; + }; + }; + # No group should be printed. + groups.emptyGroup = { }; + # Group should be printed, but left with default channels. + groups.emptyChannels = { + channels.empty1 = { }; + channels.empty2 = { }; + }; + }; }; nmt.script = '' diff --git a/tests/modules/programs/ncmpcpp-linux/default.nix b/tests/modules/programs/ncmpcpp-linux/default.nix new file mode 100644 index 000000000000..b1185c852491 --- /dev/null +++ b/tests/modules/programs/ncmpcpp-linux/default.nix @@ -0,0 +1 @@ +{ ncmpcpp-use-mpd-config = ./ncmpcpp-use-mpd-config.nix; } diff --git a/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config b/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config new file mode 100644 index 000000000000..8aa57d08f16d --- /dev/null +++ b/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config @@ -0,0 +1 @@ +mpd_music_dir=/home/user/music diff --git a/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix b/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix new file mode 100644 index 000000000000..5262f0314724 --- /dev/null +++ b/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix @@ -0,0 +1,25 @@ +{ pkgs, ... }: + +{ + config = { + programs.ncmpcpp.enable = true; + + services.mpd.enable = true; + services.mpd.musicDirectory = "/home/user/music"; + + nixpkgs.overlays = [ + (self: super: { + ncmpcpp = pkgs.writeScriptBin "dummy-ncmpcpp" ""; + mpd = pkgs.writeScriptBin "dummy-mpd" ""; + }) + ]; + + nmt.script = '' + assertFileContent \ + home-files/.config/ncmpcpp/config \ + ${./ncmpcpp-use-mpd-config-expected-config} + + assertPathNotExists home-files/.config/ncmpcpp/bindings + ''; + }; +} diff --git a/tests/modules/programs/ncmpcpp/default.nix b/tests/modules/programs/ncmpcpp/default.nix new file mode 100644 index 000000000000..c150b0d82486 --- /dev/null +++ b/tests/modules/programs/ncmpcpp/default.nix @@ -0,0 +1,4 @@ +{ + ncmpcpp-empty-settings = ./ncmpcpp-empty-settings.nix; + ncmpcpp-example-settings = ./ncmpcpp-example-settings.nix; +} diff --git a/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix b/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix new file mode 100644 index 000000000000..e5134002d3e8 --- /dev/null +++ b/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix @@ -0,0 +1,16 @@ +{ pkgs, ... }: + +{ + config = { + programs.ncmpcpp.enable = true; + + nixpkgs.overlays = + [ (self: super: { ncmpcpp = pkgs.writeScriptBin "dummy-ncmpcpp" ""; }) ]; + + nmt.script = '' + assertPathNotExists home-files/.config/ncmpcpp/config + + assertPathNotExists home-files/.config/ncmpcpp/bindings + ''; + }; +} diff --git a/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings b/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings new file mode 100644 index 000000000000..a73bd129ffd6 --- /dev/null +++ b/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings @@ -0,0 +1,16 @@ +def_key "j" + scroll_down +def_key "k" + scroll_up +def_key "J" + select_item + scroll_down +def_key "K" + select_item + scroll_up +def_key "x" + delete_playlist_items +def_key "x" + delete_browser_items +def_key "x" + delete_stored_playlist diff --git a/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config b/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config new file mode 100644 index 000000000000..6aedb6110e4e --- /dev/null +++ b/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config @@ -0,0 +1,4 @@ +display_volume_level=no +mpd_music_dir=/home/user/music +playlist_disable_highlight_delay=0 +user_interface=alternative diff --git a/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix b/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix new file mode 100644 index 000000000000..02a1f09c9027 --- /dev/null +++ b/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix @@ -0,0 +1,60 @@ +{ pkgs, ... }: + +{ + config = { + programs.ncmpcpp = { + enable = true; + mpdMusicDir = "/home/user/music"; + + settings = { + user_interface = "alternative"; + display_volume_level = false; + playlist_disable_highlight_delay = 0; + }; + + bindings = [ + { + key = "j"; + command = "scroll_down"; + } + { + key = "k"; + command = "scroll_up"; + } + { + key = "J"; + command = [ "select_item" "scroll_down" ]; + } + { + key = "K"; + command = [ "select_item" "scroll_up" ]; + } + { + key = "x"; + command = "delete_playlist_items"; + } + { + key = "x"; + command = "delete_browser_items"; + } + { + key = "x"; + command = "delete_stored_playlist"; + } + ]; + }; + + nixpkgs.overlays = + [ (self: super: { ncmpcpp = pkgs.writeScriptBin "dummy-ncmpcpp" ""; }) ]; + + nmt.script = '' + assertFileContent \ + home-files/.config/ncmpcpp/config \ + ${./ncmpcpp-example-settings-expected-config} + + assertFileContent \ + home-files/.config/ncmpcpp/bindings \ + ${./ncmpcpp-example-settings-expected-bindings} + ''; + }; +} diff --git a/tests/modules/programs/neomutt/hm-example.com-expected b/tests/modules/programs/neomutt/hm-example.com-expected index 430509c36bd4..fceae5dc1f96 100644 --- a/tests/modules/programs/neomutt/hm-example.com-expected +++ b/tests/modules/programs/neomutt/hm-example.com-expected @@ -25,8 +25,6 @@ set realname='H. M. Test' set record='+Sent' set spoolfile='+Inbox' set trash='+Trash' -color status cyan default - # Extra configuration diff --git a/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf b/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf index 1850620f3133..925c70636a78 100644 --- a/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf +++ b/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf @@ -24,8 +24,6 @@ set realname='H. M. Test' set record='+Sent' set spoolfile='+Inbox' set trash='+Trash' -color status cyan default - # Extra configuration diff --git a/tests/modules/programs/neovim/default.nix b/tests/modules/programs/neovim/default.nix new file mode 100644 index 000000000000..7d6e53aae36d --- /dev/null +++ b/tests/modules/programs/neovim/default.nix @@ -0,0 +1 @@ +{ neovim-plugin-config = ./plugin-config.nix; } diff --git a/tests/modules/programs/neovim/plugin-config.nix b/tests/modules/programs/neovim/plugin-config.nix new file mode 100644 index 000000000000..60edaee82c22 --- /dev/null +++ b/tests/modules/programs/neovim/plugin-config.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.neovim = { + enable = true; + extraConfig = '' + " This should be present in vimrc + ''; + plugins = with pkgs.vimPlugins; [ + vim-nix + { + plugin = vim-commentary; + config = '' + " This should be present too + autocmd FileType c setlocal commentstring=//\ %s + autocmd FileType c setlocal comments=:// + ''; + } + ]; + }; + + nmt.script = '' + vimrc=$(grep -Po "(?<=-u )[^ ]*" < "${ + builtins.toJSON config.programs.neovim.finalPackage + }/bin/nvim") + # We need to remove the unkown store paths in the config + TESTED="" assertFileContent \ + <( ${pkgs.perl}/bin/perl -pe "s|\Q$NIX_STORE\E/[a-z0-9]{32}-|$NIX_STORE/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-|g" < "$vimrc" + ) \ + "${./plugin-config.vim}" + ''; + }; +} + diff --git a/tests/modules/programs/neovim/plugin-config.vim b/tests/modules/programs/neovim/plugin-config.vim new file mode 100644 index 000000000000..a8d833f4e009 --- /dev/null +++ b/tests/modules/programs/neovim/plugin-config.vim @@ -0,0 +1,22 @@ +" configuration generated by NIX +set nocompatible + + + + + + +set packpath^=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-vim-pack-dir +set runtimepath^=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-vim-pack-dir + +filetype indent plugin on | syn on + + +" This should be present in vimrc +" vim-commentary {{{ +" This should be present too +autocmd FileType c setlocal commentstring=//\ %s +autocmd FileType c setlocal comments=:// + +" }}} + diff --git a/tests/modules/programs/powerline-go/standard.nix b/tests/modules/programs/powerline-go/bash.nix similarity index 100% rename from tests/modules/programs/powerline-go/standard.nix rename to tests/modules/programs/powerline-go/bash.nix diff --git a/tests/modules/programs/powerline-go/default.nix b/tests/modules/programs/powerline-go/default.nix index 50febefbccdc..e89d7c6cde05 100644 --- a/tests/modules/programs/powerline-go/default.nix +++ b/tests/modules/programs/powerline-go/default.nix @@ -1 +1,4 @@ -{ powerline-go-standard = ./standard.nix; } +{ + powerline-go-bash = ./bash.nix; + powerline-go-zsh = ./zsh.nix; +} diff --git a/tests/modules/programs/powerline-go/zsh.nix b/tests/modules/programs/powerline-go/zsh.nix new file mode 100644 index 000000000000..702149528e49 --- /dev/null +++ b/tests/modules/programs/powerline-go/zsh.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs = { + zsh.enable = true; + + powerline-go = { + enable = true; + newline = true; + modules = [ "nix-shell" ]; + pathAliases = { "\\~/project/foo" = "prj-foo"; }; + settings = { + ignore-repos = [ "/home/me/project1" "/home/me/project2" ]; + }; + }; + }; + + nixpkgs.overlays = + [ (self: super: { zsh = pkgs.writeScriptBin "dummy-zsh" ""; }) ]; + + nmt.script = '' + assertFileExists home-files/.zshrc + assertFileContains \ + home-files/.zshrc \ + '/bin/powerline-go -error $? -shell zsh -modules nix-shell -newline -path-aliases \~/project/foo=prj-foo -ignore-repos /home/me/project1,/home/me/project2' + ''; + }; +} diff --git a/tests/modules/programs/tmux/default-shell.conf b/tests/modules/programs/tmux/default-shell.conf new file mode 100644 index 000000000000..a33623630756 --- /dev/null +++ b/tests/modules/programs/tmux/default-shell.conf @@ -0,0 +1,30 @@ +# ============================================= # +# Start with defaults from the Sensible plugin # +# --------------------------------------------- # +run-shell @sensible_rtp@ +# ============================================= # + +set -g default-terminal "screen" +set -g base-index 0 +setw -g pane-base-index 0 +# We need to set default-shell before calling new-session +set -g default-shell "/usr/bin/myshell" + + + + + +set -g status-keys emacs +set -g mode-keys emacs + + + + + + + +setw -g aggressive-resize off +setw -g clock-mode-style 12 +set -s escape-time 500 +set -g history-limit 2000 + diff --git a/tests/modules/programs/tmux/default-shell.nix b/tests/modules/programs/tmux/default-shell.nix new file mode 100644 index 000000000000..05091c6ab172 --- /dev/null +++ b/tests/modules/programs/tmux/default-shell.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + substituteExpected = path: + pkgs.substituteAll { + src = path; + + sensible_rtp = pkgs.tmuxPlugins.sensible.rtp; + }; + +in { + config = { + programs.tmux = { + enable = true; + shell = "/usr/bin/myshell"; + }; + + nmt.script = '' + assertFileExists home-files/.tmux.conf + assertFileContent home-files/.tmux.conf \ + ${substituteExpected ./default-shell.conf} + ''; + }; +} diff --git a/tests/modules/programs/tmux/default.nix b/tests/modules/programs/tmux/default.nix index d4501c60981a..f0a997b7e730 100644 --- a/tests/modules/programs/tmux/default.nix +++ b/tests/modules/programs/tmux/default.nix @@ -4,4 +4,5 @@ tmux-vi-all-true = ./vi-all-true.nix; tmux-secure-socket-enabled = ./secure-socket-enabled.nix; tmux-disable-confirmation-prompt = ./disable-confirmation-prompt.nix; + tmux-default-shell = ./default-shell.nix; } diff --git a/tests/modules/programs/vscode/default.nix b/tests/modules/programs/vscode/default.nix new file mode 100644 index 000000000000..70f6d2e70608 --- /dev/null +++ b/tests/modules/programs/vscode/default.nix @@ -0,0 +1 @@ +{ vscode-keybindings = ./keybindings.nix; } diff --git a/tests/modules/programs/vscode/keybindings.nix b/tests/modules/programs/vscode/keybindings.nix new file mode 100644 index 000000000000..d1ab38d3b059 --- /dev/null +++ b/tests/modules/programs/vscode/keybindings.nix @@ -0,0 +1,71 @@ +# Test that keybdinings.json is created correctly. +{ config, lib, pkgs, ... }: + +with lib; + +let + bindings = [ + { + key = "ctrl+c"; + command = "editor.action.clipboardCopyAction"; + when = "textInputFocus && false"; + } + { + key = "ctrl+c"; + command = "deleteFile"; + when = ""; + } + { + key = "d"; + command = "deleteFile"; + when = "explorerViewletVisible"; + } + ]; + + targetPath = if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/Code/User/keybindings.json" + else + ".config/Code/User/keybindings.json"; + + expectedJson = pkgs.writeText "expected.json" '' + [ + { + "command": "editor.action.clipboardCopyAction", + "key": "ctrl+c", + "when": "textInputFocus && false" + }, + { + "command": "deleteFile", + "key": "ctrl+c", + "when": "" + }, + { + "command": "deleteFile", + "key": "d", + "when": "explorerViewletVisible" + } + ] + ''; +in { + config = { + programs.vscode = { + enable = true; + keybindings = bindings; + }; + + nixpkgs.overlays = [ + (self: super: { + vscode = pkgs.runCommandLocal "vscode" { pname = "vscode"; } '' + mkdir -p $out/bin + touch $out/bin/code + chmod +x $out/bin/code; + ''; + }) + ]; + + nmt.script = '' + assertFileExists "home-files/${targetPath}" + assertFileContent "home-files/${targetPath}" "${expectedJson}" + ''; + }; +} diff --git a/tests/modules/programs/waybar/broken-settings.nix b/tests/modules/programs/waybar/broken-settings.nix new file mode 100644 index 000000000000..f5ac39468e68 --- /dev/null +++ b/tests/modules/programs/waybar/broken-settings.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; }; + expected = pkgs.writeText "expected-json" '' + [ + { + "height": 26, + "layer": "top", + "modules-center": [ + "sway/window" + ], + "modules-left": [ + "sway/workspaces", + "sway/mode" + ], + "modules-right": [ + "idle_inhibitor", + "pulseaudio", + "network", + "cpu", + "memory", + "backlight", + "tray", + "clock" + ], + "output": [ + "DP-1", + "eDP-1", + "HEADLESS-1" + ], + "position": "top", + "sway/workspaces": { + "all-outputs": true + } + } + ] + ''; +in { + config = { + programs.waybar = { + inherit package; + enable = true; + systemd.enable = true; + settings = [{ + layer = "top"; + position = "top"; + height = 26; + output = [ "DP-1" "eDP-1" "HEADLESS-1" ]; + modules-left = [ "sway/workspaces" "sway/mode" ]; + modules-center = [ "sway/window" ]; + modules-right = [ + "idle_inhibitor" + "pulseaudio" + "network" + "cpu" + "memory" + "backlight" + "tray" + "clock" + ]; + + modules = { "sway/workspaces".all-outputs = true; }; + }]; + }; + + nmt.description = '' + Test for the broken configuration + https://github.com/nix-community/home-manager/pull/1329#issuecomment-653253069 + ''; + nmt.script = '' + assertPathNotExists home-files/.config/waybar/style.css + assertFileContent \ + home-files/.config/waybar/config \ + ${expected} + ''; + }; +} diff --git a/tests/modules/programs/waybar/default.nix b/tests/modules/programs/waybar/default.nix new file mode 100644 index 000000000000..cac5de664c6f --- /dev/null +++ b/tests/modules/programs/waybar/default.nix @@ -0,0 +1,8 @@ +{ + waybar-systemd-with-graphical-session-target = + ./systemd-with-graphical-session-target.nix; + waybar-styling = ./styling.nix; + waybar-settings-complex = ./settings-complex.nix; + # Broken configuration from https://github.com/nix-community/home-manager/pull/1329#issuecomment-653253069 + waybar-broken-settings = ./broken-settings.nix; +} diff --git a/tests/modules/programs/waybar/settings-complex-expected.json b/tests/modules/programs/waybar/settings-complex-expected.json new file mode 100644 index 000000000000..0d020c19c978 --- /dev/null +++ b/tests/modules/programs/waybar/settings-complex-expected.json @@ -0,0 +1,46 @@ +[ + { + "custom/my-module": { + "exec": "@dummy@/bin/dummy", + "format": "hello from {}" + }, + "height": 30, + "idle_inhibitor": { + "format": "{icon}" + }, + "layer": "top", + "modules-center": [ + "sway/window" + ], + "modules-left": [ + "sway/workspaces", + "sway/mode", + "custom/my-module" + ], + "modules-right": [ + "idle_inhibitor", + "pulseaudio", + "network", + "cpu", + "memory", + "backlight", + "tray", + "battery", + "clock" + ], + "output": [ + "DP-1" + ], + "position": "top", + "sway/mode": { + "tooltip": false + }, + "sway/window": { + "max-length": 120 + }, + "sway/workspaces": { + "all-outputs": true, + "disable-scroll": true + } + } +] diff --git a/tests/modules/programs/waybar/settings-complex.nix b/tests/modules/programs/waybar/settings-complex.nix new file mode 100644 index 000000000000..750e52f4581c --- /dev/null +++ b/tests/modules/programs/waybar/settings-complex.nix @@ -0,0 +1,59 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; }; +in { + config = { + programs.waybar = { + inherit package; + enable = true; + settings = [{ + layer = "top"; + position = "top"; + height = 30; + output = [ "DP-1" ]; + modules-left = [ "sway/workspaces" "sway/mode" "custom/my-module" ]; + modules-center = [ "sway/window" ]; + modules-right = [ + "idle_inhibitor" + "pulseaudio" + "network" + "cpu" + "memory" + "backlight" + "tray" + "battery" + "clock" + ]; + + modules = { + "sway/workspaces" = { + disable-scroll = true; + all-outputs = true; + }; + "sway/mode" = { tooltip = false; }; + "sway/window" = { max-length = 120; }; + "idle_inhibitor" = { format = "{icon}"; }; + "custom/my-module" = { + format = "hello from {}"; + exec = let + dummyScript = + pkgs.writeShellScriptBin "dummy" "echo within waybar" // { + outPath = "@dummy@"; + }; + in "${dummyScript}/bin/dummy"; + }; + }; + }]; + }; + + nmt.script = '' + assertPathNotExists home-files/.config/waybar/style.css + assertFileContent \ + home-files/.config/waybar/config \ + ${./settings-complex-expected.json} + ''; + }; +} diff --git a/tests/modules/programs/waybar/styling-expected.css b/tests/modules/programs/waybar/styling-expected.css new file mode 100644 index 000000000000..dc779e58770d --- /dev/null +++ b/tests/modules/programs/waybar/styling-expected.css @@ -0,0 +1,23 @@ +* { + border: none; + border-radius: 0; + font-family: Source Code Pro; + font-weight: bold; + color: #abb2bf; + font-size: 18px; + min-height: 0px; +} +window#waybar { + background: #16191C; + color: #aab2bf; +} +#window { + padding: 0 0px; +} +#workspaces button:hover { + box-shadow: inherit; + text-shadow: inherit; + background: #16191C; + border: #16191C; + padding: 0 3px; +} diff --git a/tests/modules/programs/waybar/styling.nix b/tests/modules/programs/waybar/styling.nix new file mode 100644 index 000000000000..bd73f2aafd2e --- /dev/null +++ b/tests/modules/programs/waybar/styling.nix @@ -0,0 +1,46 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; }; +in { + config = { + programs.waybar = { + inherit package; + enable = true; + style = '' + * { + border: none; + border-radius: 0; + font-family: Source Code Pro; + font-weight: bold; + color: #abb2bf; + font-size: 18px; + min-height: 0px; + } + window#waybar { + background: #16191C; + color: #aab2bf; + } + #window { + padding: 0 0px; + } + #workspaces button:hover { + box-shadow: inherit; + text-shadow: inherit; + background: #16191C; + border: #16191C; + padding: 0 3px; + } + ''; + }; + + nmt.script = '' + assertPathNotExists home-files/.config/waybar/config + assertFileContent \ + home-files/.config/waybar/style.css \ + ${./styling-expected.css} + ''; + }; +} diff --git a/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix b/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix new file mode 100644 index 000000000000..e751d804dabc --- /dev/null +++ b/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix @@ -0,0 +1,24 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; }; +in { + config = { + programs.waybar = { + inherit package; + enable = true; + systemd.enable = true; + }; + + nmt.script = '' + assertPathNotExists home-files/.config/waybar/config + assertPathNotExists home-files/.config/waybar/style.css + + assertFileContent \ + home-files/.config/systemd/user/waybar.service \ + ${./systemd-with-graphical-session-target.service} + ''; + }; +} diff --git a/tests/modules/programs/waybar/systemd-with-graphical-session-target.service b/tests/modules/programs/waybar/systemd-with-graphical-session-target.service new file mode 100644 index 000000000000..64c89f93d755 --- /dev/null +++ b/tests/modules/programs/waybar/systemd-with-graphical-session-target.service @@ -0,0 +1,14 @@ +[Install] +WantedBy=graphical-session.target + +[Service] +BusName=fr.arouillard.waybar +ExecStart=@waybar@/bin/waybar +Restart=always +RestartSec=1sec +Type=dbus + +[Unit] +Description=Highly customizable Wayland bar for Sway and Wlroots based compositors. +Documentation=https://github.com/Alexays/Waybar/wiki +PartOf=graphical-session.target diff --git a/tests/modules/programs/zplug/modules.nix b/tests/modules/programs/zplug/modules.nix index 8ebf82b861f5..704c5c5e2ef2 100644 --- a/tests/modules/programs/zplug/modules.nix +++ b/tests/modules/programs/zplug/modules.nix @@ -38,8 +38,10 @@ with lib; assertFileContains home-files/.zshrc \ 'zplug "lib/clipboard", from:oh-my-zsh, if:"[[ $OSTYPE == *darwin* ]]"' - assertFileRegex home-files/.zshrc \ - '^zplug install$' + assertFileContains home-files/.zshrc \ + 'if ! zplug check; then + zplug install + fi' assertFileRegex home-files/.zshrc \ '^zplug load$' diff --git a/tests/modules/programs/zsh/default.nix b/tests/modules/programs/zsh/default.nix index 37339598e350..274ba0942eaa 100644 --- a/tests/modules/programs/zsh/default.nix +++ b/tests/modules/programs/zsh/default.nix @@ -4,4 +4,5 @@ zsh-history-path-new-custom = ./history-path-new-custom.nix; zsh-history-path-old-default = ./history-path-old-default.nix; zsh-history-path-old-custom = ./history-path-old-custom.nix; + zsh-prezto = ./prezto.nix; } diff --git a/tests/modules/programs/zsh/prezto.nix b/tests/modules/programs/zsh/prezto.nix new file mode 100644 index 000000000000..cc173bab82f2 --- /dev/null +++ b/tests/modules/programs/zsh/prezto.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + config = { + programs.zsh.prezto.enable = true; + + nixpkgs.overlays = [ + (self: super: { + zsh-prezto = super.runCommandLocal "dummy-zsh-prezto" { } '' + mkdir -p $out/runcoms + echo '# zprofile' > $out/runcoms/zprofile + echo '# zlogin' > $out/runcoms/zlogin + echo '# zlogout' > $out/runcoms/zlogout + echo '# zshenv' > $out/runcoms/zshenv + ''; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.zpreztorc + ''; + }; +} diff --git a/tests/modules/services/emacs/emacs-emacsclient.desktop b/tests/modules/services/emacs/emacs-emacsclient.desktop index ab9849bb6b9a..1dafb4e0f11c 100644 --- a/tests/modules/services/emacs/emacs-emacsclient.desktop +++ b/tests/modules/services/emacs/emacs-emacsclient.desktop @@ -1,12 +1,11 @@ [Desktop Entry] -Type=Application -Exec=@emacs@/bin/emacsclient -c %F -Terminal=false -Name=Emacs Client -Icon=emacs +Categories=Utility;TextEditor; Comment=Edit text +Exec=@emacs@/bin/emacsclient -c %F GenericName=Text Editor +Icon=emacs MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; -Categories=Utility;TextEditor; +Name=Emacs Client StartupWMClass=Emacs - +Terminal=false +Type=Application diff --git a/tests/modules/services/kanshi/basic-configuration.conf b/tests/modules/services/kanshi/basic-configuration.conf new file mode 100644 index 000000000000..9d6442b985bb --- /dev/null +++ b/tests/modules/services/kanshi/basic-configuration.conf @@ -0,0 +1,15 @@ +profile desktop { + output "eDP-1" disable + output "Iiyama North America PLE2483H-DP" enable position 0,0 + output "Iiyama North America PLE2483H-DP 1158765348486" enable mode 1920x1080 position 1920,0 scale 2.100000 transform flipped-270 + exec echo "1 two 3" +} + +profile nomad { + output "eDP-1" enable +} + +profile test { + output "*" enable +} + diff --git a/tests/modules/services/kanshi/basic-configuration.nix b/tests/modules/services/kanshi/basic-configuration.nix new file mode 100644 index 000000000000..15fbbb9ceb12 --- /dev/null +++ b/tests/modules/services/kanshi/basic-configuration.nix @@ -0,0 +1,52 @@ +{ config, pkgs, ... }: { + config = { + services.kanshi = { + enable = true; + package = pkgs.writeScriptBin "dummy-kanshi" ""; + profiles = { + nomad = { + outputs = [{ + criteria = "eDP-1"; + status = "enable"; + }]; + }; + desktop = { + exec = ''echo "1 two 3"''; + outputs = [ + { + criteria = "eDP-1"; + status = "disable"; + } + { + criteria = "Iiyama North America PLE2483H-DP"; + status = "enable"; + position = "0,0"; + } + { + criteria = "Iiyama North America PLE2483H-DP 1158765348486"; + status = "enable"; + position = "1920,0"; + scale = 2.1; + mode = "1920x1080"; + transform = "flipped-270"; + } + ]; + }; + }; + extraConfig = '' + profile test { + output "*" enable + } + ''; + }; + + nmt.script = '' + serviceFile=home-files/.config/systemd/user/kanshi.service + assertFileExists $serviceFile + + assertFileExists home-files/.config/kanshi/config + assertFileContent home-files/.config/kanshi/config \ + ${./basic-configuration.conf} + ''; + }; +} diff --git a/tests/modules/services/kanshi/default.nix b/tests/modules/services/kanshi/default.nix new file mode 100644 index 000000000000..cb6b2a6b79f9 --- /dev/null +++ b/tests/modules/services/kanshi/default.nix @@ -0,0 +1 @@ +{ kanshi-basic-configuration = ./basic-configuration.nix; } diff --git a/tests/modules/services/window-managers/i3/i3-followmouse.nix b/tests/modules/services/window-managers/i3/i3-followmouse.nix index 8d51e3488772..43e15cda3966 100644 --- a/tests/modules/services/window-managers/i3/i3-followmouse.nix +++ b/tests/modules/services/window-managers/i3/i3-followmouse.nix @@ -14,7 +14,9 @@ with lib; (self: super: { dmenu = super.dmenu // { outPath = "@dmenu@"; }; - i3 = super.i3 // { outPath = "@i3@"; }; + i3 = super.writeScriptBin "i3" "" // { outPath = "@i3@"; }; + + i3-gaps = super.writeScriptBin "i3" "" // { outPath = "@i3-gaps@"; }; i3status = super.i3status // { outPath = "@i3status@"; }; }) diff --git a/tests/modules/services/window-managers/i3/i3-keybindings.nix b/tests/modules/services/window-managers/i3/i3-keybindings.nix index 4f8515e61ffd..0458b8744a46 100644 --- a/tests/modules/services/window-managers/i3/i3-keybindings.nix +++ b/tests/modules/services/window-managers/i3/i3-keybindings.nix @@ -19,9 +19,10 @@ with lib; nixpkgs.overlays = [ (self: super: { dmenu = super.dmenu // { outPath = "@dmenu@"; }; - - i3 = super.i3 // { outPath = "@i3@"; }; - + i3 = super.writeScriptBin "i3" "" // { outPath = "@i3@"; }; + i3-gaps = super.writeScriptBin "i3-gaps" "" // { + outPath = "@i3-gaps@"; + }; i3status = super.i3status // { outPath = "@i3status@"; }; }) ]; diff --git a/tests/modules/services/window-managers/sway/default.nix b/tests/modules/services/window-managers/sway/default.nix index 95e15cc9d8f2..b9c0ab5e099d 100644 --- a/tests/modules/services/window-managers/sway/default.nix +++ b/tests/modules/services/window-managers/sway/default.nix @@ -1,4 +1,6 @@ { + sway-default = ./sway-default.nix; + sway-post-2003 = ./sway-post-2003.nix; sway-followmouse = ./sway-followmouse.nix; sway-followmouse-legacy = ./sway-followmouse-legacy.nix; } diff --git a/tests/modules/services/window-managers/sway/sway-default.conf b/tests/modules/services/window-managers/sway/sway-default.conf new file mode 100644 index 000000000000..da5b1f47eef2 --- /dev/null +++ b/tests/modules/services/window-managers/sway/sway-default.conf @@ -0,0 +1,117 @@ +font pango:monospace 8 +floating_modifier Mod1 +default_border pixel 2 +default_floating_border pixel 2 +hide_edge_borders none +focus_wrapping no +focus_follows_mouse yes +focus_on_window_activation smart +mouse_warping output +workspace_layout default +workspace_auto_back_and_forth no + +client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577 +client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a +client.unfocused #333333 #222222 #888888 #292d2e #222222 +client.urgent #2f343a #900000 #ffffff #900000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c +client.background #ffffff + +bindsym Mod1+1 workspace number 1 +bindsym Mod1+2 workspace number 2 +bindsym Mod1+3 workspace number 3 +bindsym Mod1+4 workspace number 4 +bindsym Mod1+5 workspace number 5 +bindsym Mod1+6 workspace number 6 +bindsym Mod1+7 workspace number 7 +bindsym Mod1+8 workspace number 8 +bindsym Mod1+9 workspace number 9 +bindsym Mod1+Down focus down +bindsym Mod1+Left focus left +bindsym Mod1+Return exec @rxvt-unicode-unwrapped@/bin/urxvt +bindsym Mod1+Right focus right +bindsym Mod1+Shift+1 move container to workspace number 1 +bindsym Mod1+Shift+2 move container to workspace number 2 +bindsym Mod1+Shift+3 move container to workspace number 3 +bindsym Mod1+Shift+4 move container to workspace number 4 +bindsym Mod1+Shift+5 move container to workspace number 5 +bindsym Mod1+Shift+6 move container to workspace number 6 +bindsym Mod1+Shift+7 move container to workspace number 7 +bindsym Mod1+Shift+8 move container to workspace number 8 +bindsym Mod1+Shift+9 move container to workspace number 9 +bindsym Mod1+Shift+Down move down +bindsym Mod1+Shift+Left move left +bindsym Mod1+Shift+Right move right +bindsym Mod1+Shift+Up move up +bindsym Mod1+Shift+c reload +bindsym Mod1+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit' +bindsym Mod1+Shift+h move left +bindsym Mod1+Shift+j move down +bindsym Mod1+Shift+k move up +bindsym Mod1+Shift+l move right +bindsym Mod1+Shift+minus move scratchpad +bindsym Mod1+Shift+q kill +bindsym Mod1+Shift+space floating toggle +bindsym Mod1+Up focus up +bindsym Mod1+a focus parent +bindsym Mod1+b splith +bindsym Mod1+d exec @dmenu@/bin/dmenu_run +bindsym Mod1+e layout toggle split +bindsym Mod1+f fullscreen toggle +bindsym Mod1+h focus left +bindsym Mod1+j focus down +bindsym Mod1+k focus up +bindsym Mod1+l focus right +bindsym Mod1+minus scratchpad show +bindsym Mod1+r mode resize +bindsym Mod1+s layout stacking +bindsym Mod1+space focus mode_toggle +bindsym Mod1+v splitv +bindsym Mod1+w layout tabbed + + + +mode "resize" { +bindsym Down resize grow height 10 px +bindsym Escape mode default +bindsym Left resize shrink width 10 px +bindsym Return mode default +bindsym Right resize grow width 10 px +bindsym Up resize shrink height 10 px +bindsym h resize shrink width 10 px +bindsym j resize grow height 10 px +bindsym k resize shrink height 10 px +bindsym l resize grow width 10 px +} + + +bar { + + font pango:monospace 8 + mode dock + hidden_state hide + position bottom + status_command @i3status@/bin/i3status + swaybar_command @sway/bin/swaybar + workspace_buttons yes + strip_workspace_numbers no + tray_output primary + colors { + background #000000 + statusline #ffffff + separator #666666 + focused_workspace #4c7899 #285577 #ffffff + active_workspace #333333 #5f676a #ffffff + inactive_workspace #333333 #222222 #888888 + urgent_workspace #2f343a #900000 #ffffff + binding_mode #2f343a #900000 #ffffff + } + +} + + + + + + +exec "systemctl --user import-environment; systemctl --user start sway-session.target" diff --git a/tests/modules/services/window-managers/sway/sway-default.nix b/tests/modules/services/window-managers/sway/sway-default.nix new file mode 100644 index 000000000000..0ccedbfda8ac --- /dev/null +++ b/tests/modules/services/window-managers/sway/sway-default.nix @@ -0,0 +1,36 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out"; + +in { + config = { + wayland.windowManager.sway = { + enable = true; + package = dummy-package // { outPath = "@sway"; }; + # overriding findutils causes issues + config.menu = "${pkgs.dmenu}/bin/dmenu_run"; + }; + + nixpkgs.overlays = [ + (self: super: { + dmenu = dummy-package // { outPath = "@dmenu@"; }; + rxvt-unicode-unwrapped = dummy-package // { + outPath = "@rxvt-unicode-unwrapped@"; + }; + i3status = dummy-package // { outPath = "@i3status@"; }; + sway = dummy-package // { outPath = "@sway@"; }; + xwayland = dummy-package // { outPath = "@xwayland@"; }; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/sway/config + assertFileContent home-files/.config/sway/config \ + ${./sway-default.conf} + ''; + }; +} diff --git a/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix b/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix index 9b80a63bc596..9ed0ceaf042c 100644 --- a/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix +++ b/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix @@ -2,10 +2,15 @@ with lib; -{ +let + + dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out"; + +in { config = { wayland.windowManager.sway = { enable = true; + package = dummy-package // { outPath = "@sway"; }; config = { focus.followMouse = false; @@ -16,15 +21,17 @@ with lib; nixpkgs.overlays = [ (self: super: { - dmenu = super.dmenu // { outPath = "@dmenu@"; }; - rxvt-unicode-unwrapped = super.rxvt-unicode-unwrapped // { + dmenu = dummy-package // { outPath = "@dmenu@"; }; + rxvt-unicode-unwrapped = dummy-package // { outPath = "@rxvt-unicode-unwrapped@"; }; - sway-unwrapped = - pkgs.runCommandLocal "dummy-sway-unwrapped" { version = "1"; } - "mkdir $out"; - swaybg = pkgs.writeScriptBin "dummy-swaybg" ""; - xwayland = pkgs.writeScriptBin "xwayland" ""; + sway = dummy-package // { outPath = "@sway@"; }; + sway-unwrapped = dummy-package // { + outPath = "@sway-unwrapped@"; + version = "1"; + }; + swaybg = dummy-package // { outPath = "@swaybg@"; }; + xwayland = dummy-package // { outPath = "@xwayland@"; }; }) ]; diff --git a/tests/modules/services/window-managers/sway/sway-followmouse.nix b/tests/modules/services/window-managers/sway/sway-followmouse.nix index e05b4e56fc90..8d54eccf73ff 100644 --- a/tests/modules/services/window-managers/sway/sway-followmouse.nix +++ b/tests/modules/services/window-managers/sway/sway-followmouse.nix @@ -2,10 +2,15 @@ with lib; -{ +let + + dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out"; + +in { config = { wayland.windowManager.sway = { enable = true; + package = dummy-package // { outPath = "@sway"; }; config = { focus.followMouse = "always"; @@ -16,15 +21,14 @@ with lib; nixpkgs.overlays = [ (self: super: { - dmenu = super.dmenu // { outPath = "@dmenu@"; }; - rxvt-unicode-unwrapped = super.rxvt-unicode-unwrapped // { + dmenu = dummy-package // { outPath = "@dmenu@"; }; + rxvt-unicode-unwrapped = dummy-package // { outPath = "@rxvt-unicode-unwrapped@"; }; - sway-unwrapped = - pkgs.runCommandLocal "dummy-sway-unwrapped" { version = "1"; } - "mkdir $out"; - swaybg = pkgs.writeScriptBin "dummy-swaybg" ""; - xwayland = pkgs.writeScriptBin "xwayland" ""; + sway = dummy-package // { outPath = "@sway@"; }; + sway-unwrapped = dummy-package // { version = "1"; }; + swaybg = dummy-package // { outPath = "@swaybg@"; }; + xwayland = dummy-package // { outPath = "@xwayland@"; }; }) ]; diff --git a/tests/modules/services/window-managers/sway/sway-post-2003.nix b/tests/modules/services/window-managers/sway/sway-post-2003.nix new file mode 100644 index 000000000000..740e7e526f02 --- /dev/null +++ b/tests/modules/services/window-managers/sway/sway-post-2003.nix @@ -0,0 +1,38 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out"; + +in { + config = { + home.stateVersion = "20.09"; + + wayland.windowManager.sway = { + enable = true; + package = dummy-package // { outPath = "@sway"; }; + # overriding findutils causes issues + config.menu = "${pkgs.dmenu}/bin/dmenu_run"; + }; + + nixpkgs.overlays = [ + (self: super: { + dmenu = dummy-package // { outPath = "@dmenu@"; }; + rxvt-unicode-unwrapped = dummy-package // { + outPath = "@rxvt-unicode-unwrapped@"; + }; + sway = dummy-package // { outPath = "@sway@"; }; + i3status = dummy-package // { outPath = "@i3status@"; }; + xwayland = dummy-package // { outPath = "@xwayland@"; }; + }) + ]; + + nmt.script = '' + assertFileExists home-files/.config/sway/config + assertFileContent home-files/.config/sway/config \ + ${./sway-default.conf} + ''; + }; +} diff --git a/tests/modules/services/wlsunset/default.nix b/tests/modules/services/wlsunset/default.nix new file mode 100644 index 000000000000..d59af701535b --- /dev/null +++ b/tests/modules/services/wlsunset/default.nix @@ -0,0 +1 @@ +{ wlsunset-service = ./wlsunset-service.nix; } diff --git a/tests/modules/services/wlsunset/wlsunset-service-expected.service b/tests/modules/services/wlsunset/wlsunset-service-expected.service new file mode 100644 index 000000000000..f0cf96f3ad0a --- /dev/null +++ b/tests/modules/services/wlsunset/wlsunset-service-expected.service @@ -0,0 +1,9 @@ +[Install] +WantedBy=test.target + +[Service] +ExecStart=@wlsunset@/bin/wlsunset -l 12.3 -L 128.8 -t 3500 -T 6000 -g 0.6 + +[Unit] +Description=Day/night gamma adjustments for Wayland compositors. +PartOf=graphical-session.target diff --git a/tests/modules/services/wlsunset/wlsunset-service.nix b/tests/modules/services/wlsunset/wlsunset-service.nix new file mode 100644 index 000000000000..de32a8270be0 --- /dev/null +++ b/tests/modules/services/wlsunset/wlsunset-service.nix @@ -0,0 +1,25 @@ +{ config, pkgs, ... }: + +{ + config = { + services.wlsunset = { + enable = true; + package = pkgs.writeScriptBin "dummy-wlsunset" "" // { + outPath = "@wlsunset@"; + }; + latitude = "12.3"; + longitude = "128.8"; + temperature.day = 6000; + temperature.night = 3500; + gamma = "0.6"; + systemdTarget = "test.target"; + }; + + nmt.script = '' + serviceFile=home-files/.config/systemd/user/wlsunset.service + + assertFileExists $serviceFile + assertFileContent $serviceFile ${./wlsunset-service-expected.service} + ''; + }; +} diff --git a/tests/modules/systemd/services-expected.conf b/tests/modules/systemd/services-expected.conf deleted file mode 100644 index 34b9618d6d34..000000000000 --- a/tests/modules/systemd/services-expected.conf +++ /dev/null @@ -1,5 +0,0 @@ -[Service] -ExecStart=/some/exec/start/command --with-arguments "%i" - -[Unit] -Description=A basic test service diff --git a/tests/modules/systemd/services.nix b/tests/modules/systemd/services.nix index ea9b2b4fb874..4f73c5568fb8 100644 --- a/tests/modules/systemd/services.nix +++ b/tests/modules/systemd/services.nix @@ -10,6 +10,7 @@ with lib; }; Service = { + Environment = [ "VAR1=1" "VAR2=2" ]; ExecStart = ''/some/exec/start/command --with-arguments "%i"''; }; }; @@ -17,7 +18,16 @@ with lib; nmt.script = '' serviceFile=home-files/.config/systemd/user/test-service@.service assertFileExists $serviceFile - assertFileContent $serviceFile ${./services-expected.conf} + assertFileContent $serviceFile \ + ${builtins.toFile "services-expected.conf" '' + [Service] + Environment=VAR1=1 + Environment=VAR2=2 + ExecStart=/some/exec/start/command --with-arguments "%i" + + [Unit] + Description=A basic test service + ''} ''; }; } diff --git a/tests/modules/targets-darwin/darwin.nix b/tests/modules/targets-darwin/darwin.nix new file mode 100644 index 000000000000..511ae87fd98c --- /dev/null +++ b/tests/modules/targets-darwin/darwin.nix @@ -0,0 +1,20 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + darwinTestApp = pkgs.runCommandLocal "target-darwin-example-app" { } '' + mkdir -p $out/Applications + touch $out/Applications/example-app + ''; + +in { + config = { + home.packages = [ darwinTestApp ]; + + nmt.script = '' + assertFileExists 'home-files/Applications/Home Manager Apps/example-app' + ''; + }; +} diff --git a/tests/modules/targets-darwin/default.nix b/tests/modules/targets-darwin/default.nix new file mode 100644 index 000000000000..10e4111f779b --- /dev/null +++ b/tests/modules/targets-darwin/default.nix @@ -0,0 +1,5 @@ +{ + # Disabled for now due to conflicting behavior with nix-darwin. See + # https://github.com/nix-community/home-manager/issues/1341#issuecomment-687286866 + #targets-darwin = ./darwin.nix; +} diff --git a/tests/modules/targets/default.nix b/tests/modules/targets-linux/default.nix similarity index 100% rename from tests/modules/targets/default.nix rename to tests/modules/targets-linux/default.nix diff --git a/tests/modules/targets/generic-linux.nix b/tests/modules/targets-linux/generic-linux.nix similarity index 69% rename from tests/modules/targets/generic-linux.nix rename to tests/modules/targets-linux/generic-linux.nix index d9bb85b651ae..10481b5e9887 100644 --- a/tests/modules/targets/generic-linux.nix +++ b/tests/modules/targets-linux/generic-linux.nix @@ -4,13 +4,16 @@ with lib; { config = { - targets.genericLinux.enable = true; + targets.genericLinux = { + enable = true; + extraXdgDataDirs = [ "/foo" ]; + }; nmt.script = '' assertFileExists home-path/etc/profile.d/hm-session-vars.sh assertFileContains \ home-path/etc/profile.d/hm-session-vars.sh \ - 'export XDG_DATA_DIRS="''${NIX_STATE_DIR:-/nix/var/nix}/profiles/default/share:/home/hm-user/.nix-profile/share''${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS"' + 'export XDG_DATA_DIRS="''${NIX_STATE_DIR:-/nix/var/nix}/profiles/default/share:/home/hm-user/.nix-profile/share:/foo''${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS"' assertFileContains \ home-path/etc/profile.d/hm-session-vars.sh \ '. "${pkgs.nix}/etc/profile.d/nix.sh"'