Repository: srid/nixos-flake Branch: master Commit: d2818c36b863 Files: 37 Total size: 50.5 KB Directory structure: gitextract_shy0x3kh/ ├── .envrc ├── .github/ │ └── workflows/ │ └── ci.yaml ├── .gitignore ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── LICENSE ├── README.md ├── activate/ │ ├── activate.nu │ ├── default.nix │ └── nu.nix ├── dev/ │ └── flake.nix ├── doc/ │ ├── .gitignore │ ├── examples.md │ ├── flake.nix │ ├── guide/ │ │ ├── activate.md │ │ ├── autowiring.md │ │ ├── outputs.md │ │ ├── specialArgs.md │ │ └── templates.md │ ├── guide.md │ ├── history.md │ ├── howto.md │ ├── index.md │ ├── index.yaml │ ├── mod.just │ └── start.md ├── examples/ │ ├── home/ │ │ └── flake.nix │ ├── linux/ │ │ └── flake.nix │ └── macos/ │ └── flake.nix ├── flake.nix ├── justfile ├── nix/ │ └── modules/ │ ├── configurations/ │ │ └── default.nix │ └── flake-parts/ │ ├── autowire.nix │ ├── default.nix │ ├── lib.nix │ └── packages.nix └── vira.hs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .envrc ================================================ watch_file dev/flake.nix use flake ./dev --override-input nixos-unified . ================================================ FILE: .github/workflows/ci.yaml ================================================ name: "CI" on: push: branches: - master jobs: website-upload: runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v4 - uses: nixbuild/nix-quick-install-action@v33 - name: Build docs run: | cd doc && nix build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: doc/result website-deploy: runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' needs: website-upload environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .gitignore ================================================ result /.direnv ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "bbenoist.nix", "b4dm4n.nixpkgs-fmt", "jnoortheen.nix-ide", "mattn.lisp", "mkhl.direnv", "thenuprojectcontributors.vscode-nushell-lang" ] } ================================================ FILE: .vscode/settings.json ================================================ { "editor.defaultFormatter": "B4dM4n.nixpkgs-fmt", "editor.formatOnSave": true, "editor.formatOnType": true } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 Sridhar Ratnakumar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://nixos.zulipchat.com/#narrow/stream/413948-nixos) [![Naiveté Compass of Mood](https://img.shields.io/badge/naïve-FF10F0)](https://compass.naivete.me/ "This project follows the 'Naiveté Compass of Mood'") # nixos-unified [**nixos-unified**](https://github.com/srid/nixos-unified) is a [flake-parts](https://flake.parts/) module to unify [NixOS] + [nix-darwin] + [home-manager] configuration in a single flake, while providing a consistent interface at DX and UX level. [NixOS]: https://nixos.org/ [nix-darwin]: https://github.com/LnL7/nix-darwin [home-manager]: https://github.com/nix-community/home-manager For motivation, see https://nixos-unified.org/#why [home-manager]: https://github.com/nix-community/home-manager ## Getting Started https://nixos-unified.org/start.html ## Examples https://nixos-unified.org/examples.html ## Discussion To discuss the project, post in [our Zulip](https://nixos.zulipchat.com/#narrow/stream/413948-nixos) or in [Github Discussions](https://github.com/srid/nixos-unified/discussions). ================================================ FILE: activate/activate.nu ================================================ use std log use std assert use nixos-unified.nu getData # This module is generated in Nix let CURRENT_HOSTNAME = (hostname -s | str trim) let data = getData # Get all the data associated with a host # # Presently, this only deals with nixosConfigurations and darwinConfigurations. # But we should also incorporate home-manager configurations. def get_host_data [ host: string ] { if $host not-in $data.nixos-unified-configs { log error $"Host '($host)' not found in flake. Available hosts=($data.nixos-unified-configs | columns)" exit 1 } $data.nixos-unified-configs | get $host | insert "host" $host | insert "flake" $"($data.cleanFlake)#($host)" } # Parse "[srid@]example" into { user: "srid", host: "example" } # # localhost hosts are ignored (null'ified) def parseFlakeOutputRef [ spec: string ] { let parts = $spec | split row "@" let handleLocalhost = {|h| if $h == "localhost" { null } else { $h } } if ($parts | length) == 1 { { user: null host: (do $handleLocalhost $parts.0) } } else { { user: $parts.0 host: (do $handleLocalhost $parts.1) } } } # Activate system or home configuration # # The ref should match the name of the corresponding nixosConfigurations, darwinConfigurations or homeConfigurations attrkey. "localhost" is an exception, which will use the current host. def main [ ref: string = "localhost", # Hostname or username (if containing `@`) to activate --dry-run # Dry run (don't actually activate) ] { let spec = parseFlakeOutputRef $ref if $spec.user != null { activate_home $spec.user $spec.host --dry-run=$dry_run } else { let host = if ($spec.host | is-empty) { $CURRENT_HOSTNAME } else { $spec.host } let hostData = get_host_data $host activate_system $hostData --dry-run=$dry_run } } def activate_home [ user: string, host: string, --dry-run ] { if (($host | is-empty) or ($host == $CURRENT_HOSTNAME)) { activate_home_local $user $host --dry-run=$dry_run } else { activate_home_remote_ssh $user $host --dry-run=$dry_run } } def activate_home_local [ user: string, host: string, --dry-run ] { let name = $"($user)" + (if ($host | is-empty) { "" } else { "@" + $host }) let extraArgs = if $dry_run { ["--dry-run"] } else { [] } log info $"Activating home configuration ($name) (ansi purple)locally(ansi reset)" log info $"(ansi blue_bold)>>>(ansi reset) home-manager switch ($extraArgs | str join) --flake ($data.cleanFlake)#($name)" home-manager switch ...$extraArgs -b (date now | format date "nixos-unified.%Y-%m-%d-%H:%M:%S.bak") --flake $"($data.cleanFlake)#($name)" } def activate_home_remote_ssh [ user: string, host: string, --dry-run ] { let name = $"($user)@($host)" let sshTarget = $"($user)@($host)" log info $"Activating home configuration ($name) (ansi purple_reverse)remotely(ansi reset) on ($sshTarget)" # Copy the flake to the remote host. nix_copy $data.cleanFlake $"ssh-ng://($sshTarget)" # We re-run this activation script, but on the remote host (where it will invoke activate_home_local). log info $'(ansi blue_bold)>>>(ansi reset) ssh -t ($sshTarget) nix --extra-experimental-features '"nix-command flakes"' run $"($data.cleanFlake)#activate" -- ($name) --dry-run=($dry_run)' ssh -t $sshTarget nix --extra-experimental-features '"nix-command flakes"' run $"($data.cleanFlake)#activate" -- ($name) --dry-run=($dry_run) } def activate_system [ hostData: record, --dry-run=false ] { log info $"(ansi grey)currentSystem=($data.system) currentHost=(ansi green_bold)($CURRENT_HOSTNAME)(ansi grey) targetHost=(ansi green_reverse)($hostData.host)(ansi reset)(ansi grey) hostData=($hostData)(ansi reset)" if ($CURRENT_HOSTNAME == $hostData.host) { # Since the user asked to activate current host, do so. activate_system_local $hostData --dry-run=$dry_run } else { # Remote activation request, so copy the flake and the necessary inputs # and then activate over SSH. if $hostData.sshTarget == null { log error $"sshTarget not found in host data for ($hostData.host). Add `nixos-unified.sshTarget = \"user@hostname\";` to your configuration." exit 1 } activate_system_remote_ssh $hostData --dry-run=$dry_run } } def activate_system_local [ hostData: record, --dry-run=false ] { log info $"Activating (ansi purple)locally(ansi reset)" let darwin = $hostData.outputs.system in ["aarch64-darwin" "x86_64-darwin"] if $darwin { let subcommand = if $dry_run { "build" } else { "switch" } log info $"(ansi blue_bold)>>>(ansi reset) sudo darwin-rebuild ($subcommand) --flake ($hostData.flake) ($hostData.outputs.nixArgs | str join)" sudo darwin-rebuild $subcommand --flake $hostData.flake ...$hostData.outputs.nixArgs } else { let subcommand = if $dry_run { "dry-activate" } else { "switch" } if $hostData.localPrivilegeMode == "sudo-nixos-rebuild" { let nixosRebuild = "/run/current-system/sw/bin/nixos-rebuild" log info $"(ansi blue_bold)>>>(ansi reset) sudo ($nixosRebuild) ($subcommand) --flake ($hostData.flake) ($hostData.outputs.nixArgs | str join)" sudo $nixosRebuild $subcommand --flake $hostData.flake ...$hostData.outputs.nixArgs } else { log info $"(ansi blue_bold)>>>(ansi reset) nixos-rebuild ($subcommand) --flake ($hostData.flake) ($hostData.outputs.nixArgs | str join) --sudo" nixos-rebuild $subcommand --flake $hostData.flake ...$hostData.outputs.nixArgs --sudo } } } def activate_system_remote_ssh [ hostData: record, --dry-run=false ] { log info $"Activating (ansi purple_reverse)remotely(ansi reset) on ($hostData.sshTarget)" # Copy the flake and the necessary inputs to the remote host. nix_copy $data.cleanFlake $"ssh-ng://($hostData.sshTarget)" $hostData.outputs.overrideInputs | transpose key value | each { |input| nix_copy $input.value $"ssh-ng://($hostData.sshTarget)" } # We re-run this activation script, but on the remote host (where it will invoke activate_system_local). log info $'(ansi blue_bold)>>>(ansi reset) ssh -t ($hostData.sshTarget) nix --extra-experimental-features '"nix-command flakes"' run ($hostData.outputs.nixArgs | str join) $"($data.cleanFlake)#activate" -- ($hostData.host) --dry-run=($dry_run)' ssh -t $hostData.sshTarget nix --extra-experimental-features '"nix-command flakes"' run ...$hostData.outputs.nixArgs $"($data.cleanFlake)#activate" -- ($hostData.host) --dry-run=($dry_run) } def nix_copy [ src: string dst: string ] { log info $"(ansi blue_bold)>>>(ansi reset) nix --extra-experimental-features \"nix-command flakes\" copy ($src) --to ($dst)" nix --extra-experimental-features "nix-command flakes" copy $src --to $dst } ================================================ FILE: activate/default.nix ================================================ { self, inputs', pkgs, lib, system, ... }: let nixosFlakeNuModule = let # Workaround https://github.com/NixOS/nix/issues/8752 cleanFlake = lib.cleanSourceWith { name = "nixos-unified-activate-flake"; src = self; }; nixos-unified-configs = lib.mapAttrs (name: value: value.config.nixos-unified) (self.nixosConfigurations or { } // self.darwinConfigurations or { }); data = { nixos-unified-configs = nixos-unified-configs; system = system; cleanFlake = cleanFlake; }; dataFile = pkgs.writeTextFile { name = "nixos-unified-activate-data"; text = '' ${builtins.toJSON data} ''; }; in pkgs.writeTextFile { name = "nixos-unified.nu"; text = '' export def getData [] { open ${dataFile} | from json } ''; }; nu = import ./nu.nix { inherit pkgs; }; in nu.writeNushellApplication { name = "activate"; scriptDir = ./.; meta = { mainProgram = "activate.nu"; description = "Activate NixOS/nix-darwin/home-manager configurations"; }; runtimeInputs = # TODO: better way to check for nix-darwin availability lib.optionals (pkgs.stdenv.isDarwin && lib.hasAttr "nix-darwin" inputs') [ inputs'.nix-darwin.packages.default # Provides darwin-rebuild ] ++ lib.optionals (lib.hasAttr "home-manager" inputs') [ inputs'.home-manager.packages.default # Provides home-manager ] ++ [ pkgs.nixos-rebuild pkgs.hostname ]; extraBuildCommand = '' cp ${nixosFlakeNuModule} nixos-unified.nu ''; } ================================================ FILE: activate/nu.nix ================================================ # Nix support for working Nushell scripts # # TODO: Migrate to this once merged, # https://github.com/DeterminateSystems/nuenv/pull/27 { pkgs, ... }: { # Like writeShellApplication but for Nushell scripts # # This function likely should be improved for general use. writeNushellApplication = { name , runtimeInputs ? [ ] , scriptDir , extraBuildCommand ? "" , meta }: let nixNuModule = pkgs.writeTextFile { name = "nix.nu"; text = '' use std * let bins = '${builtins.toJSON (builtins.map (p: "${p}/bin") runtimeInputs)}' | from json log debug $"Adding runtime inputs to PATH: ($bins)" if $bins != [] { path add ...$bins } ''; }; in pkgs.runCommand name { inherit meta; } '' mkdir -p $out/bin cp ${scriptDir}/*.nu $out/bin/ chmod -R a+w $out/bin cd $out/bin rm -f ${meta.mainProgram} echo "#!${pkgs.nushell}/bin/nu" >> ${meta.mainProgram} cat ${nixNuModule} >> ${meta.mainProgram} cat ${scriptDir}/${meta.mainProgram} >> ${meta.mainProgram} chmod a+x ${meta.mainProgram} ${extraBuildCommand} ''; } ================================================ FILE: dev/flake.nix ================================================ { inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; flake-root.url = "github:srid/flake-root"; treefmt-nix.url = "github:numtide/treefmt-nix"; nixos-unified.url = "github:srid/nixos-unified"; }; outputs = inputs@{ nixpkgs, flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = nixpkgs.lib.systems.flakeExposed; imports = [ inputs.flake-root.flakeModule inputs.treefmt-nix.flakeModule ]; perSystem = { pkgs, lib, config, ... }: { treefmt.config = { projectRoot = inputs.haskell-flake; projectRootFile = "README.md"; programs.nixpkgs-fmt.enable = true; }; devShells.default = pkgs.mkShell { # cf. https://community.flake.parts/haskell-flake/devshell#composing-devshells inputsFrom = [ config.treefmt.build.devShell ]; packages = with pkgs; [ just nixd ]; shellHook = '' echo echo "🍎🍎 Run 'just ' to get started" just ''; }; }; }; } ================================================ FILE: doc/.gitignore ================================================ /book /result ================================================ FILE: doc/examples.md ================================================ --- order: 10 --- # Examples - - - ================================================ FILE: doc/flake.nix ================================================ { inputs = { emanote.url = "github:srid/emanote"; emanote.inputs.emanote-template.follows = ""; nixpkgs.follows = "emanote/nixpkgs"; flake-parts.follows = "emanote/flake-parts"; }; outputs = inputs@{ self, flake-parts, nixpkgs, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" "aarch64-darwin" ]; imports = [ inputs.emanote.flakeModule ]; perSystem = { self', pkgs, system, ... }: { emanote = { sites."default" = { layers = [{ path = ./.; pathString = "."; }]; extraConfig.template = { baseUrl = "/"; urlStrategy = "pretty"; }; }; }; }; }; } ================================================ FILE: doc/guide/activate.md ================================================ --- order: 2 --- # Activation `nixos-unified` provides an `.#activate` flake app that can be used in place of `nixos-rebuild switch` (if using NixOS),`darwin-rebuild switch` (if using `nix-darwin`) or `home-manager switch` (if using home-manager) In addition, it can remotely activate the system over SSH (see further below). ## Activating NixOS or nix-darwin configurations {#system} In order to activate a system configuration for the current host (`$HOSTNAME`), run: ```sh nix run .#activate ``` ### Passwordless local NixOS activation {#passwordless-local-nixos} By default, local NixOS activation runs `nixos-rebuild switch --sudo`, so `nixos-rebuild` decides when to invoke `sudo` for privileged steps. If you want sudoers to match a single command, configure the target host to run `nixos-rebuild` itself through sudo: ```nix { nixos-unified.localPrivilegeMode = "sudo-nixos-rebuild"; } ``` This makes the activator run `/run/current-system/sw/bin/nixos-rebuild` via sudo. Add a narrowly scoped sudoers rule for the user and command you use for activation: ```nix { security.sudo.extraRules = [ { users = [ "myuser" ]; commands = [ { command = "/run/current-system/sw/bin/nixos-rebuild switch *"; options = [ "NOPASSWD" ]; } ]; } ]; } ``` > [!TIP] > Usually, you'd make this your default package, so as to be able to use `nix run`. In `flake.nix`: > > ```nix > # In perSystem > { > packages.default = self'.packages.activate > } > ``` ## Activating home configuration {#home} If you are on a non-NixOS Linux (or on macOS but you do not use nix-darwin), you will have a home-manager configuration. Suppose, you have it stored in `legacyPackages.homeConfigurations."myuser"` (where `myuser` matches `$USER`), you can activate that by running: ```sh nix run .#activate $USER@ ``` > [!NOTE] > The activate app will activate the home-manager configuration if the argument contains a `@` (separating user and the optional hostname). The above command has no hostname, indicating that we are activating for the local host. > [!NOTE] > The activate app will move your existing dotfiles out of the way with a timestamped backup extension. For example, your existing `~/.zshrc` will be backed up in `~/.zshrc.nixos-unified.2025-01-15-22:29:54.bak`. ### Per-host home configurations {#home-perhost} You may also have separate home configurations for each machine, such as `legacyPackages.homeConfigurations."myuser@myhost"`. These can be activated using: ```sh nix run .#activate $USER@$HOSTNAME ``` This will activate the home-manager configuration for the specified host over SSH (see below). ## Remote Activation {#remote} `nixos-unified` acts as a lightweight alternative to the various deployment tools such as `deploy-rs` and `colmena`. The `.#activate` app takes the hostname as an argument and supports remote activation for both system configurations (NixOS/nix-darwin) and home-manager configurations. ### Remote System Activation {#remote-system} For NixOS or nix-darwin configurations, set the `nixos-unified.sshTarget` option in your configuration: ```nix { nixos-unified.sshTarget = "myuser@myhost"; } ``` Then, you will be able to run the following to deploy to `myhost` from any machine: ```sh nix run .#activate myhost ``` ### Remote Home-Manager Activation {#remote-home} For home-manager configurations, remote activation works by specifying the user and hostname: ```sh nix run .#activate myuser@myhost ``` This will: 1. Copy the flake and necessary inputs to the remote host via SSH 2. Run the home-manager activation remotely on the target machine > [!NOTE] > Remote home-manager activation uses the `user@host` format for the SSH connection, where the user is extracted from the configuration name and the host is the target machine. ### Non-goals Remote activation doesn't seek to replace other deployment tools, and as such doesn't provide features like rollbacks. It is meant for simple deployment use cases. > [!NOTE] > It is possible however that `nixos-unified` can grow to support more sophisticated deployment capabilities ================================================ FILE: doc/guide/autowiring.md ================================================ --- order: 5 --- # Autowiring An optional **autowiring** module is provided that will scan the directory structure and wire up the appropriate flake outputs automatically without you having to do it manually. A ready demonstration is available in [nixos-unified-template](https://github.com/juspay/nixos-unified-template) as well as [srid/nixos-config](https://github.com/srid/nixos-config). In the latter, you will notice the following directory structure: ``` ❮ lsd --tree --depth 1 configurations modules overlays packages 📁 configurations ├── 📁 darwin ├── 📁 home └── 📁 nixos 📁 modules ├── 📁 darwin ├── 📁 flake ├── 📁 home └── 📁 nixos 📁 overlays └── ❄️ default.nix 📁 packages ├── ❄️ git-squash.nix ├── ❄️ sshuttle-via.nix └── 📁 twitter-convert ``` Each of these are wired to the corresponding flake output, as indicated in the below table: | Directory | Flake Output | | ----------------------------------------- | ----------------------------------------------------------- | | `configurations/nixos/foo.nix`[^default] | `nixosConfigurations.foo` | | `configurations/darwin/foo.nix`[^default] | `darwinConfigurations.foo` | | `configurations/home/foo.nix`[^default] | `legacyPackages.${system}.homeConfigurations.foo`[^hm-pkgs] | | `modules/nixos/foo.nix` | `nixosModules.foo` | | `modules/darwin/foo.nix` | `darwinModules.foo` | | `modules/flake/foo.nix` | `flakeModules.foo` | | `overlays/foo.nix` | `overlays.foo` | | `packages/foo.nix` | `packages.${system}.foo`[^packages] | ## flake-parts Autowiring is also provided if you use just flake-parts, via the `lib.mkFlake` function. In your top-level flake.nix, you only need to define your `outputs` as follows: ```nix { inputs = ...; outputs = inputs: inputs.nixos-unified.lib.mkFlake { inherit inputs; root = ./.; }; } ``` This will, - Auto-import flake-parts modules under either `./nix/modules/flake` or `./modules/flake` (whichever exists) - Use a sensible default for `systems` which can be overriden. - Pass `root` as top-level module args, as a non-recursive way of referring to the path of the flake (without needing `inputs.self`). See [srid/haskell-template's flake.nix](https://github.com/srid/haskell-template/blob/master/flake.nix) for a ready example. For another example, see [this emanote PR](https://github.com/srid/emanote/pull/558). ## Package Autowiring Example The `packages/` directory allows you to define custom packages that will be automatically wired as flake outputs. Here's an example project structure: ``` ❮ lsd --tree --depth 2 packages 📁 packages ├── ❄️ hello-world.nix └── 📁 complex-app └── ❄️ default.nix ``` Each package file should export a function compatible with `pkgs.callPackage`. Here are two examples: **packages/hello-world.nix** - Simple shell script package: ```nix { lib, writeShellApplication }: writeShellApplication { name = "hello-world"; text = '' echo "Hello from my autowired package!" echo "Args: $*" ''; meta = { description = "A simple hello world script"; license = lib.licenses.mit; }; } ``` **packages/complex-app/default.nix** - Directory-based package: ```nix { lib, stdenv, makeWrapper }: stdenv.mkDerivation { pname = "complex-app"; version = "1.0.0"; src = ./.; nativeBuildInputs = [ makeWrapper ]; installPhase = '' mkdir -p $out/bin cp app.sh $out/bin/complex-app chmod +x $out/bin/complex-app ''; meta = { description = "A more complex application"; license = lib.licenses.gpl3; platforms = lib.platforms.unix; }; } ``` After defining these packages, they become available in your flake outputs: ```bash # Build and run packages nix build .#hello-world nix run .#complex-app # List all autowired packages nix flake show | grep packages ``` The packages will appear as: - `packages.${system}.hello-world` - `packages.${system}.complex-app` [^default]: This path could as well be `configurations/nixos/foo/default.nix`. Likewise for other output types. [^hm-pkgs]: Why `legacyPackages`? Because, creating a home-manager configuration [requires `pkgs`](https://github.com/srid/nixos-unified/blob/47a26bc9118d17500bbe0c4adb5ebc26f776cc36/nix/modules/flake-parts/lib.nix#L97). See [^packages]: Package files should export a function that can be called with `callPackage`. The autowiring system automatically calls `pkgs.callPackage` on each package file, making them available as `packages.${system}.{name}` in your flake outputs. ================================================ FILE: doc/guide/outputs.md ================================================ --- order: 4 --- # Flake Outputs Importing the `nixos-unified` flake-parts module will autowire the following flake outputs in your flake: | Name | Description | | -------------------------------------- | --------------------------------------------------------------------------------------------- | | **`nixos-unified.lib`** | Functions `mkLinuxSystem`, `mkMacosSystem` and `mkHomeConfiguration` | | **`packages.update`** | Flake app to update key flake inputs | | [**`packages.activate`**](activate.md) | Flake app to build & activate the system (locally or remotely over SSH) or home configuration | In addition, all of your NixOS/nix-darwin/home-manager modules implicitly receive the following `specialArgs`: - `flake@{self, inputs, config}` (`config` is from flake-parts) - `rosettaPkgs` (if on darwin) ================================================ FILE: doc/guide/specialArgs.md ================================================ --- order: 3 --- # Module Arguments Each of your NixOS, nix-darwin and home-manager modules implicitly receive a [`specialArgs`](https://nixos.asia/en/nix-modules) called `flake`. The components of this `flake` attrset are: | Name | Description | | ---- | ----------- | | `inputs` | The `inputs` of your flake; `inputs.self` referring to the flake itself | | `config` | The flake-parts perSystem `config` | [Here](https://github.com/srid/nixos-config/blob/a420e5f531172aef753b07a411de8e254207f5c6/modules/darwin/default.nix#L2-L5) is an example of how these can be used: ```nix { flake, pkgs, lib, ... }: let inherit (flake) config inputs; inherit (inputs) self; in { imports = [ # Reference a flake input directly from a nix-darwin module inputs.agenix.darwinModules.default ]; # Reference an arbitrary flake-parts config home-manager.users.${config.me.username} = { }; } ``` While the above example uses a nix-darwin module, you can do the same on NixOS or home-manager modules. ================================================ FILE: doc/guide/templates.md ================================================ --- order: 1 --- # Flake Templates We provide four templates, depending on your needs: ## Available templates You can easily initialize one of our templates using [Omnix](https://omnix.page/om/init.html)[^no-omnix]: [^no-omnix]: If you do not use Omnix, you must use `nix flake init`, and manually change the template values such as username and hostname. ### NixOS only {#nixos} NixOS configuration only, with [home-manager] ```sh nix --accept-flake-config run github:juspay/omnix -- \ init -o ~/nix-config github:srid/nixos-unified#linux ``` ### macOS only {#macos} [nix-darwin] configuration only, with [home-manager] ```sh nix --accept-flake-config run github:juspay/omnix -- \ init -o ~/nix-config github:srid/nixos-unified#macos ``` ### Home only {#home} [home-manager] configuration only (useful if you use other Linux distros or do not have admin access to the machine) ```bash nix --accept-flake-config run github:juspay/omnix -- \ init -o ~/nix-config github:srid/nixos-unified#home ``` ## After initializing the template Run `nix run .#activate` (`nix run .#activate $USER@` if you are using the last template, "Home only") to activate the configuration. - on macOS, if you get an error about `/etc/nix/nix.conf`, run: ```sh sudo mv /etc/nix/nix.conf /etc/nix/nix.conf.before-nix-darwin nix --extra-experimental-features "nix-command flakes" run .#activate ``` - on macOS, if you had used Determinate Systems nix-installer, you may want to [uninstall that Nix](https://github.com/LnL7/nix-darwin/issues/931#issuecomment-2075596824), such that we use the one provided by nix-darwin, ```sh sudo -i nix-env --uninstall nix ``` [^intel]: If you are on an Intel Mac, also change `nixpkgs.hostPlatform` accordingly. [home-manager]: https://github.com/nix-community/home-manager [nix-darwin]: https://github.com/LnL7/nix-darwin ================================================ FILE: doc/guide.md ================================================ # Guide - [[templates]]# - [[activate]]# - [[specialArgs]]# - [[outputs]]# - [[autowiring]]# ================================================ FILE: doc/history.md ================================================ --- order: 100 --- # Release history ## Unreleased - autoWiring of flake outputs & `mkFlake` - activate script - add `--dry-run` (#104) - Remote activation support for home-manager configurations (#143) - home-manager - More unique backup filenames (#97) - Add a default `home.homeDirectory` based on the user's username (#117) - Remove use of deprecated alias `--update-input` of `nix flake update` - Use `sudo` when activating with nix-darwin (#130) - Add an opt-in local NixOS activation mode that runs `nixos-rebuild` itself through `sudo` - Fix `nix copy` command for legacy NixOS systems by adding experimental features flag (#138) - Switch to runCommand since runCommandNoCC is dropped in newer nixpkgs (#147) ## 0.2.0 (2024-10-03) Initial release, branched from `nixos-flake` ================================================ FILE: doc/howto.md ================================================ # HOWTO ## Creating shared configuration {#config} You may want to share certain configuration (such as username or email) across multiple modules. Here is how you can do it: 1. Create a flake-parts module to hold the config schema. For example, [`config-module.nix`](https://github.com/juspay/nixos-unified-template/blob/9eeeb6c1ab4287ac0a37a22e72f053a3de82ddbc/modules/flake/config-module.nix) 1. Define your configuration in your config file. For example, [`config.nix`](https://github.com/juspay/nixos-unified-template/blob/9eeeb6c1ab4287ac0a37a22e72f053a3de82ddbc/modules/flake/config.nix). 1. Use your configuration from any of NixOS/ nix-darwin/ home-manager modules through `flake` [[specialArgs|specialArgs]], specifically `flake.config`. For example, see [here](https://github.com/juspay/nixos-unified-template/blob/9eeeb6c1ab4287ac0a37a22e72f053a3de82ddbc/modules/home/git.nix#L3). ================================================ FILE: doc/index.md ================================================ --- short-title: nixos-unified template: sidebar: collapsed: true emanote: folder-folgezettel: false --- # nixos-unified [**nixos-unified**](https://github.com/srid/nixos-unified) is a [flake-parts](https://flake.parts/) module to unify [NixOS] + [nix-darwin] + [home-manager] configuration in a single flake, while providing a consistent interface at DX and UX level. [NixOS]: https://nixos.org/ [nix-darwin]: https://github.com/LnL7/nix-darwin [home-manager]: https://github.com/nix-community/home-manager ## Why? nixos-unified provides the following features: - **One-click activation & deployment** - [[activate]]: An `.#activate` flake app that works uniformly on [NixOS], [nix-darwin] and [home-manager]. - [[activate#remote|Remote Activation]]: `.#activate` can also *remotely* activate machines (be it macOS or NixOS) over SSH, thus acting as a simple alternative to deployment tools like `deploy-rs` and `colmena`. - Also: an `.#update` flake app to update the primary inputs (which can be overriden) - **Seamless access to top-level flake** - All [NixOS]/ [nix-darwin]/ [home-manager] modules receive [[specialArgs|specialArgs]] which includes all the information in the top-level flake. - This enables those modules to be aware of the flake inputs, for instance. - **Sensible defaults** - Sensible defaults for [home-manager]/ [nix-darwin]/ and [NixOS] configurations ([\#75](https://github.com/srid/nixos-unified/pull/75)). - **Autowiring** of flake outputs - [[autowiring]]: An optional module that will scan the directory structure and wire up the appropriate flake outputs automatically without you having to do it manually. ## Getting Started See: [[start]]. ================================================ FILE: doc/index.yaml ================================================ # Emanote configuration for nixos-unified documentation # Ref: https://github.com/srid/emanote/blob/master/emanote/default/index.yaml template: editBaseUrl: https://github.com/srid/nixos-unified/edit/master/doc # List of available colors: https://tailwindcss.com/docs/customizing-colors#default-color-palette theme: lime sidebar: collapsed: false urlStrategy: pretty page: siteTitle: "nixos-unified" siteUrl: https://nixos-unified.org ================================================ FILE: doc/mod.just ================================================ default: @just --list doc # Run mdbook live server run: nix run # Build the static site build: nix build ================================================ FILE: doc/start.md ================================================ --- order: -100 --- # Getting Started Pick your desired operating system and follow the below instructions. > [!TIP] > Checkout [nixos-unified-template](https://github.com/juspay/nixos-unified-template) for the quickest way to get started. ## NixOS 1. [Install NixOS w/ Flakes enabled](https://nixos.asia/en/nixos-tutorial) 1. Convert your `flake.nix` to using `nixos-unified` using the [NixOS only template](guide/templates.md) as reference. ## non-NixOS Linux If you use other Linux distros like Ubuntu, you may use just `home-manager`. 1. [Install Nix](https://nixos.asia/en/install) 1. Use the [HOME only template](guide/templates.md) ## macOS 1. [Install Nix](https://nixos.asia/en/install) 1. Use the [macOS only template](guide/templates.md) ================================================ FILE: examples/home/flake.nix ================================================ { inputs = { # Principle inputs (updated by `nix run .#update`) nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; home-manager.url = "github:nix-community/home-manager"; home-manager.inputs.nixpkgs.follows = "nixpkgs"; flake-parts.url = "github:hercules-ci/flake-parts"; nixos-unified.url = "github:srid/nixos-unified"; }; outputs = inputs@{ self, ... }: inputs.flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; imports = [ inputs.nixos-unified.flakeModules.default ]; perSystem = { pkgs, ... }: let myUserName = "john"; in { legacyPackages.homeConfigurations.${myUserName} = self.nixos-unified.lib.mkHomeConfiguration pkgs ({ pkgs, ... }: { imports = [ self.homeModules.default ]; home.username = myUserName; home.stateVersion = "24.11"; }); }; flake = { # All home-manager configurations are kept here. homeModules.default = { pkgs, ... }: { imports = [ ]; programs = { git.enable = true; starship.enable = true; bash.enable = true; }; }; }; }; } ================================================ FILE: examples/linux/flake.nix ================================================ { inputs = { # Principle inputs (updated by `nix run .#update`) nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; home-manager.url = "github:nix-community/home-manager"; home-manager.inputs.nixpkgs.follows = "nixpkgs"; flake-parts.url = "github:hercules-ci/flake-parts"; nixos-unified.url = "github:srid/nixos-unified"; }; outputs = inputs@{ self, ... }: inputs.flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" "aarch64-linux" ]; imports = [ inputs.nixos-unified.flakeModules.default ]; flake = let myUserName = "john"; in { # Configurations for Linux (NixOS) machines nixosConfigurations."example1" = self.nixos-unified.lib.mkLinuxSystem { home-manager = true; } { nixpkgs.hostPlatform = "x86_64-linux"; imports = [ # Your machine's configuration.nix goes here ({ pkgs, ... }: { # TODO: Put your /etc/nixos/hardware-configuration.nix here boot.loader.grub.device = "nodev"; fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "btrfs"; }; users.users.${myUserName}.isNormalUser = true; system.stateVersion = "23.05"; }) # Setup home-manager in NixOS config { home-manager.users.${myUserName} = { imports = [ self.homeModules.default ]; home.stateVersion = "24.11"; }; } ]; }; # home-manager configuration goes here. homeModules.default = { pkgs, ... }: { imports = [ ]; programs.git.enable = true; programs.starship.enable = true; programs.bash.enable = true; }; }; }; } ================================================ FILE: examples/macos/flake.nix ================================================ { inputs = { # Principle inputs (updated by `nix run .#update`) nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nix-darwin.url = "github:lnl7/nix-darwin/master"; nix-darwin.inputs.nixpkgs.follows = "nixpkgs"; home-manager.url = "github:nix-community/home-manager"; home-manager.inputs.nixpkgs.follows = "nixpkgs"; flake-parts.url = "github:hercules-ci/flake-parts"; nixos-unified.url = "github:srid/nixos-unified"; }; outputs = inputs@{ self, ... }: inputs.flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "aarch64-darwin" "x86_64-darwin" ]; imports = [ inputs.nixos-unified.flakeModules.default ]; flake = let myUserName = "john"; in { # Configurations for macOS machines darwinConfigurations."example1" = self.nixos-unified.lib.mkMacosSystem { home-manager = true; } { nixpkgs.hostPlatform = "aarch64-darwin"; imports = [ # Your nix-darwin configuration goes here ({ pkgs, ... }: { # https://github.com/nix-community/home-manager/issues/4026#issuecomment-1565487545 users.users.${myUserName}.home = "/Users/${myUserName}"; security.pam.services.sudo_local.touchIdAuth = true; # Used for backwards compatibility, please read the changelog before changing. # $ darwin-rebuild changelog system.stateVersion = 6; }) # Setup home-manager in nix-darwin config { home-manager.users.${myUserName} = { imports = [ self.homeModules.default ]; home.stateVersion = "24.11"; }; } ]; }; # home-manager configuration goes here. homeModules.default = { pkgs, ... }: { imports = [ ]; programs.git.enable = true; programs.starship.enable = true; programs.zsh.enable = true; }; }; }; } ================================================ FILE: flake.nix ================================================ { outputs = _: rec { flakeModules = { default = ./nix/modules/flake-parts; autoWire = ./nix/modules/flake-parts/autowire.nix; }; # For backwards compat only flakeModule = flakeModules.default; # Like flake-parts mkFlake, but auto-imports modules/flake-parts, consistent with autowiring feature. # # Looks under either nix/modules/flake-parts or modules/flake-parts for modules to import. `systems` is set to a default value. `root` is passed as top-level module args (as distinct from `inputs.self` the use of which can lead to infinite recursion). lib.mkFlake = { inputs , root , systems ? [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ] , specialArgs ? { } }: inputs.flake-parts.lib.mkFlake { inherit inputs specialArgs; } { inherit systems; _module.args = { inherit root; }; imports = let # Patterns to search in order candidates = [ # These correspond to `flakeModules.*` "nix/modules/flake" "modules/flake" # Just for backwards compatbility "nix/modules/flake-parts" "modules/flake-parts" ]; getModulesUnderFirst = cs: with builtins; if cs == [ ] then throw "None of these paths exist: ${toString candidates}" else if pathExists "${root}/${head cs}" then map (fn: "${root}/${head cs}/${fn}") (attrNames (readDir (root + /${head cs}))) else getModulesUnderFirst (tail cs); in getModulesUnderFirst candidates; }; templates = let tmplPath = path: builtins.path { inherit path; filter = path: _: baseNameOf path != "test.sh"; }; in { linux = { description = "nixos-unified template for NixOS configuration.nix"; path = tmplPath ./examples/linux; }; macos = { description = "nixos-unified template for nix-darwin configuration"; path = tmplPath ./examples/macos; }; home = { description = "nixos-unified template for home-manager configuration"; path = tmplPath ./examples/home; }; }; om = { templates = rec { home = { template = templates.home; params = [ { name = "username"; description = "The $USER to apply home-manager configuration on"; placeholder = "john"; } ]; }; macos = { template = templates.macos; params = home.params ++ [ { name = "hostname"; description = "Hostname of the machine"; placeholder = "example1"; } ]; }; linux = { template = templates.linux; inherit (macos) params; }; }; }; }; } ================================================ FILE: justfile ================================================ # Documentation targets mod doc default: @just --list # Run CI locally ci: om ci --extra-access-tokens "github.com=$(gh auth token)" # Auto-format the Nix files in project tree fmt: treefmt ================================================ FILE: nix/modules/configurations/default.nix ================================================ # A NixOS/nix-darwin module to specify nixos-unified metadata for configurations. # # FIXME: Using this module in home-manager leads to `error: infinite recursion # encountered` on `id = x: x` { flake, config, lib, ... }: let inherit (flake) inputs; in { options = { nixos-unified = { sshTarget = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = '' SSH target for this system configuration. ''; }; overrideInputs = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = '' List of flake inputs to override when deploying or activating. ''; }; localPrivilegeMode = lib.mkOption { type = lib.types.enum [ "nixos-rebuild-sudo" "sudo-nixos-rebuild" ]; default = "nixos-rebuild-sudo"; description = '' How local NixOS activation obtains root privileges. `nixos-rebuild-sudo` runs `nixos-rebuild` as the calling user with `--sudo`, letting nixos-rebuild invoke sudo for privileged steps. `sudo-nixos-rebuild` runs `nixos-rebuild` itself via sudo. This is useful when sudoers should allow passwordless activation by matching a single `nixos-rebuild` command. ''; }; outputs = { system = lib.mkOption { type = lib.types.str; readOnly = true; default = config.nixpkgs.hostPlatform.system; description = '' System to activate. ''; }; overrideInputs = lib.mkOption { type = lib.types.attrsOf lib.types.path; readOnly = true; default = lib.foldl' (acc: x: acc // { "${x}" = inputs.${x}; }) { } config.nixos-unified.overrideInputs; }; nixArgs = lib.mkOption { type = lib.types.listOf lib.types.str; readOnly = true; default = (builtins.concatMap (name: [ "--override-input" "${name}" "${inputs.${name}}" ]) # TODO: Use `outputs.overrideInputs` instead. config.nixos-unified.overrideInputs); description = '' Arguments to pass to `nix` ''; }; }; }; }; } ================================================ FILE: nix/modules/flake-parts/autowire.nix ================================================ { self, lib, ... }: { config = let # Combine mapAttrs' and filterAttrs # # f can return null if the attribute should be filtered out. mapAttrsMaybe = f: attrs: lib.pipe attrs [ (lib.mapAttrsToList f) (builtins.filter (x: x != null)) builtins.listToAttrs ]; forAllNixFiles = dir: f: if builtins.pathExists dir then lib.pipe dir [ builtins.readDir (mapAttrsMaybe (fn: type: if type == "regular" then let name = lib.removeSuffix ".nix" fn; in if name != fn then lib.nameValuePair name (f "${dir}/${fn}") else null else if type == "directory" && builtins.pathExists "${dir}/${fn}/default.nix" then lib.nameValuePair fn (f "${dir}/${fn}") else null )) ] else { }; in { flake = { darwinConfigurations = forAllNixFiles "${self}/configurations/darwin" (fn: self.nixos-unified.lib.mkMacosSystem { home-manager = true; } fn); nixosConfigurations = forAllNixFiles "${self}/configurations/nixos" (fn: self.nixos-unified.lib.mkLinuxSystem { home-manager = true; } fn); darwinModules = forAllNixFiles "${self}/modules/darwin" (fn: fn); nixosModules = forAllNixFiles "${self}/modules/nixos" (fn: fn); homeModules = forAllNixFiles "${self}/modules/home" (fn: fn); overlays = forAllNixFiles "${self}/overlays" (fn: import fn self.nixos-unified.lib.specialArgsFor.common); }; perSystem = { pkgs, ... }: { legacyPackages.homeConfigurations = forAllNixFiles "${self}/configurations/home" (fn: self.nixos-unified.lib.mkHomeConfiguration pkgs fn); packages = forAllNixFiles "${self}/packages" (fn: pkgs.callPackage fn { }); }; }; } ================================================ FILE: nix/modules/flake-parts/default.nix ================================================ { imports = [ ./packages.nix ./lib.nix ]; } ================================================ FILE: nix/modules/flake-parts/lib.nix ================================================ { self, inputs, config, lib, ... }: let specialArgsFor = rec { common = { flake = { inherit self inputs config; }; }; nixos = common; darwin = common // { rosettaPkgs = import inputs.nixpkgs { system = "x86_64-darwin"; }; }; }; nixosModules = { # Linux home-manager module home-manager = { imports = [ inputs.home-manager.nixosModules.home-manager { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.extraSpecialArgs = specialArgsFor.nixos; home-manager.sharedModules = [ homeModules.common ]; } ]; }; # Common and useful setting across all platforms common = { lib, ... }: { nix = { settings = { # Use all CPU cores max-jobs = lib.mkDefault "auto"; # Duh experimental-features = lib.mkDefault "nix-command flakes"; }; }; }; }; homeModules = { common = { config, pkgs, ... }: { # Sensible default for `home.homeDirectory` home.homeDirectory = lib.mkDefault "/${if pkgs.stdenv.isDarwin then "Users" else "home"}/${config.home.username}"; # For macOS, $PATH must contain these. home.sessionPath = lib.mkIf pkgs.stdenv.isDarwin [ "/etc/profiles/per-user/$USER/bin" # To access home-manager binaries "/nix/var/nix/profiles/system/sw/bin" # To access nix-darwin binaries "/usr/local/bin" # Some macOS GUI programs install here ]; }; }; darwinModules = { # macOS home-manager module home-manager = { imports = [ inputs.home-manager.darwinModules.home-manager { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.extraSpecialArgs = specialArgsFor.darwin; home-manager.sharedModules = [ homeModules.common ]; } ]; }; }; in { config = { flake = { nixos-unified.lib = { inherit specialArgsFor; mkLinuxSystem = { home-manager ? false }: mod: inputs.nixpkgs.lib.nixosSystem { # Arguments to pass to all modules. specialArgs = specialArgsFor.nixos; modules = [ ../configurations nixosModules.common mod ] ++ lib.optional home-manager nixosModules.home-manager; }; mkMacosSystem = { home-manager ? false }: mod: inputs.nix-darwin.lib.darwinSystem { specialArgs = specialArgsFor.darwin; modules = [ ../configurations nixosModules.common mod ] ++ lib.optional home-manager darwinModules.home-manager; }; mkHomeConfiguration = pkgs: mod: inputs.home-manager.lib.homeManagerConfiguration { inherit pkgs; # cf. https://github.com/nix-community/home-manager/issues/3075 extraSpecialArgs = specialArgsFor.common; modules = [ homeModules.common mod ]; }; }; }; }; } ================================================ FILE: nix/modules/flake-parts/packages.nix ================================================ { self, flake-parts-lib, lib, ... }: let inherit (flake-parts-lib) mkPerSystemOption; inherit (lib) types; in { options.perSystem = mkPerSystemOption ({ config, inputs', pkgs, system, ... }: { options.nixos-unified = lib.mkOption { default = { }; type = types.submodule { options = { primary-inputs = lib.mkOption { type = types.listOf types.str; default = [ "nixpkgs" "home-manager" "nix-darwin" ]; description = '' List of flake inputs to update when running `nix run .#update`. ''; }; }; }; }; config.packages = lib.filterAttrs (_: v: v != null) { update = let inputs = config.nixos-unified.primary-inputs; in pkgs.writeShellApplication { name = "update-main-flake-inputs"; meta.description = "Update the primary flake inputs"; text = '' nix flake update${lib.foldl' (acc: x: acc + " " + x) "" inputs} ''; }; # Activate the given (system or home) configuration activate = import ../../../activate { inherit self inputs' pkgs lib system; }; }; }); } ================================================ FILE: vira.hs ================================================ -- CI configuration \ctx pipeline -> let isMaster = ctx.branch == "master" nu = [("nixos-unified", ".")] in pipeline { build.systems = [ "x86_64-linux" , "aarch64-darwin" ] , build.flakes = [ "./doc" { overrideInputs = nu } , "./examples/macos" { overrideInputs = nu } , "./examples/home" { overrideInputs = nu } , "./examples/linux" { overrideInputs = nu } ] , signoff.enable = True , cache.url = if isMaster then Just "https://cache.nixos.asia/oss" else Nothing }