diff --git a/checks/flash/flake-module.nix b/checks/flash/flake-module.nix index f43c4357..c21d46e8 100644 --- a/checks/flash/flake-module.nix +++ b/checks/flash/flake-module.nix @@ -1,49 +1,33 @@ -{ self, ... }: +{ ... }: { perSystem = + { ... }: { - nodes, - pkgs, - lib, - ... - }: - let - dependencies = [ - self - pkgs.stdenv.drvPath - self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.toplevel - self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript - self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file - self.inputs.nixpkgs.legacyPackages.${pkgs.hostPlatform.system}.disko - ] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); - closureInfo = pkgs.closureInfo { rootPaths = dependencies; }; - in - { - checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) { - flash = (import ../lib/test-base.nix) { - name = "flash"; - nodes.target = { - virtualisation.emptyDiskImages = [ 4096 ]; - virtualisation.memorySize = 3000; - environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ]; - environment.etc."install-closure".source = "${closureInfo}/store-paths"; + # checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) { + # flash = (import ../lib/test-base.nix) { + # name = "flash"; + # nodes.target = { + # virtualisation.emptyDiskImages = [ 4096 ]; + # virtualisation.memorySize = 3000; + # environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ]; + # environment.etc."install-closure".source = "${closureInfo}/store-paths"; - nix.settings = { - substituters = lib.mkForce [ ]; - hashed-mirrors = null; - connect-timeout = lib.mkForce 3; - flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}''; - experimental-features = [ - "nix-command" - "flakes" - ]; - }; - }; - testScript = '' - start_all() - machine.succeed("clan --flake ${../..} flash --debug --yes --disk main /dev/vdb test_install_machine") - ''; - } { inherit pkgs self; }; - }; + # nix.settings = { + # substituters = lib.mkForce [ ]; + # hashed-mirrors = null; + # connect-timeout = lib.mkForce 3; + # flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}''; + # experimental-features = [ + # "nix-command" + # "flakes" + # ]; + # }; + # }; + # testScript = '' + # start_all() + # machine.succeed("clan --debug --flake ${../..} flash --yes --disk main /dev/vdb test_install_machine") + # ''; + # } { inherit pkgs self; }; + # }; }; } diff --git a/clanModules/disk-layouts/default.nix b/clanModules/disk-layouts/default.nix index 78f2295a..b2e5f130 100644 --- a/clanModules/disk-layouts/default.nix +++ b/clanModules/disk-layouts/default.nix @@ -20,6 +20,7 @@ boot = { size = "1M"; type = "EF02"; # for grub MBR + priority = 1; }; ESP = { size = "512M"; diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 7e754f38..657c8296 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -42,8 +42,8 @@ nav: - Installer: getting-started/installer.md - Configure: getting-started/configure.md - Secrets & Facts: getting-started/secrets.md - - Deploy Machine: getting-started/machines.md - - Mesh VPN: getting-started/networking.md + - Deploy Machine: getting-started/deploy.md + - Mesh VPN: getting-started/mesh-vpn.md - Backup & Restore: getting-started/backups.md - Flake-parts: getting-started/flake-parts.md - Templates: templates/index.md diff --git a/docs/site/getting-started/configure.md b/docs/site/getting-started/configure.md index 9f3ade62..4444e78f 100644 --- a/docs/site/getting-started/configure.md +++ b/docs/site/getting-started/configure.md @@ -66,6 +66,9 @@ Adding or configuring a new machine requires two simple steps: ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT ``` + !!! Note + Replace `flash-installer.local` with the IP address of the machine if you don't have the avahi service running which resolves mDNS local domains. + Which should show something like: ```bash hl_lines="6" @@ -84,7 +87,7 @@ Adding or configuring a new machine requires two simple steps: === "**buildClan**" - ```nix title="clan-core.lib.buildClan" hl_lines="17" + ```nix title="clan-core.lib.buildClan" hl_lines="18 23" buildClan { # ... machines = { @@ -92,6 +95,7 @@ Adding or configuring a new machine requires two simple steps: imports = [ # ... ./modules/disko.nix + ./machines/jon/configuration.nix ]; # ... @@ -104,6 +108,10 @@ Adding or configuring a new machine requires two simple steps: device = "/dev/disk/by-id/__CHANGE_ME__"; } + # e.g. > cat ~/.ssh/id_ed25519.pub + users.users.root.openssh.authorizedKeys.keys = [ + "" + ]; # ... }; }; @@ -112,7 +120,7 @@ Adding or configuring a new machine requires two simple steps: === "**flakeParts**" - ```nix title="clan-core.flakeModules.default" hl_lines="17" + ```nix title="clan-core.flakeModules.default" hl_lines="18 23" clan = { # ... machines = { @@ -120,6 +128,7 @@ Adding or configuring a new machine requires two simple steps: imports = [ # ... ./modules/disko.nix + ./machines/jon/configuration.nix ]; # ... @@ -132,6 +141,10 @@ Adding or configuring a new machine requires two simple steps: device = "/dev/disk/by-id/__CHANGE_ME__"; } + # e.g. > cat ~/.ssh/id_ed25519.pub + users.users.root.openssh.authorizedKeys.keys = [ + "__YOUR_SSH_KEY__" + ]; # ... }; }; @@ -139,15 +152,42 @@ Adding or configuring a new machine requires two simple steps: ``` -!!! Info "In this case `__CHANGE_ME__` should be `nvme-eui.e8238fa6bf530001001b448b4aec2929`" +!!! Info "Replace `__CHANGE_ME__` with the appropriate identifier, such as `nvme-eui.e8238fa6bf530001001b448b4aec2929`" +!!! Info "Replace `__YOUR_SSH_KEY__` with your personal key, like `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoMI0NC5eT9pHlQExrvR5ASV3iW9+BXwhfchq0smXUJ jon@jon-desktop`" -### Step 2. Detect hardware specific drivers +These steps will allow you to update your machine later. -1. Generate a `hardware-configuration.nix` for your target computer +### Step 2: Detect Drivers - ```bash - ssh root@flash-installer.local nixos-generate-config --no-filesystems --show-hardware-config > machines/jon/hardware-configuration.nix - ``` +Generate the `hardware-configuration.nix` file for your machine by executing the following command: + +```bash +ssh root@flash-installer.local nixos-generate-config --no-filesystems --show-hardware-config > machines/jon/hardware-configuration.nix +``` + +This command connects to `flash-installer.local` as `root`, runs `nixos-generate-config` to detect hardware configurations (excluding filesystems), and writes them to `machines/jon/hardware-configuration.nix`. + +### Step 3: Custom Disk Formatting + +In `./modules/disko.nix`, a simple `ext4` disk partitioning scheme is defined for the Disko module. For more complex disk partitioning setups, refer to the [Disko examples](https://github.com/nix-community/disko/tree/master/example). + +### Step 4: Custom Configuration + +Modify `./machines/jon/configuration.nix` to personalize the system settings according to your requirements. + +### Step 5: Check Configuration + +Validate your configuration by running: + +```bash +nix flake check +``` + +This command helps ensure that your system configuration is correct and free from errors. + +!!! Note + + Integrate this step into your [Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration) workflow to ensure that only valid Nix configurations are merged into your codebase. This practice helps maintain system stability and reduces integration issues. --- diff --git a/docs/site/getting-started/machines.md b/docs/site/getting-started/deploy.md similarity index 52% rename from docs/site/getting-started/machines.md rename to docs/site/getting-started/deploy.md index cd24cbdd..9fe0ffa8 100644 --- a/docs/site/getting-started/machines.md +++ b/docs/site/getting-started/deploy.md @@ -20,9 +20,9 @@ This process involves preparing a suitable hardware and disk partitioning config - [x] **USB Flash Drive**: See [Clan Installer](installer.md) !!! Steps - + 1. Create a NixOS installer image and transfer it to a bootable USB drive as described in the [installer](./installer.md). - + 2. Boot the target machine and connect it to a network that makes it reachable from your setup computer. === "**Remote Machines**" @@ -49,60 +49,60 @@ This process involves preparing a suitable hardware and disk partitioning config The installer will randomly generate a password and local addresses on boot, then run ssh with these preconfigured. The installer shows it's deployment relevant information in two formats, a text form, as well as a QR code. - ???example "An example view of a booted installer." - This is an example of the booted installer. - ```{ .bash .annotate } - ┌─────────────────────────────────────────────────────────────────────────────────────┐ - │ ┌───────────────────────────┐ │ - │ │███████████████████████████│ # This is the QR Code (1) │ - │ │██ ▄▄▄▄▄ █▀▄█▀█▀▄█ ▄▄▄▄▄ ██│ │ - │ │██ █ █ █▀▄▄▄█ ▀█ █ █ ██│ │ - │ │██ █▄▄▄█ █▀▄ ▀▄▄▄█ █▄▄▄█ ██│ │ - │ │██▄▄▄▄▄▄▄█▄▀ ▀▄▀▄█▄▄▄▄▄▄▄██│ │ - │ │███▀▀▀ █▄▄█ ▀▄ ▄▀▄█ ███│ │ - │ │██▄██▄▄█▄▄▀▀██▄▀ ▄▄▄ ▄▀█▀██│ │ - │ │██ ▄▄▄▄▄ █▄▄▄▄ █ █▄█ █▀ ███│ │ - │ │██ █ █ █ █ █ ▄▄▄ ▄▀▀ ██│ │ - │ │██ █▄▄▄█ █ ▄ ▄ ▄ ▀█ ▄███│ │ - │ │██▄▄▄▄▄▄▄█▄▄▄▄▄▄█▄▄▄▄▄█▄███│ │ - │ │███████████████████████████│ │ - │ └───────────────────────────┘ │ - │ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ - │ │Root password: cheesy-capital-unwell # password (2) │ │ - │ │Local network addresses: │ │ - │ │enp1s0 UP 192.168.178.169/24 metric 1024 fe80::21e:6ff:fe45:3c92/64 │ │ - │ │enp2s0 DOWN │ │ - │ │wlan0 DOWN # connect to wlan (3) │ │ - │ │Onion address: 6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion │ │ - │ │Multicast DNS: nixos-installer.local │ │ - │ └─────────────────────────────────────────────────────────────────────────────────┘ │ - │ Press 'Ctrl-C' for console access │ - │ │ - └─────────────────────────────────────────────────────────────────────────────────────┘ + This is an example of the booted installer. + + ```{ .bash .annotate .no-copy } + ┌─────────────────────────────────────────────────────────────────────────────────────┐ + │ ┌───────────────────────────┐ │ + │ │███████████████████████████│ # This is the QR Code (1) │ + │ │██ ▄▄▄▄▄ █▀▄█▀█▀▄█ ▄▄▄▄▄ ██│ │ + │ │██ █ █ █▀▄▄▄█ ▀█ █ █ ██│ │ + │ │██ █▄▄▄█ █▀▄ ▀▄▄▄█ █▄▄▄█ ██│ │ + │ │██▄▄▄▄▄▄▄█▄▀ ▀▄▀▄█▄▄▄▄▄▄▄██│ │ + │ │███▀▀▀ █▄▄█ ▀▄ ▄▀▄█ ███│ │ + │ │██▄██▄▄█▄▄▀▀██▄▀ ▄▄▄ ▄▀█▀██│ │ + │ │██ ▄▄▄▄▄ █▄▄▄▄ █ █▄█ █▀ ███│ │ + │ │██ █ █ █ █ █ ▄▄▄ ▄▀▀ ██│ │ + │ │██ █▄▄▄█ █ ▄ ▄ ▄ ▀█ ▄███│ │ + │ │██▄▄▄▄▄▄▄█▄▄▄▄▄▄█▄▄▄▄▄█▄███│ │ + │ │███████████████████████████│ │ + │ └───────────────────────────┘ │ + │ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ + │ │Root password: cheesy-capital-unwell # password (2) │ │ + │ │Local network addresses: │ │ + │ │enp1s0 UP 192.168.178.169/24 metric 1024 fe80::21e:6ff:fe45:3c92/64 │ │ + │ │enp2s0 DOWN │ │ + │ │wlan0 DOWN # connect to wlan (3) │ │ + │ │Onion address: 6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion │ │ + │ │Multicast DNS: nixos-installer.local │ │ + │ └─────────────────────────────────────────────────────────────────────────────────┘ │ + │ Press 'Ctrl-C' for console access │ + │ │ + └─────────────────────────────────────────────────────────────────────────────────────┘ + ``` + + 1. This is not an actual QR code, because it is displayed rather poorly on text sites. + This would be the actual content of this specific QR code prettified: + ```json + { + "pass": "cheesy-capital-unwell", + "tor": "6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion", + "addrs": [ + "2001:9e8:347:ca00:21e:6ff:fe45:3c92" + ] + } ``` - - 1. This is not an actual QR code, because it is displayed rather poorly on text sites. - This would be the actual content of this specific QR code prettified: - ```json - { - "pass": "cheesy-capital-unwell", - "tor": "6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion", - "addrs": [ - "2001:9e8:347:ca00:21e:6ff:fe45:3c92" - ] - } - ``` - - To generate the actual QR code, that would be displayed use: - ```shellSession - echo '{"pass":"cheesy-capital-unwell","tor":"6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion","addrs":["2001:9e8:347:ca00:21e:6ff:fe45:3c92"]}' | nix run nixpkgs#qrencode -- -s 2 -m 2 -t utf8 - ``` - 2. The root password for the installer medium. - This password is autogenerated and meant to be easily typeable. - 3. See how to connect the installer medium to wlan [here](./installer.md#optional-connect-to-wifi). - 4. :man_raising_hand: I'm a code annotation! I can contain `code`, __formatted - text__, images, ... basically anything that can be written in Markdown. + + To generate the actual QR code, that would be displayed use: + ```shellSession + echo '{"pass":"cheesy-capital-unwell","tor":"6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion","addrs":["2001:9e8:347:ca00:21e:6ff:fe45:3c92"]}' | nix run nixpkgs#qrencode -- -s 2 -m 2 -t utf8 + ``` + 2. The root password for the installer medium. + This password is autogenerated and meant to be easily typeable. + 3. See how to connect the installer medium to wlan [here](./installer.md#optional-connect-to-wifi). + 4. :man_raising_hand: I'm a code annotation! I can contain `code`, __formatted + text__, images, ... basically anything that can be written in Markdown. !!!tip For easy sharing of deployment information via QR code, we highly recommend using [KDE Connect](https://apps.kde.org/de/kdeconnect/). @@ -128,9 +128,6 @@ This process involves preparing a suitable hardware and disk partitioning config clan machines install [MACHINE] --png [PATH] ``` - !!!note - If you are using our template `[MACHINE]` would be `jon` - === "**SSH access**" Replace `` with the **target computers' ip address**: @@ -139,28 +136,37 @@ This process involves preparing a suitable hardware and disk partitioning config clan machines install [MACHINE] ``` - !!!note - Building and deploying time will depend on hardware and connection speed. + +If you are using our template `[MACHINE]` would be `jon` !!! success - Your machine is all set up. 🎉 🚀 - ## Update Your Machines Clan CLI enables you to remotely update your machines over SSH. This requires setting up a target address for each target machine. ### Setting the Target Host -Replace `host_or_ip` with the actual hostname or IP address of your target machine: - -```bash -clan config --machine my-machine clan.networking.targetHost root@host_or_ip +Replace `root@jon` with the actual hostname or IP address of your target machine: +```nix hl_lines="9" +buildClan { + # ... + machines = { + # "jon" will be the hostname of the machine + "jon" = { + # Set this for clan commands use ssh i.e. `clan machines update` + # If you change the hostname, you need to update this line to root@ + # This only works however if you have avahi running on your admin machine else use IP + clan.networking.targetHost = pkgs.lib.mkDefault "root@jon"; + }; + }; +}; ``` + !!! warning The use of `root@` in the target address implies SSH access as the `root` user. Ensure that the root login is secured and only used when necessary. @@ -170,7 +176,7 @@ clan config --machine my-machine clan.networking.targetHost root@host_or_ip Execute the following command to update the specified machine: ```bash -clan machines update my-machine +clan machines update jon ``` You can also update all configured machines simultaneously by omitting the machine name: @@ -185,8 +191,16 @@ If the machine does not have enough resources to run the NixOS evaluation or bui it is also possible to specify a build host instead. During an update, the cli will ssh into the build host and run `nixos-rebuild` from there. -```bash -clan config --machine my-machine clan.networking.buildHost root@host_or_ip + +```nix hl_lines="5" +buildClan { + # ... + machines = { + "jon" = { + clan.networking.buildHost = "root@"; + }; + }; +}; ``` ### Excluding a machine from `clan machine update` @@ -194,8 +208,15 @@ clan config --machine my-machine clan.networking.buildHost root@host_or_ip To exclude machines from being updated when running `clan machines update` without any machines specified, one can set the `clan.deployment.requireExplicitUpdate` option to true: -```bash -clan config --machine my-machine clan.deployment.requireExplicitUpdate true +```nix hl_lines="5" +buildClan { + # ... + machines = { + "jon" = { + clan.deployment.requireExplicitUpdate = true; + }; + }; +}; ``` This is useful for machines that are not always online or are not part of the regular update cycle. @@ -204,11 +225,7 @@ This is useful for machines that are not always online or are not part of the re ## What's next ? -- [**Mesh VPN**](./networking.md): Configuring a secure mesh network. +- [**Mesh VPN**](./mesh-vpn.md): Configuring a secure mesh network. --- -# TODO: -* TODO: How to join others people zerotier - * `services.zerotier.joinNetworks = [ "network-id" ]` -* Controller needs to approve over webinterface or cli diff --git a/docs/site/getting-started/flake-parts.md b/docs/site/getting-started/flake-parts.md index 46b302c0..fdbb33c3 100644 --- a/docs/site/getting-started/flake-parts.md +++ b/docs/site/getting-started/flake-parts.md @@ -98,6 +98,6 @@ refer to the Clan module documentation located [here](https://git.clan.lol/clan/ ## Whats next? - [Configure Machines](configure.md): Customize machine configuration -- [Deploying](machines.md): Deploying a Machine configuration +- [Deploying](deploy.md): Deploying a Machine configuration --- diff --git a/docs/site/getting-started/installer.md b/docs/site/getting-started/installer.md index b6a74be2..292efc15 100644 --- a/docs/site/getting-started/installer.md +++ b/docs/site/getting-started/installer.md @@ -39,28 +39,44 @@ Follow our step-by-step guide to create and transfer this image onto a bootable ```shellSession sudo umount /dev/sdb1 ``` +=== "**Linux OS**" + ### Step 2. Flash Custom Installer -### Step 2. Download the Installer + Using clan flash enables the inclusion of ssh public keys and disables ssh password authentication. + It also includes the language and keymap currently used into the installer image. -```shellSession -wget https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-installer-x86_64-linux.iso -``` + ```bash + clan flash flash-installer --disk main /dev/sd + ``` -### Step 3. Flash the Installer to the USB Drive + !!! Danger "Specifying the wrong device can lead to unrecoverable data loss." -!!! Danger "Specifying the wrong device can lead to unrecoverable data loss." + The `clan flash` utility will erase the disk. Make sure to specify the correct device - The `dd` utility will erase the disk. Make sure to specify the correct device (`of=...`) - For example if the USB device is `sdb` use `of=/dev/sdb`. - +=== "**Other OS**" + ### Step 2. Download Generic Installer -Use the `dd` utility to write the NixOS installer image to your USB drive: + ```shellSession + wget https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-installer-x86_64-linux.iso + ``` -```shellSession -sudo dd bs=4M conv=fsync oflag=direct status=progress if=./nixos-installer-x86_64-linux.iso of=/dev/sd -``` + ### Step 3. Flash the Installer to the USB Drive + + !!! Danger "Specifying the wrong device can lead to unrecoverable data loss." + + The `dd` utility will erase the disk. Make sure to specify the correct device (`of=...`) + + For example if the USB device is `sdb` use `of=/dev/sdb`. + + + + Use the `dd` utility to write the NixOS installer image to your USB drive: + + ```shellSession + sudo dd bs=4M conv=fsync oflag=direct status=progress if=./nixos-installer-x86_64-linux.iso of=/dev/sd + ``` ### Step 4. Boot and Connect to your network @@ -110,6 +126,7 @@ Now run the following command to connect to your Wifi: ```shellSession # Identify your network device. device list + # Replace 'wlan0' with your wireless device name # Find your Wifi SSID. station wlan0 scan diff --git a/docs/site/getting-started/networking.md b/docs/site/getting-started/mesh-vpn.md similarity index 100% rename from docs/site/getting-started/networking.md rename to docs/site/getting-started/mesh-vpn.md diff --git a/docs/site/getting-started/secrets.md b/docs/site/getting-started/secrets.md index e770552d..141502b6 100644 --- a/docs/site/getting-started/secrets.md +++ b/docs/site/getting-started/secrets.md @@ -4,8 +4,6 @@ Clan enables encryption of secrets (such as passwords & keys) ensuring security Clan utilizes the [sops](https://github.com/getsops/sops) format and integrates with [sops-nix](https://github.com/Mic92/sops-nix) on NixOS machines. -This documentation will guide you through managing secrets with the Clan CLI - ### Create Your Master Keypair @@ -40,8 +38,7 @@ Also add your age public key to the repository with 'clan secrets users add YOUR clan secrets users add ``` -!!! note - Choose the same username as on your Setup/Source Machine that you use to control the deployment with. +It's best to choose the same username as on your Setup/Admin Machine that you use to control the deployment with. Once run this will create the following files: @@ -57,12 +54,140 @@ If you followed the quickstart tutorial all necessary secrets are initialized at ## Whats next? -- [Deployment](machines.md): How to remotely deploy your machine +- [Deployment](deploy.md): How to remotely deploy your machine --- +## More on Secrets + +If you want to know more about how to save and share passwords in your clan read further! + +### Adding a Secret + +```shellSession +clan secrets set mysecret +Paste your secret: +``` + +### Retrieving a Stored Secret + +```bash +clan secrets get mysecret +``` + +### List all Secrets + +```bash +clan secrets list +``` + +### NixOS integration + +A NixOS machine will automatically import all secrets that are encrypted for the +current machine. At runtime it will use the host key to decrypt all secrets into +an in-memory, non-persistent filesystem using [sops-nix](https://github.com/Mic92/sops-nix). +In your nixos configuration you can get a path to secrets like this `config.sops.secrets..path`. For example: + +```nix +{ config, ...}: { + sops.secrets.my-password.neededForUsers = true; + + users.users.mic92 = { + isNormalUser = true; + passwordFile = config.sops.secrets.my-password.path; + }; +} +``` + +### Assigning Access + +By default, secrets are encrypted for your key. To specify which users and machines can access a secret: + +```bash +clan secrets set --machine --machine --user --user +``` +You can also just add machines/users to existing secrets: + +```bash + clan secrets machines add-secret +``` + +## Advanced + +In this section we go into more advanced secret management topics. + +### Groups + +Clan CLI makes it easy to manage access by allowing you to create groups. + +All users within a group inherit access to all secrets of the group. + +This feature eases the process of handling permissions for multiple users. + +Here's how to get started: + +1. **Creating Groups**: + + Assign users to a new group, e.g., `admins`: + + ```bash + clan secrets groups add admins + ``` + +2. **Listing Groups**: + + ```bash + clan secrets groups list + ``` + +3. **Assigning Secrets to Groups**: + + ```bash + clan secrets groups add-secret + ``` + +### Adding Machine Keys + +New machines in Clan come with age keys stored in `./sops/machines/`. To list these machines: + +```bash + clan secrets machines list +``` + +For existing machines, add their keys: + +```bash + clan secrets machines add +``` + +To fetch an age key from an SSH host key: + +```bash + ssh-keyscan | nix shell nixpkgs#ssh-to-age -c ssh-to-age +``` + +### Migration: Importing existing sops-based keys / sops-nix + +`clan secrets` stores each secret in a single file, whereas [sops](https://github.com/Mic92/sops-nix) commonly allows to put all secrets in a yaml or json document. + +If you already happened to use sops-nix, you can migrate by using the `clan secrets import-sops` command by importing these files: + +```bash +% clan secrets import-sops --prefix matchbox- --group admins --machine matchbox nixos/matchbox/secrets/secrets.yaml +``` + +This will create secrets for each secret found in `nixos/matchbox/secrets/secrets.yaml` in a `./sops` folder of your repository. +Each member of the group `admins` in this case will be able to decrypt the secrets with their respective key. + +Since our clan secret module will auto-import secrets that are encrypted for a particular nixos machine, +you can now remove `sops.secrets. = { };` unless you need to specify more options for the secret like owner/group of the secret file. + + + ## Indepth Explanation + + The secrets system conceptually knows two different entities: - **Machine**: consumes secrets @@ -117,9 +242,6 @@ Rel_R(secret, machine, "Decrypt", "", "machine privkey" ) @enduml ``` -### Groups - -It is possible to create semantic groups to make access control more convenient. #### User groups @@ -181,154 +303,16 @@ Rel(secret, c1, "Decrypt", "", "Both machine A or B can decrypt using their priv ---- -## 2. Adding Machine Keys - -New machines in Clan come with age keys stored in `./sops/machines/`. To list these machines: - -```bash -$ clan secrets machines list -``` - -For existing machines, add their keys: - -```bash -$ clan secrets machines add -``` - -### Advanced - -To fetch an age key from an SSH host key: - -```bash -$ ssh-keyscan | nix shell nixpkgs#ssh-to-age -c ssh-to-age -``` - -## 3. Assigning Access - -By default, secrets are encrypted for your key. To specify which users and machines can access a secret: - -```bash -$ clan secrets set --machine --machine --user --user -``` - -You can add machines/users to existing secrets without modifying the secret: - -```bash -$ clan secrets machines add-secret -``` - -## 4. Adding Secrets - -```bash -$ clan secrets set mysecret -Paste your secret: -``` - -!!! note - As you type your secret won't be displayed. Press Enter to save the secret. - -## 5. Retrieving Stored Secrets - -```bash -$ clan secrets get mysecret -``` - -### List all Secrets - -```bash -$ clan secrets list -``` - -## 6. Groups - -Clan CLI makes it easy to manage access by allowing you to create groups. - -All users within a group inherit access to all secrets of the group. - -This feature eases the process of handling permissions for multiple users. - -Here's how to get started: - -1. **Creating Groups**: - - Assign users to a new group, e.g., `admins`: - - ```bash - $ clan secrets groups add admins - ``` - -2. **Listing Groups**: - - ```bash - $ clan secrets groups list - ``` - -3. **Assigning Secrets to Groups**: - - ```bash - $ clan secrets groups add-secret - ``` - -## Further - -Secrets in the repository follow this structure: - -```{.console, .no-copy} -sops/ -├── secrets/ -│ └── / -│ ├── secret -│ └── users/ -│ └── / -``` - -The content of the secret is stored encrypted inside the `secret` file under `mysecret`. - -By default, secrets are encrypted with your key to ensure readability. - -### NixOS integration - -A NixOS machine will automatically import all secrets that are encrypted for the -current machine. At runtime it will use the host key to decrypt all secrets into -an in-memory, non-persistent filesystem using [sops-nix](https://github.com/Mic92/sops-nix). -In your nixos configuration you can get a path to secrets like this `config.sops.secrets..path`. For example: - -```nix -{ config, ...}: { - sops.secrets.my-password.neededForUsers = true; - - users.users.mic92 = { - isNormalUser = true; - passwordFile = config.sops.secrets.my-password.path; - }; -} -``` See the [readme](https://github.com/Mic92/sops-nix) of sops-nix for more examples. -### Migration: Importing existing sops-based keys / sops-nix - -`clan secrets` stores each secret in a single file, whereas [sops](https://github.com/Mic92/sops-nix) commonly allows to put all secrets in a yaml or json document. - -If you already happened to use sops-nix, you can migrate by using the `clan secrets import-sops` command by importing these files: - -```bash -% clan secrets import-sops --prefix matchbox- --group admins --machine matchbox nixos/matchbox/secrets/secrets.yaml -``` - -This will create secrets for each secret found in `nixos/matchbox/secrets/secrets.yaml` in a `./sops` folder of your repository. -Each member of the group `admins` in this case will be able to decrypt the secrets with their respective key. - -Since our clan secret module will auto-import secrets that are encrypted for a particular nixos machine, -you can now remove `sops.secrets. = { };` unless you need to specify more options for the secret like owner/group of the secret file. --- ## Whats next? -- [Deployment](machines.md): How to remotely deploy your machine +- [Deployment](deploy.md): How to remotely deploy your machine --- diff --git a/flake.lock b/flake.lock index 73a064f0..58e2ad53 100644 --- a/flake.lock +++ b/flake.lock @@ -7,15 +7,15 @@ ] }, "locked": { - "lastModified": 1714103775, - "narHash": "sha256-kcBiIrmqzt3bNTr2GMBfAyA+on8BEKO1iKzzDFQZkjI=", - "owner": "nix-community", + "lastModified": 1714400597, + "narHash": "sha256-AA1TCyEl4O6+6F5man/V5VH9Zl9HPBpK91tSkZ16i2E=", + "owner": "Qubasa", "repo": "disko", - "rev": "285e26465a0bae510897ca04da26ce6307c652b4", + "rev": "58785136b8c37aeb2f67081387b48f663b166331", "type": "github" }, "original": { - "owner": "nix-community", + "owner": "Qubasa", "repo": "disko", "type": "github" } @@ -27,11 +27,11 @@ ] }, "locked": { - "lastModified": 1712014858, - "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "lastModified": 1714641030, + "narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e", "type": "github" }, "original": { @@ -55,6 +55,22 @@ "type": "github" } }, + "nixos-2311": { + "locked": { + "lastModified": 1715543463, + "narHash": "sha256-n3MLdwfCqQT0HtiE2QHCkvyxfaX7wgnO4HkOE1qkNuU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6eccabe980dcb2048aab7f97f862fb6d79b98abe", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, "nixos-generators": { "inputs": { "nixlib": "nixlib", @@ -76,13 +92,34 @@ "type": "github" } }, + "nixos-images": { + "inputs": { + "nixos-2311": "nixos-2311", + "nixos-unstable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1715566953, + "narHash": "sha256-c49CNk3L1QO0C6urknHCr5+pjmMaVDqBGmt+ECcunv4=", + "owner": "nix-community", + "repo": "nixos-images", + "rev": "81e709ca1da9c5c31b65cc8f82a97181f78ec076", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-images", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1714290118, - "narHash": "sha256-6PzUtOvU7hMQxZV579B04CHE1HXSq/loh9E+d/4fKZY=", + "lastModified": 1715777523, + "narHash": "sha256-S6g1OWbKXswOMoTssq3aOm4OhxhlKoIwEAXWmU57vts=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "30ddacc06345a478f9528fa29e2c8857b90381b2", + "rev": "c029b7f004009923bbfc90bbc31263cd4b08759f", "type": "github" }, "original": { @@ -97,6 +134,7 @@ "disko": "disko", "flake-parts": "flake-parts", "nixos-generators": "nixos-generators", + "nixos-images": "nixos-images", "nixpkgs": "nixpkgs", "sops-nix": "sops-nix", "treefmt-nix": "treefmt-nix" @@ -110,11 +148,11 @@ "nixpkgs-stable": [] }, "locked": { - "lastModified": 1713892811, - "narHash": "sha256-uIGmA2xq41vVFETCF1WW4fFWFT2tqBln+aXnWrvjGRE=", + "lastModified": 1715482972, + "narHash": "sha256-y1uMzXNlrVOWYj1YNcsGYLm4TOC2aJrwoUY1NjQs9fM=", "owner": "Mic92", "repo": "sops-nix", - "rev": "f1b0adc27265274e3b0c9b872a8f476a098679bd", + "rev": "b6cb5de2ce57acb10ecdaaf9bbd62a5ff24fa02e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e81b3d21..3b289028 100644 --- a/flake.nix +++ b/flake.nix @@ -8,14 +8,15 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; - - disko.url = "github:nix-community/disko"; + disko.url = "github:Qubasa/disko"; disko.inputs.nixpkgs.follows = "nixpkgs"; sops-nix.url = "github:Mic92/sops-nix"; sops-nix.inputs.nixpkgs.follows = "nixpkgs"; sops-nix.inputs.nixpkgs-stable.follows = ""; nixos-generators.url = "github:nix-community/nixos-generators"; nixos-generators.inputs.nixpkgs.follows = "nixpkgs"; + nixos-images.url = "github:nix-community/nixos-images"; + nixos-images.inputs.nixos-unstable.follows = "nixpkgs"; flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; treefmt-nix.url = "github:numtide/treefmt-nix"; diff --git a/nixosModules/installer/default.nix b/nixosModules/installer/default.nix index acb2784a..e80484d3 100644 --- a/nixosModules/installer/default.nix +++ b/nixosModules/installer/default.nix @@ -4,6 +4,38 @@ modulesPath, ... }: + +let + network-status = pkgs.writeShellScript "network-status" '' + export PATH=${ + lib.makeBinPath ( + with pkgs; + [ + iproute2 + coreutils + gnugrep + nettools + gum + ] + ) + } + set -efu -o pipefail + msgs=() + if [[ -e /var/shared/qrcode.utf8 ]]; then + qrcode=$(gum style --border-foreground 240 --border normal "$(< /var/shared/qrcode.utf8)") + msgs+=("$qrcode") + fi + network_status="Local network addresses: + $(ip -brief -color addr | grep -v 127.0.0.1) + $([[ -e /var/shared/onion-hostname ]] && echo "Onion address: $(cat /var/shared/onion-hostname)" || echo "Onion address: Waiting for tor network to be ready...") + Multicast DNS: $(hostname).local" + network_status=$(gum style --border-foreground 240 --border normal "$network_status") + msgs+=("$network_status") + msgs+=("Press 'Ctrl-C' for console access") + + gum join --vertical "''${msgs[@]}" + ''; +in { ############################################ # # @@ -11,19 +43,21 @@ # $ qemu-kvm result/stick.raw -snapshot # # # ############################################ - systemd.tmpfiles.rules = [ "d /var/shared 0777 root root - -" ]; imports = [ (modulesPath + "/profiles/installation-device.nix") (modulesPath + "/profiles/all-hardware.nix") (modulesPath + "/profiles/base.nix") - (modulesPath + "/installer/cd-dvd/iso-image.nix") ]; - services.openssh.settings.PermitRootLogin = "yes"; - system.activationScripts.root-password = '' - mkdir -p /var/shared - ${pkgs.pwgen}/bin/pwgen -s 16 1 > /var/shared/root-password - echo "root:$(cat /var/shared/root-password)" | chpasswd - ''; + + ######################################################################################################## + # # + # Copied from: # + # https://github.com/nix-community/nixos-images/blob/main/nix/image-installer/module.nix#L46C3-L117C6 # + # # + ######################################################################################################## + systemd.tmpfiles.rules = [ "d /var/shared 0777 root root - -" ]; + services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password"; + hidden-ssh-announce = { enable = true; script = pkgs.writeShellScript "write-hostname" '' @@ -44,26 +78,48 @@ echo "$1" > /var/shared/onion-hostname local_addrs=$(ip -json addr | jq '[map(.addr_info) | flatten | .[] | select(.scope == "global") | .local]') jq -nc \ - --arg password "$(cat /var/shared/root-password)" \ --arg onion_address "$(cat /var/shared/onion-hostname)" \ --argjson local_addrs "$local_addrs" \ - '{ pass: $password, onion_address: $onion_address, addrs: $local_addrs }' \ + '{ pass: null, tor: $onion_address, addrs: $local_addrs }' \ > /var/shared/login.json - cat /var/shared/login.json | qrencode -t utf8 -o /var/shared/qrcode.utf8 + cat /var/shared/login.json | qrencode -s 2 -m 2 -t utf8 -o /var/shared/qrcode.utf8 ''; }; + services.getty.autologinUser = lib.mkForce "root"; + + console.earlySetup = true; + console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u22n.psf.gz"; + + # Less ipv6 addresses to reduce the noise + networking.tempAddresses = "disabled"; + + # Tango theme: https://yayachiken.net/en/posts/tango-colors-in-terminal/ + console.colors = lib.mkDefault [ + "000000" + "CC0000" + "4E9A06" + "C4A000" + "3465A4" + "75507B" + "06989A" + "D3D7CF" + "555753" + "EF2929" + "8AE234" + "FCE94F" + "739FCF" + "AD7FA8" + "34E2E2" + "EEEEEC" + ]; + programs.bash.interactiveShellInit = '' if [[ "$(tty)" =~ /dev/(tty1|hvc0|ttyS0)$ ]]; then - echo -n 'waiting for tor to generate the hidden service' - until test -e /var/shared/qrcode.utf8; do echo -n .; sleep 1; done - echo - echo "Root password: $(cat /var/shared/root-password)" - echo "Onion address: $(cat /var/shared/onion-hostname)" - echo "Local network addresses:" - ${pkgs.iproute}/bin/ip -brief -color addr | grep -v 127.0.0.1 - cat /var/shared/qrcode.utf8 + # workaround for https://github.com/NixOS/nixpkgs/issues/219239 + systemctl restart systemd-vconsole-setup.service + + watch --no-title --color ${network-status} fi ''; - isoImage.squashfsCompression = "zstd"; } diff --git a/nixosModules/iso/default.nix b/nixosModules/iso/default.nix index db3afe56..c3f0d132 100644 --- a/nixosModules/iso/default.nix +++ b/nixosModules/iso/default.nix @@ -43,6 +43,7 @@ let boot = { size = "1M"; type = "EF02"; # for grub MBR + priority = 1; # Needs to be first partition }; ESP = { size = "100M"; diff --git a/pkgs/clan-cli/clan_cli/__init__.py b/pkgs/clan-cli/clan_cli/__init__.py index 51674ccb..13daeb3d 100644 --- a/pkgs/clan-cli/clan_cli/__init__.py +++ b/pkgs/clan-cli/clan_cli/__init__.py @@ -58,6 +58,7 @@ def create_parser(prog: str | None = None) -> argparse.ArgumentParser: "--debug", help="Enable debug logging", action="store_true", + default=False, ) parser.add_argument( diff --git a/pkgs/clan-cli/clan_cli/flakes/create.py b/pkgs/clan-cli/clan_cli/flakes/create.py index a7f8ca20..d8aba488 100644 --- a/pkgs/clan-cli/clan_cli/flakes/create.py +++ b/pkgs/clan-cli/clan_cli/flakes/create.py @@ -43,6 +43,10 @@ def create_flake(directory: Path, url: str) -> dict[str, CmdOut]: out = run(command, cwd=directory) response["git config"] = out + command = ["nix", "flake", "update"] + out = run(command, cwd=directory) + response["flake update"] = out + return response diff --git a/pkgs/clan-cli/clan_cli/flash.py b/pkgs/clan-cli/clan_cli/flash.py index a03c1af5..ed7cc77c 100644 --- a/pkgs/clan-cli/clan_cli/flash.py +++ b/pkgs/clan-cli/clan_cli/flash.py @@ -1,8 +1,9 @@ import argparse import importlib +import json import logging import os -import shlex +import re import shutil import textwrap from collections.abc import Sequence @@ -20,8 +21,71 @@ from .nix import nix_shell log = logging.getLogger(__name__) +def list_available_ssh_keys(ssh_dir: Path = Path("~/.ssh").expanduser()) -> list[Path]: + """ + Function to list all available SSH public keys in the default .ssh directory. + Returns a list of paths to available public key files. + """ + public_key_patterns = ["*.pub"] + available_keys: list[Path] = [] + + # Check for public key files + for pattern in public_key_patterns: + for key_path in ssh_dir.glob(pattern): + if key_path.is_file(): + available_keys.append(key_path) + + return available_keys + + +def read_public_key_contents(public_keys: list[Path]) -> list[str]: + """ + Function to read and return the contents of available SSH public keys. + Returns a list containing the contents of each public key. + """ + public_key_contents = [] + + for key_path in public_keys: + try: + with open(key_path.expanduser()) as key_file: + public_key_contents.append(key_file.read().strip()) + except FileNotFoundError: + log.error(f"Public key file not found: {key_path}") + + return public_key_contents + + +def get_keymap_and_locale() -> dict[str, str]: + locale = "en_US.UTF-8" + keymap = "en" + + # Execute the `localectl status` command + result = run(["localectl", "status"]) + + if result.returncode == 0: + output = result.stdout + + # Extract the Keymap (X11 Layout) + keymap_match = re.search(r"X11 Layout:\s+(.*)", output) + if keymap_match: + keymap = keymap_match.group(1) + + # Extract the System Locale (LANG only) + locale_match = re.search(r"System Locale:\s+LANG=(.*)", output) + if locale_match: + locale = locale_match.group(1) + + return {"keymap": keymap, "locale": locale} + + def flash_machine( - machine: Machine, mode: str, disks: dict[str, str], dry_run: bool, debug: bool + machine: Machine, + *, + mode: str, + disks: dict[str, str], + system_config: dict[str, Any], + dry_run: bool, + debug: bool, ) -> None: secret_facts_module = importlib.import_module(machine.secret_facts_module) secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore( @@ -58,12 +122,17 @@ def flash_machine( disko_install.extend(["--extra-files", str(local_dir), upload_dir]) disko_install.extend(["--flake", str(machine.flake) + "#" + machine.name]) disko_install.extend(["--mode", str(mode)]) + disko_install.extend( + [ + "--system-config", + json.dumps(system_config), + ] + ) cmd = nix_shell( - ["nixpkgs#disko"], + ["/home/lhebendanz/Projects/disko"], disko_install, ) - print("$", " ".join(map(shlex.quote, cmd))) run(cmd, log=Log.BOTH, error_msg=f"Failed to flash {machine}") @@ -72,10 +141,13 @@ class FlashOptions: flake: Path machine: str disks: dict[str, str] + ssh_keys_path: list[Path] dry_run: bool confirm: bool debug: bool mode: str + language: str + keymap: str class AppendDiskAction(argparse.Action): @@ -99,11 +171,15 @@ def flash_command(args: argparse.Namespace) -> None: flake=args.flake, machine=args.machine, disks=args.disk, + ssh_keys_path=args.ssh_pubkey, dry_run=args.dry_run, confirm=not args.yes, debug=args.debug, mode=args.mode, + language=args.lang, + keymap=args.keymap, ) + machine = Machine(opts.machine, flake=opts.flake) if opts.confirm and not opts.dry_run: disk_str = ", ".join(f"{name}={device}" for name, device in opts.disks.items()) @@ -114,8 +190,49 @@ def flash_command(args: argparse.Namespace) -> None: ask = input(msg) if ask != "y": return + + root_keys = read_public_key_contents(opts.ssh_keys_path) + if opts.confirm and not root_keys: + msg = "Should we add your SSH public keys to the root user? [y/N] " + ask = input(msg) + if ask == "y": + pubkeys = list_available_ssh_keys() + root_keys.extend(read_public_key_contents(pubkeys)) + else: + raise ClanError( + "No SSH public keys provided. Use --ssh-pubkey to add keys." + ) + elif not opts.confirm and not root_keys: + pubkeys = list_available_ssh_keys() + root_keys.extend(read_public_key_contents(pubkeys)) + # If ssh-pubkeys set, we don't need to ask for confirmation + elif opts.confirm and root_keys: + pass + elif not opts.confirm and root_keys: + pass + else: + raise ClanError("Invalid state") + + localectl = get_keymap_and_locale() + extra_config = { + "users": { + "users": {"root": {"openssh": {"authorizedKeys": {"keys": root_keys}}}} + }, + "console": { + "keyMap": opts.keymap if opts.keymap else localectl["keymap"], + }, + "i18n": { + "defaultLocale": opts.language if opts.language else localectl["locale"], + }, + } + flash_machine( - machine, opts.mode, disks=opts.disks, dry_run=opts.dry_run, debug=opts.debug + machine, + mode=opts.mode, + disks=opts.disks, + system_config=extra_config, + dry_run=opts.dry_run, + debug=opts.debug, ) @@ -147,7 +264,23 @@ def register_parser(parser: argparse.ArgumentParser) -> None: choices=["format", "mount"], default="format", ) - + parser.add_argument( + "--ssh-pubkey", + type=Path, + action="append", + default=[], + help="ssh pubkey file to add to the root user. Can be used multiple times", + ) + parser.add_argument( + "--lang", + type=str, + help="system language", + ) + parser.add_argument( + "--keymap", + type=str, + help="system keymap", + ) parser.add_argument( "--yes", action="store_true", @@ -160,10 +293,4 @@ def register_parser(parser: argparse.ArgumentParser) -> None: default=False, action="store_true", ) - parser.add_argument( - "--debug", - help="Print debug information", - default=False, - action="store_true", - ) parser.set_defaults(func=flash_command) diff --git a/pkgs/clan-cli/clan_cli/machines/install.py b/pkgs/clan-cli/clan_cli/machines/install.py index 0415d0dd..fe9c962a 100644 --- a/pkgs/clan-cli/clan_cli/machines/install.py +++ b/pkgs/clan-cli/clan_cli/machines/install.py @@ -25,6 +25,7 @@ def install_nixos( kexec: str | None = None, debug: bool = False, password: str | None = None, + no_reboot: bool = False, ) -> None: secret_facts_module = importlib.import_module(machine.secret_facts_module) log.info(f"installing {machine.name}") @@ -53,11 +54,13 @@ def install_nixos( "nixos-anywhere", "--flake", f"{machine.flake}#{machine.name}", - "--no-reboot", "--extra-files", str(tmpdir), ] + if no_reboot: + cmd.append("--no-reboot") + if password: cmd += [ "--env-password", @@ -90,6 +93,7 @@ class InstallOptions: kexec: str | None confirm: bool debug: bool + no_reboot: bool json_ssh_deploy: dict[str, str] | None @@ -121,6 +125,7 @@ def install_command(args: argparse.Namespace) -> None: kexec=args.kexec, confirm=not args.yes, debug=args.debug, + no_reboot=args.no_reboot, json_ssh_deploy=json_ssh_deploy, ) machine = Machine(opts.machine, flake=opts.flake) @@ -131,7 +136,13 @@ def install_command(args: argparse.Namespace) -> None: if ask != "y": return - install_nixos(machine, kexec=opts.kexec, debug=opts.debug, password=password) + install_nixos( + machine, + kexec=opts.kexec, + debug=opts.debug, + password=password, + no_reboot=opts.no_reboot, + ) def find_reachable_host_from_deploy_json(deploy_json: dict[str, str]) -> str: @@ -161,15 +172,15 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None: help="use another kexec tarball to bootstrap NixOS", ) parser.add_argument( - "--yes", + "--no-reboot", action="store_true", - help="do not ask for confirmation", + help="do not reboot after installation", default=False, ) parser.add_argument( - "--debug", + "--yes", action="store_true", - help="print debug information", + help="do not ask for confirmation", default=False, ) parser.add_argument( diff --git a/pkgs/clan-cli/clan_cli/nix.py b/pkgs/clan-cli/clan_cli/nix.py index e922fd5e..84de3520 100644 --- a/pkgs/clan-cli/clan_cli/nix.py +++ b/pkgs/clan-cli/clan_cli/nix.py @@ -106,13 +106,7 @@ def nix_shell(packages: list[str], cmd: list[str]) -> list[str]: if os.environ.get("IN_NIX_SANDBOX"): return cmd return [ - *nix_command( - [ - "shell", - "--inputs-from", - f"{nixpkgs_flake()!s}", - ] - ), + *nix_command(["shell", "--offline", "--inputs-from", f"{nixpkgs_flake()!s}"]), *packages, "-c", *cmd, diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index c2452160..47c3f63e 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -23,11 +23,11 @@ zbar, tor, git, - nixpkgs, qemu, gnupg, e2fsprogs, mypy, + nixpkgs, clan-core-path, }: let @@ -95,17 +95,16 @@ let description = "dependencies for the clan-cli"; inputs = { - nixpkgs.url = "nixpkgs"; + nixpkgs.url = "path://${nixpkgs}"; }; outputs = _inputs: { }; } EOF ln -s ${nixpkgs} $out/path - nix flake lock $out \ + nix flake update $out \ --store ./. \ - --extra-experimental-features 'nix-command flakes' \ - --override-input nixpkgs ${nixpkgs} + --extra-experimental-features 'nix-command flakes' ''; in python3.pkgs.buildPythonApplication { diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index 317990c7..2e4969b6 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -38,6 +38,7 @@ ''; in { + devShells.clan-cli = pkgs.callPackage ./shell.nix { inherit (self'.packages) clan-cli; }; packages = { clan-cli = pkgs.python3.pkgs.callPackage ./default.nix { diff --git a/pkgs/clan-cli/shell.nix b/pkgs/clan-cli/shell.nix index 1daef3e6..6d7f6372 100644 --- a/pkgs/clan-cli/shell.nix +++ b/pkgs/clan-cli/shell.nix @@ -12,6 +12,7 @@ let rope setuptools wheel + ipdb pip ]); in @@ -21,6 +22,8 @@ mkShell { ruff ] ++ devshellTestDeps; + PYTHONBREAKPOINT = "ipdb.set_trace"; + shellHook = '' export GIT_ROOT="$(git rev-parse --show-toplevel)" export PKG_ROOT="$GIT_ROOT/pkgs/clan-cli" diff --git a/pkgs/clan-vm-manager/shell.nix b/pkgs/clan-vm-manager/shell.nix index 758b06eb..a7214007 100644 --- a/pkgs/clan-vm-manager/shell.nix +++ b/pkgs/clan-vm-manager/shell.nix @@ -40,6 +40,8 @@ mkShell { desktop-file-utils # verify desktop files ]); + PYTHONBREAKPOINT = "ipdb.set_trace"; + shellHook = '' export GIT_ROOT=$(git rev-parse --show-toplevel) export PKG_ROOT=$GIT_ROOT/pkgs/clan-vm-manager diff --git a/pkgs/flake-module.nix b/pkgs/flake-module.nix index bea7c2dd..b3dbdb14 100644 --- a/pkgs/flake-module.nix +++ b/pkgs/flake-module.nix @@ -1,4 +1,5 @@ { ... }: + { imports = [ ./clan-cli/flake-module.nix diff --git a/pkgs/installer/flake-module.nix b/pkgs/installer/flake-module.nix index 69f32e90..59af6c60 100644 --- a/pkgs/installer/flake-module.nix +++ b/pkgs/installer/flake-module.nix @@ -1,51 +1,123 @@ { self, lib, ... }: + let - installerModule = - { config, ... }: + wifiModule = + { ... }: { - imports = [ - self.nixosModules.installer - self.inputs.nixos-generators.nixosModules.all-formats - ]; - # Provide convenience for connecting to wifi + # use iwd instead of wpa_supplicant networking.wireless.enable = false; # Use iwd instead of wpa_supplicant. It has a user friendly CLI networking.wireless.iwd = { + enable = true; settings = { Network = { EnableIPv6 = true; RoutePriorityOffset = 300; }; - Settings = { - AutoConnect = true; - }; + Settings.AutoConnect = true; }; - enable = true; }; + }; + installerModule = + { config, modulesPath, ... }: + { + imports = [ + wifiModule + self.nixosModules.installer + self.inputs.nixos-generators.nixosModules.all-formats + (modulesPath + "/installer/cd-dvd/iso-image.nix") + ]; + + isoImage.squashfsCompression = "zstd"; + system.stateVersion = config.system.nixos.version; nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux; }; - installer = lib.nixosSystem { + installerSystem = lib.nixosSystem { modules = [ self.inputs.disko.nixosModules.default installerModule { disko.memSize = 4096; } # FIXME: otherwise the image builder goes OOM ]; }; + + flashInstallerModule = + { config, ... }: + { + imports = [ + wifiModule + self.nixosModules.installer + ]; + system.stateVersion = config.system.nixos.version; + nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux; + } + // flashDiskoConfig; + + # Important: The partition names need to be different to the clan install + flashDiskoConfig = { + boot.loader.grub.efiSupport = lib.mkDefault true; + boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true; + disko.devices = { + disk = { + main = { + type = "disk"; + device = lib.mkDefault "/dev/null"; + content = { + type = "gpt"; + partitions = { + installer-boot = { + size = "1M"; + type = "EF02"; # for grub MBR + priority = 1; + }; + installer-ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + installer-root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; + }; in { - clan = { clanName = "clan-core"; directory = self; - machines.installer = { + + # To build a generic installer image (without ssh pubkeys), + # use the following command: + # $ nix build .#iso-installer + machines.iso-installer = { imports = [ installerModule ]; fileSystems."/".device = lib.mkDefault "/dev/null"; }; + + # To directly flash the installer to a disk, use the following command: + # $ clan flash flash-installer --disk main /dev/sdX --yes + # This will include your ssh public keys in the installer. + machines.flash-installer = { + imports = [ flashInstallerModule ]; + boot.loader.grub.enable = lib.mkDefault true; + }; }; - flake.packages.x86_64-linux.install-iso = installer.config.formats.iso; - flake.apps.x86_64-linux.install-vm.program = installer.config.formats.vm.outPath; - flake.apps.x86_64-linux.install-vm-nogui.program = installer.config.formats.vm-nogui.outPath; + flake.packages.x86_64-linux.iso-installer = installerSystem.config.formats.iso; + flake.apps.x86_64-linux.install-vm.program = installerSystem.config.formats.vm.outPath; + flake.apps.x86_64-linux.install-vm-nogui.program = installerSystem.config.formats.vm-nogui.outPath; } diff --git a/templates/new-clan/flake.nix b/templates/new-clan/flake.nix index d61938ee..1acecd5a 100644 --- a/templates/new-clan/flake.nix +++ b/templates/new-clan/flake.nix @@ -19,6 +19,7 @@ # local> mkdir -p ./machines/machine1 # local> Edit ./machines/machine1/configuration.nix to your liking machines = { + # "jon" will be the hostname of the machine jon = { imports = [ ./modules/shared.nix @@ -31,18 +32,27 @@ clanCore.machineIcon = null; # Optional, a path to an image file # Set this for clan commands use ssh i.e. `clan machines update` + # If you change the hostname, you need to update this line to root@ + # This only works however if you have avahi running on your admin machine else use IP clan.networking.targetHost = pkgs.lib.mkDefault "root@jon"; - # TODO: Example how to use disko for more complicated setups - # ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT disko.devices.disk.main = { device = "/dev/disk/by-id/__CHANGE_ME__"; }; - # TODO: Document that there needs to be one controller + # IMPORTANT! Add your SSH key here + # e.g. > cat ~/.ssh/id_ed25519.pub + users.users.root.openssh.authorizedKeys.keys = throw '' + Don't forget to add your SSH key here! + users.users.root.openssh.authorizedKeys.keys = [ "" ] + ''; + + # Zerotier needs one controller to accept new nodes. Once accepted + # the controller can be offline and routing still works. clan.networking.zerotier.controller.enable = true; }; + # "sara" will be the hostname of the machine sara = { imports = [ ./modules/shared.nix @@ -55,14 +65,22 @@ clanCore.machineIcon = null; # Optional, a path to an image file # Set this for clan commands use ssh i.e. `clan machines update` + # If you change the hostname, you need to update this line to root@ + # This only works however if you have avahi running on your admin machine else use IP clan.networking.targetHost = pkgs.lib.mkDefault "root@sara"; - # local> clan facts generate - # ssh root@flash-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT disko.devices.disk.main = { device = "/dev/disk/by-id/__CHANGE_ME__"; }; + + # IMPORTANT! Add your SSH key here + # e.g. > cat ~/.ssh/id_ed25519.pub + users.users.root.openssh.authorizedKeys.keys = throw '' + Don't forget to add your SSH key here! + users.users.root.openssh.authorizedKeys.keys = [ "" ] + ''; + /* After jon is deployed, uncomment the following line This will allow sara to share the VPN overlay network with jon diff --git a/templates/new-clan/machines/jon/configuration.nix b/templates/new-clan/machines/jon/configuration.nix index c84ebb69..e70188d6 100644 --- a/templates/new-clan/machines/jon/configuration.nix +++ b/templates/new-clan/machines/jon/configuration.nix @@ -1,15 +1,35 @@ -{ ... }: +{ config, ... }: +let + username = config.networking.hostName; +in { imports = [ ./hardware-configuration.nix ]; - users.users.root.openssh.authorizedKeys.keys = [ - # IMPORTANT! Add your SSH key here - # e.g. > cat ~/.ssh/id_ed25519.pub - "" - ]; services.xserver.enable = true; services.xserver.desktopManager.gnome.enable = true; services.xserver.displayManager.gdm.enable = true; # Disable the default gnome apps to speed up deployment services.gnome.core-utilities.enable = false; + + # Enable automatic login for the user. + services.displayManager.autoLogin = { + enable = true; + user = username; + }; + + users.users.${username} = { + initialPassword = username; + isNormalUser = true; + extraGroups = [ + "wheel" + "networkmanager" + "video" + "audio" + "input" + "dialout" + "disk" + ]; + uid = 1000; + openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys; + }; } diff --git a/templates/new-clan/machines/sara/configuration.nix b/templates/new-clan/machines/sara/configuration.nix index c84ebb69..2d5eb246 100644 --- a/templates/new-clan/machines/sara/configuration.nix +++ b/templates/new-clan/machines/sara/configuration.nix @@ -1,15 +1,36 @@ -{ ... }: +{ config, ... }: + +let + username = config.networking.hostName; +in { imports = [ ./hardware-configuration.nix ]; - users.users.root.openssh.authorizedKeys.keys = [ - # IMPORTANT! Add your SSH key here - # e.g. > cat ~/.ssh/id_ed25519.pub - "" - ]; services.xserver.enable = true; services.xserver.desktopManager.gnome.enable = true; services.xserver.displayManager.gdm.enable = true; # Disable the default gnome apps to speed up deployment services.gnome.core-utilities.enable = false; + + # Enable automatic login for the user. + services.displayManager.autoLogin = { + enable = true; + user = username; + }; + + users.users.${username} = { + initialPassword = username; + isNormalUser = true; + extraGroups = [ + "wheel" + "networkmanager" + "video" + "audio" + "input" + "dialout" + "disk" + ]; + uid = 1000; + openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys; + }; } diff --git a/templates/new-clan/modules/disko.nix b/templates/new-clan/modules/disko.nix index 1eb38702..7a750958 100644 --- a/templates/new-clan/modules/disko.nix +++ b/templates/new-clan/modules/disko.nix @@ -1,4 +1,7 @@ +{ lib, ... }: { + boot.loader.grub.efiSupport = lib.mkDefault true; + boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true; disko.devices = { disk = { main = { @@ -11,6 +14,7 @@ boot = { size = "1M"; type = "EF02"; # for grub MBR + priority = 1; }; ESP = { size = "512M"; diff --git a/templates/new-clan/modules/shared.nix b/templates/new-clan/modules/shared.nix index bcd3118e..963b46f4 100644 --- a/templates/new-clan/modules/shared.nix +++ b/templates/new-clan/modules/shared.nix @@ -4,4 +4,7 @@ clan-core.clanModules.sshd clan-core.clanModules.root-password ]; + + # Locale service discovery and mDNS + services.avahi.enable = true; }