diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 9ff2d49..f1b3692 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -46,7 +46,7 @@ jobs: --extra-platforms aarch64-linux \ --out-link result-${{ matrix.host }} - IMAGE_PATH=$(find result-${{ matrix.host }} -name "*.img.zst" -type f | head -1) + IMAGE_PATH=$(find -L result-${{ matrix.host }} -name "*.img.zst" | head -1) if [ -z "$IMAGE_PATH" ]; then echo "Error: No image found!" exit 1 diff --git a/AGENTS.md b/AGENTS.md index 367ea5c..3cec9b0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,37 +2,33 @@ ## Project Overview -This repository contains a NixOS configuration managed with Nix Flakes. It defines: -- **Hosts**: `cryodev-main` (x86_64 server), `cryodev-pi` (aarch64 Raspberry Pi) +NixOS infrastructure managed with Nix Flakes. Two hosts, reusable modules, SOPS secrets, Comin auto-deployment. + +- **Hosts**: `cryodev-main` (x86_64 server), `cryodev-pi` (aarch64 Raspberry Pi 4) - **Modules**: Reusable NixOS modules in `modules/nixos/` -- **Packages**: Custom packages in `pkgs/` +- **Apps**: `create`, `deploy`, `install`, `rebuild` in `apps/` - **Templates**: `raspberry-pi`, `generic-server` for bootstrapping new hosts ## Build & Development Commands -### Prerequisites -- **Nix** with Flakes enabled -- **Git** - -### Core Commands - ```bash -# Build host configuration +# Format code (required before committing, runs nixfmt via pre-commit) +nix fmt + +# Run all checks (formatting, package builds, overlay builds) +nix flake check + +# Quick evaluation test (faster than full build, use to validate changes) +nix eval .#nixosConfigurations.cryodev-main.config.system.build.toplevel.name +nix eval .#nixosConfigurations.cryodev-pi.config.system.build.toplevel.name + +# Full build nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel # Build Raspberry Pi SD image (requires binfmt on x86_64) nix build .#nixosConfigurations.cryodev-pi.config.system.build.sdImage -# Format code (required before committing) -nix fmt - -# Run all checks (lint, formatting) -nix flake check - -# Quick evaluation test (faster than full build) -nix eval .#nixosConfigurations.cryodev-main.config.system.build.toplevel.name - # Update flake inputs nix flake update @@ -42,176 +38,143 @@ nix develop ### Deployment -```bash -# Deploy all hosts via deploy app (uses deploy.json) -nix run .#deploy +Both hosts use **Comin** for automatic pull-based deployment (polls the git repo). +Manual deployment is only needed for initial setup or emergencies: -# Deploy a specific host +```bash +# Deploy via deploy app (uses deploy.json, SSH port 2299, asks sudo password) nix run .#deploy -- -n cryodev-main -# Manual deployment via SSH +# Manual deployment via nixos-rebuild NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ --target-host @ --sudo --ask-sudo-password ``` -> **Note:** Both hosts use Comin for automatic pull-based deployment. -> Manual deployment is only needed for the initial setup or emergencies. - ### Apps ```bash -# Create a new host from template -nix run .#create -- -t generic-server -n - -# Install NixOS on a new machine (run from NixOS live ISO) -nix run .#install -- -n -r - -# Deploy to all configured hosts -nix run .#deploy - -# Rebuild NixOS/Home Manager configuration -nix run .#rebuild -- nixos +nix run .#create -- -t generic-server -n # Scaffold new host +nix run .#install -- -n -r # Install from NixOS ISO +nix run .#deploy -- -n # Deploy to host +nix run .#rebuild -- nixos # Rebuild locally ``` ## Code Style & Conventions ### Formatting -- **Tool**: `nixfmt` via pre-commit hooks +- **Tool**: `nixfmt` via `git-hooks.nix` (pre-commit) - **Run**: `nix fmt` before every commit -- **Indentation**: 2 spaces -- **Line length**: 80-100 characters (follow formatter) +- **Indentation**: 2 spaces (enforced by formatter) -### Module Structure +### Naming Conventions + +| Type | Convention | Example | +|------|------------|---------| +| Files | kebab-case | `hardware-configuration.nix` | +| NixOS options | camelCase | `services.myService.enable` | +| Let bindings | camelCase | `let myValue = ...;` | +| Hosts | kebab-case | `cryodev-main`, `cryodev-pi` | +| Secret paths | kebab-case with `/` | `forgejo-runner/token` | + +### Module Pattern ```nix -# Standard module pattern -{ config, lib, pkgs, inputs, outputs, constants, ... }: +{ config, lib, ... }: let cfg = config.services.myService; + inherit (lib) mkDefault mkEnableOption mkIf mkOption types; in { options.services.myService = { - enable = lib.mkEnableOption "My service"; - port = lib.mkOption { - type = lib.types.port; + enable = mkEnableOption "My service"; + port = mkOption { + type = types.port; default = 8080; description = "Port to listen on"; }; }; - config = lib.mkIf cfg.enable { - # Implementation here + config = mkIf cfg.enable { + assertions = [ + { assertion = cfg.port > 1024; message = "Port must be > 1024"; } + ]; + # Implementation }; } ``` -### Naming Conventions -| Type | Convention | Example | -|------|------------|---------| -| Files | kebab-case | `hardware-configuration.nix` | -| Options | camelCase | `services.myService.enable` | -| Variables | camelCase | `let myValue = ...;` | -| Hosts | kebab-case | `cryodev-main`, `cryodev-pi` | +### Key Rules -### Imports +- **Use `lib.mkDefault`** for all module defaults (allows host-level overrides) +- **Use `constants.nix`** for domains, IPs, ports -- never hardcode these +- **Use `lib.utils`** helpers: `mkReverseProxyOption`, `mkVirtualHost`, `mkUrl` +- **Secrets via SOPS** only, never plaintext. Reference: `config.sops.secrets."path".path` +- **Imports**: relative paths for local files, `outputs.nixosModules.*` for shared modules, `inputs.*` for external +- **Assertions** for invalid configurations, `warnings` for non-critical issues +- **`lib.inherit`** pattern: extract needed functions in `let` block + +### Host Service Files + +Each service gets its own file in `hosts//services/`: ```nix -# Local modules: relative paths -imports = [ ./hardware.nix ./networking.nix ]; - -# Shared modules: via outputs -imports = [ outputs.nixosModules.common ]; - -# External inputs -imports = [ inputs.sops-nix.nixosModules.sops ]; -``` - -### Constants -Use `constants.nix` for domains, IPs, and ports: -```nix -{ constants, ... }: +# hosts/cryodev-main/services/myservice.nix +{ outputs, constants, ... }: { - services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = { ... }; + imports = [ outputs.nixosModules.myservice ]; + + services.myservice = { + enable = true; + port = constants.services.myservice.port; + }; + + services.nginx.virtualHosts."${constants.services.myservice.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.myservice.port}"; + }; + }; } ``` -### Error Handling -```nix -config = lib.mkIf cfg.enable { - assertions = [ - { assertion = cfg.port > 1024; message = "Port must be > 1024"; } - ]; - warnings = lib.optional (cfg.debug) "Debug mode enabled!"; -}; -``` +### Special Args Available in Modules -### Option Conflicts -Use `lib.mkDefault` for default values that can be overridden: -```nix -services.nginx.enable = lib.mkDefault true; -``` +- `inputs` -- flake inputs (nixpkgs, sops-nix, comin, headplane, etc.) +- `outputs` -- this flake's outputs (nixosModules, packages) +- `constants` -- values from `constants.nix` (domain, hosts, services) +- `lib` -- nixpkgs.lib extended with `lib.utils` ## Directory Structure ``` . -├── flake.nix # Entry point, inputs/outputs +├── flake.nix # Entry point, inputs/outputs, mkNixosConfiguration ├── constants.nix # Central config (domains, IPs, ports) ├── hosts/ -│ ├── cryodev-main/ # x86_64 server -│ │ ├── default.nix # Host entry point -│ │ ├── hardware.nix # Hardware configuration -│ │ ├── services/ # Service configurations -│ │ └── secrets.yaml # SOPS-encrypted secrets -│ └── cryodev-pi/ # aarch64 Raspberry Pi -├── modules/nixos/ # Reusable modules -│ ├── common/ # Shared base configuration -│ ├── sops/ # Secret management -│ ├── forgejo/ # Git server -│ ├── headscale/ # VPN control server -│ └── ... -├── lib/utils.nix # Helper functions -├── apps/ # Nix apps (rebuild) +│ ├── cryodev-main/ # x86_64 server (services/, secrets.yaml, binfmt.nix) +│ └── cryodev-pi/ # aarch64 RPi (services/, secrets.yaml, sd-image.nix) +├── modules/nixos/ # Reusable modules (common, forgejo, headscale, ...) +├── users/ # User definitions (steffen, ralph, benjamin) +├── apps/ # Nix apps (create, deploy, install, rebuild) +├── lib/utils.nix # Helper functions (mkUrl, mkVirtualHost, ...) ├── pkgs/ # Custom packages ├── overlays/ # Nixpkgs overlays -├── templates/ # Host templates -├── scripts/ # Helper scripts (install.sh) -└── docs/ # Documentation +├── templates/ # Host templates (generic-server, raspberry-pi) +├── deploy.json # Deploy app config (hosts, SSH port) +├── .sops.yaml # SOPS encryption rules (age keys per host) +├── .forgejo/workflows/ # CI pipelines (ci.yml, deploy.yml) +└── docs/ # Documentation (English) ``` -## Key Patterns - -### Adding a New Raspberry Pi Host -1. Copy template: `cp -r templates/raspberry-pi hosts/new-pi` -2. Update `hosts/new-pi/networking.nix` (hostname) -3. Add to `flake.nix`: `new-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/new-pi ];` -4. Add to `.forgejo/workflows/build-pi-image.yml` matrix -5. Push → SD image is built automatically - -### SOPS Secrets -- Secrets encrypted with age using SSH host keys -- Config in `.sops.yaml`, secrets in `hosts//secrets.yaml` -- Reference: `config.sops.secrets."path/to/secret".path` - -### Special Args Available in Modules -- `inputs`: Flake inputs (nixpkgs, sops-nix, etc.) -- `outputs`: This flake's outputs (nixosModules, packages) -- `constants`: Values from `constants.nix` -- `lib`: Extended nixpkgs.lib with `lib.utils` - -## Deployment Workflows - -| Host | Strategy | Trigger | -|------|----------|---------| -| `cryodev-main` | Pull via Comin | Automatic polling | -| `cryodev-pi` | Pull via Comin | Automatic polling | -| SD Images | Built in CI | Push to main (for Pi hosts) | - ## Verification Checklist Before committing: - [ ] `nix fmt` passes -- [ ] `nix flake check` passes (or at least `nix eval` works) -- [ ] New hosts added to `flake.nix` +- [ ] `nix flake check` passes (or at least `nix eval` works for both hosts) +- [ ] New hosts added to `flake.nix` nixosConfigurations - [ ] Constants in `constants.nix`, not hardcoded - [ ] Secrets use SOPS, not plaintext +- [ ] New services have their own file in `hosts//services/` +- [ ] New modules registered in `modules/nixos/default.nix` +- [ ] Documentation in English diff --git a/digest.txt b/digest.txt index e11d733..f554e4b 100644 --- a/digest.txt +++ b/digest.txt @@ -3,10 +3,17 @@ Directory structure: ├── README.md ├── AGENTS.md ├── constants.nix + ├── deploy.json ├── flake.lock ├── flake.nix ├── .sops.yaml ├── apps/ + │ ├── create/ + │ │ ├── create.sh + │ │ └── default.nix + │ ├── deploy/ + │ │ ├── default.nix + │ │ └── deploy.sh │ ├── install/ │ │ ├── default.nix │ │ └── install.sh @@ -44,7 +51,9 @@ Directory structure: │ │ ├── secrets.yaml │ │ ├── users.nix │ │ └── services/ + │ │ ├── comin.nix │ │ ├── default.nix + │ │ ├── forgejo-runner.nix │ │ ├── forgejo.nix │ │ ├── headplane.nix │ │ ├── headscale.nix @@ -165,7 +174,9 @@ Directory structure: │ ├── openssh.nix │ └── tailscale.nix ├── users/ - │ ├── cryotherm/ + │ ├── benjamin/ + │ │ └── default.nix + │ ├── ralph/ │ │ └── default.nix │ └── steffen/ │ ├── default.nix @@ -173,10 +184,8 @@ Directory structure: │ └── X670E.pub └── .forgejo/ └── workflows/ - ├── build-hosts.yml - ├── build-pi-image.yml - ├── deploy-main.yml - └── flake-check.yml + ├── ci.yml + └── deploy.yml ================================================ FILE: README.md @@ -189,8 +198,8 @@ Declarative NixOS infrastructure for the **cryodev** environment, managed with N ```bash # Clone repository -git clone https://git.cryodev.xyz/steffen/cryodev-server.git -cd cryodev-server +git clone https://git.cryodev.xyz/steffen/cryodev.git +cd cryodev # Check configuration nix flake check @@ -203,7 +212,7 @@ nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel | Host | Architecture | Deployment | Description | |------|--------------|------------|-------------| -| `cryodev-main` | x86_64 | Push (deploy-rs) | Main server | +| `cryodev-main` | x86_64 | Pull (Comin) | Main server | | `cryodev-pi` | aarch64 | Pull (Comin) | Raspberry Pi client | ## Services @@ -220,7 +229,7 @@ nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel SD card images for Raspberry Pi clients are **built automatically** on every push to `main`. -Download from: [Releases](https://git.cryodev.xyz/steffen/cryodev-server/releases) +Download from: [Releases](https://git.cryodev.xyz/steffen/cryodev/releases) ```bash # Flash to SD card @@ -291,37 +300,33 @@ FILE: AGENTS.md ## Project Overview -This repository contains a NixOS configuration managed with Nix Flakes. It defines: -- **Hosts**: `cryodev-main` (x86_64 server), `cryodev-pi` (aarch64 Raspberry Pi) +NixOS infrastructure managed with Nix Flakes. Two hosts, reusable modules, SOPS secrets, Comin auto-deployment. + +- **Hosts**: `cryodev-main` (x86_64 server), `cryodev-pi` (aarch64 Raspberry Pi 4) - **Modules**: Reusable NixOS modules in `modules/nixos/` -- **Packages**: Custom packages in `pkgs/` +- **Apps**: `create`, `deploy`, `install`, `rebuild` in `apps/` - **Templates**: `raspberry-pi`, `generic-server` for bootstrapping new hosts ## Build & Development Commands -### Prerequisites -- **Nix** with Flakes enabled -- **Git** - -### Core Commands - ```bash -# Build host configuration +# Format code (required before committing, runs nixfmt via pre-commit) +nix fmt + +# Run all checks (formatting, package builds, overlay builds) +nix flake check + +# Quick evaluation test (faster than full build, use to validate changes) +nix eval .#nixosConfigurations.cryodev-main.config.system.build.toplevel.name +nix eval .#nixosConfigurations.cryodev-pi.config.system.build.toplevel.name + +# Full build nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel # Build Raspberry Pi SD image (requires binfmt on x86_64) nix build .#nixosConfigurations.cryodev-pi.config.system.build.sdImage -# Format code (required before committing) -nix fmt - -# Run all checks (lint, formatting, deploy-rs validation) -nix flake check - -# Quick evaluation test (faster than full build) -nix eval .#nixosConfigurations.cryodev-main.config.system.build.toplevel.name - # Update flake inputs nix flake update @@ -331,158 +336,146 @@ nix develop ### Deployment -```bash -# Deploy to cryodev-main via deploy-rs -nix run github:serokell/deploy-rs -- .#cryodev-main +Both hosts use **Comin** for automatic pull-based deployment (polls the git repo). +Manual deployment is only needed for initial setup or emergencies: -# Manual deployment via SSH -nixos-rebuild switch --flake .# \ - --target-host @ --use-remote-sudo \ - --ssh-option="-p 2299" +```bash +# Deploy via deploy app (uses deploy.json, SSH port 2299, asks sudo password) +nix run .#deploy -- -n cryodev-main + +# Manual deployment via nixos-rebuild +NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ + --target-host @ --sudo --ask-sudo-password +``` + +### Apps + +```bash +nix run .#create -- -t generic-server -n # Scaffold new host +nix run .#install -- -n -r # Install from NixOS ISO +nix run .#deploy -- -n # Deploy to host +nix run .#rebuild -- nixos # Rebuild locally ``` ## Code Style & Conventions ### Formatting -- **Tool**: `nixfmt` via pre-commit hooks +- **Tool**: `nixfmt` via `git-hooks.nix` (pre-commit) - **Run**: `nix fmt` before every commit -- **Indentation**: 2 spaces -- **Line length**: 80-100 characters (follow formatter) +- **Indentation**: 2 spaces (enforced by formatter) -### Module Structure +### Naming Conventions + +| Type | Convention | Example | +|------|------------|---------| +| Files | kebab-case | `hardware-configuration.nix` | +| NixOS options | camelCase | `services.myService.enable` | +| Let bindings | camelCase | `let myValue = ...;` | +| Hosts | kebab-case | `cryodev-main`, `cryodev-pi` | +| Secret paths | kebab-case with `/` | `forgejo-runner/token` | + +### Module Pattern ```nix -# Standard module pattern -{ config, lib, pkgs, inputs, outputs, constants, ... }: +{ config, lib, ... }: let cfg = config.services.myService; + inherit (lib) mkDefault mkEnableOption mkIf mkOption types; in { options.services.myService = { - enable = lib.mkEnableOption "My service"; - port = lib.mkOption { - type = lib.types.port; + enable = mkEnableOption "My service"; + port = mkOption { + type = types.port; default = 8080; description = "Port to listen on"; }; }; - config = lib.mkIf cfg.enable { - # Implementation here + config = mkIf cfg.enable { + assertions = [ + { assertion = cfg.port > 1024; message = "Port must be > 1024"; } + ]; + # Implementation }; } ``` -### Naming Conventions -| Type | Convention | Example | -|------|------------|---------| -| Files | kebab-case | `hardware-configuration.nix` | -| Options | camelCase | `services.myService.enable` | -| Variables | camelCase | `let myValue = ...;` | -| Hosts | kebab-case | `cryodev-main`, `cryodev-pi` | +### Key Rules -### Imports +- **Use `lib.mkDefault`** for all module defaults (allows host-level overrides) +- **Use `constants.nix`** for domains, IPs, ports -- never hardcode these +- **Use `lib.utils`** helpers: `mkReverseProxyOption`, `mkVirtualHost`, `mkUrl` +- **Secrets via SOPS** only, never plaintext. Reference: `config.sops.secrets."path".path` +- **Imports**: relative paths for local files, `outputs.nixosModules.*` for shared modules, `inputs.*` for external +- **Assertions** for invalid configurations, `warnings` for non-critical issues +- **`lib.inherit`** pattern: extract needed functions in `let` block + +### Host Service Files + +Each service gets its own file in `hosts//services/`: ```nix -# Local modules: relative paths -imports = [ ./hardware.nix ./networking.nix ]; - -# Shared modules: via outputs -imports = [ outputs.nixosModules.common ]; - -# External inputs -imports = [ inputs.sops-nix.nixosModules.sops ]; -``` - -### Constants -Use `constants.nix` for domains, IPs, and ports: -```nix -{ constants, ... }: +# hosts/cryodev-main/services/myservice.nix +{ outputs, constants, ... }: { - services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = { ... }; + imports = [ outputs.nixosModules.myservice ]; + + services.myservice = { + enable = true; + port = constants.services.myservice.port; + }; + + services.nginx.virtualHosts."${constants.services.myservice.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.myservice.port}"; + }; + }; } ``` -### Error Handling -```nix -config = lib.mkIf cfg.enable { - assertions = [ - { assertion = cfg.port > 1024; message = "Port must be > 1024"; } - ]; - warnings = lib.optional (cfg.debug) "Debug mode enabled!"; -}; -``` +### Special Args Available in Modules -### Option Conflicts -Use `lib.mkDefault` for default values that can be overridden: -```nix -services.nginx.enable = lib.mkDefault true; -``` +- `inputs` -- flake inputs (nixpkgs, sops-nix, comin, headplane, etc.) +- `outputs` -- this flake's outputs (nixosModules, packages) +- `constants` -- values from `constants.nix` (domain, hosts, services) +- `lib` -- nixpkgs.lib extended with `lib.utils` ## Directory Structure ``` . -├── flake.nix # Entry point, inputs/outputs +├── flake.nix # Entry point, inputs/outputs, mkNixosConfiguration ├── constants.nix # Central config (domains, IPs, ports) ├── hosts/ -│ ├── cryodev-main/ # x86_64 server -│ │ ├── default.nix # Host entry point -│ │ ├── hardware.nix # Hardware configuration -│ │ ├── services/ # Service configurations -│ │ └── secrets.yaml # SOPS-encrypted secrets -│ └── cryodev-pi/ # aarch64 Raspberry Pi -├── modules/nixos/ # Reusable modules -│ ├── common/ # Shared base configuration -│ ├── sops/ # Secret management -│ ├── forgejo/ # Git server -│ ├── headscale/ # VPN control server -│ └── ... -├── lib/utils.nix # Helper functions -├── apps/ # Nix apps (rebuild) +│ ├── cryodev-main/ # x86_64 server (services/, secrets.yaml, binfmt.nix) +│ └── cryodev-pi/ # aarch64 RPi (services/, secrets.yaml, sd-image.nix) +├── modules/nixos/ # Reusable modules (common, forgejo, headscale, ...) +├── users/ # User definitions (steffen, ralph, benjamin) +├── apps/ # Nix apps (create, deploy, install, rebuild) +├── lib/utils.nix # Helper functions (mkUrl, mkVirtualHost, ...) ├── pkgs/ # Custom packages ├── overlays/ # Nixpkgs overlays -├── templates/ # Host templates -├── scripts/ # Helper scripts (install.sh) -└── docs/ # Documentation +├── templates/ # Host templates (generic-server, raspberry-pi) +├── deploy.json # Deploy app config (hosts, SSH port) +├── .sops.yaml # SOPS encryption rules (age keys per host) +├── .forgejo/workflows/ # CI pipelines (ci.yml, deploy.yml) +└── docs/ # Documentation (English) ``` -## Key Patterns - -### Adding a New Raspberry Pi Host -1. Copy template: `cp -r templates/raspberry-pi hosts/new-pi` -2. Update `hosts/new-pi/networking.nix` (hostname) -3. Add to `flake.nix`: `new-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/new-pi ];` -4. Add to `.forgejo/workflows/build-pi-image.yml` matrix -5. Push → SD image is built automatically - -### SOPS Secrets -- Secrets encrypted with age using SSH host keys -- Config in `.sops.yaml`, secrets in `hosts//secrets.yaml` -- Reference: `config.sops.secrets."path/to/secret".path` - -### Special Args Available in Modules -- `inputs`: Flake inputs (nixpkgs, sops-nix, etc.) -- `outputs`: This flake's outputs (nixosModules, packages) -- `constants`: Values from `constants.nix` -- `lib`: Extended nixpkgs.lib with `lib.utils` - -## Deployment Workflows - -| Host | Strategy | Trigger | -|------|----------|---------| -| `cryodev-main` | Push via deploy-rs | Forgejo Actions on push to main | -| `cryodev-pi` | Pull via Comin | Automatic polling | -| SD Images | Built in CI | Push to main (for Pi hosts) | - ## Verification Checklist Before committing: - [ ] `nix fmt` passes -- [ ] `nix flake check` passes (or at least `nix eval` works) -- [ ] New hosts added to `flake.nix` +- [ ] `nix flake check` passes (or at least `nix eval` works for both hosts) +- [ ] New hosts added to `flake.nix` nixosConfigurations - [ ] Constants in `constants.nix`, not hardcoded - [ ] Secrets use SOPS, not plaintext +- [ ] New services have their own file in `hosts//services/` +- [ ] New modules registered in `modules/nixos/default.nix` +- [ ] Documentation in English @@ -530,6 +523,22 @@ FILE: constants.nix +================================================ +FILE: deploy.json +================================================ +{ + "sshPort": "2299", + "buildHost": "localhost", + "hosts": [ + { + "name": "cryodev-main", + "address": "steffen@cryodev.xyz" + } + ] +} + + + ================================================ FILE: flake.lock ================================================ @@ -573,28 +582,6 @@ FILE: flake.lock "type": "github" } }, - "deploy-rs": { - "inputs": { - "flake-compat": "flake-compat_2", - "nixpkgs": [ - "nixpkgs" - ], - "utils": "utils" - }, - "locked": { - "lastModified": 1770019181, - "narHash": "sha256-hwsYgDnby50JNVpTRYlF3UR/Rrpt01OrxVuryF40CFY=", - "owner": "serokell", - "repo": "deploy-rs", - "rev": "77c906c0ba56aabdbc72041bf9111b565cdd6171", - "type": "github" - }, - "original": { - "owner": "serokell", - "repo": "deploy-rs", - "type": "github" - } - }, "devshell": { "inputs": { "nixpkgs": [ @@ -633,22 +620,6 @@ FILE: flake.lock } }, "flake-compat_2": { - "flake": false, - "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_3": { "flake": false, "locked": { "lastModified": 1767039857, @@ -664,7 +635,7 @@ FILE: flake.lock "type": "github" } }, - "flake-compat_4": { + "flake-compat_3": { "flake": false, "locked": { "lastModified": 1767039857, @@ -703,7 +674,7 @@ FILE: flake.lock }, "flake-utils": { "inputs": { - "systems": "systems_2" + "systems": "systems" }, "locked": { "lastModified": 1731533236, @@ -721,7 +692,7 @@ FILE: flake.lock }, "flake-utils_2": { "inputs": { - "systems": "systems_3" + "systems": "systems_2" }, "locked": { "lastModified": 1731533236, @@ -739,7 +710,7 @@ FILE: flake.lock }, "git-hooks": { "inputs": { - "flake-compat": "flake-compat_3", + "flake-compat": "flake-compat_2", "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" @@ -879,7 +850,7 @@ FILE: flake.lock "nixos-mailserver": { "inputs": { "blobs": "blobs", - "flake-compat": "flake-compat_4", + "flake-compat": "flake-compat_3", "git-hooks": "git-hooks_2", "nixpkgs": [ "nixpkgs" @@ -986,7 +957,7 @@ FILE: flake.lock "nixpkgs" ], "nuschtosSearch": "nuschtosSearch", - "systems": "systems_4" + "systems": "systems_3" }, "locked": { "lastModified": 1769049374, @@ -1029,7 +1000,6 @@ FILE: flake.lock "root": { "inputs": { "comin": "comin", - "deploy-rs": "deploy-rs", "git-hooks": "git-hooks", "headplane": "headplane", "nixos-mailserver": "nixos-mailserver", @@ -1105,21 +1075,6 @@ FILE: flake.lock "type": "github" } }, - "systems_4": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "treefmt-nix": { "inputs": { "nixpkgs": "nixpkgs" @@ -1137,24 +1092,6 @@ FILE: flake.lock "repo": "treefmt-nix", "type": "github" } - }, - "utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } } }, "root": "root", @@ -1183,9 +1120,6 @@ FILE: flake.nix comin.url = "github:nlewo/comin"; comin.inputs.nixpkgs.follows = "nixpkgs"; - deploy-rs.url = "github:serokell/deploy-rs"; - deploy-rs.inputs.nixpkgs.follows = "nixpkgs"; - nixvim.url = "github:nix-community/nixvim/nixos-25.11"; nixvim.inputs.nixpkgs.follows = "nixpkgs"; @@ -1245,6 +1179,8 @@ FILE: flake.nix }; in { + create = mkApp "create"; + deploy = mkApp "deploy"; install = mkApp "install"; rebuild = mkApp "rebuild"; } @@ -1285,18 +1221,6 @@ FILE: flake.nix pkgs.writeShellScriptBin "pre-commit-run" script ); - deploy = { - nodes = { - cryodev-main = { - hostname = constants.domain; - profiles.system = { - user = "root"; - path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.cryodev-main; - }; - }; - }; - }; - checks = forAllSystems ( system: let @@ -1306,7 +1230,6 @@ FILE: flake.nix inherit system; overlays = [ self.overlays.modifications ]; }; - deployChecks = inputs.deploy-rs.lib.${system}.deployChecks self.deploy; in { pre-commit-check = inputs.git-hooks.lib.${system}.run { @@ -1320,7 +1243,6 @@ FILE: flake.nix # package = overlaidPkgs.package; }; } - // deployChecks ); }; } @@ -1331,19 +1253,299 @@ FILE: flake.nix FILE: .sops.yaml ================================================ keys: - - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t + - &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local) - &cryodev-main_key age1y6hushuapy0k04mrvvpev0t8lq44w904r596jus44nhkflky0yhqgq2xx6 creation_rules: - path_regex: hosts/cryodev-main/secrets.yaml$ key_groups: - age: - - *admin_key + - *steffen_key - *cryodev-main_key - path_regex: hosts/cryodev-pi/secrets.yaml$ key_groups: - age: - - *admin_key - # - *pi_key # Add pi key here once obtained + - *steffen_key + # - *cryodev-pi_key # Add after Pi installation + + + +================================================ +FILE: apps/create/create.sh +================================================ +#!/usr/bin/env bash + +# Create a new host from a template + +FLAKE_DIR="." +TEMPLATE="" +HOSTNAME="" +SYSTEM="" + +SEPARATOR="________________________________________" + +usage() { + cat <&2 + exit 1 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -t|--template) TEMPLATE="$2"; shift 2 ;; + -n|--hostname) HOSTNAME="$2"; shift 2 ;; + -s|--system) SYSTEM="$2"; shift 2 ;; + -f|--flake) FLAKE_DIR="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) error "Unknown option: $1" ;; + esac +done + +# Validate +[[ -z "$TEMPLATE" ]] && error "Template is required (-t)" +[[ -z "$HOSTNAME" ]] && error "Hostname is required (-n)" + +TEMPLATE_DIR="$FLAKE_DIR/templates/$TEMPLATE" +HOST_DIR="$FLAKE_DIR/hosts/$HOSTNAME" + +[[ ! -d "$TEMPLATE_DIR" ]] && error "Template '$TEMPLATE' not found in $TEMPLATE_DIR" +[[ -d "$HOST_DIR" ]] && error "Host '$HOSTNAME' already exists in $HOST_DIR" + +# Derive system from template if not specified +if [[ -z "$SYSTEM" ]]; then + case "$TEMPLATE" in + generic-server) SYSTEM="x86_64-linux" ;; + raspberry-pi) SYSTEM="aarch64-linux" ;; + *) error "Cannot derive system for template '$TEMPLATE'. Use -s to specify." ;; + esac +fi + +echo "$SEPARATOR" +echo "Creating host '$HOSTNAME' from template '$TEMPLATE'" +echo " System: $SYSTEM" +echo " Target: $HOST_DIR" +echo "$SEPARATOR" + +# Copy template +cp -r "$TEMPLATE_DIR" "$HOST_DIR" + +# Remove template flake.nix (not needed in host dir) +rm -f "$HOST_DIR/flake.nix" + +# Replace hostname in networking.nix +sed -i "s/networking.hostName = \".*\"/networking.hostName = \"$HOSTNAME\"/" "$HOST_DIR/networking.nix" + +# Create empty secrets.yaml placeholder +touch "$HOST_DIR/secrets.yaml" + +# Add to git +git -C "$FLAKE_DIR" add "$HOST_DIR" + +echo "$SEPARATOR" +echo "Host '$HOSTNAME' created successfully." +echo "" +echo "Next steps:" +echo " 1. Add to flake.nix:" +echo "" +echo " $HOSTNAME = mkNixosConfiguration \"$SYSTEM\" [ ./hosts/$HOSTNAME ];" +echo "" +echo " 2. Update hardware.nix and disks.sh for your hardware" +echo " 3. Update .sops.yaml with creation rules for hosts/$HOSTNAME/secrets.yaml" +echo " 4. Follow the first-install guide: docs/getting-started/first-install.md" + + + +================================================ +FILE: apps/create/default.nix +================================================ +{ + writeShellApplication, + git, + gnused, + ... +}: + +let + name = "create"; + text = builtins.readFile ./${name}.sh; +in +writeShellApplication { + inherit name text; + meta.mainProgram = name; + + runtimeInputs = [ + git + gnused + ]; +} + + + +================================================ +FILE: apps/deploy/default.nix +================================================ +{ + writeShellApplication, + jq, + ... +}: + +let + name = "deploy"; + text = builtins.readFile ./${name}.sh; +in +writeShellApplication { + inherit name text; + meta.mainProgram = name; + + runtimeInputs = [ + jq + ]; +} + + + +================================================ +FILE: apps/deploy/deploy.sh +================================================ +#!/usr/bin/env bash + +# defaults +FLAKE_URI="." +CONFIG_FILE="./deploy.json" +ACTION="switch" +USE_SUDO=true +DO_BUILD=true +FILTER_HOSTS=() + +usage() { + cat < $1\033[0m"; } +success() { echo -e "\033[0;32m$1\033[0m"; } +error() { echo -e "\033[0;31mError: $1\033[0m" >&2; exit 1; } + +while [[ $# -gt 0 ]]; do + case "$1" in + switch|boot|test) ACTION="$1"; shift ;; + -n|--host) FILTER_HOSTS+=("$2"); shift 2 ;; + -f|--flake) FLAKE_URI="$2"; shift 2 ;; + -c|--config) CONFIG_FILE="$2"; shift 2 ;; + --no-sudo) USE_SUDO=false; shift ;; + --skip-build) DO_BUILD=false; shift ;; + -h|--help) usage; exit 0 ;; + *) error "Invalid argument '$1'" ;; + esac +done + +command -v jq &> /dev/null || error "jq is not installed." +[ -f "$CONFIG_FILE" ] || error "Config '$CONFIG_FILE' not found." + +BUILD_HOST=$(jq -r '.buildHost // "localhost"' "$CONFIG_FILE") +[[ "$BUILD_HOST" =~ ^(127\.0\.0\.1|::1)$ ]] && BUILD_HOST="localhost" + +SSH_PORT=$(jq -r '.sshPort // "22"' "$CONFIG_FILE") +export NIX_SSHOPTS="-p $SSH_PORT" + +mapfile -t ALL_ENTRIES < <(jq -r '.hosts[] | "\(.name) \(.address)"' "$CONFIG_FILE") +[ ${#ALL_ENTRIES[@]} -eq 0 ] && error "No hosts defined in $CONFIG_FILE" + +# Filter hosts if -n was provided +HOST_ENTRIES=() +if [ ${#FILTER_HOSTS[@]} -gt 0 ]; then + for entry in "${ALL_ENTRIES[@]}"; do + read -r name _address <<< "$entry" + for filter in "${FILTER_HOSTS[@]}"; do + if [[ "$name" == "$filter" ]]; then + HOST_ENTRIES+=("$entry") + break + fi + done + done + # Check for unknown hosts + for filter in "${FILTER_HOSTS[@]}"; do + found=false + for entry in "${ALL_ENTRIES[@]}"; do + read -r name _ <<< "$entry" + [[ "$name" == "$filter" ]] && found=true && break + done + [[ "$found" == false ]] && error "Host '$filter' not found in $CONFIG_FILE" + done + [ ${#HOST_ENTRIES[@]} -eq 0 ] && error "No matching hosts found" +else + HOST_ENTRIES=("${ALL_ENTRIES[@]}") +fi + +echo "Action: $ACTION" +echo "Flake: $FLAKE_URI" +echo "Builder: $BUILD_HOST" +echo "SSH Port: $SSH_PORT" +echo "Hosts: $(printf '%s ' "${HOST_ENTRIES[@]}" | sed 's/ [^ ]*//g; s/ */, /g')" + +if [ "$DO_BUILD" = true ]; then + _status "Building configurations..." + for entry in "${HOST_ENTRIES[@]}"; do + read -r name address <<< "$entry" + echo "------------------------------------------------" + echo "Building host '$name':" + + CMD=("nixos-rebuild" "build" "--flake" "${FLAKE_URI}#${name}") + [[ "$BUILD_HOST" != "localhost" ]] && CMD+=("--build-host" "$BUILD_HOST") + + "${CMD[@]}" || error "Build failed for $name" + success "Build for host '$name' successful." + done +fi + +_status "Deploying to targets..." +for entry in "${HOST_ENTRIES[@]}"; do + read -r name address <<< "$entry" + echo "------------------------------------------------" + echo "Deploying to host '$name' ($address):" + + CMD=("nixos-rebuild" "$ACTION" "--flake" "${FLAKE_URI}#${name}" "--target-host" "$address") + [[ "$BUILD_HOST" != "localhost" ]] && CMD+=("--build-host" "$BUILD_HOST") + [[ "$USE_SUDO" = true ]] && CMD+=("--sudo" "--ask-sudo-password") + + "${CMD[@]}" || error "Activation failed for $name" + success "Host '$name' updated." +done + +success "Deployment complete." @@ -1834,34 +2036,34 @@ FILE: docs/index.md ================================================ # Cryodev NixOS Configuration Documentation -Willkommen zur Dokumentation der **cryodev** NixOS-Infrastruktur. +Welcome to the documentation for the **cryodev** NixOS infrastructure. ## Quick Links ### Getting Started -- [Voraussetzungen](getting-started/prerequisites.md) - Benötigte Tools -- [Neuen Raspberry Pi hinzufügen](getting-started/new-client.md) - Kompletter Workflow für neue Clients -- [SD-Image Referenz](getting-started/sd-image.md) - Details zum Image-Build -- [Erstinstallation (Server)](getting-started/first-install.md) - Bootstrap für x86_64 Hosts -- [Neuinstallation](getting-started/reinstall.md) - Reinstall mit Hardware-Änderungen +- [Prerequisites](getting-started/prerequisites.md) - Required tools +- [Adding a New Raspberry Pi](getting-started/new-client.md) - Complete workflow for new clients +- [SD Image Reference](getting-started/sd-image.md) - Details on image building +- [First Installation (Server)](getting-started/first-install.md) - Bootstrap for x86_64 hosts +- [Reinstallation](getting-started/reinstall.md) - Reinstall with hardware changes ### Services -- [SOPS Secrets](services/sops.md) - Geheimnisverwaltung mit sops-nix -- [Headscale](services/headscale.md) - Self-hosted Tailscale Server -- [Headplane](services/headplane.md) - Web-UI für Headscale -- [Tailscale](services/tailscale.md) - Mesh-VPN Client -- [Mailserver](services/mailserver.md) - E-Mail Stack (Postfix/Dovecot) -- [Forgejo](services/forgejo.md) - Git-Hosting mit CI/CD -- [Netdata](services/netdata.md) - Monitoring und Alerting +- [SOPS Secrets](services/sops.md) - Secret management with sops-nix +- [Headscale](services/headscale.md) - Self-hosted Tailscale server +- [Headplane](services/headplane.md) - Web UI for Headscale +- [Tailscale](services/tailscale.md) - Mesh VPN client +- [Mailserver](services/mailserver.md) - Email stack (Postfix/Dovecot) +- [Forgejo](services/forgejo.md) - Git hosting with CI/CD +- [Netdata](services/netdata.md) - Monitoring and alerting ### Deployment -- [Continuous Deployment](deployment/cd.md) - Push- und Pull-basiertes Deployment -- [DNS-Konfiguration](deployment/dns.md) - Benötigte DNS-Einträge +- [Continuous Deployment](deployment/cd.md) - Push- and pull-based deployment +- [DNS Configuration](deployment/dns.md) - Required DNS records -## Architektur +## Architecture ``` Internet @@ -1891,41 +2093,41 @@ Willkommen zur Dokumentation der **cryodev** NixOS-Infrastruktur. +-------------------+ ``` -## Installations-Szenarien +## Installation Scenarios -| Szenario | Beschreibung | Anleitung | -|----------|--------------|-----------| -| **Neuer Raspberry Pi** | Config erstellen → Image bauen → Flashen | [new-client.md](getting-started/new-client.md) | -| **Erstinstallation (Server)** | x86_64 Host, manuelle Installation | [first-install.md](getting-started/first-install.md) | -| **Neuinstallation** | Bestehender Host, neue Hardware | [reinstall.md](getting-started/reinstall.md) | +| Scenario | Description | Guide | +|----------|-------------|-------| +| **New Raspberry Pi** | Create config, build image, flash | [new-client.md](getting-started/new-client.md) | +| **First Installation (Server)** | x86_64 host, manual installation | [first-install.md](getting-started/first-install.md) | +| **Reinstallation** | Existing host, new hardware | [reinstall.md](getting-started/reinstall.md) | -Für Raspberry Pi: [SD-Image Referenz](getting-started/sd-image.md) +For Raspberry Pi: [SD Image Reference](getting-started/sd-image.md) -## Verzeichnisstruktur +## Directory Structure ``` . ├── flake.nix # Entry point, inputs and outputs -├── constants.nix # Zentrale Config (Domains, IPs, Ports) -├── hosts/ # Host-spezifische Konfigurationen +├── constants.nix # Central configuration (domains, IPs, ports) +├── hosts/ # Host-specific configurations │ ├── cryodev-main/ │ └── cryodev-pi/ -├── modules/ # Wiederverwendbare NixOS-Module +├── modules/ # Reusable NixOS modules │ └── nixos/ -├── pkgs/ # Eigene Pakete -├── overlays/ # Nixpkgs Overlays -├── templates/ # Templates für neue Hosts -├── scripts/ # Helper-Scripts (install.sh) -├── apps/ # Nix Apps (rebuild) -└── lib/ # Helper-Funktionen (utils.nix) +├── pkgs/ # Custom packages +├── overlays/ # Nixpkgs overlays +├── templates/ # Templates for new hosts +├── scripts/ # Helper scripts (install.sh) +├── apps/ # Nix apps (rebuild) +└── lib/ # Helper functions (utils.nix) ``` -## Deployment-Strategien +## Deployment Strategies -| Host | Strategie | Tool | Beschreibung | -|------|-----------|------|--------------| -| `cryodev-main` | Push-basiert | deploy-rs via Forgejo Actions | Sofortige Updates bei Push | -| `cryodev-pi` | Pull-basiert | Comin | Pollt Repository auf Änderungen | +| Host | Strategy | Tool | Description | +|------|----------|------|-------------| +| `cryodev-main` | Pull-based | Comin | Polls the repository for changes | +| `cryodev-pi` | Pull-based | Comin | Polls the repository for changes | @@ -1934,122 +2136,39 @@ FILE: docs/deployment/cd.md ================================================ # Continuous Deployment -The cryodev infrastructure uses two deployment strategies optimized for different host types. +All hosts use **Comin** (pull-based) for automatic deployment. ## Overview | Host | Strategy | Tool | Trigger | |------|----------|------|---------| -| `cryodev-main` | Push-based | deploy-rs | Git push via Forgejo Actions | -| `cryodev-pi` | Pull-based | Comin | Periodic polling | +| `cryodev-main` | Pull-based | Comin | Automatic polling | +| `cryodev-pi` | Pull-based | Comin | Automatic polling | -## Push-based Deployment (cryodev-main) - -### How It Works +## How It Works 1. Developer pushes to `main` branch -2. Forgejo Actions workflow triggers -3. `deploy-rs` connects via SSH and deploys +2. CI (Forgejo Actions) runs flake-check and builds all hosts +3. Comin on each host periodically polls the Git repository +4. On changes, Comin builds and activates the new configuration -### Setup - -#### 1. Generate Deploy Key - -```bash -ssh-keygen -t ed25519 -f deploy_key -C "forgejo-actions" -``` - -#### 2. Add Public Key to Server - -On `cryodev-main`: - -```bash -echo "PUBLIC_KEY_CONTENT" >> /root/.ssh/authorized_keys -``` - -#### 3. Add Private Key to Forgejo - -1. Go to Repository Settings > Secrets -2. Add secret named `DEPLOY_SSH_KEY` -3. Paste the private key content - -#### 4. Workflow Configuration - -`.forgejo/workflows/deploy.yaml`: - -```yaml -name: Deploy -on: - push: - branches: [main] - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v24 - - run: nix flake check - - deploy: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v24 - - - name: Setup SSH - env: - SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }} - run: | - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan cryodev-main >> ~/.ssh/known_hosts - - - name: Deploy - run: nix run github:serokell/deploy-rs -- .#cryodev-main -``` - -### Rollback - -deploy-rs automatically rolls back if the new configuration fails health checks. - -Manual rollback: - -```bash -# List generations -sudo nix-env -p /nix/var/nix/profiles/system --list-generations - -# Rollback to previous -sudo nixos-rebuild switch --rollback -``` - -## Pull-based Deployment (cryodev-pi) - -### How It Works - -1. Comin periodically polls the Git repository -2. On changes, it builds and activates the new configuration -3. Works through NAT without incoming connections - -### Configuration +## Configuration ```nix -# hosts/cryodev-pi/services/comin.nix +# hosts//services/comin.nix { services.comin = { enable = true; remotes = [{ name = "origin"; - url = "https://git.cryodev.xyz/steffen/cryodev-server.git"; + url = "https://git.cryodev.xyz/steffen/cryodev.git"; branches.main.name = "main"; }]; }; } ``` -### Monitoring +## Monitoring Check Comin status: @@ -2064,7 +2183,7 @@ Force immediate update: sudo systemctl restart comin ``` -### Troubleshooting +## Troubleshooting If Comin fails to build: @@ -2074,24 +2193,30 @@ sudo journalctl -u comin --since "1 hour ago" # Manual build test cd /var/lib/comin/repo -nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel +nix build .#nixosConfigurations..config.system.build.toplevel +``` + +## Rollback + +```bash +# List generations +sudo nix-env -p /nix/var/nix/profiles/system --list-generations + +# Rollback to previous +sudo nixos-rebuild switch --rollback ``` ## Manual Deployment -For hosts not using automated deployment: +For initial setup or emergencies: ```bash -# Build locally -nix build .#nixosConfigurations..config.system.build.toplevel +# Using the deploy app +nix run .#deploy -- -n -# Deploy with nixos-rebuild -nixos-rebuild switch --flake .# \ - --target-host @ --use-remote-sudo \ - --ssh-option="-p 2299" - -# Or using deploy-rs -nix run github:serokell/deploy-rs -- .# +# Or manually with nixos-rebuild +NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ + --target-host @ --sudo --ask-sudo-password ``` ## Testing Changes @@ -2126,6 +2251,8 @@ Required DNS records for the cryodev infrastructure. |----------|------|-------|---------| | `@` | A | `` | Main server | | `@` | AAAA | `` | Main server (IPv6) | +| `www` | A | `` | www redirect | +| `www` | AAAA | `` | www redirect (IPv6) | | `mail` | A | `` | Mail server | | `mail` | AAAA | `` | Mail server (IPv6) | @@ -2145,7 +2272,32 @@ Required DNS records for the cryodev infrastructure. | `@` | MX | `10 mail.cryodev.xyz.` | Mail delivery | | `@` | TXT | `"v=spf1 mx ~all"` | SPF | | `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC | -| `mail._domainkey` | TXT | `"v=DKIM1; k=rsa; p=..."` | DKIM | +| `mail._domainkey` | TXT | *(see below)* | DKIM | + +### Reverse DNS (PTR) + +For reliable mail delivery, a **PTR record** must be configured at the hosting +provider (not in the domain's DNS panel): + +| IP | PTR Value | +|----|-----------| +| `` | `mail.cryodev.xyz` | +| `` | `mail.cryodev.xyz` | + +#### Hetzner Robot (Dedicated Server) + +1. [robot.hetzner.com](https://robot.hetzner.com) > **Server** > Select the server +2. **IPs** tab +3. Click the **pencil icon** next to the IPv4 address +4. Enter `mail.cryodev.xyz` and save +5. For IPv6: Under **Subnets**, repeat the same for the primary IPv6 address + +#### Hetzner Cloud + +1. [cloud.hetzner.com](https://cloud.hetzner.com) > Select the server +2. **Networking** tab +3. Under "Primary IP", click the IP > **Reverse DNS** +4. Enter `mail.cryodev.xyz` (for both IPv4 and IPv6) ## Getting the DKIM Key @@ -2157,6 +2309,18 @@ sudo cat /var/dkim/cryodev.xyz.mail.txt Add this as a TXT record for `mail._domainkey.cryodev.xyz`. +## Complete Checklist + +- [ ] A/AAAA for `@` (root domain) +- [ ] A/AAAA for `www` +- [ ] A/AAAA for `mail` +- [ ] CNAME for `git`, `headscale`, `headplane`, `netdata` +- [ ] MX record +- [ ] TXT for SPF (`v=spf1 mx ~all`) +- [ ] TXT for DMARC (`v=DMARC1; p=none`) +- [ ] TXT for DKIM (`mail._domainkey` -- after first deployment) +- [ ] PTR record at hosting provider (reverse DNS) + ## Verification ### Check DNS Propagation @@ -2176,6 +2340,9 @@ dig TXT mail._domainkey.cryodev.xyz # DMARC dig TXT _dmarc.cryodev.xyz + +# Reverse DNS +dig -x ``` ### Online Tools @@ -2190,7 +2357,7 @@ For initial setup, use low TTLs (300 seconds) to allow quick changes. After verification, increase to: - A/AAAA records: 3600 (1 hour) -- CNAME records: 3600 (1 hour) +- CNAME records: 3600 (1 hour) - MX records: 3600 (1 hour) - TXT records: 3600 (1 hour) @@ -2200,7 +2367,7 @@ Ensure these ports are open on `cryodev-main`: | Port | Protocol | Service | |------|----------|---------| -| 22 | TCP | SSH | +| 2299 | TCP | SSH | | 80 | TCP | HTTP (ACME/redirect) | | 443 | TCP | HTTPS | | 25 | TCP | SMTP | @@ -2213,50 +2380,47 @@ Ensure these ports are open on `cryodev-main`: ================================================ FILE: docs/getting-started/first-install.md ================================================ -# Erstinstallation (x86_64 Server) +# Initial Installation (x86_64 Server) -Diese Anleitung beschreibt die **Erstinstallation** eines neuen x86_64 Servers (z.B. cryodev-main). +This guide describes the **initial installation** of a new x86_64 server (e.g. cryodev-main). -> **Fuer Raspberry Pi:** Siehe [SD-Image erstellen](sd-image.md). +> **For Raspberry Pi:** See [Creating an SD Image](sd-image.md). -## Uebersicht +## Overview -Bei der Erstinstallation gibt es ein Henne-Ei-Problem: -- SOPS-Secrets werden mit dem SSH-Host-Key verschluesselt -- Der SSH-Host-Key wird erst bei der Installation generiert -- Daher: **Erst ohne Secrets installieren, dann Secrets konfigurieren** +During initial installation there is a chicken-and-egg problem: +- SOPS secrets are encrypted with the SSH host key +- The SSH host key is only generated during installation +- Therefore: **Install without secrets first, then configure secrets** -### Ablauf +### Process ``` -1. Services deaktivieren (die Secrets brauchen) -2. NixOS installieren -3. SSH-Host-Key extrahieren, SOPS konfigurieren, Secrets erstellen -4. Services reaktivieren und deployen +1. Disable services (that require secrets) +2. Install NixOS +3. Extract SSH host key, configure SOPS, create immediately available secrets +4. Enable stage-1 services and deploy (Headscale, Forgejo, Mail, Nginx) +5. Generate remaining secrets (Tailscale, Headplane, Forgejo Runner) +6. Enable stage-2 services and perform final deployment ``` -## Schritt 1: Host-Konfiguration vorbereiten +## Step 1: Prepare Host Configuration -> Falls der Host bereits in `hosts/` und `flake.nix` existiert, ueberspringe 1.1-1.3. +> If the host already exists in `hosts/` and `flake.nix`, skip 1.1-1.2. -### 1.1 Template kopieren +### 1.1 Create Host from Template ```bash -cp -r templates/generic-server hosts/ +nix run .#create -- -t generic-server -n ``` -### 1.2 Hostname setzen +The script: +- Copies the template to `hosts//` +- Sets the hostname in `networking.nix` +- Creates an empty `secrets.yaml` +- Adds the files to Git -`hosts//networking.nix`: - -```nix -{ - networking.hostName = ""; - networking.domain = "cryodev.xyz"; -} -``` - -### 1.3 In flake.nix registrieren +### 1.2 Register in flake.nix ```nix nixosConfigurations = { @@ -2264,22 +2428,24 @@ nixosConfigurations = { }; ``` -### 1.4 Services temporaer deaktivieren +Also adjust `hardware.nix` and `disks.sh` for the target hardware. -Alle Services, die SOPS-Secrets referenzieren, muessen fuer die Erstinstallation deaktiviert werden. Andernfalls schlaegt die Installation fehl, weil die Secrets noch nicht entschluesselt werden koennen. +### 1.4 Temporarily Disable Services -In `hosts//services/default.nix` die entsprechenden Imports auskommentieren: +All services that reference SOPS secrets must be disabled for the initial installation. Otherwise the installation will fail because the secrets cannot yet be decrypted. + +In `hosts//services/default.nix`, comment out the corresponding imports: ```nix { imports = [ - # Deaktiviert bis SOPS-Secrets konfiguriert sind: - # ./forgejo.nix # braucht: forgejo-runner/token, forgejo/mail-pw - # ./headplane.nix # braucht: headplane/cookie_secret, headplane/agent_pre_authkey - # ./mailserver.nix # braucht: mailserver/accounts/* - # ./tailscale.nix # braucht: tailscale/auth-key + # Disabled until SOPS secrets are configured: + # ./forgejo.nix # requires: forgejo-runner/token, forgejo/mail-pw + # ./headplane.nix # requires: headplane/cookie_secret, headplane/agent_pre_authkey + # ./mailserver.nix # requires: mailserver/accounts/* + # ./tailscale.nix # requires: tailscale/auth-key - # Diese Services brauchen keine Secrets: + # These services do not require secrets: ./headscale.nix ./netdata.nix ./nginx.nix @@ -2289,7 +2455,7 @@ In `hosts//services/default.nix` die entsprechenden Imports auskomment } ``` -Zusaetzlich in `hosts//services/sops.nix` die Secrets-Definitionen auskommentieren: +Additionally, in `hosts//services/sops.nix`, comment out the secret definitions: ```nix sops = { @@ -2301,32 +2467,32 @@ sops = { }; ``` -### 1.5 Konfiguration testen +### 1.5 Test the Configuration ```bash nix eval .#nixosConfigurations..config.system.build.toplevel.name ``` -## Schritt 2: Installation durchfuehren +## Step 2: Perform Installation -### 2.1 NixOS ISO booten +### 2.1 Boot NixOS ISO -Vom [NixOS Minimal ISO](https://nixos.org/download/#nixos-iso) booten (USB/CD). +Boot from the [NixOS Minimal ISO](https://nixos.org/download/#nixos-iso) (USB/CD). -### 2.2 Netzwerk und SSH einrichten +### 2.2 Set Up Network and SSH ```bash -passwd # Root-Passwort setzen fuer SSH-Zugang -ip a # IP-Adresse ermitteln +passwd # Set root password for SSH access +ip a # Determine IP address ``` -Optional per SSH verbinden (bequemer): +Optionally connect via SSH (more convenient): ```bash ssh -o StrictHostKeyChecking=no root@ ``` -### 2.3 Installieren +### 2.3 Install ```bash nix --experimental-features "nix-command flakes" run \ @@ -2335,20 +2501,20 @@ nix --experimental-features "nix-command flakes" run \ -r ``` -Alternativ, falls das Repository bereits unter `/tmp/nixos` geklont wurde: +Alternatively, if the repository has already been cloned to `/tmp/nixos`: ```bash nix --experimental-features "nix-command flakes" run /tmp/nixos#install -- -n ``` -> **Hinweis:** Die Disk-ID in `hosts//disks.sh` muss zur Hardware passen. -> Pruefen mit `ls -la /dev/disk/by-id/`. +> **Note:** The disk ID in `hosts//disks.sh` must match the hardware. +> Verify with `ls -la /dev/disk/by-id/`. -Das Script: -1. Klont das Repository (bei `-r`) -2. Partitioniert die Disk (via `disks.nix` oder `disks.sh`) -3. Generiert `hardware.nix` (falls nicht vorhanden) -4. Installiert NixOS +The script: +1. Clones the repository (when using `-r`) +2. Partitions the disk (via `disks.nix` or `disks.sh`) +3. Generates `hardware.nix` (if not present) +4. Installs NixOS ### 2.4 Reboot @@ -2356,226 +2522,293 @@ Das Script: reboot ``` -## Schritt 3: SOPS-Secrets konfigurieren +## Step 3: Configure SOPS Secrets -Nach dem ersten Boot einloggen (Passwort: `changeme`, sofort aendern mit `passwd`). +After the first boot, log in (password: `changeme`, change immediately with `passwd`). -### 3.1 SSH-Host-Key zu Age-Key konvertieren +### 3.1 Convert SSH Host Key to Age Key -Auf dem **neuen Server**: +On the **new server**: ```bash nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' ``` -Ausgabe notieren (z.B. `age1abc123...`). +Note the output (e.g. `age1abc123...`). -Alternativ remote: +Alternatively, remotely: ```bash nix-shell -p ssh-to-age --run 'ssh-keyscan -p 2299 -t ed25519 | ssh-to-age' ``` -### 3.2 .sops.yaml aktualisieren +### 3.2 Update .sops.yaml -Auf dem **Entwicklungsrechner** den neuen Host-Key in `.sops.yaml` eintragen: +On the **development machine**, add the new host key to `.sops.yaml`: ```yaml keys: - - &admin_key age1e8p... # Dein lokaler Admin-Key - - &hostname_key age1abc... # Key von Schritt 3.1 + - &steffen_key age1e8p... # steffen (local) + - &hostname_key age1abc... # Key from step 3.1 creation_rules: - path_regex: hosts//secrets.yaml$ key_groups: - age: - - *admin_key + - *steffen_key - *hostname_key ``` -### 3.3 Secrets erstellen +### 3.3 Create Secrets -Secrets-Datei oeffnen: +Open the secrets file: ```bash sops hosts//secrets.yaml ``` -Die folgende Tabelle zeigt alle Secrets fuer **cryodev-main** und wie sie generiert werden: +The following table shows all secrets for **cryodev-main** and how they are generated: -#### Sofort erstellbare Secrets +#### Immediately Available Secrets -Diese Secrets haben keine Abhaengigkeiten und koennen direkt generiert werden: +These secrets have no dependencies and can be generated directly: -| Secret | Befehl | -|--------|--------| +| Secret | Command | +|--------|---------| | `headplane/cookie_secret` | `openssl rand -hex 16` | -| `mailserver/accounts/admin` | `mkpasswd -sm bcrypt` (Passwort merken!) | -| `mailserver/accounts/forgejo` | `mkpasswd -sm bcrypt` (Passwort merken!) | -| `forgejo/mail-pw` | Klartext-Passwort das zum bcrypt-Hash von `mailserver/accounts/forgejo` passt | +| `mailserver/accounts/admin` | `mkpasswd -sm bcrypt` (remember the password!) | +| `mailserver/accounts/forgejo` | `mkpasswd -sm bcrypt` (remember the password!) | +| `forgejo/mail-pw` | Plaintext password matching the bcrypt hash of `mailserver/accounts/forgejo` | -#### Secrets die laufende Services brauchen +#### Secrets That Require Running Services -Diese Secrets koennen erst erstellt werden, nachdem die entsprechenden Services laufen. Bis dahin **Platzhalter** eintragen (z.B. `placeholder`): +These secrets can only be created after step 4. **Do not add them yet** -- they will be added later. -| Secret | Befehl | Voraussetzung | -|--------|--------|---------------| -| `tailscale/auth-key` | `sudo headscale preauthkeys create --expiration 99y --reusable --user default` | Headscale laeuft | -| `headplane/agent_pre_authkey` | `sudo headscale users create headplane-agent && sudo headscale preauthkeys create --expiration 99y --user headplane-agent` | Headscale laeuft | -| `forgejo-runner/token` | Forgejo Admin Panel > Actions > Runners > Create Runner | Forgejo laeuft | +| Secret | Command | Prerequisite | +|--------|---------|--------------| +| `tailscale/auth-key` | See steps 4.1-4.2 | Headscale is running | +| `headplane/agent_pre_authkey` | See steps 4.1-4.2 | Headscale is running | +| `forgejo-runner/token` | Forgejo Admin Panel > Actions > Runners > Create Runner | Forgejo is running | -#### Beispiel secrets.yaml (Klartext vor Verschluesselung) +#### Example secrets.yaml (Plaintext Before Encryption) ```yaml -tailscale: - auth-key: "placeholder" -forgejo-runner: - token: "placeholder" headplane: cookie_secret: "a1b2c3d4e5f6..." - agent_pre_authkey: "placeholder" mailserver: accounts: admin: "$2b$05$..." forgejo: "$2b$05$..." forgejo: - mail-pw: "das-klartext-passwort" + mail-pw: "the-plaintext-password" ``` -### 3.4 Services reaktivieren +### 3.4 Gradually Re-enable Services -- Stage 1 -Auf dem **Entwicklungsrechner** die in Schritt 1.4 auskommentierten Imports in `hosts//services/default.nix` wieder aktivieren: +> **Important:** Services that require Headscale or Forgejo secrets (Tailscale, +> Headplane, Forgejo Runner) must **not** be enabled yet, as these +> secrets can only be generated once those services are running. + +On the **development machine**, in `hosts//services/default.nix`, enable +the services **without external dependencies**: ```nix { imports = [ + # Stage 1: Services without external dependencies ./forgejo.nix - ./headplane.nix ./headscale.nix ./mailserver.nix ./netdata.nix ./nginx.nix ./openssh.nix ./sops.nix - ./tailscale.nix + + # Stage 2: Enable only after step 4 + # ./forgejo-runner.nix # requires: forgejo-runner/token (Forgejo) + # ./headplane.nix # requires: headplane/agent_pre_authkey (Headscale) + # ./tailscale.nix # requires: tailscale/auth-key (Headscale) ]; } ``` -Ebenso in `hosts//services/sops.nix` die Secrets-Definitionen wieder einkommentieren. - -### 3.5 Deployen +### 3.5 Deploy (Stage 1) ```bash -nixos-rebuild switch --flake .# \ - --target-host @ --use-remote-sudo \ - --ssh-option="-p 2299" +nix run .#deploy -- -n ``` -## Schritt 4: Platzhalter-Secrets ersetzen +This uses the configuration from `deploy.json`. Alternatively, deploy manually: -Nachdem der Server mit Headscale und Forgejo laeuft, die Platzhalter durch echte Werte ersetzen: +```bash +NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ + --target-host @ --sudo --ask-sudo-password +``` -1. **Headscale-User anlegen** (auf dem Server): +After this deployment, Headscale, Forgejo, Mailserver, and Nginx are running. + +### 3.6 Create Forgejo Admin Account + +On first start, Forgejo has no users. Create an admin account via CLI +(on the **server**): + +```bash +forgejo admin user create \ + --username \ + --email @ \ + --password \ + --admin +``` + +> **Note:** The `forgejo` shell alias is provided by the module and automatically +> runs the command as the `forgejo` user with the correct config. +> If the alias is not available, start a new shell (`bash` or `zsh`). +> +> Since `DISABLE_REGISTRATION = true` is set, new accounts +> can only be created via CLI. + +## Step 4: Generate Remaining Secrets and Enable All Services + +After the server is running with Headscale and Forgejo: + +1. **Create Headscale users** (on the server): ```bash sudo headscale users create default sudo headscale users create headplane-agent ``` -2. **Preauth-Keys generieren**: +2. **Determine user IDs** (needed for the preauth keys): ```bash - # Fuer Tailscale - sudo headscale preauthkeys create --expiration 99y --reusable --user default - - # Fuer Headplane Agent - sudo headscale preauthkeys create --expiration 99y --user headplane-agent + sudo headscale users list ``` -3. **Forgejo-Runner-Token** ueber das Forgejo Admin Panel erstellen: + The output shows the numeric IDs (e.g. `1` for default, `2` for headplane-agent). + +3. **Generate preauth keys** (using the IDs from step 2): + + ```bash + # For Tailscale (use the user ID of "default") + sudo headscale preauthkeys create --expiration 99y --reusable --user + + # For Headplane Agent (use the user ID of "headplane-agent") + sudo headscale preauthkeys create --expiration 99y --user + ``` + +4. **Create the Forgejo Runner token** via the Forgejo Admin Panel: Administration > Actions > Runners > Create new Runner -4. **Secrets aktualisieren**: +5. **Add the remaining secrets**: ```bash sops hosts//secrets.yaml - # Platzhalter durch echte Werte ersetzen ``` -5. **Erneut deployen**: + Add the missing secrets: + + ```yaml + tailscale: + auth-key: "tskey-..." + forgejo-runner: + token: "..." + headplane: + agent_pre_authkey: "..." + ``` + +6. **Enable stage-2 services** in `hosts//services/default.nix`: + + ```nix + { + imports = [ + ./forgejo.nix + ./forgejo-runner.nix + ./headplane.nix + ./headscale.nix + ./mailserver.nix + ./netdata.nix + ./nginx.nix + ./openssh.nix + ./sops.nix + ./tailscale.nix + ]; + } + ``` + +6. **Deploy again**: ```bash - nixos-rebuild switch --flake .# \ - --target-host @ --use-remote-sudo \ - --ssh-option="-p 2299" + nix run .#deploy -- -n ``` -## Naechste Schritte +## Next Steps -- [SOPS-Referenz](../services/sops.md) -- Detail-Dokumentation zur Secret-Verwaltung -- [SD-Image erstellen](sd-image.md) -- Raspberry Pi installieren -- [CD einrichten](../deployment/cd.md) -- Automatisches Deployment +- [SOPS Reference](../services/sops.md) -- Detailed documentation on secret management +- [Creating an SD Image](sd-image.md) -- Install Raspberry Pi +- [Set Up CD](../deployment/cd.md) -- Automatic deployment ================================================ FILE: docs/getting-started/new-client.md ================================================ -# Neuen Raspberry Pi Client hinzufügen +# Adding a New Raspberry Pi Client -Diese Anleitung beschreibt das Hinzufügen eines **neuen Raspberry Pi Clients** zur Infrastruktur. +This guide describes how to add a **new Raspberry Pi client** to the infrastructure. -## Übersicht: Der Ablauf +## Overview: The Process ``` -1. Konfiguration erstellen ──► Template kopieren, anpassen +1. Create configuration ──► Copy template, customize │ ▼ -2. Zur Image-Pipeline hinzufügen ──► Workflow-Matrix erweitern +2. Add to image pipeline ──► Extend workflow matrix │ ▼ -3. Push auf main ──► Forgejo baut automatisch SD-Image +3. Push to main ──► Forgejo automatically builds SD image │ ▼ -4. Image flashen & booten ──► SD-Karte beschreiben, Pi starten +4. Flash image & boot ──► Write SD card, start Pi │ ▼ -5. SOPS konfigurieren ──► Age-Key holen, Secrets erstellen +5. Configure SOPS ──► Retrieve age key, create secrets │ ▼ -6. Finales Deployment ──► Tailscale etc. aktivieren +6. Final deployment ──► Activate Tailscale etc. ``` -## Voraussetzungen +## Prerequisites -- SSH-Zugang zu cryodev-main (für Tailscale Auth-Key) -- Entwicklungsrechner mit Repository-Zugriff -- SD-Karte (mindestens 8 GB) +- SSH access to cryodev-main (for Tailscale auth key) +- Development machine with repository access +- SD card (at least 8 GB) --- -## Schritt 1: Tailscale Auth-Key generieren +## Step 1: Generate Tailscale Auth Key -**Auf cryodev-main** (per SSH): +**On cryodev-main** (via SSH): ```bash -sudo headscale preauthkeys create --expiration 99y --reusable --user default +# Determine user ID +sudo headscale users list +# Create preauth key (use user ID of "default") +sudo headscale preauthkeys create --expiration 99y --reusable --user ``` -**Ausgabe notieren!** (z.B. `tskey-preauth-abc123...`) +**Take note of the output!** (e.g. `tskey-preauth-abc123...`) --- -## Schritt 2: Host-Konfiguration erstellen +## Step 2: Create Host Configuration -### 2.1 Template kopieren +### 2.1 Copy Template ```bash cp -r templates/raspberry-pi hosts/neuer-pi ``` -### 2.2 Hostname setzen +### 2.2 Set Hostname `hosts/neuer-pi/networking.nix`: @@ -2585,58 +2818,58 @@ cp -r templates/raspberry-pi hosts/neuer-pi } ``` -### 2.3 In flake.nix registrieren +### 2.3 Register in flake.nix ```nix nixosConfigurations = { - # ... bestehende Hosts ... + # ... existing hosts ... neuer-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/neuer-pi ]; }; ``` -### 2.4 In constants.nix eintragen +### 2.4 Add to constants.nix ```nix { hosts = { - # ... bestehende Hosts ... + # ... existing hosts ... neuer-pi = { - ip = "100.64.0.X"; # Wird von Headscale vergeben + ip = "100.64.0.X"; # Assigned by Headscale }; }; } ``` -### 2.5 Placeholder secrets.yaml erstellen +### 2.5 Create Placeholder secrets.yaml ```bash touch hosts/neuer-pi/secrets.yaml ``` -### 2.6 SOPS temporär deaktivieren +### 2.6 Temporarily Disable SOPS -In `hosts/neuer-pi/default.nix` die `sops.secrets.*` Referenzen auskommentieren, damit das Image ohne Secrets gebaut werden kann. +In `hosts/neuer-pi/default.nix`, comment out the `sops.secrets.*` references so the image can be built without secrets. --- -## Schritt 3: Zur Image-Pipeline hinzufügen +## Step 3: Add to Image Pipeline -Bearbeite `.forgejo/workflows/build-pi-image.yml`: +Edit `.forgejo/workflows/build-pi-image.yml`: ```yaml jobs: build-pi-images: strategy: matrix: - # Neuen Host hier hinzufügen: + # Add new host here: host: [cryodev-pi, neuer-pi] ``` --- -## Schritt 4: Push und Image bauen lassen +## Step 4: Push and Build Image ```bash git add . @@ -2644,118 +2877,118 @@ git commit -m "Add neuer-pi host configuration" git push ``` -Der Forgejo Workflow baut jetzt automatisch ein SD-Image für `neuer-pi`. +The Forgejo workflow will now automatically build an SD image for `neuer-pi`. -**Warten** bis der Workflow fertig ist (30-60 Minuten). Status prüfen unter: +**Wait** until the workflow completes (30-60 minutes). Check the status at: `https://git.cryodev.xyz/steffen/cryodev-server/actions` --- -## Schritt 5: Image flashen +## Step 5: Flash Image -### 5.1 Image herunterladen +### 5.1 Download Image -Nach erfolgreichem Build unter **Releases**: +After a successful build, find the image under **Releases**: ```bash wget https://git.cryodev.xyz/steffen/cryodev-server/releases/latest/download/neuer-pi-sd-image.img.zst ``` -### 5.2 Dekomprimieren +### 5.2 Decompress ```bash zstd -d neuer-pi-sd-image.img.zst -o neuer-pi.img ``` -### 5.3 Auf SD-Karte schreiben +### 5.3 Write to SD Card -**Achtung:** `/dev/sdX` durch das richtige Gerät ersetzen! +**Warning:** Replace `/dev/sdX` with the correct device! ```bash -lsblk # Richtiges Gerät finden +lsblk # Identify the correct device sudo dd if=neuer-pi.img of=/dev/sdX bs=4M conv=fsync status=progress ``` -### 5.4 Booten +### 5.4 Boot -1. SD-Karte in den Raspberry Pi einlegen -2. Ethernet anschließen -3. Strom anschließen -4. Warten bis gebootet (ca. 2 Minuten) +1. Insert the SD card into the Raspberry Pi +2. Connect Ethernet +3. Connect power +4. Wait until booted (approximately 2 minutes) --- -## Schritt 6: SOPS konfigurieren +## Step 6: Configure SOPS -### 6.1 IP-Adresse finden +### 6.1 Find IP Address -Der Pi sollte per DHCP eine IP bekommen. Prüfe deinen Router oder scanne das Netzwerk: +The Pi should receive an IP address via DHCP. Check your router or scan the network: ```bash nmap -sn 192.168.1.0/24 | grep -B2 "Raspberry" ``` -### 6.2 SSH verbinden +### 6.2 Connect via SSH ```bash -ssh steffen@ # oder der konfigurierte User +ssh steffen@ # or the configured user ``` -Standard-Passwort siehe `hosts/neuer-pi/users.nix`. +For the default password, see `hosts/neuer-pi/users.nix`. -### 6.3 Age-Key ermitteln +### 6.3 Determine Age Key -Auf dem Pi: +On the Pi: ```bash nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' ``` -**Ausgabe notieren!** (z.B. `age1xyz...`) +**Take note of the output!** (e.g. `age1xyz...`) -### 6.4 .sops.yaml aktualisieren +### 6.4 Update .sops.yaml -Auf dem Entwicklungsrechner: +On the development machine: ```yaml keys: - - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t - - &neuer_pi_key age1xyz... # Der neue Key + - &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local) + - &neuer_pi_key age1xyz... # The new key creation_rules: - # ... bestehende Regeln ... - + # ... existing rules ... + - path_regex: hosts/neuer-pi/secrets.yaml$ key_groups: - age: - - *admin_key - - *neuer_pi_key + - *steffen_key + - *neuer_pi_key ``` -### 6.5 Secrets erstellen +### 6.5 Create Secrets ```bash sops hosts/neuer-pi/secrets.yaml ``` -Inhalt: +Contents: ```yaml tailscale: - auth-key: "tskey-preauth-abc123..." # Key aus Schritt 1 + auth-key: "tskey-preauth-abc123..." # Key from Step 1 netdata: stream: child-uuid: "..." # uuidgen ``` -### 6.6 SOPS-Referenzen aktivieren +### 6.6 Activate SOPS References -Die in Schritt 2.6 auskommentierten `sops.secrets.*` Referenzen wieder aktivieren. +Re-enable the `sops.secrets.*` references that were commented out in Step 2.6. --- -## Schritt 7: Finales Deployment +## Step 7: Final Deployment ```bash git add . @@ -2763,49 +2996,48 @@ git commit -m "Configure SOPS secrets for neuer-pi" git push ``` -Da Comin auf dem Pi läuft, wird er die neue Konfiguration automatisch pullen. +Since Comin is running on the Pi, it will automatically pull the new configuration. -Alternativ manuell: +Alternatively, deploy manually: ```bash -nixos-rebuild switch --flake .#neuer-pi \ - --target-host @ --use-remote-sudo \ - --ssh-option="-p 2299" +NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .#neuer-pi \ + --target-host @ --sudo --ask-sudo-password ``` --- -## Schritt 8: Verifizieren +## Step 8: Verify -### Tailscale-Verbindung +### Tailscale Connection ```bash -# Auf dem Pi +# On the Pi tailscale status -# Auf cryodev-main +# On cryodev-main sudo headscale nodes list ``` -### Netdata-Streaming +### Netdata Streaming -Prüfe ob der neue Client im Netdata-Dashboard erscheint: +Check whether the new client appears in the Netdata dashboard: `https://netdata.cryodev.xyz` --- -## Checkliste +## Checklist -- [ ] Tailscale Auth-Key auf cryodev-main generiert -- [ ] Host-Konfiguration erstellt (Template, flake.nix, constants.nix) -- [ ] Host zur Workflow-Matrix hinzugefügt -- [ ] Gepusht und auf Image-Build gewartet -- [ ] SD-Karte geflasht und Pi gebootet -- [ ] Age-Key ermittelt und in .sops.yaml eingetragen -- [ ] secrets.yaml erstellt (Tailscale-Key, Netdata-UUID) -- [ ] SOPS-Referenzen aktiviert und deployed -- [ ] Tailscale-Verbindung funktioniert -- [ ] Netdata-Streaming funktioniert +- [ ] Tailscale auth key generated on cryodev-main +- [ ] Host configuration created (template, flake.nix, constants.nix) +- [ ] Host added to workflow matrix +- [ ] Pushed and waited for image build +- [ ] SD card flashed and Pi booted +- [ ] Age key determined and added to .sops.yaml +- [ ] secrets.yaml created (Tailscale key, Netdata UUID) +- [ ] SOPS references activated and deployed +- [ ] Tailscale connection working +- [ ] Netdata streaming working @@ -2881,73 +3113,73 @@ nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel --dry- ================================================ FILE: docs/getting-started/reinstall.md ================================================ -# Neuinstallation (Reinstall) +# Reinstallation -Diese Anleitung beschreibt die **Neuinstallation** eines bestehenden Hosts, z.B. nach Hardwarewechsel oder bei Problemen. +This guide describes the **reinstallation** of an existing host, e.g. after a hardware change or in case of issues. -## Unterschied zur Erstinstallation +## Difference from Initial Installation -| Aspekt | Erstinstallation | Neuinstallation | -|--------|------------------|-----------------| -| SOPS-Secrets | Noch nicht vorhanden | Bereits konfiguriert | -| SSH-Host-Key | Neu generiert | **Muss wiederhergestellt werden!** | -| Disk-IDs | Neu ermitteln | Oft geändert (neue Hardware) | -| secrets.yaml | Wird erstellt | Bereits vorhanden | +| Aspect | Initial Installation | Reinstallation | +|--------|----------------------|----------------| +| SOPS Secrets | Not yet present | Already configured | +| SSH Host Key | Newly generated | **Must be restored!** | +| Disk IDs | Newly determined | Often changed (new hardware) | +| secrets.yaml | Will be created | Already exists | -## Wichtig: SSH-Host-Key Problem +## Important: SSH Host Key Issue -Bei einer Neuinstallation wird ein **neuer SSH-Host-Key** generiert. Dieser stimmt nicht mehr mit dem Age-Key in `.sops.yaml` überein! +During a reinstallation, a **new SSH host key** is generated. This key will no longer match the age key in `.sops.yaml`! -### Lösungsmöglichkeiten +### Possible Solutions -**Option A: Alten Host-Key sichern und wiederherstellen** (empfohlen) +**Option A: Back up and restore the old host key** (recommended) -**Option B: Neuen Key generieren und SOPS aktualisieren** +**Option B: Generate a new key and update SOPS** -## Voraussetzungen +## Prerequisites -- Backup des alten SSH-Host-Keys (falls Option A) -- Zugriff auf `.sops.yaml` und die Admin-Age-Keys -- Bootbares NixOS ISO +- Backup of the old SSH host key (if using Option A) +- Access to `.sops.yaml` and the admin age keys +- Bootable NixOS ISO -## Schritt 1: Vorbereitung (vor der Installation) +## Step 1: Preparation (before the installation) -### 1.1 Alten SSH-Host-Key sichern (Option A) +### 1.1 Back Up the Old SSH Host Key (Option A) -Falls der alte Host noch läuft: +If the old host is still running: ```bash -# Auf dem alten Host +# On the old host sudo cat /etc/ssh/ssh_host_ed25519_key > ~/ssh_host_ed25519_key.backup sudo cat /etc/ssh/ssh_host_ed25519_key.pub > ~/ssh_host_ed25519_key.pub.backup ``` -Dateien sicher auf den Entwicklungsrechner kopieren. +Copy the files securely to the development machine. -### 1.2 Disk-IDs ermitteln +### 1.2 Determine Disk IDs -**Bei neuer Hardware** ändern sich die Disk-IDs! +**With new hardware**, the disk IDs will change! ```bash -# Im NixOS Live-System +# In the NixOS live system lsblk -o NAME,SIZE,MODEL,SERIAL ls -la /dev/disk/by-id/ ``` -Die neue Disk-ID in `hosts//disks.sh` oder `disks.nix` eintragen: +Enter the new disk ID in `hosts//disks.sh` or `disks.nix`: ```bash -# Beispiel disks.sh +# Example disks.sh DISK="/dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_XXXXX" ``` -## Schritt 2: Installation durchführen +## Step 2: Perform the Installation -### 2.1 NixOS ISO booten +### 2.1 Boot the NixOS ISO -Von USB/CD booten, Root-Passwort setzen, per SSH verbinden. +Boot from USB/CD, set a root password, and connect via SSH. -### 2.2 Repository klonen +### 2.2 Clone the Repository ```bash sudo -i @@ -2956,30 +3188,30 @@ git clone /tmp/nixos cd /tmp/nixos ``` -### 2.3 Disk-Konfiguration prüfen +### 2.3 Verify the Disk Configuration ```bash -# Aktuelle Disk-IDs anzeigen +# Display current disk IDs ls -la /dev/disk/by-id/ -# Mit Konfiguration vergleichen +# Compare with the configuration cat hosts//disks.sh | grep DISK ``` -**Falls nötig:** Disk-ID in der Konfiguration anpassen. +**If necessary:** Update the disk ID in the configuration. -### 2.4 Install-Script ausführen +### 2.4 Run the Install Script ```bash bash scripts/install.sh -n ``` -### 2.5 SSH-Host-Key wiederherstellen (Option A) +### 2.5 Restore the SSH Host Key (Option A) -**Vor dem Reboot!** +**Before rebooting!** ```bash -# Host-Key vom Backup wiederherstellen +# Restore the host key from backup cp /path/to/ssh_host_ed25519_key.backup /mnt/etc/ssh/ssh_host_ed25519_key cp /path/to/ssh_host_ed25519_key.pub.backup /mnt/etc/ssh/ssh_host_ed25519_key.pub chmod 600 /mnt/etc/ssh/ssh_host_ed25519_key @@ -2993,177 +3225,176 @@ umount -Rl /mnt reboot ``` -## Schritt 3: Nach dem Reboot +## Step 3: After the Reboot -### Bei Option A (Key wiederhergestellt) +### Option A (Key Restored) -SOPS-Secrets sollten automatisch funktionieren. Testen: +SOPS secrets should work automatically. Verify: ```bash sudo cat /run/secrets/tailscale/auth-key ``` -### Bei Option B (Neuer Key) +### Option B (New Key) -Der Host kann die Secrets nicht entschlüsseln. Neuen Key konfigurieren: +The host cannot decrypt the secrets. Configure the new key: ```bash -# Neuen Age-Key ermitteln +# Determine the new age key nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' ``` -Auf dem Entwicklungsrechner: +On the development machine: ```bash -# .sops.yaml aktualisieren mit neuem Key +# Update .sops.yaml with the new key vim .sops.yaml -# Secrets mit neuem Key neu verschlüsseln +# Re-encrypt secrets with the new key sops updatekeys hosts//secrets.yaml ``` -Dann Konfiguration neu deployen: +Then redeploy the configuration: ```bash -nixos-rebuild switch --flake .# \ - --target-host @ --use-remote-sudo \ - --ssh-option="-p 2299" +NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ + --target-host @ --sudo --ask-sudo-password ``` -## Häufige Probleme +## Common Issues ### "No secret key available" -SOPS kann die Secrets nicht entschlüsseln. Ursache: -- SSH-Host-Key stimmt nicht mit Age-Key in `.sops.yaml` überein +SOPS cannot decrypt the secrets. Cause: +- SSH host key does not match the age key in `.sops.yaml` -Lösung: Option B durchführen (neuen Key konfigurieren). +Solution: Follow Option B (configure the new key). -### "Device not found" beim Partitionieren +### "Device not found" during partitioning -Disk-ID in `disks.sh`/`disks.nix` ist falsch. +The disk ID in `disks.sh`/`disks.nix` is incorrect. ```bash -# Richtige ID finden +# Find the correct ID ls -la /dev/disk/by-id/ ``` -### Hardware-Config veraltet +### Outdated Hardware Config -Bei neuer Hardware muss `hardware.nix` neu generiert werden: +With new hardware, `hardware.nix` must be regenerated: ```bash -# Install-Script generiert automatisch neu, falls Datei fehlt +# The install script regenerates automatically if the file is missing rm hosts//hardware.nix bash scripts/install.sh -n ``` -## Checkliste +## Checklist -- [ ] Alten SSH-Host-Key gesichert (falls möglich) -- [ ] Disk-IDs in Konfiguration geprüft/aktualisiert -- [ ] Installation durchgeführt -- [ ] SSH-Host-Key wiederhergestellt ODER neuen Key in SOPS konfiguriert -- [ ] Secrets funktionieren (`sudo cat /run/secrets/...`) -- [ ] Tailscale verbunden (`tailscale status`) +- [ ] Old SSH host key backed up (if possible) +- [ ] Disk IDs in configuration verified/updated +- [ ] Installation completed +- [ ] SSH host key restored OR new key configured in SOPS +- [ ] Secrets are functional (`sudo cat /run/secrets/...`) +- [ ] Tailscale connected (`tailscale status`) ================================================ FILE: docs/getting-started/sd-image.md ================================================ -# SD-Karten-Images für Raspberry Pi +# SD Card Images for Raspberry Pi -Das Repository baut automatisch SD-Karten-Images für alle konfigurierten Raspberry Pi Hosts. +The repository automatically builds SD card images for all configured Raspberry Pi hosts. -## Automatischer Build +## Automatic Build -Bei Änderungen an `main` werden automatisch Images für alle Pi-Hosts gebaut und als Release veröffentlicht. +When changes are pushed to `main`, images are automatically built for all Pi hosts and published as a release. -**Download:** [Releases auf Forgejo](https://git.cryodev.xyz/steffen/cryodev-server/releases) +**Download:** [Releases on Forgejo](https://git.cryodev.xyz/steffen/cryodev-server/releases) -## Verfügbare Images +## Available Images -| Host | Image-Name | +| Host | Image Name | |------|------------| | `cryodev-pi` | `cryodev-pi-sd-image.img.zst` | -Neue Hosts werden automatisch gebaut, wenn sie zur Workflow-Matrix hinzugefügt werden. +New hosts are built automatically once they are added to the workflow matrix. -## Image flashen +## Flashing the Image -### 1. Herunterladen +### 1. Download ```bash wget https://git.cryodev.xyz/.../releases/latest/download/-sd-image.img.zst wget https://git.cryodev.xyz/.../releases/latest/download/-sd-image.img.zst.sha256 -# Checksum prüfen +# Verify checksum sha256sum -c -sd-image.img.zst.sha256 ``` -### 2. Dekomprimieren +### 2. Decompress ```bash zstd -d -sd-image.img.zst -o .img ``` -### 3. Auf SD-Karte schreiben +### 3. Write to SD Card ```bash -# Richtiges Gerät finden +# Identify the correct device lsblk -# Schreiben (ACHTUNG: richtiges Gerät wählen!) +# Write (WARNING: make sure to select the correct device!) sudo dd if=.img of=/dev/sdX bs=4M conv=fsync status=progress ``` -Alternativ: `balenaEtcher` oder `Raspberry Pi Imager` verwenden. +Alternatively, use `balenaEtcher` or `Raspberry Pi Imager`. -## Was ist im Image? +## What Is Included in the Image? -- Vollständige NixOS-Installation für den spezifischen Host -- Alle konfigurierten Services (außer Secrets) -- SSH-Server aktiviert -- Automatische Root-Partition-Erweiterung beim ersten Boot -- Comin für automatische Updates +- Complete NixOS installation for the specific host +- All configured services (except secrets) +- SSH server enabled +- Automatic root partition expansion on first boot +- Comin for automatic updates -## Was fehlt? +## What Is Missing? -**SOPS-Secrets** können nicht im Image enthalten sein (Henne-Ei-Problem mit SSH-Host-Key). +**SOPS secrets** cannot be included in the image (chicken-and-egg problem with the SSH host key). -Nach dem ersten Boot: -1. Age-Key vom Pi holen -2. `.sops.yaml` aktualisieren -3. `secrets.yaml` erstellen -4. Konfiguration deployen +After the first boot: +1. Retrieve the age key from the Pi +2. Update `.sops.yaml` +3. Create `secrets.yaml` +4. Deploy the configuration -Siehe [Neuen Client hinzufügen](new-client.md) für die vollständige Anleitung. +See [Adding a New Client](new-client.md) for the complete guide. -## Neuen Host zur Pipeline hinzufügen +## Adding a New Host to the Pipeline -1. Host-Konfiguration in `hosts//` erstellen -2. In `.forgejo/workflows/build-pi-image.yml` zur Matrix hinzufügen: +1. Create the host configuration in `hosts//` +2. Add it to the matrix in `.forgejo/workflows/build-pi-image.yml`: ```yaml matrix: - host: [cryodev-pi, neuer-host] # <- hier hinzufügen + host: [cryodev-pi, new-host] # <- add here ``` -3. Push auf `main` → Image wird automatisch gebaut +3. Push to `main` -- the image will be built automatically -## Manuell bauen +## Building Manually ```bash -# Auf aarch64 (z.B. anderem Pi) +# On aarch64 (e.g., another Pi) nix build .#nixosConfigurations..config.system.build.sdImage -# Auf x86_64 mit QEMU-Emulation (langsam) +# On x86_64 with QEMU emulation (slow) nix build .#nixosConfigurations..config.system.build.sdImage \ --extra-platforms aarch64-linux ``` -Voraussetzung auf x86_64: +Prerequisite on x86_64: ```nix { @@ -3173,21 +3404,21 @@ Voraussetzung auf x86_64: ## Troubleshooting -### Workflow schlägt fehl +### Workflow Fails -- Prüfe ob `sd-image.nix` in der Host-Konfiguration importiert wird -- Prüfe ob binfmt auf cryodev-main aktiviert ist +- Check whether `sd-image.nix` is imported in the host configuration +- Check whether binfmt is enabled on cryodev-main -### Image bootet nicht +### Image Does Not Boot -- SD-Karte korrekt beschrieben? -- Andere SD-Karte versuchen -- Stromversorgung prüfen (min. 3A für Pi 4) +- Was the SD card written correctly? +- Try a different SD card +- Check the power supply (minimum 3A for Pi 4) -### Kein Netzwerk +### No Network -- Ethernet-Kabel prüfen -- DHCP-Server im Netzwerk? +- Check the Ethernet cable +- Is there a DHCP server on the network? @@ -3271,44 +3502,23 @@ forgejo-runner: ## CI/CD Workflows -### deploy-rs Workflow +CI runs on every push to `main` via Forgejo Actions: -`.forgejo/workflows/deploy.yaml`: +1. **flake-check** -- validates the flake +2. **build-hosts** -- builds all host configurations -```yaml -name: Deploy -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v24 - - - name: Deploy - env: - SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }} - run: | - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - nix run .#deploy -``` +Deployment is handled by **Comin** (pull-based), not by CI. +See [CD documentation](../deployment/cd.md) for details. ## Administration ### Create Admin User ```bash -sudo -u forgejo forgejo admin user create \ - --username admin \ - --password changeme \ - --email admin@cryodev.xyz \ +forgejo admin user create \ + --username \ + --email @ \ + --password \ --admin ``` @@ -3376,9 +3586,10 @@ nix-shell -p openssl --run 'openssl rand -hex 16' ```bash # First, create a dedicated user sudo headscale users create headplane-agent - -# Then create a reusable pre-auth key -sudo headscale preauthkeys create --expiration 99y --reusable --user headplane-agent +# Find the user ID +sudo headscale users list +# Then create a reusable pre-auth key (use the ID of headplane-agent) +sudo headscale preauthkeys create --expiration 99y --reusable --user ``` ### Add to Secrets @@ -3452,7 +3663,7 @@ sudo journalctl -u headplane -f Verify the agent pre-auth key is valid: ```bash -sudo headscale preauthkeys list --user headplane-agent +sudo headscale preauthkeys list --user ``` If expired, create a new one and update the secrets file. @@ -3957,7 +4168,7 @@ Add the host key to `.sops.yaml`: ```yaml keys: - - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t + - &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local) - &main_key age1... # cryodev-main - &pi_key age1... # cryodev-pi @@ -3965,14 +4176,14 @@ creation_rules: - path_regex: hosts/cryodev-main/secrets.yaml$ key_groups: - age: - - *admin_key - - *main_key - + - *steffen_key + - *main_key + - path_regex: hosts/cryodev-pi/secrets.yaml$ key_groups: - age: - - *admin_key - - *pi_key + - *steffen_key + - *pi_key ``` ### 3. Create Secrets File @@ -4079,7 +4290,7 @@ netdata: | Mailserver password | `nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'` | | Random hex token | `nix-shell -p openssl --run 'openssl rand -hex 16'` | | UUID | `uuidgen` | -| Tailscale preauth | `sudo headscale preauthkeys create --expiration 99y --reusable --user default` | +| Tailscale preauth | `sudo headscale preauthkeys create --expiration 99y --reusable --user ` | ## Updating Keys @@ -4120,7 +4331,10 @@ Tailscale clients connect to the self-hosted Headscale server to join the mesh V On the Headscale server (cryodev-main): ```bash -sudo headscale preauthkeys create --expiration 99y --reusable --user default +# Look up user ID +sudo headscale users list +# Create preauth key (use the user ID for "default") +sudo headscale preauthkeys create --expiration 99y --reusable --user ``` ### Add to Secrets @@ -4217,7 +4431,7 @@ Check the auth key is valid: ```bash # On Headscale server -sudo headscale preauthkeys list --user default +sudo headscale preauthkeys list --user ``` Verify the login server URL is correct in the client configuration. @@ -4431,12 +4645,12 @@ FILE: hosts/cryodev-main/packages.nix FILE: hosts/cryodev-main/secrets.yaml ================================================ tailscale: - auth-key: ENC[AES256_GCM,data:APMZrLYEqywYTmc=,iv:KiFwgR3UXLXCdl9DlR5tJOr8XUyQEeDomPx9hOREhnw=,tag:32quLtu74EIxAgmjH3hvIw==,type:str] + auth-key: ENC[AES256_GCM,data:v5C3DqYJsDKq6oUa/3G6WKxyKeIK4EJLNxWMbKjSbwe5MPtS4sZjFszMviKcEVGW,iv:4G8irABGuVhOYnK15EjbpNQ4B9VY/NdwCrfz+YAMzvA=,tag:0Vhq/TJgx+48frRy30yKFg==,type:str] forgejo-runner: - token: ENC[AES256_GCM,data:/i9KVMeEXYwQnn0=,iv:pILMNbhDviifDUFRINi6n9dtGSAeqxKMdBgjYwtXXEM=,tag:JCj5v5BZdZteo0MdTVKREw==,type:str] + token: ENC[AES256_GCM,data:sdnJcyRiTLxXoZDNbEzJAjpiK+iSUH0gV0XwbEQf94IE/6IZz5/zHw==,iv:py+qqp3VAwBGEpYiQwft3jnQS943JaBlrcckColv4f8=,tag:rtmRwW8rpXB6Pv+LSkp+Fw==,type:str] headplane: cookie_secret: ENC[AES256_GCM,data:HICF31i6yCLZGNeOFYTR3Bp0a7i0UKOvGAvx/pD3NB4=,iv:ZtK8r1YUWnf5Af0Ls341k0w1mZm+D5Rb0E1uS5z/Gdo=,tag:vwM9+4dpcmnjn/wR6Ty/MQ==,type:str] - agent_pre_authkey: ENC[AES256_GCM,data:aYkPZTR4fwArcKQ=,iv:+OhbIpwsyCJ4i4k8eyCKYAHE25F4iUHfdM+CG0+BQd8=,tag:BkT73WPjOv5Lu6dCFBXxWg==,type:str] + agent_pre_authkey: ENC[AES256_GCM,data:QvhPi2lhyP7w6HTeOSS8660NzIY9Q6AOhlOGQXnvz+qYu9vOAMQPOFMZfie5+e8g,iv:X60wVOEUIsTiMHrrd4lId0VpR7VfFDr74p8RGka3+18=,tag:kIvaHrOWIM+VQ+Qz1GiheQ==,type:str] mailserver: accounts: admin: ENC[AES256_GCM,data:gY2k3x3sA98yGNLcSWUr9aC0566MJM2UXhwLtWPUL3PRvxQt0XOzjeiC7ddgbqTAol4dBNeaV0zbFInD,iv:rxp0M9kHMgD73K+RDC562sUpXaJ067eU1CeciAke+LM=,tag:VKobduo/ZULAk17M9LD3bw==,type:str] @@ -4463,8 +4677,8 @@ sops: MEpGbGlQbVRsM1NxN1JxY2J1MVNTTE0KuIvuM2c1VIXKv0LGLb0NwqtSyBYcRcb1 uiIjNV0UzEt/WvnCeUTMPgIXBHk6jWcaKe13v6MHeha+/CVZ9Su/Lw== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-14T10:28:25Z" - mac: ENC[AES256_GCM,data:oeT8I9gMIAPnm8wlNUFjn/0UT6qfTA//fLp3USO33FMsNIOWmqt3kB4NsozS+n6ZeMxBVWQZPss8t819DYqv0xQarzfOqQe1idCGCB+7NBFcFP2VLFzkIH+9Wei9AJSlR3BRnzyVaQDi797P6pEXFn/IoQWPWZ8sX8ZKugOfY0w=,iv:RjsKhPcVZBHHLs1W3PDhcseGLV4eawafg0is6KrzhtE=,tag:ifkobUteslEZ78OvkZw8JQ==,type:str] + lastmodified: "2026-03-14T11:38:57Z" + mac: ENC[AES256_GCM,data:gmxyp3XaHeU/CT2lgo14wIbJsKs/JrZmUPhgHwo1XRN5Sf/Su6lHOpVlQS1M6R3+ZlBnS/oEur+y0gydCCqhJK1C3Y5YuUfPlOWOeQWMVxQBqxWkyemvz5KgGseDc9nG09FpoGEYa4sSeuD1J6vRsGcZiOStaA6s8NICWivdWcQ=,iv:cYILLrScr7cFiLx5INbc9z3BT7LaCjLnCH0wdn3lZ1k=,tag:IIRb/Tu8YqWNiHXH7CSOfQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 @@ -4479,17 +4693,51 @@ FILE: hosts/cryodev-main/users.nix imports = [ outputs.nixosModules.normalUsers ../../users/steffen + ../../users/ralph + ../../users/benjamin ]; } +================================================ +FILE: hosts/cryodev-main/services/comin.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.comin + ]; + + services.comin = { + enable = true; + remotes = [ + { + name = "origin"; + url = "https://${constants.services.forgejo.fqdn}/steffen/cryodev.git"; + branches.main.name = "main"; + } + ]; + }; +} + + + ================================================ FILE: hosts/cryodev-main/services/default.nix ================================================ { imports = [ + ./comin.nix ./forgejo.nix + ./forgejo-runner.nix ./headplane.nix ./headscale.nix ./mailserver.nix @@ -4503,6 +4751,40 @@ FILE: hosts/cryodev-main/services/default.nix +================================================ +FILE: hosts/cryodev-main/services/forgejo-runner.nix +================================================ +{ + config, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.forgejo-runner + ]; + + services.forgejo-runner = { + enable = true; + url = "http://127.0.0.1:${toString constants.services.forgejo.port}"; + tokenFile = config.sops.templates."forgejo-runner-token".path; + }; + + sops.secrets."forgejo-runner/token" = { + mode = "0400"; + }; + + sops.templates."forgejo-runner-token" = { + content = '' + TOKEN=${config.sops.placeholder."forgejo-runner/token"} + ''; + }; +} + + + ================================================ FILE: hosts/cryodev-main/services/forgejo.nix ================================================ @@ -4516,7 +4798,6 @@ FILE: hosts/cryodev-main/services/forgejo.nix { imports = [ outputs.nixosModules.forgejo - outputs.nixosModules.forgejo-runner ]; services.forgejo = { @@ -4540,17 +4821,6 @@ FILE: hosts/cryodev-main/services/forgejo.nix }; }; - services.forgejo-runner = { - enable = true; - url = "https://${constants.services.forgejo.fqdn}"; - tokenFile = config.sops.secrets."forgejo-runner/token".path; - }; - - sops.secrets."forgejo-runner/token" = { - # gitea-runner user is created by gitea-actions-runner service - mode = "0400"; - }; - services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = { forceSSL = true; enableACME = true; @@ -4583,6 +4853,7 @@ FILE: hosts/cryodev-main/services/headplane.nix headscale = { url = "http://127.0.0.1:${toString constants.services.headscale.port}"; public_url = "https://${constants.services.headscale.fqdn}"; + config_strict = false; }; }; }; @@ -4773,10 +5044,10 @@ FILE: hosts/cryodev-main/services/sops.nix sops = { defaultSopsFile = ../secrets.yaml; # age.keyFile is not set, sops-nix defaults to using /etc/ssh/ssh_host_ed25519_key - secrets = { - "forgejo-runner/token" = { }; - "tailscale/auth-key" = { }; - }; + + # Secrets fuer Stufe-2-Services werden in deren eigenen Dateien definiert: + # forgejo-runner/token -> forgejo-runner.nix + # tailscale/auth-key -> tailscale.nix (via Modul) }; } @@ -4939,11 +5210,15 @@ FILE: hosts/cryodev-pi/hardware.nix { boot = { kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; - initrd.availableKernelModules = [ - "xhci_pci" - "usbhid" - "usb_storage" - ]; + initrd = { + availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + # Disable default x86 modules that don't exist in the Pi kernel (e.g. dw-hdmi) + includeDefaultModules = false; + }; }; fileSystems = { @@ -5013,6 +5288,14 @@ FILE: hosts/cryodev-pi/sd-image.nix "vfat" "ext4" ]; + + # sd-image.nix imports all-hardware.nix which adds x86 modules like dw-hdmi + # that don't exist in the RPi4 kernel. Filter them out. + boot.initrd.availableKernelModules = lib.mkForce [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; } @@ -5043,7 +5326,6 @@ FILE: hosts/cryodev-pi/users.nix imports = [ outputs.nixosModules.normalUsers ../../users/steffen - ../../users/cryotherm ]; } @@ -5070,7 +5352,7 @@ FILE: hosts/cryodev-pi/services/comin.nix remotes = [ { name = "origin"; - url = "https://${constants.services.forgejo.fqdn}/steffen/cryodev-server.git"; + url = "https://${constants.services.forgejo.fqdn}/steffen/cryodev.git"; branches.main.name = "main"; } ]; @@ -5894,7 +6176,6 @@ in nix nodejs openssh - deploy-rs ]; settings = { @@ -5950,6 +6231,14 @@ in config = mkIf cfg.enable { nixpkgs.overlays = [ inputs.headplane.overlays.default + # Fix upstream pnpm-deps hash mismatch (https://github.com/tale/headplane) + (final: prev: { + headplane = prev.headplane.overrideAttrs (old: { + pnpmDeps = old.pnpmDeps.overrideAttrs { + outputHash = "sha256-lk/ezsrW6JHh5nXPSstqHUbaMTeOARBGZcBSoG1S5ns="; + }; + }); + }) ]; services.headplane = { @@ -6311,6 +6600,7 @@ in acceptTerms = true; defaults.email = mkDefault "postmaster@${config.networking.domain}"; defaults.webroot = mkDefault "/var/lib/acme/acme-challenge"; + defaults.group = mkDefault "nginx"; }; security.dhparams = mkIf cfg.forceSSL { @@ -6897,8 +7187,6 @@ FILE: modules/nixos/nixvim/plugins/default.nix ]; config.programs.nixvim.plugins = { - markdown-preview.enable = lib.mkDefault true; - # warning: Nixvim: `plugins.web-devicons` was enabled automatically because the following plugins are enabled. This behaviour is deprecated. Please explicitly define `plugins.web-devicons.enable` web-devicons.enable = true; }; } @@ -7583,12 +7871,11 @@ fi FILE: templates/generic-server/boot.nix ================================================ { - boot = { - loader = { - grub.enable = false; - generic-extlinux-compatible.enable = true; - }; + boot.loader.systemd-boot = { + enable = true; + configurationLimit = 10; }; + boot.loader.efi.canTouchEfiVariables = true; } @@ -7702,28 +7989,49 @@ FILE: templates/generic-server/flake.nix ================================================ FILE: templates/generic-server/hardware.nix ================================================ -{ pkgs, lib, ... }: +{ + config, + lib, + pkgs, + modulesPath, + ... +}: { - boot = { - kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; - initrd.availableKernelModules = [ - "xhci_pci" - "usbhid" - "usb_storage" + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ + "ahci" + "nvme" + "sd_mod" + "usb_storage" + "xhci_pci" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = { + device = "/dev/disk/by-label/ROOT"; + fsType = "ext4"; + }; + + fileSystems."/boot" = { + device = "/dev/disk/by-label/BOOT"; + fsType = "vfat"; + options = [ + "fmask=0022" + "dmask=0022" ]; }; - fileSystems = { - "/" = { - device = "/dev/disk/by-label/NIXOS_SD"; - fsType = "ext4"; - options = [ "noatime" ]; - }; - }; + swapDevices = [ { device = "/dev/disk/by-label/SWAP"; } ]; - nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; - hardware.enableRedistributableFirmware = true; + networking.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } @@ -7732,7 +8040,7 @@ FILE: templates/generic-server/hardware.nix FILE: templates/generic-server/networking.nix ================================================ { - networking.hostName = "cryodev-pi"; + networking.hostName = "HOSTNAME"; networking.domain = "cryodev.xyz"; } @@ -7757,8 +8065,8 @@ FILE: templates/generic-server/users.nix { imports = [ outputs.nixosModules.normalUsers - ../../users/steffen - ../../users/cryotherm + # Add users here, e.g.: + # ../../users/ ]; } @@ -8046,11 +8354,15 @@ FILE: templates/raspberry-pi/hardware.nix { boot = { kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; - initrd.availableKernelModules = [ - "xhci_pci" - "usbhid" - "usb_storage" - ]; + initrd = { + availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + # Disable default x86 modules that don't exist in the Pi kernel (e.g. dw-hdmi) + includeDefaultModules = false; + }; }; fileSystems = { @@ -8071,7 +8383,7 @@ FILE: templates/raspberry-pi/hardware.nix FILE: templates/raspberry-pi/networking.nix ================================================ { - networking.hostName = "cryodev-pi"; + networking.hostName = "HOSTNAME"; networking.domain = "cryodev.xyz"; } @@ -8096,8 +8408,8 @@ FILE: templates/raspberry-pi/users.nix { imports = [ outputs.nixosModules.normalUsers - ../../users/steffen - ../../users/cryotherm + # Add users here, e.g.: + # ../../users/ ]; } @@ -8258,13 +8570,34 @@ FILE: templates/raspberry-pi/services/tailscale.nix ================================================ -FILE: users/cryotherm/default.nix +FILE: users/benjamin/default.nix ================================================ { - normalUsers.cryotherm = { - extraGroups = [ ]; - # No sshKeyFiles, so password login only (if allowed) or local access - sshKeyFiles = [ ]; + normalUsers.benjamin = { + extraGroups = [ + "wheel" + ]; + sshKeyFiles = [ + # TODO: Add benjamin's public key + # ./pubkeys/benjamin.pub + ]; + }; +} + + + +================================================ +FILE: users/ralph/default.nix +================================================ +{ + normalUsers.ralph = { + extraGroups = [ + "wheel" + ]; + sshKeyFiles = [ + # TODO: Add ralph's public key + # ./pubkeys/ralph.pub + ]; }; } @@ -8294,62 +8627,77 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKNTpsF9Z313gWHiHi4SvjeXI4Mh80mtq0bR0AjsZr ================================================ -FILE: .forgejo/workflows/build-hosts.yml +FILE: .forgejo/workflows/ci.yml ================================================ -name: Build hosts +name: CI -on: - pull_request: - branches: - - main +on: [pull_request] jobs: - build-hosts: - runs-on: docker + flake-check: + runs-on: host steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - nix_path: nixpkgs=channel:nixos-unstable + - name: Run flake check + run: nix flake check --impure + + build-hosts: + needs: flake-check + runs-on: host + steps: + - name: Checkout repository + uses: actions/checkout@v4 - name: Build cryodev-main run: nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel --impure - name: Build cryodev-pi - run: nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel --impure + run: nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel --impure --extra-platforms aarch64-linux ================================================ -FILE: .forgejo/workflows/build-pi-image.yml +FILE: .forgejo/workflows/deploy.yml ================================================ -name: Build Raspberry Pi SD Images +name: Deploy on: push: branches: - main - paths: - - 'hosts/**' - - 'modules/**' - - 'templates/**' - - 'flake.nix' - - 'flake.lock' - - 'constants.nix' - workflow_dispatch: jobs: + flake-check: + runs-on: host + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run flake check + run: nix flake check --impure + + build-hosts: + needs: flake-check + runs-on: host + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build cryodev-main + run: nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel --impure + + - name: Build cryodev-pi + run: nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel --impure --extra-platforms aarch64-linux + build-pi-images: + needs: build-hosts runs-on: host strategy: matrix: - # Add new Pi hosts to this list when created host: [cryodev-pi] fail-fast: false - steps: - name: Checkout repository uses: actions/checkout@v4 @@ -8357,21 +8705,19 @@ jobs: - name: Build SD image for ${{ matrix.host }} run: | echo "Building SD image for: ${{ matrix.host }}" - echo "This may take 30-60 minutes with emulation..." - nix build .#nixosConfigurations.${{ matrix.host }}.config.system.build.sdImage \ --extra-platforms aarch64-linux \ --out-link result-${{ matrix.host }} - + IMAGE_PATH=$(find result-${{ matrix.host }} -name "*.img.zst" -type f | head -1) if [ -z "$IMAGE_PATH" ]; then echo "Error: No image found!" exit 1 fi - + cp "$IMAGE_PATH" ./${{ matrix.host }}-sd-image.img.zst sha256sum ${{ matrix.host }}-sd-image.img.zst > ${{ matrix.host }}-sd-image.img.zst.sha256 - + echo "Image size:" ls -lh ${{ matrix.host }}-sd-image.img.zst @@ -8400,19 +8746,17 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="v$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" - - # Create release via API + curl -s -X POST \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\": \"${VERSION}\", \"name\": \"Pi Images ${VERSION}\", \"body\": \"Raspberry Pi SD card images. See docs for usage.\", \"draft\": false, \"prerelease\": false}" \ "https://git.cryodev.xyz/api/v1/repos/${GITHUB_REPOSITORY}/releases" \ -o release.json - + RELEASE_ID=$(jq -r '.id' release.json) echo "Release ID: $RELEASE_ID" - - # Upload all files + for file in $(find artifacts -type f); do echo "Uploading: $(basename $file)" curl -s -X POST \ @@ -8421,71 +8765,7 @@ jobs: --data-binary @"$file" \ "https://git.cryodev.xyz/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" done - + echo "Done: https://git.cryodev.xyz/${GITHUB_REPOSITORY}/releases/tag/${VERSION}" - -================================================ -FILE: .forgejo/workflows/deploy-main.yml -================================================ -name: Deploy cryodev-main - -on: - push: - branches: - - main - -jobs: - deploy-cryodev-main: - runs-on: docker - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Set up SSH - env: - DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }} - run: | - mkdir -p ~/.ssh - echo "$DEPLOY_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - - # Add host key (replace with actual host key or use ssh-keyscan in unsafe environments) - ssh-keyscan -H cryodev.xyz >> ~/.ssh/known_hosts - - - name: Deploy with deploy-rs - run: | - # Deploy using deploy-rs - nix run github:serokell/deploy-rs -- -s .#cryodev-main - - - -================================================ -FILE: .forgejo/workflows/flake-check.yml -================================================ -name: Flake check - -on: [pull_request] - -jobs: - flake-check: - runs-on: docker - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Run flake check - run: nix flake check --impure - - diff --git a/docs/deployment/dns.md b/docs/deployment/dns.md index 8d0f99b..5f2fdc2 100644 --- a/docs/deployment/dns.md +++ b/docs/deployment/dns.md @@ -31,12 +31,12 @@ Required DNS records for the cryodev infrastructure. | `@` | MX | `10 mail.cryodev.xyz.` | Mail delivery | | `@` | TXT | `"v=spf1 mx ~all"` | SPF | | `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC | -| `mail._domainkey` | TXT | *(siehe unten)* | DKIM | +| `mail._domainkey` | TXT | *(see below)* | DKIM | ### Reverse DNS (PTR) -Fuer zuverlaessige Mail-Zustellung muss ein **PTR Record** beim Hosting-Provider -konfiguriert werden (nicht im DNS-Panel der Domain): +For reliable mail delivery, a **PTR record** must be configured at the hosting +provider (not in the domain's DNS panel): | IP | PTR Value | |----|-----------| @@ -45,18 +45,18 @@ konfiguriert werden (nicht im DNS-Panel der Domain): #### Hetzner Robot (Dedicated Server) -1. [robot.hetzner.com](https://robot.hetzner.com) > **Server** > Server auswaehlen -2. **IPs** Tab -3. Bei der IPv4-Adresse auf das **Stift-Symbol** klicken -4. `mail.cryodev.xyz` eintragen und speichern -5. Fuer IPv6: Unter **Subnets** dasselbe fuer die primaere IPv6-Adresse +1. [robot.hetzner.com](https://robot.hetzner.com) > **Server** > Select the server +2. **IPs** tab +3. Click the **pencil icon** next to the IPv4 address +4. Enter `mail.cryodev.xyz` and save +5. For IPv6: Under **Subnets**, repeat the same for the primary IPv6 address #### Hetzner Cloud -1. [cloud.hetzner.com](https://cloud.hetzner.com) > Server auswaehlen -2. **Networking** Tab -3. Bei "Primary IP" auf die IP klicken > **Reverse DNS** -4. `mail.cryodev.xyz` eintragen (fuer IPv4 und IPv6) +1. [cloud.hetzner.com](https://cloud.hetzner.com) > Select the server +2. **Networking** tab +3. Under "Primary IP", click the IP > **Reverse DNS** +4. Enter `mail.cryodev.xyz` (for both IPv4 and IPv6) ## Getting the DKIM Key @@ -70,15 +70,15 @@ Add this as a TXT record for `mail._domainkey.cryodev.xyz`. ## Complete Checklist -- [ ] A/AAAA fuer `@` (Root-Domain) -- [ ] A/AAAA fuer `www` -- [ ] A/AAAA fuer `mail` -- [ ] CNAME fuer `git`, `headscale`, `headplane`, `netdata` -- [ ] MX Record -- [ ] TXT fuer SPF (`v=spf1 mx ~all`) -- [ ] TXT fuer DMARC (`v=DMARC1; p=none`) -- [ ] TXT fuer DKIM (`mail._domainkey` -- nach erstem Deploy) -- [ ] PTR Record beim Hosting-Provider (Reverse DNS) +- [ ] A/AAAA for `@` (root domain) +- [ ] A/AAAA for `www` +- [ ] A/AAAA for `mail` +- [ ] CNAME for `git`, `headscale`, `headplane`, `netdata` +- [ ] MX record +- [ ] TXT for SPF (`v=spf1 mx ~all`) +- [ ] TXT for DMARC (`v=DMARC1; p=none`) +- [ ] TXT for DKIM (`mail._domainkey` -- after first deployment) +- [ ] PTR record at hosting provider (reverse DNS) ## Verification diff --git a/docs/getting-started/first-install.md b/docs/getting-started/first-install.md index f50c51a..d029a63 100644 --- a/docs/getting-started/first-install.md +++ b/docs/getting-started/first-install.md @@ -1,44 +1,44 @@ -# Erstinstallation (x86_64 Server) +# Initial Installation (x86_64 Server) -Diese Anleitung beschreibt die **Erstinstallation** eines neuen x86_64 Servers (z.B. cryodev-main). +This guide describes the **initial installation** of a new x86_64 server (e.g. cryodev-main). -> **Fuer Raspberry Pi:** Siehe [SD-Image erstellen](sd-image.md). +> **For Raspberry Pi:** See [Creating an SD Image](sd-image.md). -## Uebersicht +## Overview -Bei der Erstinstallation gibt es ein Henne-Ei-Problem: -- SOPS-Secrets werden mit dem SSH-Host-Key verschluesselt -- Der SSH-Host-Key wird erst bei der Installation generiert -- Daher: **Erst ohne Secrets installieren, dann Secrets konfigurieren** +During initial installation there is a chicken-and-egg problem: +- SOPS secrets are encrypted with the SSH host key +- The SSH host key is only generated during installation +- Therefore: **Install without secrets first, then configure secrets** -### Ablauf +### Process ``` -1. Services deaktivieren (die Secrets brauchen) -2. NixOS installieren -3. SSH-Host-Key extrahieren, SOPS konfigurieren, sofort erstellbare Secrets anlegen -4. Stufe-1-Services aktivieren und deployen (Headscale, Forgejo, Mail, Nginx) -5. Restliche Secrets generieren (Tailscale, Headplane, Forgejo-Runner) -6. Stufe-2-Services aktivieren und final deployen +1. Disable services (that require secrets) +2. Install NixOS +3. Extract SSH host key, configure SOPS, create immediately available secrets +4. Enable stage-1 services and deploy (Headscale, Forgejo, Mail, Nginx) +5. Generate remaining secrets (Tailscale, Headplane, Forgejo Runner) +6. Enable stage-2 services and perform final deployment ``` -## Schritt 1: Host-Konfiguration vorbereiten +## Step 1: Prepare Host Configuration -> Falls der Host bereits in `hosts/` und `flake.nix` existiert, ueberspringe 1.1-1.2. +> If the host already exists in `hosts/` and `flake.nix`, skip 1.1-1.2. -### 1.1 Host aus Template erstellen +### 1.1 Create Host from Template ```bash nix run .#create -- -t generic-server -n ``` -Das Script: -- Kopiert das Template nach `hosts//` -- Setzt den Hostname in `networking.nix` -- Erstellt eine leere `secrets.yaml` -- Fuegt die Dateien zu Git hinzu +The script: +- Copies the template to `hosts//` +- Sets the hostname in `networking.nix` +- Creates an empty `secrets.yaml` +- Adds the files to Git -### 1.2 In flake.nix registrieren +### 1.2 Register in flake.nix ```nix nixosConfigurations = { @@ -46,24 +46,24 @@ nixosConfigurations = { }; ``` -Ausserdem `hardware.nix` und `disks.sh` fuer die Zielhardware anpassen. +Also adjust `hardware.nix` and `disks.sh` for the target hardware. -### 1.4 Services temporaer deaktivieren +### 1.4 Temporarily Disable Services -Alle Services, die SOPS-Secrets referenzieren, muessen fuer die Erstinstallation deaktiviert werden. Andernfalls schlaegt die Installation fehl, weil die Secrets noch nicht entschluesselt werden koennen. +All services that reference SOPS secrets must be disabled for the initial installation. Otherwise the installation will fail because the secrets cannot yet be decrypted. -In `hosts//services/default.nix` die entsprechenden Imports auskommentieren: +In `hosts//services/default.nix`, comment out the corresponding imports: ```nix { imports = [ - # Deaktiviert bis SOPS-Secrets konfiguriert sind: - # ./forgejo.nix # braucht: forgejo-runner/token, forgejo/mail-pw - # ./headplane.nix # braucht: headplane/cookie_secret, headplane/agent_pre_authkey - # ./mailserver.nix # braucht: mailserver/accounts/* - # ./tailscale.nix # braucht: tailscale/auth-key + # Disabled until SOPS secrets are configured: + # ./forgejo.nix # requires: forgejo-runner/token, forgejo/mail-pw + # ./headplane.nix # requires: headplane/cookie_secret, headplane/agent_pre_authkey + # ./mailserver.nix # requires: mailserver/accounts/* + # ./tailscale.nix # requires: tailscale/auth-key - # Diese Services brauchen keine Secrets: + # These services do not require secrets: ./headscale.nix ./netdata.nix ./nginx.nix @@ -73,7 +73,7 @@ In `hosts//services/default.nix` die entsprechenden Imports auskomment } ``` -Zusaetzlich in `hosts//services/sops.nix` die Secrets-Definitionen auskommentieren: +Additionally, in `hosts//services/sops.nix`, comment out the secret definitions: ```nix sops = { @@ -85,32 +85,32 @@ sops = { }; ``` -### 1.5 Konfiguration testen +### 1.5 Test the Configuration ```bash nix eval .#nixosConfigurations..config.system.build.toplevel.name ``` -## Schritt 2: Installation durchfuehren +## Step 2: Perform Installation -### 2.1 NixOS ISO booten +### 2.1 Boot NixOS ISO -Vom [NixOS Minimal ISO](https://nixos.org/download/#nixos-iso) booten (USB/CD). +Boot from the [NixOS Minimal ISO](https://nixos.org/download/#nixos-iso) (USB/CD). -### 2.2 Netzwerk und SSH einrichten +### 2.2 Set Up Network and SSH ```bash -passwd # Root-Passwort setzen fuer SSH-Zugang -ip a # IP-Adresse ermitteln +passwd # Set root password for SSH access +ip a # Determine IP address ``` -Optional per SSH verbinden (bequemer): +Optionally connect via SSH (more convenient): ```bash ssh -o StrictHostKeyChecking=no root@ ``` -### 2.3 Installieren +### 2.3 Install ```bash nix --experimental-features "nix-command flakes" run \ @@ -119,20 +119,20 @@ nix --experimental-features "nix-command flakes" run \ -r ``` -Alternativ, falls das Repository bereits unter `/tmp/nixos` geklont wurde: +Alternatively, if the repository has already been cloned to `/tmp/nixos`: ```bash nix --experimental-features "nix-command flakes" run /tmp/nixos#install -- -n ``` -> **Hinweis:** Die Disk-ID in `hosts//disks.sh` muss zur Hardware passen. -> Pruefen mit `ls -la /dev/disk/by-id/`. +> **Note:** The disk ID in `hosts//disks.sh` must match the hardware. +> Verify with `ls -la /dev/disk/by-id/`. -Das Script: -1. Klont das Repository (bei `-r`) -2. Partitioniert die Disk (via `disks.nix` oder `disks.sh`) -3. Generiert `hardware.nix` (falls nicht vorhanden) -4. Installiert NixOS +The script: +1. Clones the repository (when using `-r`) +2. Partitions the disk (via `disks.nix` or `disks.sh`) +3. Generates `hardware.nix` (if not present) +4. Installs NixOS ### 2.4 Reboot @@ -140,34 +140,34 @@ Das Script: reboot ``` -## Schritt 3: SOPS-Secrets konfigurieren +## Step 3: Configure SOPS Secrets -Nach dem ersten Boot einloggen (Passwort: `changeme`, sofort aendern mit `passwd`). +After the first boot, log in (password: `changeme`, change immediately with `passwd`). -### 3.1 SSH-Host-Key zu Age-Key konvertieren +### 3.1 Convert SSH Host Key to Age Key -Auf dem **neuen Server**: +On the **new server**: ```bash nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' ``` -Ausgabe notieren (z.B. `age1abc123...`). +Note the output (e.g. `age1abc123...`). -Alternativ remote: +Alternatively, remotely: ```bash nix-shell -p ssh-to-age --run 'ssh-keyscan -p 2299 -t ed25519 | ssh-to-age' ``` -### 3.2 .sops.yaml aktualisieren +### 3.2 Update .sops.yaml -Auf dem **Entwicklungsrechner** den neuen Host-Key in `.sops.yaml` eintragen: +On the **development machine**, add the new host key to `.sops.yaml`: ```yaml keys: - - &steffen_key age1e8p... # steffen (lokal) - - &hostname_key age1abc... # Key von Schritt 3.1 + - &steffen_key age1e8p... # steffen (local) + - &hostname_key age1abc... # Key from step 3.1 creation_rules: - path_regex: hosts//secrets.yaml$ @@ -177,38 +177,38 @@ creation_rules: - *hostname_key ``` -### 3.3 Secrets erstellen +### 3.3 Create Secrets -Secrets-Datei oeffnen: +Open the secrets file: ```bash sops hosts//secrets.yaml ``` -Die folgende Tabelle zeigt alle Secrets fuer **cryodev-main** und wie sie generiert werden: +The following table shows all secrets for **cryodev-main** and how they are generated: -#### Sofort erstellbare Secrets +#### Immediately Available Secrets -Diese Secrets haben keine Abhaengigkeiten und koennen direkt generiert werden: +These secrets have no dependencies and can be generated directly: -| Secret | Befehl | -|--------|--------| +| Secret | Command | +|--------|---------| | `headplane/cookie_secret` | `openssl rand -hex 16` | -| `mailserver/accounts/admin` | `mkpasswd -sm bcrypt` (Passwort merken!) | -| `mailserver/accounts/forgejo` | `mkpasswd -sm bcrypt` (Passwort merken!) | -| `forgejo/mail-pw` | Klartext-Passwort das zum bcrypt-Hash von `mailserver/accounts/forgejo` passt | +| `mailserver/accounts/admin` | `mkpasswd -sm bcrypt` (remember the password!) | +| `mailserver/accounts/forgejo` | `mkpasswd -sm bcrypt` (remember the password!) | +| `forgejo/mail-pw` | Plaintext password matching the bcrypt hash of `mailserver/accounts/forgejo` | -#### Secrets die laufende Services brauchen +#### Secrets That Require Running Services -Diese Secrets koennen erst nach Schritt 4 erstellt werden. **Jetzt noch nicht eintragen** -- sie werden spaeter ergaenzt. +These secrets can only be created after step 4. **Do not add them yet** -- they will be added later. -| Secret | Befehl | Voraussetzung | -|--------|--------|---------------| -| `tailscale/auth-key` | Siehe Schritt 4.1-4.2 | Headscale laeuft | -| `headplane/agent_pre_authkey` | Siehe Schritt 4.1-4.2 | Headscale laeuft | -| `forgejo-runner/token` | Forgejo Admin Panel > Actions > Runners > Create Runner | Forgejo laeuft | +| Secret | Command | Prerequisite | +|--------|---------|--------------| +| `tailscale/auth-key` | See steps 4.1-4.2 | Headscale is running | +| `headplane/agent_pre_authkey` | See steps 4.1-4.2 | Headscale is running | +| `forgejo-runner/token` | Forgejo Admin Panel > Actions > Runners > Create Runner | Forgejo is running | -#### Beispiel secrets.yaml (Klartext vor Verschluesselung) +#### Example secrets.yaml (Plaintext Before Encryption) ```yaml headplane: @@ -218,22 +218,22 @@ mailserver: admin: "$2b$05$..." forgejo: "$2b$05$..." forgejo: - mail-pw: "das-klartext-passwort" + mail-pw: "the-plaintext-password" ``` -### 3.4 Services stufenweise reaktivieren -- Stufe 1 +### 3.4 Gradually Re-enable Services -- Stage 1 -> **Wichtig:** Services die Headscale- oder Forgejo-Secrets brauchen (Tailscale, -> Headplane, Forgejo-Runner) duerfen noch **nicht** aktiviert werden, da diese -> Secrets erst generiert werden koennen, wenn die Services laufen. +> **Important:** Services that require Headscale or Forgejo secrets (Tailscale, +> Headplane, Forgejo Runner) must **not** be enabled yet, as these +> secrets can only be generated once those services are running. -Auf dem **Entwicklungsrechner** in `hosts//services/default.nix` die -Services **ohne externe Abhaengigkeiten** aktivieren: +On the **development machine**, in `hosts//services/default.nix`, enable +the services **without external dependencies**: ```nix { imports = [ - # Stufe 1: Services ohne externe Abhaengigkeiten + # Stage 1: Services without external dependencies ./forgejo.nix ./headscale.nix ./mailserver.nix @@ -242,88 +242,88 @@ Services **ohne externe Abhaengigkeiten** aktivieren: ./openssh.nix ./sops.nix - # Stufe 2: Erst nach Schritt 4 aktivieren - # ./forgejo-runner.nix # braucht: forgejo-runner/token (Forgejo) - # ./headplane.nix # braucht: headplane/agent_pre_authkey (Headscale) - # ./tailscale.nix # braucht: tailscale/auth-key (Headscale) + # Stage 2: Enable only after step 4 + # ./forgejo-runner.nix # requires: forgejo-runner/token (Forgejo) + # ./headplane.nix # requires: headplane/agent_pre_authkey (Headscale) + # ./tailscale.nix # requires: tailscale/auth-key (Headscale) ]; } ``` -### 3.5 Deployen (Stufe 1) +### 3.5 Deploy (Stage 1) ```bash nix run .#deploy -- -n ``` -Dies nutzt die Konfiguration aus `deploy.json`. Alternativ manuell: +This uses the configuration from `deploy.json`. Alternatively, deploy manually: ```bash NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ --target-host @ --sudo --ask-sudo-password ``` -Nach diesem Deploy laufen Headscale, Forgejo, Mailserver und Nginx. +After this deployment, Headscale, Forgejo, Mailserver, and Nginx are running. -### 3.6 Forgejo Admin-Account erstellen +### 3.6 Create Forgejo Admin Account -Beim ersten Start hat Forgejo noch keine Benutzer. Admin-Account per CLI anlegen -(auf dem **Server**): +On first start, Forgejo has no users. Create an admin account via CLI +(on the **server**): ```bash forgejo admin user create \ - --username \ + --username \ --email @ \ - --password \ + --password \ --admin ``` -> **Hinweis:** Das `forgejo` Shell-Alias wird vom Modul bereitgestellt und fuehrt -> automatisch den Befehl als `forgejo`-User mit der richtigen Config aus. -> Falls der Alias nicht verfuegbar ist, neue Shell starten (`bash` oder `zsh`). +> **Note:** The `forgejo` shell alias is provided by the module and automatically +> runs the command as the `forgejo` user with the correct config. +> If the alias is not available, start a new shell (`bash` or `zsh`). > -> Da `DISABLE_REGISTRATION = true` gesetzt ist, koennen neue Accounts -> nur per CLI erstellt werden. +> Since `DISABLE_REGISTRATION = true` is set, new accounts +> can only be created via CLI. -## Schritt 4: Restliche Secrets generieren und alle Services aktivieren +## Step 4: Generate Remaining Secrets and Enable All Services -Nachdem der Server mit Headscale und Forgejo laeuft: +After the server is running with Headscale and Forgejo: -1. **Headscale-User anlegen** (auf dem Server): +1. **Create Headscale users** (on the server): ```bash sudo headscale users create default sudo headscale users create headplane-agent ``` -2. **User-IDs ermitteln** (wird fuer die Preauth-Keys benoetigt): +2. **Determine user IDs** (needed for the preauth keys): ```bash sudo headscale users list ``` - Die Ausgabe zeigt die numerischen IDs (z.B. `1` fuer default, `2` fuer headplane-agent). + The output shows the numeric IDs (e.g. `1` for default, `2` for headplane-agent). -3. **Preauth-Keys generieren** (mit den IDs aus Schritt 2): +3. **Generate preauth keys** (using the IDs from step 2): ```bash - # Fuer Tailscale (User-ID von "default" einsetzen) + # For Tailscale (use the user ID of "default") sudo headscale preauthkeys create --expiration 99y --reusable --user - # Fuer Headplane Agent (User-ID von "headplane-agent" einsetzen) + # For Headplane Agent (use the user ID of "headplane-agent") sudo headscale preauthkeys create --expiration 99y --user ``` -4. **Forgejo-Runner-Token** ueber das Forgejo Admin Panel erstellen: +4. **Create the Forgejo Runner token** via the Forgejo Admin Panel: Administration > Actions > Runners > Create new Runner -5. **Secrets ergaenzen**: +5. **Add the remaining secrets**: ```bash sops hosts//secrets.yaml ``` - Die fehlenden Secrets eintragen: + Add the missing secrets: ```yaml tailscale: @@ -334,7 +334,7 @@ Nachdem der Server mit Headscale und Forgejo laeuft: agent_pre_authkey: "..." ``` -6. **Stufe-2-Services aktivieren** in `hosts//services/default.nix`: +6. **Enable stage-2 services** in `hosts//services/default.nix`: ```nix { @@ -353,14 +353,14 @@ Nachdem der Server mit Headscale und Forgejo laeuft: } ``` -6. **Erneut deployen**: +6. **Deploy again**: ```bash nix run .#deploy -- -n ``` -## Naechste Schritte +## Next Steps -- [SOPS-Referenz](../services/sops.md) -- Detail-Dokumentation zur Secret-Verwaltung -- [SD-Image erstellen](sd-image.md) -- Raspberry Pi installieren -- [CD einrichten](../deployment/cd.md) -- Automatisches Deployment +- [SOPS Reference](../services/sops.md) -- Detailed documentation on secret management +- [Creating an SD Image](sd-image.md) -- Install Raspberry Pi +- [Set Up CD](../deployment/cd.md) -- Automatic deployment diff --git a/docs/getting-started/new-client.md b/docs/getting-started/new-client.md index 8cb29a8..dffb30a 100644 --- a/docs/getting-started/new-client.md +++ b/docs/getting-started/new-client.md @@ -1,60 +1,60 @@ -# Neuen Raspberry Pi Client hinzufügen +# Adding a New Raspberry Pi Client -Diese Anleitung beschreibt das Hinzufügen eines **neuen Raspberry Pi Clients** zur Infrastruktur. +This guide describes how to add a **new Raspberry Pi client** to the infrastructure. -## Übersicht: Der Ablauf +## Overview: The Process ``` -1. Konfiguration erstellen ──► Template kopieren, anpassen +1. Create configuration ──► Copy template, customize │ ▼ -2. Zur Image-Pipeline hinzufügen ──► Workflow-Matrix erweitern +2. Add to image pipeline ──► Extend workflow matrix │ ▼ -3. Push auf main ──► Forgejo baut automatisch SD-Image +3. Push to main ──► Forgejo automatically builds SD image │ ▼ -4. Image flashen & booten ──► SD-Karte beschreiben, Pi starten +4. Flash image & boot ──► Write SD card, start Pi │ ▼ -5. SOPS konfigurieren ──► Age-Key holen, Secrets erstellen +5. Configure SOPS ──► Retrieve age key, create secrets │ ▼ -6. Finales Deployment ──► Tailscale etc. aktivieren +6. Final deployment ──► Activate Tailscale etc. ``` -## Voraussetzungen +## Prerequisites -- SSH-Zugang zu cryodev-main (für Tailscale Auth-Key) -- Entwicklungsrechner mit Repository-Zugriff -- SD-Karte (mindestens 8 GB) +- SSH access to cryodev-main (for Tailscale auth key) +- Development machine with repository access +- SD card (at least 8 GB) --- -## Schritt 1: Tailscale Auth-Key generieren +## Step 1: Generate Tailscale Auth Key -**Auf cryodev-main** (per SSH): +**On cryodev-main** (via SSH): ```bash -# User-ID ermitteln +# Determine user ID sudo headscale users list -# Preauth-Key erstellen (User-ID von "default" einsetzen) +# Create preauth key (use user ID of "default") sudo headscale preauthkeys create --expiration 99y --reusable --user ``` -**Ausgabe notieren!** (z.B. `tskey-preauth-abc123...`) +**Take note of the output!** (e.g. `tskey-preauth-abc123...`) --- -## Schritt 2: Host-Konfiguration erstellen +## Step 2: Create Host Configuration -### 2.1 Template kopieren +### 2.1 Copy Template ```bash cp -r templates/raspberry-pi hosts/neuer-pi ``` -### 2.2 Hostname setzen +### 2.2 Set Hostname `hosts/neuer-pi/networking.nix`: @@ -64,58 +64,58 @@ cp -r templates/raspberry-pi hosts/neuer-pi } ``` -### 2.3 In flake.nix registrieren +### 2.3 Register in flake.nix ```nix nixosConfigurations = { - # ... bestehende Hosts ... + # ... existing hosts ... neuer-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/neuer-pi ]; }; ``` -### 2.4 In constants.nix eintragen +### 2.4 Add to constants.nix ```nix { hosts = { - # ... bestehende Hosts ... + # ... existing hosts ... neuer-pi = { - ip = "100.64.0.X"; # Wird von Headscale vergeben + ip = "100.64.0.X"; # Assigned by Headscale }; }; } ``` -### 2.5 Placeholder secrets.yaml erstellen +### 2.5 Create Placeholder secrets.yaml ```bash touch hosts/neuer-pi/secrets.yaml ``` -### 2.6 SOPS temporär deaktivieren +### 2.6 Temporarily Disable SOPS -In `hosts/neuer-pi/default.nix` die `sops.secrets.*` Referenzen auskommentieren, damit das Image ohne Secrets gebaut werden kann. +In `hosts/neuer-pi/default.nix`, comment out the `sops.secrets.*` references so the image can be built without secrets. --- -## Schritt 3: Zur Image-Pipeline hinzufügen +## Step 3: Add to Image Pipeline -Bearbeite `.forgejo/workflows/build-pi-image.yml`: +Edit `.forgejo/workflows/build-pi-image.yml`: ```yaml jobs: build-pi-images: strategy: matrix: - # Neuen Host hier hinzufügen: + # Add new host here: host: [cryodev-pi, neuer-pi] ``` --- -## Schritt 4: Push und Image bauen lassen +## Step 4: Push and Build Image ```bash git add . @@ -123,86 +123,86 @@ git commit -m "Add neuer-pi host configuration" git push ``` -Der Forgejo Workflow baut jetzt automatisch ein SD-Image für `neuer-pi`. +The Forgejo workflow will now automatically build an SD image for `neuer-pi`. -**Warten** bis der Workflow fertig ist (30-60 Minuten). Status prüfen unter: +**Wait** until the workflow completes (30-60 minutes). Check the status at: `https://git.cryodev.xyz/steffen/cryodev-server/actions` --- -## Schritt 5: Image flashen +## Step 5: Flash Image -### 5.1 Image herunterladen +### 5.1 Download Image -Nach erfolgreichem Build unter **Releases**: +After a successful build, find the image under **Releases**: ```bash wget https://git.cryodev.xyz/steffen/cryodev-server/releases/latest/download/neuer-pi-sd-image.img.zst ``` -### 5.2 Dekomprimieren +### 5.2 Decompress ```bash zstd -d neuer-pi-sd-image.img.zst -o neuer-pi.img ``` -### 5.3 Auf SD-Karte schreiben +### 5.3 Write to SD Card -**Achtung:** `/dev/sdX` durch das richtige Gerät ersetzen! +**Warning:** Replace `/dev/sdX` with the correct device! ```bash -lsblk # Richtiges Gerät finden +lsblk # Identify the correct device sudo dd if=neuer-pi.img of=/dev/sdX bs=4M conv=fsync status=progress ``` -### 5.4 Booten +### 5.4 Boot -1. SD-Karte in den Raspberry Pi einlegen -2. Ethernet anschließen -3. Strom anschließen -4. Warten bis gebootet (ca. 2 Minuten) +1. Insert the SD card into the Raspberry Pi +2. Connect Ethernet +3. Connect power +4. Wait until booted (approximately 2 minutes) --- -## Schritt 6: SOPS konfigurieren +## Step 6: Configure SOPS -### 6.1 IP-Adresse finden +### 6.1 Find IP Address -Der Pi sollte per DHCP eine IP bekommen. Prüfe deinen Router oder scanne das Netzwerk: +The Pi should receive an IP address via DHCP. Check your router or scan the network: ```bash nmap -sn 192.168.1.0/24 | grep -B2 "Raspberry" ``` -### 6.2 SSH verbinden +### 6.2 Connect via SSH ```bash -ssh steffen@ # oder der konfigurierte User +ssh steffen@ # or the configured user ``` -Standard-Passwort siehe `hosts/neuer-pi/users.nix`. +For the default password, see `hosts/neuer-pi/users.nix`. -### 6.3 Age-Key ermitteln +### 6.3 Determine Age Key -Auf dem Pi: +On the Pi: ```bash nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' ``` -**Ausgabe notieren!** (z.B. `age1xyz...`) +**Take note of the output!** (e.g. `age1xyz...`) -### 6.4 .sops.yaml aktualisieren +### 6.4 Update .sops.yaml -Auf dem Entwicklungsrechner: +On the development machine: ```yaml keys: - &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local) - - &neuer_pi_key age1xyz... # Der neue Key + - &neuer_pi_key age1xyz... # The new key creation_rules: - # ... bestehende Regeln ... + # ... existing rules ... - path_regex: hosts/neuer-pi/secrets.yaml$ key_groups: @@ -211,30 +211,30 @@ creation_rules: - *neuer_pi_key ``` -### 6.5 Secrets erstellen +### 6.5 Create Secrets ```bash sops hosts/neuer-pi/secrets.yaml ``` -Inhalt: +Contents: ```yaml tailscale: - auth-key: "tskey-preauth-abc123..." # Key aus Schritt 1 + auth-key: "tskey-preauth-abc123..." # Key from Step 1 netdata: stream: child-uuid: "..." # uuidgen ``` -### 6.6 SOPS-Referenzen aktivieren +### 6.6 Activate SOPS References -Die in Schritt 2.6 auskommentierten `sops.secrets.*` Referenzen wieder aktivieren. +Re-enable the `sops.secrets.*` references that were commented out in Step 2.6. --- -## Schritt 7: Finales Deployment +## Step 7: Final Deployment ```bash git add . @@ -242,9 +242,9 @@ git commit -m "Configure SOPS secrets for neuer-pi" git push ``` -Da Comin auf dem Pi läuft, wird er die neue Konfiguration automatisch pullen. +Since Comin is running on the Pi, it will automatically pull the new configuration. -Alternativ manuell: +Alternatively, deploy manually: ```bash NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .#neuer-pi \ @@ -253,34 +253,34 @@ NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .#neuer-pi \ --- -## Schritt 8: Verifizieren +## Step 8: Verify -### Tailscale-Verbindung +### Tailscale Connection ```bash -# Auf dem Pi +# On the Pi tailscale status -# Auf cryodev-main +# On cryodev-main sudo headscale nodes list ``` -### Netdata-Streaming +### Netdata Streaming -Prüfe ob der neue Client im Netdata-Dashboard erscheint: +Check whether the new client appears in the Netdata dashboard: `https://netdata.cryodev.xyz` --- -## Checkliste +## Checklist -- [ ] Tailscale Auth-Key auf cryodev-main generiert -- [ ] Host-Konfiguration erstellt (Template, flake.nix, constants.nix) -- [ ] Host zur Workflow-Matrix hinzugefügt -- [ ] Gepusht und auf Image-Build gewartet -- [ ] SD-Karte geflasht und Pi gebootet -- [ ] Age-Key ermittelt und in .sops.yaml eingetragen -- [ ] secrets.yaml erstellt (Tailscale-Key, Netdata-UUID) -- [ ] SOPS-Referenzen aktiviert und deployed -- [ ] Tailscale-Verbindung funktioniert -- [ ] Netdata-Streaming funktioniert +- [ ] Tailscale auth key generated on cryodev-main +- [ ] Host configuration created (template, flake.nix, constants.nix) +- [ ] Host added to workflow matrix +- [ ] Pushed and waited for image build +- [ ] SD card flashed and Pi booted +- [ ] Age key determined and added to .sops.yaml +- [ ] secrets.yaml created (Tailscale key, Netdata UUID) +- [ ] SOPS references activated and deployed +- [ ] Tailscale connection working +- [ ] Netdata streaming working diff --git a/docs/getting-started/reinstall.md b/docs/getting-started/reinstall.md index 091378c..cb7775f 100644 --- a/docs/getting-started/reinstall.md +++ b/docs/getting-started/reinstall.md @@ -1,70 +1,70 @@ -# Neuinstallation (Reinstall) +# Reinstallation -Diese Anleitung beschreibt die **Neuinstallation** eines bestehenden Hosts, z.B. nach Hardwarewechsel oder bei Problemen. +This guide describes the **reinstallation** of an existing host, e.g. after a hardware change or in case of issues. -## Unterschied zur Erstinstallation +## Difference from Initial Installation -| Aspekt | Erstinstallation | Neuinstallation | -|--------|------------------|-----------------| -| SOPS-Secrets | Noch nicht vorhanden | Bereits konfiguriert | -| SSH-Host-Key | Neu generiert | **Muss wiederhergestellt werden!** | -| Disk-IDs | Neu ermitteln | Oft geändert (neue Hardware) | -| secrets.yaml | Wird erstellt | Bereits vorhanden | +| Aspect | Initial Installation | Reinstallation | +|--------|----------------------|----------------| +| SOPS Secrets | Not yet present | Already configured | +| SSH Host Key | Newly generated | **Must be restored!** | +| Disk IDs | Newly determined | Often changed (new hardware) | +| secrets.yaml | Will be created | Already exists | -## Wichtig: SSH-Host-Key Problem +## Important: SSH Host Key Issue -Bei einer Neuinstallation wird ein **neuer SSH-Host-Key** generiert. Dieser stimmt nicht mehr mit dem Age-Key in `.sops.yaml` überein! +During a reinstallation, a **new SSH host key** is generated. This key will no longer match the age key in `.sops.yaml`! -### Lösungsmöglichkeiten +### Possible Solutions -**Option A: Alten Host-Key sichern und wiederherstellen** (empfohlen) +**Option A: Back up and restore the old host key** (recommended) -**Option B: Neuen Key generieren und SOPS aktualisieren** +**Option B: Generate a new key and update SOPS** -## Voraussetzungen +## Prerequisites -- Backup des alten SSH-Host-Keys (falls Option A) -- Zugriff auf `.sops.yaml` und die Admin-Age-Keys -- Bootbares NixOS ISO +- Backup of the old SSH host key (if using Option A) +- Access to `.sops.yaml` and the admin age keys +- Bootable NixOS ISO -## Schritt 1: Vorbereitung (vor der Installation) +## Step 1: Preparation (before the installation) -### 1.1 Alten SSH-Host-Key sichern (Option A) +### 1.1 Back Up the Old SSH Host Key (Option A) -Falls der alte Host noch läuft: +If the old host is still running: ```bash -# Auf dem alten Host +# On the old host sudo cat /etc/ssh/ssh_host_ed25519_key > ~/ssh_host_ed25519_key.backup sudo cat /etc/ssh/ssh_host_ed25519_key.pub > ~/ssh_host_ed25519_key.pub.backup ``` -Dateien sicher auf den Entwicklungsrechner kopieren. +Copy the files securely to the development machine. -### 1.2 Disk-IDs ermitteln +### 1.2 Determine Disk IDs -**Bei neuer Hardware** ändern sich die Disk-IDs! +**With new hardware**, the disk IDs will change! ```bash -# Im NixOS Live-System +# In the NixOS live system lsblk -o NAME,SIZE,MODEL,SERIAL ls -la /dev/disk/by-id/ ``` -Die neue Disk-ID in `hosts//disks.sh` oder `disks.nix` eintragen: +Enter the new disk ID in `hosts//disks.sh` or `disks.nix`: ```bash -# Beispiel disks.sh +# Example disks.sh DISK="/dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_XXXXX" ``` -## Schritt 2: Installation durchführen +## Step 2: Perform the Installation -### 2.1 NixOS ISO booten +### 2.1 Boot the NixOS ISO -Von USB/CD booten, Root-Passwort setzen, per SSH verbinden. +Boot from USB/CD, set a root password, and connect via SSH. -### 2.2 Repository klonen +### 2.2 Clone the Repository ```bash sudo -i @@ -73,30 +73,30 @@ git clone /tmp/nixos cd /tmp/nixos ``` -### 2.3 Disk-Konfiguration prüfen +### 2.3 Verify the Disk Configuration ```bash -# Aktuelle Disk-IDs anzeigen +# Display current disk IDs ls -la /dev/disk/by-id/ -# Mit Konfiguration vergleichen +# Compare with the configuration cat hosts//disks.sh | grep DISK ``` -**Falls nötig:** Disk-ID in der Konfiguration anpassen. +**If necessary:** Update the disk ID in the configuration. -### 2.4 Install-Script ausführen +### 2.4 Run the Install Script ```bash bash scripts/install.sh -n ``` -### 2.5 SSH-Host-Key wiederherstellen (Option A) +### 2.5 Restore the SSH Host Key (Option A) -**Vor dem Reboot!** +**Before rebooting!** ```bash -# Host-Key vom Backup wiederherstellen +# Restore the host key from backup cp /path/to/ssh_host_ed25519_key.backup /mnt/etc/ssh/ssh_host_ed25519_key cp /path/to/ssh_host_ed25519_key.pub.backup /mnt/etc/ssh/ssh_host_ed25519_key.pub chmod 600 /mnt/etc/ssh/ssh_host_ed25519_key @@ -110,75 +110,75 @@ umount -Rl /mnt reboot ``` -## Schritt 3: Nach dem Reboot +## Step 3: After the Reboot -### Bei Option A (Key wiederhergestellt) +### Option A (Key Restored) -SOPS-Secrets sollten automatisch funktionieren. Testen: +SOPS secrets should work automatically. Verify: ```bash sudo cat /run/secrets/tailscale/auth-key ``` -### Bei Option B (Neuer Key) +### Option B (New Key) -Der Host kann die Secrets nicht entschlüsseln. Neuen Key konfigurieren: +The host cannot decrypt the secrets. Configure the new key: ```bash -# Neuen Age-Key ermitteln +# Determine the new age key nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' ``` -Auf dem Entwicklungsrechner: +On the development machine: ```bash -# .sops.yaml aktualisieren mit neuem Key +# Update .sops.yaml with the new key vim .sops.yaml -# Secrets mit neuem Key neu verschlüsseln +# Re-encrypt secrets with the new key sops updatekeys hosts//secrets.yaml ``` -Dann Konfiguration neu deployen: +Then redeploy the configuration: ```bash NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ --target-host @ --sudo --ask-sudo-password ``` -## Häufige Probleme +## Common Issues ### "No secret key available" -SOPS kann die Secrets nicht entschlüsseln. Ursache: -- SSH-Host-Key stimmt nicht mit Age-Key in `.sops.yaml` überein +SOPS cannot decrypt the secrets. Cause: +- SSH host key does not match the age key in `.sops.yaml` -Lösung: Option B durchführen (neuen Key konfigurieren). +Solution: Follow Option B (configure the new key). -### "Device not found" beim Partitionieren +### "Device not found" during partitioning -Disk-ID in `disks.sh`/`disks.nix` ist falsch. +The disk ID in `disks.sh`/`disks.nix` is incorrect. ```bash -# Richtige ID finden +# Find the correct ID ls -la /dev/disk/by-id/ ``` -### Hardware-Config veraltet +### Outdated Hardware Config -Bei neuer Hardware muss `hardware.nix` neu generiert werden: +With new hardware, `hardware.nix` must be regenerated: ```bash -# Install-Script generiert automatisch neu, falls Datei fehlt +# The install script regenerates automatically if the file is missing rm hosts//hardware.nix bash scripts/install.sh -n ``` -## Checkliste +## Checklist -- [ ] Alten SSH-Host-Key gesichert (falls möglich) -- [ ] Disk-IDs in Konfiguration geprüft/aktualisiert -- [ ] Installation durchgeführt -- [ ] SSH-Host-Key wiederhergestellt ODER neuen Key in SOPS konfiguriert -- [ ] Secrets funktionieren (`sudo cat /run/secrets/...`) -- [ ] Tailscale verbunden (`tailscale status`) +- [ ] Old SSH host key backed up (if possible) +- [ ] Disk IDs in configuration verified/updated +- [ ] Installation completed +- [ ] SSH host key restored OR new key configured in SOPS +- [ ] Secrets are functional (`sudo cat /run/secrets/...`) +- [ ] Tailscale connected (`tailscale status`) diff --git a/docs/getting-started/sd-image.md b/docs/getting-started/sd-image.md index f6a5c70..2bde0d1 100644 --- a/docs/getting-started/sd-image.md +++ b/docs/getting-started/sd-image.md @@ -1,95 +1,95 @@ -# SD-Karten-Images für Raspberry Pi +# SD Card Images for Raspberry Pi -Das Repository baut automatisch SD-Karten-Images für alle konfigurierten Raspberry Pi Hosts. +The repository automatically builds SD card images for all configured Raspberry Pi hosts. -## Automatischer Build +## Automatic Build -Bei Änderungen an `main` werden automatisch Images für alle Pi-Hosts gebaut und als Release veröffentlicht. +When changes are pushed to `main`, images are automatically built for all Pi hosts and published as a release. -**Download:** [Releases auf Forgejo](https://git.cryodev.xyz/steffen/cryodev-server/releases) +**Download:** [Releases on Forgejo](https://git.cryodev.xyz/steffen/cryodev-server/releases) -## Verfügbare Images +## Available Images -| Host | Image-Name | +| Host | Image Name | |------|------------| | `cryodev-pi` | `cryodev-pi-sd-image.img.zst` | -Neue Hosts werden automatisch gebaut, wenn sie zur Workflow-Matrix hinzugefügt werden. +New hosts are built automatically once they are added to the workflow matrix. -## Image flashen +## Flashing the Image -### 1. Herunterladen +### 1. Download ```bash wget https://git.cryodev.xyz/.../releases/latest/download/-sd-image.img.zst wget https://git.cryodev.xyz/.../releases/latest/download/-sd-image.img.zst.sha256 -# Checksum prüfen +# Verify checksum sha256sum -c -sd-image.img.zst.sha256 ``` -### 2. Dekomprimieren +### 2. Decompress ```bash zstd -d -sd-image.img.zst -o .img ``` -### 3. Auf SD-Karte schreiben +### 3. Write to SD Card ```bash -# Richtiges Gerät finden +# Identify the correct device lsblk -# Schreiben (ACHTUNG: richtiges Gerät wählen!) +# Write (WARNING: make sure to select the correct device!) sudo dd if=.img of=/dev/sdX bs=4M conv=fsync status=progress ``` -Alternativ: `balenaEtcher` oder `Raspberry Pi Imager` verwenden. +Alternatively, use `balenaEtcher` or `Raspberry Pi Imager`. -## Was ist im Image? +## What Is Included in the Image? -- Vollständige NixOS-Installation für den spezifischen Host -- Alle konfigurierten Services (außer Secrets) -- SSH-Server aktiviert -- Automatische Root-Partition-Erweiterung beim ersten Boot -- Comin für automatische Updates +- Complete NixOS installation for the specific host +- All configured services (except secrets) +- SSH server enabled +- Automatic root partition expansion on first boot +- Comin for automatic updates -## Was fehlt? +## What Is Missing? -**SOPS-Secrets** können nicht im Image enthalten sein (Henne-Ei-Problem mit SSH-Host-Key). +**SOPS secrets** cannot be included in the image (chicken-and-egg problem with the SSH host key). -Nach dem ersten Boot: -1. Age-Key vom Pi holen -2. `.sops.yaml` aktualisieren -3. `secrets.yaml` erstellen -4. Konfiguration deployen +After the first boot: +1. Retrieve the age key from the Pi +2. Update `.sops.yaml` +3. Create `secrets.yaml` +4. Deploy the configuration -Siehe [Neuen Client hinzufügen](new-client.md) für die vollständige Anleitung. +See [Adding a New Client](new-client.md) for the complete guide. -## Neuen Host zur Pipeline hinzufügen +## Adding a New Host to the Pipeline -1. Host-Konfiguration in `hosts//` erstellen -2. In `.forgejo/workflows/build-pi-image.yml` zur Matrix hinzufügen: +1. Create the host configuration in `hosts//` +2. Add it to the matrix in `.forgejo/workflows/build-pi-image.yml`: ```yaml matrix: - host: [cryodev-pi, neuer-host] # <- hier hinzufügen + host: [cryodev-pi, new-host] # <- add here ``` -3. Push auf `main` → Image wird automatisch gebaut +3. Push to `main` -- the image will be built automatically -## Manuell bauen +## Building Manually ```bash -# Auf aarch64 (z.B. anderem Pi) +# On aarch64 (e.g., another Pi) nix build .#nixosConfigurations..config.system.build.sdImage -# Auf x86_64 mit QEMU-Emulation (langsam) +# On x86_64 with QEMU emulation (slow) nix build .#nixosConfigurations..config.system.build.sdImage \ --extra-platforms aarch64-linux ``` -Voraussetzung auf x86_64: +Prerequisite on x86_64: ```nix { @@ -99,18 +99,18 @@ Voraussetzung auf x86_64: ## Troubleshooting -### Workflow schlägt fehl +### Workflow Fails -- Prüfe ob `sd-image.nix` in der Host-Konfiguration importiert wird -- Prüfe ob binfmt auf cryodev-main aktiviert ist +- Check whether `sd-image.nix` is imported in the host configuration +- Check whether binfmt is enabled on cryodev-main -### Image bootet nicht +### Image Does Not Boot -- SD-Karte korrekt beschrieben? -- Andere SD-Karte versuchen -- Stromversorgung prüfen (min. 3A für Pi 4) +- Was the SD card written correctly? +- Try a different SD card +- Check the power supply (minimum 3A for Pi 4) -### Kein Netzwerk +### No Network -- Ethernet-Kabel prüfen -- DHCP-Server im Netzwerk? +- Check the Ethernet cable +- Is there a DHCP server on the network? diff --git a/docs/index.md b/docs/index.md index c63bb6c..e7b9258 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,33 +1,33 @@ # Cryodev NixOS Configuration Documentation -Willkommen zur Dokumentation der **cryodev** NixOS-Infrastruktur. +Welcome to the documentation for the **cryodev** NixOS infrastructure. ## Quick Links ### Getting Started -- [Voraussetzungen](getting-started/prerequisites.md) - Benötigte Tools -- [Neuen Raspberry Pi hinzufügen](getting-started/new-client.md) - Kompletter Workflow für neue Clients -- [SD-Image Referenz](getting-started/sd-image.md) - Details zum Image-Build -- [Erstinstallation (Server)](getting-started/first-install.md) - Bootstrap für x86_64 Hosts -- [Neuinstallation](getting-started/reinstall.md) - Reinstall mit Hardware-Änderungen +- [Prerequisites](getting-started/prerequisites.md) - Required tools +- [Adding a New Raspberry Pi](getting-started/new-client.md) - Complete workflow for new clients +- [SD Image Reference](getting-started/sd-image.md) - Details on image building +- [First Installation (Server)](getting-started/first-install.md) - Bootstrap for x86_64 hosts +- [Reinstallation](getting-started/reinstall.md) - Reinstall with hardware changes ### Services -- [SOPS Secrets](services/sops.md) - Geheimnisverwaltung mit sops-nix -- [Headscale](services/headscale.md) - Self-hosted Tailscale Server -- [Headplane](services/headplane.md) - Web-UI für Headscale -- [Tailscale](services/tailscale.md) - Mesh-VPN Client -- [Mailserver](services/mailserver.md) - E-Mail Stack (Postfix/Dovecot) -- [Forgejo](services/forgejo.md) - Git-Hosting mit CI/CD -- [Netdata](services/netdata.md) - Monitoring und Alerting +- [SOPS Secrets](services/sops.md) - Secret management with sops-nix +- [Headscale](services/headscale.md) - Self-hosted Tailscale server +- [Headplane](services/headplane.md) - Web UI for Headscale +- [Tailscale](services/tailscale.md) - Mesh VPN client +- [Mailserver](services/mailserver.md) - Email stack (Postfix/Dovecot) +- [Forgejo](services/forgejo.md) - Git hosting with CI/CD +- [Netdata](services/netdata.md) - Monitoring and alerting ### Deployment -- [Continuous Deployment](deployment/cd.md) - Push- und Pull-basiertes Deployment -- [DNS-Konfiguration](deployment/dns.md) - Benötigte DNS-Einträge +- [Continuous Deployment](deployment/cd.md) - Push- and pull-based deployment +- [DNS Configuration](deployment/dns.md) - Required DNS records -## Architektur +## Architecture ``` Internet @@ -57,38 +57,38 @@ Willkommen zur Dokumentation der **cryodev** NixOS-Infrastruktur. +-------------------+ ``` -## Installations-Szenarien +## Installation Scenarios -| Szenario | Beschreibung | Anleitung | -|----------|--------------|-----------| -| **Neuer Raspberry Pi** | Config erstellen → Image bauen → Flashen | [new-client.md](getting-started/new-client.md) | -| **Erstinstallation (Server)** | x86_64 Host, manuelle Installation | [first-install.md](getting-started/first-install.md) | -| **Neuinstallation** | Bestehender Host, neue Hardware | [reinstall.md](getting-started/reinstall.md) | +| Scenario | Description | Guide | +|----------|-------------|-------| +| **New Raspberry Pi** | Create config, build image, flash | [new-client.md](getting-started/new-client.md) | +| **First Installation (Server)** | x86_64 host, manual installation | [first-install.md](getting-started/first-install.md) | +| **Reinstallation** | Existing host, new hardware | [reinstall.md](getting-started/reinstall.md) | -Für Raspberry Pi: [SD-Image Referenz](getting-started/sd-image.md) +For Raspberry Pi: [SD Image Reference](getting-started/sd-image.md) -## Verzeichnisstruktur +## Directory Structure ``` . ├── flake.nix # Entry point, inputs and outputs -├── constants.nix # Zentrale Config (Domains, IPs, Ports) -├── hosts/ # Host-spezifische Konfigurationen +├── constants.nix # Central configuration (domains, IPs, ports) +├── hosts/ # Host-specific configurations │ ├── cryodev-main/ │ └── cryodev-pi/ -├── modules/ # Wiederverwendbare NixOS-Module +├── modules/ # Reusable NixOS modules │ └── nixos/ -├── pkgs/ # Eigene Pakete -├── overlays/ # Nixpkgs Overlays -├── templates/ # Templates für neue Hosts -├── scripts/ # Helper-Scripts (install.sh) -├── apps/ # Nix Apps (rebuild) -└── lib/ # Helper-Funktionen (utils.nix) +├── pkgs/ # Custom packages +├── overlays/ # Nixpkgs overlays +├── templates/ # Templates for new hosts +├── scripts/ # Helper scripts (install.sh) +├── apps/ # Nix apps (rebuild) +└── lib/ # Helper functions (utils.nix) ``` -## Deployment-Strategien +## Deployment Strategies -| Host | Strategie | Tool | Beschreibung | -|------|-----------|------|--------------| -| `cryodev-main` | Pull-basiert | Comin | Pollt Repository auf Aenderungen | -| `cryodev-pi` | Pull-basiert | Comin | Pollt Repository auf Aenderungen | +| Host | Strategy | Tool | Description | +|------|----------|------|-------------| +| `cryodev-main` | Pull-based | Comin | Polls the repository for changes | +| `cryodev-pi` | Pull-based | Comin | Polls the repository for changes | diff --git a/docs/services/forgejo.md b/docs/services/forgejo.md index 5955405..7f1191f 100644 --- a/docs/services/forgejo.md +++ b/docs/services/forgejo.md @@ -89,9 +89,9 @@ See [CD documentation](../deployment/cd.md) for details. ```bash forgejo admin user create \ - --username \ + --username \ --email @ \ - --password \ + --password \ --admin ``` diff --git a/docs/services/tailscale.md b/docs/services/tailscale.md index 768a21c..fea4252 100644 --- a/docs/services/tailscale.md +++ b/docs/services/tailscale.md @@ -14,9 +14,9 @@ Tailscale clients connect to the self-hosted Headscale server to join the mesh V On the Headscale server (cryodev-main): ```bash -# User-ID ermitteln +# Look up user ID sudo headscale users list -# Preauth-Key erstellen (User-ID von "default" einsetzen) +# Create preauth key (use the user ID for "default") sudo headscale preauthkeys create --expiration 99y --reusable --user ```