From 430194beda856ca03f2a650cbdfaf31a76d3268c Mon Sep 17 00:00:00 2001 From: stherm Date: Fri, 6 Mar 2026 08:31:13 +0100 Subject: [PATCH] Initial commit --- .forgejo/workflows/build-hosts.yml | 24 + .forgejo/workflows/deploy-main.yml | 34 + .forgejo/workflows/flake-check.yml | 18 + .sops.yaml | 13 + AGENTS.md | 114 + INSTRUCTIONS.md | 391 ++ README.md | 147 + constants.nix | 38 + digest.txt | 4930 +++++++++++++++++ flake.nix | 135 + hosts/cryodev-main/boot.nix | 7 + hosts/cryodev-main/default.nix | 21 + hosts/cryodev-main/disks.sh | 63 + hosts/cryodev-main/hardware.nix | 48 + hosts/cryodev-main/networking.nix | 4 + hosts/cryodev-main/packages.nix | 5 + hosts/cryodev-main/services/default.nix | 13 + hosts/cryodev-main/services/forgejo.nix | 51 + hosts/cryodev-main/services/headplane.nix | 35 + hosts/cryodev-main/services/headscale.nix | 32 + hosts/cryodev-main/services/mailserver.nix | 27 + hosts/cryodev-main/services/netdata.nix | 34 + hosts/cryodev-main/services/nginx.nix | 22 + hosts/cryodev-main/services/openssh.nix | 12 + hosts/cryodev-main/services/sops.nix | 21 + hosts/cryodev-main/services/tailscale.nix | 23 + hosts/cryodev-main/users.nix | 8 + hosts/cryodev-pi/boot.nix | 8 + hosts/cryodev-pi/default.nix | 21 + hosts/cryodev-pi/disks.sh | 63 + hosts/cryodev-pi/hardware.nix | 23 + hosts/cryodev-pi/networking.nix | 4 + hosts/cryodev-pi/packages.nix | 5 + hosts/cryodev-pi/services/comin.nix | 24 + hosts/cryodev-pi/services/default.nix | 9 + hosts/cryodev-pi/services/netdata.nix | 31 + hosts/cryodev-pi/services/nginx.nix | 14 + hosts/cryodev-pi/services/openssh.nix | 12 + hosts/cryodev-pi/services/tailscale.nix | 28 + hosts/cryodev-pi/users.nix | 9 + modules/nixos/comin/default.nix | 8 + modules/nixos/common/default.nix | 15 + modules/nixos/common/environment.nix | 63 + modules/nixos/common/htop.nix | 8 + modules/nixos/common/nationalization.nix | 31 + modules/nixos/common/networking.nix | 40 + modules/nixos/common/nix.nix | 19 + modules/nixos/common/overlays.nix | 10 + modules/nixos/common/shared/default.nix | 5 + modules/nixos/common/shared/nix.nix | 85 + modules/nixos/common/sudo.nix | 26 + modules/nixos/common/well-known.nix | 17 + modules/nixos/common/zsh.nix | 26 + modules/nixos/default.nix | 13 + modules/nixos/forgejo-runner/default.nix | 68 + modules/nixos/forgejo/default.nix | 63 + modules/nixos/headplane/default.nix | 78 + modules/nixos/headscale/acl.hujson | 17 + modules/nixos/headscale/default.nix | 105 + modules/nixos/mailserver/default.nix | 104 + modules/nixos/nginx/default.nix | 72 + modules/nixos/nixvim/default.nix | 106 + modules/nixos/nixvim/keymaps.nix | 347 ++ modules/nixos/nixvim/plugins/cmp.nix | 54 + modules/nixos/nixvim/plugins/default.nix | 18 + modules/nixos/nixvim/plugins/lsp.nix | 69 + modules/nixos/nixvim/plugins/lualine.nix | 18 + modules/nixos/nixvim/plugins/telescope.nix | 52 + modules/nixos/nixvim/plugins/treesitter.nix | 42 + modules/nixos/nixvim/plugins/trouble.nix | 43 + modules/nixos/nixvim/spellfiles.nix | 26 + modules/nixos/normalUsers/default.nix | 69 + modules/nixos/openssh/default.nix | 16 + modules/nixos/sops/default.nix | 21 + modules/nixos/tailscale/default.nix | 50 + overlays/default.nix | 32 + pkgs/default.nix | 8 + scripts/install.sh | 173 + templates/generic-server/boot.nix | 8 + templates/generic-server/default.nix | 21 + templates/generic-server/disks.sh | 63 + templates/generic-server/flake.nix | 4 + templates/generic-server/hardware.nix | 23 + templates/generic-server/networking.nix | 4 + templates/generic-server/packages.nix | 5 + templates/generic-server/services/comin.nix | 24 + templates/generic-server/services/default.nix | 9 + templates/generic-server/services/netdata.nix | 31 + templates/generic-server/services/nginx.nix | 14 + templates/generic-server/services/openssh.nix | 12 + .../generic-server/services/tailscale.nix | 28 + templates/generic-server/users.nix | 9 + templates/raspberry-pi/boot.nix | 8 + templates/raspberry-pi/default.nix | 21 + templates/raspberry-pi/disks.sh | 63 + templates/raspberry-pi/flake.nix | 4 + templates/raspberry-pi/hardware.nix | 23 + templates/raspberry-pi/networking.nix | 4 + templates/raspberry-pi/packages.nix | 5 + templates/raspberry-pi/services/comin.nix | 24 + templates/raspberry-pi/services/default.nix | 9 + templates/raspberry-pi/services/netdata.nix | 31 + templates/raspberry-pi/services/nginx.nix | 14 + templates/raspberry-pi/services/openssh.nix | 12 + templates/raspberry-pi/services/tailscale.nix | 28 + templates/raspberry-pi/users.nix | 9 + users/cryotherm/default.nix | 7 + users/steffen/default.nix | 10 + users/steffen/pubkeys/X670E.pub | 1 + 109 files changed, 9066 insertions(+) create mode 100644 .forgejo/workflows/build-hosts.yml create mode 100644 .forgejo/workflows/deploy-main.yml create mode 100644 .forgejo/workflows/flake-check.yml create mode 100644 .sops.yaml create mode 100644 AGENTS.md create mode 100644 INSTRUCTIONS.md create mode 100644 README.md create mode 100644 constants.nix create mode 100644 digest.txt create mode 100644 flake.nix create mode 100644 hosts/cryodev-main/boot.nix create mode 100644 hosts/cryodev-main/default.nix create mode 100644 hosts/cryodev-main/disks.sh create mode 100644 hosts/cryodev-main/hardware.nix create mode 100644 hosts/cryodev-main/networking.nix create mode 100644 hosts/cryodev-main/packages.nix create mode 100644 hosts/cryodev-main/services/default.nix create mode 100644 hosts/cryodev-main/services/forgejo.nix create mode 100644 hosts/cryodev-main/services/headplane.nix create mode 100644 hosts/cryodev-main/services/headscale.nix create mode 100644 hosts/cryodev-main/services/mailserver.nix create mode 100644 hosts/cryodev-main/services/netdata.nix create mode 100644 hosts/cryodev-main/services/nginx.nix create mode 100644 hosts/cryodev-main/services/openssh.nix create mode 100644 hosts/cryodev-main/services/sops.nix create mode 100644 hosts/cryodev-main/services/tailscale.nix create mode 100644 hosts/cryodev-main/users.nix create mode 100644 hosts/cryodev-pi/boot.nix create mode 100644 hosts/cryodev-pi/default.nix create mode 100644 hosts/cryodev-pi/disks.sh create mode 100644 hosts/cryodev-pi/hardware.nix create mode 100644 hosts/cryodev-pi/networking.nix create mode 100644 hosts/cryodev-pi/packages.nix create mode 100644 hosts/cryodev-pi/services/comin.nix create mode 100644 hosts/cryodev-pi/services/default.nix create mode 100644 hosts/cryodev-pi/services/netdata.nix create mode 100644 hosts/cryodev-pi/services/nginx.nix create mode 100644 hosts/cryodev-pi/services/openssh.nix create mode 100644 hosts/cryodev-pi/services/tailscale.nix create mode 100644 hosts/cryodev-pi/users.nix create mode 100644 modules/nixos/comin/default.nix create mode 100644 modules/nixos/common/default.nix create mode 100644 modules/nixos/common/environment.nix create mode 100644 modules/nixos/common/htop.nix create mode 100644 modules/nixos/common/nationalization.nix create mode 100644 modules/nixos/common/networking.nix create mode 100644 modules/nixos/common/nix.nix create mode 100644 modules/nixos/common/overlays.nix create mode 100644 modules/nixos/common/shared/default.nix create mode 100644 modules/nixos/common/shared/nix.nix create mode 100644 modules/nixos/common/sudo.nix create mode 100644 modules/nixos/common/well-known.nix create mode 100644 modules/nixos/common/zsh.nix create mode 100644 modules/nixos/default.nix create mode 100644 modules/nixos/forgejo-runner/default.nix create mode 100644 modules/nixos/forgejo/default.nix create mode 100644 modules/nixos/headplane/default.nix create mode 100644 modules/nixos/headscale/acl.hujson create mode 100644 modules/nixos/headscale/default.nix create mode 100644 modules/nixos/mailserver/default.nix create mode 100644 modules/nixos/nginx/default.nix create mode 100644 modules/nixos/nixvim/default.nix create mode 100644 modules/nixos/nixvim/keymaps.nix create mode 100644 modules/nixos/nixvim/plugins/cmp.nix create mode 100644 modules/nixos/nixvim/plugins/default.nix create mode 100644 modules/nixos/nixvim/plugins/lsp.nix create mode 100644 modules/nixos/nixvim/plugins/lualine.nix create mode 100644 modules/nixos/nixvim/plugins/telescope.nix create mode 100644 modules/nixos/nixvim/plugins/treesitter.nix create mode 100644 modules/nixos/nixvim/plugins/trouble.nix create mode 100644 modules/nixos/nixvim/spellfiles.nix create mode 100644 modules/nixos/normalUsers/default.nix create mode 100644 modules/nixos/openssh/default.nix create mode 100644 modules/nixos/sops/default.nix create mode 100644 modules/nixos/tailscale/default.nix create mode 100644 overlays/default.nix create mode 100644 pkgs/default.nix create mode 100755 scripts/install.sh create mode 100644 templates/generic-server/boot.nix create mode 100644 templates/generic-server/default.nix create mode 100644 templates/generic-server/disks.sh create mode 100644 templates/generic-server/flake.nix create mode 100644 templates/generic-server/hardware.nix create mode 100644 templates/generic-server/networking.nix create mode 100644 templates/generic-server/packages.nix create mode 100644 templates/generic-server/services/comin.nix create mode 100644 templates/generic-server/services/default.nix create mode 100644 templates/generic-server/services/netdata.nix create mode 100644 templates/generic-server/services/nginx.nix create mode 100644 templates/generic-server/services/openssh.nix create mode 100644 templates/generic-server/services/tailscale.nix create mode 100644 templates/generic-server/users.nix create mode 100644 templates/raspberry-pi/boot.nix create mode 100644 templates/raspberry-pi/default.nix create mode 100644 templates/raspberry-pi/disks.sh create mode 100644 templates/raspberry-pi/flake.nix create mode 100644 templates/raspberry-pi/hardware.nix create mode 100644 templates/raspberry-pi/networking.nix create mode 100644 templates/raspberry-pi/packages.nix create mode 100644 templates/raspberry-pi/services/comin.nix create mode 100644 templates/raspberry-pi/services/default.nix create mode 100644 templates/raspberry-pi/services/netdata.nix create mode 100644 templates/raspberry-pi/services/nginx.nix create mode 100644 templates/raspberry-pi/services/openssh.nix create mode 100644 templates/raspberry-pi/services/tailscale.nix create mode 100644 templates/raspberry-pi/users.nix create mode 100644 users/cryotherm/default.nix create mode 100644 users/steffen/default.nix create mode 100644 users/steffen/pubkeys/X670E.pub diff --git a/.forgejo/workflows/build-hosts.yml b/.forgejo/workflows/build-hosts.yml new file mode 100644 index 0000000..7987dc3 --- /dev/null +++ b/.forgejo/workflows/build-hosts.yml @@ -0,0 +1,24 @@ +name: Build hosts + +on: + pull_request: + branches: + - main + +jobs: + build-hosts: + 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: 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 diff --git a/.forgejo/workflows/deploy-main.yml b/.forgejo/workflows/deploy-main.yml new file mode 100644 index 0000000..4140f01 --- /dev/null +++ b/.forgejo/workflows/deploy-main.yml @@ -0,0 +1,34 @@ +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 diff --git a/.forgejo/workflows/flake-check.yml b/.forgejo/workflows/flake-check.yml new file mode 100644 index 0000000..aed0b59 --- /dev/null +++ b/.forgejo/workflows/flake-check.yml @@ -0,0 +1,18 @@ +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/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..104f256 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,13 @@ +keys: + - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # Admin key (Steffen) +creation_rules: + - path_regex: hosts/cryodev-main/secrets.yaml$ + key_groups: + - age: + - *admin_key + # - *server_key # Add server key here once obtained + - path_regex: hosts/cryodev-pi/secrets.yaml$ + key_groups: + - age: + - *admin_key + # - *pi_key # Add pi key here once obtained diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..91c1fbf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,114 @@ +# Agent Guidelines for NixOS Configuration + +## Project Overview +This repository contains a NixOS configuration managed with Nix Flakes. It defines system configurations for one or more hosts (currently `cryodev-main` and `cryodev-pi`), custom packages, modules, overlays, and templates. + +## Environment & Build Commands + +### Prerequisites +- **Nix** with Flakes enabled. +- **Git** + +### Core Commands +- **Build Host Configuration**: + ```bash + nix build .#nixosConfigurations..config.system.build.toplevel + # Examples: + nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel + nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel + ``` +- **Format Code**: + ```bash + nix fmt + ``` + This uses the formatter defined in `flake.nix` (typically `nixfmt` via `pre-commit-hooks`). +- **Run Checks (Lint/Test)**: + ```bash + nix flake check + ``` + This validates the flake outputs and runs configured checks (including formatting checks and deploy-rs checks). +- **Update Flake Inputs**: + ```bash + nix flake update + ``` + +### Development Shell +You can enter a development shell with necessary tools (if configured in `devShells`): +```bash +nix develop +``` + +## Code Style & Conventions + +### General Nix Style +- **Formatting**: Strictly adhere to `nixfmt` style. Run `nix fmt` before committing. +- **Indentation**: Use 2 spaces for indentation. +- **Lines**: Limit lines to 80-100 characters where possible, but follow the formatter's lead. +- **Comments**: Use `#` for single-line comments. Avoid block comments `/* ... */` unless necessary for large blocks of text. + +### Module Structure +- **Function Header**: Always use the standard module arguments pattern: + ```nix + { config, lib, pkgs, inputs, outputs, constants, ... }: + ``` + - Include `inputs`, `outputs`, and `constants` if you need access to flake inputs, the flake's own outputs, or the central constants. +- **Option Definitions**: + - Define options in `options = { ... };`. + - Implement configuration in `config = { ... };`. + - Use `mkEnableOption` for boolean flags. + - Use `mkOption` with types (e.g., `types.str`, `types.bool`) and descriptions. +- **Imports**: + - Use relative paths for local modules: `imports = [ ./module.nix ];`. + - Use `inputs` or `outputs` for external/shared modules: `imports = [ outputs.nixosModules.common ];`. + +### Naming Conventions +- **Files**: `kebab-case.nix` (e.g., `hardware-configuration.nix`). +- **Options**: `camelCase` (e.g., `services.myService.enable`). +- **Variables**: `camelCase` in `let ... in` blocks. + +### Flake Specifics +- **Inputs**: Defined in `flake.nix`. + - `nixpkgs`: Main package set (typically following a stable release). + - `nixpkgs-unstable`: Unstable channel for latest packages. +- **Outputs**: + - `nixosConfigurations`: Host definitions. + - `nixosModules`: Reusable modules exported by this flake. + - `packages`: Custom packages exported by this flake. + - `overlays`: Overlays to modify `nixpkgs`. + - `templates`: Project templates for creating new hosts. + +### Error Handling +- Use `assert` for critical configuration invariants. +- Use `warnings` or `trace` for deprecation or non-critical issues during evaluation. +- Example: + ```nix + config = lib.mkIf cfg.enable { + assertions = [ + { assertion = cfg.port > 1024; message = "Port must be non-privileged"; } + ]; + }; + ``` + +## Directory Structure +- `flake.nix`: Entry point, defines inputs and outputs. +- `hosts/`: Specific host configurations. + - `/default.nix`: Host entry point. +- `modules/`: Reusable NixOS modules. + - `nixos/`: Modules specific to NixOS (e.g. `nixvim`, `comin`, `forgejo`). +- `pkgs/`: Custom package definitions. +- `overlays/`: Nixpkgs overlays. +- `templates/`: Templates for bootstrapping new hosts (`raspberry-pi`, `generic-server`). +- `scripts/`: Helper scripts (e.g., `install.sh` for bootstrapping). +- `constants.nix`: Central configuration for domains, IPs, and ports. +- `INSTRUCTIONS.md`: Setup and deployment instructions (DNS, SOPS, bootstrap). +- `README.md`: General project documentation and architecture overview. + +## Deployment Workflows +- **cryodev-main**: Push-based deployment via Forgejo Actions using `deploy-rs`. +- **cryodev-pi**: Pull-based deployment using `comin` (polling the repository). + +## Working with Agents +- **Context**: When asking for changes, specify if it's for a specific host (`hosts/cryodev-main`) or a shared module (`modules/`). +- **Verification**: Always run `nix flake check` after changes to ensure validity. +- **Refactoring**: When moving code, update `imports` carefully. +- **Constants**: Use `constants.nix` for values like domains, IPs, and ports instead of hardcoding them in modules. diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md new file mode 100644 index 0000000..4bf38be --- /dev/null +++ b/INSTRUCTIONS.md @@ -0,0 +1,391 @@ +# Server Setup Instructions / Server-Einrichtungsanleitung + +--- + +# πŸ‡¬πŸ‡§ English Instructions + +## 1. Prerequisites + +Ensure you have the following tools installed on your local machine: +- `nix` (with flakes enabled) +- `sops` +- `age` +- `ssh` + +## 2. DNS Configuration + +Configure the following DNS records for your domain `cryodev.xyz`: + +| Hostname | Type | Value | Purpose | +|----------|------|-------|---------| +| `@` | A | `` | Main entry point | +| `@` | AAAA | `` | Main entry point (IPv6) | +| `git` | CNAME | `@` | Forgejo | +| `headscale` | CNAME | `@` | Headscale | +| `headplane` | CNAME | `@` | Headplane | +| `netdata` | CNAME | `@` | Netdata Monitoring | +| `mail` | A | `` | Mailserver | +| `mail` | AAAA | `` | Mailserver (IPv6) | +| `@` | MX | `10 mail.cryodev.xyz.` | Mail delivery | +| `@` | TXT | `"v=spf1 mx ~all"` | SPF Record | +| `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC Record | + +## 3. Secret Management (SOPS) + +This repository uses `sops-nix` to manage secrets encrypted with `age`, utilizing the SSH host keys of the servers. + +### 3.1 Get Server Public Keys +You need to convert the servers' SSH host public keys to age public keys. + +**For `cryodev-main`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +**For `cryodev-pi`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +### 3.2 Configure `.sops.yaml` +Edit the `.sops.yaml` file in the root of this repository. Add the age public keys to the `keys` section and ensure creation rules exist for both hosts. + +```yaml +keys: + - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # Admin Key (Steffen) + - &main_key age1... # cryodev-main Key + - &pi_key age1... # cryodev-pi Key +creation_rules: + - path_regex: hosts/cryodev-main/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *main_key + - path_regex: hosts/cryodev-pi/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *pi_key +``` + +### 3.3 Generating Secret Values + +**Mailserver Passwords (for `cryodev-main`):** +```bash +nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt' +``` + +**Headplane Secrets (for `cryodev-main`):** +```bash +nix-shell -p openssl --run "openssl rand -hex 16" +# Agent Pre-Authkey requires Headscale running: +sudo headscale users create headplane-agent +sudo headscale preauthkeys create --expiration 99y --reusable --user headplane-agent +``` + +**Tailscale Auth Keys (for both hosts):** +*Requires Headscale running on `cryodev-main`.* +```bash +# For cryodev-main: +sudo headscale preauthkeys create --expiration 99y --reusable --user default +# For cryodev-pi: +sudo headscale preauthkeys create --expiration 99y --reusable --user default +``` + +**Netdata Child UUID (for `cryodev-pi`):** +```bash +uuidgen +``` + +**Forgejo Runner Token:** +Get from Forgejo Admin Panel. + +### 3.4 Creating Secrets Files + +**`hosts/cryodev-main/secrets.yaml`:** +```bash +sops hosts/cryodev-main/secrets.yaml +``` +```yaml +mailserver: + accounts: + forgejo: "$2y$05$..." + admin: "$2y$05$..." + +forgejo-runner: + token: "..." + +headplane: + cookie_secret: "..." + agent_pre_authkey: "..." + +tailscale: + auth-key: "..." +``` + +**`hosts/cryodev-pi/secrets.yaml`:** +```bash +sops hosts/cryodev-pi/secrets.yaml +``` +```yaml +tailscale: + auth-key: "..." + +netdata: + stream: + child-uuid: "..." # Output from uuidgen +``` + +## 4. Initial Deployment (Bootstrap) + +Before the continuous deployment can take over, you must perform an initial deployment manually using the provided install script. + +### 4.1 Prepare Target Machine +1. Boot into the NixOS Installation ISO. +2. Set a root password (for SSH): `passwd`. +3. Ensure internet connectivity. + +### 4.2 Run Install Script +From your local machine (where this repo is), copy the script to the target or run it directly if you can fetch it. + +**Method A: Copy Script via SSH** +```bash +scp scripts/install.sh nixos@:install.sh +ssh nixos@ +sudo -i +chmod +x /home/nixos/install.sh +./home/nixos/install.sh -r -n +``` + +**Method B: Run on Target (if repo is public or reachable)** +```bash +# On the target machine (as root) +nix-shell -p git +git clone /tmp/nixos +cd /tmp/nixos +bash scripts/install.sh -n +``` + +*Note: The script handles disk partitioning (via disko/script), hardware config generation, and installation.* + +## 5. Continuous Deployment (CD) + +### 5.1 cryodev-pi (Pull-based via Comin) +The `cryodev-pi` host is configured to pull updates automatically via `comin`. + +1. **Create Repository:** Create a new repository named `cryodev-server` on your Forgejo instance (`https://git.cryodev.xyz`). +2. **Push Configuration:** Push this entire NixOS configuration to the `main` branch of that repository. +3. **Comin URL:** The configuration expects the repository at: `https://git.cryodev.xyz/steffen/cryodev-server.git`. + +### 5.2 cryodev-main (Push-based via Forgejo Actions) +The main server is deployed via a Forgejo Action. + +1. **Generate SSH Key:** + ```bash + ssh-keygen -t ed25519 -f deploy_key -C "forgejo-actions" + ``` +2. **Add Public Key:** Add the content of `deploy_key.pub` to `/root/.ssh/authorized_keys` on `cryodev-main`. +3. **Add Secret:** Add the content of `deploy_key` (private key) as a secret named `DEPLOY_SSH_KEY` in your Forgejo repository settings. + +## 6. Creating New Hosts (Templates) + +To quickly bootstrap a new host configuration, you can use the provided templates. + +1. **Copy Template:** + ```bash + # For a Raspberry Pi: + cp -r templates/raspberry-pi hosts/new-pi-name + + # For a generic x86 server: + cp -r templates/generic-server hosts/new-server-name + ``` +2. **Adjust Configuration:** + * **Hostname:** Edit `hosts/new-name/networking.nix`. + * **Flake:** Register the new host in `flake.nix` under `nixosConfigurations`. + * **Constants:** Add IP and ports to `constants.nix`. + * **Secrets:** Add keys to `.sops.yaml` and create `hosts/new-name/secrets.yaml`. + +--- + +# πŸ‡©πŸ‡ͺ Deutsche Anleitung + +## 1. Voraussetzungen + +Stellen Sie sicher, dass folgende Tools lokal installiert sind: +- `nix` (mit Flakes) +- `sops` +- `age` +- `ssh` +- `ssh-to-age` +- `uuidgen` + +## 2. DNS-Konfiguration + +Richten Sie folgende DNS-EintrΓ€ge fΓΌr `cryodev.xyz` ein: + +| Hostname | Typ | Wert | Zweck | +|----------|-----|------|-------| +| `@` | A | `` | Hauptserver | +| `@` | AAAA | `` | Hauptserver (IPv6) | +| `git` | CNAME | `@` | Forgejo | +| `headscale` | CNAME | `@` | Headscale | +| `headplane` | CNAME | `@` | Headplane | +| `netdata` | CNAME | `@` | Netdata Monitoring | +| `mail` | A | `` | Mailserver | +| `mail` | AAAA | `` | Mailserver (IPv6) | +| `@` | MX | `10 mail.cryodev.xyz.` | Mail-Empfang | +| `@` | TXT | `"v=spf1 mx ~all"` | SPF-Record | +| `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC-Record | + +## 3. Verwaltung von Geheimnissen (SOPS) + +Dieses Repository nutzt `sops-nix` mit den SSH-Host-Keys der Server. + +### 3.1 Public Keys abrufen + +**FΓΌr `cryodev-main`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +**FΓΌr `cryodev-pi`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +### 3.2 `.sops.yaml` konfigurieren +Bearbeiten Sie `.sops.yaml` und fΓΌgen Sie die Keys sowie Regeln fΓΌr beide Hosts hinzu: + +```yaml +keys: + - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # Admin Key (Steffen) + - &main_key age1... # cryodev-main Key + - &pi_key age1... # cryodev-pi Key +creation_rules: + - path_regex: hosts/cryodev-main/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *main_key + - path_regex: hosts/cryodev-pi/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *pi_key +``` + +### 3.3 Werte generieren + +**Mailserver:** `mkpasswd -sm bcrypt` +**Headplane:** `openssl rand -hex 16` +**Netdata UUID:** `uuidgen` + +**Tailscale Auth Keys (auf `cryodev-main`):** +```bash +sudo headscale preauthkeys create --expiration 99y --reusable --user default +``` + +### 3.4 Secrets-Dateien erstellen + +**`hosts/cryodev-main/secrets.yaml`:** +```bash +sops hosts/cryodev-main/secrets.yaml +``` +```yaml +mailserver: + accounts: + forgejo: "$2y$05$..." + admin: "$2y$05$..." + +forgejo-runner: + token: "..." + +headplane: + cookie_secret: "..." + agent_pre_authkey: "..." + +tailscale: + auth-key: "..." +``` + +**`hosts/cryodev-pi/secrets.yaml`:** +```bash +sops hosts/cryodev-pi/secrets.yaml +``` +```yaml +tailscale: + auth-key: "..." + +netdata: + stream: + child-uuid: "..." # Output von uuidgen +``` + +## 4. Erstes Deployment (Bootstrap) + +Bevor das automatische Deployment funktionieren kann, mΓΌssen Sie das System einmal manuell mit dem Installationsskript installieren. + +### 4.1 Zielmaschine vorbereiten +1. Booten Sie das NixOS Installations-ISO. +2. Setzen Sie ein Root-Passwort: `passwd`. +3. Stellen Sie eine Internetverbindung her. + +### 4.2 Install-Script ausfΓΌhren +Kopieren Sie das Skript von Ihrem lokalen Rechner auf das Zielsystem. + +**Methode A: Per SCP** +```bash +scp scripts/install.sh nixos@:install.sh +ssh nixos@ +sudo -i +chmod +x /home/nixos/install.sh +./home/nixos/install.sh -r -n +``` + +**Methode B: Direkt auf dem Ziel (bei ΓΆffentlichem/erreichbarem Repo)** +```bash +# Auf der Zielmaschine (als root) +nix-shell -p git +git clone /tmp/nixos +cd /tmp/nixos +bash scripts/install.sh -n +``` + +*Hinweis: Das Skript kΓΌmmert sich um Partitionierung, Hardware-Config und Installation.* + +## 5. Continuous Deployment (CD) + +### 5.1 cryodev-pi (Pull-basiert via Comin) +Der Host `cryodev-pi` zieht Updates automatisch via `comin`. + +1. **Repository erstellen:** Erstellen Sie ein Repository namens `cryodev-server` auf `https://git.cryodev.xyz`. +2. **Konfiguration pushen:** Pushen Sie diese Konfiguration in den `main`-Branch. +3. **Comin URL:** `https://git.cryodev.xyz/steffen/cryodev-server.git`. + +### 5.2 cryodev-main (Push-basiert via Forgejo Actions) +Der Hauptserver wird ΓΌber eine Forgejo Action deployt. + +1. **SSH Key generieren:** + ```bash + ssh-keygen -t ed25519 -f deploy_key -C "forgejo-actions" + ``` +2. **Public Key hinzufΓΌgen:** Inhalt von `deploy_key.pub` in `/root/.ssh/authorized_keys` auf `cryodev-main` eintragen. +3. **Secret hinzufΓΌgen:** Inhalt von `deploy_key` (Private Key) als Secret `DEPLOY_SSH_KEY` im Forgejo-Repository hinterlegen. + +## 6. Neue Hosts erstellen (Templates) + +Um schnell eine neue Host-Konfiguration zu erstellen, kΓΆnnen Sie die bereitgestellten Templates nutzen. + +1. **Template kopieren:** + ```bash + # FΓΌr einen Raspberry Pi: + cp -r templates/raspberry-pi hosts/neuer-pi-name + + # FΓΌr einen generischen x86 Server: + cp -r templates/generic-server hosts/neuer-server-name + ``` +2. **Konfiguration anpassen:** + * **Hostname:** Bearbeiten Sie `hosts/neuer-name/networking.nix`. + * **Flake:** Registrieren Sie den neuen Host in `flake.nix` unter `nixosConfigurations`. + * **Constants:** FΓΌgen Sie IP und Ports in `constants.nix` hinzu. + * **Secrets:** FΓΌgen Sie Keys zu `.sops.yaml` hinzu und erstellen Sie `hosts/neuer-name/secrets.yaml`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..51b9eab --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# cryodev-server NixOS Configuration + +This repository contains the declarative NixOS configuration for the **cryodev** infrastructure, managed using **Nix Flakes**. It defines a robust, secure, and self-hosted environment spanning a main server and client devices. + +--- + +# πŸ‡¬πŸ‡§ English Description + +## Overview + +The infrastructure is designed around a central server (`cryodev-main`) and satellite clients (e.g., `cryodev-pi`). It leverages modern DevOps practices including Infrastructure as Code (IaC), GitOps, and Mesh VPNs. + +## Key Features & Architecture + +### πŸ–₯️ Hosts +* **`cryodev-main` (x86_64 Server)**: The core infrastructure hub. +* **`cryodev-pi` (Raspberry Pi 4)**: A remote client/worker node. + +### πŸš€ Continuous Deployment (CD) +We utilize different deployment strategies optimized for each host type: +* **Push-based (Server):** The main server is deployed via **Forgejo Actions** using **[deploy-rs](https://github.com/serokell/deploy-rs)**. This ensures immediate updates and robust rollbacks in case of failure. +* **Pull-based (Client):** The Raspberry Pi uses **[Comin](https://github.com/nlewo/comin)** to periodically poll the Git repository and apply updates automatically. This is ideal for devices behind NAT or with unstable connections. + +### 🌐 Networking (Tailscale & Headscale) +* **Self-hosted VPN:** We run **Headscale**, an open-source implementation of the Tailscale control server. +* **Headplane:** A web frontend for managing Headscale users and routes. +* **Mesh Network:** All hosts are connected via a secure, private WireGuard mesh network. +* **MagicDNS:** Automatic DNS resolution for devices within the tailnet. + +### πŸ“Š Monitoring (Netdata) +* **Parent/Child Streaming:** The main server acts as a centralized Netdata parent node. +* **Distributed Monitoring:** `cryodev-pi` (and other clients) stream their metrics securely over the Tailscale VPN to the parent node. +* **Alerting:** Integrated with the mailserver to send health alerts. + +### πŸ“§ Mail Services +* **NixOS Mailserver:** A fully functional mail stack (Postfix/Dovecot). +* **Integration:** Used by internal services (Forgejo, Netdata) to send notifications. +* **Security:** SPF, DKIM, and DMARC configured for `cryodev.xyz`. + +### πŸ› οΈ Development & Productivity +* **Forgejo:** Self-hosted Git service (fork of Gitea) with built-in CI/CD Actions. +* **Forgejo Runner:** Self-hosted runners executing the CI/CD pipelines. +* **Neovim:** A fully pre-configured Neovim environment (aliased as `v`) available system-wide via a custom NixOS module. +* **Secret Management:** **[sops-nix](https://github.com/Mic92/sops-nix)** encrypts secrets using Age and SSH host keys, ensuring no sensitive data is committed in plain text. +* **Templates:** Ready-to-use NixOS configurations for quickly bootstrapping new clients. + * `#raspberry-pi`: Template for Raspberry Pi 4 clients. + * `#generic-server`: Template for generic x86_64 servers. +* **Bootstrap Script:** An `install.sh` script automates disk partitioning (via disko) and system installation for new hosts. + +## 🚧 Roadmap & Missing Features + +### BioSafe Gateway (Dual Ethernet) +The Raspberry Pi hosts utilize a custom board with two Ethernet ports: +* **WAN:** Standard Internet connection. +* **LAN (`eth1`):** A dedicated local connection managed specifically by the **BioSafe Gateway App**. +* *Status:* The network configuration logic and the integration of the controlling app are currently missing. + +### Closed Source Integration +The **BioSafe Gateway App** is closed source. +* It needs to be added as a **private Flake input**. +* Authentication mechanism (e.g., access tokens via Secrets) to fetch this private input during the build process is not yet implemented. + +### SD Card Image Pipeline +Currently, the Pi requires manual setup. +* *Goal:* A CI/CD pipeline that builds a fully configured, flashable SD card image. +* *Usage:* Download image -> Flash to SD -> Insert in Pi -> Boot & Auto-connect. + +## Directory Structure +* `flake.nix`: The entry point defining inputs (dependencies) and outputs (system configs). +* `hosts/`: Configuration specific to each machine. +* `modules/`: Reusable NixOS modules (custom or imported). +* `pkgs/`: Custom packages. +* `constants.nix`: Central source of truth for IPs, ports, and domains. +* `INSTRUCTIONS.md`: Detailed setup guide (DNS, SOPS, Initial Deployment). +* `AGENTS.md`: Guidelines for AI agents working on this repository. + +--- + +# πŸ‡©πŸ‡ͺ Deutsche Beschreibung + +## Überblick + +Die Infrastruktur ist um einen zentralen Server (`cryodev-main`) und Satelliten-Clients (z.B. `cryodev-pi`) herum aufgebaut. Sie nutzt moderne DevOps-Praktiken wie Infrastructure as Code (IaC), GitOps und Mesh-VPNs. + +## Hauptfunktionen & Architektur + +### πŸ–₯️ Hosts +* **`cryodev-main` (x86_64 Server)**: Der zentrale Infrastruktur-Knoten. +* **`cryodev-pi` (Raspberry Pi 4)**: Ein entfernter Client/Worker-Node. + +### πŸš€ Continuous Deployment (CD) +Wir verwenden unterschiedliche Deployment-Strategien, optimiert fΓΌr den jeweiligen Host-Typ: +* **Push-basiert (Server):** Der Hauptserver wird ΓΌber **Forgejo Actions** mittels **[deploy-rs](https://github.com/serokell/deploy-rs)** aktualisiert. Dies garantiert sofortige Updates und robuste Rollbacks bei Fehlern. +* **Pull-basiert (Client):** Der Raspberry Pi nutzt **[Comin](https://github.com/nlewo/comin)**, um das Git-Repository periodisch abzufragen und Updates automatisch anzuwenden. Ideal fΓΌr GerΓ€te hinter NAT oder mit instabilen Verbindungen. + +### 🌐 Netzwerk (Tailscale & Headscale) +* **Self-hosted VPN:** Wir betreiben **Headscale**, eine Open-Source-Implementierung des Tailscale-Kontrollservers. +* **Headplane:** Ein Web-Frontend zur Verwaltung von Headscale-Benutzern und Routen. +* **Mesh-Netzwerk:** Alle Hosts sind ΓΌber ein sicheres, privates WireGuard-Mesh-Netzwerk verbunden. +* **MagicDNS:** Automatische NamensauflΓΆsung fΓΌr GerΓ€te innerhalb des Tailnets. + +### πŸ“Š Monitoring (Netdata) +* **Parent/Child Streaming:** Der Hauptserver agiert als zentraler Netdata Parent-Node. +* **Verteiltes Monitoring:** `cryodev-pi` (und andere Clients) streamen ihre Metriken sicher ΓΌber das Tailscale-VPN an den Parent-Node. +* **Alarmierung:** Integriert mit dem Mailserver zum Versenden von Gesundheitswarnungen. + +### πŸ“§ Mail-Dienste +* **NixOS Mailserver:** Ein voll funktionsfΓ€higer Mail-Stack (Postfix/Dovecot). +* **Integration:** Wird von internen Diensten (Forgejo, Netdata) fΓΌr Benachrichtigungen genutzt. +* **Sicherheit:** SPF, DKIM und DMARC sind fΓΌr `cryodev.xyz` konfiguriert. + +### πŸ› οΈ Entwicklung & ProduktivitΓ€t +* **Forgejo:** Self-hosted Git-Dienst (Gitea-Fork) mit integrierten CI/CD Actions. +* **Forgejo Runner:** Eigene Runner, die die CI/CD-Pipelines ausfΓΌhren. +* **Neovim:** Eine vollstΓ€ndig vorkonfigurierte Neovim-Umgebung (Alias `v`), die systemweit ΓΌber ein eigenes NixOS-Modul bereitgestellt wird. +* **Secret Management:** **[sops-nix](https://github.com/Mic92/sops-nix)** verschlΓΌsselt Geheimnisse mittels Age und SSH-Host-Keys, sodass keine sensiblen Daten im Klartext gespeichert werden. +* **Templates:** Vorgefertigte NixOS-Konfigurationen zum schnellen Aufsetzen neuer Clients. + * `#raspberry-pi`: Template fΓΌr Raspberry Pi 4 Clients. + * `#generic-server`: Template fΓΌr generische x86_64 Server. +* **Bootstrap Skript:** Ein `install.sh` Skript automatisiert die Disk-Partitionierung (via disko) und Systeminstallation fΓΌr neue Hosts. + +## 🚧 Roadmap & Fehlende Funktionen + +### BioSafe Gateway (Dual-Ethernet) +Die Raspberry Pi Hosts nutzen ein spezielles Board mit zwei Ethernet-AnschlΓΌssen: +* **WAN:** Normale Internetverbindung. +* **LAN (`eth1`):** Eine dedizierte lokale Verbindung, die spezifisch durch die **BioSafe Gateway App** verwaltet wird. +* *Status:* Die Logik fΓΌr die Netzwerkkonfiguration und die Integration der steuernden App fehlen aktuell. + +### Integration von Closed Source +Die **BioSafe Gateway App** ist Closed Source. +* Sie muss als **privater Flake Input** hinzugefΓΌgt werden. +* Mechanismen zur Authentifizierung (z.B. Access Tokens via Secrets) fΓΌr das Abrufen dieses privaten Inputs wΓ€hrend des Build-Prozesses sind noch nicht implementiert. + +### Pipeline fΓΌr SD-Karten-Images +Momentan erfordert der Pi eine manuelle Einrichtung. +* *Ziel:* Eine CI/CD-Pipeline, die ein fertig konfiguriertes, flashbares SD-Karten-Image baut. +* *Nutzung:* Image herunterladen -> auf SD flashen -> in Pi stecken -> booten & automatisch verbinden. + +## Verzeichnisstruktur +* `flake.nix`: Der Einstiegspunkt, der Inputs (AbhΓ€ngigkeiten) und Outputs (Systemkonfigurationen) definiert. +* `hosts/`: Maschinenspezifische Konfigurationen. +* `modules/`: Wiederverwendbare NixOS-Module (eigene oder importierte). +* `pkgs/`: Benutzerdefinierte Pakete. +* `constants.nix`: Zentrale Quelle fΓΌr IPs, Ports und Domains. +* `INSTRUCTIONS.md`: Detaillierte Einrichtungsanleitung (DNS, SOPS, Initial Deployment). +* `AGENTS.md`: Richtlinien fΓΌr KI-Agenten, die an diesem Repository arbeiten. diff --git a/constants.nix b/constants.nix new file mode 100644 index 0000000..d5a1f9d --- /dev/null +++ b/constants.nix @@ -0,0 +1,38 @@ +{ + # Domain + domain = "cryodev.xyz"; + + # Hosts + hosts = { + cryodev-main = { + ip = "100.64.0.1"; # Tailscale IP example + }; + cryodev-pi = { + ip = "100.64.0.2"; # Tailscale IP example + }; + }; + + # Services + services = { + forgejo = { + fqdn = "git.cryodev.xyz"; + port = 3000; + }; + headscale = { + fqdn = "headscale.cryodev.xyz"; + port = 8080; + }; + headplane = { + fqdn = "headplane.cryodev.xyz"; + port = 3001; + }; + netdata = { + fqdn = "netdata.cryodev.xyz"; + port = 19999; + }; + mail = { + fqdn = "mail.cryodev.xyz"; + port = 587; + }; + }; +} diff --git a/digest.txt b/digest.txt new file mode 100644 index 0000000..a52f6e5 --- /dev/null +++ b/digest.txt @@ -0,0 +1,4930 @@ +Directory structure: +└── cryodev-server/ + β”œβ”€β”€ README.md + β”œβ”€β”€ AGENTS.md + β”œβ”€β”€ constants.nix + β”œβ”€β”€ flake.nix + β”œβ”€β”€ INSTRUCTIONS.md + β”œβ”€β”€ .sops.yaml + β”œβ”€β”€ hosts/ + β”‚ β”œβ”€β”€ cryodev-main/ + β”‚ β”‚ β”œβ”€β”€ boot.nix + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ disks.sh + β”‚ β”‚ β”œβ”€β”€ hardware.nix + β”‚ β”‚ β”œβ”€β”€ networking.nix + β”‚ β”‚ β”œβ”€β”€ packages.nix + β”‚ β”‚ β”œβ”€β”€ users.nix + β”‚ β”‚ └── services/ + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ forgejo.nix + β”‚ β”‚ β”œβ”€β”€ headplane.nix + β”‚ β”‚ β”œβ”€β”€ headscale.nix + β”‚ β”‚ β”œβ”€β”€ mailserver.nix + β”‚ β”‚ β”œβ”€β”€ netdata.nix + β”‚ β”‚ β”œβ”€β”€ nginx.nix + β”‚ β”‚ β”œβ”€β”€ openssh.nix + β”‚ β”‚ β”œβ”€β”€ sops.nix + β”‚ β”‚ └── tailscale.nix + β”‚ └── cryodev-pi/ + β”‚ β”œβ”€β”€ boot.nix + β”‚ β”œβ”€β”€ default.nix + β”‚ β”œβ”€β”€ disks.sh + β”‚ β”œβ”€β”€ hardware.nix + β”‚ β”œβ”€β”€ networking.nix + β”‚ β”œβ”€β”€ packages.nix + β”‚ β”œβ”€β”€ users.nix + β”‚ └── services/ + β”‚ β”œβ”€β”€ comin.nix + β”‚ β”œβ”€β”€ default.nix + β”‚ β”œβ”€β”€ netdata.nix + β”‚ β”œβ”€β”€ nginx.nix + β”‚ β”œβ”€β”€ openssh.nix + β”‚ └── tailscale.nix + β”œβ”€β”€ modules/ + β”‚ └── nixos/ + β”‚ β”œβ”€β”€ default.nix + β”‚ β”œβ”€β”€ comin/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ common/ + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ environment.nix + β”‚ β”‚ β”œβ”€β”€ htop.nix + β”‚ β”‚ β”œβ”€β”€ nationalization.nix + β”‚ β”‚ β”œβ”€β”€ networking.nix + β”‚ β”‚ β”œβ”€β”€ nix.nix + β”‚ β”‚ β”œβ”€β”€ overlays.nix + β”‚ β”‚ β”œβ”€β”€ sudo.nix + β”‚ β”‚ β”œβ”€β”€ well-known.nix + β”‚ β”‚ β”œβ”€β”€ zsh.nix + β”‚ β”‚ └── shared/ + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ └── nix.nix + β”‚ β”œβ”€β”€ forgejo/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ forgejo-runner/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ headplane/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ headscale/ + β”‚ β”‚ β”œβ”€β”€ acl.hujson + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ mailserver/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ nginx/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ nixvim/ + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ keymaps.nix + β”‚ β”‚ β”œβ”€β”€ spellfiles.nix + β”‚ β”‚ └── plugins/ + β”‚ β”‚ β”œβ”€β”€ cmp.nix + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ lsp.nix + β”‚ β”‚ β”œβ”€β”€ lualine.nix + β”‚ β”‚ β”œβ”€β”€ telescope.nix + β”‚ β”‚ β”œβ”€β”€ treesitter.nix + β”‚ β”‚ └── trouble.nix + β”‚ β”œβ”€β”€ normalUsers/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ openssh/ + β”‚ β”‚ └── default.nix + β”‚ β”œβ”€β”€ sops/ + β”‚ β”‚ └── default.nix + β”‚ └── tailscale/ + β”‚ └── default.nix + β”œβ”€β”€ overlays/ + β”‚ └── default.nix + β”œβ”€β”€ pkgs/ + β”‚ └── default.nix + β”œβ”€β”€ scripts/ + β”‚ └── install.sh + β”œβ”€β”€ templates/ + β”‚ β”œβ”€β”€ generic-server/ + β”‚ β”‚ β”œβ”€β”€ boot.nix + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ disks.sh + β”‚ β”‚ β”œβ”€β”€ flake.nix + β”‚ β”‚ β”œβ”€β”€ hardware.nix + β”‚ β”‚ β”œβ”€β”€ networking.nix + β”‚ β”‚ β”œβ”€β”€ packages.nix + β”‚ β”‚ β”œβ”€β”€ users.nix + β”‚ β”‚ └── services/ + β”‚ β”‚ β”œβ”€β”€ comin.nix + β”‚ β”‚ β”œβ”€β”€ default.nix + β”‚ β”‚ β”œβ”€β”€ netdata.nix + β”‚ β”‚ β”œβ”€β”€ nginx.nix + β”‚ β”‚ β”œβ”€β”€ openssh.nix + β”‚ β”‚ └── tailscale.nix + β”‚ └── raspberry-pi/ + β”‚ β”œβ”€β”€ boot.nix + β”‚ β”œβ”€β”€ default.nix + β”‚ β”œβ”€β”€ disks.sh + β”‚ β”œβ”€β”€ flake.nix + β”‚ β”œβ”€β”€ hardware.nix + β”‚ β”œβ”€β”€ networking.nix + β”‚ β”œβ”€β”€ packages.nix + β”‚ β”œβ”€β”€ users.nix + β”‚ └── services/ + β”‚ β”œβ”€β”€ comin.nix + β”‚ β”œβ”€β”€ default.nix + β”‚ β”œβ”€β”€ netdata.nix + β”‚ β”œβ”€β”€ nginx.nix + β”‚ β”œβ”€β”€ openssh.nix + β”‚ └── tailscale.nix + β”œβ”€β”€ users/ + β”‚ β”œβ”€β”€ cryotherm/ + β”‚ β”‚ └── default.nix + β”‚ └── steffen/ + β”‚ β”œβ”€β”€ default.nix + β”‚ └── pubkeys/ + β”‚ └── X670E.pub + └── .forgejo/ + └── workflows/ + β”œβ”€β”€ build-hosts.yml + β”œβ”€β”€ deploy-main.yml + └── flake-check.yml + +================================================ +FILE: README.md +================================================ +# cryodev-server NixOS Configuration + +This repository contains the declarative NixOS configuration for the **cryodev** infrastructure, managed using **Nix Flakes**. It defines a robust, secure, and self-hosted environment spanning a main server and client devices. + +--- + +# πŸ‡¬πŸ‡§ English Description + +## Overview + +The infrastructure is designed around a central server (`cryodev-main`) and satellite clients (e.g., `cryodev-pi`). It leverages modern DevOps practices including Infrastructure as Code (IaC), GitOps, and Mesh VPNs. + +## Key Features & Architecture + +### πŸ–₯️ Hosts +* **`cryodev-main` (x86_64 Server)**: The core infrastructure hub. +* **`cryodev-pi` (Raspberry Pi 4)**: A remote client/worker node. + +### πŸš€ Continuous Deployment (CD) +We utilize different deployment strategies optimized for each host type: +* **Push-based (Server):** The main server is deployed via **Forgejo Actions** using **[deploy-rs](https://github.com/serokell/deploy-rs)**. This ensures immediate updates and robust rollbacks in case of failure. +* **Pull-based (Client):** The Raspberry Pi uses **[Comin](https://github.com/nlewo/comin)** to periodically poll the Git repository and apply updates automatically. This is ideal for devices behind NAT or with unstable connections. + +### 🌐 Networking (Tailscale & Headscale) +* **Self-hosted VPN:** We run **Headscale**, an open-source implementation of the Tailscale control server. +* **Headplane:** A web frontend for managing Headscale users and routes. +* **Mesh Network:** All hosts are connected via a secure, private WireGuard mesh network. +* **MagicDNS:** Automatic DNS resolution for devices within the tailnet. + +### πŸ“Š Monitoring (Netdata) +* **Parent/Child Streaming:** The main server acts as a centralized Netdata parent node. +* **Distributed Monitoring:** `cryodev-pi` (and other clients) stream their metrics securely over the Tailscale VPN to the parent node. +* **Alerting:** Integrated with the mailserver to send health alerts. + +### πŸ“§ Mail Services +* **NixOS Mailserver:** A fully functional mail stack (Postfix/Dovecot). +* **Integration:** Used by internal services (Forgejo, Netdata) to send notifications. +* **Security:** SPF, DKIM, and DMARC configured for `cryodev.xyz`. + +### πŸ› οΈ Development & Productivity +* **Forgejo:** Self-hosted Git service (fork of Gitea) with built-in CI/CD Actions. +* **Forgejo Runner:** Self-hosted runners executing the CI/CD pipelines. +* **Neovim:** A fully pre-configured Neovim environment (aliased as `v`) available system-wide via a custom NixOS module. +* **Secret Management:** **[sops-nix](https://github.com/Mic92/sops-nix)** encrypts secrets using Age and SSH host keys, ensuring no sensitive data is committed in plain text. +* **Templates:** Ready-to-use NixOS configurations for quickly bootstrapping new clients. + * `#raspberry-pi`: Template for Raspberry Pi 4 clients. + * `#generic-server`: Template for generic x86_64 servers. +* **Bootstrap Script:** An `install.sh` script automates disk partitioning (via disko) and system installation for new hosts. + +## 🚧 Roadmap & Missing Features + +### BioSafe Gateway (Dual Ethernet) +The Raspberry Pi hosts utilize a custom board with two Ethernet ports: +* **WAN:** Standard Internet connection. +* **LAN (`eth1`):** A dedicated local connection managed specifically by the **BioSafe Gateway App**. +* *Status:* The network configuration logic and the integration of the controlling app are currently missing. + +### Closed Source Integration +The **BioSafe Gateway App** is closed source. +* It needs to be added as a **private Flake input**. +* Authentication mechanism (e.g., access tokens via Secrets) to fetch this private input during the build process is not yet implemented. + +### SD Card Image Pipeline +Currently, the Pi requires manual setup. +* *Goal:* A CI/CD pipeline that builds a fully configured, flashable SD card image. +* *Usage:* Download image -> Flash to SD -> Insert in Pi -> Boot & Auto-connect. + +## Directory Structure +* `flake.nix`: The entry point defining inputs (dependencies) and outputs (system configs). +* `hosts/`: Configuration specific to each machine. +* `modules/`: Reusable NixOS modules (custom or imported). +* `pkgs/`: Custom packages. +* `constants.nix`: Central source of truth for IPs, ports, and domains. +* `INSTRUCTIONS.md`: Detailed setup guide (DNS, SOPS, Initial Deployment). +* `AGENTS.md`: Guidelines for AI agents working on this repository. + +--- + +# πŸ‡©πŸ‡ͺ Deutsche Beschreibung + +## Überblick + +Die Infrastruktur ist um einen zentralen Server (`cryodev-main`) und Satelliten-Clients (z.B. `cryodev-pi`) herum aufgebaut. Sie nutzt moderne DevOps-Praktiken wie Infrastructure as Code (IaC), GitOps und Mesh-VPNs. + +## Hauptfunktionen & Architektur + +### πŸ–₯️ Hosts +* **`cryodev-main` (x86_64 Server)**: Der zentrale Infrastruktur-Knoten. +* **`cryodev-pi` (Raspberry Pi 4)**: Ein entfernter Client/Worker-Node. + +### πŸš€ Continuous Deployment (CD) +Wir verwenden unterschiedliche Deployment-Strategien, optimiert fΓΌr den jeweiligen Host-Typ: +* **Push-basiert (Server):** Der Hauptserver wird ΓΌber **Forgejo Actions** mittels **[deploy-rs](https://github.com/serokell/deploy-rs)** aktualisiert. Dies garantiert sofortige Updates und robuste Rollbacks bei Fehlern. +* **Pull-basiert (Client):** Der Raspberry Pi nutzt **[Comin](https://github.com/nlewo/comin)**, um das Git-Repository periodisch abzufragen und Updates automatisch anzuwenden. Ideal fΓΌr GerΓ€te hinter NAT oder mit instabilen Verbindungen. + +### 🌐 Netzwerk (Tailscale & Headscale) +* **Self-hosted VPN:** Wir betreiben **Headscale**, eine Open-Source-Implementierung des Tailscale-Kontrollservers. +* **Headplane:** Ein Web-Frontend zur Verwaltung von Headscale-Benutzern und Routen. +* **Mesh-Netzwerk:** Alle Hosts sind ΓΌber ein sicheres, privates WireGuard-Mesh-Netzwerk verbunden. +* **MagicDNS:** Automatische NamensauflΓΆsung fΓΌr GerΓ€te innerhalb des Tailnets. + +### πŸ“Š Monitoring (Netdata) +* **Parent/Child Streaming:** Der Hauptserver agiert als zentraler Netdata Parent-Node. +* **Verteiltes Monitoring:** `cryodev-pi` (und andere Clients) streamen ihre Metriken sicher ΓΌber das Tailscale-VPN an den Parent-Node. +* **Alarmierung:** Integriert mit dem Mailserver zum Versenden von Gesundheitswarnungen. + +### πŸ“§ Mail-Dienste +* **NixOS Mailserver:** Ein voll funktionsfΓ€higer Mail-Stack (Postfix/Dovecot). +* **Integration:** Wird von internen Diensten (Forgejo, Netdata) fΓΌr Benachrichtigungen genutzt. +* **Sicherheit:** SPF, DKIM und DMARC sind fΓΌr `cryodev.xyz` konfiguriert. + +### πŸ› οΈ Entwicklung & ProduktivitΓ€t +* **Forgejo:** Self-hosted Git-Dienst (Gitea-Fork) mit integrierten CI/CD Actions. +* **Forgejo Runner:** Eigene Runner, die die CI/CD-Pipelines ausfΓΌhren. +* **Neovim:** Eine vollstΓ€ndig vorkonfigurierte Neovim-Umgebung (Alias `v`), die systemweit ΓΌber ein eigenes NixOS-Modul bereitgestellt wird. +* **Secret Management:** **[sops-nix](https://github.com/Mic92/sops-nix)** verschlΓΌsselt Geheimnisse mittels Age und SSH-Host-Keys, sodass keine sensiblen Daten im Klartext gespeichert werden. +* **Templates:** Vorgefertigte NixOS-Konfigurationen zum schnellen Aufsetzen neuer Clients. + * `#raspberry-pi`: Template fΓΌr Raspberry Pi 4 Clients. + * `#generic-server`: Template fΓΌr generische x86_64 Server. +* **Bootstrap Skript:** Ein `install.sh` Skript automatisiert die Disk-Partitionierung (via disko) und Systeminstallation fΓΌr neue Hosts. + +## 🚧 Roadmap & Fehlende Funktionen + +### BioSafe Gateway (Dual-Ethernet) +Die Raspberry Pi Hosts nutzen ein spezielles Board mit zwei Ethernet-AnschlΓΌssen: +* **WAN:** Normale Internetverbindung. +* **LAN (`eth1`):** Eine dedizierte lokale Verbindung, die spezifisch durch die **BioSafe Gateway App** verwaltet wird. +* *Status:* Die Logik fΓΌr die Netzwerkkonfiguration und die Integration der steuernden App fehlen aktuell. + +### Integration von Closed Source +Die **BioSafe Gateway App** ist Closed Source. +* Sie muss als **privater Flake Input** hinzugefΓΌgt werden. +* Mechanismen zur Authentifizierung (z.B. Access Tokens via Secrets) fΓΌr das Abrufen dieses privaten Inputs wΓ€hrend des Build-Prozesses sind noch nicht implementiert. + +### Pipeline fΓΌr SD-Karten-Images +Momentan erfordert der Pi eine manuelle Einrichtung. +* *Ziel:* Eine CI/CD-Pipeline, die ein fertig konfiguriertes, flashbares SD-Karten-Image baut. +* *Nutzung:* Image herunterladen -> auf SD flashen -> in Pi stecken -> booten & automatisch verbinden. + +## Verzeichnisstruktur +* `flake.nix`: Der Einstiegspunkt, der Inputs (AbhΓ€ngigkeiten) und Outputs (Systemkonfigurationen) definiert. +* `hosts/`: Maschinenspezifische Konfigurationen. +* `modules/`: Wiederverwendbare NixOS-Module (eigene oder importierte). +* `pkgs/`: Benutzerdefinierte Pakete. +* `constants.nix`: Zentrale Quelle fΓΌr IPs, Ports und Domains. +* `INSTRUCTIONS.md`: Detaillierte Einrichtungsanleitung (DNS, SOPS, Initial Deployment). +* `AGENTS.md`: Richtlinien fΓΌr KI-Agenten, die an diesem Repository arbeiten. + + + +================================================ +FILE: AGENTS.md +================================================ +# Agent Guidelines for NixOS Configuration + +## Project Overview +This repository contains a NixOS configuration managed with Nix Flakes. It defines system configurations for one or more hosts (currently `cryodev-main` and `cryodev-pi`), custom packages, modules, overlays, and templates. + +## Environment & Build Commands + +### Prerequisites +- **Nix** with Flakes enabled. +- **Git** + +### Core Commands +- **Build Host Configuration**: + ```bash + nix build .#nixosConfigurations..config.system.build.toplevel + # Examples: + nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel + nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel + ``` +- **Format Code**: + ```bash + nix fmt + ``` + This uses the formatter defined in `flake.nix` (typically `nixfmt` via `pre-commit-hooks`). +- **Run Checks (Lint/Test)**: + ```bash + nix flake check + ``` + This validates the flake outputs and runs configured checks (including formatting checks and deploy-rs checks). +- **Update Flake Inputs**: + ```bash + nix flake update + ``` + +### Development Shell +You can enter a development shell with necessary tools (if configured in `devShells`): +```bash +nix develop +``` + +## Code Style & Conventions + +### General Nix Style +- **Formatting**: Strictly adhere to `nixfmt` style. Run `nix fmt` before committing. +- **Indentation**: Use 2 spaces for indentation. +- **Lines**: Limit lines to 80-100 characters where possible, but follow the formatter's lead. +- **Comments**: Use `#` for single-line comments. Avoid block comments `/* ... */` unless necessary for large blocks of text. + +### Module Structure +- **Function Header**: Always use the standard module arguments pattern: + ```nix + { config, lib, pkgs, inputs, outputs, constants, ... }: + ``` + - Include `inputs`, `outputs`, and `constants` if you need access to flake inputs, the flake's own outputs, or the central constants. +- **Option Definitions**: + - Define options in `options = { ... };`. + - Implement configuration in `config = { ... };`. + - Use `mkEnableOption` for boolean flags. + - Use `mkOption` with types (e.g., `types.str`, `types.bool`) and descriptions. +- **Imports**: + - Use relative paths for local modules: `imports = [ ./module.nix ];`. + - Use `inputs` or `outputs` for external/shared modules: `imports = [ outputs.nixosModules.common ];`. + +### Naming Conventions +- **Files**: `kebab-case.nix` (e.g., `hardware-configuration.nix`). +- **Options**: `camelCase` (e.g., `services.myService.enable`). +- **Variables**: `camelCase` in `let ... in` blocks. + +### Flake Specifics +- **Inputs**: Defined in `flake.nix`. + - `nixpkgs`: Main package set (typically following a stable release). + - `nixpkgs-unstable`: Unstable channel for latest packages. +- **Outputs**: + - `nixosConfigurations`: Host definitions. + - `nixosModules`: Reusable modules exported by this flake. + - `packages`: Custom packages exported by this flake. + - `overlays`: Overlays to modify `nixpkgs`. + - `templates`: Project templates for creating new hosts. + +### Error Handling +- Use `assert` for critical configuration invariants. +- Use `warnings` or `trace` for deprecation or non-critical issues during evaluation. +- Example: + ```nix + config = lib.mkIf cfg.enable { + assertions = [ + { assertion = cfg.port > 1024; message = "Port must be non-privileged"; } + ]; + }; + ``` + +## Directory Structure +- `flake.nix`: Entry point, defines inputs and outputs. +- `hosts/`: Specific host configurations. + - `/default.nix`: Host entry point. +- `modules/`: Reusable NixOS modules. + - `nixos/`: Modules specific to NixOS (e.g. `nixvim`, `comin`, `forgejo`). +- `pkgs/`: Custom package definitions. +- `overlays/`: Nixpkgs overlays. +- `templates/`: Templates for bootstrapping new hosts (`raspberry-pi`, `generic-server`). +- `scripts/`: Helper scripts (e.g., `install.sh` for bootstrapping). +- `constants.nix`: Central configuration for domains, IPs, and ports. +- `INSTRUCTIONS.md`: Setup and deployment instructions (DNS, SOPS, bootstrap). +- `README.md`: General project documentation and architecture overview. + +## Deployment Workflows +- **cryodev-main**: Push-based deployment via Forgejo Actions using `deploy-rs`. +- **cryodev-pi**: Pull-based deployment using `comin` (polling the repository). + +## Working with Agents +- **Context**: When asking for changes, specify if it's for a specific host (`hosts/cryodev-main`) or a shared module (`modules/`). +- **Verification**: Always run `nix flake check` after changes to ensure validity. +- **Refactoring**: When moving code, update `imports` carefully. +- **Constants**: Use `constants.nix` for values like domains, IPs, and ports instead of hardcoding them in modules. + + + +================================================ +FILE: constants.nix +================================================ +{ + # Domain + domain = "cryodev.xyz"; + + # Hosts + hosts = { + cryodev-main = { + ip = "100.64.0.1"; # Tailscale IP example + }; + cryodev-pi = { + ip = "100.64.0.2"; # Tailscale IP example + }; + }; + + # Services + services = { + forgejo = { + fqdn = "git.cryodev.xyz"; + port = 3000; + }; + headscale = { + fqdn = "headscale.cryodev.xyz"; + port = 8080; + }; + headplane = { + fqdn = "headplane.cryodev.xyz"; + port = 3001; + }; + netdata = { + fqdn = "netdata.cryodev.xyz"; + port = 19999; + }; + mail = { + fqdn = "mail.cryodev.xyz"; + port = 587; + }; + }; +} + + + +================================================ +FILE: flake.nix +================================================ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs-old-stable.url = "github:nixos/nixpkgs/nixos-25.05"; + + sops-nix.url = "github:Mic92/sops-nix"; + sops-nix.inputs.nixpkgs.follows = "nixpkgs"; + + nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver"; + nixos-mailserver.inputs.nixpkgs.follows = "nixpkgs"; + + headplane.url = "github:yrd/headplane-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"; + + git-hooks.url = "github:cachix/git-hooks.nix"; + git-hooks.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = + { + self, + nixpkgs, + ... + }@inputs: + let + inherit (self) outputs; + + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + lib = nixpkgs.lib; + constants = import ./constants.nix; + + mkNixosConfiguration = + system: modules: + nixpkgs.lib.nixosSystem { + inherit system modules; + specialArgs = { + inherit + inputs + outputs + lib + constants + ; + }; + }; + in + { + packages = forAllSystems (system: import ./pkgs nixpkgs.legacyPackages.${system}); + + overlays = import ./overlays { inherit inputs; }; + + nixosModules = import ./modules/nixos; + + nixosConfigurations = { + cryodev-main = mkNixosConfiguration "x86_64-linux" [ ./hosts/cryodev-main ]; + cryodev-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/cryodev-pi ]; + }; + + templates = { + raspberry-pi = { + path = ./templates/raspberry-pi; + description = "Raspberry Pi 4 Client"; + }; + generic-server = { + path = ./templates/generic-server; + description = "Generic x86_64 Customer Server"; + }; + }; + + formatter = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + config = self.checks.${system}.pre-commit-check.config; + inherit (config) package configFile; + script = '' + ${pkgs.lib.getExe package} run --all-files --config ${configFile} + ''; + in + 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 + pkgs = nixpkgs.legacyPackages.${system}; + flakePkgs = self.packages.${system}; + overlaidPkgs = import nixpkgs { + 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 { + src = ./.; + hooks = { + nixfmt.enable = true; + }; + }; + build-packages = pkgs.linkFarm "flake-packages-${system}" flakePkgs; + build-overlays = pkgs.linkFarm "flake-overlays-${system}" { + # package = overlaidPkgs.package; + }; + } + // deployChecks + ); + }; +} + + + +================================================ +FILE: INSTRUCTIONS.md +================================================ +# Server Setup Instructions / Server-Einrichtungsanleitung + +--- + +# πŸ‡¬πŸ‡§ English Instructions + +## 1. Prerequisites + +Ensure you have the following tools installed on your local machine: +- `nix` (with flakes enabled) +- `sops` +- `age` +- `ssh` + +## 2. DNS Configuration + +Configure the following DNS records for your domain `cryodev.xyz`: + +| Hostname | Type | Value | Purpose | +|----------|------|-------|---------| +| `@` | A | `` | Main entry point | +| `@` | AAAA | `` | Main entry point (IPv6) | +| `git` | CNAME | `@` | Forgejo | +| `headscale` | CNAME | `@` | Headscale | +| `headplane` | CNAME | `@` | Headplane | +| `netdata` | CNAME | `@` | Netdata Monitoring | +| `mail` | A | `` | Mailserver | +| `mail` | AAAA | `` | Mailserver (IPv6) | +| `@` | MX | `10 mail.cryodev.xyz.` | Mail delivery | +| `@` | TXT | `"v=spf1 mx ~all"` | SPF Record | +| `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC Record | + +## 3. Secret Management (SOPS) + +This repository uses `sops-nix` to manage secrets encrypted with `age`, utilizing the SSH host keys of the servers. + +### 3.1 Get Server Public Keys +You need to convert the servers' SSH host public keys to age public keys. + +**For `cryodev-main`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +**For `cryodev-pi`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +### 3.2 Configure `.sops.yaml` +Edit the `.sops.yaml` file in the root of this repository. Add the age public keys to the `keys` section and ensure creation rules exist for both hosts. + +```yaml +keys: + - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # Admin Key (Steffen) + - &main_key age1... # cryodev-main Key + - &pi_key age1... # cryodev-pi Key +creation_rules: + - path_regex: hosts/cryodev-main/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *main_key + - path_regex: hosts/cryodev-pi/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *pi_key +``` + +### 3.3 Generating Secret Values + +**Mailserver Passwords (for `cryodev-main`):** +```bash +nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt' +``` + +**Headplane Secrets (for `cryodev-main`):** +```bash +nix-shell -p openssl --run "openssl rand -hex 16" +# Agent Pre-Authkey requires Headscale running: +sudo headscale users create headplane-agent +sudo headscale preauthkeys create --expiration 99y --reusable --user headplane-agent +``` + +**Tailscale Auth Keys (for both hosts):** +*Requires Headscale running on `cryodev-main`.* +```bash +# For cryodev-main: +sudo headscale preauthkeys create --expiration 99y --reusable --user default +# For cryodev-pi: +sudo headscale preauthkeys create --expiration 99y --reusable --user default +``` + +**Netdata Child UUID (for `cryodev-pi`):** +```bash +uuidgen +``` + +**Forgejo Runner Token:** +Get from Forgejo Admin Panel. + +### 3.4 Creating Secrets Files + +**`hosts/cryodev-main/secrets.yaml`:** +```bash +sops hosts/cryodev-main/secrets.yaml +``` +```yaml +mailserver: + accounts: + forgejo: "$2y$05$..." + admin: "$2y$05$..." + +forgejo-runner: + token: "..." + +headplane: + cookie_secret: "..." + agent_pre_authkey: "..." + +tailscale: + auth-key: "..." +``` + +**`hosts/cryodev-pi/secrets.yaml`:** +```bash +sops hosts/cryodev-pi/secrets.yaml +``` +```yaml +tailscale: + auth-key: "..." + +netdata: + stream: + child-uuid: "..." # Output from uuidgen +``` + +## 4. Initial Deployment (Bootstrap) + +Before the continuous deployment can take over, you must perform an initial deployment manually using the provided install script. + +### 4.1 Prepare Target Machine +1. Boot into the NixOS Installation ISO. +2. Set a root password (for SSH): `passwd`. +3. Ensure internet connectivity. + +### 4.2 Run Install Script +From your local machine (where this repo is), copy the script to the target or run it directly if you can fetch it. + +**Method A: Copy Script via SSH** +```bash +scp scripts/install.sh nixos@:install.sh +ssh nixos@ +sudo -i +chmod +x /home/nixos/install.sh +./home/nixos/install.sh -r -n +``` + +**Method B: Run on Target (if repo is public or reachable)** +```bash +# On the target machine (as root) +nix-shell -p git +git clone /tmp/nixos +cd /tmp/nixos +bash scripts/install.sh -n +``` + +*Note: The script handles disk partitioning (via disko/script), hardware config generation, and installation.* + +## 5. Continuous Deployment (CD) + +### 5.1 cryodev-pi (Pull-based via Comin) +The `cryodev-pi` host is configured to pull updates automatically via `comin`. + +1. **Create Repository:** Create a new repository named `cryodev-server` on your Forgejo instance (`https://git.cryodev.xyz`). +2. **Push Configuration:** Push this entire NixOS configuration to the `main` branch of that repository. +3. **Comin URL:** The configuration expects the repository at: `https://git.cryodev.xyz/steffen/cryodev-server.git`. + +### 5.2 cryodev-main (Push-based via Forgejo Actions) +The main server is deployed via a Forgejo Action. + +1. **Generate SSH Key:** + ```bash + ssh-keygen -t ed25519 -f deploy_key -C "forgejo-actions" + ``` +2. **Add Public Key:** Add the content of `deploy_key.pub` to `/root/.ssh/authorized_keys` on `cryodev-main`. +3. **Add Secret:** Add the content of `deploy_key` (private key) as a secret named `DEPLOY_SSH_KEY` in your Forgejo repository settings. + +## 6. Creating New Hosts (Templates) + +To quickly bootstrap a new host configuration, you can use the provided templates. + +1. **Copy Template:** + ```bash + # For a Raspberry Pi: + cp -r templates/raspberry-pi hosts/new-pi-name + + # For a generic x86 server: + cp -r templates/generic-server hosts/new-server-name + ``` +2. **Adjust Configuration:** + * **Hostname:** Edit `hosts/new-name/networking.nix`. + * **Flake:** Register the new host in `flake.nix` under `nixosConfigurations`. + * **Constants:** Add IP and ports to `constants.nix`. + * **Secrets:** Add keys to `.sops.yaml` and create `hosts/new-name/secrets.yaml`. + +--- + +# πŸ‡©πŸ‡ͺ Deutsche Anleitung + +## 1. Voraussetzungen + +Stellen Sie sicher, dass folgende Tools lokal installiert sind: +- `nix` (mit Flakes) +- `sops` +- `age` +- `ssh` +- `ssh-to-age` +- `uuidgen` + +## 2. DNS-Konfiguration + +Richten Sie folgende DNS-EintrΓ€ge fΓΌr `cryodev.xyz` ein: + +| Hostname | Typ | Wert | Zweck | +|----------|-----|------|-------| +| `@` | A | `` | Hauptserver | +| `@` | AAAA | `` | Hauptserver (IPv6) | +| `git` | CNAME | `@` | Forgejo | +| `headscale` | CNAME | `@` | Headscale | +| `headplane` | CNAME | `@` | Headplane | +| `netdata` | CNAME | `@` | Netdata Monitoring | +| `mail` | A | `` | Mailserver | +| `mail` | AAAA | `` | Mailserver (IPv6) | +| `@` | MX | `10 mail.cryodev.xyz.` | Mail-Empfang | +| `@` | TXT | `"v=spf1 mx ~all"` | SPF-Record | +| `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC-Record | + +## 3. Verwaltung von Geheimnissen (SOPS) + +Dieses Repository nutzt `sops-nix` mit den SSH-Host-Keys der Server. + +### 3.1 Public Keys abrufen + +**FΓΌr `cryodev-main`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +**FΓΌr `cryodev-pi`:** +```bash +nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 | ssh-to-age' +``` + +### 3.2 `.sops.yaml` konfigurieren +Bearbeiten Sie `.sops.yaml` und fΓΌgen Sie die Keys sowie Regeln fΓΌr beide Hosts hinzu: + +```yaml +keys: + - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # Admin Key (Steffen) + - &main_key age1... # cryodev-main Key + - &pi_key age1... # cryodev-pi Key +creation_rules: + - path_regex: hosts/cryodev-main/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *main_key + - path_regex: hosts/cryodev-pi/secrets.yaml$ + key_groups: + - age: + - *admin_key + - *pi_key +``` + +### 3.3 Werte generieren + +**Mailserver:** `mkpasswd -sm bcrypt` +**Headplane:** `openssl rand -hex 16` +**Netdata UUID:** `uuidgen` + +**Tailscale Auth Keys (auf `cryodev-main`):** +```bash +sudo headscale preauthkeys create --expiration 99y --reusable --user default +``` + +### 3.4 Secrets-Dateien erstellen + +**`hosts/cryodev-main/secrets.yaml`:** +```bash +sops hosts/cryodev-main/secrets.yaml +``` +```yaml +mailserver: + accounts: + forgejo: "$2y$05$..." + admin: "$2y$05$..." + +forgejo-runner: + token: "..." + +headplane: + cookie_secret: "..." + agent_pre_authkey: "..." + +tailscale: + auth-key: "..." +``` + +**`hosts/cryodev-pi/secrets.yaml`:** +```bash +sops hosts/cryodev-pi/secrets.yaml +``` +```yaml +tailscale: + auth-key: "..." + +netdata: + stream: + child-uuid: "..." # Output von uuidgen +``` + +## 4. Erstes Deployment (Bootstrap) + +Bevor das automatische Deployment funktionieren kann, mΓΌssen Sie das System einmal manuell mit dem Installationsskript installieren. + +### 4.1 Zielmaschine vorbereiten +1. Booten Sie das NixOS Installations-ISO. +2. Setzen Sie ein Root-Passwort: `passwd`. +3. Stellen Sie eine Internetverbindung her. + +### 4.2 Install-Script ausfΓΌhren +Kopieren Sie das Skript von Ihrem lokalen Rechner auf das Zielsystem. + +**Methode A: Per SCP** +```bash +scp scripts/install.sh nixos@:install.sh +ssh nixos@ +sudo -i +chmod +x /home/nixos/install.sh +./home/nixos/install.sh -r -n +``` + +**Methode B: Direkt auf dem Ziel (bei ΓΆffentlichem/erreichbarem Repo)** +```bash +# Auf der Zielmaschine (als root) +nix-shell -p git +git clone /tmp/nixos +cd /tmp/nixos +bash scripts/install.sh -n +``` + +*Hinweis: Das Skript kΓΌmmert sich um Partitionierung, Hardware-Config und Installation.* + +## 5. Continuous Deployment (CD) + +### 5.1 cryodev-pi (Pull-basiert via Comin) +Der Host `cryodev-pi` zieht Updates automatisch via `comin`. + +1. **Repository erstellen:** Erstellen Sie ein Repository namens `cryodev-server` auf `https://git.cryodev.xyz`. +2. **Konfiguration pushen:** Pushen Sie diese Konfiguration in den `main`-Branch. +3. **Comin URL:** `https://git.cryodev.xyz/steffen/cryodev-server.git`. + +### 5.2 cryodev-main (Push-basiert via Forgejo Actions) +Der Hauptserver wird ΓΌber eine Forgejo Action deployt. + +1. **SSH Key generieren:** + ```bash + ssh-keygen -t ed25519 -f deploy_key -C "forgejo-actions" + ``` +2. **Public Key hinzufΓΌgen:** Inhalt von `deploy_key.pub` in `/root/.ssh/authorized_keys` auf `cryodev-main` eintragen. +3. **Secret hinzufΓΌgen:** Inhalt von `deploy_key` (Private Key) als Secret `DEPLOY_SSH_KEY` im Forgejo-Repository hinterlegen. + +## 6. Neue Hosts erstellen (Templates) + +Um schnell eine neue Host-Konfiguration zu erstellen, kΓΆnnen Sie die bereitgestellten Templates nutzen. + +1. **Template kopieren:** + ```bash + # FΓΌr einen Raspberry Pi: + cp -r templates/raspberry-pi hosts/neuer-pi-name + + # FΓΌr einen generischen x86 Server: + cp -r templates/generic-server hosts/neuer-server-name + ``` +2. **Konfiguration anpassen:** + * **Hostname:** Bearbeiten Sie `hosts/neuer-name/networking.nix`. + * **Flake:** Registrieren Sie den neuen Host in `flake.nix` unter `nixosConfigurations`. + * **Constants:** FΓΌgen Sie IP und Ports in `constants.nix` hinzu. + * **Secrets:** FΓΌgen Sie Keys zu `.sops.yaml` hinzu und erstellen Sie `hosts/neuer-name/secrets.yaml`. + + + +================================================ +FILE: .sops.yaml +================================================ +keys: + - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # Admin key (Steffen) +creation_rules: + - path_regex: hosts/cryodev-main/secrets.yaml$ + key_groups: + - age: + - *admin_key + # - *server_key # Add server key here once obtained + - path_regex: hosts/cryodev-pi/secrets.yaml$ + key_groups: + - age: + - *admin_key + # - *pi_key # Add pi key here once obtained + + + +================================================ +FILE: hosts/cryodev-main/boot.nix +================================================ +{ + boot.loader.systemd-boot = { + enable = true; + configurationLimit = 10; + }; + boot.loader.efi.canTouchEfiVariables = true; +} + + + +================================================ +FILE: hosts/cryodev-main/default.nix +================================================ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} + + + +================================================ +FILE: hosts/cryodev-main/disks.sh +================================================ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL + + + +================================================ +FILE: hosts/cryodev-main/hardware.nix +================================================ +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ + "ahci" + "nvme" + "sd_mod" + "sdhci_pci" + "sr_mod" + "usb_storage" + "virtio_pci" + "virtio_scsi" + "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" + ]; + }; + + swapDevices = [ { device = "/dev/disk/by-label/SWAP"; } ]; + + networking.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} + + + +================================================ +FILE: hosts/cryodev-main/networking.nix +================================================ +{ + networking.hostName = "cryodev-main"; + networking.domain = "cryodev.xyz"; +} + + + +================================================ +FILE: hosts/cryodev-main/packages.nix +================================================ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} + + + +================================================ +FILE: hosts/cryodev-main/users.nix +================================================ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ]; +} + + + +================================================ +FILE: hosts/cryodev-main/services/default.nix +================================================ +{ + imports = [ + ./forgejo.nix + ./headplane.nix + ./headscale.nix + ./mailserver.nix + ./netdata.nix + ./nginx.nix + ./openssh.nix + ./sops.nix + ./tailscale.nix + ]; +} + + + +================================================ +FILE: hosts/cryodev-main/services/forgejo.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.forgejo + outputs.nixosModules.forgejo-runner + ]; + + services.forgejo = { + enable = true; + settings = { + server = { + DOMAIN = constants.services.forgejo.fqdn; + ROOT_URL = "https://${constants.services.forgejo.fqdn}/"; + HTTP_PORT = constants.services.forgejo.port; + }; + service = { + DISABLE_REGISTRATION = true; + }; + mailer = { + ENABLED = true; + FROM = "forgejo@${constants.domain}"; + SMTP_ADDR = constants.services.mail.fqdn; + SMTP_PORT = constants.services.mail.port; + USER = "forgejo@${constants.domain}"; + }; + }; + sops = true; # Enable sops integration for secrets + }; + + services.forgejo-runner = { + enable = true; + url = "https://${constants.services.forgejo.fqdn}"; + # Token needs to be set up via sops/secrets + sops = true; + }; + + services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.forgejo.port}"; + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/headplane.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.headplane + ]; + + services.headplane = { + enable = true; + port = constants.services.headplane.port; + headscale = { + url = "http://127.0.0.1:${toString constants.services.headscale.port}"; + public_url = "https://${constants.services.headscale.fqdn}"; + }; + # Secrets for headplane need to be configured via sops + sops.secrets = { + "headplane/cookie_secret" = { }; + "headplane/agent_pre_authkey" = { }; + }; + }; + + services.nginx.virtualHosts."${constants.services.headplane.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.headplane.port}"; + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/headscale.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.headscale + ]; + + services.headscale = { + enable = true; + address = "127.0.0.1"; + port = constants.services.headscale.port; + settings = { + server_url = "https://${constants.services.headscale.fqdn}"; + dns_config.base_domain = constants.domain; + }; + }; + + services.nginx.virtualHosts."${constants.services.headscale.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.headscale.port}"; + proxyWebsockets = true; + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/mailserver.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.mailserver + ]; + + mailserver = { + enable = true; + fqdn = constants.services.mail.fqdn; + domains = [ constants.domain ]; + accounts = { + forgejo = { }; + admin = { + aliases = [ "postmaster" ]; + }; + }; + certificateScheme = "acme-nginx"; + sops = true; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/netdata.nix +================================================ +{ + config, + pkgs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + package = pkgs.netdata.override { + withCloudUi = true; + }; + config = { + global = { + "debug log" = "syslog"; + "access log" = "syslog"; + "error log" = "syslog"; + "bind to" = "127.0.0.1"; + }; + }; + }; + + services.nginx.virtualHosts."${constants.services.netdata.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.netdata.port}"; + proxyWebsockets = true; + # Basic Auth can be added here if desired, or restrict by IP + # extraConfig = "allow 100.64.0.0/10; deny all;"; # Example for Tailscale only + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/nginx.nix +================================================ +{ + inputs, + outputs, + lib, + config, + pkgs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; # Force SSL for all vhosts by default if configured to use this option + openFirewall = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/openssh.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} + + + +================================================ +FILE: hosts/cryodev-main/services/sops.nix +================================================ +{ + config, + pkgs, + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.sops + ]; + + 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" = { }; + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-main/services/tailscale.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + }; +} + + + +================================================ +FILE: hosts/cryodev-pi/boot.nix +================================================ +{ + boot = { + loader = { + grub.enable = false; + generic-extlinux-compatible.enable = true; + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-pi/default.nix +================================================ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} + + + +================================================ +FILE: hosts/cryodev-pi/disks.sh +================================================ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL + + + +================================================ +FILE: hosts/cryodev-pi/hardware.nix +================================================ +{ pkgs, lib, ... }: + +{ + boot = { + kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; + initrd.availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ "noatime" ]; + }; + }; + + nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; + hardware.enableRedistributableFirmware = true; +} + + + +================================================ +FILE: hosts/cryodev-pi/networking.nix +================================================ +{ + networking.hostName = "cryodev-pi"; + networking.domain = "cryodev.xyz"; +} + + + +================================================ +FILE: hosts/cryodev-pi/packages.nix +================================================ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} + + + +================================================ +FILE: hosts/cryodev-pi/users.nix +================================================ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ../../users/cryotherm + ]; +} + + + +================================================ +FILE: hosts/cryodev-pi/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-server.git"; + branches.main.name = "main"; + } + ]; + }; +} + + + +================================================ +FILE: hosts/cryodev-pi/services/default.nix +================================================ +{ + imports = [ + ./nginx.nix + ./openssh.nix + ./tailscale.nix + ./netdata.nix + ./comin.nix + ]; +} + + + +================================================ +FILE: hosts/cryodev-pi/services/netdata.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + config = { + stream = { + enabled = "yes"; + destination = "${constants.hosts.cryodev-main.ip}:${toString constants.services.netdata.port}"; + "api key" = config.sops.placeholder."netdata/stream/child-uuid"; + }; + }; + }; + + # Make sure sops is enabled/imported for this host to handle the secret + imports = [ outputs.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + secrets."netdata/stream/child-uuid" = { + owner = "netdata"; + group = "netdata"; + }; + }; +} + + + +================================================ +FILE: hosts/cryodev-pi/services/nginx.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; + openFirewall = true; + }; +} + + + +================================================ +FILE: hosts/cryodev-pi/services/openssh.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} + + + +================================================ +FILE: hosts/cryodev-pi/services/tailscale.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + + # Auth key for automated enrollment + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + }; + + sops.secrets."tailscale/auth-key" = { }; +} + + + +================================================ +FILE: modules/nixos/default.nix +================================================ +{ + common = import ./common; + comin = import ./comin; + forgejo = import ./forgejo; + forgejo-runner = import ./forgejo-runner; + mailserver = import ./mailserver; + nixvim = import ./nixvim; + normalUsers = import ./normalUsers; + nginx = import ./nginx; + openssh = import ./openssh; + sops = import ./sops; + tailscale = import ./tailscale; +} + + + +================================================ +FILE: modules/nixos/comin/default.nix +================================================ +{ + inputs, + ... +}: + +{ + imports = [ inputs.comin.nixosModules.comin ]; +} + + + +================================================ +FILE: modules/nixos/common/default.nix +================================================ +{ + imports = [ + ./environment.nix + ./htop.nix + ./nationalization.nix + ./networking.nix + ./nix.nix + ./sudo.nix + ./well-known.nix + ./zsh.nix + + ./shared + ./overlays.nix + ]; +} + + + +================================================ +FILE: modules/nixos/common/environment.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) mkDefault optionals; +in +{ + environment.systemPackages = + with pkgs; + [ + cryptsetup + curl + dig + dnsutils + fzf + gptfdisk + iproute2 + jq + lm_sensors + lsof + netcat-openbsd + nettools + nixos-container + nmap + nurl + p7zip + pciutils + psmisc + rclone + rsync + tcpdump + tmux + tree + unzip + usbutils + wget + xxd + zip + + (callPackage ../../../apps/rebuild { }) + ] + ++ optionals (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) [ + pkgs.kitty.terminfo + ]; + + environment.shellAliases = { + l = "ls -lh"; + ll = "ls -lAh"; + ports = "ss -tulpn"; + publicip = "curl ifconfig.me/all"; + sudo = "sudo "; # make aliases work with `sudo` + }; + + # saves one instance of nixpkgs. + environment.ldso32 = null; + + boot.tmp.cleanOnBoot = mkDefault true; + boot.initrd.systemd.enable = mkDefault (!config.boot.swraid.enable && !config.boot.isContainer); +} + + + +================================================ +FILE: modules/nixos/common/htop.nix +================================================ +{ + programs.htop = { + enable = true; + settings = { + highlight_base_name = 1; + }; + }; +} + + + +================================================ +FILE: modules/nixos/common/nationalization.nix +================================================ +{ lib, ... }: + +let + de = "de_DE.UTF-8"; + en = "en_US.UTF-8"; + + inherit (lib) mkDefault; +in +{ + i18n = { + defaultLocale = mkDefault en; + extraLocaleSettings = { + LC_ADDRESS = mkDefault de; + LC_IDENTIFICATION = mkDefault de; + LC_MEASUREMENT = mkDefault de; + LC_MONETARY = mkDefault de; + LC_NAME = mkDefault de; + LC_NUMERIC = mkDefault de; + LC_PAPER = mkDefault de; + LC_TELEPHONE = mkDefault de; + LC_TIME = mkDefault en; + }; + }; + + console = { + font = mkDefault "Lat2-Terminus16"; + keyMap = mkDefault "de"; + }; + + time.timeZone = mkDefault "Europe/Berlin"; +} + + + +================================================ +FILE: modules/nixos/common/networking.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) mkDefault; + inherit (lib.utils) isNotEmptyStr; +in +{ + config = { + assertions = [ + { + assertion = isNotEmptyStr config.networking.domain; + message = "synix/nixos/common: config.networking.domain cannot be empty."; + } + { + assertion = isNotEmptyStr config.networking.hostName; + message = "synix/nixos/common: config.networking.hostName cannot be empty."; + } + ]; + + networking = { + domain = mkDefault "${config.networking.hostName}.local"; + hostId = mkDefault "8425e349"; # same as NixOS install ISO and nixos-anywhere + + # NetworkManager + useDHCP = false; + networkmanager = { + enable = true; + plugins = with pkgs; [ + networkmanager-openconnect + networkmanager-openvpn + ]; + }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/common/nix.nix +================================================ +{ + config, + lib, + ... +}: + +let + inherit (lib) mkDefault; +in +{ + nix = { + # use flakes + channel.enable = mkDefault false; + + # De-duplicate store paths using hardlinks except in containers + # where the store is host-managed. + optimise.automatic = mkDefault (!config.boot.isContainer); + }; +} + + + +================================================ +FILE: modules/nixos/common/overlays.nix +================================================ +{ outputs, ... }: + +{ + nixpkgs.overlays = [ + outputs.overlays.local-packages + outputs.overlays.modifications + outputs.overlays.old-stable-packages + outputs.overlays.unstable-packages + ]; +} + + + +================================================ +FILE: modules/nixos/common/sudo.nix +================================================ +{ config, ... }: + +{ + security.sudo = { + enable = true; + execWheelOnly = true; + extraConfig = '' + Defaults lecture = never + ''; + }; + + assertions = + let + validUsers = users: users == [ ] || users == [ "root" ]; + validGroups = groups: groups == [ ] || groups == [ "wheel" ]; + validUserGroups = builtins.all ( + r: validUsers (r.users or [ ]) && validGroups (r.groups or [ ]) + ) config.security.sudo.extraRules; + in + [ + { + assertion = config.security.sudo.execWheelOnly -> validUserGroups; + message = "Some definitions in `security.sudo.extraRules` refer to users other than 'root' or groups other than 'wheel'. Disable `config.security.sudo.execWheelOnly`, or adjust the rules."; + } + ]; +} + + + +================================================ +FILE: modules/nixos/common/well-known.nix +================================================ +{ + # avoid TOFU MITM + programs.ssh.knownHosts = { + "github.com".hostNames = [ "github.com" ]; + "github.com".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"; + + "gitlab.com".hostNames = [ "gitlab.com" ]; + "gitlab.com".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf"; + + "git.sr.ht".hostNames = [ "git.sr.ht" ]; + "git.sr.ht".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60"; + }; + # TODO: add synix +} + + + +================================================ +FILE: modules/nixos/common/zsh.nix +================================================ +{ + programs.zsh = { + enable = true; + syntaxHighlighting = { + enable = true; + highlighters = [ + "main" + "brackets" + "cursor" + "pattern" + ]; + patterns = { + "rm -rf" = "fg=white,bold,bg=red"; + "rm -fr" = "fg=white,bold,bg=red"; + }; + }; + autosuggestions = { + enable = true; + strategy = [ + "completion" + "history" + ]; + }; + enableLsColors = true; + }; +} + + + +================================================ +FILE: modules/nixos/common/shared/default.nix +================================================ +{ + imports = [ + ./nix.nix + ]; +} + + + +================================================ +FILE: modules/nixos/common/shared/nix.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) + mkDefault + optional + versionOlder + versions + ; +in +{ + nix.package = mkDefault pkgs.nix; + + # for `nix run synix#foo`, `nix build synix#bar`, etc + nix.registry = { + synix = { + from = { + id = "synix"; + type = "indirect"; + }; + to = { + owner = "sid"; + repo = "synix"; + host = "git.sid.ovh"; + type = "gitea"; + }; + }; + }; + + # fallback quickly if substituters are not available. + nix.settings.connect-timeout = mkDefault 5; + nix.settings.fallback = true; + + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ] + ++ optional ( + config.nix.package != null && versionOlder (versions.majorMinor config.nix.package.version) "2.22" + ) "repl-flake"; + + nix.settings.log-lines = mkDefault 25; + + # avoid disk full issues + nix.settings.max-free = mkDefault (3000 * 1024 * 1024); + nix.settings.min-free = mkDefault (512 * 1024 * 1024); + + # avoid copying unnecessary stuff over SSH + nix.settings.builders-use-substitutes = true; + + # workaround for https://github.com/NixOS/nix/issues/9574 + nix.settings.nix-path = config.nix.nixPath; + + nix.settings.download-buffer-size = 524288000; # 500 MiB + + # add all wheel users to the trusted-users group + nix.settings.trusted-users = [ + "@wheel" + ]; + + # binary caches + nix.settings.substituters = [ + "https://cache.nixos.org" + "https://nix-community.cachix.org" + "https://cache.garnix.io" + "https://numtide.cachix.org" + ]; + nix.settings.trusted-public-keys = [ + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" + "numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE=" + ]; + + nix.gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; + }; +} + + + +================================================ +FILE: modules/nixos/forgejo/default.nix +================================================ +{ + config, + lib, + ... +}: + +let + cfg = config.services.forgejo; + + inherit (cfg) settings; + inherit (lib) + getExe + head + mkDefault + mkIf + ; +in +{ + config = mkIf cfg.enable { + services.forgejo = { + database.type = "postgres"; + lfs.enable = true; + settings = { + server = { + DOMAIN = "git.${config.networking.domain}"; + PROTOCOL = "http"; + ROOT_URL = "https://${settings.server.DOMAIN}/"; + HTTP_ADDR = "0.0.0.0"; + HTTP_PORT = 3456; + SSH_PORT = head config.services.openssh.ports; + }; + service = { + DISABLE_REGISTRATION = true; + }; + ui = { + DEFAULT_THEME = "forgejo-dark"; + }; + actions = { + ENABLED = true; + }; + mailer = { + ENABLED = mkDefault false; + SMTP_ADDR = "mail.${config.networking.domain}"; + FROM = "git@${settings.server.DOMAIN}"; + USER = "git@${settings.server.DOMAIN}"; + }; + }; + secrets = { + mailer.PASSWD = mkIf settings.mailer.ENABLED config.sops.secrets."forgejo/mail-pw".path; + }; + }; + + environment.shellAliases = { + forgejo = "sudo -u ${cfg.user} ${getExe cfg.package} --config ${cfg.stateDir}/custom/conf/app.ini"; + }; + + sops.secrets."forgejo/mail-pw" = mkIf settings.mailer.ENABLED { + owner = cfg.user; + group = cfg.group; + mode = "0400"; + }; + }; +} + + + +================================================ +FILE: modules/nixos/forgejo-runner/default.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.forgejo-runner; + + inherit (lib) + mkEnableOption + mkIf + mkOption + types + ; +in +{ + options.services.forgejo-runner = { + enable = mkEnableOption "Nix-based Forgejo Runner service"; + url = mkOption { + type = types.str; + description = "Forgejo instance URL."; + }; + tokenFile = mkOption { + type = types.path; + description = "Path to EnvironmentFile containing TOKEN=..."; + }; + }; + + config = mkIf cfg.enable { + nix.settings.trusted-users = [ "gitea-runner" ]; + + services.gitea-actions-runner = { + package = pkgs.forgejo-runner; + instances.default = { + enable = true; + name = "${config.networking.hostName}-nix"; + inherit (cfg) url tokenFile; + + labels = [ "host:host" ]; + + hostPackages = with pkgs; [ + bash + coreutils + curl + gitMinimal + gnused + nix + nodejs + openssh + deploy-rs + ]; + + settings = { + log.level = "info"; + runner = { + capacity = 1; + envs = { + NIX_CONFIG = "extra-experimental-features = nix-command flakes"; + NIX_REMOTE = "daemon"; + }; + }; + }; + }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/headplane/default.nix +================================================ +{ + inputs, + config, + lib, + ... +}: + +let + cfg = config.services.headplane; + domain = config.networking.domain; + subdomain = cfg.reverseProxy.subdomain; + fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain; + headscale = config.services.headscale; + + inherit (lib) + mkDefault + mkIf + ; + + inherit (lib.utils) + mkReverseProxyOption + mkVirtualHost + ; +in +{ + imports = [ inputs.headplane.nixosModules.headplane ]; + + options.services.headplane = { + reverseProxy = mkReverseProxyOption "Headplane" "hp"; + }; + + config = mkIf cfg.enable { + nixpkgs.overlays = [ + inputs.headplane.overlays.default + ]; + + services.headplane = { + settings = { + server = { + host = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0"); + port = mkDefault 3000; + cookie_secret_path = config.sops.secrets."headplane/cookie_secret".path; + }; + headscale = { + url = "http://127.0.0.1:${toString headscale.port}"; + public_url = headscale.settings.server_url; + config_path = "/etc/headscale/config.yaml"; + }; + integration.agent = { + enabled = mkDefault true; + pre_authkey_path = config.sops.secrets."headplane/agent_pre_authkey".path; + }; + }; + }; + + services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable { + "${fqdn}" = mkVirtualHost { + port = cfg.settings.server.port; + ssl = cfg.reverseProxy.forceSSL; + }; + }; + + sops.secrets = + let + owner = headscale.user; + group = headscale.group; + mode = "0400"; + in + { + "headplane/cookie_secret" = { + inherit owner group mode; + }; + "headplane/agent_pre_authkey" = { + inherit owner group mode; + }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/headscale/acl.hujson +================================================ +{ + "acls": [ + { + "action": "accept", + "src": ["*"], + "dst": ["*:*"] + } + ], + "ssh": [ + { + "action": "accept", + "src": ["autogroup:member"], + "dst": ["autogroup:member"], + "users": ["autogroup:nonroot", "root"] + } + ] +} + + + +================================================ +FILE: modules/nixos/headscale/default.nix +================================================ +{ + config, + lib, + ... +}: + +let + cfg = config.services.headscale; + domain = config.networking.domain; + subdomain = cfg.reverseProxy.subdomain; + fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain; + acl = "headscale/acl.hujson"; + + inherit (lib) + mkDefault + mkIf + mkOption + optional + optionals + types + ; + + inherit (lib.utils) + mkReverseProxyOption + mkUrl + mkVirtualHost + ; +in +{ + options.services.headscale = { + reverseProxy = mkReverseProxyOption "Headscale" "hs"; + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Whether to automatically open firewall ports. TCP: 80, 443; UDP: 3478."; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !cfg.settings.derp.server.enable || cfg.reverseProxy.forceSSL; + message = "cryodev/nixos/headscale: DERP requires TLS"; + } + { + assertion = fqdn != cfg.settings.dns.base_domain; + message = "cryodev/nixos/headscale: `settings.server_url` must be different from `settings.dns.base_domain`"; + } + { + assertion = !cfg.settings.dns.override_local_dns || cfg.settings.dns.nameservers.global != [ ]; + message = "cryodev/nixos/headscale: `settings.dns.nameservers.global` must be set when `settings.dns.override_local_dns` is true"; + } + ]; + + environment.etc.${acl} = { + inherit (config.services.headscale) user group; + source = ./acl.hujson; + }; + + environment.shellAliases = { + hs = "${cfg.package}/bin/headscale"; + }; + + services.headscale = { + address = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0"); + port = mkDefault 8077; + settings = { + policy.path = "/etc/${acl}"; + database.type = "sqlite"; # postgres is highly discouraged as it is only supported for legacy reasons + server_url = mkUrl { + inherit fqdn; + ssl = with cfg.reverseProxy; enable && forceSSL; + }; + derp.server.enable = cfg.reverseProxy.forceSSL; + dns = { + magic_dns = mkDefault true; + base_domain = mkDefault "tail"; + search_domains = [ cfg.settings.dns.base_domain ]; + override_local_dns = mkDefault true; + nameservers.global = optionals cfg.settings.dns.override_local_dns [ + "1.1.1.1" + "1.0.0.1" + "2606:4700:4700::1111" + "2606:4700:4700::1001" + ]; + }; + }; + }; + + services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable { + "${fqdn}" = mkVirtualHost { + inherit (cfg) address port; + ssl = cfg.reverseProxy.forceSSL; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ + 80 + 443 + ]; + allowedUDPPorts = optional cfg.settings.derp.server.enable 3478; + }; + }; +} + + + +================================================ +FILE: modules/nixos/mailserver/default.nix +================================================ +{ + inputs, + config, + lib, + pkgs, + ... +}: + +let + cfg = config.mailserver; + domain = config.networking.domain; + fqdn = "${cfg.subdomain}.${domain}"; + + inherit (lib) + mapAttrs' + mkDefault + mkIf + mkOption + nameValuePair + types + ; +in +{ + imports = [ inputs.nixos-mailserver.nixosModules.mailserver ]; + + options.mailserver = { + subdomain = mkOption { + type = types.str; + default = "mail"; + description = "Subdomain for rDNS"; + }; + accounts = mkOption { + type = types.attrsOf ( + types.submodule { + options = { + aliases = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "A list of aliases of this account. `@domain` will be appended automatically."; + }; + sendOnly = mkOption { + type = types.bool; + default = false; + description = "Specifies if the account should be a send-only account."; + }; + }; + } + ); + default = { }; + description = '' + This options wraps `loginAccounts`. + `loginAccounts..name` will be automatically set to `@`. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.subdomain != ""; + message = "cryodev/nixos/mailserver: config.mailserver.subdomain cannot be empty."; + } + ]; + + mailserver = { + fqdn = mkDefault fqdn; + + domains = mkDefault [ domain ]; + certificateScheme = mkDefault "acme-nginx"; + stateVersion = mkDefault 1; + + loginAccounts = mapAttrs' ( + user: accConf: + nameValuePair "${user}@${domain}" { + name = "${user}@${domain}"; + aliases = map (alias: "${alias}@${domain}") (accConf.aliases or [ ]); + sendOnly = accConf.sendOnly; + quota = mkDefault "5G"; + hashedPasswordFile = config.sops.secrets."mailserver/accounts/${user}".path; + } + ) cfg.accounts; + }; + + security.acme = { + acceptTerms = true; + defaults.email = mkDefault "postmaster@cryodev.xyz"; + defaults.webroot = mkDefault "/var/lib/acme/acme-challenge"; + }; + + environment.systemPackages = [ pkgs.mailutils ]; + + sops = { + secrets = mapAttrs' ( + user: _config: + nameValuePair "mailserver/accounts/${user}" { + restartUnits = [ + "postfix.service" + "dovecot.service" + ]; + } + ) cfg.accounts; + }; + }; +} + + + +================================================ +FILE: modules/nixos/nginx/default.nix +================================================ +{ config, lib, ... }: + +let + cfg = config.services.nginx; + + inherit (lib) + mkDefault + mkIf + mkOption + optional + optionals + types + ; +in +{ + options.services.nginx = { + forceSSL = mkOption { + type = types.bool; + default = false; + description = "Force SSL for Nginx virtual host."; + }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Whether to open the firewall for HTTP (and HTTPS if forceSSL is enabled)."; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = optionals cfg.openFirewall ( + [ + 80 + ] + ++ optional cfg.forceSSL 443 + ); + + services.nginx = { + recommendedOptimisation = mkDefault true; + recommendedGzipSettings = mkDefault true; + recommendedProxySettings = mkDefault true; + recommendedTlsSettings = cfg.forceSSL; + + commonHttpConfig = "access_log syslog:server=unix:/dev/log;"; + + resolver.addresses = + let + isIPv6 = addr: builtins.match ".*:.*:.*" addr != null; + escapeIPv6 = addr: if isIPv6 addr then "[${addr}]" else addr; + cloudflare = [ + "1.1.1.1" + "2606:4700:4700::1111" + ]; + resolvers = + if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers; + in + map escapeIPv6 resolvers; + + sslDhparam = mkIf cfg.forceSSL config.security.dhparams.params.nginx.path; + }; + + security.acme = mkIf cfg.forceSSL { + acceptTerms = true; + defaults.email = mkDefault "postmaster@${config.networking.domain}"; + defaults.webroot = mkDefault "/var/lib/acme/acme-challenge"; + }; + + security.dhparams = mkIf cfg.forceSSL { + enable = true; + params.nginx = { }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/default.nix +================================================ +{ + inputs, + config, + lib, + ... +}: + +let + cfg = config.programs.nixvim; + + inherit (lib) mkDefault mkIf; +in +{ + imports = [ + inputs.nixvim.nixosModules.nixvim + ./plugins + + ./spellfiles.nix + ]; + + config = { + programs.nixvim = { + enable = true; # Enable globally on NixOS + defaultEditor = mkDefault true; + viAlias = mkDefault true; + vimAlias = mkDefault true; + + # Removed home-manager specific options like 'enableMan' which is handled differently or not needed in system module context + # Removed clipboard.providers.wl-copy as it's home-manager specific. + # System-wide clipboard integration for headless servers is less critical but can be added if needed. + + # vim.g.* + globals = { + mapleader = mkDefault " "; + }; + + # vim.opt.* + opts = { + # behavior + cursorline = mkDefault true; # highlights the line under the cursor + mouse = mkDefault "a"; # enable mouse support + nu = mkDefault true; # line numbers + relativenumber = mkDefault true; # relative line numbers + scrolloff = mkDefault 20; # keeps some context above/below cursor + signcolumn = mkDefault "yes"; # reserve space for signs (e.g., GitGutter) + undofile = mkDefault true; # persistent undo + updatetime = mkDefault 500; # ms to wait for trigger an event (default 4000ms) + wrap = mkDefault true; # wraps text if it exceeds the width of the window + + # search + ignorecase = mkDefault true; # ignore case in search patterns + smartcase = mkDefault true; # smart case + incsearch = mkDefault true; # incremental search + hlsearch = mkDefault true; # highlight search + + # windows + splitbelow = mkDefault true; # new windows are created below current + splitright = mkDefault true; # new windows are created to the right of current + equalalways = mkDefault true; # window sizes are automatically updated. + + # tabs + expandtab = mkDefault true; # convert tabs into spaces + shiftwidth = mkDefault 2; # number of spaces to use for each step of (auto)indent + smartindent = mkDefault true; # smart autoindenting on new lines + softtabstop = mkDefault 2; # number of spaces in tab when editing + tabstop = mkDefault 2; # number of visual spaces per tab + + # spell checking + spell = mkDefault true; + spelllang = mkDefault [ + "en_us" + "de_20" + ]; + + }; + + # vim.diagnostic.config.* + diagnostic.settings = { + virtual_text = { + spacing = 4; + prefix = "●"; + severity_sort = true; + }; + signs = true; + underline = true; + update_in_insert = false; + }; + + extraConfigLua = '' + vim.cmd "set noshowmode" -- Hides "--INSERT--" mode indicator + ''; + + keymaps = import ./keymaps.nix; + }; + + environment = { + variables = { + EDITOR = mkIf cfg.enable "nvim"; + VISUAL = mkIf cfg.enable "nvim"; + }; + shellAliases = { + v = mkIf cfg.enable "nvim"; + }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/keymaps.nix +================================================ +[ + # cursor navigation + { + # scroll down, recenter + key = ""; + action = "zz"; + mode = "n"; + } + { + # scroll up, recenter + key = ""; + action = "zz"; + mode = "n"; + } + + # searching + { + # center cursor after search next + key = "n"; + action = "nzzzv"; + mode = "n"; + } + { + # center cursor after search previous + key = "N"; + action = "Nzzzv"; + mode = "n"; + } + { + # ex command + key = "pv"; + action = "Ex"; + mode = "n"; + } + + # search and replace + { + # search and replace word under cursor + key = "s"; + action = ":%s///gI"; + mode = "n"; + } + # search and replace selected text + { + key = "s"; + action = "y:%s/0/0/gI"; + mode = "v"; + } + + # clipboard operations + { + # copy to system clipboard in visual mode + key = ""; + action = ''"+y ''; + mode = "v"; + } + { + # paste from system clipboard in visual mode + key = ""; + action = ''"+p ''; + mode = "v"; + } + { + # yank to system clipboard + key = "Y"; + action = "+Y"; + mode = "n"; + } + { + # replace selected text with clipboard content + key = "p"; + action = "_dP"; + mode = "x"; + } + { + # delete without copying to clipboard + key = "d"; + action = "_d"; + mode = [ + "n" + "v" + ]; + } + + # line operations + { + # move lines down in visual mode + key = "J"; + action = ":m '>+1gv=gv"; + mode = "v"; + } + { + # move lines up in visual mode + key = "K"; + action = ":m '<-2gv=gv"; + mode = "v"; + } + { + # join lines + key = "J"; + action = "mzJ`z"; + mode = "n"; + } + + # quickfix + { + # Run make command + key = "m"; + action = ":make"; + mode = "n"; + } + { + # previous quickfix item + key = ""; + action = "cprevzz"; + mode = "n"; + } + { + # next quickfix item + key = ""; + action = "cnextzz"; + mode = "n"; + } + + # location list navigation + { + # previous location list item + key = "j"; + action = "lprevzz"; + mode = "n"; + } + { + # next location list item + key = "k"; + action = "lnextzz"; + mode = "n"; + } + + # disabling keys + { + # disable the 'Q' key + key = "Q"; + action = ""; + mode = "n"; + } + + # text selection + { + # select whole buffer + key = ""; + action = "ggVG"; + mode = "n"; + } + + # window operations + { + # focus next window + key = ""; + action = ":wincmd W"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # focus previous window + key = ""; + action = ":wincmd w"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # window size adjustments + { + # increase window width + key = ""; + action = ":vertical resize +5"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # decrease window width + key = ""; + action = ":vertical resize -5"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # window closing and opening + { + # close current window + key = "c"; + action = ":q"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # new vertical split at $HOME + key = "n"; + action = ":vsp $HOME"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # window split orientation toggling + { + # toggle split orientation + key = "t"; + action = ":wincmd T"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # spell checking + { + # toggle spell checking + key = "ss"; + action = ":setlocal spell!"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # switch to english spell checking + key = "se"; + action = ":setlocal spelllang=en_us"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # switch to german spell checking + key = "sg"; + action = ":setlocal spelllang=de_20"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # move to next misspelling + key = "]s"; + action = "]szz"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # move to previous misspelling + key = "[s"; + action = "[szz"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # correction suggestions for a misspelled word + key = "z="; + action = "z="; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # adding words to the dictionary + key = "zg"; + action = "zg"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # buffer navigation + { + # next buffer + key = ""; + action = ":bnext"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # previous buffer + key = ""; + action = ":bprevious"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # close current buffer + key = "bd"; + action = ":bdelete"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + { + # apply code action + key = "ca"; + action = ":lua vim.lsp.buf.code_action()"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } +] + + + +================================================ +FILE: modules/nixos/nixvim/spellfiles.nix +================================================ +{ config, pkgs, ... }: + +let + spellDir = config.xdg.dataHome + "/nvim/site/spell"; + baseUrl = "http://ftp.de.vim.org/runtime/spell"; +in +{ + home.file = { + de-spl = { + enable = true; + source = pkgs.fetchurl { + url = baseUrl + "/de.utf-8.spl"; + sha256 = "sha256-c8cQfqM5hWzb6SHeuSpFk5xN5uucByYdobndGfaDo9E="; + }; + target = spellDir + "/de.utf8.spl"; + }; + de-sug = { + enable = true; + source = pkgs.fetchurl { + url = baseUrl + "/de.utf-8.sug"; + sha256 = "sha256-E9Ds+Shj2J72DNSopesqWhOg6Pm6jRxqvkerqFcUqUg="; + }; + target = spellDir + "/de.utf8.sug"; + }; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/cmp.nix +================================================ +{ config, lib, ... }: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.cmp; + + inherit (lib) mkDefault mkIf; +in +{ + programs.nixvim = { + plugins = { + cmp = { + enable = mkDefault true; + settings = { + autoEnableSources = mkDefault true; + experimental.ghost_text = mkDefault true; + snippet.expand = mkDefault "luasnip"; + formatting.fields = mkDefault [ + "kind" + "abbr" + "menu" + ]; + sources = [ + { name = "git"; } + { name = "nvim_lsp"; } + { + name = "buffer"; + option.get_bufnrs.__raw = "vim.api.nvim_list_bufs"; + keywordLength = 3; + } + { + name = "path"; + keywordLength = 3; + } + { name = "luasnip"; } + ]; + mapping = { + "" = "cmp.mapping.complete()"; + "" = "cmp.mapping.scroll_docs(-4)"; + "" = "cmp.mapping.close()"; + "" = "cmp.mapping.scroll_docs(4)"; + "" = "cmp.mapping.confirm({ select = true })"; + "" = "cmp.mapping(cmp.mapping.select_prev_item(), {'i', 's'})"; + "" = "cmp.mapping(cmp.mapping.select_next_item(), {'i', 's'})"; + }; + }; + }; + cmp-cmdline = mkIf plugin.enable { enable = mkDefault false; }; # autocomplete for cmdline + cmp_luasnip = mkIf plugin.enable { enable = mkDefault true; }; + luasnip = mkIf plugin.enable { enable = mkDefault true; }; + cmp-treesitter = mkIf (plugin.enable && cfg.plugins.treesitter.enable) { enable = mkDefault true; }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/default.nix +================================================ +{ lib, ... }: + +{ + imports = [ + ./cmp.nix + ./lsp.nix + ./lualine.nix + ./telescope.nix + # ./treesitter.nix # HOTFIX: does not build + ./trouble.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; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/lsp.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.lsp; + + inherit (lib) mkDefault mkIf optional; +in +{ + config = { + programs.nixvim = { + plugins = { + lsp-format = mkIf plugin.enable { enable = mkDefault true; }; + + lsp = { + enable = mkDefault true; + postConfig = ""; + keymaps = { + silent = mkDefault true; + diagnostic = mkDefault { + # Navigate in diagnostics + "k" = "goto_prev"; + "j" = "goto_next"; + }; + + lspBuf = mkDefault { + gd = "definition"; + gD = "references"; + gt = "type_definition"; + gi = "implementation"; + K = "hover"; + "" = "rename"; + }; + }; + + servers = { + bashls.enable = mkDefault true; + clangd.enable = mkDefault true; + cssls.enable = mkDefault true; + dockerls.enable = mkDefault true; + gopls.enable = mkDefault true; + html.enable = mkDefault true; + jsonls.enable = mkDefault true; + nixd.enable = mkDefault true; + pyright.enable = mkDefault true; + rust_analyzer = { + enable = mkDefault true; + installCargo = mkDefault true; + installRustc = mkDefault true; + settings.rustfmt.overrideCommand = mkDefault [ + "${pkgs.rustfmt}/bin/rustfmt --edition 2021" # --config tab_spaces=2" + ]; + }; + texlab.enable = mkDefault true; + vhdl_ls.enable = mkDefault true; + yamlls.enable = mkDefault true; + }; + }; + }; + }; + + home.packages = optional (cfg.enable && plugin.servers.nixd.enable) pkgs.nixfmt; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/lualine.nix +================================================ +{ config, lib, ... }: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.lualine; + + inherit (lib) mkDefault; +in +{ + config = { + programs.nixvim = { + plugins.lualine = { + enable = mkDefault true; + settings.options.icons_enabled = mkDefault false; + }; + }; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/telescope.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.telescope; + + inherit (lib) mkDefault optionals; +in +{ + config = { + programs.nixvim = { + plugins.telescope = { + enable = mkDefault true; + extensions = { + file-browser.enable = mkDefault true; + fzf-native.enable = mkDefault true; + live-grep-args.enable = mkDefault true; + manix.enable = mkDefault true; + }; + keymaps = mkDefault { + "" = "file_browser"; + "" = "git_files"; + "bl" = "buffers"; + "fd" = "diagnostics"; + "ff" = "find_files"; + "fg" = "live_grep"; + "fh" = "help_tags"; + "fm" = "man_pages"; + "fn" = "manix"; + "fo" = "oldfiles"; + "fb" = "file_browser"; + }; + }; + keymaps = optionals plugin.enable [ + { + key = ""; + action = ":lua require('telescope').extensions.live_grep_args.live_grep_args()"; + mode = "n"; + } + ]; + }; + + home.packages = optionals plugin.enable [ + pkgs.ripgrep # for "live_grep" + ]; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/treesitter.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.treesitter; + + cc = "${pkgs.gcc}/bin/gcc"; + + inherit (lib) mkDefault mkIf; +in +{ + config = { + programs.nixvim = { + plugins.treesitter = { + enable = mkDefault true; + nixvimInjections = mkDefault true; + settings = { + folding.enable = mkDefault true; + highlight.enable = mkDefault true; + indent.enable = mkDefault true; + }; + }; + plugins.treesitter-context = mkIf plugin.enable { enable = mkDefault true; }; + plugins.treesitter-textobjects = mkIf plugin.enable { enable = mkDefault true; }; + }; + + # Fix for: ERROR `cc` executable not found. + home.sessionVariables = mkIf plugin.enable { + CC = mkDefault cc; + }; + + # Fix for: WARNING `tree-sitter` executable not found + home.packages = mkIf plugin.enable [ + plugin.package + ]; + }; +} + + + +================================================ +FILE: modules/nixos/nixvim/plugins/trouble.nix +================================================ +{ config, lib, ... }: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.trouble; + + inherit (lib) mkDefault mkIf; +in +{ + config = { + programs.nixvim = { + plugins.trouble = { + enable = mkDefault true; + }; + keymaps = mkIf plugin.enable [ + { + mode = "n"; + key = "xq"; + action = "Trouble qflist toggle"; + options = { + desc = "Trouble quifick toggle"; + }; + } + { + mode = "n"; + key = "xl"; + action = "Trouble loclist toggle"; + options = { + desc = "Trouble loclist toggle"; + }; + } + { + mode = "n"; + key = "xx"; + action = "Trouble diagnostics toggle"; + options = { + desc = "Trouble diagnostics toggle"; + }; + } + ]; + }; + }; +} + + + +================================================ +FILE: modules/nixos/normalUsers/default.nix +================================================ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.normalUsers; + + inherit (lib) + attrNames + genAttrs + mkOption + types + ; +in +{ + options.normalUsers = mkOption { + type = types.attrsOf ( + types.submodule { + options = { + extraGroups = mkOption { + type = (types.listOf types.str); + default = [ ]; + description = "Extra groups for the user"; + example = [ "wheel" ]; + }; + shell = mkOption { + type = types.path; + default = pkgs.zsh; + description = "Shell for the user"; + }; + initialPassword = mkOption { + type = types.str; + default = "changeme"; + description = "Initial password for the user"; + }; + sshKeyFiles = mkOption { + type = (types.listOf types.path); + default = [ ]; + description = "SSH key files for the user"; + example = [ "/path/to/id_rsa.pub" ]; + }; + }; + } + ); + default = { }; + description = "Users to create. The usernames are the attribute names."; + }; + + config = { + # Create user groups + users.groups = genAttrs (attrNames cfg) (userName: { + name = userName; + }); + + # Create users + users.users = genAttrs (attrNames cfg) (userName: { + name = userName; + inherit (cfg.${userName}) extraGroups shell initialPassword; + + isNormalUser = true; + group = "${userName}"; + home = "/home/${userName}"; + openssh.authorizedKeys.keyFiles = cfg.${userName}.sshKeyFiles; + }); + }; +} + + + +================================================ +FILE: modules/nixos/openssh/default.nix +================================================ +{ lib, ... }: + +let + inherit (lib) mkDefault; +in +{ + services.openssh = { + enable = mkDefault true; + ports = mkDefault [ 2299 ]; + openFirewall = mkDefault true; + settings = { + PermitRootLogin = mkDefault "no"; + PasswordAuthentication = mkDefault false; + }; + }; +} + + + +================================================ +FILE: modules/nixos/sops/default.nix +================================================ +{ + inputs, + config, + lib, + pkgs, + ... +}: + +let + secrets = "${toString inputs.self}/hosts/${config.networking.hostName}/secrets/secrets.yaml"; +in +{ + imports = [ inputs.sops-nix.nixosModules.sops ]; + + environment.systemPackages = with pkgs; [ + age + sops + ]; + + sops.defaultSopsFile = lib.mkIf (builtins.pathExists secrets) (lib.mkDefault secrets); +} + + + +================================================ +FILE: modules/nixos/tailscale/default.nix +================================================ +{ config, lib, ... }: + +let + cfg = config.services.tailscale; + + inherit (lib) + mkIf + mkOption + optional + types + ; +in +{ + options.services.tailscale = { + loginServer = mkOption { + type = types.str; + description = "The Tailscale login server to use."; + }; + enableSSH = mkOption { + type = types.bool; + default = false; + description = "Enable Tailscale SSH functionality."; + }; + acceptDNS = mkOption { + type = types.bool; + default = true; + description = "Enable Tailscale's MagicDNS and custom DNS configuration."; + }; + }; + + config = mkIf cfg.enable { + services.tailscale = { + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + extraSetFlags = optional cfg.enableSSH "--ssh" ++ optional cfg.acceptDNS "--accept-dns"; + extraUpFlags = [ + "--login-server=${cfg.loginServer}" + ] + ++ optional cfg.enableSSH "--ssh" + ++ optional cfg.acceptDNS "--accept-dns"; + }; + + environment.shellAliases = { + ts = "${cfg.package}/bin/tailscale"; + }; + + networking.firewall.trustedInterfaces = [ cfg.interfaceName ]; + + sops.secrets."tailscale/auth-key" = { }; + }; +} + + + +================================================ +FILE: overlays/default.nix +================================================ +{ inputs, ... }: + +{ + # packages in `pkgs/` accessible through 'pkgs.local' + local-packages = final: prev: { local = import ../pkgs { pkgs = final; }; }; + + # https://nixos.wiki/wiki/Overlays + modifications = + final: prev: + let + files = [ + ]; + imports = builtins.map (f: import f final prev) files; + in + builtins.foldl' (a: b: a // b) { } imports; + + # old-stable nixpkgs accessible through 'pkgs.old-stable' + old-stable-packages = final: prev: { + old-stable = import inputs.nixpkgs-old-stable { + inherit (final) system; + inherit (prev) config; + }; + }; + + # unstable nixpkgs accessible through 'pkgs.unstable' + unstable-packages = final: prev: { + unstable = import inputs.nixpkgs-unstable { + inherit (final) system; + inherit (prev) config; + }; + }; +} + + + +================================================ +FILE: pkgs/default.nix +================================================ +{ + pkgs ? import , + ... +}: + +{ + # example = pkgs.callPackage ./example { }; +} + + + +================================================ +FILE: scripts/install.sh +================================================ +#!/usr/bin/env bash + +# NixOS install script + + +### VARIABLES ### + +ASK_VERIFICATION=1 # Default to ask for verification +CONFIG_DIR="/tmp/nixos" # Directory to copy flake to / clone flake into +GIT_BRANCH="master" # Default Git branch +GIT_REPO="" # Git repository URL +HOSTNAME="" # Hostname +MNT="/mnt" # root mount point +SEPARATOR="________________________________________" # line separator + +### FUNCTIONS ### + +# Function to display help information +Show_help() { + echo "Usage: $0 [-r REPO] [-n HOSTNAME] [-b BRANCH] [-y] [-h]" + echo + echo "Options:" + echo " -r, --repo REPO Your NixOS configuration Git repository URL" + echo " -n, --hostname HOSTNAME Specify the hostname for the NixOS configuration" + echo " -b, --branch BRANCH Specify the Git branch to use (default: $GIT_BRANCH)" + echo " -y, --yes Do not ask for user verification before proceeding" + echo " -h, --help Show this help message and exit" +} + +# Function to format, partition, and mount disks for $HOSTNAME using disko +Run_disko() { + echo "$SEPARATOR" + echo "Running disko..." + nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode disko "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.nix +} + +# Function to format, partition, and mount disks for $HOSTNAME using a partitioning script +Run_script() { + echo "$SEPARATOR" + echo "Running partitioning script..." + bash "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.sh +} + +# Function to check mount points and partitioning +Check_partitioning() { + echo "$SEPARATOR" + echo "Printing mount points and partitioning..." + mount | grep "$MNT" + lsblk -f + [[ "$ASK_VERIFICATION" == 1 ]] && read -rp "Verify the mount points and partitioning. Press Ctrl+c to cancel or Enter to continue..." +} + +# Function to generate hardware configuration +Generate_hardware_config() { + [[ "$ASK_VERIFICATION" == 1 ]] && read -rp "No hardware configuration found. Press Ctrl+c to cancel or Enter to generate one..." + + echo "$SEPARATOR" + echo "Generating hardware configuration..." + nixos-generate-config --root "$MNT" --show-hardware-config > "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix + + # Check if hardware configuration has been generated + if [[ ! -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix ]]; then + echo "Error: Hardware configuration cannot be generated." + exit 1 + fi + + # Add configuration to git + # TODO: get rid of cd + cd "$CONFIG_DIR"/hosts/"$HOSTNAME" || exit 1 + git add "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix + cd || exit 1 + + echo "Hardware configuration generated successfully." +}; + +# Function to install configuration for $HOSTNAME +Install() { + # Check if hardware configuration exists + [[ ! -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix ]] && Generate_hardware_config + + echo "$SEPARATOR" + echo "Installing NixOS..." + nixos-install --root "$MNT" --no-root-password --flake "$CONFIG_DIR"#"$HOSTNAME" && echo "You can reboot the system now." +} + +### PARSE ARGUMENTS ### + +while [[ "$#" -gt 0 ]]; do + case $1 in + -r|--repo) GIT_REPO="$2"; shift ;; + -b|--branch) GIT_BRANCH="$2"; shift ;; + -y|--yes) ASK_VERIFICATION=0 ;; + -h|--help) Show_help; exit 0 ;; + -n|--hostname) HOSTNAME="$2"; shift ;; + *) echo "Unknown option: $1"; Show_help; exit 1 ;; + esac + shift +done + +### PREREQUISITES ### + +echo "$SEPARATOR" +mkdir -p "$CONFIG_DIR" + +# Clone NixOS configuration from $GIT_REPO if provided +if [[ ! -z "$GIT_REPO" ]]; then + # Install git if not already installed + if ! command -v git &> /dev/null; then + echo "Git is not installed. Installing..." + nix-env -iA nixos.git + fi + + # Clone Git repo if directory is empty + if [[ -z "$(ls -A "$CONFIG_DIR" 2>/dev/null)" ]]; then + echo "Cloning NixOS configuration repo..." + git clone --depth 1 -b "$GIT_BRANCH" "$GIT_REPO" "$CONFIG_DIR" + + # Check if git repository has been cloned + if [[ ! -d "$CONFIG_DIR"/.git ]]; then + echo "Error: Git repository could not be cloned." + exit 1 + fi + else + echo "$CONFIG_DIR is not empty. Skip cloning $GIT_REPO." + fi +fi + +if [[ ! -f "$CONFIG_DIR"/flake.nix ]]; then + echo "Error: $CONFIG_DIR does not contain 'flake.nix'." + exit 1 +fi + +### CHOOSE CONFIG ### + +# If hostname is not provided via options, prompt the user +if [[ -z "$HOSTNAME" ]]; then + # Get list of available hostnames + HOSTNAMES=$(ls "$CONFIG_DIR"/hosts) + + echo "$SEPARATOR" + echo "Please choose a hostname to install its NixOS configuration." + echo "$HOSTNAMES" + read -rp "Enter hostname: " HOSTNAME + + # Check if hostname is empty + if [[ -z "$HOSTNAME" ]]; then + echo "Error: Hostname cannot be empty." + exit 1 + fi +fi + +### INSTALLATION ### + +# Check if NixOS configuration exists +if [[ -d "$CONFIG_DIR"/hosts/"$HOSTNAME" ]]; then + + # Check for existing disko configuration + if [[ -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.nix ]]; then + Run_disko || ( echo "Error: disko failed." && exit 1 ) + # Check for partitioning script + elif [[ -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.sh ]]; then + Run_script || ( echo "Error: Partitioning script failed." && exit 1 ) + else + echo "Error: No disko configuration (disks.nix) or partitioning script (disks.sh) found for host '$HOSTNAME'." + exit 1 + fi + + Check_partitioning + Install || ( echo "Error: Installation failed." && exit 1 ) +else + echo "Error: Configuration for host '$HOSTNAME' does not exist." + exit 1 +fi + + + +================================================ +FILE: templates/generic-server/boot.nix +================================================ +{ + boot = { + loader = { + grub.enable = false; + generic-extlinux-compatible.enable = true; + }; + }; +} + + + +================================================ +FILE: templates/generic-server/default.nix +================================================ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} + + + +================================================ +FILE: templates/generic-server/disks.sh +================================================ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL + + + +================================================ +FILE: templates/generic-server/flake.nix +================================================ +{ + description = "A generic x86_64 server client template"; + path = ./.; +} + + + +================================================ +FILE: templates/generic-server/hardware.nix +================================================ +{ pkgs, lib, ... }: + +{ + boot = { + kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; + initrd.availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ "noatime" ]; + }; + }; + + nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; + hardware.enableRedistributableFirmware = true; +} + + + +================================================ +FILE: templates/generic-server/networking.nix +================================================ +{ + networking.hostName = "cryodev-pi"; + networking.domain = "cryodev.xyz"; +} + + + +================================================ +FILE: templates/generic-server/packages.nix +================================================ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} + + + +================================================ +FILE: templates/generic-server/users.nix +================================================ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ../../users/cryotherm + ]; +} + + + +================================================ +FILE: templates/generic-server/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-server.git"; + branches.main.name = "main"; + } + ]; + }; +} + + + +================================================ +FILE: templates/generic-server/services/default.nix +================================================ +{ + imports = [ + ./nginx.nix + ./openssh.nix + ./tailscale.nix + ./netdata.nix + ./comin.nix + ]; +} + + + +================================================ +FILE: templates/generic-server/services/netdata.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + config = { + stream = { + enabled = "yes"; + destination = "${constants.hosts.cryodev-main.ip}:${toString constants.services.netdata.port}"; + "api key" = config.sops.placeholder."netdata/stream/child-uuid"; + }; + }; + }; + + # Make sure sops is enabled/imported for this host to handle the secret + imports = [ outputs.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + secrets."netdata/stream/child-uuid" = { + owner = "netdata"; + group = "netdata"; + }; + }; +} + + + +================================================ +FILE: templates/generic-server/services/nginx.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; + openFirewall = true; + }; +} + + + +================================================ +FILE: templates/generic-server/services/openssh.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} + + + +================================================ +FILE: templates/generic-server/services/tailscale.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + + # Auth key for automated enrollment + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + }; + + sops.secrets."tailscale/auth-key" = { }; +} + + + +================================================ +FILE: templates/raspberry-pi/boot.nix +================================================ +{ + boot = { + loader = { + grub.enable = false; + generic-extlinux-compatible.enable = true; + }; + }; +} + + + +================================================ +FILE: templates/raspberry-pi/default.nix +================================================ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} + + + +================================================ +FILE: templates/raspberry-pi/disks.sh +================================================ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL + + + +================================================ +FILE: templates/raspberry-pi/flake.nix +================================================ +{ + description = "A Raspberry Pi 4 client template"; + path = ./.; +} + + + +================================================ +FILE: templates/raspberry-pi/hardware.nix +================================================ +{ pkgs, lib, ... }: + +{ + boot = { + kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; + initrd.availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ "noatime" ]; + }; + }; + + nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; + hardware.enableRedistributableFirmware = true; +} + + + +================================================ +FILE: templates/raspberry-pi/networking.nix +================================================ +{ + networking.hostName = "cryodev-pi"; + networking.domain = "cryodev.xyz"; +} + + + +================================================ +FILE: templates/raspberry-pi/packages.nix +================================================ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} + + + +================================================ +FILE: templates/raspberry-pi/users.nix +================================================ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ../../users/cryotherm + ]; +} + + + +================================================ +FILE: templates/raspberry-pi/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-server.git"; + branches.main.name = "main"; + } + ]; + }; +} + + + +================================================ +FILE: templates/raspberry-pi/services/default.nix +================================================ +{ + imports = [ + ./nginx.nix + ./openssh.nix + ./tailscale.nix + ./netdata.nix + ./comin.nix + ]; +} + + + +================================================ +FILE: templates/raspberry-pi/services/netdata.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + config = { + stream = { + enabled = "yes"; + destination = "${constants.hosts.cryodev-main.ip}:${toString constants.services.netdata.port}"; + "api key" = config.sops.placeholder."netdata/stream/child-uuid"; + }; + }; + }; + + # Make sure sops is enabled/imported for this host to handle the secret + imports = [ outputs.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + secrets."netdata/stream/child-uuid" = { + owner = "netdata"; + group = "netdata"; + }; + }; +} + + + +================================================ +FILE: templates/raspberry-pi/services/nginx.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; + openFirewall = true; + }; +} + + + +================================================ +FILE: templates/raspberry-pi/services/openssh.nix +================================================ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} + + + +================================================ +FILE: templates/raspberry-pi/services/tailscale.nix +================================================ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + + # Auth key for automated enrollment + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + }; + + sops.secrets."tailscale/auth-key" = { }; +} + + + +================================================ +FILE: users/cryotherm/default.nix +================================================ +{ + normalUsers.cryotherm = { + extraGroups = [ ]; + # No sshKeyFiles, so password login only (if allowed) or local access + sshKeyFiles = [ ]; + }; +} + + + +================================================ +FILE: users/steffen/default.nix +================================================ +{ outputs, ... }: + +{ + normalUsers.steffen = { + extraGroups = [ + "wheel" + ]; + sshKeyFiles = [ ./pubkeys/X670E.pub ]; + }; +} + + + +================================================ +FILE: users/steffen/pubkeys/X670E.pub +================================================ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKNTpsF9Z313gWHiHi4SvjeXI4Mh80mtq0bR0AjsZr/SnPsXEiM8/ODbQNJ806qHLFSA4uA4vaevdZIJkpDqRIQviW7zHGp/weRh2+2ynH8RyFqJvsWIqWn8G5wXPYcRZ6eFjcqKraAQC46ITER4+NPgdC6Cr+dsHWyIroBep4m3EGhSLYNRaMYoKZ5aqD2jJLBolokVfseF06Y7tQ3QSwUioXgiodBdZ9hgXc/5AJdsXSxJMHmRArqbHwbWI0fhwkX+0jiUpOMXMGsJZx5G20X70mQpJu+UnQsGcw+ylQw6ZYtFmzNcYmOS//91DTzraHprnrENyb+pYV2UUZhKxjdkexpSBkkPoVEzMcw9+LCg4e/jsZ+urlRhdTPWW0/AaWJx3UJc1pHHu5UpIvQKfMdt9dZbgG7oYYE1JeCoTvtQKiBcdc54cmJuvwshaAkfN92tYGvj/L1Jeb06M34dycdCXGDGMIofMsZOsnDcHuY1CT82NlRjXmatAUOaO0rCbVNPluNmu4gmWhclQmhoUEmojBGaIXrcRuxrIJYZpWubQdBUCZiJFBJzEb2qnT0nFSe0Gu0tPOYdD/jcUVgYPRWggxQV6hssSlgERTJdzC5PhBnSe8Xi8W/rMgZA8+YBIKBJpJjF5HZTJ67EBZmNS3HWaZNIUmRXcgsONr41RCrw== steffen@X670E + + + +================================================ +FILE: .forgejo/workflows/build-hosts.yml +================================================ +name: Build hosts + +on: + pull_request: + branches: + - main + +jobs: + build-hosts: + 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: 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 + + + +================================================ +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/flake.nix b/flake.nix new file mode 100644 index 0000000..9eca4c5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,135 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs-old-stable.url = "github:nixos/nixpkgs/nixos-25.05"; + + sops-nix.url = "github:Mic92/sops-nix"; + sops-nix.inputs.nixpkgs.follows = "nixpkgs"; + + nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver"; + nixos-mailserver.inputs.nixpkgs.follows = "nixpkgs"; + + headplane.url = "github:yrd/headplane-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"; + + git-hooks.url = "github:cachix/git-hooks.nix"; + git-hooks.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = + { + self, + nixpkgs, + ... + }@inputs: + let + inherit (self) outputs; + + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + lib = nixpkgs.lib; + constants = import ./constants.nix; + + mkNixosConfiguration = + system: modules: + nixpkgs.lib.nixosSystem { + inherit system modules; + specialArgs = { + inherit + inputs + outputs + lib + constants + ; + }; + }; + in + { + packages = forAllSystems (system: import ./pkgs nixpkgs.legacyPackages.${system}); + + overlays = import ./overlays { inherit inputs; }; + + nixosModules = import ./modules/nixos; + + nixosConfigurations = { + cryodev-main = mkNixosConfiguration "x86_64-linux" [ ./hosts/cryodev-main ]; + cryodev-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/cryodev-pi ]; + }; + + templates = { + raspberry-pi = { + path = ./templates/raspberry-pi; + description = "Raspberry Pi 4 Client"; + }; + generic-server = { + path = ./templates/generic-server; + description = "Generic x86_64 Customer Server"; + }; + }; + + formatter = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + config = self.checks.${system}.pre-commit-check.config; + inherit (config) package configFile; + script = '' + ${pkgs.lib.getExe package} run --all-files --config ${configFile} + ''; + in + 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 + pkgs = nixpkgs.legacyPackages.${system}; + flakePkgs = self.packages.${system}; + overlaidPkgs = import nixpkgs { + 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 { + src = ./.; + hooks = { + nixfmt.enable = true; + }; + }; + build-packages = pkgs.linkFarm "flake-packages-${system}" flakePkgs; + build-overlays = pkgs.linkFarm "flake-overlays-${system}" { + # package = overlaidPkgs.package; + }; + } + // deployChecks + ); + }; +} diff --git a/hosts/cryodev-main/boot.nix b/hosts/cryodev-main/boot.nix new file mode 100644 index 0000000..53a9686 --- /dev/null +++ b/hosts/cryodev-main/boot.nix @@ -0,0 +1,7 @@ +{ + boot.loader.systemd-boot = { + enable = true; + configurationLimit = 10; + }; + boot.loader.efi.canTouchEfiVariables = true; +} diff --git a/hosts/cryodev-main/default.nix b/hosts/cryodev-main/default.nix new file mode 100644 index 0000000..482c6a8 --- /dev/null +++ b/hosts/cryodev-main/default.nix @@ -0,0 +1,21 @@ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} diff --git a/hosts/cryodev-main/disks.sh b/hosts/cryodev-main/disks.sh new file mode 100644 index 0000000..5e06bc8 --- /dev/null +++ b/hosts/cryodev-main/disks.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL diff --git a/hosts/cryodev-main/hardware.nix b/hosts/cryodev-main/hardware.nix new file mode 100644 index 0000000..2bfd7b4 --- /dev/null +++ b/hosts/cryodev-main/hardware.nix @@ -0,0 +1,48 @@ +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ + "ahci" + "nvme" + "sd_mod" + "sdhci_pci" + "sr_mod" + "usb_storage" + "virtio_pci" + "virtio_scsi" + "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" + ]; + }; + + swapDevices = [ { device = "/dev/disk/by-label/SWAP"; } ]; + + networking.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/hosts/cryodev-main/networking.nix b/hosts/cryodev-main/networking.nix new file mode 100644 index 0000000..03d5c10 --- /dev/null +++ b/hosts/cryodev-main/networking.nix @@ -0,0 +1,4 @@ +{ + networking.hostName = "cryodev-main"; + networking.domain = "cryodev.xyz"; +} diff --git a/hosts/cryodev-main/packages.nix b/hosts/cryodev-main/packages.nix new file mode 100644 index 0000000..96cc691 --- /dev/null +++ b/hosts/cryodev-main/packages.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} diff --git a/hosts/cryodev-main/services/default.nix b/hosts/cryodev-main/services/default.nix new file mode 100644 index 0000000..0bd3c67 --- /dev/null +++ b/hosts/cryodev-main/services/default.nix @@ -0,0 +1,13 @@ +{ + imports = [ + ./forgejo.nix + ./headplane.nix + ./headscale.nix + ./mailserver.nix + ./netdata.nix + ./nginx.nix + ./openssh.nix + ./sops.nix + ./tailscale.nix + ]; +} diff --git a/hosts/cryodev-main/services/forgejo.nix b/hosts/cryodev-main/services/forgejo.nix new file mode 100644 index 0000000..ae676c6 --- /dev/null +++ b/hosts/cryodev-main/services/forgejo.nix @@ -0,0 +1,51 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.forgejo + outputs.nixosModules.forgejo-runner + ]; + + services.forgejo = { + enable = true; + settings = { + server = { + DOMAIN = constants.services.forgejo.fqdn; + ROOT_URL = "https://${constants.services.forgejo.fqdn}/"; + HTTP_PORT = constants.services.forgejo.port; + }; + service = { + DISABLE_REGISTRATION = true; + }; + mailer = { + ENABLED = true; + FROM = "forgejo@${constants.domain}"; + SMTP_ADDR = constants.services.mail.fqdn; + SMTP_PORT = constants.services.mail.port; + USER = "forgejo@${constants.domain}"; + }; + }; + sops = true; # Enable sops integration for secrets + }; + + services.forgejo-runner = { + enable = true; + url = "https://${constants.services.forgejo.fqdn}"; + # Token needs to be set up via sops/secrets + sops = true; + }; + + services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.forgejo.port}"; + }; + }; +} diff --git a/hosts/cryodev-main/services/headplane.nix b/hosts/cryodev-main/services/headplane.nix new file mode 100644 index 0000000..1b28198 --- /dev/null +++ b/hosts/cryodev-main/services/headplane.nix @@ -0,0 +1,35 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.headplane + ]; + + services.headplane = { + enable = true; + port = constants.services.headplane.port; + headscale = { + url = "http://127.0.0.1:${toString constants.services.headscale.port}"; + public_url = "https://${constants.services.headscale.fqdn}"; + }; + # Secrets for headplane need to be configured via sops + sops.secrets = { + "headplane/cookie_secret" = { }; + "headplane/agent_pre_authkey" = { }; + }; + }; + + services.nginx.virtualHosts."${constants.services.headplane.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.headplane.port}"; + }; + }; +} diff --git a/hosts/cryodev-main/services/headscale.nix b/hosts/cryodev-main/services/headscale.nix new file mode 100644 index 0000000..e299295 --- /dev/null +++ b/hosts/cryodev-main/services/headscale.nix @@ -0,0 +1,32 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.headscale + ]; + + services.headscale = { + enable = true; + address = "127.0.0.1"; + port = constants.services.headscale.port; + settings = { + server_url = "https://${constants.services.headscale.fqdn}"; + dns_config.base_domain = constants.domain; + }; + }; + + services.nginx.virtualHosts."${constants.services.headscale.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.headscale.port}"; + proxyWebsockets = true; + }; + }; +} diff --git a/hosts/cryodev-main/services/mailserver.nix b/hosts/cryodev-main/services/mailserver.nix new file mode 100644 index 0000000..42bead3 --- /dev/null +++ b/hosts/cryodev-main/services/mailserver.nix @@ -0,0 +1,27 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.mailserver + ]; + + mailserver = { + enable = true; + fqdn = constants.services.mail.fqdn; + domains = [ constants.domain ]; + accounts = { + forgejo = { }; + admin = { + aliases = [ "postmaster" ]; + }; + }; + certificateScheme = "acme-nginx"; + sops = true; + }; +} diff --git a/hosts/cryodev-main/services/netdata.nix b/hosts/cryodev-main/services/netdata.nix new file mode 100644 index 0000000..6389465 --- /dev/null +++ b/hosts/cryodev-main/services/netdata.nix @@ -0,0 +1,34 @@ +{ + config, + pkgs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + package = pkgs.netdata.override { + withCloudUi = true; + }; + config = { + global = { + "debug log" = "syslog"; + "access log" = "syslog"; + "error log" = "syslog"; + "bind to" = "127.0.0.1"; + }; + }; + }; + + services.nginx.virtualHosts."${constants.services.netdata.fqdn}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString constants.services.netdata.port}"; + proxyWebsockets = true; + # Basic Auth can be added here if desired, or restrict by IP + # extraConfig = "allow 100.64.0.0/10; deny all;"; # Example for Tailscale only + }; + }; +} diff --git a/hosts/cryodev-main/services/nginx.nix b/hosts/cryodev-main/services/nginx.nix new file mode 100644 index 0000000..8616c61 --- /dev/null +++ b/hosts/cryodev-main/services/nginx.nix @@ -0,0 +1,22 @@ +{ + inputs, + outputs, + lib, + config, + pkgs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; # Force SSL for all vhosts by default if configured to use this option + openFirewall = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + }; +} diff --git a/hosts/cryodev-main/services/openssh.nix b/hosts/cryodev-main/services/openssh.nix new file mode 100644 index 0000000..f71c084 --- /dev/null +++ b/hosts/cryodev-main/services/openssh.nix @@ -0,0 +1,12 @@ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} diff --git a/hosts/cryodev-main/services/sops.nix b/hosts/cryodev-main/services/sops.nix new file mode 100644 index 0000000..8df48e1 --- /dev/null +++ b/hosts/cryodev-main/services/sops.nix @@ -0,0 +1,21 @@ +{ + config, + pkgs, + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.sops + ]; + + 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" = { }; + }; + }; +} diff --git a/hosts/cryodev-main/services/tailscale.nix b/hosts/cryodev-main/services/tailscale.nix new file mode 100644 index 0000000..c0c3793 --- /dev/null +++ b/hosts/cryodev-main/services/tailscale.nix @@ -0,0 +1,23 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + }; +} diff --git a/hosts/cryodev-main/users.nix b/hosts/cryodev-main/users.nix new file mode 100644 index 0000000..a198c5a --- /dev/null +++ b/hosts/cryodev-main/users.nix @@ -0,0 +1,8 @@ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ]; +} diff --git a/hosts/cryodev-pi/boot.nix b/hosts/cryodev-pi/boot.nix new file mode 100644 index 0000000..a459d88 --- /dev/null +++ b/hosts/cryodev-pi/boot.nix @@ -0,0 +1,8 @@ +{ + boot = { + loader = { + grub.enable = false; + generic-extlinux-compatible.enable = true; + }; + }; +} diff --git a/hosts/cryodev-pi/default.nix b/hosts/cryodev-pi/default.nix new file mode 100644 index 0000000..482c6a8 --- /dev/null +++ b/hosts/cryodev-pi/default.nix @@ -0,0 +1,21 @@ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} diff --git a/hosts/cryodev-pi/disks.sh b/hosts/cryodev-pi/disks.sh new file mode 100644 index 0000000..5e06bc8 --- /dev/null +++ b/hosts/cryodev-pi/disks.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL diff --git a/hosts/cryodev-pi/hardware.nix b/hosts/cryodev-pi/hardware.nix new file mode 100644 index 0000000..a0d751a --- /dev/null +++ b/hosts/cryodev-pi/hardware.nix @@ -0,0 +1,23 @@ +{ pkgs, lib, ... }: + +{ + boot = { + kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; + initrd.availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ "noatime" ]; + }; + }; + + nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; + hardware.enableRedistributableFirmware = true; +} diff --git a/hosts/cryodev-pi/networking.nix b/hosts/cryodev-pi/networking.nix new file mode 100644 index 0000000..c00bcf5 --- /dev/null +++ b/hosts/cryodev-pi/networking.nix @@ -0,0 +1,4 @@ +{ + networking.hostName = "cryodev-pi"; + networking.domain = "cryodev.xyz"; +} diff --git a/hosts/cryodev-pi/packages.nix b/hosts/cryodev-pi/packages.nix new file mode 100644 index 0000000..96cc691 --- /dev/null +++ b/hosts/cryodev-pi/packages.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} diff --git a/hosts/cryodev-pi/services/comin.nix b/hosts/cryodev-pi/services/comin.nix new file mode 100644 index 0000000..2317b06 --- /dev/null +++ b/hosts/cryodev-pi/services/comin.nix @@ -0,0 +1,24 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.comin + ]; + + services.comin = { + enable = true; + remotes = [ + { + name = "origin"; + url = "https://${constants.services.forgejo.fqdn}/steffen/cryodev-server.git"; + branches.main.name = "main"; + } + ]; + }; +} diff --git a/hosts/cryodev-pi/services/default.nix b/hosts/cryodev-pi/services/default.nix new file mode 100644 index 0000000..e4c52d5 --- /dev/null +++ b/hosts/cryodev-pi/services/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./nginx.nix + ./openssh.nix + ./tailscale.nix + ./netdata.nix + ./comin.nix + ]; +} diff --git a/hosts/cryodev-pi/services/netdata.nix b/hosts/cryodev-pi/services/netdata.nix new file mode 100644 index 0000000..9ff9a6c --- /dev/null +++ b/hosts/cryodev-pi/services/netdata.nix @@ -0,0 +1,31 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + config = { + stream = { + enabled = "yes"; + destination = "${constants.hosts.cryodev-main.ip}:${toString constants.services.netdata.port}"; + "api key" = config.sops.placeholder."netdata/stream/child-uuid"; + }; + }; + }; + + # Make sure sops is enabled/imported for this host to handle the secret + imports = [ outputs.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + secrets."netdata/stream/child-uuid" = { + owner = "netdata"; + group = "netdata"; + }; + }; +} diff --git a/hosts/cryodev-pi/services/nginx.nix b/hosts/cryodev-pi/services/nginx.nix new file mode 100644 index 0000000..00734e5 --- /dev/null +++ b/hosts/cryodev-pi/services/nginx.nix @@ -0,0 +1,14 @@ +{ + outputs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; + openFirewall = true; + }; +} diff --git a/hosts/cryodev-pi/services/openssh.nix b/hosts/cryodev-pi/services/openssh.nix new file mode 100644 index 0000000..f71c084 --- /dev/null +++ b/hosts/cryodev-pi/services/openssh.nix @@ -0,0 +1,12 @@ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} diff --git a/hosts/cryodev-pi/services/tailscale.nix b/hosts/cryodev-pi/services/tailscale.nix new file mode 100644 index 0000000..b9ab502 --- /dev/null +++ b/hosts/cryodev-pi/services/tailscale.nix @@ -0,0 +1,28 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + + # Auth key for automated enrollment + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + }; + + sops.secrets."tailscale/auth-key" = { }; +} diff --git a/hosts/cryodev-pi/users.nix b/hosts/cryodev-pi/users.nix new file mode 100644 index 0000000..3570761 --- /dev/null +++ b/hosts/cryodev-pi/users.nix @@ -0,0 +1,9 @@ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ../../users/cryotherm + ]; +} diff --git a/modules/nixos/comin/default.nix b/modules/nixos/comin/default.nix new file mode 100644 index 0000000..aac8c06 --- /dev/null +++ b/modules/nixos/comin/default.nix @@ -0,0 +1,8 @@ +{ + inputs, + ... +}: + +{ + imports = [ inputs.comin.nixosModules.comin ]; +} diff --git a/modules/nixos/common/default.nix b/modules/nixos/common/default.nix new file mode 100644 index 0000000..ab63e24 --- /dev/null +++ b/modules/nixos/common/default.nix @@ -0,0 +1,15 @@ +{ + imports = [ + ./environment.nix + ./htop.nix + ./nationalization.nix + ./networking.nix + ./nix.nix + ./sudo.nix + ./well-known.nix + ./zsh.nix + + ./shared + ./overlays.nix + ]; +} diff --git a/modules/nixos/common/environment.nix b/modules/nixos/common/environment.nix new file mode 100644 index 0000000..6921f2c --- /dev/null +++ b/modules/nixos/common/environment.nix @@ -0,0 +1,63 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) mkDefault optionals; +in +{ + environment.systemPackages = + with pkgs; + [ + cryptsetup + curl + dig + dnsutils + fzf + gptfdisk + iproute2 + jq + lm_sensors + lsof + netcat-openbsd + nettools + nixos-container + nmap + nurl + p7zip + pciutils + psmisc + rclone + rsync + tcpdump + tmux + tree + unzip + usbutils + wget + xxd + zip + + (callPackage ../../../apps/rebuild { }) + ] + ++ optionals (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) [ + pkgs.kitty.terminfo + ]; + + environment.shellAliases = { + l = "ls -lh"; + ll = "ls -lAh"; + ports = "ss -tulpn"; + publicip = "curl ifconfig.me/all"; + sudo = "sudo "; # make aliases work with `sudo` + }; + + # saves one instance of nixpkgs. + environment.ldso32 = null; + + boot.tmp.cleanOnBoot = mkDefault true; + boot.initrd.systemd.enable = mkDefault (!config.boot.swraid.enable && !config.boot.isContainer); +} diff --git a/modules/nixos/common/htop.nix b/modules/nixos/common/htop.nix new file mode 100644 index 0000000..c95c3fc --- /dev/null +++ b/modules/nixos/common/htop.nix @@ -0,0 +1,8 @@ +{ + programs.htop = { + enable = true; + settings = { + highlight_base_name = 1; + }; + }; +} diff --git a/modules/nixos/common/nationalization.nix b/modules/nixos/common/nationalization.nix new file mode 100644 index 0000000..1d35507 --- /dev/null +++ b/modules/nixos/common/nationalization.nix @@ -0,0 +1,31 @@ +{ lib, ... }: + +let + de = "de_DE.UTF-8"; + en = "en_US.UTF-8"; + + inherit (lib) mkDefault; +in +{ + i18n = { + defaultLocale = mkDefault en; + extraLocaleSettings = { + LC_ADDRESS = mkDefault de; + LC_IDENTIFICATION = mkDefault de; + LC_MEASUREMENT = mkDefault de; + LC_MONETARY = mkDefault de; + LC_NAME = mkDefault de; + LC_NUMERIC = mkDefault de; + LC_PAPER = mkDefault de; + LC_TELEPHONE = mkDefault de; + LC_TIME = mkDefault en; + }; + }; + + console = { + font = mkDefault "Lat2-Terminus16"; + keyMap = mkDefault "de"; + }; + + time.timeZone = mkDefault "Europe/Berlin"; +} diff --git a/modules/nixos/common/networking.nix b/modules/nixos/common/networking.nix new file mode 100644 index 0000000..15d1b1f --- /dev/null +++ b/modules/nixos/common/networking.nix @@ -0,0 +1,40 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) mkDefault; + inherit (lib.utils) isNotEmptyStr; +in +{ + config = { + assertions = [ + { + assertion = isNotEmptyStr config.networking.domain; + message = "synix/nixos/common: config.networking.domain cannot be empty."; + } + { + assertion = isNotEmptyStr config.networking.hostName; + message = "synix/nixos/common: config.networking.hostName cannot be empty."; + } + ]; + + networking = { + domain = mkDefault "${config.networking.hostName}.local"; + hostId = mkDefault "8425e349"; # same as NixOS install ISO and nixos-anywhere + + # NetworkManager + useDHCP = false; + networkmanager = { + enable = true; + plugins = with pkgs; [ + networkmanager-openconnect + networkmanager-openvpn + ]; + }; + }; + }; +} diff --git a/modules/nixos/common/nix.nix b/modules/nixos/common/nix.nix new file mode 100644 index 0000000..e793401 --- /dev/null +++ b/modules/nixos/common/nix.nix @@ -0,0 +1,19 @@ +{ + config, + lib, + ... +}: + +let + inherit (lib) mkDefault; +in +{ + nix = { + # use flakes + channel.enable = mkDefault false; + + # De-duplicate store paths using hardlinks except in containers + # where the store is host-managed. + optimise.automatic = mkDefault (!config.boot.isContainer); + }; +} diff --git a/modules/nixos/common/overlays.nix b/modules/nixos/common/overlays.nix new file mode 100644 index 0000000..6e4a070 --- /dev/null +++ b/modules/nixos/common/overlays.nix @@ -0,0 +1,10 @@ +{ outputs, ... }: + +{ + nixpkgs.overlays = [ + outputs.overlays.local-packages + outputs.overlays.modifications + outputs.overlays.old-stable-packages + outputs.overlays.unstable-packages + ]; +} diff --git a/modules/nixos/common/shared/default.nix b/modules/nixos/common/shared/default.nix new file mode 100644 index 0000000..3925d83 --- /dev/null +++ b/modules/nixos/common/shared/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./nix.nix + ]; +} diff --git a/modules/nixos/common/shared/nix.nix b/modules/nixos/common/shared/nix.nix new file mode 100644 index 0000000..2d0daa3 --- /dev/null +++ b/modules/nixos/common/shared/nix.nix @@ -0,0 +1,85 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) + mkDefault + optional + versionOlder + versions + ; +in +{ + nix.package = mkDefault pkgs.nix; + + # for `nix run synix#foo`, `nix build synix#bar`, etc + nix.registry = { + synix = { + from = { + id = "synix"; + type = "indirect"; + }; + to = { + owner = "sid"; + repo = "synix"; + host = "git.sid.ovh"; + type = "gitea"; + }; + }; + }; + + # fallback quickly if substituters are not available. + nix.settings.connect-timeout = mkDefault 5; + nix.settings.fallback = true; + + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ] + ++ optional ( + config.nix.package != null && versionOlder (versions.majorMinor config.nix.package.version) "2.22" + ) "repl-flake"; + + nix.settings.log-lines = mkDefault 25; + + # avoid disk full issues + nix.settings.max-free = mkDefault (3000 * 1024 * 1024); + nix.settings.min-free = mkDefault (512 * 1024 * 1024); + + # avoid copying unnecessary stuff over SSH + nix.settings.builders-use-substitutes = true; + + # workaround for https://github.com/NixOS/nix/issues/9574 + nix.settings.nix-path = config.nix.nixPath; + + nix.settings.download-buffer-size = 524288000; # 500 MiB + + # add all wheel users to the trusted-users group + nix.settings.trusted-users = [ + "@wheel" + ]; + + # binary caches + nix.settings.substituters = [ + "https://cache.nixos.org" + "https://nix-community.cachix.org" + "https://cache.garnix.io" + "https://numtide.cachix.org" + ]; + nix.settings.trusted-public-keys = [ + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" + "numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE=" + ]; + + nix.gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; + }; +} diff --git a/modules/nixos/common/sudo.nix b/modules/nixos/common/sudo.nix new file mode 100644 index 0000000..3459e93 --- /dev/null +++ b/modules/nixos/common/sudo.nix @@ -0,0 +1,26 @@ +{ config, ... }: + +{ + security.sudo = { + enable = true; + execWheelOnly = true; + extraConfig = '' + Defaults lecture = never + ''; + }; + + assertions = + let + validUsers = users: users == [ ] || users == [ "root" ]; + validGroups = groups: groups == [ ] || groups == [ "wheel" ]; + validUserGroups = builtins.all ( + r: validUsers (r.users or [ ]) && validGroups (r.groups or [ ]) + ) config.security.sudo.extraRules; + in + [ + { + assertion = config.security.sudo.execWheelOnly -> validUserGroups; + message = "Some definitions in `security.sudo.extraRules` refer to users other than 'root' or groups other than 'wheel'. Disable `config.security.sudo.execWheelOnly`, or adjust the rules."; + } + ]; +} diff --git a/modules/nixos/common/well-known.nix b/modules/nixos/common/well-known.nix new file mode 100644 index 0000000..07a938c --- /dev/null +++ b/modules/nixos/common/well-known.nix @@ -0,0 +1,17 @@ +{ + # avoid TOFU MITM + programs.ssh.knownHosts = { + "github.com".hostNames = [ "github.com" ]; + "github.com".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"; + + "gitlab.com".hostNames = [ "gitlab.com" ]; + "gitlab.com".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf"; + + "git.sr.ht".hostNames = [ "git.sr.ht" ]; + "git.sr.ht".publicKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60"; + }; + # TODO: add synix +} diff --git a/modules/nixos/common/zsh.nix b/modules/nixos/common/zsh.nix new file mode 100644 index 0000000..a0ff7fb --- /dev/null +++ b/modules/nixos/common/zsh.nix @@ -0,0 +1,26 @@ +{ + programs.zsh = { + enable = true; + syntaxHighlighting = { + enable = true; + highlighters = [ + "main" + "brackets" + "cursor" + "pattern" + ]; + patterns = { + "rm -rf" = "fg=white,bold,bg=red"; + "rm -fr" = "fg=white,bold,bg=red"; + }; + }; + autosuggestions = { + enable = true; + strategy = [ + "completion" + "history" + ]; + }; + enableLsColors = true; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix new file mode 100644 index 0000000..512cd66 --- /dev/null +++ b/modules/nixos/default.nix @@ -0,0 +1,13 @@ +{ + common = import ./common; + comin = import ./comin; + forgejo = import ./forgejo; + forgejo-runner = import ./forgejo-runner; + mailserver = import ./mailserver; + nixvim = import ./nixvim; + normalUsers = import ./normalUsers; + nginx = import ./nginx; + openssh = import ./openssh; + sops = import ./sops; + tailscale = import ./tailscale; +} diff --git a/modules/nixos/forgejo-runner/default.nix b/modules/nixos/forgejo-runner/default.nix new file mode 100644 index 0000000..2547f0e --- /dev/null +++ b/modules/nixos/forgejo-runner/default.nix @@ -0,0 +1,68 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.forgejo-runner; + + inherit (lib) + mkEnableOption + mkIf + mkOption + types + ; +in +{ + options.services.forgejo-runner = { + enable = mkEnableOption "Nix-based Forgejo Runner service"; + url = mkOption { + type = types.str; + description = "Forgejo instance URL."; + }; + tokenFile = mkOption { + type = types.path; + description = "Path to EnvironmentFile containing TOKEN=..."; + }; + }; + + config = mkIf cfg.enable { + nix.settings.trusted-users = [ "gitea-runner" ]; + + services.gitea-actions-runner = { + package = pkgs.forgejo-runner; + instances.default = { + enable = true; + name = "${config.networking.hostName}-nix"; + inherit (cfg) url tokenFile; + + labels = [ "host:host" ]; + + hostPackages = with pkgs; [ + bash + coreutils + curl + gitMinimal + gnused + nix + nodejs + openssh + deploy-rs + ]; + + settings = { + log.level = "info"; + runner = { + capacity = 1; + envs = { + NIX_CONFIG = "extra-experimental-features = nix-command flakes"; + NIX_REMOTE = "daemon"; + }; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/forgejo/default.nix b/modules/nixos/forgejo/default.nix new file mode 100644 index 0000000..6c7a65a --- /dev/null +++ b/modules/nixos/forgejo/default.nix @@ -0,0 +1,63 @@ +{ + config, + lib, + ... +}: + +let + cfg = config.services.forgejo; + + inherit (cfg) settings; + inherit (lib) + getExe + head + mkDefault + mkIf + ; +in +{ + config = mkIf cfg.enable { + services.forgejo = { + database.type = "postgres"; + lfs.enable = true; + settings = { + server = { + DOMAIN = "git.${config.networking.domain}"; + PROTOCOL = "http"; + ROOT_URL = "https://${settings.server.DOMAIN}/"; + HTTP_ADDR = "0.0.0.0"; + HTTP_PORT = 3456; + SSH_PORT = head config.services.openssh.ports; + }; + service = { + DISABLE_REGISTRATION = true; + }; + ui = { + DEFAULT_THEME = "forgejo-dark"; + }; + actions = { + ENABLED = true; + }; + mailer = { + ENABLED = mkDefault false; + SMTP_ADDR = "mail.${config.networking.domain}"; + FROM = "git@${settings.server.DOMAIN}"; + USER = "git@${settings.server.DOMAIN}"; + }; + }; + secrets = { + mailer.PASSWD = mkIf settings.mailer.ENABLED config.sops.secrets."forgejo/mail-pw".path; + }; + }; + + environment.shellAliases = { + forgejo = "sudo -u ${cfg.user} ${getExe cfg.package} --config ${cfg.stateDir}/custom/conf/app.ini"; + }; + + sops.secrets."forgejo/mail-pw" = mkIf settings.mailer.ENABLED { + owner = cfg.user; + group = cfg.group; + mode = "0400"; + }; + }; +} diff --git a/modules/nixos/headplane/default.nix b/modules/nixos/headplane/default.nix new file mode 100644 index 0000000..017c4e3 --- /dev/null +++ b/modules/nixos/headplane/default.nix @@ -0,0 +1,78 @@ +{ + inputs, + config, + lib, + ... +}: + +let + cfg = config.services.headplane; + domain = config.networking.domain; + subdomain = cfg.reverseProxy.subdomain; + fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain; + headscale = config.services.headscale; + + inherit (lib) + mkDefault + mkIf + ; + + inherit (lib.utils) + mkReverseProxyOption + mkVirtualHost + ; +in +{ + imports = [ inputs.headplane.nixosModules.headplane ]; + + options.services.headplane = { + reverseProxy = mkReverseProxyOption "Headplane" "hp"; + }; + + config = mkIf cfg.enable { + nixpkgs.overlays = [ + inputs.headplane.overlays.default + ]; + + services.headplane = { + settings = { + server = { + host = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0"); + port = mkDefault 3000; + cookie_secret_path = config.sops.secrets."headplane/cookie_secret".path; + }; + headscale = { + url = "http://127.0.0.1:${toString headscale.port}"; + public_url = headscale.settings.server_url; + config_path = "/etc/headscale/config.yaml"; + }; + integration.agent = { + enabled = mkDefault true; + pre_authkey_path = config.sops.secrets."headplane/agent_pre_authkey".path; + }; + }; + }; + + services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable { + "${fqdn}" = mkVirtualHost { + port = cfg.settings.server.port; + ssl = cfg.reverseProxy.forceSSL; + }; + }; + + sops.secrets = + let + owner = headscale.user; + group = headscale.group; + mode = "0400"; + in + { + "headplane/cookie_secret" = { + inherit owner group mode; + }; + "headplane/agent_pre_authkey" = { + inherit owner group mode; + }; + }; + }; +} diff --git a/modules/nixos/headscale/acl.hujson b/modules/nixos/headscale/acl.hujson new file mode 100644 index 0000000..e975c9e --- /dev/null +++ b/modules/nixos/headscale/acl.hujson @@ -0,0 +1,17 @@ +{ + "acls": [ + { + "action": "accept", + "src": ["*"], + "dst": ["*:*"] + } + ], + "ssh": [ + { + "action": "accept", + "src": ["autogroup:member"], + "dst": ["autogroup:member"], + "users": ["autogroup:nonroot", "root"] + } + ] +} diff --git a/modules/nixos/headscale/default.nix b/modules/nixos/headscale/default.nix new file mode 100644 index 0000000..f0cd0b0 --- /dev/null +++ b/modules/nixos/headscale/default.nix @@ -0,0 +1,105 @@ +{ + config, + lib, + ... +}: + +let + cfg = config.services.headscale; + domain = config.networking.domain; + subdomain = cfg.reverseProxy.subdomain; + fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain; + acl = "headscale/acl.hujson"; + + inherit (lib) + mkDefault + mkIf + mkOption + optional + optionals + types + ; + + inherit (lib.utils) + mkReverseProxyOption + mkUrl + mkVirtualHost + ; +in +{ + options.services.headscale = { + reverseProxy = mkReverseProxyOption "Headscale" "hs"; + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Whether to automatically open firewall ports. TCP: 80, 443; UDP: 3478."; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !cfg.settings.derp.server.enable || cfg.reverseProxy.forceSSL; + message = "cryodev/nixos/headscale: DERP requires TLS"; + } + { + assertion = fqdn != cfg.settings.dns.base_domain; + message = "cryodev/nixos/headscale: `settings.server_url` must be different from `settings.dns.base_domain`"; + } + { + assertion = !cfg.settings.dns.override_local_dns || cfg.settings.dns.nameservers.global != [ ]; + message = "cryodev/nixos/headscale: `settings.dns.nameservers.global` must be set when `settings.dns.override_local_dns` is true"; + } + ]; + + environment.etc.${acl} = { + inherit (config.services.headscale) user group; + source = ./acl.hujson; + }; + + environment.shellAliases = { + hs = "${cfg.package}/bin/headscale"; + }; + + services.headscale = { + address = mkDefault (if cfg.reverseProxy.enable then "127.0.0.1" else "0.0.0.0"); + port = mkDefault 8077; + settings = { + policy.path = "/etc/${acl}"; + database.type = "sqlite"; # postgres is highly discouraged as it is only supported for legacy reasons + server_url = mkUrl { + inherit fqdn; + ssl = with cfg.reverseProxy; enable && forceSSL; + }; + derp.server.enable = cfg.reverseProxy.forceSSL; + dns = { + magic_dns = mkDefault true; + base_domain = mkDefault "tail"; + search_domains = [ cfg.settings.dns.base_domain ]; + override_local_dns = mkDefault true; + nameservers.global = optionals cfg.settings.dns.override_local_dns [ + "1.1.1.1" + "1.0.0.1" + "2606:4700:4700::1111" + "2606:4700:4700::1001" + ]; + }; + }; + }; + + services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable { + "${fqdn}" = mkVirtualHost { + inherit (cfg) address port; + ssl = cfg.reverseProxy.forceSSL; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ + 80 + 443 + ]; + allowedUDPPorts = optional cfg.settings.derp.server.enable 3478; + }; + }; +} diff --git a/modules/nixos/mailserver/default.nix b/modules/nixos/mailserver/default.nix new file mode 100644 index 0000000..7c28972 --- /dev/null +++ b/modules/nixos/mailserver/default.nix @@ -0,0 +1,104 @@ +{ + inputs, + config, + lib, + pkgs, + ... +}: + +let + cfg = config.mailserver; + domain = config.networking.domain; + fqdn = "${cfg.subdomain}.${domain}"; + + inherit (lib) + mapAttrs' + mkDefault + mkIf + mkOption + nameValuePair + types + ; +in +{ + imports = [ inputs.nixos-mailserver.nixosModules.mailserver ]; + + options.mailserver = { + subdomain = mkOption { + type = types.str; + default = "mail"; + description = "Subdomain for rDNS"; + }; + accounts = mkOption { + type = types.attrsOf ( + types.submodule { + options = { + aliases = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "A list of aliases of this account. `@domain` will be appended automatically."; + }; + sendOnly = mkOption { + type = types.bool; + default = false; + description = "Specifies if the account should be a send-only account."; + }; + }; + } + ); + default = { }; + description = '' + This options wraps `loginAccounts`. + `loginAccounts..name` will be automatically set to `@`. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.subdomain != ""; + message = "cryodev/nixos/mailserver: config.mailserver.subdomain cannot be empty."; + } + ]; + + mailserver = { + fqdn = mkDefault fqdn; + + domains = mkDefault [ domain ]; + certificateScheme = mkDefault "acme-nginx"; + stateVersion = mkDefault 1; + + loginAccounts = mapAttrs' ( + user: accConf: + nameValuePair "${user}@${domain}" { + name = "${user}@${domain}"; + aliases = map (alias: "${alias}@${domain}") (accConf.aliases or [ ]); + sendOnly = accConf.sendOnly; + quota = mkDefault "5G"; + hashedPasswordFile = config.sops.secrets."mailserver/accounts/${user}".path; + } + ) cfg.accounts; + }; + + security.acme = { + acceptTerms = true; + defaults.email = mkDefault "postmaster@cryodev.xyz"; + defaults.webroot = mkDefault "/var/lib/acme/acme-challenge"; + }; + + environment.systemPackages = [ pkgs.mailutils ]; + + sops = { + secrets = mapAttrs' ( + user: _config: + nameValuePair "mailserver/accounts/${user}" { + restartUnits = [ + "postfix.service" + "dovecot.service" + ]; + } + ) cfg.accounts; + }; + }; +} diff --git a/modules/nixos/nginx/default.nix b/modules/nixos/nginx/default.nix new file mode 100644 index 0000000..d28d7a9 --- /dev/null +++ b/modules/nixos/nginx/default.nix @@ -0,0 +1,72 @@ +{ config, lib, ... }: + +let + cfg = config.services.nginx; + + inherit (lib) + mkDefault + mkIf + mkOption + optional + optionals + types + ; +in +{ + options.services.nginx = { + forceSSL = mkOption { + type = types.bool; + default = false; + description = "Force SSL for Nginx virtual host."; + }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Whether to open the firewall for HTTP (and HTTPS if forceSSL is enabled)."; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = optionals cfg.openFirewall ( + [ + 80 + ] + ++ optional cfg.forceSSL 443 + ); + + services.nginx = { + recommendedOptimisation = mkDefault true; + recommendedGzipSettings = mkDefault true; + recommendedProxySettings = mkDefault true; + recommendedTlsSettings = cfg.forceSSL; + + commonHttpConfig = "access_log syslog:server=unix:/dev/log;"; + + resolver.addresses = + let + isIPv6 = addr: builtins.match ".*:.*:.*" addr != null; + escapeIPv6 = addr: if isIPv6 addr then "[${addr}]" else addr; + cloudflare = [ + "1.1.1.1" + "2606:4700:4700::1111" + ]; + resolvers = + if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers; + in + map escapeIPv6 resolvers; + + sslDhparam = mkIf cfg.forceSSL config.security.dhparams.params.nginx.path; + }; + + security.acme = mkIf cfg.forceSSL { + acceptTerms = true; + defaults.email = mkDefault "postmaster@${config.networking.domain}"; + defaults.webroot = mkDefault "/var/lib/acme/acme-challenge"; + }; + + security.dhparams = mkIf cfg.forceSSL { + enable = true; + params.nginx = { }; + }; + }; +} diff --git a/modules/nixos/nixvim/default.nix b/modules/nixos/nixvim/default.nix new file mode 100644 index 0000000..2d6a105 --- /dev/null +++ b/modules/nixos/nixvim/default.nix @@ -0,0 +1,106 @@ +{ + inputs, + config, + lib, + ... +}: + +let + cfg = config.programs.nixvim; + + inherit (lib) mkDefault mkIf; +in +{ + imports = [ + inputs.nixvim.nixosModules.nixvim + ./plugins + + ./spellfiles.nix + ]; + + config = { + programs.nixvim = { + enable = true; # Enable globally on NixOS + defaultEditor = mkDefault true; + viAlias = mkDefault true; + vimAlias = mkDefault true; + + # Removed home-manager specific options like 'enableMan' which is handled differently or not needed in system module context + # Removed clipboard.providers.wl-copy as it's home-manager specific. + # System-wide clipboard integration for headless servers is less critical but can be added if needed. + + # vim.g.* + globals = { + mapleader = mkDefault " "; + }; + + # vim.opt.* + opts = { + # behavior + cursorline = mkDefault true; # highlights the line under the cursor + mouse = mkDefault "a"; # enable mouse support + nu = mkDefault true; # line numbers + relativenumber = mkDefault true; # relative line numbers + scrolloff = mkDefault 20; # keeps some context above/below cursor + signcolumn = mkDefault "yes"; # reserve space for signs (e.g., GitGutter) + undofile = mkDefault true; # persistent undo + updatetime = mkDefault 500; # ms to wait for trigger an event (default 4000ms) + wrap = mkDefault true; # wraps text if it exceeds the width of the window + + # search + ignorecase = mkDefault true; # ignore case in search patterns + smartcase = mkDefault true; # smart case + incsearch = mkDefault true; # incremental search + hlsearch = mkDefault true; # highlight search + + # windows + splitbelow = mkDefault true; # new windows are created below current + splitright = mkDefault true; # new windows are created to the right of current + equalalways = mkDefault true; # window sizes are automatically updated. + + # tabs + expandtab = mkDefault true; # convert tabs into spaces + shiftwidth = mkDefault 2; # number of spaces to use for each step of (auto)indent + smartindent = mkDefault true; # smart autoindenting on new lines + softtabstop = mkDefault 2; # number of spaces in tab when editing + tabstop = mkDefault 2; # number of visual spaces per tab + + # spell checking + spell = mkDefault true; + spelllang = mkDefault [ + "en_us" + "de_20" + ]; + + }; + + # vim.diagnostic.config.* + diagnostic.settings = { + virtual_text = { + spacing = 4; + prefix = "●"; + severity_sort = true; + }; + signs = true; + underline = true; + update_in_insert = false; + }; + + extraConfigLua = '' + vim.cmd "set noshowmode" -- Hides "--INSERT--" mode indicator + ''; + + keymaps = import ./keymaps.nix; + }; + + environment = { + variables = { + EDITOR = mkIf cfg.enable "nvim"; + VISUAL = mkIf cfg.enable "nvim"; + }; + shellAliases = { + v = mkIf cfg.enable "nvim"; + }; + }; + }; +} diff --git a/modules/nixos/nixvim/keymaps.nix b/modules/nixos/nixvim/keymaps.nix new file mode 100644 index 0000000..aaefaa0 --- /dev/null +++ b/modules/nixos/nixvim/keymaps.nix @@ -0,0 +1,347 @@ +[ + # cursor navigation + { + # scroll down, recenter + key = ""; + action = "zz"; + mode = "n"; + } + { + # scroll up, recenter + key = ""; + action = "zz"; + mode = "n"; + } + + # searching + { + # center cursor after search next + key = "n"; + action = "nzzzv"; + mode = "n"; + } + { + # center cursor after search previous + key = "N"; + action = "Nzzzv"; + mode = "n"; + } + { + # ex command + key = "pv"; + action = "Ex"; + mode = "n"; + } + + # search and replace + { + # search and replace word under cursor + key = "s"; + action = ":%s///gI"; + mode = "n"; + } + # search and replace selected text + { + key = "s"; + action = "y:%s/0/0/gI"; + mode = "v"; + } + + # clipboard operations + { + # copy to system clipboard in visual mode + key = ""; + action = ''"+y ''; + mode = "v"; + } + { + # paste from system clipboard in visual mode + key = ""; + action = ''"+p ''; + mode = "v"; + } + { + # yank to system clipboard + key = "Y"; + action = "+Y"; + mode = "n"; + } + { + # replace selected text with clipboard content + key = "p"; + action = "_dP"; + mode = "x"; + } + { + # delete without copying to clipboard + key = "d"; + action = "_d"; + mode = [ + "n" + "v" + ]; + } + + # line operations + { + # move lines down in visual mode + key = "J"; + action = ":m '>+1gv=gv"; + mode = "v"; + } + { + # move lines up in visual mode + key = "K"; + action = ":m '<-2gv=gv"; + mode = "v"; + } + { + # join lines + key = "J"; + action = "mzJ`z"; + mode = "n"; + } + + # quickfix + { + # Run make command + key = "m"; + action = ":make"; + mode = "n"; + } + { + # previous quickfix item + key = ""; + action = "cprevzz"; + mode = "n"; + } + { + # next quickfix item + key = ""; + action = "cnextzz"; + mode = "n"; + } + + # location list navigation + { + # previous location list item + key = "j"; + action = "lprevzz"; + mode = "n"; + } + { + # next location list item + key = "k"; + action = "lnextzz"; + mode = "n"; + } + + # disabling keys + { + # disable the 'Q' key + key = "Q"; + action = ""; + mode = "n"; + } + + # text selection + { + # select whole buffer + key = ""; + action = "ggVG"; + mode = "n"; + } + + # window operations + { + # focus next window + key = ""; + action = ":wincmd W"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # focus previous window + key = ""; + action = ":wincmd w"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # window size adjustments + { + # increase window width + key = ""; + action = ":vertical resize +5"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # decrease window width + key = ""; + action = ":vertical resize -5"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # window closing and opening + { + # close current window + key = "c"; + action = ":q"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # new vertical split at $HOME + key = "n"; + action = ":vsp $HOME"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # window split orientation toggling + { + # toggle split orientation + key = "t"; + action = ":wincmd T"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # spell checking + { + # toggle spell checking + key = "ss"; + action = ":setlocal spell!"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # switch to english spell checking + key = "se"; + action = ":setlocal spelllang=en_us"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # switch to german spell checking + key = "sg"; + action = ":setlocal spelllang=de_20"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # move to next misspelling + key = "]s"; + action = "]szz"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # move to previous misspelling + key = "[s"; + action = "[szz"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # correction suggestions for a misspelled word + key = "z="; + action = "z="; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # adding words to the dictionary + key = "zg"; + action = "zg"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + # buffer navigation + { + # next buffer + key = ""; + action = ":bnext"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # previous buffer + key = ""; + action = ":bprevious"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + { + # close current buffer + key = "bd"; + action = ":bdelete"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } + + { + # apply code action + key = "ca"; + action = ":lua vim.lsp.buf.code_action()"; + options = { + noremap = true; + silent = true; + }; + mode = "n"; + } +] diff --git a/modules/nixos/nixvim/plugins/cmp.nix b/modules/nixos/nixvim/plugins/cmp.nix new file mode 100644 index 0000000..b93fcb6 --- /dev/null +++ b/modules/nixos/nixvim/plugins/cmp.nix @@ -0,0 +1,54 @@ +{ config, lib, ... }: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.cmp; + + inherit (lib) mkDefault mkIf; +in +{ + programs.nixvim = { + plugins = { + cmp = { + enable = mkDefault true; + settings = { + autoEnableSources = mkDefault true; + experimental.ghost_text = mkDefault true; + snippet.expand = mkDefault "luasnip"; + formatting.fields = mkDefault [ + "kind" + "abbr" + "menu" + ]; + sources = [ + { name = "git"; } + { name = "nvim_lsp"; } + { + name = "buffer"; + option.get_bufnrs.__raw = "vim.api.nvim_list_bufs"; + keywordLength = 3; + } + { + name = "path"; + keywordLength = 3; + } + { name = "luasnip"; } + ]; + mapping = { + "" = "cmp.mapping.complete()"; + "" = "cmp.mapping.scroll_docs(-4)"; + "" = "cmp.mapping.close()"; + "" = "cmp.mapping.scroll_docs(4)"; + "" = "cmp.mapping.confirm({ select = true })"; + "" = "cmp.mapping(cmp.mapping.select_prev_item(), {'i', 's'})"; + "" = "cmp.mapping(cmp.mapping.select_next_item(), {'i', 's'})"; + }; + }; + }; + cmp-cmdline = mkIf plugin.enable { enable = mkDefault false; }; # autocomplete for cmdline + cmp_luasnip = mkIf plugin.enable { enable = mkDefault true; }; + luasnip = mkIf plugin.enable { enable = mkDefault true; }; + cmp-treesitter = mkIf (plugin.enable && cfg.plugins.treesitter.enable) { enable = mkDefault true; }; + }; + }; +} diff --git a/modules/nixos/nixvim/plugins/default.nix b/modules/nixos/nixvim/plugins/default.nix new file mode 100644 index 0000000..1941f43 --- /dev/null +++ b/modules/nixos/nixvim/plugins/default.nix @@ -0,0 +1,18 @@ +{ lib, ... }: + +{ + imports = [ + ./cmp.nix + ./lsp.nix + ./lualine.nix + ./telescope.nix + # ./treesitter.nix # HOTFIX: does not build + ./trouble.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; + }; +} diff --git a/modules/nixos/nixvim/plugins/lsp.nix b/modules/nixos/nixvim/plugins/lsp.nix new file mode 100644 index 0000000..b3c70a8 --- /dev/null +++ b/modules/nixos/nixvim/plugins/lsp.nix @@ -0,0 +1,69 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.lsp; + + inherit (lib) mkDefault mkIf optional; +in +{ + config = { + programs.nixvim = { + plugins = { + lsp-format = mkIf plugin.enable { enable = mkDefault true; }; + + lsp = { + enable = mkDefault true; + postConfig = ""; + keymaps = { + silent = mkDefault true; + diagnostic = mkDefault { + # Navigate in diagnostics + "k" = "goto_prev"; + "j" = "goto_next"; + }; + + lspBuf = mkDefault { + gd = "definition"; + gD = "references"; + gt = "type_definition"; + gi = "implementation"; + K = "hover"; + "" = "rename"; + }; + }; + + servers = { + bashls.enable = mkDefault true; + clangd.enable = mkDefault true; + cssls.enable = mkDefault true; + dockerls.enable = mkDefault true; + gopls.enable = mkDefault true; + html.enable = mkDefault true; + jsonls.enable = mkDefault true; + nixd.enable = mkDefault true; + pyright.enable = mkDefault true; + rust_analyzer = { + enable = mkDefault true; + installCargo = mkDefault true; + installRustc = mkDefault true; + settings.rustfmt.overrideCommand = mkDefault [ + "${pkgs.rustfmt}/bin/rustfmt --edition 2021" # --config tab_spaces=2" + ]; + }; + texlab.enable = mkDefault true; + vhdl_ls.enable = mkDefault true; + yamlls.enable = mkDefault true; + }; + }; + }; + }; + + home.packages = optional (cfg.enable && plugin.servers.nixd.enable) pkgs.nixfmt; + }; +} diff --git a/modules/nixos/nixvim/plugins/lualine.nix b/modules/nixos/nixvim/plugins/lualine.nix new file mode 100644 index 0000000..72d2238 --- /dev/null +++ b/modules/nixos/nixvim/plugins/lualine.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.lualine; + + inherit (lib) mkDefault; +in +{ + config = { + programs.nixvim = { + plugins.lualine = { + enable = mkDefault true; + settings.options.icons_enabled = mkDefault false; + }; + }; + }; +} diff --git a/modules/nixos/nixvim/plugins/telescope.nix b/modules/nixos/nixvim/plugins/telescope.nix new file mode 100644 index 0000000..6e1dabc --- /dev/null +++ b/modules/nixos/nixvim/plugins/telescope.nix @@ -0,0 +1,52 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.telescope; + + inherit (lib) mkDefault optionals; +in +{ + config = { + programs.nixvim = { + plugins.telescope = { + enable = mkDefault true; + extensions = { + file-browser.enable = mkDefault true; + fzf-native.enable = mkDefault true; + live-grep-args.enable = mkDefault true; + manix.enable = mkDefault true; + }; + keymaps = mkDefault { + "" = "file_browser"; + "" = "git_files"; + "bl" = "buffers"; + "fd" = "diagnostics"; + "ff" = "find_files"; + "fg" = "live_grep"; + "fh" = "help_tags"; + "fm" = "man_pages"; + "fn" = "manix"; + "fo" = "oldfiles"; + "fb" = "file_browser"; + }; + }; + keymaps = optionals plugin.enable [ + { + key = ""; + action = ":lua require('telescope').extensions.live_grep_args.live_grep_args()"; + mode = "n"; + } + ]; + }; + + home.packages = optionals plugin.enable [ + pkgs.ripgrep # for "live_grep" + ]; + }; +} diff --git a/modules/nixos/nixvim/plugins/treesitter.nix b/modules/nixos/nixvim/plugins/treesitter.nix new file mode 100644 index 0000000..22b7040 --- /dev/null +++ b/modules/nixos/nixvim/plugins/treesitter.nix @@ -0,0 +1,42 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.treesitter; + + cc = "${pkgs.gcc}/bin/gcc"; + + inherit (lib) mkDefault mkIf; +in +{ + config = { + programs.nixvim = { + plugins.treesitter = { + enable = mkDefault true; + nixvimInjections = mkDefault true; + settings = { + folding.enable = mkDefault true; + highlight.enable = mkDefault true; + indent.enable = mkDefault true; + }; + }; + plugins.treesitter-context = mkIf plugin.enable { enable = mkDefault true; }; + plugins.treesitter-textobjects = mkIf plugin.enable { enable = mkDefault true; }; + }; + + # Fix for: ERROR `cc` executable not found. + home.sessionVariables = mkIf plugin.enable { + CC = mkDefault cc; + }; + + # Fix for: WARNING `tree-sitter` executable not found + home.packages = mkIf plugin.enable [ + plugin.package + ]; + }; +} diff --git a/modules/nixos/nixvim/plugins/trouble.nix b/modules/nixos/nixvim/plugins/trouble.nix new file mode 100644 index 0000000..139576d --- /dev/null +++ b/modules/nixos/nixvim/plugins/trouble.nix @@ -0,0 +1,43 @@ +{ config, lib, ... }: + +let + cfg = config.programs.nixvim; + plugin = cfg.plugins.trouble; + + inherit (lib) mkDefault mkIf; +in +{ + config = { + programs.nixvim = { + plugins.trouble = { + enable = mkDefault true; + }; + keymaps = mkIf plugin.enable [ + { + mode = "n"; + key = "xq"; + action = "Trouble qflist toggle"; + options = { + desc = "Trouble quifick toggle"; + }; + } + { + mode = "n"; + key = "xl"; + action = "Trouble loclist toggle"; + options = { + desc = "Trouble loclist toggle"; + }; + } + { + mode = "n"; + key = "xx"; + action = "Trouble diagnostics toggle"; + options = { + desc = "Trouble diagnostics toggle"; + }; + } + ]; + }; + }; +} diff --git a/modules/nixos/nixvim/spellfiles.nix b/modules/nixos/nixvim/spellfiles.nix new file mode 100644 index 0000000..35b4a6e --- /dev/null +++ b/modules/nixos/nixvim/spellfiles.nix @@ -0,0 +1,26 @@ +{ config, pkgs, ... }: + +let + spellDir = config.xdg.dataHome + "/nvim/site/spell"; + baseUrl = "http://ftp.de.vim.org/runtime/spell"; +in +{ + home.file = { + de-spl = { + enable = true; + source = pkgs.fetchurl { + url = baseUrl + "/de.utf-8.spl"; + sha256 = "sha256-c8cQfqM5hWzb6SHeuSpFk5xN5uucByYdobndGfaDo9E="; + }; + target = spellDir + "/de.utf8.spl"; + }; + de-sug = { + enable = true; + source = pkgs.fetchurl { + url = baseUrl + "/de.utf-8.sug"; + sha256 = "sha256-E9Ds+Shj2J72DNSopesqWhOg6Pm6jRxqvkerqFcUqUg="; + }; + target = spellDir + "/de.utf8.sug"; + }; + }; +} diff --git a/modules/nixos/normalUsers/default.nix b/modules/nixos/normalUsers/default.nix new file mode 100644 index 0000000..7f769bb --- /dev/null +++ b/modules/nixos/normalUsers/default.nix @@ -0,0 +1,69 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.normalUsers; + + inherit (lib) + attrNames + genAttrs + mkOption + types + ; +in +{ + options.normalUsers = mkOption { + type = types.attrsOf ( + types.submodule { + options = { + extraGroups = mkOption { + type = (types.listOf types.str); + default = [ ]; + description = "Extra groups for the user"; + example = [ "wheel" ]; + }; + shell = mkOption { + type = types.path; + default = pkgs.zsh; + description = "Shell for the user"; + }; + initialPassword = mkOption { + type = types.str; + default = "changeme"; + description = "Initial password for the user"; + }; + sshKeyFiles = mkOption { + type = (types.listOf types.path); + default = [ ]; + description = "SSH key files for the user"; + example = [ "/path/to/id_rsa.pub" ]; + }; + }; + } + ); + default = { }; + description = "Users to create. The usernames are the attribute names."; + }; + + config = { + # Create user groups + users.groups = genAttrs (attrNames cfg) (userName: { + name = userName; + }); + + # Create users + users.users = genAttrs (attrNames cfg) (userName: { + name = userName; + inherit (cfg.${userName}) extraGroups shell initialPassword; + + isNormalUser = true; + group = "${userName}"; + home = "/home/${userName}"; + openssh.authorizedKeys.keyFiles = cfg.${userName}.sshKeyFiles; + }); + }; +} diff --git a/modules/nixos/openssh/default.nix b/modules/nixos/openssh/default.nix new file mode 100644 index 0000000..0958445 --- /dev/null +++ b/modules/nixos/openssh/default.nix @@ -0,0 +1,16 @@ +{ lib, ... }: + +let + inherit (lib) mkDefault; +in +{ + services.openssh = { + enable = mkDefault true; + ports = mkDefault [ 2299 ]; + openFirewall = mkDefault true; + settings = { + PermitRootLogin = mkDefault "no"; + PasswordAuthentication = mkDefault false; + }; + }; +} diff --git a/modules/nixos/sops/default.nix b/modules/nixos/sops/default.nix new file mode 100644 index 0000000..627014b --- /dev/null +++ b/modules/nixos/sops/default.nix @@ -0,0 +1,21 @@ +{ + inputs, + config, + lib, + pkgs, + ... +}: + +let + secrets = "${toString inputs.self}/hosts/${config.networking.hostName}/secrets/secrets.yaml"; +in +{ + imports = [ inputs.sops-nix.nixosModules.sops ]; + + environment.systemPackages = with pkgs; [ + age + sops + ]; + + sops.defaultSopsFile = lib.mkIf (builtins.pathExists secrets) (lib.mkDefault secrets); +} diff --git a/modules/nixos/tailscale/default.nix b/modules/nixos/tailscale/default.nix new file mode 100644 index 0000000..3e43963 --- /dev/null +++ b/modules/nixos/tailscale/default.nix @@ -0,0 +1,50 @@ +{ config, lib, ... }: + +let + cfg = config.services.tailscale; + + inherit (lib) + mkIf + mkOption + optional + types + ; +in +{ + options.services.tailscale = { + loginServer = mkOption { + type = types.str; + description = "The Tailscale login server to use."; + }; + enableSSH = mkOption { + type = types.bool; + default = false; + description = "Enable Tailscale SSH functionality."; + }; + acceptDNS = mkOption { + type = types.bool; + default = true; + description = "Enable Tailscale's MagicDNS and custom DNS configuration."; + }; + }; + + config = mkIf cfg.enable { + services.tailscale = { + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + extraSetFlags = optional cfg.enableSSH "--ssh" ++ optional cfg.acceptDNS "--accept-dns"; + extraUpFlags = [ + "--login-server=${cfg.loginServer}" + ] + ++ optional cfg.enableSSH "--ssh" + ++ optional cfg.acceptDNS "--accept-dns"; + }; + + environment.shellAliases = { + ts = "${cfg.package}/bin/tailscale"; + }; + + networking.firewall.trustedInterfaces = [ cfg.interfaceName ]; + + sops.secrets."tailscale/auth-key" = { }; + }; +} diff --git a/overlays/default.nix b/overlays/default.nix new file mode 100644 index 0000000..32635d4 --- /dev/null +++ b/overlays/default.nix @@ -0,0 +1,32 @@ +{ inputs, ... }: + +{ + # packages in `pkgs/` accessible through 'pkgs.local' + local-packages = final: prev: { local = import ../pkgs { pkgs = final; }; }; + + # https://nixos.wiki/wiki/Overlays + modifications = + final: prev: + let + files = [ + ]; + imports = builtins.map (f: import f final prev) files; + in + builtins.foldl' (a: b: a // b) { } imports; + + # old-stable nixpkgs accessible through 'pkgs.old-stable' + old-stable-packages = final: prev: { + old-stable = import inputs.nixpkgs-old-stable { + inherit (final) system; + inherit (prev) config; + }; + }; + + # unstable nixpkgs accessible through 'pkgs.unstable' + unstable-packages = final: prev: { + unstable = import inputs.nixpkgs-unstable { + inherit (final) system; + inherit (prev) config; + }; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix new file mode 100644 index 0000000..2dadf8a --- /dev/null +++ b/pkgs/default.nix @@ -0,0 +1,8 @@ +{ + pkgs ? import , + ... +}: + +{ + # example = pkgs.callPackage ./example { }; +} diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..1f53b98 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +# NixOS install script + + +### VARIABLES ### + +ASK_VERIFICATION=1 # Default to ask for verification +CONFIG_DIR="/tmp/nixos" # Directory to copy flake to / clone flake into +GIT_BRANCH="master" # Default Git branch +GIT_REPO="" # Git repository URL +HOSTNAME="" # Hostname +MNT="/mnt" # root mount point +SEPARATOR="________________________________________" # line separator + +### FUNCTIONS ### + +# Function to display help information +Show_help() { + echo "Usage: $0 [-r REPO] [-n HOSTNAME] [-b BRANCH] [-y] [-h]" + echo + echo "Options:" + echo " -r, --repo REPO Your NixOS configuration Git repository URL" + echo " -n, --hostname HOSTNAME Specify the hostname for the NixOS configuration" + echo " -b, --branch BRANCH Specify the Git branch to use (default: $GIT_BRANCH)" + echo " -y, --yes Do not ask for user verification before proceeding" + echo " -h, --help Show this help message and exit" +} + +# Function to format, partition, and mount disks for $HOSTNAME using disko +Run_disko() { + echo "$SEPARATOR" + echo "Running disko..." + nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode disko "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.nix +} + +# Function to format, partition, and mount disks for $HOSTNAME using a partitioning script +Run_script() { + echo "$SEPARATOR" + echo "Running partitioning script..." + bash "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.sh +} + +# Function to check mount points and partitioning +Check_partitioning() { + echo "$SEPARATOR" + echo "Printing mount points and partitioning..." + mount | grep "$MNT" + lsblk -f + [[ "$ASK_VERIFICATION" == 1 ]] && read -rp "Verify the mount points and partitioning. Press Ctrl+c to cancel or Enter to continue..." +} + +# Function to generate hardware configuration +Generate_hardware_config() { + [[ "$ASK_VERIFICATION" == 1 ]] && read -rp "No hardware configuration found. Press Ctrl+c to cancel or Enter to generate one..." + + echo "$SEPARATOR" + echo "Generating hardware configuration..." + nixos-generate-config --root "$MNT" --show-hardware-config > "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix + + # Check if hardware configuration has been generated + if [[ ! -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix ]]; then + echo "Error: Hardware configuration cannot be generated." + exit 1 + fi + + # Add configuration to git + # TODO: get rid of cd + cd "$CONFIG_DIR"/hosts/"$HOSTNAME" || exit 1 + git add "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix + cd || exit 1 + + echo "Hardware configuration generated successfully." +}; + +# Function to install configuration for $HOSTNAME +Install() { + # Check if hardware configuration exists + [[ ! -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/hardware.nix ]] && Generate_hardware_config + + echo "$SEPARATOR" + echo "Installing NixOS..." + nixos-install --root "$MNT" --no-root-password --flake "$CONFIG_DIR"#"$HOSTNAME" && echo "You can reboot the system now." +} + +### PARSE ARGUMENTS ### + +while [[ "$#" -gt 0 ]]; do + case $1 in + -r|--repo) GIT_REPO="$2"; shift ;; + -b|--branch) GIT_BRANCH="$2"; shift ;; + -y|--yes) ASK_VERIFICATION=0 ;; + -h|--help) Show_help; exit 0 ;; + -n|--hostname) HOSTNAME="$2"; shift ;; + *) echo "Unknown option: $1"; Show_help; exit 1 ;; + esac + shift +done + +### PREREQUISITES ### + +echo "$SEPARATOR" +mkdir -p "$CONFIG_DIR" + +# Clone NixOS configuration from $GIT_REPO if provided +if [[ ! -z "$GIT_REPO" ]]; then + # Install git if not already installed + if ! command -v git &> /dev/null; then + echo "Git is not installed. Installing..." + nix-env -iA nixos.git + fi + + # Clone Git repo if directory is empty + if [[ -z "$(ls -A "$CONFIG_DIR" 2>/dev/null)" ]]; then + echo "Cloning NixOS configuration repo..." + git clone --depth 1 -b "$GIT_BRANCH" "$GIT_REPO" "$CONFIG_DIR" + + # Check if git repository has been cloned + if [[ ! -d "$CONFIG_DIR"/.git ]]; then + echo "Error: Git repository could not be cloned." + exit 1 + fi + else + echo "$CONFIG_DIR is not empty. Skip cloning $GIT_REPO." + fi +fi + +if [[ ! -f "$CONFIG_DIR"/flake.nix ]]; then + echo "Error: $CONFIG_DIR does not contain 'flake.nix'." + exit 1 +fi + +### CHOOSE CONFIG ### + +# If hostname is not provided via options, prompt the user +if [[ -z "$HOSTNAME" ]]; then + # Get list of available hostnames + HOSTNAMES=$(ls "$CONFIG_DIR"/hosts) + + echo "$SEPARATOR" + echo "Please choose a hostname to install its NixOS configuration." + echo "$HOSTNAMES" + read -rp "Enter hostname: " HOSTNAME + + # Check if hostname is empty + if [[ -z "$HOSTNAME" ]]; then + echo "Error: Hostname cannot be empty." + exit 1 + fi +fi + +### INSTALLATION ### + +# Check if NixOS configuration exists +if [[ -d "$CONFIG_DIR"/hosts/"$HOSTNAME" ]]; then + + # Check for existing disko configuration + if [[ -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.nix ]]; then + Run_disko || ( echo "Error: disko failed." && exit 1 ) + # Check for partitioning script + elif [[ -f "$CONFIG_DIR"/hosts/"$HOSTNAME"/disks.sh ]]; then + Run_script || ( echo "Error: Partitioning script failed." && exit 1 ) + else + echo "Error: No disko configuration (disks.nix) or partitioning script (disks.sh) found for host '$HOSTNAME'." + exit 1 + fi + + Check_partitioning + Install || ( echo "Error: Installation failed." && exit 1 ) +else + echo "Error: Configuration for host '$HOSTNAME' does not exist." + exit 1 +fi diff --git a/templates/generic-server/boot.nix b/templates/generic-server/boot.nix new file mode 100644 index 0000000..a459d88 --- /dev/null +++ b/templates/generic-server/boot.nix @@ -0,0 +1,8 @@ +{ + boot = { + loader = { + grub.enable = false; + generic-extlinux-compatible.enable = true; + }; + }; +} diff --git a/templates/generic-server/default.nix b/templates/generic-server/default.nix new file mode 100644 index 0000000..482c6a8 --- /dev/null +++ b/templates/generic-server/default.nix @@ -0,0 +1,21 @@ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} diff --git a/templates/generic-server/disks.sh b/templates/generic-server/disks.sh new file mode 100644 index 0000000..5e06bc8 --- /dev/null +++ b/templates/generic-server/disks.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL diff --git a/templates/generic-server/flake.nix b/templates/generic-server/flake.nix new file mode 100644 index 0000000..fc47489 --- /dev/null +++ b/templates/generic-server/flake.nix @@ -0,0 +1,4 @@ +{ + description = "A generic x86_64 server client template"; + path = ./.; +} diff --git a/templates/generic-server/hardware.nix b/templates/generic-server/hardware.nix new file mode 100644 index 0000000..a0d751a --- /dev/null +++ b/templates/generic-server/hardware.nix @@ -0,0 +1,23 @@ +{ pkgs, lib, ... }: + +{ + boot = { + kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; + initrd.availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ "noatime" ]; + }; + }; + + nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; + hardware.enableRedistributableFirmware = true; +} diff --git a/templates/generic-server/networking.nix b/templates/generic-server/networking.nix new file mode 100644 index 0000000..c00bcf5 --- /dev/null +++ b/templates/generic-server/networking.nix @@ -0,0 +1,4 @@ +{ + networking.hostName = "cryodev-pi"; + networking.domain = "cryodev.xyz"; +} diff --git a/templates/generic-server/packages.nix b/templates/generic-server/packages.nix new file mode 100644 index 0000000..96cc691 --- /dev/null +++ b/templates/generic-server/packages.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} diff --git a/templates/generic-server/services/comin.nix b/templates/generic-server/services/comin.nix new file mode 100644 index 0000000..2317b06 --- /dev/null +++ b/templates/generic-server/services/comin.nix @@ -0,0 +1,24 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.comin + ]; + + services.comin = { + enable = true; + remotes = [ + { + name = "origin"; + url = "https://${constants.services.forgejo.fqdn}/steffen/cryodev-server.git"; + branches.main.name = "main"; + } + ]; + }; +} diff --git a/templates/generic-server/services/default.nix b/templates/generic-server/services/default.nix new file mode 100644 index 0000000..e4c52d5 --- /dev/null +++ b/templates/generic-server/services/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./nginx.nix + ./openssh.nix + ./tailscale.nix + ./netdata.nix + ./comin.nix + ]; +} diff --git a/templates/generic-server/services/netdata.nix b/templates/generic-server/services/netdata.nix new file mode 100644 index 0000000..9ff9a6c --- /dev/null +++ b/templates/generic-server/services/netdata.nix @@ -0,0 +1,31 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + config = { + stream = { + enabled = "yes"; + destination = "${constants.hosts.cryodev-main.ip}:${toString constants.services.netdata.port}"; + "api key" = config.sops.placeholder."netdata/stream/child-uuid"; + }; + }; + }; + + # Make sure sops is enabled/imported for this host to handle the secret + imports = [ outputs.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + secrets."netdata/stream/child-uuid" = { + owner = "netdata"; + group = "netdata"; + }; + }; +} diff --git a/templates/generic-server/services/nginx.nix b/templates/generic-server/services/nginx.nix new file mode 100644 index 0000000..00734e5 --- /dev/null +++ b/templates/generic-server/services/nginx.nix @@ -0,0 +1,14 @@ +{ + outputs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; + openFirewall = true; + }; +} diff --git a/templates/generic-server/services/openssh.nix b/templates/generic-server/services/openssh.nix new file mode 100644 index 0000000..f71c084 --- /dev/null +++ b/templates/generic-server/services/openssh.nix @@ -0,0 +1,12 @@ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} diff --git a/templates/generic-server/services/tailscale.nix b/templates/generic-server/services/tailscale.nix new file mode 100644 index 0000000..b9ab502 --- /dev/null +++ b/templates/generic-server/services/tailscale.nix @@ -0,0 +1,28 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + + # Auth key for automated enrollment + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + }; + + sops.secrets."tailscale/auth-key" = { }; +} diff --git a/templates/generic-server/users.nix b/templates/generic-server/users.nix new file mode 100644 index 0000000..3570761 --- /dev/null +++ b/templates/generic-server/users.nix @@ -0,0 +1,9 @@ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ../../users/cryotherm + ]; +} diff --git a/templates/raspberry-pi/boot.nix b/templates/raspberry-pi/boot.nix new file mode 100644 index 0000000..a459d88 --- /dev/null +++ b/templates/raspberry-pi/boot.nix @@ -0,0 +1,8 @@ +{ + boot = { + loader = { + grub.enable = false; + generic-extlinux-compatible.enable = true; + }; + }; +} diff --git a/templates/raspberry-pi/default.nix b/templates/raspberry-pi/default.nix new file mode 100644 index 0000000..482c6a8 --- /dev/null +++ b/templates/raspberry-pi/default.nix @@ -0,0 +1,21 @@ +{ + inputs, + outputs, + ... +}: + +{ + imports = [ + ./boot.nix + ./hardware.nix + ./networking.nix + ./packages.nix + ./services + ./users.nix + + outputs.nixosModules.common + outputs.nixosModules.nixvim + ]; + + system.stateVersion = "25.11"; +} diff --git a/templates/raspberry-pi/disks.sh b/templates/raspberry-pi/disks.sh new file mode 100644 index 0000000..5e06bc8 --- /dev/null +++ b/templates/raspberry-pi/disks.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +SSD='/dev/disk/by-id/FIXME' +MNT='/mnt' +SWAP_GB=4 + +# Helper function to wait for devices +wait_for_device() { + local device=$1 + echo "Waiting for device: $device ..." + while [[ ! -e $device ]]; do + sleep 1 + done + echo "Device $device is ready." +} + +# Function to install a package if it's not already installed +install_if_missing() { + local cmd="$1" + local package="$2" + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd not found, installing $package..." + nix-env -iA "nixos.$package" + fi +} + +install_if_missing "sgdisk" "gptfdisk" +install_if_missing "partprobe" "parted" + +wait_for_device $SSD + +echo "Wiping filesystem on $SSD..." +wipefs -a $SSD + +echo "Clearing partition table on $SSD..." +sgdisk --zap-all $SSD + +echo "Partitioning $SSD..." +sgdisk -n1:1M:+1G -t1:EF00 -c1:BOOT $SSD +sgdisk -n2:0:+"$SWAP_GB"G -t2:8200 -c2:SWAP $SSD +sgdisk -n3:0:0 -t3:8304 -c3:ROOT $SSD +partprobe -s $SSD +udevadm settle + +wait_for_device ${SSD}-part1 +wait_for_device ${SSD}-part2 +wait_for_device ${SSD}-part3 + +echo "Formatting partitions..." +mkfs.vfat -F 32 -n BOOT "${SSD}-part1" +mkswap -L SWAP "${SSD}-part2" +mkfs.ext4 -L ROOT "${SSD}-part3" + +echo "Mounting partitions..." +mount -o X-mount.mkdir "${SSD}-part3" "$MNT" +mkdir -p "$MNT/boot" +mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1 "${SSD}-part1" "$MNT/boot" + +echo "Enabling swap..." +swapon "${SSD}-part2" + +echo "Partitioning and setup complete:" +lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL diff --git a/templates/raspberry-pi/flake.nix b/templates/raspberry-pi/flake.nix new file mode 100644 index 0000000..56389b8 --- /dev/null +++ b/templates/raspberry-pi/flake.nix @@ -0,0 +1,4 @@ +{ + description = "A Raspberry Pi 4 client template"; + path = ./.; +} diff --git a/templates/raspberry-pi/hardware.nix b/templates/raspberry-pi/hardware.nix new file mode 100644 index 0000000..a0d751a --- /dev/null +++ b/templates/raspberry-pi/hardware.nix @@ -0,0 +1,23 @@ +{ pkgs, lib, ... }: + +{ + boot = { + kernelPackages = pkgs.linuxKernel.packages.linux_rpi4; + initrd.availableKernelModules = [ + "xhci_pci" + "usbhid" + "usb_storage" + ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ "noatime" ]; + }; + }; + + nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; + hardware.enableRedistributableFirmware = true; +} diff --git a/templates/raspberry-pi/networking.nix b/templates/raspberry-pi/networking.nix new file mode 100644 index 0000000..c00bcf5 --- /dev/null +++ b/templates/raspberry-pi/networking.nix @@ -0,0 +1,4 @@ +{ + networking.hostName = "cryodev-pi"; + networking.domain = "cryodev.xyz"; +} diff --git a/templates/raspberry-pi/packages.nix b/templates/raspberry-pi/packages.nix new file mode 100644 index 0000000..96cc691 --- /dev/null +++ b/templates/raspberry-pi/packages.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ ]; +} diff --git a/templates/raspberry-pi/services/comin.nix b/templates/raspberry-pi/services/comin.nix new file mode 100644 index 0000000..2317b06 --- /dev/null +++ b/templates/raspberry-pi/services/comin.nix @@ -0,0 +1,24 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.comin + ]; + + services.comin = { + enable = true; + remotes = [ + { + name = "origin"; + url = "https://${constants.services.forgejo.fqdn}/steffen/cryodev-server.git"; + branches.main.name = "main"; + } + ]; + }; +} diff --git a/templates/raspberry-pi/services/default.nix b/templates/raspberry-pi/services/default.nix new file mode 100644 index 0000000..e4c52d5 --- /dev/null +++ b/templates/raspberry-pi/services/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./nginx.nix + ./openssh.nix + ./tailscale.nix + ./netdata.nix + ./comin.nix + ]; +} diff --git a/templates/raspberry-pi/services/netdata.nix b/templates/raspberry-pi/services/netdata.nix new file mode 100644 index 0000000..9ff9a6c --- /dev/null +++ b/templates/raspberry-pi/services/netdata.nix @@ -0,0 +1,31 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + services.netdata = { + enable = true; + config = { + stream = { + enabled = "yes"; + destination = "${constants.hosts.cryodev-main.ip}:${toString constants.services.netdata.port}"; + "api key" = config.sops.placeholder."netdata/stream/child-uuid"; + }; + }; + }; + + # Make sure sops is enabled/imported for this host to handle the secret + imports = [ outputs.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + secrets."netdata/stream/child-uuid" = { + owner = "netdata"; + group = "netdata"; + }; + }; +} diff --git a/templates/raspberry-pi/services/nginx.nix b/templates/raspberry-pi/services/nginx.nix new file mode 100644 index 0000000..00734e5 --- /dev/null +++ b/templates/raspberry-pi/services/nginx.nix @@ -0,0 +1,14 @@ +{ + outputs, + ... +}: + +{ + imports = [ outputs.nixosModules.nginx ]; + + services.nginx = { + enable = true; + forceSSL = true; + openFirewall = true; + }; +} diff --git a/templates/raspberry-pi/services/openssh.nix b/templates/raspberry-pi/services/openssh.nix new file mode 100644 index 0000000..f71c084 --- /dev/null +++ b/templates/raspberry-pi/services/openssh.nix @@ -0,0 +1,12 @@ +{ + outputs, + ... +}: + +{ + imports = [ + outputs.nixosModules.openssh + ]; + + services.openssh.enable = true; +} diff --git a/templates/raspberry-pi/services/tailscale.nix b/templates/raspberry-pi/services/tailscale.nix new file mode 100644 index 0000000..b9ab502 --- /dev/null +++ b/templates/raspberry-pi/services/tailscale.nix @@ -0,0 +1,28 @@ +{ + config, + pkgs, + outputs, + constants, + ... +}: + +{ + imports = [ + outputs.nixosModules.tailscale + ]; + + services.tailscale = { + enable = true; + # Connect to our own headscale instance + loginServer = "https://${constants.services.headscale.fqdn}"; + # Allow SSH access over Tailscale + enableSSH = true; + # Use MagicDNS names + acceptDNS = true; + + # Auth key for automated enrollment + authKeyFile = config.sops.secrets."tailscale/auth-key".path; + }; + + sops.secrets."tailscale/auth-key" = { }; +} diff --git a/templates/raspberry-pi/users.nix b/templates/raspberry-pi/users.nix new file mode 100644 index 0000000..3570761 --- /dev/null +++ b/templates/raspberry-pi/users.nix @@ -0,0 +1,9 @@ +{ inputs, outputs, ... }: + +{ + imports = [ + outputs.nixosModules.normalUsers + ../../users/steffen + ../../users/cryotherm + ]; +} diff --git a/users/cryotherm/default.nix b/users/cryotherm/default.nix new file mode 100644 index 0000000..7de4740 --- /dev/null +++ b/users/cryotherm/default.nix @@ -0,0 +1,7 @@ +{ + normalUsers.cryotherm = { + extraGroups = [ ]; + # No sshKeyFiles, so password login only (if allowed) or local access + sshKeyFiles = [ ]; + }; +} diff --git a/users/steffen/default.nix b/users/steffen/default.nix new file mode 100644 index 0000000..a7503e9 --- /dev/null +++ b/users/steffen/default.nix @@ -0,0 +1,10 @@ +{ outputs, ... }: + +{ + normalUsers.steffen = { + extraGroups = [ + "wheel" + ]; + sshKeyFiles = [ ./pubkeys/X670E.pub ]; + }; +} diff --git a/users/steffen/pubkeys/X670E.pub b/users/steffen/pubkeys/X670E.pub new file mode 100644 index 0000000..068fb57 --- /dev/null +++ b/users/steffen/pubkeys/X670E.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKNTpsF9Z313gWHiHi4SvjeXI4Mh80mtq0bR0AjsZr/SnPsXEiM8/ODbQNJ806qHLFSA4uA4vaevdZIJkpDqRIQviW7zHGp/weRh2+2ynH8RyFqJvsWIqWn8G5wXPYcRZ6eFjcqKraAQC46ITER4+NPgdC6Cr+dsHWyIroBep4m3EGhSLYNRaMYoKZ5aqD2jJLBolokVfseF06Y7tQ3QSwUioXgiodBdZ9hgXc/5AJdsXSxJMHmRArqbHwbWI0fhwkX+0jiUpOMXMGsJZx5G20X70mQpJu+UnQsGcw+ylQw6ZYtFmzNcYmOS//91DTzraHprnrENyb+pYV2UUZhKxjdkexpSBkkPoVEzMcw9+LCg4e/jsZ+urlRhdTPWW0/AaWJx3UJc1pHHu5UpIvQKfMdt9dZbgG7oYYE1JeCoTvtQKiBcdc54cmJuvwshaAkfN92tYGvj/L1Jeb06M34dycdCXGDGMIofMsZOsnDcHuY1CT82NlRjXmatAUOaO0rCbVNPluNmu4gmWhclQmhoUEmojBGaIXrcRuxrIJYZpWubQdBUCZiJFBJzEb2qnT0nFSe0Gu0tPOYdD/jcUVgYPRWggxQV6hssSlgERTJdzC5PhBnSe8Xi8W/rMgZA8+YBIKBJpJjF5HZTJ67EBZmNS3HWaZNIUmRXcgsONr41RCrw== steffen@X670E