From b311e21d5b488e62e993e40a11bfc09f8228f4c0 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 23 Mar 2026 19:16:05 +0100 Subject: [PATCH 001/185] feat(macos): Alacritty follows system light/dark appearance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New setup — due for review after you run darwin-rebuild switch and live with it for a few days. See CLAUDE.md (Alacritty) and assets/alacritty/README.md. - HM: import active-colors.toml + Catppuccin latte/mocha fragments - nix-darwin: launchd.user.agents.alacritty-system-theme + PATH helper - fish: background sync on Darwin; theme.sh no longer rebuilds for Alacritty - Remove switch-alacritty-theme.sh (sed + darwin-rebuild per toggle) Made-with: Cursor --- CLAUDE.md | 4 + assets/alacritty/README.md | 106 +++++------------- ...tppuccin-dark.yml => catppuccin-dark.toml} | 0 assets/alacritty/catppuccin-latte-colors.toml | 29 +++++ ...puccin-light.yml => catppuccin-light.toml} | 0 assets/alacritty/catppuccin-mocha-colors.toml | 29 +++++ .../com.user.alacritty-theme-sync.plist | 14 +-- nixos/fish.nix | 5 + nixos/home/danny/home.nix | 61 ++++------ nixos/hosts/macos.nix | 18 ++- scripts/alacritty-sync-system-theme.sh | 40 +++++++ scripts/setup-simple-theme-sync.sh | 28 +---- scripts/switch-alacritty-theme.sh | 80 ------------- scripts/sync-alacritty-theme.sh | 30 +---- scripts/theme.sh | 45 ++------ 15 files changed, 196 insertions(+), 293 deletions(-) rename assets/alacritty/{catppuccin-dark.yml => catppuccin-dark.toml} (100%) create mode 100644 assets/alacritty/catppuccin-latte-colors.toml rename assets/alacritty/{catppuccin-light.yml => catppuccin-light.toml} (100%) create mode 100644 assets/alacritty/catppuccin-mocha-colors.toml create mode 100644 scripts/alacritty-sync-system-theme.sh delete mode 100755 scripts/switch-alacritty-theme.sh diff --git a/CLAUDE.md b/CLAUDE.md index c7544c0..a433568 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,10 @@ cd ~/dotfiles/nixos && nix build .#installer-iso Custom nix-darwin module at `nixos/ollama.nix` (upstream PR not yet merged). Enabled on macOS via `nixos/hosts/macos.nix`. Runs as a launchd user agent with `ollama serve`. +## Alacritty (macOS) + +Terminal colors follow **System Settings → Appearance**: `programs.alacritty` imports `~/.config/alacritty/active-colors.toml`; `scripts/alacritty-sync-system-theme.sh` copies Catppuccin latte/mocha there when the OS mode changes. **nix-darwin** `launchd.user.agents.alacritty-system-theme` polls every 30s; **fish** runs the same script on interactive startup. After changing Nix, one `darwin-rebuild switch`. Details: `assets/alacritty/README.md`. + ## Shell Fish is the default shell. Bash auto-execs fish unless the parent process is already fish. Vi keybindings with fzf integration. Zoxide aliased to `cd`. diff --git a/assets/alacritty/README.md b/assets/alacritty/README.md index f590549..0ac58d4 100644 --- a/assets/alacritty/README.md +++ b/assets/alacritty/README.md @@ -1,102 +1,54 @@ -# Unified Theme Switching +# Alacritty + system appearance (macOS) -Unified theme switching that works across platforms (WSL and macOS) for Neovim, Alacritty, and Windows Terminal. +Alacritty follows **System Settings → Appearance** automatically. No `darwin-rebuild` when you change light/dark. -**This solution uses a single `theme` command that detects the platform and switches themes appropriately.** +## How it works -## How It Works +1. Home Manager installs Catppuccin palettes as `~/.config/alacritty/catppuccin-{latte,mocha}-colors.toml` and a generated `alacritty.toml` that sets `general.import` to `active-colors.toml`. +2. `scripts/alacritty-sync-system-theme.sh` copies the matching palette to `active-colors.toml`. Alacritty’s `live_config_reload` picks it up immediately. +3. **nix-darwin** runs that script from a user LaunchAgent every 30s (`nixos/hosts/macos.nix`: `launchd.user.agents.alacritty-system-theme`). It is also installed on `PATH` as `alacritty-sync-system-theme`. +4. **Fish** runs the same script in the background when you open an interactive shell on Darwin, so changes apply quickly without waiting for the next poll. -1. The `theme` command detects the platform (WSL vs macOS) -2. **On WSL:** Updates Neovim, Windows Terminal, and Windows system theme -3. **On macOS:** Updates Neovim and Alacritty themes via Nix configuration -4. Uses the same `nvim_color_scheme` file for Neovim on both platforms +## Optional manual LaunchAgent -## Setup +If you are not using the nix-darwin agent, you can load `assets/launchd/com.user.alacritty-theme-sync.plist` (adjust paths if needed). **Do not** load both the nix-darwin agent and this plist or you will run two pollers. -1. **The configuration is already set up!** The `theme` command is available as a fish alias. +If you previously used the old plist label `com.user.alacritty-theme-sync` and switch to nix-darwin only: -2. **To switch themes, use the unified command:** - ```bash - theme light # Switch to light theme - theme dark # Switch to dark theme - theme toggle # Toggle between light and dark themes - theme status # Show current theme status - ``` - -## Usage - -### Unified Theme Command ```bash -# Switch to light theme (works on WSL and macOS) -theme light +launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/com.user.alacritty-theme-sync.plist 2>/dev/null || true +``` -# Switch to dark theme (works on WSL and macOS) +## `theme` command (Neovim / WSL) + +The fish alias `theme` still updates `~/.local/share/nvim_color_scheme` (and Windows Terminal on WSL). On macOS, **Alacritty ignores** `theme light|dark` for terminal colors—it only follows System Settings. Neovim stays on whatever you set with `theme`; the Alacritty sync script does not touch the nvim file. + +```bash +theme light # Neovim (+ WSL terminal); macOS Alacritty unchanged (uses Appearance) theme dark - -# Toggle between light and dark themes theme toggle - -# Show current theme status theme status ``` -### What Gets Updated - -**On WSL:** -- Neovim theme (via `~/.local/share/nvim_color_scheme`) -- Windows Terminal settings -- Windows system theme -- Windows sound scheme - -**On macOS:** -- Neovim theme (via `~/.local/share/nvim_color_scheme`) -- Alacritty theme (via Nix configuration) - -### Manual Configuration (macOS only) -You can also manually edit `nixos/home/danny/home.nix` and change: -```nix -isLightTheme = true; # for light theme -isLightTheme = false; # for dark theme -``` -Then run: `cd nixos && sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air` - ## Files -- `scripts/theme.sh` - **Main unified theme switching script** -- `scripts/switch-alacritty-theme.sh` - Alacritty-specific theme switching (used by theme.sh) -- `scripts/detect-system-theme.sh` - Detects current macOS system theme (for reference) -- `nixos/fish.nix` - Contains the `theme` fish alias -- `nixos/home/danny/home.nix` - Contains the conditional Alacritty configuration -- `bashscripts/wsl_theme.sh` - Legacy WSL script (replaced by theme.sh) +- `assets/alacritty/catppuccin-latte-colors.toml` / `catppuccin-mocha-colors.toml` — palette fragments +- `scripts/alacritty-sync-system-theme.sh` — detect macOS appearance, copy palette, refresh nvim marker +- `scripts/sync-alacritty-theme.sh` — thin wrapper (backwards compatible) +- `nixos/home/danny/home.nix` — `programs.alacritty` + `xdg.configFile` for palettes +- `nixos/hosts/macos.nix` — LaunchAgent + `alacritty-sync-system-theme` in `environment.systemPackages` +- `nixos/fish.nix` — optional shell-open sync on Darwin -## Theme Colors +After changing Nix config, run `darwin-rebuild switch` once (see repo `AGENTS.md`). + +## Theme colors ### Catppuccin Latte (Light) + - Background: `#eff1f5` (base) - Foreground: `#4c4f69` (text) -- Accent colors optimized for light backgrounds ### Catppuccin Mocha (Dark) + - Background: `#1e1e2e` (base) - Foreground: `#cdd6f4` (text) -- Accent colors optimized for dark backgrounds - -## Integration with NixOS - -The solution uses Nix's conditional configuration in `home.nix`: - -```nix -colors = let - isLightTheme = true; # Change this to switch themes - - lightColors = { /* Catppuccin Latte colors */ }; - darkColors = { /* Catppuccin Mocha colors */ }; -in if isLightTheme then lightColors else darkColors; -``` - -This approach: -- ✅ Works with Spotlight/Applications folder launches -- ✅ No complex file reading or external dependencies -- ✅ Integrates cleanly with NixOS configuration -- ✅ Simple and reliable - just change a boolean and rebuild -- ✅ Easy to understand and maintain \ No newline at end of file diff --git a/assets/alacritty/catppuccin-dark.yml b/assets/alacritty/catppuccin-dark.toml similarity index 100% rename from assets/alacritty/catppuccin-dark.yml rename to assets/alacritty/catppuccin-dark.toml diff --git a/assets/alacritty/catppuccin-latte-colors.toml b/assets/alacritty/catppuccin-latte-colors.toml new file mode 100644 index 0000000..dbc54cd --- /dev/null +++ b/assets/alacritty/catppuccin-latte-colors.toml @@ -0,0 +1,29 @@ +# Catppuccin Latte — imported by main alacritty.toml; swapped by sync script. + +[colors.primary] +background = "#eff1f5" +foreground = "#4c4f69" + +[colors.cursor] +text = "#eff1f5" +cursor = "#dc8a78" + +[colors.normal] +black = "#5c5f77" +red = "#d20f39" +green = "#40a02b" +yellow = "#df8e1d" +blue = "#1e40af" +magenta = "#ea76cb" +cyan = "#179299" +white = "#acb0be" + +[colors.bright] +black = "#6c6f85" +red = "#d20f39" +green = "#40a02b" +yellow = "#df8e1d" +blue = "#1e40af" +magenta = "#ea76cb" +cyan = "#179299" +white = "#bcc0cc" diff --git a/assets/alacritty/catppuccin-light.yml b/assets/alacritty/catppuccin-light.toml similarity index 100% rename from assets/alacritty/catppuccin-light.yml rename to assets/alacritty/catppuccin-light.toml diff --git a/assets/alacritty/catppuccin-mocha-colors.toml b/assets/alacritty/catppuccin-mocha-colors.toml new file mode 100644 index 0000000..866f246 --- /dev/null +++ b/assets/alacritty/catppuccin-mocha-colors.toml @@ -0,0 +1,29 @@ +# Catppuccin Mocha — imported by main alacritty.toml; swapped by sync script. + +[colors.primary] +background = "#1e1e2e" +foreground = "#cdd6f4" + +[colors.cursor] +text = "#1e1e2e" +cursor = "#f5e0dc" + +[colors.normal] +black = "#45475a" +red = "#f38ba8" +green = "#a6e3a1" +yellow = "#f9e2af" +blue = "#89b4fa" +magenta = "#f5c2e7" +cyan = "#94e2d5" +white = "#bac2de" + +[colors.bright] +black = "#585b70" +red = "#f38ba8" +green = "#a6e3a1" +yellow = "#f9e2af" +blue = "#89b4fa" +magenta = "#f5c2e7" +cyan = "#94e2d5" +white = "#a6adc8" diff --git a/assets/launchd/com.user.alacritty-theme-sync.plist b/assets/launchd/com.user.alacritty-theme-sync.plist index c23206d..02a25da 100644 --- a/assets/launchd/com.user.alacritty-theme-sync.plist +++ b/assets/launchd/com.user.alacritty-theme-sync.plist @@ -4,28 +4,18 @@ Label com.user.alacritty-theme-sync - ProgramArguments - /Users/danny/dotfiles/scripts/sync-alacritty-theme.sh + /bin/bash + /Users/danny/dotfiles/scripts/alacritty-sync-system-theme.sh - StartInterval 30 - RunAtLoad - StandardOutPath /tmp/alacritty-theme-sync.log - StandardErrorPath /tmp/alacritty-theme-sync-error.log - - EnvironmentVariables - - PATH - /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin - diff --git a/nixos/fish.nix b/nixos/fish.nix index 5c7a2d2..c32edaa 100644 --- a/nixos/fish.nix +++ b/nixos/fish.nix @@ -24,6 +24,11 @@ set fish_greeting 🐟: (set_color yellow; date +%T; set_color green; date --iso-8601 2>/dev/null; or date +%F; set_color normal) + # Alacritty palette follows macOS appearance; refresh when opening a shell (LaunchAgent also polls). + if test (uname -s) = Darwin + bash ~/dotfiles/scripts/alacritty-sync-system-theme.sh >/dev/null 2>&1 & + end + # name: Default # author: Lily Ballard # edits: DannyDannyDanny diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index ce08a80..f2b4384 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, zen-browser ? null, ... }: +{ pkgs, lib, config, zen-browser ? null, ... }: { # TODO: remove next two lines from here or from flake.nix # home.username = "danny"; @@ -112,15 +112,26 @@ executable = true; }; - # Alacritty terminal configuration with conditional theme switching + # Palette fragments: synced to system appearance (see scripts/alacritty-sync-system-theme.sh). + xdg.configFile."alacritty/catppuccin-latte-colors.toml".source = + ../../../assets/alacritty/catppuccin-latte-colors.toml; + xdg.configFile."alacritty/catppuccin-mocha-colors.toml".source = + ../../../assets/alacritty/catppuccin-mocha-colors.toml; + + # Alacritty: base config + imported active-colors.toml (updated without rebuild) programs.alacritty = { enable = true; settings = { + general = { + live_config_reload = true; + import = [ "${config.xdg.configHome}/alacritty/active-colors.toml" ]; + }; window = { padding = { x = 8; y = 8; }; dynamic_padding = true; decorations = "buttonless"; - opacity = 0.95; + decorations_theme_variant = "None"; + opacity = 1.0; startup_mode = "Maximized"; option_as_alt = "Both"; }; @@ -134,43 +145,19 @@ program = "${pkgs.fish}/bin/fish"; }; }; - # Conditional colors based on system theme - colors = let - # Set this to true for light theme, false for dark theme - # You can change this and run 'darwin-rebuild switch' to switch themes - isLightTheme = true; - - # Catppuccin Latte (Light) colors - lightColors = { - primary = { background = "0xeff1f5"; foreground = "0x4c4f69"; }; - cursor = { text = "0xeff1f5"; cursor = "0xdc8a78"; }; - normal = { - black = "0x5c5f77"; red = "0xd20f39"; green = "0x40a02b"; yellow = "0xdf8e1d"; - blue = "0x1e40af"; magenta = "0xea76cb"; cyan = "0x179299"; white = "0xacb0be"; - }; - bright = { - black = "0x6c6f85"; red = "0xd20f39"; green = "0x40a02b"; yellow = "0xdf8e1d"; - blue = "0x1e40af"; magenta = "0xea76cb"; cyan = "0x179299"; white = "0xbcc0cc"; - }; - }; - - # Catppuccin Mocha (Dark) colors - darkColors = { - primary = { background = "0x1e1e2e"; foreground = "0xcdd6f4"; }; - cursor = { text = "0x1e1e2e"; cursor = "0xf5e0dc"; }; - normal = { - black = "0x45475a"; red = "0xf38ba8"; green = "0xa6e3a1"; yellow = "0xf9e2af"; - blue = "0x89b4fa"; magenta = "0xf5c2e7"; cyan = "0x94e2d5"; white = "0xbac2de"; - }; - bright = { - black = "0x585b70"; red = "0xf38ba8"; green = "0xa6e3a1"; yellow = "0xf9e2af"; - blue = "0x89b4fa"; magenta = "0xf5c2e7"; cyan = "0x94e2d5"; white = "0xa6adc8"; - }; - }; - in if isLightTheme then lightColors else darkColors; }; }; + # Writable copy (not a symlink to the store — cp in the sync script must replace a real file). + home.activation.alacrittySystemTheme = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + MOCHA="${config.xdg.configHome}/alacritty/catppuccin-mocha-colors.toml" + ACTIVE="${config.xdg.configHome}/alacritty/active-colors.toml" + if [ ! -f "$ACTIVE" ]; then + $DRY_RUN_CMD cp "$MOCHA" "$ACTIVE" + fi + $DRY_RUN_CMD ${pkgs.bash}/bin/bash "${../../../scripts/alacritty-sync-system-theme.sh}" || true + ''; + # TODO: Put user-installed binaries here if you want HM to own them (optional) # Fonts diff --git a/nixos/hosts/macos.nix b/nixos/hosts/macos.nix index 808a61f..2061f93 100644 --- a/nixos/hosts/macos.nix +++ b/nixos/hosts/macos.nix @@ -1,6 +1,9 @@ { config, lib, pkgs, ... }: -{ +let + alacrittySyncSystemTheme = pkgs.writeShellScriptBin "alacritty-sync-system-theme" + (builtins.readFile ../../scripts/alacritty-sync-system-theme.sh); +in { # Apple Silicon + nix-darwin basics nixpkgs.hostPlatform = "aarch64-darwin"; nix.enable = false; # Determinate manages Nix @@ -48,6 +51,19 @@ # User-specific packages and environment variables are now in home-manager (home.nix) # Only system-level packages should remain here if needed + environment.systemPackages = [ alacrittySyncSystemTheme ]; + + # Poll macOS appearance; updates ~/.config/alacritty/active-colors.toml (Alacritty live_config_reload). + launchd.user.agents.alacritty-system-theme = { + serviceConfig = { + RunAtLoad = true; + StartInterval = 30; + ProgramArguments = [ "${alacrittySyncSystemTheme}/bin/alacritty-sync-system-theme" ]; + StandardOutPath = "/tmp/alacritty-theme-sync.log"; + StandardErrorPath = "/tmp/alacritty-theme-sync-error.log"; + }; + }; + # Keep for darwin as well (tracks defaults across upgrades) # current max per nix-darwin; bump only if a release notes says so system.stateVersion = 6; diff --git a/scripts/alacritty-sync-system-theme.sh b/scripts/alacritty-sync-system-theme.sh new file mode 100644 index 0000000..5e03a94 --- /dev/null +++ b/scripts/alacritty-sync-system-theme.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Keep Alacritty in sync with macOS light/dark appearance. +# No Nix rebuild: copies a palette into active-colors.toml; Alacritty reloads via live_config_reload. + +set -euo pipefail + +[[ "$(uname -s)" == "Darwin" ]] || exit 0 + +XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +ALACRITTY_DIR="$XDG_CONFIG_HOME/alacritty" +ACTIVE="$ALACRITTY_DIR/active-colors.toml" +MARKER="$ALACRITTY_DIR/.last-system-theme" + +LIGHT="$ALACRITTY_DIR/catppuccin-latte-colors.toml" +DARK="$ALACRITTY_DIR/catppuccin-mocha-colors.toml" + +if [[ ! -f "$LIGHT" || ! -f "$DARK" ]]; then + echo "alacritty-sync-system-theme: missing $LIGHT or $DARK (run home-manager switch first)" >&2 + exit 1 +fi + +appearance="$(defaults read -g AppleInterfaceStyle 2>/dev/null || true)" +if [[ "$appearance" == "Dark" ]]; then + want="dark" +else + want="light" +fi + +if [[ -f "$MARKER" ]] && [[ "$(tr -d '\n' <"$MARKER")" == "$want" ]]; then + exit 0 +fi + +mkdir -p "$ALACRITTY_DIR" +printf '%s' "$want" >"$MARKER" + +if [[ "$want" == "light" ]]; then + cp "$LIGHT" "$ACTIVE" +else + cp "$DARK" "$ACTIVE" +fi diff --git a/scripts/setup-simple-theme-sync.sh b/scripts/setup-simple-theme-sync.sh index 820114d..733c9df 100755 --- a/scripts/setup-simple-theme-sync.sh +++ b/scripts/setup-simple-theme-sync.sh @@ -1,27 +1,9 @@ #!/bin/bash - -# Simple setup for Alacritty theme synchronization -# This creates the theme file and rebuilds the Nix configuration - +# One-shot sync of Alacritty palette + nvim marker from current macOS appearance. set -e - -# Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -echo "Setting up simple Alacritty theme synchronization..." - -# Run the theme sync script to create the initial theme file -echo "Detecting current system theme..." -"$SCRIPT_DIR/sync-alacritty-theme.sh" - +echo "Syncing from system appearance..." +"$SCRIPT_DIR/alacritty-sync-system-theme.sh" echo "" -echo "Setup complete!" -echo "" -echo "To apply the theme to Alacritty, run:" -echo " cd nixos && sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air" -echo "" -echo "To sync themes when your system theme changes:" -echo " $SCRIPT_DIR/sync-alacritty-theme.sh && cd nixos && sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air" -echo "" -echo "For automatic theme switching, you can set up a LaunchAgent or" -echo "run the sync script manually when needed." +echo "Done. Alacritty reloads colors automatically if live_config_reload is enabled." +echo "A LaunchAgent (nix-darwin: launchd.user.agents.alacritty-system-theme) runs this every 30s." diff --git a/scripts/switch-alacritty-theme.sh b/scripts/switch-alacritty-theme.sh deleted file mode 100755 index e3565af..0000000 --- a/scripts/switch-alacritty-theme.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash - -# Switch Alacritty theme by updating the Nix configuration -# This script changes the isLightTheme variable in home.nix and rebuilds - -set -e - -# Get the directory where this script is located -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DOTFILES_DIR="$(dirname "$SCRIPT_DIR")" -HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix" - -# Check if home.nix exists -if [ ! -f "$HOME_NIX" ]; then - echo "Error: home.nix not found at $HOME_NIX" - exit 1 -fi - -# Function to switch to light theme -switch_to_light() { - echo "Switching to light theme (Catppuccin Latte)..." - sed -i '' 's/isLightTheme = false;/isLightTheme = true;/' "$HOME_NIX" -} - -# Function to switch to dark theme -switch_to_dark() { - echo "Switching to dark theme (Catppuccin Mocha)..." - sed -i '' 's/isLightTheme = true;/isLightTheme = false;/' "$HOME_NIX" -} - -# Function to show current theme -show_current() { - if grep -q "isLightTheme = true" "$HOME_NIX"; then - echo "Current theme: Light (Catppuccin Latte)" - else - echo "Current theme: Dark (Catppuccin Mocha)" - fi -} - -# Function to rebuild the configuration -rebuild() { - echo "Rebuilding configuration..." - cd "$DOTFILES_DIR/nixos" - sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air -} - -# Main logic -case "${1:-}" in - "light") - switch_to_light - rebuild - ;; - "dark") - switch_to_dark - rebuild - ;; - "toggle") - if grep -q "isLightTheme = true" "$HOME_NIX"; then - switch_to_dark - else - switch_to_light - fi - rebuild - ;; - "status"|"current") - show_current - ;; - *) - echo "Usage: $0 {light|dark|toggle|status}" - echo "" - echo "Commands:" - echo " light - Switch to light theme (Catppuccin Latte)" - echo " dark - Switch to dark theme (Catppuccin Mocha)" - echo " toggle - Toggle between light and dark themes" - echo " status - Show current theme" - echo "" - show_current - exit 1 - ;; -esac diff --git a/scripts/sync-alacritty-theme.sh b/scripts/sync-alacritty-theme.sh index e2b1b36..772536b 100755 --- a/scripts/sync-alacritty-theme.sh +++ b/scripts/sync-alacritty-theme.sh @@ -1,31 +1,5 @@ #!/bin/bash - -# Sync Alacritty theme with system theme -# This script detects the current system theme and updates the theme file that Nix reads - +# Back-compat wrapper: sync Alacritty + nvim marker from macOS appearance. set -e - -# Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Paths -THEME_DETECTION_SCRIPT="$SCRIPT_DIR/detect-system-theme.sh" -THEME_FILE="/Users/danny/.local/share/nvim_color_scheme" - -# Create the directory if it doesn't exist -mkdir -p "$(dirname "$THEME_FILE")" - -# Detect current system theme -if [ ! -f "$THEME_DETECTION_SCRIPT" ]; then - echo "Error: Theme detection script not found at $THEME_DETECTION_SCRIPT" - exit 1 -fi - -CURRENT_THEME=$("$THEME_DETECTION_SCRIPT") -echo "Current system theme: $CURRENT_THEME" - -# Write the theme to the file that Nix reads -echo "$CURRENT_THEME" > "$THEME_FILE" - -echo "Theme file updated: $THEME_FILE" -echo "Run 'home-manager switch' to apply the new theme to Alacritty" \ No newline at end of file +exec "$SCRIPT_DIR/alacritty-sync-system-theme.sh" diff --git a/scripts/theme.sh b/scripts/theme.sh index f0a3e1d..4cad1e4 100755 --- a/scripts/theme.sh +++ b/scripts/theme.sh @@ -17,7 +17,7 @@ show_usage() { echo "" echo "This command switches themes for:" echo " - Neovim (via nvim_color_scheme file)" - echo " - Alacritty (via Nix configuration on macOS)" + echo " - Alacritty on macOS follows System Settings (LaunchAgent sync)" echo " - Windows Terminal (via settings.json on WSL)" echo " - Windows system theme (on WSL)" } @@ -43,19 +43,11 @@ show_status() { elif [[ "$OSTYPE" == "darwin"* ]]; then echo " Platform: macOS" - # Check Alacritty theme from Nix config - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - DOTFILES_DIR="$(dirname "$SCRIPT_DIR")" - HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix" - - if [ -f "$HOME_NIX" ]; then - if grep -q "isLightTheme = true" "$HOME_NIX"; then - echo " Alacritty: light (Catppuccin Latte)" - else - echo " Alacritty: dark (Catppuccin Mocha)" - fi + marker="$HOME/.config/alacritty/.last-system-theme" + if [ -f "$marker" ]; then + echo " Alacritty: follows system (active palette: $(tr -d '\n' <"$marker"))" else - echo " Alacritty: config file not found" + echo " Alacritty: follows system (sync after next login or run alacritty-sync-system-theme)" fi else echo " Platform: other" @@ -67,17 +59,10 @@ toggle_theme() { current_theme="" if [[ "$OSTYPE" == "darwin"* ]]; then - # On macOS, check the Nix config for current theme - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - DOTFILES_DIR="$(dirname "$SCRIPT_DIR")" - HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix" - - if [ -f "$HOME_NIX" ]; then - if grep -q "isLightTheme = true" "$HOME_NIX"; then - current_theme="light" - else - current_theme="dark" - fi + if [[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" == "Dark" ]]; then + current_theme="dark" + else + current_theme="light" fi fi @@ -183,18 +168,8 @@ if [[ -n "$WSL_DISTRO_NAME" ]]; then powershell.exe -Command "Set-ItemProperty -Path HKCU:\AppEvents\Schemes -Name '(Default)' -Value '.None'" elif [[ "$OSTYPE" == "darwin"* ]]; then - # macOS platform - handle Alacritty theme echo "Detected macOS platform" - - # Use the existing Alacritty theme switching script - alacritty_script="$DOTFILES_DIR/scripts/switch-alacritty-theme.sh" - if [ -f "$alacritty_script" ]; then - echo "Switching Alacritty theme to: $color_scheme" - "$alacritty_script" "$color_scheme" - else - echo "Warning: Alacritty theme script not found at $alacritty_script" - echo "Theme file updated, but Alacritty theme not switched" - fi + echo "Alacritty follows System Settings → Appearance (no rebuild). Neovim theme file updated above." else # Other platforms - just update the theme file From ca0d38316f6dd19bc88b7e2c03bd82f3edda4fd7 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 23 Mar 2026 19:16:22 +0100 Subject: [PATCH 002/185] docs(agents): link macOS Alacritty system-theme notes from AGENTS.md Made-with: Cursor --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index ba41641..d400a6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,8 @@ darwin-rebuild switch --flake . Do not automatically run rebuild commands - ask the user first. +On macOS, Alacritty colors follow **System Settings → Appearance** (no rebuild when toggling light/dark); see **CLAUDE.md** § Alacritty and `assets/alacritty/README.md`. + ## Repo is public No keys, tokens, or identifying secrets in the repo. Prefer `scp` or config outside the repo. From 82ce5a7fe8d307aeaac278788c35a07ae1622f46 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 23 Mar 2026 19:36:39 +0100 Subject: [PATCH 003/185] Rename macOS nix-darwin host to daniel-macbook-air.nix Match hostname Daniel-Macbook-Air; update flake and docs. Made-with: Cursor --- CLAUDE.md | 4 ++-- assets/alacritty/README.md | 4 ++-- nixos/flake.nix | 2 +- nixos/hosts/{macos.nix => daniel-macbook-air.nix} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename nixos/hosts/{macos.nix => daniel-macbook-air.nix} (100%) diff --git a/CLAUDE.md b/CLAUDE.md index a433568..7640fa6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,7 @@ cd ~/dotfiles/nixos && nix build .#installer-iso - **Flake:** `nixos/flake.nix` — single flake for all hosts - **Inputs:** nixpkgs-unstable, nix-darwin, home-manager, nixos-wsl, disko, zen-browser - **Host configs** in `nixos/hosts/`: - - `macos.nix` — Apple Silicon MacBook Air (aarch64-darwin, nix-darwin) + - `daniel-macbook-air.nix` — hostname `Daniel-Macbook-Air` (aarch64-darwin, nix-darwin) - `sunken-ship.nix` — NixOS home server (x86_64-linux) - `wsl.nix` — WSL (x86_64-linux) - `macbookair.nix` — old MacBook Air NixOS/WSL config @@ -52,7 +52,7 @@ cd ~/dotfiles/nixos && nix build .#installer-iso ## Ollama -Custom nix-darwin module at `nixos/ollama.nix` (upstream PR not yet merged). Enabled on macOS via `nixos/hosts/macos.nix`. Runs as a launchd user agent with `ollama serve`. +Custom nix-darwin module at `nixos/ollama.nix` (upstream PR not yet merged). Enabled on macOS via `nixos/hosts/daniel-macbook-air.nix`. Runs as a launchd user agent with `ollama serve`. ## Alacritty (macOS) diff --git a/assets/alacritty/README.md b/assets/alacritty/README.md index 0ac58d4..d063fb7 100644 --- a/assets/alacritty/README.md +++ b/assets/alacritty/README.md @@ -6,7 +6,7 @@ Alacritty follows **System Settings → Appearance** automatically. No `darwin-r 1. Home Manager installs Catppuccin palettes as `~/.config/alacritty/catppuccin-{latte,mocha}-colors.toml` and a generated `alacritty.toml` that sets `general.import` to `active-colors.toml`. 2. `scripts/alacritty-sync-system-theme.sh` copies the matching palette to `active-colors.toml`. Alacritty’s `live_config_reload` picks it up immediately. -3. **nix-darwin** runs that script from a user LaunchAgent every 30s (`nixos/hosts/macos.nix`: `launchd.user.agents.alacritty-system-theme`). It is also installed on `PATH` as `alacritty-sync-system-theme`. +3. **nix-darwin** runs that script from a user LaunchAgent every 30s (`nixos/hosts/daniel-macbook-air.nix`: `launchd.user.agents.alacritty-system-theme`). It is also installed on `PATH` as `alacritty-sync-system-theme`. 4. **Fish** runs the same script in the background when you open an interactive shell on Darwin, so changes apply quickly without waiting for the next poll. ## Optional manual LaunchAgent @@ -36,7 +36,7 @@ theme status - `scripts/alacritty-sync-system-theme.sh` — detect macOS appearance, copy palette, refresh nvim marker - `scripts/sync-alacritty-theme.sh` — thin wrapper (backwards compatible) - `nixos/home/danny/home.nix` — `programs.alacritty` + `xdg.configFile` for palettes -- `nixos/hosts/macos.nix` — LaunchAgent + `alacritty-sync-system-theme` in `environment.systemPackages` +- `nixos/hosts/daniel-macbook-air.nix` — LaunchAgent + `alacritty-sync-system-theme` in `environment.systemPackages` - `nixos/fish.nix` — optional shell-open sync on Darwin After changing Nix config, run `darwin-rebuild switch` once (see repo `AGENTS.md`). diff --git a/nixos/flake.nix b/nixos/flake.nix index 6563f56..aa7e0d6 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -106,7 +106,7 @@ darwinConfigurations."Daniel-Macbook-Air" = nix-darwin.lib.darwinSystem { specialArgs = { inherit zen-browser; }; modules = [ - ./hosts/macos.nix + ./hosts/daniel-macbook-air.nix ./fish.nix # Home Manager on macOS diff --git a/nixos/hosts/macos.nix b/nixos/hosts/daniel-macbook-air.nix similarity index 100% rename from nixos/hosts/macos.nix rename to nixos/hosts/daniel-macbook-air.nix From f9edde90e472d4b7868d6435476c44cdf415e070 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 24 Mar 2026 10:19:41 +0100 Subject: [PATCH 004/185] fix(macos): make Alacritty system-theme sync robust New setup follow-up: ensure activation seeds a writable active-colors file and make theme sync always enforce the current system appearance. Made-with: Cursor --- nixos/home/danny/home.nix | 4 +++- scripts/alacritty-sync-system-theme.sh | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) mode change 100644 => 100755 scripts/alacritty-sync-system-theme.sh diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index f2b4384..91fd887 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -150,10 +150,12 @@ # Writable copy (not a symlink to the store — cp in the sync script must replace a real file). home.activation.alacrittySystemTheme = lib.hm.dag.entryAfter [ "writeBoundary" ] '' - MOCHA="${config.xdg.configHome}/alacritty/catppuccin-mocha-colors.toml" + MOCHA="${../../../assets/alacritty/catppuccin-mocha-colors.toml}" ACTIVE="${config.xdg.configHome}/alacritty/active-colors.toml" + $DRY_RUN_CMD mkdir -p "${config.xdg.configHome}/alacritty" if [ ! -f "$ACTIVE" ]; then $DRY_RUN_CMD cp "$MOCHA" "$ACTIVE" + $DRY_RUN_CMD chmod 0644 "$ACTIVE" fi $DRY_RUN_CMD ${pkgs.bash}/bin/bash "${../../../scripts/alacritty-sync-system-theme.sh}" || true ''; diff --git a/scripts/alacritty-sync-system-theme.sh b/scripts/alacritty-sync-system-theme.sh old mode 100644 new mode 100755 index 5e03a94..2238ef5 --- a/scripts/alacritty-sync-system-theme.sh +++ b/scripts/alacritty-sync-system-theme.sh @@ -26,15 +26,16 @@ else want="light" fi -if [[ -f "$MARKER" ]] && [[ "$(tr -d '\n' <"$MARKER")" == "$want" ]]; then - exit 0 -fi - mkdir -p "$ALACRITTY_DIR" printf '%s' "$want" >"$MARKER" if [[ "$want" == "light" ]]; then - cp "$LIGHT" "$ACTIVE" + tmp="$(mktemp "$ALACRITTY_DIR/active-colors.toml.XXXXXX")" + cp "$LIGHT" "$tmp" else - cp "$DARK" "$ACTIVE" + tmp="$(mktemp "$ALACRITTY_DIR/active-colors.toml.XXXXXX")" + cp "$DARK" "$tmp" fi + +chmod 0644 "$tmp" +mv -f "$tmp" "$ACTIVE" From befe2f8a5b9a103ef8346f1e3b5538c3d0a4ae69 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 24 Mar 2026 10:20:10 +0100 Subject: [PATCH 005/185] chore: remove unused Alacritty duplicates and dead script Clean up legacy Alacritty theme files and an unreferenced theme-detection script, and fix README links to existing setup docs. Made-with: Cursor --- README.md | 5 +++-- assets/alacritty/catppuccin-dark.toml | 28 -------------------------- assets/alacritty/catppuccin-light.toml | 28 -------------------------- scripts/detect-system-theme.sh | 13 ------------ 4 files changed, 3 insertions(+), 71 deletions(-) delete mode 100644 assets/alacritty/catppuccin-dark.toml delete mode 100644 assets/alacritty/catppuccin-light.toml delete mode 100755 scripts/detect-system-theme.sh diff --git a/README.md b/README.md index fbd92f9..a181f19 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Extension of [dannydannydanny/methodology](https://github.com/DannyDannyDanny/me ## Roadmap - [firefox-scrolling](firefox-scrolling.md) via terminal -- Server: [server](server.md); NixOS flake and bootstrap [nixos/readme.md](nixos/readme.md). SSH and secrets: [docs/ssh-and-secrets.md](docs/ssh-and-secrets.md). New server install (USB, LUKS, WiFi): [docs/server-installer-usb.md](docs/server-installer-usb.md). +- Server: [server-quickstart](server-quickstart.md); NixOS flake and bootstrap [nixos/readme.md](nixos/readme.md). SSH and secrets: [docs/ssh-and-secrets.md](docs/ssh-and-secrets.md). New server install (USB, LUKS, WiFi): [docs/server-installer-usb.md](docs/server-installer-usb.md). - nvim checkhealth; tmux setup; [fonts](https://www.programmingfonts.org/) / nerdfonts; [HN: home server](https://news.ycombinator.com/item?id=34271167) ## Windows @@ -40,9 +40,10 @@ ssh-add ~/.ssh/id_ed25519_github git clone git@github.com:DannyDannyDanny/dotfiles.git && cd dotfiles git config user.name "DannyDannyDanny" git config user.email "dth@taiga.ai" -bash install.sh ``` +Apply machine config from `nixos/` (see [CLAUDE.md](CLAUDE.md) for macOS rebuild commands or [nixos/readme.md](nixos/readme.md) for NixOS). + ## Good reads - [TODOs aren't for doing](https://sophiebits.com/2025/07/21/todos-arent-for-doing) diff --git a/assets/alacritty/catppuccin-dark.toml b/assets/alacritty/catppuccin-dark.toml deleted file mode 100644 index b19d112..0000000 --- a/assets/alacritty/catppuccin-dark.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Catppuccin Mocha (Dark) theme for Alacritty -[colors.primary] -background = "0x1e1e2e" # base -foreground = "0xcdd6f4" # text - -[colors.cursor] -text = "0x1e1e2e" # base -cursor = "0xf5e0dc" # rosewater - -[colors.normal] -black = "0x45475a" # surface1 -red = "0xf38ba8" # red -green = "0xa6e3a1" # green -yellow = "0xf9e2af" # yellow -blue = "0x89b4fa" # blue -magenta = "0xf5c2e7" # pink -cyan = "0x94e2d5" # teal -white = "0xbac2de" # subtext1 - -[colors.bright] -black = "0x585b70" # surface2 -red = "0xf38ba8" # red -green = "0xa6e3a1" # green -yellow = "0xf9e2af" # yellow -blue = "0x89b4fa" # blue -magenta = "0xf5c2e7" # pink -cyan = "0x94e2d5" # teal -white = "0xa6adc8" # subtext0 diff --git a/assets/alacritty/catppuccin-light.toml b/assets/alacritty/catppuccin-light.toml deleted file mode 100644 index 04df7e6..0000000 --- a/assets/alacritty/catppuccin-light.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Catppuccin Latte (Light) theme for Alacritty -[colors.primary] -background = "0xeff1f5" # base -foreground = "0x4c4f69" # text - -[colors.cursor] -text = "0xeff1f5" # base -cursor = "0xdc8a78" # rosewater - -[colors.normal] -black = "0x5c5f77" # surface1 -red = "0xd20f39" # red -green = "0x40a02b" # green -yellow = "0xdf8e1d" # yellow -blue = "0x1e40af" # blue -magenta = "0xea76cb" # pink -cyan = "0x179299" # teal -white = "0xacb0be" # subtext1 - -[colors.bright] -black = "0x6c6f85" # surface2 -red = "0xd20f39" # red -green = "0x40a02b" # green -yellow = "0xdf8e1d" # yellow -blue = "0x1e40af" # blue -magenta = "0xea76cb" # pink -cyan = "0x179299" # teal -white = "0xbcc0cc" # subtext0 diff --git a/scripts/detect-system-theme.sh b/scripts/detect-system-theme.sh deleted file mode 100755 index 39ccd67..0000000 --- a/scripts/detect-system-theme.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Detect macOS system theme (light/dark mode) -# Returns "light" or "dark" - -# Get the current appearance setting -appearance=$(defaults read -g AppleInterfaceStyle 2>/dev/null) - -if [ "$appearance" = "Dark" ]; then - echo "dark" -else - echo "light" -fi From 463249961e685658140f414ede81d67633bd1a5d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 24 Mar 2026 12:58:40 +0100 Subject: [PATCH 006/185] fix(nixos): replace removed light option and harden char-count script Restore flake checks by removing deprecated `programs.light` from sunken-ship and switching to brightnessctl guidance. Also clean up flake formatting and make the Raycast char-count script safer for empty input. Made-with: Cursor --- nixos/flake.nix | 3 --- nixos/hosts/sunken-ship.nix | 10 ++++++---- raycast-scripts/char-count.sh | 7 ++++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nixos/flake.nix b/nixos/flake.nix index aa7e0d6..7aebcde 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -1,8 +1,5 @@ { inputs = { - - - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixos-wsl.url = "github:nix-community/NixOS-WSL/main"; vscode-server.url = "github:nix-community/nixos-vscode-server"; diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index e12c666..4774878 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -22,8 +22,7 @@ in boot.kernelParams = [ "consoleblank=60" ]; # blank TTY after 60s to reduce burn-in # Turn off panel backlight after boot so the screen actually dims (consoleblank only blanks framebuffer). - # At the console, run: light -S 100 (or any 0–100) to restore brightness. - programs.light.enable = true; + # At the console, run: brightnessctl set 100% (or `brightnessctl max`) to restore brightness. systemd.services.server-backlight-off = { description = "Turn off panel backlight after console idle (reduce burn-in)"; after = [ "multi-user.target" ]; @@ -42,7 +41,7 @@ in users.users.danny = { isNormalUser = true; - extraGroups = [ "wheel" "video" ]; # video: backlight control via light(1) + extraGroups = [ "wheel" "video" ]; # video: backlight control via sysfs / brightnessctl # SSH keys: push via scp, don't commit. NixOS does not manage authorized_keys so scp'd keys persist. # Example: scp ~/.ssh/id_ed25519_sunken_ship.pub danny@server:/tmp/ then on server: mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys }; @@ -59,7 +58,10 @@ in # Passwordless sudo for wheel. security.sudo.wheelNeedsPassword = false; - environment.systemPackages = [ pkgs.git ]; # for clone/bootstrap and timer + environment.systemPackages = with pkgs; [ + git # clone/bootstrap and dotfiles-rebuild timer + brightnessctl # manual backlight; replaces removed `light` from nixpkgs + ]; # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { diff --git a/raycast-scripts/char-count.sh b/raycast-scripts/char-count.sh index 268cedc..f4f09e4 100755 --- a/raycast-scripts/char-count.sh +++ b/raycast-scripts/char-count.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash +set -euo pipefail # Required parameters: # @raycast.schemaVersion 1 @@ -7,11 +8,11 @@ # Optional parameters: # @raycast.icon 🤖 -# @raycast.argument1 { "type": "text", "placeholder": "Placeholder" } +# @raycast.argument1 { "type": "text", "placeholder": "Text to count" } # Documentation: # @raycast.description counts chars in selected text # @raycast.author DannyDannyDanny # @raycast.authorURL https://raycast.com/DannyDannyDanny -echo -n "$1" | wc -c +printf '%s' "${1:-}" | wc -c | awk '{ print $1 }' From be4233a53b74f857ea530ac6099cccb39c756657 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 24 Mar 2026 15:17:19 +0100 Subject: [PATCH 007/185] feat(macos): install Google Chrome via Homebrew cask Enable declarative Homebrew cask management on the macOS host so Google Chrome is installed during darwin activation and stale Homebrew items are cleaned up with zap. Made-with: Cursor --- nixos/hosts/daniel-macbook-air.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index 2061f93..63bb3f9 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -29,6 +29,14 @@ in { knownNetworkServices = [ "Wi-Fi" "Thunderbolt Bridge" ]; }; + homebrew = { + enable = true; + casks = [ + "google-chrome" + ]; + onActivation.cleanup = "zap"; + }; + # macOS niceties security.pam.services.sudo_local.touchIdAuth = true; From 309d97c708c94852c7bf0914bd6af724a9b9f476 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 25 Mar 2026 10:49:37 +0100 Subject: [PATCH 008/185] feat(nixos): add scheduled garbage collection and optimization for Nix :zap::art: Implement launchd daemons for automatic Nix garbage collection and store optimization on a weekly schedule. The configuration includes intervals for both tasks to ensure efficient management of Nix store resources. --- nixos/hosts/daniel-macbook-air.nix | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index 63bb3f9..11ead3e 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -3,6 +3,11 @@ let alacrittySyncSystemTheme = pkgs.writeShellScriptBin "alacritty-sync-system-theme" (builtins.readFile ../../scripts/alacritty-sync-system-theme.sh); + + # nix-darwin's nix.gc / nix.optimise require nix.enable; with Determinate (nix.enable = false) + # we schedule the same commands via launchd using nixpkgs' nix CLI (same defaults as upstream modules). + nixGcInterval = [{ Weekday = 7; Hour = 3; Minute = 15; }]; + nixOptimiseInterval = [{ Weekday = 7; Hour = 4; Minute = 15; }]; in { # Apple Silicon + nix-darwin basics nixpkgs.hostPlatform = "aarch64-darwin"; @@ -72,6 +77,24 @@ in { }; }; + launchd.daemons = { + nix-gc-determ = { + command = + "${lib.getExe' pkgs.nix "nix-collect-garbage"} --delete-older-than 14d"; + serviceConfig = { + RunAtLoad = false; + StartCalendarInterval = nixGcInterval; + }; + }; + nix-store-optimise-determ = { + command = "${lib.getExe' pkgs.nix "nix-store"} --optimise"; + serviceConfig = { + RunAtLoad = false; + StartCalendarInterval = nixOptimiseInterval; + }; + }; + }; + # Keep for darwin as well (tracks defaults across upgrades) # current max per nix-darwin; bump only if a release notes says so system.stateVersion = 6; From bded1b359d4ba1d3d0bbb75dbc8b5d8f45094e06 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 25 Mar 2026 10:54:41 +0100 Subject: [PATCH 009/185] fix(macos): install disk-inventory-x via Homebrew cask Use the Homebrew cask on Apple Silicon because the nixpkgs package is x86_64-darwin only, and document the reason inline to prevent future regressions. Made-with: Cursor --- nixos/hosts/daniel-macbook-air.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index 11ead3e..354cfb1 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -38,6 +38,7 @@ in { enable = true; casks = [ "google-chrome" + "disk-inventory-x" # Apple Silicon uses Homebrew; nixpkgs package is x86_64-darwin only. ]; onActivation.cleanup = "zap"; }; From afbc87be2b038ed27d0d29584e129aa5e1dda848 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 25 Mar 2026 14:51:31 +0100 Subject: [PATCH 010/185] fix(macos): sync Neovim Catppuccin with system appearance Write ~/.local/share/nvim_color_scheme from the same macOS Appearance probe as Alacritty; trim the nvim theme line read for robustness. Made-with: Cursor --- nixos/neovim.nix | 5 ++++- scripts/alacritty-sync-system-theme.sh | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/nixos/neovim.nix b/nixos/neovim.nix index 587aca0..ce8bb5c 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -16,8 +16,11 @@ local config_file = os.getenv("HOME")..'/.local/share/nvim_color_scheme' local f = io.open(config_file, "r") if f ~= nil then - local system_theme = f:read() + local system_theme = f:read("*l") io.close(f) + if system_theme then + system_theme = system_theme:gsub("^%s+", ""):gsub("%s+$", "") + end if system_theme == 'dark' then vim.cmd("set bg=dark") elseif system_theme == 'light' then diff --git a/scripts/alacritty-sync-system-theme.sh b/scripts/alacritty-sync-system-theme.sh index 2238ef5..c323973 100755 --- a/scripts/alacritty-sync-system-theme.sh +++ b/scripts/alacritty-sync-system-theme.sh @@ -29,6 +29,11 @@ fi mkdir -p "$ALACRITTY_DIR" printf '%s' "$want" >"$MARKER" +# Neovim (see nixos/neovim.nix): same file as `theme` on WSL; keep in sync with Appearance. +NVIM_THEME="${XDG_DATA_HOME:-$HOME/.local/share}/nvim_color_scheme" +mkdir -p "$(dirname "$NVIM_THEME")" +printf '%s\n' "$want" >"$NVIM_THEME" + if [[ "$want" == "light" ]]; then tmp="$(mktemp "$ALACRITTY_DIR/active-colors.toml.XXXXXX")" cp "$LIGHT" "$tmp" From d8e5cbe26a572cd95ef9f1846a5a7616cb940a21 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 14:20:31 +0200 Subject: [PATCH 011/185] fix(nixos): add safe.directory for dotfiles-rebuild service :wrench: Git refuses to operate on /etc/dotfiles owned by danny when the service runs as root. Pass safe.directory via environment variables. --- nixos/hosts/sunken-ship.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 4774878..5739d3f 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -67,6 +67,9 @@ in systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; path = with pkgs; [ git nix ]; + environment.GIT_CONFIG_COUNT = "1"; + environment.GIT_CONFIG_KEY_0 = "safe.directory"; + environment.GIT_CONFIG_VALUE_0 = dotfilesDir; script = '' set -euo pipefail cd ${dotfilesDir} From e2b820aac0bb35fbabe524a187bbcd6b483da105 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 15:38:27 +0200 Subject: [PATCH 012/185] =?UTF-8?q?feat(nixos):=20add=20UxPlay=20AirPlay?= =?UTF-8?q?=20receiver=20to=20sunken-ship=20=F0=9F=93=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable Avahi for mDNS discovery and open firewall ports for AirPlay mirroring (TCP 7000-7100, UDP 5353/6000-6001/7011). --- nixos/hosts/sunken-ship.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 5739d3f..5ddb99b 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -61,8 +61,22 @@ in environment.systemPackages = with pkgs; [ git # clone/bootstrap and dotfiles-rebuild timer brightnessctl # manual backlight; replaces removed `light` from nixpkgs + uxplay # AirPlay mirroring receiver ]; + # Avahi (mDNS) — required for AirPlay discovery. + services.avahi = { + enable = true; + nssmdns4 = true; + publish = { enable = true; userServices = true; }; + }; + + # Open firewall for AirPlay (mDNS + UxPlay default ports). + networking.firewall = { + allowedTCPPorts = [ 7000 7001 7100 ]; + allowedUDPPorts = [ 5353 6000 6001 7011 ]; + }; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From ee2fa1e5f1ecae49c316d1c182933e884a737f9f Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 15:44:00 +0200 Subject: [PATCH 013/185] =?UTF-8?q?feat(nixos):=20add=20UxPlay=20systemd?= =?UTF-8?q?=20service=20on=20sunken-ship=20=F0=9F=94=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audio-only AirPlay receiver that starts at boot, advertises as "sunken-ship", and auto-restarts on failure. --- nixos/hosts/sunken-ship.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 5ddb99b..c9b982a 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -77,6 +77,20 @@ in allowedUDPPorts = [ 5353 6000 6001 7011 ]; }; + # UxPlay AirPlay receiver — audio-only, runs as a persistent service. + systemd.services.uxplay = { + description = "UxPlay AirPlay receiver"; + after = [ "network-online.target" "avahi-daemon.service" ]; + wants = [ "network-online.target" "avahi-daemon.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0"; + Restart = "on-failure"; + RestartSec = 5; + User = "danny"; + }; + }; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From 84715596f51dfb4afec7584f81bdf4822f359ffc Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 15:49:35 +0200 Subject: [PATCH 014/185] =?UTF-8?q?feat(nixos):=20add=20PipeWire=20and=20f?= =?UTF-8?q?ix=20UxPlay=20audio=20on=20sunken-ship=20=F0=9F=94=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable PipeWire with ALSA/PulseAudio compat so GStreamer can output audio. Move UxPlay to a user service with linger so it can reach PipeWire. Add danny to audio group, add alsa-utils for debugging. --- nixos/hosts/sunken-ship.nix | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index c9b982a..4d0ea5f 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -41,7 +41,7 @@ in users.users.danny = { isNormalUser = true; - extraGroups = [ "wheel" "video" ]; # video: backlight control via sysfs / brightnessctl + extraGroups = [ "wheel" "video" "audio" ]; # video: backlight; audio: sound devices # SSH keys: push via scp, don't commit. NixOS does not manage authorized_keys so scp'd keys persist. # Example: scp ~/.ssh/id_ed25519_sunken_ship.pub danny@server:/tmp/ then on server: mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys }; @@ -62,8 +62,19 @@ in git # clone/bootstrap and dotfiles-rebuild timer brightnessctl # manual backlight; replaces removed `light` from nixpkgs uxplay # AirPlay mirroring receiver + alsa-utils # aplay, amixer, arecord for audio debugging ]; + # PipeWire — sound server for UxPlay / GStreamer. + services.pipewire = { + enable = true; + alsa.enable = true; + pulse.enable = true; # PulseAudio compat for GStreamer pulsesink + }; + # PipeWire runs as a user service; ensure it starts for danny even without a login session. + systemd.user.services.pipewire.wantedBy = [ "default.target" ]; + systemd.user.services.pipewire-pulse.wantedBy = [ "default.target" ]; + # Avahi (mDNS) — required for AirPlay discovery. services.avahi = { enable = true; @@ -77,19 +88,21 @@ in allowedUDPPorts = [ 5353 6000 6001 7011 ]; }; - # UxPlay AirPlay receiver — audio-only, runs as a persistent service. - systemd.services.uxplay = { + # UxPlay AirPlay receiver — audio-only, runs as a user service under danny. + # Runs inside danny's user session so it can reach PipeWire/PulseAudio. + systemd.user.services.uxplay = { description = "UxPlay AirPlay receiver"; - after = [ "network-online.target" "avahi-daemon.service" ]; - wants = [ "network-online.target" "avahi-daemon.service" ]; - wantedBy = [ "multi-user.target" ]; + after = [ "pipewire-pulse.service" ]; + wants = [ "pipewire-pulse.service" ]; + wantedBy = [ "default.target" ]; serviceConfig = { ExecStart = "${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0"; Restart = "on-failure"; RestartSec = 5; - User = "danny"; }; }; + # Ensure danny's user services start at boot (not just on login). + users.users.danny.linger = true; # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { From cea6913cf32400b68078472c6ba4995ec58ed4bd Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 16:16:18 +0200 Subject: [PATCH 015/185] =?UTF-8?q?fix(nixos):=20route=20UxPlay=20audio=20?= =?UTF-8?q?directly=20to=20ALSA=20on=20sunken-ship=20=F0=9F=94=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop PipeWire (WirePlumber fails to detect ALSA cards without a graphical session). Use GStreamer alsasink with plughw:USB,0 to output directly to the Scarlett Solo. --- nixos/hosts/sunken-ship.nix | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 4d0ea5f..ec67ab2 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -65,16 +65,6 @@ in alsa-utils # aplay, amixer, arecord for audio debugging ]; - # PipeWire — sound server for UxPlay / GStreamer. - services.pipewire = { - enable = true; - alsa.enable = true; - pulse.enable = true; # PulseAudio compat for GStreamer pulsesink - }; - # PipeWire runs as a user service; ensure it starts for danny even without a login session. - systemd.user.services.pipewire.wantedBy = [ "default.target" ]; - systemd.user.services.pipewire-pulse.wantedBy = [ "default.target" ]; - # Avahi (mDNS) — required for AirPlay discovery. services.avahi = { enable = true; @@ -88,21 +78,21 @@ in allowedUDPPorts = [ 5353 6000 6001 7011 ]; }; - # UxPlay AirPlay receiver — audio-only, runs as a user service under danny. - # Runs inside danny's user session so it can reach PipeWire/PulseAudio. - systemd.user.services.uxplay = { + # UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA. + # Runs as a system service (no PipeWire needed on a headless server). + systemd.services.uxplay = { description = "UxPlay AirPlay receiver"; - after = [ "pipewire-pulse.service" ]; - wants = [ "pipewire-pulse.service" ]; - wantedBy = [ "default.target" ]; + after = [ "network-online.target" "avahi-daemon.service" ]; + wants = [ "network-online.target" "avahi-daemon.service" ]; + wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0"; + ExecStart = "${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as alsasink device=plughw:USB,0"; Restart = "on-failure"; RestartSec = 5; + User = "danny"; + SupplementaryGroups = [ "audio" ]; }; }; - # Ensure danny's user services start at boot (not just on login). - users.users.danny.linger = true; # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { From 657e250f750b629f191be97ccacc53ea1f1cf4e8 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 16:17:14 +0200 Subject: [PATCH 016/185] =?UTF-8?q?fix(nixos):=20quote=20UxPlay=20alsasink?= =?UTF-8?q?=20GStreamer=20pipeline=20arg=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index ec67ab2..7344970 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -86,7 +86,7 @@ in wants = [ "network-online.target" "avahi-daemon.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as alsasink device=plughw:USB,0"; + ExecStart = ''${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as "alsasink device=plughw:USB,0"''; Restart = "on-failure"; RestartSec = 5; User = "danny"; From 69e07dbc14464d31e68c5b9b388f8421e4141ad5 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 18:01:23 +0200 Subject: [PATCH 017/185] chore: remove unused uxplay.nix :fire: AirPlay config is inline in sunken-ship.nix; this file was never imported. --- nixos/uxplay.nix | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 nixos/uxplay.nix diff --git a/nixos/uxplay.nix b/nixos/uxplay.nix deleted file mode 100644 index 3ae199f..0000000 --- a/nixos/uxplay.nix +++ /dev/null @@ -1,17 +0,0 @@ -# article / guide: -# https://taoa.io/posts/Setting-up-ipad-screen-mirroring-on-nixos -# https://gist.github.com/cmrfrd/fe8f61da076f8a4a751bf8fc8cb579a5 -# also see: 24_nix_uxplay for script - -{ config, pkgs, ... }: -{ - services.avahi = { - nssmdns4 = true; - enable = true; - publish = { - enable = true; - userServices = true; - domain = true; - }; - }; -} From e44ef1fdcc04e00d25b115deacf93e2bc2119592 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 18:02:03 +0200 Subject: [PATCH 018/185] chore: remove legacy macbookair host config :fire: Superseded by daniel-macbook-air.nix (nix-darwin) and wsl.nix. Also removes its orphaned hardware-configuration.nix. --- nixos/flake.nix | 15 --- nixos/hardware-configuration.nix | 42 ------- nixos/hosts/macbookair.nix | 197 ------------------------------- 3 files changed, 254 deletions(-) delete mode 100644 nixos/hardware-configuration.nix delete mode 100644 nixos/hosts/macbookair.nix diff --git a/nixos/flake.nix b/nixos/flake.nix index 7aebcde..99f0eca 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -43,21 +43,6 @@ ]; }; - macbookair = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - nixos-wsl.nixosModules.default - vscode-server.nixosModules.default - ./hosts/macbookair.nix - ./hardware-configuration.nix - ./tmux.nix - # TODO: handle all user-level programs via home-manager - # ./neovim.nix # Now handled via home-manager - ./fish.nix - # home-manager.nixosModules.default - ]; - }; - sunken-ship = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ diff --git a/nixos/hardware-configuration.nix b/nixos/hardware-configuration.nix deleted file mode 100644 index 67723d9..0000000 --- a/nixos/hardware-configuration.nix +++ /dev/null @@ -1,42 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, modulesPath, ... }: - -{ - imports = - [ (modulesPath + "/installer/scan/not-detected.nix") - ]; - - boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" "wl" ]; - boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ]; - - fileSystems."/" = - { device = "/dev/disk/by-uuid/bf59e35a-f96d-489d-9b14-93f67d5e294d"; - fsType = "ext4"; - }; - - boot.initrd.luks.devices."luks-5b4978ab-ee25-4a85-8f56-0bdbe932f154".device = "/dev/disk/by-uuid/5b4978ab-ee25-4a85-8f56-0bdbe932f154"; - - fileSystems."/boot" = - { device = "/dev/disk/by-uuid/691B-AF9A"; - fsType = "vfat"; - options = [ "fmask=0022" "dmask=0022" ]; - }; - - swapDevices = - [ { device = "/dev/disk/by-uuid/08f3fa7a-1e84-4819-b696-1536bc44ef99"; } - ]; - - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.wlp2s0b1.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; -} diff --git a/nixos/hosts/macbookair.nix b/nixos/hosts/macbookair.nix deleted file mode 100644 index 72b7fc6..0000000 --- a/nixos/hosts/macbookair.nix +++ /dev/null @@ -1,197 +0,0 @@ -# Edit this configuration file to define what should be installed on -# your system. Help is available in the configuration.nix(5) man page -# and in the NixOS manual (accessible by running ‘nixos-help’). - -{ config, pkgs, ... }: - -{ - # Bootloader. - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; - - boot.initrd.luks.devices."luks-04715655-635c-46ee-8100-1a5a4f3700a5".device = "/dev/disk/by-uuid/04715655-635c-46ee-8100-1a5a4f3700a5"; - networking.hostName = "nixos"; # Define your hostname. - # NOTE: You can not use networking.networkmanager with networking.wireless - # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. - - nix.settings.experimental-features = [ "nix-command" "flakes" ]; # for vscode remote server - - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - - # Enable networking - networking.networkmanager.enable = true; - - # Set your time zone. - time.timeZone = "Europe/Copenhagen"; - - # Select internationalisation properties. - i18n.defaultLocale = "en_DK.UTF-8"; - - i18n.extraLocaleSettings = { - LC_ADDRESS = "da_DK.UTF-8"; - LC_IDENTIFICATION = "da_DK.UTF-8"; - LC_MEASUREMENT = "da_DK.UTF-8"; - LC_MONETARY = "da_DK.UTF-8"; - LC_NAME = "da_DK.UTF-8"; - LC_NUMERIC = "da_DK.UTF-8"; - LC_PAPER = "da_DK.UTF-8"; - LC_TELEPHONE = "da_DK.UTF-8"; - LC_TIME = "da_DK.UTF-8"; - }; - - # Enable the X11 windowing system. - services.xserver.enable = true; - - # Enable the KDE Plasma Desktop Environment. - services.displayManager.sddm.enable = true; - services.desktopManager.plasma6.enable = true; - - # Configure keymap in X11 - services.xserver = { - xkb.layout = "us"; - xkb.variant = ""; - }; - - programs.nix-ld.enable = true; - # TODO: move to home manager (?) - programs = { - direnv = { - enable = true; - enableFishIntegration = true; - nix-direnv.enable = true; - }; - }; - - # Enable CUPS to print documents. - services.printing.enable = true; - - # Enable sound with pipewire. - services.pulseaudio.enable = false; - hardware.alsa.enable = false; - security.rtkit.enable = true; - services.pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - # If you want to use JACK applications, uncomment this - #jack.enable = true; - - # use the example session manager (no others are packaged yet so this is enabled by default, - # no need to redefine it in your config for now) - #media-session.enable = true; - }; - - # Enable touchpad support (enabled default in most desktopManager). - # services.xserver.libinput.enable = true; - - # Define a user account. Don't forget to set a password with ‘passwd’. - users.users.dth = { - isNormalUser = true; - description = "dth"; - extraGroups = [ "networkmanager" "wheel" ]; - # TODO: use home manager to define user packages - packages = with pkgs; [ - vlc # video player - # kate # editor - ripgrep # faster grep - nextcloud-client # private cloud - # digikam # photo / video management - # thunderbird # bloat - ]; - }; - - # Install firefox. - programs.firefox.enable = true; - - # install kde partition manager - programs.partition-manager.enable = true; - - # TODO: install gnome disk manager - # programs.gnome-disks.enable = true; - - # Allow unfree packages - nixpkgs.config.allowUnfree = true; - nixpkgs.config.permittedInsecurePackages = [ - "broadcom-sta-6.30.223.271-59-6.18.10" - ]; - - boot.kernelModules = [ "wl" ]; - - # List packages installed in system profile. To search, run: - # $ nix search wget - environment.systemPackages = with pkgs; [ - - # tmux # activated in tmux.nix - # vim # using neovim in stead - # neovim # activated in neovim.nix - - git # version control - gh # github cli tool - - claude-code #anthropic's agentic coding cli - - ripgrep # faster grep - busybox # useful programs e.g. tree, unzip etc - openssl # cryptography swiss army knife - xdg-utils # terminal desktop intergrations (i.e. allow terminal to open browser) - xclip # terminal clipboard integration (i.e. allow terminal to r/w clipboard) - - fastfetch # system info - btop # resource monitor - wget # downloader - tldr # community driven manpage alternative - - ntfs3g # mount NTFS drives on linux - gptfdisk # formatting drives - like fdisk but better - # this stuff runs gparted - - # gimp # bloat image editing - # blender # bloat 3D modelling - # inkscape # bloat vector graphics / drawing - kdePackages.kdenlive # bloat video editor - - # desktop applications - thunderbird # email / calendar - telegram-desktop # instant messager - - cowsay - lolcat - - ]; - - # firefox smooth scrolling - environment.sessionVariables = { - MOZ_USE_XINPUT2 = "1"; - }; - - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; - - # List services that you want to enable: - - # Enable the OpenSSH daemon. - # services.openssh.enable = true; - - # Open ports in the firewall. - # networking.firewall.allowedTCPPorts = [ ... ]; - # networking.firewall.allowedUDPPorts = [ ... ]; - # Or disable the firewall altogether. - # networking.firewall.enable = false; - - # This value determines the NixOS release from which the default - # settings for stateful data, like file locations and database versions - # on your system were taken. It‘s perfectly fine and recommended to leave - # this value at the release version of the first install of this system. - # Before changing this value read the documentation for this option - # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). - system.stateVersion = "23.11"; # Did you read the comment? - -} From 6c057d945e3cf048a87c368b46419e305a99a220 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 18:02:27 +0200 Subject: [PATCH 019/185] chore: gitignore result symlink and openclaw dir :see_no_evil: --- .gitignore | 6 ++++++ result | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) delete mode 120000 result diff --git a/.gitignore b/.gitignore index 34b25fd..5eede6d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ env/ # Installer ISO live WiFi (SSID/PSK); see docs/server-installer-usb.md nixos/installer-wifi.nix + +# Nix build output symlink +result + +# Archived / local-only directories +openclaw-documents-repo/ diff --git a/result b/result deleted file mode 120000 index 8b617dd..0000000 --- a/result +++ /dev/null @@ -1 +0,0 @@ -/nix/store/x8ain9193yl3k10mk0bi667qp5iwk03w-lua-5.2.4 \ No newline at end of file From 533e5810a9a49445ccad6dea239118f26ab09a37 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 18:03:11 +0200 Subject: [PATCH 020/185] refactor(wsl): move user packages to home-manager :art: Enable home-manager on WSL, importing the shared home.nix config. Remove duplicate packages and env vars from wsl.nix that are now provided by home-manager (git, ripgrep, fzf, direnv, etc.). --- nixos/flake.nix | 16 ++++++++++++--- nixos/hosts/wsl.nix | 49 +++++++-------------------------------------- 2 files changed, 20 insertions(+), 45 deletions(-) diff --git a/nixos/flake.nix b/nixos/flake.nix index 99f0eca..df13a6c 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -36,10 +36,20 @@ vscode-server.nixosModules.default ./hosts/wsl.nix ./tmux.nix - # TODO: handle all user-level programs via home-manager - # ./neovim.nix # Now handled via home-manager ./fish.nix - # home-manager.nixosModules.default + + # Home Manager on WSL + home-manager.nixosModules.home-manager + ({ lib, ... }: { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.backupFileExtension = "backup"; + home-manager.users.dth = { ... }: { + home.username = "dth"; + home.homeDirectory = lib.mkForce "/home/dth"; + imports = [ ./home/danny/home.nix ]; + }; + }) ]; }; diff --git a/nixos/hosts/wsl.nix b/nixos/hosts/wsl.nix index f4786e4..ca7cd82 100644 --- a/nixos/hosts/wsl.nix +++ b/nixos/hosts/wsl.nix @@ -23,14 +23,7 @@ nix.settings.experimental-features = [ "nix-command" "flakes" ]; programs.nix-ld.enable = true; - # TODO: move to home manager (?) - programs = { - direnv = { - enable = true; - # enableFishIntegration = true; - nix-direnv.enable = true; - }; - }; + # direnv is now managed by home-manager (home/danny/home.nix) # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions @@ -47,42 +40,14 @@ }; nixpkgs.config.allowUnfree = true; - environment.variables = { - DBT_USER = "DNTH"; - EDITOR = "nvim"; - VISUAL = "nvim"; - }; + # User-level packages (git, ripgrep, fzf, etc.) are managed by + # home-manager via home/danny/home.nix. Only system-wide deps here. environment.systemPackages = with pkgs; [ - # tmux # activated in tmux.nix - # vim # using neovim in stead - # neovim # activated in neovim.nix - - git # version control - gh # github cli tool - - ripgrep # faster grep - wget # for vscode-server - busybox # useful programs e.g. tree, unzip etc - openssl # cryptography swiss army knife - xdg-utils # terminal desktop intergrations (i.e. allow terminal to open browser) - - # make default.nix in python project folders instead of using a top-level python environment manager - # pyenv - # poetry - - fastfetch # system info - btop # resource monitor - tldr # community alternative to man - fzf # fuzzy finder - jq # parse json - - # gimp # bloat - # blender # bloat - # inkscape # bloat - - cowsay - lolcat + wget # needed by vscode-server + busybox # useful system utilities (tree, unzip, etc.) + xdg-utils # terminal desktop integrations + jq # parse json ]; services.ollama.enable = true; From e997a83c93100ec4ccc8d2ef5753bf31b0426bb5 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 18:03:37 +0200 Subject: [PATCH 021/185] refactor(neovim): migrate to extraLuaConfig :art: Move Lua config out of VimScript heredoc into proper extraLuaConfig. Use vim.opt and vim.keymap.set instead of legacy set/nnoremap. Keep VimScript only for settings that are simpler in vim (colorscheme, netrw, let g: vars). --- nixos/neovim.nix | 88 +++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/nixos/neovim.nix b/nixos/neovim.nix index ce8bb5c..8f65b29 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -4,74 +4,62 @@ programs.neovim = { enable = true; defaultEditor = true; - # TODO: refactor (some parts) to extraLuaConfig + + # VimScript settings (options that have no Lua equivalent or are simpler in vim) extraConfig = '' set title - set mouse=a set nohlsearch set number let mapleader="," - lua << EOF - local config_file = os.getenv("HOME")..'/.local/share/nvim_color_scheme' - local f = io.open(config_file, "r") - if f ~= nil then - local system_theme = f:read("*l") - io.close(f) - if system_theme then - system_theme = system_theme:gsub("^%s+", ""):gsub("%s+$", "") - end - if system_theme == 'dark' then - vim.cmd("set bg=dark") - elseif system_theme == 'light' then - vim.cmd("set bg=light") - else - print('warning: expected value "light" or "dark"') - print(' got:', system_theme) - print(' expected path:', config_file) - end - else - print('warning: nvim color scheme not found') - print(' expected path:', config_file) - end - EOF - - colorscheme catppuccin " catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha + colorscheme catppuccin " netrw (dir listing) settings let g:netrw_liststyle = 3 let g:netrw_banner = 0 let g:netrw_browse_split = 3 - let g:netrw_winsize = 25 " % of page + let g:netrw_winsize = 25 + ''; - set listchars=tab:→\ ,space:·,nbsp:␣,trail:•,eol:¶,precedes:«,extends:» - set clipboard+=unnamedplus + extraLuaConfig = '' + -- Auto-detect system theme (dark/light) from marker file + local config_file = os.getenv("HOME") .. "/.local/share/nvim_color_scheme" + local f = io.open(config_file, "r") + if f then + local theme = f:read("*l") + f:close() + if theme then + theme = theme:gsub("^%s+", ""):gsub("%s+$", "") + end + if theme == "dark" or theme == "light" then + vim.opt.background = theme + else + vim.notify("nvim_color_scheme: expected 'light' or 'dark', got: " .. tostring(theme), vim.log.levels.WARN) + end + end - " Replace-all is aliased to S. - nnoremap S :%s//g + -- General options + vim.opt.mouse = "a" + vim.opt.listchars = { tab = "→ ", space = "·", nbsp = "␣", trail = "•", eol = "¶", precedes = "«", extends = "»" } + vim.opt.clipboard:append("unnamedplus") + vim.opt.spell = true + vim.opt.spelllang = "en_us" - " save file with ,w - map w :w - - " spellcheck - set spell spelllang=en_us - setlocal spell! spelllang=en_us + -- Keymaps + vim.keymap.set("n", "S", ":%s//g", { desc = "Replace all" }) + vim.keymap.set("n", "w", ":w", { desc = "Save file" }) ''; plugins = with pkgs.vimPlugins; [ - vim-surround # shortcuts for setting () {} etc. - vim-gitgutter # git diff in sign column - # vim-airline # nice and light status bar # doesn't work nicely with tmux - # coc-nvim coc-git coc-highlight coc-python coc-rls coc-vetur coc-vimtex coc-yaml coc-html coc-json # auto completion - vim-nix # nix highlight - # vimtex # latex stuff - disabled due to build check issue - fzf-lua # fuzzy finder through lua - nerdtree # file structure inside nvim - rainbow # color parenthesis - # gruvbox-nvim # theme + vim-surround # shortcuts for setting () {} etc. + vim-gitgutter # git diff in sign column + vim-nix # nix highlight + fzf-lua # fuzzy finder through lua + nerdtree # file structure inside nvim + rainbow # color parenthesis catppuccin-nvim # theme - goyo-vim # write prose - limelight-vim # prose paragraph highlighter + goyo-vim # write prose + limelight-vim # prose paragraph highlighter ]; }; } From ee4c2db93fb906d916eef83816dabc4d4d3b6066 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 18:12:41 +0200 Subject: [PATCH 022/185] refactor: consolidate tmux config into home-manager :art: Remove system-level tmux.nix; home.nix is now the single source. Port resize-pane shortcuts (H/J/K/L) from the old config. --- nixos/flake.nix | 1 - nixos/home/danny/home.nix | 6 +++++ nixos/tmux.nix | 57 --------------------------------------- 3 files changed, 6 insertions(+), 58 deletions(-) delete mode 100644 nixos/tmux.nix diff --git a/nixos/flake.nix b/nixos/flake.nix index df13a6c..e55b552 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -35,7 +35,6 @@ nixos-wsl.nixosModules.default vscode-server.nixosModules.default ./hosts/wsl.nix - ./tmux.nix ./fish.nix # Home Manager on WSL diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 91fd887..22074e9 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -43,6 +43,12 @@ bind -r C-h select-window -t :- bind -r C-l select-window -t :+ + # Resize pane shortcuts + bind -r H resize-pane -L 10 + bind -r J resize-pane -D 10 + bind -r K resize-pane -U 10 + bind -r L resize-pane -R 10 + # split with dash and vbar bind | split-window -h -c "#{pane_current_path}" bind - split-window -v -c "#{pane_current_path}" diff --git a/nixos/tmux.nix b/nixos/tmux.nix deleted file mode 100644 index c09e84b..0000000 --- a/nixos/tmux.nix +++ /dev/null @@ -1,57 +0,0 @@ -{ config, pkgs, ... }: - -{ - programs.tmux = { - enable = true; - clock24 = true; - escapeTime = 20; - keyMode = "vi"; - historyLimit = 100000; - baseIndex = 1; - - extraConfig = '' - # remap prefix from ^+B to alt-f - unbind C-b - set -g prefix M-f - bind M-f send-prefix - - # nvim 'checkhealth' advice - set-option -g focus-events on - set-option -sa terminal-overrides ',xterm-256color:RGB' - set-option -g default-terminal "screen-256color" - - # enable mouse support for switching panes/windows - set -g mouse on - - # pane movement shortcuts - bind h select-pane -L - bind j select-pane -D - bind k select-pane -U - bind l select-pane -R - - # window selection - bind -r C-h select-window -t :- - bind -r C-l select-window -t :+ - - # Resize pane shortcuts - bind -r H resize-pane -L 10 - bind -r J resize-pane -D 10 - bind -r K resize-pane -U 10 - bind -r L resize-pane -R 10 - - # split with dash and vbar - bind | split-window -h -c "#{pane_current_path}" - bind - split-window -v -c "#{pane_current_path}" - - # server-tmux only: - # fix ssh agent when tmux is detached - # setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock - ''; - plugins = [ - # pkgs.tmuxPlugins.tmux-powerline # status bar - pkgs.tmuxPlugins.catppuccin - pkgs.tmuxPlugins.tmux-fzf # search tmux commands (prefix + F) - pkgs.tmuxPlugins.extrakto # fuzzyfind text history (prefix + tab) - ]; - }; -} From d9e569d4778db8daa5574f4967e34a78528db299 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 23:44:02 +0200 Subject: [PATCH 023/185] docs: remove stale macbookair/tmux refs from CLAUDE.md :memo: --- CLAUDE.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7640fa6..7538956 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,10 +31,9 @@ cd ~/dotfiles/nixos && nix build .#installer-iso - `daniel-macbook-air.nix` — hostname `Daniel-Macbook-Air` (aarch64-darwin, nix-darwin) - `sunken-ship.nix` — NixOS home server (x86_64-linux) - `wsl.nix` — WSL (x86_64-linux) - - `macbookair.nix` — old MacBook Air NixOS/WSL config - `server-install.nix` — disko-install target (LUKS + WiFi) -- **Home Manager:** integrated via `home-manager.darwinModules.home-manager` on macOS; user config in `nixos/home/danny/home.nix` -- **Shared modules:** `nixos/fish.nix` (fish + bash), `nixos/tmux.nix`, `nixos/ollama.nix` +- **Home Manager:** integrated on macOS, WSL, and sunken-ship; user config in `nixos/home/danny/home.nix` +- **Shared modules:** `nixos/fish.nix` (fish + bash), `nixos/ollama.nix` - **Darwin config name:** `Daniel-Macbook-Air` (must match in rebuild commands) ## Repo rules From d7bd99744c24397cc1fc17cc8026c911aab36cb2 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 23:44:18 +0200 Subject: [PATCH 024/185] docs: update nixos/readme.md with current host targets :memo: Replace stale #macbookair example with current macOS, WSL, and sunken-ship rebuild commands. --- nixos/readme.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nixos/readme.md b/nixos/readme.md index e264a5f..34e6316 100644 --- a/nixos/readme.md +++ b/nixos/readme.md @@ -3,9 +3,14 @@ Rebuild from dotfiles dir: ```bash -sudo nixos-rebuild switch --flake ~/dotfiles/nixos#macbookair -# or #wsl -# macOS: cd ~/dotfiles/nixos && darwin-rebuild switch --flake . +# macOS +cd ~/dotfiles/nixos && darwin-rebuild switch --flake . + +# WSL +sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl + +# sunken-ship (on server) +sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship ``` ## Server (sunken-ship) @@ -18,7 +23,7 @@ sudo mv /tmp/dotfiles /etc/dotfiles sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship --option accept-flake-config true ``` -If the daemon doesn’t have flakes: copy [server-configuration-with-flakes.nix](server-configuration-with-flakes.nix) to `/etc/nixos/configuration.nix`, run `sudo nixos-rebuild switch`, then build and switch to the flake (see [server-quickstart.md](../server-quickstart.md) for SSH keys). +If the daemon doesn't have flakes: copy [server-configuration-with-flakes.nix](server-configuration-with-flakes.nix) to `/etc/nixos/configuration.nix`, run `sudo nixos-rebuild switch`, then build and switch to the flake (see [server-quickstart.md](../server-quickstart.md) for SSH keys). SSH keys (not in repo): `scp ~/.ssh/*.pub danny@server:/tmp/`, then on server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`. See [docs/ssh-and-secrets.md](../docs/ssh-and-secrets.md). From 81c510ca168c9dea8dd359b95974f620bfb54dc9 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 23:44:38 +0200 Subject: [PATCH 025/185] docs: deduplicate AGENTS.md, defer to CLAUDE.md :memo: Remove rebuild protocol, repo rules, SSH key strategy, and server bootstrap info that was duplicated from CLAUDE.md. Keep only agent-specific operational details and learnings. --- AGENTS.md | 59 +++++++++++++------------------------------------------ 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d400a6b..1a11b77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,55 +1,24 @@ # Agent Instructions -## Nix/Darwin Rebuilds +See **CLAUDE.md** for build commands, rebuild protocol, flake architecture, repo rules, and SSH key strategy. This file covers agent-specific operational details. -**IMPORTANT**: When making changes to Nix configuration files (e.g., `nixos/home/danny/home.nix`, `nixos/flake.nix`, etc.), **always ask the user to rebuild** before assuming packages are available. +## Running commands on sunken-ship -To rebuild: -```bash -cd ~/dotfiles/nixos -darwin-rebuild switch --flake . -``` - -Do not automatically run rebuild commands - ask the user first. - -On macOS, Alacritty colors follow **System Settings → Appearance** (no rebuild when toggling light/dark); see **CLAUDE.md** § Alacritty and `assets/alacritty/README.md`. - -## Repo is public - -No keys, tokens, or identifying secrets in the repo. Prefer `scp` or config outside the repo. - -## SSH keys (one key per purpose) - -We use **one key per purpose**, not one per machine: separate keys for server access, GitHub, Forgejo (and other forges if needed). Benefits: limit blast radius if a key is compromised; clear revocation; clear which key is for what. - -- **Key names:** e.g. `id_ed25519_github`, `id_ed25519_forgejo`, `id_ed25519_servers` (Ed25519 preferred). -- **Config:** Use `~/.ssh/config` with `IdentityFile` and `IdentitiesOnly yes` per host so the right key is used. Keys and sensitive config stay outside the repo. -- **Server / NixOS:** Use actual key names on the machine (e.g. `id_ed25519_github`), not a generic `id_ed25519` (see Learnings below). - -## Server installer USB (new machines only) - -- Build: from **Linux** `cd ~/dotfiles/nixos && nix build .#installer-iso` (ISO is x86_64-linux only; cannot build on macOS). Or use official NixOS minimal ISO, write to USB, boot server, clone repo, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) when building custom ISO on Linux. - -## Learnings (NixOS server) - -- Minimal ISO: use Ethernet or the graphical installer (Wi‑Fi on minimal is fiddly). -- Server hardware: stub in repo; user replaces with `nixos-generate-config --show-hardware-config` from the server. -- Root password: console only; set danny’s password as root once for sudo. -- SSH keys: use actual key names on the machine (e.g. `id_ed25519_github`), not assumed `id_ed25519`. - -## Server (sunken-ship) - -- **Commit and push** before testing on the server; it clones/pulls from origin. -- Bootstrap: server has no git until first rebuild. Use `nix run --extra-experimental-features "nix-command flakes" nixpkgs#git` to clone. Enable flakes in the daemon via `server-configuration-with-flakes.nix`: scp to server `/tmp/configuration.nix`, on server `sudo cp` to `/etc/nixos/configuration.nix`, then `sudo nixos-rebuild switch`. Then build flake and run `switch-to-configuration switch` (see nixos/readme.md). -- Auto-rebuild timer (`dotfiles-rebuild`) only runs after the system has been switched to the flake config. Check with `systemctl is-active dotfiles-rebuild.timer` on the server. - -### Running commands on sunken-ship - -From the Mac (where the dotfiles workspace lives), agents can SSH to sunken-ship to run commands. Use the sunken-ship key and the host alias or IP the user has configured (e.g. `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship` or `danny@192.168.1.x`). Example: +From the Mac, agents can SSH to sunken-ship: ```bash ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship 'hostname; ip addr' ``` -Rebuild on the server (flake is in `nixos/`): `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'`. The server has WiFi (see [docs/sunken-ship-wifi.md](docs/sunken-ship-wifi.md)); it remains reachable when ethernet is unplugged. +Rebuild on the server: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'`. The server has WiFi; it remains reachable when ethernet is unplugged. +## Server installer USB (new machines only) + +Build from **Linux**: `cd ~/dotfiles/nixos && nix build .#installer-iso` (x86_64-linux only; cannot build on macOS). Or use official NixOS minimal ISO, write to USB, boot server, clone repo, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) when building custom ISO on Linux. + +## Learnings (NixOS server) + +- Minimal ISO: use Ethernet or the graphical installer (Wi‑Fi on minimal is fiddly). +- Server hardware: stub in repo; user replaces with `nixos-generate-config --show-hardware-config` from the server. +- Root password: console only; set danny's password as root once for sudo. +- SSH keys: use actual key names on the machine (e.g. `id_ed25519_github`), not assumed `id_ed25519`. From 384b84fec2f8accec931a9b93c20aa17d7be2b81 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 30 Mar 2026 23:52:03 +0200 Subject: [PATCH 026/185] fix(nixos): enable nix-ld on sunken-ship for Claude Code :bug: The Claude Code remote CLI is a dynamically linked binary that fails on NixOS without a standard ld-linux stub. nix-ld provides it. --- nixos/hosts/sunken-ship.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 7344970..c53296a 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -37,6 +37,7 @@ in }; nix.settings.experimental-features = [ "nix-command" "flakes" ]; + programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) system.stateVersion = "24.11"; users.users.danny = { From 33e2e327b509909658721a8ab7bd158b6d45f0d3 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 10:01:22 +0200 Subject: [PATCH 027/185] fix(nixos): increase UxPlay ALSA buffer to reduce audio chop :bug: WiFi jitter causes underruns with default buffer. Set buffer-time to 200ms for smoother playback. --- nixos/hosts/sunken-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index c53296a..4b90677 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -87,7 +87,7 @@ in wants = [ "network-online.target" "avahi-daemon.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = ''${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as "alsasink device=plughw:USB,0"''; + ExecStart = ''${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as "alsasink device=plughw:USB,0 buffer-time=200000"''; Restart = "on-failure"; RestartSec = 5; User = "danny"; From 42462f57a28f07bffe697a1815a172d192333f25 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 10:08:30 +0200 Subject: [PATCH 028/185] docs: replace completed TODOs with Tailscale investigation :memo: --- TODO.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 67d900a..164e285 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,3 @@ # TODO -1. Create a setup/boot USB that: installs NixOS on the server with encryption and WiFi configured from the start; only required input is the server's name (e.g. sunken-ship). - * I have a set wifi SSID/PSK, assume servers will start up and be able to reach this wifi. - * I don't know how to go about the rest of this. - -2. Encrypt sunken-ship (LUKS); update hardware/config for encrypted root and boot. +- [ ] **Tailscale**: Investigate setting up Tailscale mesh VPN across devices (sunken-ship, Mac, iPhone). Would allow SSH, AirPlay, and Claude Code remote sessions from anywhere. Free tier, ~5 lines of NixOS config. Needs Tailscale app on Mac + iPhone. See: https://tailscale.com From 2c9cf1e8b49cc93ae85f9c169a4c2e8f2ade95d7 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 10:10:04 +0200 Subject: [PATCH 029/185] docs: restore USB installer and encryption TODOs :memo: Sunken-ship is not actually encrypted (plain ext4). USB installer workflow still needs refinement. --- TODO.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 164e285..ec4798e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ # TODO -- [ ] **Tailscale**: Investigate setting up Tailscale mesh VPN across devices (sunken-ship, Mac, iPhone). Would allow SSH, AirPlay, and Claude Code remote sessions from anywhere. Free tier, ~5 lines of NixOS config. Needs Tailscale app on Mac + iPhone. See: https://tailscale.com +- [ ] **USB installer**: Refine the installer USB workflow (`scripts/nixos-server-install.sh`, `disko-server.nix`, `installer-iso.nix`). Goal: boot USB, provide hostname, get a LUKS-encrypted NixOS server with WiFi ready to go. +- [ ] **Encrypt sunken-ship**: Currently running on plain ext4. Needs reinstall with LUKS via disko, or in-place migration (backup, reformat, restore). +- [ ] **Tailscale**: Investigate setting up Tailscale mesh VPN across devices (sunken-ship, Mac, iPhone). Would allow SSH, AirPlay, and Claude Code remote sessions from anywhere. Free tier, ~5 lines of NixOS config. See: https://tailscale.com From d4dbd73a8c6a396e353b3fb7cdb123b58f7792ee Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 11:37:15 +0200 Subject: [PATCH 030/185] feat(nixos): add phantom-ship host and streamline server installer :sparkles: - New host config: phantom-ship.nix (SSH, auto-rebuild, nix-ld, Ethernet) - Hardware stub: phantom-ship-hardware.nix (replaced by install script) - Add phantom-ship to flake.nix with home-manager - Install script now auto-provisions post-install: - Clones dotfiles to /etc/dotfiles - Installs SSH public key (SSH_PUBKEY_FILE env var) - Generates hardware config - Supports INSTALLER_HOSTNAME and INSTALLER_DISK env vars - Fix bootstrap-install.sh default branch to main - Update CLAUDE.md and server-installer-usb.md --- CLAUDE.md | 20 ++- docs/server-installer-usb.md | 208 ++++++++++---------------- nixos/flake.nix | 20 +++ nixos/hosts/phantom-ship-hardware.nix | 17 +++ nixos/hosts/phantom-ship.nix | 65 ++++++++ scripts/bootstrap-install.sh | 4 +- scripts/nixos-server-install.sh | 163 ++++++++++++++------ 7 files changed, 314 insertions(+), 183 deletions(-) create mode 100644 nixos/hosts/phantom-ship-hardware.nix create mode 100644 nixos/hosts/phantom-ship.nix diff --git a/CLAUDE.md b/CLAUDE.md index 7538956..43d0038 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,8 +6,9 @@ # macOS (from ~/dotfiles/nixos) darwin-rebuild switch --flake . -# NixOS server (SSH from mac, or on server) +# NixOS servers (SSH from mac, or on server) sudo nixos-rebuild switch --flake .#sunken-ship +sudo nixos-rebuild switch --flake .#phantom-ship # WSL sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl @@ -29,9 +30,10 @@ cd ~/dotfiles/nixos && nix build .#installer-iso - **Inputs:** nixpkgs-unstable, nix-darwin, home-manager, nixos-wsl, disko, zen-browser - **Host configs** in `nixos/hosts/`: - `daniel-macbook-air.nix` — hostname `Daniel-Macbook-Air` (aarch64-darwin, nix-darwin) - - `sunken-ship.nix` — NixOS home server (x86_64-linux) + - `sunken-ship.nix` — NixOS home server (x86_64-linux, WiFi + AirPlay) + - `phantom-ship.nix` — NixOS home server (x86_64-linux, Ethernet) - `wsl.nix` — WSL (x86_64-linux) - - `server-install.nix` — disko-install target (LUKS + WiFi) + - `server-install.nix` — disko-install target (LUKS) - **Home Manager:** integrated on macOS, WSL, and sunken-ship; user config in `nixos/home/danny/home.nix` - **Shared modules:** `nixos/fish.nix` (fish + bash), `nixos/ollama.nix` - **Darwin config name:** `Daniel-Macbook-Air` (must match in rebuild commands) @@ -46,8 +48,16 @@ cd ~/dotfiles/nixos && nix build .#installer-iso - SSH: `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship` - Remote rebuild: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'` -- Auto-rebuild timer: `dotfiles-rebuild` — only active after flake config switch. Check with `systemctl is-active dotfiles-rebuild.timer`. -- Server has WiFi; stays reachable when ethernet is unplugged. +- Auto-rebuild timer: `dotfiles-rebuild` — every 15 min. Check with `systemctl is-active dotfiles-rebuild.timer`. +- WiFi connected; stays reachable when ethernet is unplugged. +- Services: UxPlay (AirPlay receiver on Scarlett Solo) + +## Server (phantom-ship) + +- SSH: `ssh danny@phantom-ship` +- Remote rebuild: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#phantom-ship'` +- Auto-rebuild timer: same pattern as sunken-ship. +- Ethernet only (no WiFi). ## Ollama diff --git a/docs/server-installer-usb.md b/docs/server-installer-usb.md index bcd320d..4377f22 100644 --- a/docs/server-installer-usb.md +++ b/docs/server-installer-usb.md @@ -1,79 +1,100 @@ -# Server installer USB (NixOS + LUKS + WiFi) +# Server installer USB (NixOS + LUKS) -Bootable USB that installs NixOS on a new server with disk encryption (LUKS) and optional WiFi from first boot. Only required input is the hostname (and LUKS passphrase when disko creates the volume). Existing hosts are not modified. +Bootable USB that installs NixOS on a new server with disk encryption (LUKS). The install script handles partitioning, encryption, dotfiles cloning, SSH key setup, and hardware config generation. Only required inputs: hostname, LUKS passphrase, and target disk. -## Quick path: boot USB → WiFi → SSH in → run bootstrap +## Quick path (Ethernet server like phantom-ship) -1. Boot the target machine from the NixOS installer USB. -2. On the live system, connect to Wi‑Fi (or plug in Ethernet). Check internet (e.g. `ping -c 2 8.8.8.8`). -3. On the **live** system, start SSH and set a password for the `nixos` user so you can log in from your Mac: +### Prep (on sunken-ship or any Linux box) + +1. Download the [NixOS minimal ISO](https://nixos.org/download.html#nixos-iso) on sunken-ship. +2. Plug in USB and write the ISO: + ```bash + # Find your USB device (e.g. /dev/sdc) + lsblk + sudo dd if=nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M + sync + ``` + +### Install (on the new server) + +3. Boot the new machine from USB, plug in Ethernet, verify connectivity (`ping 8.8.8.8`). +4. Start SSH on the live system so you can paste commands from your Mac: ```bash sudo systemctl start sshd sudo passwd nixos - hostname -I + hostname -I # note the IP ``` - Note the IP from `hostname -I`. -4. From your **Mac**: `ssh nixos@` (use the password you set). Now you can paste the bootstrap command instead of typing on the machine. -5. In that SSH session, run the bootstrap (installs NixOS with LUKS; prompts for hostname, disk, **danny password**, LUKS passphrase, then once more LUKS to set the password on disk): +5. From your **Mac**, scp your SSH public key and SSH in: ```bash - curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash + scp ~/.ssh/id_ed25519_phantom_ship.pub nixos@:/tmp/key.pub + ssh nixos@ ``` -6. When it finishes, reboot and remove the USB. Unlock LUKS at boot, then log in as **danny** with the password you set during the install. +6. Run the bootstrap (one command): + ```bash + curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/bootstrap-install.sh | \ + INSTALLER_HOSTNAME=phantom-ship SSH_PUBKEY_FILE=/tmp/key.pub sudo -E bash + ``` + This will prompt for: target disk, optional danny password, confirmation, and LUKS passphrase (twice: once for disko, once for post-install provisioning). -## Option A: Official NixOS ISO (works from macOS) + The script automatically: + - Partitions and encrypts the disk (LUKS + ext4) + - Installs NixOS with the hostname + - Clones dotfiles to `/etc/dotfiles` + - Installs your SSH public key + - Generates `phantom-ship-hardware.nix` -You **cannot** build the custom installer ISO on macOS (it is x86_64-linux only and `--system` is restricted). Use the official NixOS minimal ISO instead: +7. Reboot, remove USB, unlock LUKS. -1. Download the [minimal ISO](https://nixos.org/download.html#nixos-iso) (e.g. `nixos-minimal-*-x86_64-linux.iso`). -2. Write it to your USB (on macOS: `diskutil unmountDisk diskN`, then `sudo dd if=path/to/nixos-minimal-*.iso of=/dev/rdiskN bs=4m`). -3. Boot the server from the USB. Attach Ethernet or use the **graphical** ISO if you need Wi‑Fi on the live system. -4. On the live system, clone this repo and run the install script (see [Install on the server](#install-on-the-server) below). The script runs `disko-install` and does LUKS + hostname; no custom ISO needed. +### After first boot + +8. SSH in: `ssh danny@phantom-ship` +9. First rebuild to switch from generic `server-install` to `phantom-ship` config: + ```bash + cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#phantom-ship + ``` +10. Commit the generated `phantom-ship-hardware.nix` back to the repo. + +## Environment variables + +All optional; skip interactive prompts or add automation: + +| Variable | Description | +|----------|-------------| +| `INSTALLER_HOSTNAME` | Skip hostname prompt | +| `INSTALLER_DISK` | Skip disk prompt (validated as block device) | +| `SSH_PUBKEY_FILE` | Path to `.pub` file; installed to danny's `authorized_keys` | +| `FLAKE_REF` | Override flake reference (default: auto-detect from repo) | +| `INSTALLER_SYSTEM_CONFIG_FILE` | JSON file merged into `--system-config` (e.g. WiFi config) | + +## Option A: Official NixOS ISO (recommended) + +Cannot build the custom ISO on macOS (x86_64-linux only). Use the official NixOS minimal ISO: + +1. Download from [nixos.org](https://nixos.org/download.html#nixos-iso). +2. Write to USB from sunken-ship or any Linux box. +3. Boot, connect Ethernet, run bootstrap. ## Option B: Custom ISO (build on Linux only) -The custom ISO adds Wi‑Fi kernel modules and optional live Wi‑Fi; it must be built on **x86_64-linux** (or with a Nix remote builder configured for that system). Building on macOS will fail. +Adds WiFi kernel modules for servers that need WiFi on the live system. -### Build from sunken-ship (one command from your Mac) - -When the server is on the same network, run from the dotfiles repo: +### Build from sunken-ship ```bash ./scripts/build-installer-iso-on-server.sh ``` -This pushes the branch, SSHs to sunken-ship, clones the repo there, runs `nix build .#installer-iso`, and copies the ISO back to the current directory. Optional: `./scripts/build-installer-iso-on-server.sh sunken-ship /path/to/output`. - -### Build directly on a Linux machine - -From a Linux box (or on sunken-ship after SSH in): +### Build directly on Linux ```bash -cd ~/dotfiles/nixos -nix build .#installer-iso +cd ~/dotfiles/nixos && nix build .#installer-iso +# Write to USB: +sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M ``` -The image is at `result/iso/nixos-minimal-*.iso`. Write it to a USB stick (replace `sdX` with your device, e.g. `sda`): +## Live-system WiFi (optional, custom ISO only) -```bash -# Linux -sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress -sync -``` - -On macOS, use the disk number (e.g. `4` for `disk4`): - -```bash -sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/rdisk4 bs=4m -diskutil eject disk4 -``` - -Or adapt [scripts/make-ubuntu-usb.sh](../scripts/make-ubuntu-usb.sh) for the NixOS ISO path. - -## Live-system WiFi (optional) - -So the live system can reach the network (and fetch the flake) without Ethernet, add WiFi to the ISO at **build time**. Do not put SSID/PSK in the repo. - -1. Create **`nixos/installer-wifi.nix`** (gitignored) with your network: +Create `nixos/installer-wifi.nix` (gitignored): ```nix { @@ -82,77 +103,18 @@ So the live system can reach the network (and fetch the flake) without Ethernet, } ``` -2. Add it to the flake for the installer ISO only. In `nixos/flake.nix`, change the `installer-iso` modules to: +Add to flake's installer-iso modules, rebuild ISO on Linux. -```nix -installer-iso = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ ./installer-iso.nix ./installer-wifi.nix ]; # add installer-wifi.nix -}; -``` +## Installed-system WiFi (optional) -3. Ensure `nixos/installer-wifi.nix` is in `.gitignore`, then rebuild the ISO. - -If you skip this, use Ethernet on the live system or the graphical NixOS installer to join Wi‑Fi, then run the install script. - -## Install on the server - -1. Boot the server from the USB. -2. If you did not bake WiFi into the ISO, attach Ethernet or (on graphical installer) join Wi‑Fi so the machine has network. -3. Run **one** of the following (shortest first). - -**Shortest — fetch and run (no clone step):** -Exact URL (watch for typos: **.com** not .con, **usb** not ush, **DannyDannyDanny** with three capital Ds): +Pass a JSON file with wireless config: ```bash -curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash +sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi.json INSTALLER_HOSTNAME=my-server ./scripts/nixos-server-install.sh ``` -If you see `bash: 404: command not found`, the URL was wrong or the branch doesn’t exist. Check the URL, or verify first: `curl -sL "THE_URL_ABOVE" | head -1` should show `#!/bin/bash`, not HTML. - -To type less, create a [git.io](https://git.io) short link once (paste the raw URL above), then on the machine run: `curl -sL https://git.io/YOUR_CODE | sudo bash`. - -**Alternative — clone then run** (if you prefer not to pipe curl to bash): - -```bash -nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- clone https://github.com/USER/REPO.git /tmp/dotfiles && cd /tmp/dotfiles && git checkout server-installer-usb && sudo ./scripts/nixos-server-install.sh -``` - -If you see `command not found` when running the script, use `sudo bash ./scripts/nixos-server-install.sh` instead of `sudo ./scripts/...`. - -4. When prompted: enter **hostname** (e.g. `phantom-ship`), then **target disk** (default `/dev/sda`), then **y** to proceed. When disko creates the LUKS volume, enter your encryption passphrase. -5. When the script finishes, remove the USB and reboot. The new NixOS system will have LUKS root and the hostname you chose. - -## WiFi on the installed system (optional) - -To have WiFi configured from first boot (no manual step after reboot): - -1. Create a JSON file **outside the repo** with the config to merge (hostname is set by the script from the prompt): - -```json -{ - "networking": { - "wireless": { - "networks": { - "YourSSID": { "psk": "your-password" } - } - } - } -} -``` - -2. Copy that file onto the live system (e.g. put it on the USB or scp it). If the script is run with `jq` available and `INSTALLER_SYSTEM_CONFIG_FILE` set to that file, the script will merge it and set the hostname: - -```bash -sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi-config.json ./scripts/nixos-server-install.sh -``` - -If you omit this, the installed system still has `networking.wireless.enable = true`. Add credentials after first boot (e.g. [imperative wpa_supplicant config](sunken-ship-wifi.md)). - ## Manual install (without the script) -You can run disko-install yourself: - ```bash sudo nix run github:nix-community/disko/latest#disko-install -- \ --flake 'path:/tmp/dotfiles/nixos#server-install' \ @@ -160,21 +122,13 @@ sudo nix run github:nix-community/disko/latest#disko-install -- \ --system-config '{"networking":{"hostName":"my-server"}}' ``` -Adjust the flake path and `--system-config` (e.g. add WiFi) as needed. - -## After install - -- Add your SSH key: from your machine `scp ~/.ssh/id_ed25519_servers.pub danny@NEW-SERVER:/tmp/`, then on the server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`. -- To switch this machine to another host config in the same flake (e.g. a full server profile), clone the repo on the new system and run `sudo nixos-rebuild switch --flake /path/to/nixos#other-host`. - ## Summary | Step | Action | |------|--------| -| **From macOS** | Use Option A: download official NixOS minimal ISO, write to USB, boot server, clone repo, run install script. | -| **From Linux** | Option B: `nix build .#installer-iso` in `nixos/`, then write `result/iso/*.iso` to USB. | -| Optional live WiFi | (Custom ISO only) Add `installer-wifi.nix` (gitignored), include in flake, rebuild on Linux. | -| Boot | Boot server from USB | -| Install | On live system: `curl -sL https://raw.githubusercontent.com/.../server-installer-usb/scripts/bootstrap-install.sh | sudo bash` (or clone then `sudo ./scripts/nixos-server-install.sh`) | -| Optional installed WiFi | Set `INSTALLER_SYSTEM_CONFIG_FILE` to a JSON file with wireless config | -| Reboot | Remove USB, reboot; set root password if needed, add SSH keys | +| **Prep** | Download NixOS minimal ISO on sunken-ship, write to USB | +| **Boot** | Boot new server from USB, plug Ethernet | +| **Install** | `curl ... \| INSTALLER_HOSTNAME=phantom-ship SSH_PUBKEY_FILE=/tmp/key.pub sudo -E bash` | +| **Reboot** | Remove USB, unlock LUKS | +| **First rebuild** | `sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#phantom-ship` | +| **Commit** | Push generated `phantom-ship-hardware.nix` to repo | diff --git a/nixos/flake.nix b/nixos/flake.nix index e55b552..f74750f 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -72,6 +72,26 @@ ]; }; + phantom-ship = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ./hosts/phantom-ship.nix + + # Home Manager on NixOS + home-manager.nixosModules.home-manager + ({ lib, ... }: { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.backupFileExtension = "backup"; + home-manager.users.danny = { ... }: { + home.username = "danny"; + home.homeDirectory = lib.mkForce "/home/danny"; + home.stateVersion = "25.11"; + }; + }) + ]; + }; + # For disko-install: LUKS + WiFi; hostname/WiFi via --system-config. server-install = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; diff --git a/nixos/hosts/phantom-ship-hardware.nix b/nixos/hosts/phantom-ship-hardware.nix new file mode 100644 index 0000000..19413f8 --- /dev/null +++ b/nixos/hosts/phantom-ship-hardware.nix @@ -0,0 +1,17 @@ +# STUB — replace with output of: nixos-generate-config --show-hardware-config +# The install script saves the real config here automatically. +# Filesystems are managed by disko during install; only boot/initrd config here. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" ]; + boot.kernelModules = [ "kvm-intel" ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix new file mode 100644 index 0000000..6d52cad --- /dev/null +++ b/nixos/hosts/phantom-ship.nix @@ -0,0 +1,65 @@ +# NixOS server: bare config with SSH, auto-rebuild, Ethernet. +# Services (OpenClaw, etc.) to be added later. +{ config, lib, pkgs, ... }: + +let + dotfilesDir = "/etc/dotfiles"; + flakeRef = "${dotfilesDir}/nixos#phantom-ship"; +in +{ + imports = [ ./phantom-ship-hardware.nix ]; + + networking.hostName = "phantom-ship"; + networking.useDHCP = lib.mkDefault true; # Ethernet; no wireless + time.timeZone = "Europe/Copenhagen"; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) + system.stateVersion = "24.11"; + + users.users.danny = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + }; + + # Key-only auth; no password or keyboard-interactive. + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + # Passwordless sudo for wheel. + security.sudo.wheelNeedsPassword = false; + environment.systemPackages = with pkgs; [ + git # clone/bootstrap and dotfiles-rebuild timer + ]; + + # Pull dotfiles and rebuild if the repo has new commits. + systemd.services.dotfiles-rebuild = { + description = "Pull dotfiles and run nixos-rebuild if repo changed"; + path = with pkgs; [ git nix ]; + environment.GIT_CONFIG_COUNT = "1"; + environment.GIT_CONFIG_KEY_0 = "safe.directory"; + environment.GIT_CONFIG_VALUE_0 = dotfilesDir; + script = '' + set -euo pipefail + cd ${dotfilesDir} + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then + exit 0 + fi + git pull origin main + exec nixos-rebuild switch --flake ${flakeRef} + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.dotfiles-rebuild = { + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes + timerConfig.RandomizedDelaySec = "2min"; + }; +} diff --git a/scripts/bootstrap-install.sh b/scripts/bootstrap-install.sh index 66c1c58..617c8fc 100755 --- a/scripts/bootstrap-install.sh +++ b/scripts/bootstrap-install.sh @@ -1,13 +1,13 @@ #!/bin/bash # Fetch with curl and run to install NixOS (clone + run nixos-server-install.sh). # On the live system, run only: -# curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash +# curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/bootstrap-install.sh | sudo bash # # Optional: REPO_URL=... BRANCH=... (default repo and server-installer-usb) set -euo pipefail REPO_URL="${REPO_URL:-https://github.com/DannyDannyDanny/dotfiles.git}" -BRANCH="${BRANCH:-server-installer-usb}" +BRANCH="${BRANCH:-main}" DEST="/tmp/dotfiles" INSTALL_SCRIPT="$DEST/scripts/nixos-server-install.sh" diff --git a/scripts/nixos-server-install.sh b/scripts/nixos-server-install.sh index a70960b..f5a67e2 100644 --- a/scripts/nixos-server-install.sh +++ b/scripts/nixos-server-install.sh @@ -1,16 +1,17 @@ #!/bin/bash -# Run on a NixOS minimal live system (or installer ISO) to install NixOS with -# disko (LUKS + root). Prompts for hostname and target disk; optionally use -# INSTALLER_SYSTEM_CONFIG_FILE for WiFi etc. +# Install NixOS with disko (LUKS + root) on a live system. +# Prompts for hostname and target disk, then provisions the installed system +# (clones dotfiles, installs SSH key, generates hardware config). # # Usage (from repo root, e.g. /tmp/dotfiles): # sudo ./scripts/nixos-server-install.sh -# If you see "command not found", use: sudo bash ./scripts/nixos-server-install.sh # -# Optional: FLAKE_REF=github:User/dotfiles or path:/path/to/dotfiles/nixos -# -# Optional: INSTALLER_SYSTEM_CONFIG_FILE=/path/to/json with full --system-config -# (e.g. hostName + networking.wireless.networks). If unset, only hostname is passed. +# Environment variables (all optional): +# INSTALLER_HOSTNAME — skip hostname prompt +# INSTALLER_DISK — skip disk prompt (validated as block device) +# SSH_PUBKEY_FILE — path to .pub file; installed to danny's authorized_keys +# FLAKE_REF — override flake reference (default: auto-detect from repo) +# INSTALLER_SYSTEM_CONFIG_FILE — JSON file merged into --system-config set -euo pipefail FLAKE_REF="${FLAKE_REF:-}" @@ -30,21 +31,29 @@ if [[ "$EUID" -ne 0 ]]; then exit 1 fi -read -r -p "Hostname (e.g. my-server): " hostname +# --- Hostname --- +hostname="${INSTALLER_HOSTNAME:-}" +if [[ -z "$hostname" ]]; then + read -r -p "Hostname (e.g. phantom-ship): " hostname +fi if [[ -z "$hostname" ]]; then echo "Hostname cannot be empty." exit 1 fi -read -r -p "Target disk [default: /dev/sda]: " disk -disk="${disk:-/dev/sda}" +# --- Target disk --- +disk="${INSTALLER_DISK:-}" +if [[ -z "$disk" ]]; then + read -r -p "Target disk [default: /dev/sda]: " disk + disk="${disk:-/dev/sda}" +fi if [[ ! -b "$disk" ]]; then echo "Not a block device: $disk" exit 1 fi +# --- System config (hostname + optional extras) --- if [[ -n "${INSTALLER_SYSTEM_CONFIG_FILE:-}" ]] && [[ -f "$INSTALLER_SYSTEM_CONFIG_FILE" ]]; then - # Use provided JSON; ensure hostname is set if command -v jq &>/dev/null; then SYSTEM_CONFIG=$(jq --arg h "$hostname" '.networking.hostName = $h' "$INSTALLER_SYSTEM_CONFIG_FILE") else @@ -55,8 +64,9 @@ else SYSTEM_CONFIG='{"networking":{"hostName":"'"$hostname"'"}}' fi -# Prompt for password for danny so you can log in at console after reboot (no rescue needed) -read -r -p "Set a password for user danny (console/SSH login)? [y/N] " set_pass +# --- Optional: danny password --- +danny_pass="" +read -r -p "Set a password for user danny? [y/N] " set_pass if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then read -s -r -p "Password for danny: " danny_pass echo @@ -70,23 +80,27 @@ if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then echo "Password cannot be empty. Aborted." exit 1 fi - HASH=$(echo -n "$danny_pass" | openssl passwd -6 -stdin 2>/dev/null) || HASH=$(mkpasswd -6 -m sha-512 "$danny_pass" 2>/dev/null) - if [[ -z "$HASH" ]]; then - echo "Could not hash password (need openssl or mkpasswd). Skipping password." - else + HASH=$(echo -n "$danny_pass" | openssl passwd -6 -stdin 2>/dev/null) || HASH=$(mkpasswd -6 -m sha-512 "$danny_pass" 2>/dev/null) || true + if [[ -n "${HASH:-}" ]]; then if command -v jq &>/dev/null; then SYSTEM_CONFIG=$(echo "$SYSTEM_CONFIG" | jq --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}') else NEW_CONFIG=$(echo "$SYSTEM_CONFIG" | nix run nixpkgs#jq -- --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}' 2>/dev/null) - [[ -n "$NEW_CONFIG" ]] && SYSTEM_CONFIG="$NEW_CONFIG" || echo "Could not merge password (jq not found). Set after boot: passwd danny" + [[ -n "$NEW_CONFIG" ]] && SYSTEM_CONFIG="$NEW_CONFIG" || echo "Could not merge password. Set after boot: passwd danny" fi - [[ -n "$SYSTEM_CONFIG" ]] && echo "Password will be set for danny." + echo "Password will be set for danny." + else + echo "Could not hash password (need openssl or mkpasswd). Set after boot: passwd danny" fi fi +# --- Confirm and install --- +echo "" +echo "=== Install Summary ===" echo "Flake: ${FLAKE_REF}#server-install" echo "Disk: $disk" echo "Hostname: $hostname" +echo "SSH pubkey: ${SSH_PUBKEY_FILE:-none}" echo "System config: $SYSTEM_CONFIG" read -r -p "Proceed? [y/N] " confirm if [[ "${confirm,,}" != "y" && "${confirm,,}" != "yes" ]]; then @@ -100,33 +114,84 @@ nix run --extra-experimental-features "nix-command flakes" \ --disk main "$disk" \ --system-config "$SYSTEM_CONFIG" -# Set danny password directly on disk (Nix merge can fail); re-open LUKS and chroot -if [[ -n "${danny_pass:-}" ]]; then - echo "Setting password for danny on installed system (re-enter LUKS passphrase once)..." - read -s -r -p "LUKS passphrase: " luks_pass - echo - LUKS_DEV="/dev/disk/by-partlabel/disk-main-luks" - ESP_DEV="/dev/disk/by-partlabel/disk-main-ESP" - if [[ ! -b "$LUKS_DEV" ]]; then - LUKS_DEV="${disk}2" - ESP_DEV="${disk}1" - fi - if [[ -b "$LUKS_DEV" ]]; then - if ! echo -n "$luks_pass" | cryptsetup open "$LUKS_DEV" crypted --key-file -; then - echo "Wrong LUKS passphrase; set danny password after boot: passwd danny" - else - mount /dev/mapper/crypted /mnt - [[ -b "$ESP_DEV" ]] && mount "$ESP_DEV" /mnt/boot - mount --bind /dev /mnt/dev - mount --bind /proc /mnt/proc - mount --bind /sys /mnt/sys - echo "danny:${danny_pass}" | chroot /mnt chpasswd - umount -R /mnt - cryptsetup close crypted - echo "Password for danny set. Reboot and log in." - fi - unset luks_pass - else - echo "Could not find LUKS partition; set password after boot: passwd danny" - fi +echo "" +echo "=== Post-install provisioning ===" +echo "Re-opening LUKS to provision the installed system..." +read -s -r -p "LUKS passphrase: " luks_pass +echo + +LUKS_DEV="/dev/disk/by-partlabel/disk-main-luks" +ESP_DEV="/dev/disk/by-partlabel/disk-main-ESP" +if [[ ! -b "$LUKS_DEV" ]]; then + LUKS_DEV="${disk}2" + ESP_DEV="${disk}1" fi + +if [[ ! -b "$LUKS_DEV" ]]; then + echo "Could not find LUKS partition. Complete these steps manually after boot:" + echo " 1. Clone dotfiles: sudo git clone ... /etc/dotfiles" + echo " 2. Add SSH key: mkdir -p ~/.ssh && cat /tmp/key.pub >> ~/.ssh/authorized_keys" + echo " 3. Generate hardware config: nixos-generate-config --show-hardware-config > /etc/dotfiles/nixos/hosts/${hostname}-hardware.nix" + exit 0 +fi + +if ! echo -n "$luks_pass" | cryptsetup open "$LUKS_DEV" crypted --key-file -; then + echo "Wrong LUKS passphrase. Complete provisioning manually after boot." + unset luks_pass + exit 0 +fi +unset luks_pass + +mount /dev/mapper/crypted /mnt +[[ -b "$ESP_DEV" ]] && mount "$ESP_DEV" /mnt/boot +mount --bind /dev /mnt/dev +mount --bind /proc /mnt/proc +mount --bind /sys /mnt/sys + +# 1. Set danny password (belt-and-suspenders; Nix merge can fail) +if [[ -n "$danny_pass" ]]; then + echo "danny:${danny_pass}" | chroot /mnt chpasswd + echo " [ok] danny password set" +fi +unset danny_pass + +# 2. Clone dotfiles +if [[ ! -d /mnt/etc/dotfiles ]]; then + chroot /mnt nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ + clone https://github.com/DannyDannyDanny/dotfiles.git /etc/dotfiles + echo " [ok] dotfiles cloned to /etc/dotfiles" +else + echo " [skip] /etc/dotfiles already exists" +fi + +# 3. Install SSH public key +if [[ -n "${SSH_PUBKEY_FILE:-}" ]] && [[ -f "$SSH_PUBKEY_FILE" ]]; then + mkdir -p /mnt/home/danny/.ssh + cat "$SSH_PUBKEY_FILE" >> /mnt/home/danny/.ssh/authorized_keys + chmod 700 /mnt/home/danny/.ssh + chmod 600 /mnt/home/danny/.ssh/authorized_keys + chroot /mnt chown -R danny:users /home/danny/.ssh + echo " [ok] SSH public key installed" +elif [[ -n "${SSH_PUBKEY_FILE:-}" ]]; then + echo " [warn] SSH_PUBKEY_FILE set but file not found: $SSH_PUBKEY_FILE" +fi + +# 4. Generate hardware config +HW_CONFIG="/mnt/etc/dotfiles/nixos/hosts/${hostname}-hardware.nix" +if nixos-generate-config --show-hardware-config --root /mnt > "$HW_CONFIG" 2>/dev/null; then + echo " [ok] hardware config saved to hosts/${hostname}-hardware.nix" + echo " NOTE: Commit this file to the repo after first boot." +else + echo " [warn] nixos-generate-config failed; run manually after boot:" + echo " nixos-generate-config --show-hardware-config > /etc/dotfiles/nixos/hosts/${hostname}-hardware.nix" +fi + +umount -R /mnt +cryptsetup close crypted + +echo "" +echo "=== Done! ===" +echo "Remove the USB and reboot. After unlocking LUKS:" +echo " 1. SSH in: ssh danny@${hostname}" +echo " 2. First rebuild: cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#${hostname}" +echo " 3. Commit ${hostname}-hardware.nix back to the repo" From c7793b68eafb52fdc423e141ecb6c7bcd76b4eb4 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 14:24:58 +0200 Subject: [PATCH 031/185] =?UTF-8?q?fix:=20detect=20already-open=20LUKS=20d?= =?UTF-8?q?evice=20in=20post-install=20provisioning=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit disko-install leaves the LUKS device open; re-opening failed with "Device crypted already exists". Now detects the open mapper and skips the redundant cryptsetup open call. --- scripts/nixos-server-install.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/nixos-server-install.sh b/scripts/nixos-server-install.sh index f5a67e2..d8370ed 100644 --- a/scripts/nixos-server-install.sh +++ b/scripts/nixos-server-install.sh @@ -135,12 +135,16 @@ if [[ ! -b "$LUKS_DEV" ]]; then exit 0 fi -if ! echo -n "$luks_pass" | cryptsetup open "$LUKS_DEV" crypted --key-file -; then +if [[ -e /dev/mapper/crypted ]]; then + echo " [ok] LUKS device already open (left open by disko-install)" + unset luks_pass +elif ! echo -n "$luks_pass" | cryptsetup open "$LUKS_DEV" crypted --key-file -; then echo "Wrong LUKS passphrase. Complete provisioning manually after boot." unset luks_pass exit 0 +else + unset luks_pass fi -unset luks_pass mount /dev/mapper/crypted /mnt [[ -b "$ESP_DEV" ]] && mount "$ESP_DEV" /mnt/boot From f327b8e868d99c2775e7afde84472566fe805c8d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 14:26:46 +0200 Subject: [PATCH 032/185] =?UTF-8?q?feat:=20add=20post-install=20provisioni?= =?UTF-8?q?ng=20script=20=F0=9F=9B=A0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standalone script for completing provisioning after disko-install (mounts installed system, clones dotfiles, installs SSH key, generates hardware config). Run via curl for single-command provisioning. --- scripts/post-install-provision.sh | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100755 scripts/post-install-provision.sh diff --git a/scripts/post-install-provision.sh b/scripts/post-install-provision.sh new file mode 100755 index 0000000..2532b11 --- /dev/null +++ b/scripts/post-install-provision.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Run after disko-install when LUKS is already open. +# Usage: curl -fsSL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/post-install-provision.sh | sudo bash -s -- phantom-ship +set -euo pipefail + +HOSTNAME="${1:-phantom-ship}" +USB_DATA="/tmp/usb-data" +REPO="https://github.com/DannyDannyDanny/dotfiles.git" + +echo "=== Post-install provisioning for ${HOSTNAME} ===" + +# Mount installed system (LUKS already open from disko-install) +mount /dev/mapper/crypted /mnt +mount /dev/disk/by-partlabel/disk-main-ESP /mnt/boot 2>/dev/null || true +for d in dev proc sys; do mount --bind /$d /mnt/$d; done + +# Clone dotfiles +if [[ ! -d /mnt/etc/dotfiles ]]; then + chroot /mnt nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ + clone "$REPO" /etc/dotfiles + echo "[ok] dotfiles cloned" +else + echo "[skip] dotfiles already present" +fi + +# Install SSH key +if [[ -f "$USB_DATA/authorized_keys" ]]; then + mkdir -p /mnt/home/danny/.ssh + cp "$USB_DATA/authorized_keys" /mnt/home/danny/.ssh/authorized_keys + chmod 700 /mnt/home/danny/.ssh + chmod 600 /mnt/home/danny/.ssh/authorized_keys + chroot /mnt chown -R danny:users /home/danny/.ssh + echo "[ok] SSH key installed" +else + echo "[warn] no authorized_keys on USB — add SSH key manually after boot" +fi + +# Generate hardware config +nixos-generate-config --show-hardware-config --root /mnt \ + > /mnt/etc/dotfiles/nixos/hosts/${HOSTNAME}-hardware.nix +echo "[ok] hardware config saved to hosts/${HOSTNAME}-hardware.nix" + +# Copy hardware config to USB for committing from Mac +mkdir -p "$USB_DATA" +cp /mnt/etc/dotfiles/nixos/hosts/${HOSTNAME}-hardware.nix "$USB_DATA/" +echo "[ok] hardware config also copied to USB ($USB_DATA/)" + +umount -R /mnt +cryptsetup close crypted 2>/dev/null || true + +echo "" +echo "=== Done! Remove USB and reboot. ===" +echo "After unlocking LUKS, SSH in: ssh danny@${HOSTNAME}" +echo "Then: cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#${HOSTNAME}" +echo "Commit ${HOSTNAME}-hardware.nix from the USB back to the repo." From ef6e303a6032f4423303a02e9a970dfae3f8ec98 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 14:28:35 +0200 Subject: [PATCH 033/185] =?UTF-8?q?fix:=20run=20git=20from=20live=20system?= =?UTF-8?q?=20instead=20of=20chroot=20in=20provisioning=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chroot had no nix in PATH; clone directly into /mnt/etc/dotfiles from the live installer environment instead. --- scripts/post-install-provision.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/post-install-provision.sh b/scripts/post-install-provision.sh index 2532b11..a0ac006 100755 --- a/scripts/post-install-provision.sh +++ b/scripts/post-install-provision.sh @@ -14,10 +14,10 @@ mount /dev/mapper/crypted /mnt mount /dev/disk/by-partlabel/disk-main-ESP /mnt/boot 2>/dev/null || true for d in dev proc sys; do mount --bind /$d /mnt/$d; done -# Clone dotfiles +# Clone dotfiles (run git from live system, clone directly into /mnt) if [[ ! -d /mnt/etc/dotfiles ]]; then - chroot /mnt nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ - clone "$REPO" /etc/dotfiles + nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ + clone "$REPO" /mnt/etc/dotfiles echo "[ok] dotfiles cloned" else echo "[skip] dotfiles already present" From 245eb912a9df05991e67f1b4524b1847f6fae7ce Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 14:30:25 +0200 Subject: [PATCH 034/185] =?UTF-8?q?fix:=20find=20git/nix=20in=20PATH=20bef?= =?UTF-8?q?ore=20cloning=20dotfiles=20in=20provisioning=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live installer strips PATH under sudo; extend PATH to include nix profile dirs. Prefer git directly if available, fall back to nix run. No chroot involved. --- scripts/post-install-provision.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/post-install-provision.sh b/scripts/post-install-provision.sh index a0ac006..2e67ccc 100755 --- a/scripts/post-install-provision.sh +++ b/scripts/post-install-provision.sh @@ -14,10 +14,16 @@ mount /dev/mapper/crypted /mnt mount /dev/disk/by-partlabel/disk-main-ESP /mnt/boot 2>/dev/null || true for d in dev proc sys; do mount --bind /$d /mnt/$d; done -# Clone dotfiles (run git from live system, clone directly into /mnt) +# Clone dotfiles — find git or nix, clone directly into /mnt (no chroot) if [[ ! -d /mnt/etc/dotfiles ]]; then - nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ - clone "$REPO" /mnt/etc/dotfiles + # Ensure nix is in PATH (live installer may strip it under sudo) + export PATH=$PATH:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin + if command -v git &>/dev/null; then + git clone "$REPO" /mnt/etc/dotfiles + else + nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ + clone "$REPO" /mnt/etc/dotfiles + fi echo "[ok] dotfiles cloned" else echo "[skip] dotfiles already present" From 92593c7d0ae4233ad2475027e5fa7f72d5b70bc9 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 14:46:19 +0200 Subject: [PATCH 035/185] =?UTF-8?q?fix:=20add=20initialPassword=20fallback?= =?UTF-8?q?=20for=20phantom-ship=20console=20login=20=F0=9F=94=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No password was set, locking out console access. initialPassword gives a known fallback until SSH key is installed and password is changed. --- nixos/hosts/phantom-ship.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 6d52cad..afa71f7 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -20,6 +20,7 @@ in users.users.danny = { isNormalUser = true; extraGroups = [ "wheel" ]; + initialPassword = "changeme"; # console fallback; change after first login }; # Key-only auth; no password or keyboard-interactive. From 420f3881b5c2cb03cb80914fb0a905ff9695d79b Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 14:52:57 +0200 Subject: [PATCH 036/185] =?UTF-8?q?feat:=20add=20phantom-ship=20real=20har?= =?UTF-8?q?dware=20config=20=F0=9F=96=A5=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated by nixos-generate-config during install; cleaned up duplicate bind-mount entries from chroot detection. --- nixos/hosts/phantom-ship-hardware.nix | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/nixos/hosts/phantom-ship-hardware.nix b/nixos/hosts/phantom-ship-hardware.nix index 19413f8..36ac938 100644 --- a/nixos/hosts/phantom-ship-hardware.nix +++ b/nixos/hosts/phantom-ship-hardware.nix @@ -1,16 +1,27 @@ -# STUB — replace with output of: nixos-generate-config --show-hardware-config -# The install script saves the real config here automatically. -# Filesystems are managed by disko during install; only boot/initrd config here. +# Generated by nixos-generate-config on phantom-ship (cleaned of chroot bind-mount duplicates) { config, lib, pkgs, modulesPath, ... }: - { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" "rtsx_pci_sdmmc" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; - boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" ]; - boot.kernelModules = [ "kvm-intel" ]; + boot.initrd.luks.devices."crypted".device = "/dev/disk/by-uuid/796c1fcd-af8b-449a-90f2-9ebcb9640462"; + + fileSystems."/" = { + device = "/dev/mapper/crypted"; + fsType = "ext4"; + }; + + fileSystems."/boot" = { + device = "/dev/disk/by-uuid/70E3-E5D8"; + fsType = "vfat"; + options = [ "fmask=0022" "dmask=0022" ]; + }; + + swapDevices = [ ]; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; From 9f73571f55a2a6466a67b449d364333da822a33a Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 15:33:23 +0200 Subject: [PATCH 037/185] =?UTF-8?q?fix:=20restore=20bootloader=20config=20?= =?UTF-8?q?in=20phantom-ship=20hardware=20nix=20=F0=9F=A5=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accidentally stripped systemd-boot config when cleaning up duplicate fileSystems entries. --- nixos/hosts/phantom-ship-hardware.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nixos/hosts/phantom-ship-hardware.nix b/nixos/hosts/phantom-ship-hardware.nix index 36ac938..f47a34b 100644 --- a/nixos/hosts/phantom-ship-hardware.nix +++ b/nixos/hosts/phantom-ship-hardware.nix @@ -3,6 +3,9 @@ { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" "rtsx_pci_sdmmc" ]; boot.initrd.kernelModules = [ ]; boot.kernelModules = [ ]; From 1bfd96c0d0096ea46cbf24020f4d1425d9090496 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 15:36:34 +0200 Subject: [PATCH 038/185] =?UTF-8?q?feat:=20enable=20WiFi=20on=20phantom-sh?= =?UTF-8?q?ip=20=F0=9F=93=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses /etc/wpa_supplicant.conf for credentials (outside repo), same pattern as sunken-ship. --- nixos/hosts/phantom-ship.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index afa71f7..e286e4a 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -10,7 +10,8 @@ in imports = [ ./phantom-ship-hardware.nix ]; networking.hostName = "phantom-ship"; - networking.useDHCP = lib.mkDefault true; # Ethernet; no wireless + networking.useDHCP = lib.mkDefault true; + networking.wireless.enable = true; # credentials in /etc/wpa_supplicant.conf (outside repo) time.timeZone = "Europe/Copenhagen"; nix.settings.experimental-features = [ "nix-command" "flakes" ]; From c43cd0ee17398521446376ef2aadb19388c32bff Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 15:41:33 +0200 Subject: [PATCH 039/185] =?UTF-8?q?fix:=20enable=20redistributable=20firmw?= =?UTF-8?q?are=20on=20phantom-ship=20=F0=9F=93=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iwlwifi (Intel 8260 WiFi), GPU, and Bluetooth firmware were missing. --- nixos/hosts/phantom-ship.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index e286e4a..c981e50 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -12,6 +12,8 @@ in networking.hostName = "phantom-ship"; networking.useDHCP = lib.mkDefault true; networking.wireless.enable = true; # credentials in /etc/wpa_supplicant.conf (outside repo) + + hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware time.timeZone = "Europe/Copenhagen"; nix.settings.experimental-features = [ "nix-command" "flakes" ]; From a5f0d36d821d177de506bebdb4aa3fa17e41fe80 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 31 Mar 2026 17:19:45 +0200 Subject: [PATCH 040/185] =?UTF-8?q?chore:=20claim=20rusty-anchor=20as=20ne?= =?UTF-8?q?xt=20hostname=20=F0=9F=A6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old iMac G4 / Power Mac G4 (PowerPC) — will run OpenBSD. --- docs/hostname-candidates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hostname-candidates.md b/docs/hostname-candidates.md index ab25d51..acfd287 100644 --- a/docs/hostname-candidates.md +++ b/docs/hostname-candidates.md @@ -2,7 +2,7 @@ Two-word hostnames, non-human / non-specific. -- **Ships / sea:** sunken-ship, phantom-ship, rusty-anchor, salty-wind, stormy-wave, calm-harbor, distant-shore, foreign-port, wooden-hull, anchor-chain +- **Ships / sea:** sunken-ship ✓, phantom-ship ✓, **rusty-anchor** (next), salty-wind, stormy-wave, calm-harbor, distant-shore, foreign-port, wooden-hull, anchor-chain - **Prison / stone:** prison-rock, cold-stone, iron-chain, damp-cell, guard-tower, midnight-bell, stony-corridor, broken-chain - **Secrets / treasure:** buried-treasure, secret-cave, forgotten-tunnel, hidden-key, rusty-sword, faded-parchment, ancient-map, broken-seal, buried-chest - **Atmosphere:** strange-companion, masked-ball, poison-vial From 14c29945ebbd326ad77ff60cf5dd50c4b54c8580 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 1 Apr 2026 10:23:36 +0200 Subject: [PATCH 041/185] =?UTF-8?q?chore:=20add=20server=20alerting=20to?= =?UTF-8?q?=20TODO=20=F0=9F=94=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit phantom-ship lost power unnoticed; want alerting when servers go down. --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index ec4798e..9cdb4aa 100644 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,4 @@ - [ ] **USB installer**: Refine the installer USB workflow (`scripts/nixos-server-install.sh`, `disko-server.nix`, `installer-iso.nix`). Goal: boot USB, provide hostname, get a LUKS-encrypted NixOS server with WiFi ready to go. - [ ] **Encrypt sunken-ship**: Currently running on plain ext4. Needs reinstall with LUKS via disko, or in-place migration (backup, reformat, restore). - [ ] **Tailscale**: Investigate setting up Tailscale mesh VPN across devices (sunken-ship, Mac, iPhone). Would allow SSH, AirPlay, and Claude Code remote sessions from anywhere. Free tier, ~5 lines of NixOS config. See: https://tailscale.com +- [ ] **Server alerting**: Get notified when a server goes down (power loss, crash, etc). Options: simple ping-based cron on Mac sending macOS notifications, or lightweight uptime monitor (Uptime Kuma on one of the servers). From 5fcb54cc6342b03f9a5643d61cb6e052ce4f8d09 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 1 Apr 2026 13:04:19 +0200 Subject: [PATCH 042/185] =?UTF-8?q?feat:=20NAT=20+=20DHCP=20on=20phantom-s?= =?UTF-8?q?hip=20ethernet=20for=20rusty-anchor=20install=20=F0=9F=8C=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shares WiFi internet to rusty-anchor over ethernet via dnsmasq DHCP and iptables NAT. Rusty-anchor gets DHCP on 10.0.0.x with phantom-ship as gateway and DNS. --- nixos/hosts/phantom-ship.nix | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index c981e50..d2a236e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -13,6 +13,26 @@ in networking.useDHCP = lib.mkDefault true; networking.wireless.enable = true; # credentials in /etc/wpa_supplicant.conf (outside repo) + # NAT: share WiFi internet to rusty-anchor over ethernet + networking.nat = { + enable = true; + externalInterface = "wlp1s0"; + internalInterfaces = [ "enp0s31f6" ]; + }; + networking.interfaces.enp0s31f6.ipv4.addresses = [{ + address = "10.0.0.1"; + prefixLength = 24; + }]; + services.dnsmasq = { + enable = true; + settings = { + interface = "enp0s31f6"; + dhcp-range = "10.0.0.10,10.0.0.50,24h"; + dhcp-option = [ "3,10.0.0.1" "6,10.0.0.1" ]; # gateway + DNS + }; + }; + networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; + hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware time.timeZone = "Europe/Copenhagen"; From d1ab7d9a69a8c91d3db235237f01ef8263c1769c Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Thu, 2 Apr 2026 09:16:08 +0200 Subject: [PATCH 043/185] =?UTF-8?q?feat:=20blank=20phantom-ship=20display?= =?UTF-8?q?=20after=2060s=20idle=20=F0=9F=96=A5=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same consoleblank + backlight-off pattern as sunken-ship. --- nixos/hosts/phantom-ship.nix | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index d2a236e..809ae48 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -34,6 +34,22 @@ in networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware + + boot.kernelParams = [ "consoleblank=60" ]; # blank TTY after 60s to reduce burn-in + + # Turn off panel backlight after boot so the screen actually dims. + systemd.services.server-backlight-off = { + description = "Turn off panel backlight after console idle (reduce burn-in)"; + after = [ "multi-user.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig.Type = "oneshot"; + script = '' + ${pkgs.coreutils}/bin/sleep 65 + for d in /sys/class/backlight/*; do + [ -f "$d/brightness" ] && echo 0 > "$d/brightness" 2>/dev/null || true + done + ''; + }; time.timeZone = "Europe/Copenhagen"; nix.settings.experimental-features = [ "nix-command" "flakes" ]; From b04b53f9c449433b25d6eea60b6baff7b4f08d58 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 3 Apr 2026 12:02:02 +0200 Subject: [PATCH 044/185] =?UTF-8?q?feat:=20add=20OpenClaw=20gateway=20to?= =?UTF-8?q?=20phantom-ship=20=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegram bot via nix-openclaw NixOS module. Secrets (API key, bot token) loaded from /etc/openclaw/ at runtime. Telegram user ID read from gitignored openclaw-allow-from.nix. --- .gitignore | 3 + nixos/flake.lock | 126 +++++++++++++++++++++++++++++++++-- nixos/flake.nix | 5 ++ nixos/hosts/phantom-ship.nix | 21 +++++- 4 files changed, 146 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 5eede6d..b2da242 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,8 @@ nixos/installer-wifi.nix # Nix build output symlink result +# OpenClaw: Telegram user ID (not committed to public repo) +nixos/hosts/openclaw-allow-from.nix + # Archived / local-only directories openclaw-documents-repo/ diff --git a/nixos/flake.lock b/nixos/flake.lock index 75d6555..acb3cba 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -40,6 +40,24 @@ "inputs": { "systems": "systems" }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { "lastModified": 1681202837, "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", @@ -75,6 +93,27 @@ } }, "home-manager_2": { + "inputs": { + "nixpkgs": [ + "nix-openclaw", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1767909183, + "narHash": "sha256-u/bcU0xePi5bgNoRsiqSIwaGBwDilKKFTz3g0hqOBAo=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "cd6e96d56ed4b2a779ac73a1227e0bb1519b3509", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "home-manager_3": { "inputs": { "nixpkgs": [ "zen-browser", @@ -116,10 +155,51 @@ "type": "github" } }, + "nix-openclaw": { + "inputs": { + "flake-utils": "flake-utils", + "home-manager": "home-manager_2", + "nix-steipete-tools": "nix-steipete-tools", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773851886, + "narHash": "sha256-+3ygZuf5K8mtSGMMEZ/h+vxGvXCu1CmiB+531KMagH8=", + "owner": "openclaw", + "repo": "nix-openclaw", + "rev": "64d410666821866c565e048a4d07d6cf5d8e494e", + "type": "github" + }, + "original": { + "owner": "openclaw", + "repo": "nix-openclaw", + "type": "github" + } + }, + "nix-steipete-tools": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1773561580, + "narHash": "sha256-wT0bKTp45YnMkc4yXQvk943Zz/rksYiIjEXGdWzxnic=", + "owner": "openclaw", + "repo": "nix-steipete-tools", + "rev": "cd4c429ff3b3aaef9f92e59812cf2baf5704b86f", + "type": "github" + }, + "original": { + "owner": "openclaw", + "repo": "nix-steipete-tools", + "type": "github" + } + }, "nixos-wsl": { "inputs": { "flake-compat": "flake-compat", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs_2" }, "locked": { "lastModified": 1773603777, @@ -137,6 +217,22 @@ } }, "nixpkgs": { + "locked": { + "lastModified": 1767364772, + "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1773282481, "narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=", @@ -152,7 +248,7 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { "lastModified": 1773628058, "narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=", @@ -168,7 +264,7 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { "lastModified": 1682134069, "narHash": "sha256-TnI/ZXSmRxQDt2sjRYK/8j8iha4B4zP2cnQCZZ3vp7k=", @@ -187,8 +283,9 @@ "disko": "disko", "home-manager": "home-manager", "nix-darwin": "nix-darwin", + "nix-openclaw": "nix-openclaw", "nixos-wsl": "nixos-wsl", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "vscode-server": "vscode-server", "zen-browser": "zen-browser" } @@ -208,10 +305,25 @@ "type": "github" } }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "vscode-server": { "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs_3" + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_4" }, "locked": { "lastModified": 1770124655, @@ -229,7 +341,7 @@ }, "zen-browser": { "inputs": { - "home-manager": "home-manager_2", + "home-manager": "home-manager_3", "nixpkgs": [ "nixpkgs" ] diff --git a/nixos/flake.nix b/nixos/flake.nix index f74750f..0f757d3 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -15,6 +15,9 @@ disko.url = "github:nix-community/disko"; disko.inputs.nixpkgs.follows = "nixpkgs"; + + nix-openclaw.url = "github:openclaw/nix-openclaw"; + nix-openclaw.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = { @@ -26,6 +29,7 @@ home-manager, zen-browser, disko, + nix-openclaw, ... }: { nixosConfigurations = { @@ -75,6 +79,7 @@ phantom-ship = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ + nix-openclaw.nixosModules.openclaw-gateway ./hosts/phantom-ship.nix # Home Manager on NixOS diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 809ae48..b0eccda 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -1,10 +1,14 @@ -# NixOS server: bare config with SSH, auto-rebuild, Ethernet. -# Services (OpenClaw, etc.) to be added later. +# NixOS server: SSH, auto-rebuild, NAT for rusty-anchor, OpenClaw gateway. { config, lib, pkgs, ... }: let dotfilesDir = "/etc/dotfiles"; flakeRef = "${dotfilesDir}/nixos#phantom-ship"; + + # Telegram user ID(s) — gitignored, not committed to public repo. + # Create openclaw-allow-from.nix with e.g.: [ 12345678 ] + allowFromPath = ./openclaw-allow-from.nix; + openclawAllowFrom = if builtins.pathExists allowFromPath then import allowFromPath else [ ]; in { imports = [ ./phantom-ship-hardware.nix ]; @@ -77,6 +81,19 @@ in git # clone/bootstrap and dotfiles-rebuild timer ]; + # OpenClaw AI gateway — Telegram bot, Anthropic API. + # Secrets (not in repo): /etc/openclaw/telegram-bot-token, /etc/openclaw/env (ANTHROPIC_API_KEY) + services.openclaw-gateway = { + enable = true; + environmentFiles = [ "/etc/openclaw/env" ]; + config = { + channels.telegram = { + tokenFile = "/etc/openclaw/telegram-bot-token"; + allowFrom = openclawAllowFrom; + }; + }; + }; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From cfa2834516c7a3186bf48a04f131138ef3b98197 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 3 Apr 2026 12:05:42 +0200 Subject: [PATCH 045/185] =?UTF-8?q?fix:=20permit=20insecure=20openclaw=20p?= =?UTF-8?q?ackage=20on=20phantom-ship=20=F0=9F=94=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index b0eccda..c42fde5 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -56,6 +56,7 @@ in }; time.timeZone = "Europe/Copenhagen"; + nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ]; programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) system.stateVersion = "24.11"; From 49165590a6eb5cb2cb616977468e875e5c8cf885 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 3 Apr 2026 12:15:51 +0200 Subject: [PATCH 046/185] feat: add fitness bot systemd service to sunken-ship Code deployed separately via rsync (private repo, not referenced here). Expects code at /home/danny/tg_fitness_bot/ and token at ~/.secrets/bigbiggerbiggestbot. Co-Authored-By: Claude Opus 4.6 (1M context) --- nixos/hosts/sunken-ship.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 4b90677..71385c6 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -95,6 +95,30 @@ in }; }; + # BigBiggerBiggestBot — Telegram fitness tracker with Mini App. + # Code deployed separately via rsync (private repo, not referenced here). + # Bot token: ~danny/.secrets/bigbiggerbiggestbot + systemd.services.fitness-bot = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + python-telegram-bot + python-dotenv + aiohttp + ]); + in { + description = "BigBiggerBiggestBot Telegram fitness tracker"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv pkgs.cloudflared ]; + serviceConfig = { + WorkingDirectory = "/home/danny/tg_fitness_bot"; + ExecStart = "${pythonEnv}/bin/python start.py"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From 0de86837dff8b6bea5688c6f95b40de66440206d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 3 Apr 2026 14:36:57 +0200 Subject: [PATCH 047/185] =?UTF-8?q?fix:=20set=20gateway.mode=3Dlocal=20for?= =?UTF-8?q?=20openclaw=20on=20phantom-ship=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index c42fde5..6a3dbb9 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -88,6 +88,7 @@ in enable = true; environmentFiles = [ "/etc/openclaw/env" ]; config = { + gateway.mode = "local"; channels.telegram = { tokenFile = "/etc/openclaw/telegram-bot-token"; allowFrom = openclawAllowFrom; @@ -98,7 +99,7 @@ in # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; - path = with pkgs; [ git nix ]; + path = with pkgs; [ git nix nixos-rebuild ]; environment.GIT_CONFIG_COUNT = "1"; environment.GIT_CONFIG_KEY_0 = "safe.directory"; environment.GIT_CONFIG_VALUE_0 = dotfilesDir; From f3854af82aea94bc455bbf1cdfc4c5cb5627fdfb Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 3 Apr 2026 14:38:03 +0200 Subject: [PATCH 048/185] =?UTF-8?q?fix:=20grant=20openclaw=20write=20acces?= =?UTF-8?q?s=20to=20config=20dir=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 6a3dbb9..436320e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -96,6 +96,11 @@ in }; }; + # OpenClaw gateway needs write access to its config dir for runtime state. + systemd.tmpfiles.rules = [ + "d /etc/openclaw 0775 root openclaw - -" + ]; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From 8ce36f8726edd6aec3e8a23b62fe984f9bbc2b26 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 11:25:54 +0200 Subject: [PATCH 049/185] =?UTF-8?q?feat:=20add=20Flipper=20Zero=20tools=20?= =?UTF-8?q?(dfu-util=20+=20qFlipper)=20=F0=9F=90=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/home/danny/home.nix | 1 + nixos/hosts/daniel-macbook-air.nix | 1 + 2 files changed, 2 insertions(+) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 22074e9..6e52073 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -213,6 +213,7 @@ # zed-editor # TODO: Bloat code-cursor cursor-cli + dfu-util # USB DFU firmware flasher (Flipper Zero etc.) discord mapscii mpv diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index 354cfb1..75a6eac 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -39,6 +39,7 @@ in { casks = [ "google-chrome" "disk-inventory-x" # Apple Silicon uses Homebrew; nixpkgs package is x86_64-darwin only. + "qflipper" # Flipper Zero firmware updater GUI ]; onActivation.cleanup = "zap"; }; From 4d6b64dee9a6aa1cca22b2a65afa991ef2947811 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 11:25:59 +0200 Subject: [PATCH 050/185] =?UTF-8?q?fix:=20add=20nixos-rebuild=20to=20dotfi?= =?UTF-8?q?les-rebuild=20PATH=20on=20sunken-ship=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 71385c6..f51700e 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -122,7 +122,7 @@ in # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; - path = with pkgs; [ git nix ]; + path = with pkgs; [ git nix nixos-rebuild ]; environment.GIT_CONFIG_COUNT = "1"; environment.GIT_CONFIG_KEY_0 = "safe.directory"; environment.GIT_CONFIG_VALUE_0 = dotfilesDir; From 4544635ad6276645049123fb61bf1a82e069dbd4 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 11:26:54 +0200 Subject: [PATCH 051/185] =?UTF-8?q?security:=20remove=20initialPassword=20?= =?UTF-8?q?from=20phantom-ship=20config=20=F0=9F=94=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Password is locked in shadow and SSH is key-only, so the initialPassword served no purpose and was a minor security concern. --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 436320e..6370681 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -64,7 +64,7 @@ in users.users.danny = { isNormalUser = true; extraGroups = [ "wheel" ]; - initialPassword = "changeme"; # console fallback; change after first login + # Password is locked (key-only SSH). Use NixOS installer or recovery to reset if needed. }; # Key-only auth; no password or keyboard-interactive. From 369e96cbd7d0552b1f6c15b06a4585257d6d958a Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 11:27:05 +0200 Subject: [PATCH 052/185] =?UTF-8?q?security:=20harden=20openclaw-gateway?= =?UTF-8?q?=20systemd=20service=20=F0=9F=9B=A1=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProtectSystem=strict, ProtectHome=read-only, PrivateTmp, NoNewPrivileges. Only /var/lib/openclaw and /etc/openclaw are writable. --- nixos/hosts/phantom-ship.nix | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 6370681..966d365 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -101,6 +101,15 @@ in "d /etc/openclaw 0775 root openclaw - -" ]; + # Harden the openclaw-gateway systemd service. + systemd.services.openclaw-gateway.serviceConfig = { + ProtectHome = "read-only"; + ProtectSystem = "strict"; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = [ "/var/lib/openclaw" "/etc/openclaw" ]; + }; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From 52649f500ad42d6efee0c029bf0589291f8b2d76 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 12:06:08 +0200 Subject: [PATCH 053/185] =?UTF-8?q?feat:=20add=20git/nodejs=20to=20opencla?= =?UTF-8?q?w,=20configure=20GitHub=20PAT=20credential=20helper=20?= =?UTF-8?q?=F0=9F=94=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds git and nodejs to openclaw-gateway service PATH. Configures a git credential helper that reads a fine-grained PAT from /etc/openclaw/github-token. Creates /var/lib/openclaw/repos for repo clones. --- nixos/hosts/phantom-ship.nix | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 966d365..ffa28d5 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -87,6 +87,7 @@ in services.openclaw-gateway = { enable = true; environmentFiles = [ "/etc/openclaw/env" ]; + servicePath = [ pkgs.git pkgs.nodejs ]; config = { gateway.mode = "local"; channels.telegram = { @@ -96,12 +97,29 @@ in }; }; - # OpenClaw gateway needs write access to its config dir for runtime state. + # OpenClaw gateway needs write access to its config dir and repo clones. systemd.tmpfiles.rules = [ "d /etc/openclaw 0775 root openclaw - -" + "d /var/lib/openclaw/repos 0750 openclaw openclaw - -" ]; + # Git config for the openclaw user: credential helper reads PAT from file. + # PAT (not in repo): /etc/openclaw/github-token (fine-grained, scoped to specific repos) + environment.etc."openclaw/gitconfig" = { + text = '' + [user] + name = OpenClaw Bot + email = noreply@openclaw.local + [credential "https://github.com"] + helper = "!f() { echo username=x-access-token; echo password=$(cat /etc/openclaw/github-token); }; f" + [safe] + directory = /var/lib/openclaw/repos + ''; + mode = "0644"; + }; + # Harden the openclaw-gateway systemd service. + systemd.services.openclaw-gateway.environment.GIT_CONFIG_GLOBAL = "/etc/openclaw/gitconfig"; systemd.services.openclaw-gateway.serviceConfig = { ProtectHome = "read-only"; ProtectSystem = "strict"; From 3813206a3eca7f2a203160b5db3623a93789d752 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 13:38:04 +0200 Subject: [PATCH 054/185] =?UTF-8?q?feat:=20add=20nodejs=20and=20python3=20?= =?UTF-8?q?to=20phantom-ship=20for=20openclaw=20plugins=20=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index ffa28d5..94b515f 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -79,7 +79,9 @@ in # Passwordless sudo for wheel. security.sudo.wheelNeedsPassword = false; environment.systemPackages = with pkgs; [ - git # clone/bootstrap and dotfiles-rebuild timer + git # clone/bootstrap and dotfiles-rebuild timer + nodejs # npm for openclaw plugin installs + python3 # node-gyp dependency for openclaw plugins ]; # OpenClaw AI gateway — Telegram bot, Anthropic API. From 098550300226d1c92f7e530cad9fa8b42f51814b Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Sat, 4 Apr 2026 14:14:12 +0200 Subject: [PATCH 055/185] phantom-ship: add openai-whisper to openclaw service path --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 94b515f..17295eb 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -89,7 +89,7 @@ in services.openclaw-gateway = { enable = true; environmentFiles = [ "/etc/openclaw/env" ]; - servicePath = [ pkgs.git pkgs.nodejs ]; + servicePath = [ pkgs.git pkgs.nodejs pkgs.openai-whisper ]; config = { gateway.mode = "local"; channels.telegram = { From 74eb3a9c4067e5c13399997cfcf561e469e880be Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 4 Apr 2026 21:18:36 +0200 Subject: [PATCH 056/185] =?UTF-8?q?feat:=20rusty-anchor=20WoL,=20auto=20da?= =?UTF-8?q?rk/light=20VT=20theme,=20wakeonlan=20on=20phantom-ship=20?= =?UTF-8?q?=F0=9F=A6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enable Wake-on-LAN (magic packet) on rusty-anchor enp2s0 via systemd service - Add vt-theme script to rusty-anchor: switches between Catppuccin Latte/Mocha - Theme state persisted in /etc/vt-theme, applied on login via profile.d - alacritty-sync-system-theme.sh now SSHes to rusty-anchor and pushes the macOS light/dark change (best-effort, non-blocking, skips if unchanged) - Add wakeonlan to phantom-ship packages (wakeonlan 00:16:cb:87:20:ba) --- nixos/hosts/phantom-ship.nix | 1 + scripts/alacritty-sync-system-theme.sh | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 17295eb..0f08e9b 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -82,6 +82,7 @@ in git # clone/bootstrap and dotfiles-rebuild timer nodejs # npm for openclaw plugin installs python3 # node-gyp dependency for openclaw plugins + wakeonlan # wake rusty-anchor: wakeonlan 00:16:cb:87:20:ba ]; # OpenClaw AI gateway — Telegram bot, Anthropic API. diff --git a/scripts/alacritty-sync-system-theme.sh b/scripts/alacritty-sync-system-theme.sh index c323973..e44b3a5 100755 --- a/scripts/alacritty-sync-system-theme.sh +++ b/scripts/alacritty-sync-system-theme.sh @@ -44,3 +44,22 @@ fi chmod 0644 "$tmp" mv -f "$tmp" "$ACTIVE" + +# Sync theme to rusty-anchor VT console (best-effort, non-blocking) +RUSTY_ANCHOR_KEY="$HOME/.ssh/id_ed25519_phantom_ship" +RUSTY_ANCHOR_THEME_FILE="/etc/vt-theme" +if [[ -f "$RUSTY_ANCHOR_KEY" ]]; then + last_rusty="$ALACRITTY_DIR/.last-rusty-anchor-theme" + if [[ "$(cat "$last_rusty" 2>/dev/null)" != "$want" ]]; then + ( + ssh -i "$RUSTY_ANCHOR_KEY" \ + -J danny@phantom-ship \ + -o ConnectTimeout=5 \ + -o StrictHostKeyChecking=no \ + danny@10.0.0.2 \ + "echo '$want' | sudo tee $RUSTY_ANCHOR_THEME_FILE > /dev/null && sudo /usr/local/bin/vt-theme '$want' /dev/tty1" \ + 2>/dev/null \ + && printf '%s' "$want" >"$last_rusty" + ) & + fi +fi From 1c7794e904a75aaeb9f43db3e4b3e62f683c8e1d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 10:37:38 +0200 Subject: [PATCH 057/185] =?UTF-8?q?fix:=20remove=20rusty-anchor=20Mac=20de?= =?UTF-8?q?pendency=20from=20alacritty-sync-system-theme=20=F0=9F=A7=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rusty-anchor now switches themes independently via systemd timer + sunrise-sunset API — no longer needs the Mac to push changes over SSH --- scripts/alacritty-sync-system-theme.sh | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/scripts/alacritty-sync-system-theme.sh b/scripts/alacritty-sync-system-theme.sh index e44b3a5..c323973 100755 --- a/scripts/alacritty-sync-system-theme.sh +++ b/scripts/alacritty-sync-system-theme.sh @@ -44,22 +44,3 @@ fi chmod 0644 "$tmp" mv -f "$tmp" "$ACTIVE" - -# Sync theme to rusty-anchor VT console (best-effort, non-blocking) -RUSTY_ANCHOR_KEY="$HOME/.ssh/id_ed25519_phantom_ship" -RUSTY_ANCHOR_THEME_FILE="/etc/vt-theme" -if [[ -f "$RUSTY_ANCHOR_KEY" ]]; then - last_rusty="$ALACRITTY_DIR/.last-rusty-anchor-theme" - if [[ "$(cat "$last_rusty" 2>/dev/null)" != "$want" ]]; then - ( - ssh -i "$RUSTY_ANCHOR_KEY" \ - -J danny@phantom-ship \ - -o ConnectTimeout=5 \ - -o StrictHostKeyChecking=no \ - danny@10.0.0.2 \ - "echo '$want' | sudo tee $RUSTY_ANCHOR_THEME_FILE > /dev/null && sudo /usr/local/bin/vt-theme '$want' /dev/tty1" \ - 2>/dev/null \ - && printf '%s' "$want" >"$last_rusty" - ) & - fi -fi From 4bccb6e6a80568bf373760da34b3da15894a8e61 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 11:55:06 +0200 Subject: [PATCH 058/185] =?UTF-8?q?fix(sunken-ship):=20add=20audioconvert?= =?UTF-8?q?=20to=20uxplay=20pipeline=20=E2=80=94=20fixes=20ALAC=20format?= =?UTF-8?q?=20error=20=F0=9F=8E=B5=20feat(home):=20add=20uhk-agent=20for?= =?UTF-8?q?=20UHK=20keyboard=20configuration=20=F0=9F=8E=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/home/danny/home.nix | 1 + nixos/hosts/sunken-ship.nix | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 6e52073..a24ee87 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -217,6 +217,7 @@ discord mapscii mpv + uhk-agent # UHK keyboard configuration GUI + CLI ]); # First HM version for this user config; bump only if you understand the migration notes. diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index f51700e..b9e1fa6 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -73,12 +73,24 @@ in publish = { enable = true; userServices = true; }; }; - # Open firewall for AirPlay (mDNS + UxPlay default ports). + # Open firewall for AirPlay (mDNS + UxPlay default ports) + Navidrome. networking.firewall = { - allowedTCPPorts = [ 7000 7001 7100 ]; + allowedTCPPorts = [ 7000 7001 7100 4533 ]; allowedUDPPorts = [ 5353 6000 6001 7011 ]; }; + # Navidrome — self-hosted music streaming server (Subsonic API). + # Music library: /home/danny/music + # Web UI + Substreamer client on port 4533. + services.navidrome = { + enable = true; + settings = { + Address = "0.0.0.0"; + Port = 4533; + MusicFolder = "/home/danny/music"; + }; + }; + # UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA. # Runs as a system service (no PipeWire needed on a headless server). systemd.services.uxplay = { @@ -87,7 +99,7 @@ in wants = [ "network-online.target" "avahi-daemon.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = ''${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as "alsasink device=plughw:USB,0 buffer-time=200000"''; + ExecStart = ''${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as "audioconvert ! audioresample ! alsasink device=plughw:USB,0 buffer-time=200000"''; Restart = "on-failure"; RestartSec = 5; User = "danny"; From 300849b8c694151f056972c553bfcc8913772e9a Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 12:15:10 +0200 Subject: [PATCH 059/185] =?UTF-8?q?fix:=20neovim=20extraLuaConfig=E2=86=92?= =?UTF-8?q?initLua,=20remove=20uhk-agent=20(linux-only)=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - programs.neovim.extraLuaConfig renamed to initLua in nixpkgs unstable - uhk-agent is x86_64-linux only, removed from darwin home config; macOS: download .dmg from ultimatehackingkeyboard.com --- nixos/neovim.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/neovim.nix b/nixos/neovim.nix index 8f65b29..b820bdf 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -21,7 +21,7 @@ let g:netrw_winsize = 25 ''; - extraLuaConfig = '' + initLua = '' -- Auto-detect system theme (dark/light) from marker file local config_file = os.getenv("HOME") .. "/.local/share/nvim_color_scheme" local f = io.open(config_file, "r") From f0d52aed04d0035665527fd416349e8b22faaae0 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 13:40:37 +0200 Subject: [PATCH 060/185] =?UTF-8?q?feat(darwin):=20add=20uhk-agent=20to=20?= =?UTF-8?q?homebrew=20casks=20=F0=9F=8E=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/daniel-macbook-air.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index 75a6eac..cc43412 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -40,6 +40,7 @@ in { "google-chrome" "disk-inventory-x" # Apple Silicon uses Homebrew; nixpkgs package is x86_64-darwin only. "qflipper" # Flipper Zero firmware updater GUI + "uhk-agent" # Ultimate Hacking Keyboard configuration ]; onActivation.cleanup = "zap"; }; From c31ca7d473abef349ab4828a4dffc4ac416289d6 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 14:46:24 +0200 Subject: [PATCH 061/185] =?UTF-8?q?feat(sunken-ship):=20add=20navidrome=20?= =?UTF-8?q?user=20to=20users=20group=20for=20music=20dir=20access=20?= =?UTF-8?q?=F0=9F=8E=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index b9e1fa6..1eaed4a 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -90,6 +90,7 @@ in MusicFolder = "/home/danny/music"; }; }; + users.users.navidrome.extraGroups = [ "users" ]; # UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA. # Runs as a system service (no PipeWire needed on a headless server). From 76f63f0ae32f888bf4d942c3822fd498f8956a1e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 15:36:07 +0200 Subject: [PATCH 062/185] =?UTF-8?q?fix(sunken-ship):=20move=20navidrome=20?= =?UTF-8?q?music=20folder=20to=20/srv/music=20=E2=80=94=20ProtectHome=20by?= =?UTF-8?q?pass=20=F0=9F=8E=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 1eaed4a..5b18a80 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -80,17 +80,22 @@ in }; # Navidrome — self-hosted music streaming server (Subsonic API). - # Music library: /home/danny/music + # Music library: /srv/music (bind-mounted from /home/danny/music). # Web UI + Substreamer client on port 4533. services.navidrome = { enable = true; settings = { Address = "0.0.0.0"; Port = 4533; - MusicFolder = "/home/danny/music"; + MusicFolder = "/srv/music"; }; }; - users.users.navidrome.extraGroups = [ "users" ]; + + # Persist the bind mount so navidrome can read music outside ProtectHome. + fileSystems."/srv/music" = { + device = "/home/danny/music"; + options = [ "bind" "ro" ]; + }; # UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA. # Runs as a system service (no PipeWire needed on a headless server). From be6dde6f0a5bc785c935c2c6f46877799ae167b0 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 6 Apr 2026 21:19:38 +0200 Subject: [PATCH 063/185] =?UTF-8?q?feat(sunken-ship):=20add=20cloudflare?= =?UTF-8?q?=20tunnel=20for=20external=20access=20=F0=9F=8C=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exposes navidrome via music.dannydannydanny.me. Bypasses CGNAT — no port forwarding needed. Token stored outside repo at ~/.secrets/cloudflare-tunnel-token. --- nixos/hosts/sunken-ship.nix | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 5b18a80..a98a9e0 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -64,6 +64,7 @@ in brightnessctl # manual backlight; replaces removed `light` from nixpkgs uxplay # AirPlay mirroring receiver alsa-utils # aplay, amixer, arecord for audio debugging + cloudflared # Cloudflare Tunnel for external access ]; # Avahi (mDNS) — required for AirPlay discovery. @@ -97,6 +98,23 @@ in options = [ "bind" "ro" ]; }; + # Cloudflare Tunnel — exposes services to the internet without port forwarding. + # Token (not in repo): ~danny/.secrets/cloudflare-tunnel-token + # Routes configured in Cloudflare Zero Trust dashboard: + # music.dannydannydanny.me → http://localhost:4533 + systemd.services.cloudflare-tunnel = { + description = "Cloudflare Tunnel for sunken-ship"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "/bin/sh -c '${pkgs.cloudflared}/bin/cloudflared tunnel --no-autoupdate run --token $(cat /home/danny/.secrets/cloudflare-tunnel-token)'"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA. # Runs as a system service (no PipeWire needed on a headless server). systemd.services.uxplay = { From 72d8714e5198360152dfd9c01422187dbe980360 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 10 Apr 2026 12:05:34 +0200 Subject: [PATCH 064/185] =?UTF-8?q?feat(neovim):=20add=20markdown=20foldin?= =?UTF-8?q?g=20by=20heading=20via=20Treesitter=20=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/neovim.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nixos/neovim.nix b/nixos/neovim.nix index b820bdf..90743fb 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -45,6 +45,16 @@ vim.opt.spell = true vim.opt.spelllang = "en_us" + -- Markdown: fold by heading/section using Treesitter + vim.api.nvim_create_autocmd("FileType", { + pattern = "markdown", + callback = function() + vim.opt_local.foldmethod = "expr" + vim.opt_local.foldexpr = "v:lua.vim.treesitter.foldexpr()" + vim.opt_local.foldenable = true + end, + }) + -- Keymaps vim.keymap.set("n", "S", ":%s//g", { desc = "Replace all" }) vim.keymap.set("n", "w", ":w", { desc = "Save file" }) From d1b0742f3257d924ee25784d1d7deea1ebcec33e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Thu, 16 Apr 2026 09:45:05 +0200 Subject: [PATCH 065/185] chore(disable): UHK - x86_64-linux only --- nixos/home/danny/home.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index a24ee87..35b6a09 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -217,7 +217,7 @@ discord mapscii mpv - uhk-agent # UHK keyboard configuration GUI + CLI + # uhk-agent # UHK keyboard configuration GUI + CLI — removed, nixpkgs marks x86_64-linux only TODO ]); # First HM version for this user config; bump only if you understand the migration notes. From b667f7c247d6a1d2208651bbe82e8d890b5da5a2 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Thu, 16 Apr 2026 09:46:30 +0200 Subject: [PATCH 066/185] feat: add feishin + disable uhk --- nixos/hosts/daniel-macbook-air.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index cc43412..a05ff6a 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -40,7 +40,7 @@ in { "google-chrome" "disk-inventory-x" # Apple Silicon uses Homebrew; nixpkgs package is x86_64-darwin only. "qflipper" # Flipper Zero firmware updater GUI - "uhk-agent" # Ultimate Hacking Keyboard configuration + # "uhk-agent" # Ultimate Hacking Keyboard configuration — removed, nixpkgs marks x86_64-linux only TODO ]; onActivation.cleanup = "zap"; }; @@ -67,7 +67,10 @@ in { # User-specific packages and environment variables are now in home-manager (home.nix) # Only system-level packages should remain here if needed - environment.systemPackages = [ alacrittySyncSystemTheme ]; + environment.systemPackages = [ + alacrittySyncSystemTheme + pkgs.feishin # Subsonic/Navidrome desktop music player + ]; # Poll macOS appearance; updates ~/.config/alacritty/active-colors.toml (Alacritty live_config_reload). launchd.user.agents.alacritty-system-theme = { From 5db45664abfbffc8f143a4bacea7a60f1b8c75e6 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 17 Apr 2026 14:33:15 +0200 Subject: [PATCH 067/185] feat(sunken-ship): auto-pull fitness bot from GitHub every 15 min New fitness-bot-pull service + timer, modeled on dotfiles-rebuild. Checks origin/main for new commits, pulls + restarts the service if the HEAD moved. Offset by 7 min from dotfiles-rebuild to avoid overlap. Code now lives at github.com/DannyDannyDanny/bigbiggerbiggestbot (cloned to /home/danny/tg_fitness_bot). workouts.db is gitignored so it's preserved across pulls. Co-Authored-By: Claude Opus 4.6 (1M context) --- nixos/flake.lock | 54 ++++++++++++++++++------------------- nixos/hosts/sunken-ship.nix | 31 ++++++++++++++++++++- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/nixos/flake.lock b/nixos/flake.lock index acb3cba..6c0321e 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1773506317, - "narHash": "sha256-qWKbLUJpavIpvOdX1fhHYm0WGerytFHRoh9lVck6Bh0=", + "lastModified": 1773889306, + "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", "owner": "nix-community", "repo": "disko", - "rev": "878ec37d6a8f52c6c801d0e2a2ad554c75b9353c", + "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", "type": "github" }, "original": { @@ -79,11 +79,11 @@ ] }, "locked": { - "lastModified": 1773810247, - "narHash": "sha256-6Vz1Thy/1s7z+Rq5OfkWOBAdV4eD+OrvDs10yH6xJzQ=", + "lastModified": 1776184304, + "narHash": "sha256-No6QGBmIv5ChiwKCcbkxjdEQ/RO2ZS1gD7SFy6EZ7rc=", "owner": "nix-community", "repo": "home-manager", - "rev": "d47357a4c806d18a3e853ad2699eaec3c01622e7", + "rev": "3c7524c68348ef79ce48308e0978611a050089b2", "type": "github" }, "original": { @@ -121,11 +121,11 @@ ] }, "locked": { - "lastModified": 1773422513, - "narHash": "sha256-MPjR48roW7CUMU6lu0+qQGqj92Kuh3paIulMWFZy+NQ=", + "lastModified": 1774991950, + "narHash": "sha256-kScKj3qJDIWuN9/6PMmgy5esrTUkYinrO5VvILik/zw=", "owner": "nix-community", "repo": "home-manager", - "rev": "ef12a9a2b0f77c8fa3dda1e7e494fca668909056", + "rev": "f2d3e04e278422c7379e067e323734f3e8c585a7", "type": "github" }, "original": { @@ -141,11 +141,11 @@ ] }, "locked": { - "lastModified": 1773000227, - "narHash": "sha256-zm3ftUQw0MPumYi91HovoGhgyZBlM4o3Zy0LhPNwzXE=", + "lastModified": 1775037210, + "narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "da529ac9e46f25ed5616fd634079a5f3c579135f", + "rev": "06648f4902343228ce2de79f291dd5a58ee12146", "type": "github" }, "original": { @@ -165,11 +165,11 @@ ] }, "locked": { - "lastModified": 1773851886, - "narHash": "sha256-+3ygZuf5K8mtSGMMEZ/h+vxGvXCu1CmiB+531KMagH8=", + "lastModified": 1776183358, + "narHash": "sha256-uRWaRXGhkyGWMbNgQcmx0+RPzPLenVGopkNHgAEfmBQ=", "owner": "openclaw", "repo": "nix-openclaw", - "rev": "64d410666821866c565e048a4d07d6cf5d8e494e", + "rev": "53aac0dce0810c40c75793fdad3d41b0f7e7baaf", "type": "github" }, "original": { @@ -202,11 +202,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1773603777, - "narHash": "sha256-oXSEbMR/IuHYk9nvrbRhaYBxVK5s63DH2UGOZT2ok48=", + "lastModified": 1776255237, + "narHash": "sha256-LQjlc0VEn55WAT4BiI8sIsokb/2FNlcbBD+Xr3MTE24=", "owner": "nix-community", "repo": "NixOS-WSL", - "rev": "0efe7af73d6e4a8d447a22936c5526d73822b0a7", + "rev": "9a8c2a85f1ffdcecfb0f9c52c5a73c49ceb43911", "type": "github" }, "original": { @@ -234,11 +234,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1773282481, - "narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=", + "lastModified": 1773734432, + "narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fe416aaedd397cacb33a610b33d60ff2b431b127", + "rev": "cda48547b432e8d3b18b4180ba07473762ec8558", "type": "github" }, "original": { @@ -250,11 +250,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1773628058, - "narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=", + "lastModified": 1776255774, + "narHash": "sha256-psVTpH6PK3q1htMJpmdz1hLF5pQgEshu7gQWgKO6t6Y=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df", + "rev": "566acc07c54dc807f91625bb286cb9b321b5f42a", "type": "github" }, "original": { @@ -347,11 +347,11 @@ ] }, "locked": { - "lastModified": 1773737882, - "narHash": "sha256-P6k0BtT1/idYveVRdcwAZk8By9UjZW8XOMhSoS6wTBY=", + "lastModified": 1776317517, + "narHash": "sha256-JP1XVRabZquf7pnXvRUjp7DV+EBrB6Qmp3+vG3HMy/k=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "a7f1db35d74faf04e5189b3a32f890186ace5c28", + "rev": "0a7be59e988bb2cb452080f59aaabae70bc415ae", "type": "github" }, "original": { diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index a98a9e0..6f18a4c 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -132,8 +132,9 @@ in }; # BigBiggerBiggestBot — Telegram fitness tracker with Mini App. - # Code deployed separately via rsync (private repo, not referenced here). + # Code: https://github.com/DannyDannyDanny/bigbiggerbiggestbot cloned at /home/danny/tg_fitness_bot # Bot token: ~danny/.secrets/bigbiggerbiggestbot + # Deployment: fitness-bot-pull timer below runs every 15 min, git pulls, restarts service on changes. systemd.services.fitness-bot = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ python-telegram-bot @@ -155,6 +156,34 @@ in }; }; + # Pull fitness bot from GitHub and restart the service if the repo has new commits. + # Code lives at /home/danny/tg_fitness_bot (git clone of DannyDannyDanny/bigbiggerbiggestbot). + # workouts.db is gitignored — preserved across pulls. + systemd.services.fitness-bot-pull = { + description = "Pull fitness bot and restart service if repo changed"; + path = with pkgs; [ git systemd ]; + environment.GIT_CONFIG_COUNT = "1"; + environment.GIT_CONFIG_KEY_0 = "safe.directory"; + environment.GIT_CONFIG_VALUE_0 = "/home/danny/tg_fitness_bot"; + script = '' + set -euo pipefail + cd /home/danny/tg_fitness_bot + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then + exit 0 + fi + git pull origin main + systemctl restart fitness-bot + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.fitness-bot-pull = { + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*-*-* *:07/15:00"; # every 15 minutes, offset from dotfiles-rebuild + timerConfig.RandomizedDelaySec = "2min"; + }; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From a36b90e65605530798351a161f0cce381f680e32 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 17 Apr 2026 14:35:27 +0200 Subject: [PATCH 068/185] fix(sunken-ship): set fsType=none on /srv/music bind mount nixos-rebuild was failing with "fsType accessed but has no value defined" on newer nixpkgs. Bind mounts need fsType=none explicitly. Co-Authored-By: Claude Opus 4.6 (1M context) --- nixos/hosts/sunken-ship.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 6f18a4c..7099ae4 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -95,6 +95,7 @@ in # Persist the bind mount so navidrome can read music outside ProtectHome. fileSystems."/srv/music" = { device = "/home/danny/music"; + fsType = "none"; options = [ "bind" "ro" ]; }; From d0d25160c856eb28b8a74a889e7f3e120eb2769b Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 16:48:10 +0200 Subject: [PATCH 069/185] =?UTF-8?q?feat:=20add=20bun=20+=20claude-code=20t?= =?UTF-8?q?o=20phantom-ship=20for=20channels=20migration=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code Channels will replace OpenClaw for the Telegram bot. Channels uses claude.ai subscription auth instead of pay-as-you-go API, sidestepping the rate limits Hara has been hitting. --- nixos/hosts/phantom-ship.nix | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 0f08e9b..9f30acc 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -79,10 +79,12 @@ in # Passwordless sudo for wheel. security.sudo.wheelNeedsPassword = false; environment.systemPackages = with pkgs; [ - git # clone/bootstrap and dotfiles-rebuild timer - nodejs # npm for openclaw plugin installs - python3 # node-gyp dependency for openclaw plugins - wakeonlan # wake rusty-anchor: wakeonlan 00:16:cb:87:20:ba + git # clone/bootstrap and dotfiles-rebuild timer + nodejs # npm for openclaw plugin installs + python3 # node-gyp dependency for openclaw plugins + wakeonlan # wake rusty-anchor: wakeonlan 00:16:cb:87:20:ba + bun # runtime for claude-code channel plugins + claude-code # Claude Code CLI (channels replaces openclaw) ]; # OpenClaw AI gateway — Telegram bot, Anthropic API. From 7ad82a41b1fbe871eaa94a79c751c662188c89ac Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 16:48:33 +0200 Subject: [PATCH 070/185] =?UTF-8?q?fix:=20permit=20openclaw=202026.4.12=20?= =?UTF-8?q?on=20phantom-ship=20=F0=9F=94=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nixpkgs bumped openclaw version; keep both permitted so rebuild works until we fully cut over to channels and remove the input. --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 9f30acc..bb28205 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -56,7 +56,7 @@ in }; time.timeZone = "Europe/Copenhagen"; - nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ]; + nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" "openclaw-2026.4.12" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ]; programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) system.stateVersion = "24.11"; From af486e8a3397177a2ff1e82eac61a255c68af129 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 16:48:58 +0200 Subject: [PATCH 071/185] =?UTF-8?q?fix:=20allow=20unfree=20claude-code=20p?= =?UTF-8?q?ackage=20on=20phantom-ship=20=F0=9F=94=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index bb28205..617b9e5 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -57,6 +57,7 @@ in time.timeZone = "Europe/Copenhagen"; nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" "openclaw-2026.4.12" ]; + nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "claude-code" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ]; programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) system.stateVersion = "24.11"; From c434a479a597d2c1b9377868b09846e748d1b7fa Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 17:00:19 +0200 Subject: [PATCH 072/185] =?UTF-8?q?refactor(nix):=20migrate=20to=20flake-p?= =?UTF-8?q?arts,=20drop=20specialArgs=20=E2=99=BB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert flake.nix to flake-parts.lib.mkFlake; split each host into its own module under nixos/flake-modules/. - Replace zen-browser specialArgs plumbing with a nixpkgs overlay so home.nix can just reference pkgs.zen-browser. --- nixos/flake-modules/daniel-macbook-air.nix | 31 +++++ nixos/flake-modules/installer-iso.nix | 11 ++ nixos/flake-modules/phantom-ship.nix | 22 ++++ nixos/flake-modules/server-install.nix | 11 ++ nixos/flake-modules/sunken-ship.nix | 21 ++++ nixos/flake-modules/wsl.nix | 24 ++++ nixos/flake.lock | 21 ++++ nixos/flake.nix | 137 ++------------------- nixos/home/danny/home.nix | 8 +- 9 files changed, 158 insertions(+), 128 deletions(-) create mode 100644 nixos/flake-modules/daniel-macbook-air.nix create mode 100644 nixos/flake-modules/installer-iso.nix create mode 100644 nixos/flake-modules/phantom-ship.nix create mode 100644 nixos/flake-modules/server-install.nix create mode 100644 nixos/flake-modules/sunken-ship.nix create mode 100644 nixos/flake-modules/wsl.nix diff --git a/nixos/flake-modules/daniel-macbook-air.nix b/nixos/flake-modules/daniel-macbook-air.nix new file mode 100644 index 0000000..68ede41 --- /dev/null +++ b/nixos/flake-modules/daniel-macbook-air.nix @@ -0,0 +1,31 @@ +{ inputs, ... }: { + flake.darwinConfigurations."Daniel-Macbook-Air" = inputs.nix-darwin.lib.darwinSystem { + modules = [ + # Overlay: make zen-browser available as pkgs.zen-browser + { nixpkgs.overlays = [ (final: prev: { + zen-browser = inputs.zen-browser.packages.${final.stdenv.hostPlatform.system}.default; + }) ]; + } + + ../hosts/daniel-macbook-air.nix + ../fish.nix + + # Home Manager on macOS + inputs.home-manager.darwinModules.home-manager + ({ lib, ... }: { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + # Automatically backup files before home-manager overwrites them + home-manager.backupFileExtension = "backup"; + home-manager.users.danny = { ... }: { + # Force an absolute path even if another module sets a bad value. + home.username = "danny"; + home.homeDirectory = lib.mkForce "/Users/danny"; + imports = [ + ../home/danny/home.nix + ]; + }; + }) + ]; + }; +} diff --git a/nixos/flake-modules/installer-iso.nix b/nixos/flake-modules/installer-iso.nix new file mode 100644 index 0000000..f2f7163 --- /dev/null +++ b/nixos/flake-modules/installer-iso.nix @@ -0,0 +1,11 @@ +{ inputs, self, ... }: { + # Custom minimal installer ISO (build with: nix build .#installer-iso). + # Optional: add ./installer-wifi.nix (gitignored) to modules for live WiFi. + flake.nixosConfigurations.installer-iso = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ../installer-iso.nix ]; + }; + + flake.packages.x86_64-linux.installer-iso = + self.nixosConfigurations.installer-iso.config.system.build.isoImage; +} diff --git a/nixos/flake-modules/phantom-ship.nix b/nixos/flake-modules/phantom-ship.nix new file mode 100644 index 0000000..24d1d2d --- /dev/null +++ b/nixos/flake-modules/phantom-ship.nix @@ -0,0 +1,22 @@ +{ inputs, ... }: { + flake.nixosConfigurations.phantom-ship = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.nix-openclaw.nixosModules.openclaw-gateway + ../hosts/phantom-ship.nix + + # Home Manager on NixOS + inputs.home-manager.nixosModules.home-manager + ({ lib, ... }: { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.backupFileExtension = "backup"; + home-manager.users.danny = { ... }: { + home.username = "danny"; + home.homeDirectory = lib.mkForce "/home/danny"; + home.stateVersion = "25.11"; + }; + }) + ]; + }; +} diff --git a/nixos/flake-modules/server-install.nix b/nixos/flake-modules/server-install.nix new file mode 100644 index 0000000..1366ae9 --- /dev/null +++ b/nixos/flake-modules/server-install.nix @@ -0,0 +1,11 @@ +{ inputs, ... }: { + # For disko-install: LUKS + WiFi; hostname/WiFi via --system-config. + flake.nixosConfigurations.server-install = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.disko.nixosModules.disko + ../disko-server.nix + ../hosts/server-install.nix + ]; + }; +} diff --git a/nixos/flake-modules/sunken-ship.nix b/nixos/flake-modules/sunken-ship.nix new file mode 100644 index 0000000..3a78476 --- /dev/null +++ b/nixos/flake-modules/sunken-ship.nix @@ -0,0 +1,21 @@ +{ inputs, ... }: { + flake.nixosConfigurations.sunken-ship = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ../hosts/sunken-ship.nix + + # Home Manager on NixOS + inputs.home-manager.nixosModules.home-manager + ({ lib, ... }: { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.backupFileExtension = "backup"; + home-manager.users.danny = { ... }: { + home.username = "danny"; + home.homeDirectory = lib.mkForce "/home/danny"; + home.stateVersion = "25.11"; + }; + }) + ]; + }; +} diff --git a/nixos/flake-modules/wsl.nix b/nixos/flake-modules/wsl.nix new file mode 100644 index 0000000..711a66e --- /dev/null +++ b/nixos/flake-modules/wsl.nix @@ -0,0 +1,24 @@ +{ inputs, ... }: { + flake.nixosConfigurations.wsl = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.nixos-wsl.nixosModules.default + inputs.vscode-server.nixosModules.default + ../hosts/wsl.nix + ../fish.nix + + # Home Manager on WSL + inputs.home-manager.nixosModules.home-manager + ({ lib, ... }: { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.backupFileExtension = "backup"; + home-manager.users.dth = { ... }: { + home.username = "dth"; + home.homeDirectory = lib.mkForce "/home/dth"; + imports = [ ../home/danny/home.nix ]; + }; + }) + ]; + }; +} diff --git a/nixos/flake.lock b/nixos/flake.lock index acb3cba..fbc9b8f 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -36,6 +36,26 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -281,6 +301,7 @@ "root": { "inputs": { "disko": "disko", + "flake-parts": "flake-parts", "home-manager": "home-manager", "nix-darwin": "nix-darwin", "nix-openclaw": "nix-openclaw", diff --git a/nixos/flake.nix b/nixos/flake.nix index 0f757d3..c9ab314 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -4,6 +4,9 @@ nixos-wsl.url = "github:nix-community/NixOS-WSL/main"; vscode-server.url = "github:nix-community/nixos-vscode-server"; + flake-parts.url = "github:hercules-ci/flake-parts"; + flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; + nix-darwin.url = "github:nix-darwin/nix-darwin/master"; nix-darwin.inputs.nixpkgs.follows = "nixpkgs"; @@ -20,131 +23,17 @@ nix-openclaw.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = { - nixpkgs, - nixos-wsl, - vscode-server, - nix-darwin, - self, - home-manager, - zen-browser, - disko, - nix-openclaw, - ... - }: { - nixosConfigurations = { - wsl = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - nixos-wsl.nixosModules.default - vscode-server.nixosModules.default - ./hosts/wsl.nix - ./fish.nix + outputs = inputs @ { flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" "aarch64-darwin" ]; - # Home Manager on WSL - home-manager.nixosModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - home-manager.backupFileExtension = "backup"; - home-manager.users.dth = { ... }: { - home.username = "dth"; - home.homeDirectory = lib.mkForce "/home/dth"; - imports = [ ./home/danny/home.nix ]; - }; - }) - ]; - }; - - sunken-ship = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - ./hosts/sunken-ship.nix - - # Home Manager on NixOS - home-manager.nixosModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - home-manager.backupFileExtension = "backup"; - home-manager.users.danny = { ... }: { - home.username = "danny"; - home.homeDirectory = lib.mkForce "/home/danny"; - home.stateVersion = "25.11"; - }; - }) - ]; - }; - - phantom-ship = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - nix-openclaw.nixosModules.openclaw-gateway - ./hosts/phantom-ship.nix - - # Home Manager on NixOS - home-manager.nixosModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - home-manager.backupFileExtension = "backup"; - home-manager.users.danny = { ... }: { - home.username = "danny"; - home.homeDirectory = lib.mkForce "/home/danny"; - home.stateVersion = "25.11"; - }; - }) - ]; - }; - - # For disko-install: LUKS + WiFi; hostname/WiFi via --system-config. - server-install = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - disko.nixosModules.disko - ./disko-server.nix - ./hosts/server-install.nix - ]; - }; - - # Custom minimal installer ISO (build with: nix build .#installer-iso). - # Optional: add ./installer-wifi.nix (gitignored) to modules for live WiFi. - installer-iso = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ ./installer-iso.nix ]; - }; - }; - - packages.x86_64-linux.installer-iso = - self.nixosConfigurations.installer-iso.config.system.build.isoImage; - - # macOS (nix-darwin) configuration - darwinConfigurations."Daniel-Macbook-Air" = nix-darwin.lib.darwinSystem { - specialArgs = { inherit zen-browser; }; - modules = [ - ./hosts/daniel-macbook-air.nix - ./fish.nix - - # Home Manager on macOS - home-manager.darwinModules.home-manager - ({ lib, zen-browser, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - # Automatically backup files before home-manager overwrites them - home-manager.backupFileExtension = "backup"; - # Pass flake inputs to home-manager modules (e.g. home.nix) - home-manager.extraSpecialArgs = { inherit zen-browser; }; - home-manager.users.danny = { ... }: { - - # Force an absolute path even if another module sets a bad value. - home.username = "danny"; - home.homeDirectory = lib.mkForce "/Users/danny"; - imports = [ - ./home/danny/home.nix - ]; - }; - }) + imports = [ + ./flake-modules/wsl.nix + ./flake-modules/sunken-ship.nix + ./flake-modules/phantom-ship.nix + ./flake-modules/daniel-macbook-air.nix + ./flake-modules/server-install.nix + ./flake-modules/installer-iso.nix ]; }; - }; } diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 35b6a09..d3f346d 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, zen-browser ? null, ... }: +{ pkgs, lib, config, ... }: { # TODO: remove next two lines from here or from flake.nix # home.username = "danny"; @@ -171,9 +171,9 @@ # Fonts fonts.fontconfig.enable = true; home.packages = with pkgs; [ - # Zen Browser (Firefox fork; from flake, supports aarch64-darwin) - ] ++ (lib.optionals (zen-browser != null) [ - zen-browser.packages.${pkgs.stdenv.hostPlatform.system}.default + # Zen Browser (Firefox fork; from flake overlay, supports aarch64-darwin) + ] ++ (lib.optionals (pkgs ? zen-browser) [ + pkgs.zen-browser ]) ++ (with pkgs; [ # Google Fonts (includes Michroma) google-fonts From c69c7c9b1114089f5f93a408d71b802b5c97b72a Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 17:20:51 +0200 Subject: [PATCH 073/185] =?UTF-8?q?refactor(nix):=20dedupe=20home-manager?= =?UTF-8?q?=20wiring=20across=20hosts=20=E2=99=BB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the per-host home-manager block (useGlobalPkgs, useUserPackages, backupFileExtension, users. with username/homeDirectory/optional stateVersion/optional imports) into nixos/lib/home-manager-user.nix. Each flake-module now imports it with its per-host parameters, removing ~40 lines of boilerplate across the four hosts. --- nixos/flake-modules/daniel-macbook-air.nix | 19 ++++-------- nixos/flake-modules/phantom-ship.nix | 15 ++++------ nixos/flake-modules/sunken-ship.nix | 15 ++++------ nixos/flake-modules/wsl.nix | 15 ++++------ nixos/lib/home-manager-user.nix | 35 ++++++++++++++++++++++ 5 files changed, 55 insertions(+), 44 deletions(-) create mode 100644 nixos/lib/home-manager-user.nix diff --git a/nixos/flake-modules/daniel-macbook-air.nix b/nixos/flake-modules/daniel-macbook-air.nix index 68ede41..4729175 100644 --- a/nixos/flake-modules/daniel-macbook-air.nix +++ b/nixos/flake-modules/daniel-macbook-air.nix @@ -10,21 +10,12 @@ ../hosts/daniel-macbook-air.nix ../fish.nix - # Home Manager on macOS inputs.home-manager.darwinModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - # Automatically backup files before home-manager overwrites them - home-manager.backupFileExtension = "backup"; - home-manager.users.danny = { ... }: { - # Force an absolute path even if another module sets a bad value. - home.username = "danny"; - home.homeDirectory = lib.mkForce "/Users/danny"; - imports = [ - ../home/danny/home.nix - ]; - }; + (import ../lib/home-manager-user.nix { + lib = inputs.nixpkgs.lib; + user = "danny"; + homeDirectory = "/Users/danny"; + userImports = [ ../home/danny/home.nix ]; }) ]; }; diff --git a/nixos/flake-modules/phantom-ship.nix b/nixos/flake-modules/phantom-ship.nix index 24d1d2d..f77b454 100644 --- a/nixos/flake-modules/phantom-ship.nix +++ b/nixos/flake-modules/phantom-ship.nix @@ -5,17 +5,12 @@ inputs.nix-openclaw.nixosModules.openclaw-gateway ../hosts/phantom-ship.nix - # Home Manager on NixOS inputs.home-manager.nixosModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - home-manager.backupFileExtension = "backup"; - home-manager.users.danny = { ... }: { - home.username = "danny"; - home.homeDirectory = lib.mkForce "/home/danny"; - home.stateVersion = "25.11"; - }; + (import ../lib/home-manager-user.nix { + lib = inputs.nixpkgs.lib; + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; }) ]; }; diff --git a/nixos/flake-modules/sunken-ship.nix b/nixos/flake-modules/sunken-ship.nix index 3a78476..402a126 100644 --- a/nixos/flake-modules/sunken-ship.nix +++ b/nixos/flake-modules/sunken-ship.nix @@ -4,17 +4,12 @@ modules = [ ../hosts/sunken-ship.nix - # Home Manager on NixOS inputs.home-manager.nixosModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - home-manager.backupFileExtension = "backup"; - home-manager.users.danny = { ... }: { - home.username = "danny"; - home.homeDirectory = lib.mkForce "/home/danny"; - home.stateVersion = "25.11"; - }; + (import ../lib/home-manager-user.nix { + lib = inputs.nixpkgs.lib; + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; }) ]; }; diff --git a/nixos/flake-modules/wsl.nix b/nixos/flake-modules/wsl.nix index 711a66e..e9a491d 100644 --- a/nixos/flake-modules/wsl.nix +++ b/nixos/flake-modules/wsl.nix @@ -7,17 +7,12 @@ ../hosts/wsl.nix ../fish.nix - # Home Manager on WSL inputs.home-manager.nixosModules.home-manager - ({ lib, ... }: { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - home-manager.backupFileExtension = "backup"; - home-manager.users.dth = { ... }: { - home.username = "dth"; - home.homeDirectory = lib.mkForce "/home/dth"; - imports = [ ../home/danny/home.nix ]; - }; + (import ../lib/home-manager-user.nix { + lib = inputs.nixpkgs.lib; + user = "dth"; + homeDirectory = "/home/dth"; + userImports = [ ../home/danny/home.nix ]; }) ]; }; diff --git a/nixos/lib/home-manager-user.nix b/nixos/lib/home-manager-user.nix new file mode 100644 index 0000000..7ee81e0 --- /dev/null +++ b/nixos/lib/home-manager-user.nix @@ -0,0 +1,35 @@ +# Shared home-manager wiring for NixOS and nix-darwin hosts. +# +# Usage (from a flake-module): +# modules = [ +# inputs.home-manager.nixosModules.home-manager # or .darwinModules +# (import ../lib/home-manager-user.nix { +# lib = inputs.nixpkgs.lib; +# user = "danny"; +# homeDirectory = "/home/danny"; +# stateVersion = "25.11"; # optional +# userImports = [ ../home/danny/home.nix ]; # optional +# }) +# ]; +{ lib +, user +, homeDirectory +, stateVersion ? null +, userImports ? [ ] +}: +{ + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + # Automatically back up files before home-manager overwrites them. + home-manager.backupFileExtension = "backup"; + home-manager.users.${user} = { ... }: { + imports = userImports; + home = { + username = user; + # Force an absolute path even if another module sets a bad value. + homeDirectory = lib.mkForce homeDirectory; + } // lib.optionalAttrs (stateVersion != null) { + stateVersion = stateVersion; + }; + }; +} From 5e7b76bdcf7a373bb649295362aadf1d7a77ddbd Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 17:29:11 +0200 Subject: [PATCH 074/185] =?UTF-8?q?fix(servers):=20declare=20safe.director?= =?UTF-8?q?y=20in=20/etc/gitconfig=20=F0=9F=94=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dotfiles-rebuild service runs as root, but /etc/dotfiles is owned by `danny`. The GIT_CONFIG_* env vars in the service unit only affect the git CLI — nix/libgit2 reads safe.directory from /etc/gitconfig. After a recent nixpkgs bump libgit2 now enforces this strictly, so the service was failing to evaluate the flake. Enable programs.git and set programs.git.config.safe.directory = [ dotfilesDir ] on both sunken-ship and phantom-ship so the trust is persistent and Nix-managed. --- nixos/hosts/phantom-ship.nix | 6 ++++++ nixos/hosts/sunken-ship.nix | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 617b9e5..587ecee 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -134,6 +134,12 @@ in ReadWritePaths = [ "/var/lib/openclaw" "/etc/openclaw" ]; }; + # Trust /etc/dotfiles as root even though it's owned by `danny`. + # The GIT_CONFIG_* env vars below only affect the git CLI; nix/libgit2 + # reads safe.directory from /etc/gitconfig, so set it there too. + programs.git.enable = true; + programs.git.config.safe.directory = [ dotfilesDir ]; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 7099ae4..41c461b 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -185,6 +185,12 @@ in timerConfig.RandomizedDelaySec = "2min"; }; + # Trust /etc/dotfiles as root even though it's owned by `danny`. + # The GIT_CONFIG_* env vars below only affect the git CLI; nix/libgit2 + # reads safe.directory from /etc/gitconfig, so set it there too. + programs.git.enable = true; + programs.git.config.safe.directory = [ dotfilesDir ]; + # Pull dotfiles and rebuild if the repo has new commits. systemd.services.dotfiles-rebuild = { description = "Pull dotfiles and run nixos-rebuild if repo changed"; From 975b2a3ee91088c34a9b046f887410be7194db32 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 18:00:54 +0200 Subject: [PATCH 075/185] =?UTF-8?q?refactor(nix):=20auto-load=20flake-modu?= =?UTF-8?q?les=20+=20extract=20shared=20dotfiles-rebuild=20=F0=9F=8C=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add import-tree input; flake.nix now auto-loads every file under ./flake-modules so new hosts/features drop in without editing flake.nix. - Extract the duplicated dotfiles-rebuild service, timer, and safe.directory wiring into nixos/modules/dotfiles-rebuild.nix, exposed via flake.nixosModules.dotfiles-rebuild. - sunken-ship and phantom-ship now pull it in from their flake-modules; hostname-specific flakeRef is derived from config.networking.hostName. --- nixos/flake-modules/nixos-modules.nix | 7 +++++ nixos/flake-modules/phantom-ship.nix | 3 +- nixos/flake-modules/sunken-ship.nix | 3 +- nixos/flake.lock | 16 ++++++++++ nixos/flake.nix | 15 +++------ nixos/hosts/phantom-ship.nix | 36 ++-------------------- nixos/hosts/sunken-ship.nix | 37 ++-------------------- nixos/modules/dotfiles-rebuild.nix | 44 +++++++++++++++++++++++++++ 8 files changed, 80 insertions(+), 81 deletions(-) create mode 100644 nixos/flake-modules/nixos-modules.nix create mode 100644 nixos/modules/dotfiles-rebuild.nix diff --git a/nixos/flake-modules/nixos-modules.nix b/nixos/flake-modules/nixos-modules.nix new file mode 100644 index 0000000..c982dd9 --- /dev/null +++ b/nixos/flake-modules/nixos-modules.nix @@ -0,0 +1,7 @@ +# Expose reusable NixOS modules via `flake.nixosModules`. +# +# Consume from a host's flake-module via: +# modules = [ config.flake.nixosModules.dotfiles-rebuild ]; +{ ... }: { + flake.nixosModules.dotfiles-rebuild = ../modules/dotfiles-rebuild.nix; +} diff --git a/nixos/flake-modules/phantom-ship.nix b/nixos/flake-modules/phantom-ship.nix index f77b454..9a1ebbc 100644 --- a/nixos/flake-modules/phantom-ship.nix +++ b/nixos/flake-modules/phantom-ship.nix @@ -1,9 +1,10 @@ -{ inputs, ... }: { +{ inputs, config, ... }: { flake.nixosConfigurations.phantom-ship = inputs.nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ inputs.nix-openclaw.nixosModules.openclaw-gateway ../hosts/phantom-ship.nix + config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager (import ../lib/home-manager-user.nix { diff --git a/nixos/flake-modules/sunken-ship.nix b/nixos/flake-modules/sunken-ship.nix index 402a126..0bf77ef 100644 --- a/nixos/flake-modules/sunken-ship.nix +++ b/nixos/flake-modules/sunken-ship.nix @@ -1,8 +1,9 @@ -{ inputs, ... }: { +{ inputs, config, ... }: { flake.nixosConfigurations.sunken-ship = inputs.nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ ../hosts/sunken-ship.nix + config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager (import ../lib/home-manager-user.nix { diff --git a/nixos/flake.lock b/nixos/flake.lock index f185299..e2ee1ce 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -154,6 +154,21 @@ "type": "github" } }, + "import-tree": { + "locked": { + "lastModified": 1773693634, + "narHash": "sha256-BtZ2dtkBdSUnFPPFc+n0kcMbgaTxzFNPv2iaO326Ffg=", + "owner": "vic", + "repo": "import-tree", + "rev": "c41e7d58045f9057880b0d85e1152d6a4430dbf1", + "type": "github" + }, + "original": { + "owner": "vic", + "repo": "import-tree", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -303,6 +318,7 @@ "disko": "disko", "flake-parts": "flake-parts", "home-manager": "home-manager", + "import-tree": "import-tree", "nix-darwin": "nix-darwin", "nix-openclaw": "nix-openclaw", "nixos-wsl": "nixos-wsl", diff --git a/nixos/flake.nix b/nixos/flake.nix index c9ab314..c465290 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -7,6 +7,9 @@ flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; + # Auto-loads every .nix file under ./flake-modules as a flake-parts module. + import-tree.url = "github:vic/import-tree"; + nix-darwin.url = "github:nix-darwin/nix-darwin/master"; nix-darwin.inputs.nixpkgs.follows = "nixpkgs"; @@ -23,17 +26,9 @@ nix-openclaw.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = inputs @ { flake-parts, ... }: + outputs = inputs @ { flake-parts, import-tree, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" "aarch64-darwin" ]; - - imports = [ - ./flake-modules/wsl.nix - ./flake-modules/sunken-ship.nix - ./flake-modules/phantom-ship.nix - ./flake-modules/daniel-macbook-air.nix - ./flake-modules/server-install.nix - ./flake-modules/installer-iso.nix - ]; + imports = [ (import-tree ./flake-modules) ]; }; } diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 587ecee..4ebc6c4 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -2,9 +2,6 @@ { config, lib, pkgs, ... }: let - dotfilesDir = "/etc/dotfiles"; - flakeRef = "${dotfilesDir}/nixos#phantom-ship"; - # Telegram user ID(s) — gitignored, not committed to public repo. # Create openclaw-allow-from.nix with e.g.: [ 12345678 ] allowFromPath = ./openclaw-allow-from.nix; @@ -134,35 +131,6 @@ in ReadWritePaths = [ "/var/lib/openclaw" "/etc/openclaw" ]; }; - # Trust /etc/dotfiles as root even though it's owned by `danny`. - # The GIT_CONFIG_* env vars below only affect the git CLI; nix/libgit2 - # reads safe.directory from /etc/gitconfig, so set it there too. - programs.git.enable = true; - programs.git.config.safe.directory = [ dotfilesDir ]; - - # Pull dotfiles and rebuild if the repo has new commits. - systemd.services.dotfiles-rebuild = { - description = "Pull dotfiles and run nixos-rebuild if repo changed"; - path = with pkgs; [ git nix nixos-rebuild ]; - environment.GIT_CONFIG_COUNT = "1"; - environment.GIT_CONFIG_KEY_0 = "safe.directory"; - environment.GIT_CONFIG_VALUE_0 = dotfilesDir; - script = '' - set -euo pipefail - cd ${dotfilesDir} - git fetch origin - if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then - exit 0 - fi - git pull origin main - exec nixos-rebuild switch --flake ${flakeRef} - ''; - serviceConfig.Type = "oneshot"; - }; - - systemd.timers.dotfiles-rebuild = { - wantedBy = [ "timers.target" ]; - timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes - timerConfig.RandomizedDelaySec = "2min"; - }; + # Auto-rebuild service/timer + safe.directory provided by the + # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 41c461b..2e8175c 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -7,10 +7,6 @@ # Timer runs every 15 min: git fetch, pull if origin/main changed, rebuild. { config, lib, pkgs, ... }: -let - dotfilesDir = "/etc/dotfiles"; - flakeRef = "${dotfilesDir}/nixos#sunken-ship"; -in { imports = [ ./sunken-ship-hardware.nix ]; @@ -185,35 +181,6 @@ in timerConfig.RandomizedDelaySec = "2min"; }; - # Trust /etc/dotfiles as root even though it's owned by `danny`. - # The GIT_CONFIG_* env vars below only affect the git CLI; nix/libgit2 - # reads safe.directory from /etc/gitconfig, so set it there too. - programs.git.enable = true; - programs.git.config.safe.directory = [ dotfilesDir ]; - - # Pull dotfiles and rebuild if the repo has new commits. - systemd.services.dotfiles-rebuild = { - description = "Pull dotfiles and run nixos-rebuild if repo changed"; - path = with pkgs; [ git nix nixos-rebuild ]; - environment.GIT_CONFIG_COUNT = "1"; - environment.GIT_CONFIG_KEY_0 = "safe.directory"; - environment.GIT_CONFIG_VALUE_0 = dotfilesDir; - script = '' - set -euo pipefail - cd ${dotfilesDir} - git fetch origin - if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then - exit 0 - fi - git pull origin main - exec nixos-rebuild switch --flake ${flakeRef} - ''; - serviceConfig.Type = "oneshot"; - }; - - systemd.timers.dotfiles-rebuild = { - wantedBy = [ "timers.target" ]; - timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes - timerConfig.RandomizedDelaySec = "2min"; - }; + # Auto-rebuild service/timer + safe.directory provided by the + # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } diff --git a/nixos/modules/dotfiles-rebuild.nix b/nixos/modules/dotfiles-rebuild.nix new file mode 100644 index 0000000..709ebaf --- /dev/null +++ b/nixos/modules/dotfiles-rebuild.nix @@ -0,0 +1,44 @@ +# Shared auto-rebuild-from-git service for homelab hosts. +# +# Every 15 min: git fetch origin, fast-forward main, and if there were any +# new commits run nixos-rebuild switch against `/nixos#`. +# +# Assumes /etc/dotfiles is an already-cloned checkout of the dotfiles repo. +{ config, lib, pkgs, ... }: +let + dotfilesDir = "/etc/dotfiles"; + flakeRef = "${dotfilesDir}/nixos#${config.networking.hostName}"; +in { + environment.systemPackages = [ pkgs.git ]; + + # Trust /etc/dotfiles as root even though it's owned by `danny`. + # nix/libgit2 reads safe.directory from /etc/gitconfig; the GIT_CONFIG_* + # env vars on the service only affect the git CLI, not nix. + programs.git.enable = true; + programs.git.config.safe.directory = [ dotfilesDir ]; + + systemd.services.dotfiles-rebuild = { + description = "Pull dotfiles and run nixos-rebuild if repo changed"; + path = with pkgs; [ git nix nixos-rebuild ]; + environment.GIT_CONFIG_COUNT = "1"; + environment.GIT_CONFIG_KEY_0 = "safe.directory"; + environment.GIT_CONFIG_VALUE_0 = dotfilesDir; + script = '' + set -euo pipefail + cd ${dotfilesDir} + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then + exit 0 + fi + git pull origin main + exec nixos-rebuild switch --flake ${flakeRef} + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.dotfiles-rebuild = { + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes + timerConfig.RandomizedDelaySec = "2min"; + }; +} From 40627405f72f2ee1dcf751aeecf1108223d9f27e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 22:27:28 +0200 Subject: [PATCH 076/185] =?UTF-8?q?feat:=20add=20claude-channels=20systemd?= =?UTF-8?q?=20service=20on=20phantom-ship=20=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code Channels replaces OpenClaw for the @HarakatBot Telegram bridge. Uses claude.ai subscription auth via long-lived OAuth token at /etc/claude-channels/env — sidesteps the API rate limits OpenClaw was hitting. Runs as danny since plugin + pairing state lives in ~/.claude. Wraps claude in script(1) because claude needs a PTY for its interactive session mode. OpenClaw service disabled but config kept for easy rollback during validation. Will be fully removed once Channels is proven stable. Her workspace (SOUL/MEMORY/IDENTITY/etc) is preserved in vimwiki/openclaw/workspace/. --- nixos/hosts/phantom-ship.nix | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 4ebc6c4..605d222 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -85,10 +85,13 @@ in claude-code # Claude Code CLI (channels replaces openclaw) ]; - # OpenClaw AI gateway — Telegram bot, Anthropic API. + # OpenClaw AI gateway — DISABLED. Replaced by Claude Code Channels below. + # Config kept for easy rollback during validation; will be fully removed in a + # follow-up commit once Channels is proven stable. Workspace state at + # /var/lib/openclaw/ is preserved and also committed to vimwiki/openclaw/. # Secrets (not in repo): /etc/openclaw/telegram-bot-token, /etc/openclaw/env (ANTHROPIC_API_KEY) services.openclaw-gateway = { - enable = true; + enable = false; environmentFiles = [ "/etc/openclaw/env" ]; servicePath = [ pkgs.git pkgs.nodejs pkgs.openai-whisper ]; config = { @@ -100,6 +103,33 @@ in }; }; + # Claude Code Channels — Telegram bridge for @HarakatBot. + # Uses claude.ai subscription auth (long-lived OAuth token) to bypass + # the API rate limits OpenClaw was hitting. + # Secret (not in repo): /etc/claude-channels/env (CLAUDE_CODE_OAUTH_TOKEN) + # Plugin + pairing state lives at /home/danny/.claude/ (set up interactively). + systemd.services.claude-channels = { + description = "Claude Code Channels (Telegram bridge for @HarakatBot)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.claude-code pkgs.bun pkgs.git pkgs.util-linux ]; + environment = { + HOME = "/home/danny"; + }; + serviceConfig = { + Type = "simple"; + User = "danny"; + Group = "users"; + WorkingDirectory = "/home/danny"; + EnvironmentFile = "/etc/claude-channels/env"; + # claude needs a PTY; wrap with script(1). /dev/null discards the typescript. + ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official" /dev/null''; + Restart = "always"; + RestartSec = 5; + }; + }; + # OpenClaw gateway needs write access to its config dir and repo clones. systemd.tmpfiles.rules = [ "d /etc/openclaw 0775 root openclaw - -" From 6500ad39bff165a9d1233c96a046af4def19febc Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 22:28:32 +0200 Subject: [PATCH 077/185] =?UTF-8?q?fix:=20gate=20openclaw-gateway=20harden?= =?UTF-8?q?ing=20on=20enable=20flag=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 605d222..3137532 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -151,14 +151,16 @@ in mode = "0644"; }; - # Harden the openclaw-gateway systemd service. - systemd.services.openclaw-gateway.environment.GIT_CONFIG_GLOBAL = "/etc/openclaw/gitconfig"; - systemd.services.openclaw-gateway.serviceConfig = { - ProtectHome = "read-only"; - ProtectSystem = "strict"; - PrivateTmp = true; - NoNewPrivileges = true; - ReadWritePaths = [ "/var/lib/openclaw" "/etc/openclaw" ]; + # Harden the openclaw-gateway systemd service (only when enabled). + systemd.services.openclaw-gateway = lib.mkIf config.services.openclaw-gateway.enable { + environment.GIT_CONFIG_GLOBAL = "/etc/openclaw/gitconfig"; + serviceConfig = { + ProtectHome = "read-only"; + ProtectSystem = "strict"; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = [ "/var/lib/openclaw" "/etc/openclaw" ]; + }; }; # Auto-rebuild service/timer + safe.directory provided by the From 7f40280700335b248d720df5b7954efb33332210 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 22:45:03 +0200 Subject: [PATCH 078/185] =?UTF-8?q?feat:=20skip=20permission=20prompts=20f?= =?UTF-8?q?or=20claude-channels=20unattended=20use=20=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 3137532..427b5c1 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -124,7 +124,9 @@ in WorkingDirectory = "/home/danny"; EnvironmentFile = "/etc/claude-channels/env"; # claude needs a PTY; wrap with script(1). /dev/null discards the typescript. - ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official" /dev/null''; + # --dangerously-skip-permissions: unattended use. Safe here because the bot + # is paired only to danny's Telegram ID (allowFrom) on a private server. + ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official --dangerously-skip-permissions" /dev/null''; Restart = "always"; RestartSec = 5; }; From 9566986ade28989277c7fa8687bf9868e9a81e91 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 18 Apr 2026 22:47:21 +0200 Subject: [PATCH 079/185] =?UTF-8?q?fix:=20move=20permission=20bypass=20to?= =?UTF-8?q?=20settings.json=20to=20avoid=20warning=20dialog=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 427b5c1..3aeb7e1 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -124,9 +124,9 @@ in WorkingDirectory = "/home/danny"; EnvironmentFile = "/etc/claude-channels/env"; # claude needs a PTY; wrap with script(1). /dev/null discards the typescript. - # --dangerously-skip-permissions: unattended use. Safe here because the bot - # is paired only to danny's Telegram ID (allowFrom) on a private server. - ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official --dangerously-skip-permissions" /dev/null''; + # Permission bypass lives in ~/.claude/settings.json (permissions.defaultMode) + # — using the CLI flag triggers an interactive warning dialog at startup. + ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official" /dev/null''; Restart = "always"; RestartSec = 5; }; From 14e60ca839a1f1bf1ee338d73f19fd2987aa920c Mon Sep 17 00:00:00 2001 From: Hara Date: Sat, 18 Apr 2026 23:05:24 +0200 Subject: [PATCH 080/185] phantom-ship: add openai-whisper + ffmpeg for voice transcription --- nixos/hosts/phantom-ship.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 3aeb7e1..4bb0de0 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -83,6 +83,8 @@ in wakeonlan # wake rusty-anchor: wakeonlan 00:16:cb:87:20:ba bun # runtime for claude-code channel plugins claude-code # Claude Code CLI (channels replaces openclaw) + openai-whisper # voice message transcription + ffmpeg # audio decoding for whisper ]; # OpenClaw AI gateway — DISABLED. Replaced by Claude Code Channels below. From c3742db32ee358ae24b05ae5084ac619f02e70af Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 13:20:20 +0200 Subject: [PATCH 081/185] =?UTF-8?q?feat(phantom-ship):=20add=20shipyard=20?= =?UTF-8?q?systemd=20service=20=F0=9F=9A=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegram bot hub that lists mini-apps and collects feedback via ForceReply. Code deployed via rsync to /home/danny/shipyard/; token at ~danny/.secrets/telegram-bot-token-shipyard. --- nixos/hosts/phantom-ship.nix | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 4bb0de0..60668e7 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -167,6 +167,34 @@ in }; }; + # Shipyard — Telegram bot that lists Danny's mini-apps and collects feedback. + # Code deployed out-of-band via rsync to /home/danny/shipyard/ + # (staying in-tree in ~/python-projects/26_shipyard/ until spun out to its own repo). + # Bot token (not in repo): ~danny/.secrets/telegram-bot-token-shipyard + # Data (feedback.jsonl, pointer cache): ~danny/.local/share/shipyard/ + systemd.services.shipyard = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + python-telegram-bot + httpx + ]); + in { + description = "Shipyard Telegram bot (mini-app launcher + feedback)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/shipyard"; + ExecStart = "${pythonEnv}/bin/python bot.py"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 663be7872ab5f6c47b02cbe327187cb3e1577b0f Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 13:48:25 +0200 Subject: [PATCH 082/185] =?UTF-8?q?fix(neovim):=20set=20withRuby=20and=20w?= =?UTF-8?q?ithPython3=20explicitly=20to=20false=20=F0=9F=94=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/neovim.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/neovim.nix b/nixos/neovim.nix index 90743fb..59a6f85 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -4,6 +4,8 @@ programs.neovim = { enable = true; defaultEditor = true; + withRuby = false; + withPython3 = false; # VimScript settings (options that have no Lua equivalent or are simpler in vim) extraConfig = '' From 29ff1c9be7014f558301b3440e31dcfbd8289b80 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 13:54:44 +0200 Subject: [PATCH 083/185] =?UTF-8?q?feat(nix):=20bootstrap=20clan-core=20fo?= =?UTF-8?q?r=20sunken-ship=20+=20phantom-ship=20=F0=9F=8F=B4=E2=80=8D?= =?UTF-8?q?=E2=98=A0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4a of the dendritic + clan migration. Both servers now live under clan.machines (via nixos/flake-modules/clan.nix) and clan-core generates their nixosConfigurations for us; the previous per-host flake-modules are removed. Notes: - clan.core.enableRecommendedDefaults = false on both machines so we keep the existing dhcpcd / non-networkd / non-resolved stack. Services like dnsmasq, navidrome, and the existing wireless setup break with the clan defaults on. - dotfiles-rebuild timer is untouched (safety net). Replacing it with clan machines update / dm-pull-deploy comes in 4e. - mac stays outside the clan as admin only. Verified: `clan machines list --flake path:…/nixos` returns both hosts; both servers rebuild cleanly and all services (navidrome, cloudflare- tunnel, fitness-bot, dnsmasq, openclaw-gateway, sshd) stay active. --- nixos/flake-modules/clan.nix | 55 ++++++++ nixos/flake-modules/phantom-ship.nix | 18 --- nixos/flake-modules/sunken-ship.nix | 17 --- nixos/flake.lock | 183 ++++++++++++++++++++++++++- nixos/flake.nix | 4 + 5 files changed, 235 insertions(+), 42 deletions(-) create mode 100644 nixos/flake-modules/clan.nix delete mode 100644 nixos/flake-modules/phantom-ship.nix delete mode 100644 nixos/flake-modules/sunken-ship.nix diff --git a/nixos/flake-modules/clan.nix b/nixos/flake-modules/clan.nix new file mode 100644 index 0000000..4a2dd3d --- /dev/null +++ b/nixos/flake-modules/clan.nix @@ -0,0 +1,55 @@ +# clan.lol wiring for the homelab. +# +# Declares `sunken-ship` and `phantom-ship` as clan machines. Each machine's +# `imports` list is the NixOS module set that used to live in its own +# flake-module. clan-core produces `flake.nixosConfigurations.` from +# these, which is why the old per-host flake-modules were removed. +# +# The mac stays outside the clan — admin only, uses `clan machines update` +# to push to the servers. +{ config, inputs, ... }: +let + lib = inputs.nixpkgs.lib; + hmModule = { user, homeDirectory, stateVersion ? null, userImports ? [ ] }: + import ../lib/home-manager-user.nix { + inherit lib user homeDirectory stateVersion userImports; + }; +in { + imports = [ inputs.clan-core.flakeModules.default ]; + + clan = { + meta.name = "homelab"; + + # Preserve current network / init stack (no systemd-networkd/resolved, + # no boot.initrd.systemd, no extra debug packages). Revisit per-service + # in later stages rather than flipping this fleet-wide. + machines.sunken-ship = { + imports = [ + { clan.core.enableRecommendedDefaults = false; } + ../hosts/sunken-ship.nix + config.flake.nixosModules.dotfiles-rebuild + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + + machines.phantom-ship = { + imports = [ + { clan.core.enableRecommendedDefaults = false; } + inputs.nix-openclaw.nixosModules.openclaw-gateway + ../hosts/phantom-ship.nix + config.flake.nixosModules.dotfiles-rebuild + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + }; +} diff --git a/nixos/flake-modules/phantom-ship.nix b/nixos/flake-modules/phantom-ship.nix deleted file mode 100644 index 9a1ebbc..0000000 --- a/nixos/flake-modules/phantom-ship.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ inputs, config, ... }: { - flake.nixosConfigurations.phantom-ship = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - inputs.nix-openclaw.nixosModules.openclaw-gateway - ../hosts/phantom-ship.nix - config.flake.nixosModules.dotfiles-rebuild - - inputs.home-manager.nixosModules.home-manager - (import ../lib/home-manager-user.nix { - lib = inputs.nixpkgs.lib; - user = "danny"; - homeDirectory = "/home/danny"; - stateVersion = "25.11"; - }) - ]; - }; -} diff --git a/nixos/flake-modules/sunken-ship.nix b/nixos/flake-modules/sunken-ship.nix deleted file mode 100644 index 0bf77ef..0000000 --- a/nixos/flake-modules/sunken-ship.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ inputs, config, ... }: { - flake.nixosConfigurations.sunken-ship = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - ../hosts/sunken-ship.nix - config.flake.nixosModules.dotfiles-rebuild - - inputs.home-manager.nixosModules.home-manager - (import ../lib/home-manager-user.nix { - lib = inputs.nixpkgs.lib; - user = "danny"; - homeDirectory = "/home/danny"; - stateVersion = "25.11"; - }) - ]; - }; -} diff --git a/nixos/flake.lock b/nixos/flake.lock index e2ee1ce..2de4459 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -1,6 +1,82 @@ { "nodes": { + "clan-core": { + "inputs": { + "data-mesher": "data-mesher", + "disko": "disko", + "flake-parts": [ + "flake-parts" + ], + "nix-darwin": "nix-darwin", + "nix-select": "nix-select", + "nixpkgs": [ + "nixpkgs" + ], + "sops-nix": "sops-nix", + "systems": "systems", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1776557977, + "narHash": "sha256-j+UWg3fR6jWKPqkPoqRf1a6nR1b/AnZXDuh04H+voUc=", + "rev": "e9ced950bedc726492e5cb52139bf5f17258dc69", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/e9ced950bedc726492e5cb52139bf5f17258dc69.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.clan.lol/clan/clan-core/archive/main.tar.gz" + } + }, + "data-mesher": { + "inputs": { + "flake-parts": [ + "clan-core", + "flake-parts" + ], + "nixpkgs": [ + "clan-core", + "nixpkgs" + ], + "treefmt-nix": [ + "clan-core", + "treefmt-nix" + ] + }, + "locked": { + "lastModified": 1776506822, + "narHash": "sha256-WlxAhXEoDHbkfFw3uNYra0CXce7pBk314x9chPu7ycE=", + "rev": "c3f48f5931b27bb9cc58de8799d36ecefb867d98", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/c3f48f5931b27bb9cc58de8799d36ecefb867d98.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.clan.lol/clan/data-mesher/archive/main.tar.gz" + } + }, "disko": { + "inputs": { + "nixpkgs": [ + "clan-core", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773889306, + "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", + "owner": "nix-community", + "repo": "disko", + "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, + "disko_2": { "inputs": { "nixpkgs": [ "nixpkgs" @@ -58,7 +134,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems" + "systems": "systems_2" }, "locked": { "lastModified": 1731533236, @@ -76,7 +152,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1681202837, @@ -170,6 +246,27 @@ } }, "nix-darwin": { + "inputs": { + "nixpkgs": [ + "clan-core", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775037210, + "narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=", + "owner": "nix-darwin", + "repo": "nix-darwin", + "rev": "06648f4902343228ce2de79f291dd5a58ee12146", + "type": "github" + }, + "original": { + "owner": "nix-darwin", + "repo": "nix-darwin", + "type": "github" + } + }, + "nix-darwin_2": { "inputs": { "nixpkgs": [ "nixpkgs" @@ -213,6 +310,19 @@ "type": "github" } }, + "nix-select": { + "locked": { + "lastModified": 1763303120, + "narHash": "sha256-yxcNOha7Cfv2nhVpz9ZXSNKk0R7wt4AiBklJ8D24rVg=", + "rev": "3d1e3860bef36857a01a2ddecba7cdb0a14c35a9", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/nix-select/archive/3d1e3860bef36857a01a2ddecba7cdb0a14c35a9.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.clan.lol/clan/nix-select/archive/main.tar.gz" + } + }, "nix-steipete-tools": { "inputs": { "nixpkgs": "nixpkgs" @@ -315,11 +425,12 @@ }, "root": { "inputs": { - "disko": "disko", + "clan-core": "clan-core", + "disko": "disko_2", "flake-parts": "flake-parts", "home-manager": "home-manager", "import-tree": "import-tree", - "nix-darwin": "nix-darwin", + "nix-darwin": "nix-darwin_2", "nix-openclaw": "nix-openclaw", "nixos-wsl": "nixos-wsl", "nixpkgs": "nixpkgs_3", @@ -327,17 +438,39 @@ "zen-browser": "zen-browser" } }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "clan-core", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1776119890, + "narHash": "sha256-Zm6bxLNnEOYuS/SzrAGsYuXSwk3cbkRQZY0fJnk8a5M=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "d4971dd58c6627bfee52a1ad4237637c0a2fb0cd", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, "systems": { "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "lastModified": 1774449309, + "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", "owner": "nix-systems", "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "rev": "c29398b59d2048c4ab79345812849c9bd15e9150", "type": "github" }, "original": { "owner": "nix-systems", + "ref": "future-26.11", "repo": "default", "type": "github" } @@ -357,6 +490,42 @@ "type": "github" } }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "clan-core", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775636079, + "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "vscode-server": { "inputs": { "flake-utils": "flake-utils_2", diff --git a/nixos/flake.nix b/nixos/flake.nix index c465290..8b802e3 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -24,6 +24,10 @@ nix-openclaw.url = "github:openclaw/nix-openclaw"; nix-openclaw.inputs.nixpkgs.follows = "nixpkgs"; + + clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; + clan-core.inputs.nixpkgs.follows = "nixpkgs"; + clan-core.inputs.flake-parts.follows = "flake-parts"; }; outputs = inputs @ { flake-parts, import-tree, ... }: From 9921a7f9f108f2c793641b2315e8b164dcb80612 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 14:43:29 +0200 Subject: [PATCH 084/185] =?UTF-8?q?feat(nix):=20zerotier=20overlay=20via?= =?UTF-8?q?=20clan=20inventory=20+=20mac=20ZT=20client=20=F0=9F=95=B8?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4b of the clan migration. Declares a clan.inventory.instances.zerotier instance with sunken-ship as controller and phantom-ship as peer (controller is also listed as a peer so it joins its own network). Generates the network ID, controller identity, and per-peer identities via `clan vars generate`; all secrets are SOPS-encrypted to the user's age key and the per-machine age keys. - nixos/sops/ — clan-managed SOPS state (user + per-machine age keys). - nixos/vars/ — shared + per-machine zerotier vars; *-identity-secret files are SOPS-encrypted, *.value files are plain public data. - clan.core.networking.{targetHost,buildHost} = "danny@" on both servers so `clan machines update` knows where to push and build. - mac gets `zerotier-one` installed as a homebrew cask; authorization on the controller happens manually by node-ID in a follow-up step. Known rough edges (to chase in later stages): - zerotier-inventory-autoaccept.service races zerotierone.service on first activation (connection refused against the local API). Retrying the unit succeeds; clan upstream bug. - Deployment must go through `clan machines update`, not plain nixos-rebuild, or the per-host SOPS age key isn't uploaded and zerotier-one can't decrypt its identity. --- CLAUDE.md | 15 ++++++++++ nixos/flake-modules/clan.nix | 29 +++++++++++++++++-- nixos/hosts/daniel-macbook-air.nix | 1 + nixos/sops/machines/phantom-ship/key.json | 6 ++++ nixos/sops/machines/sunken-ship/key.json | 6 ++++ .../sops/secrets/phantom-ship-age.key/secret | 14 +++++++++ .../secrets/phantom-ship-age.key/users/danny | 1 + nixos/sops/secrets/sunken-ship-age.key/secret | 14 +++++++++ .../secrets/sunken-ship-age.key/users/danny | 1 + nixos/sops/users/danny/key.json | 6 ++++ .../machines/phantom-ship | 1 + .../zerotier/zerotier-identity-secret/secret | 18 ++++++++++++ .../zerotier-identity-secret/users/danny | 1 + .../phantom-ship/zerotier/zerotier-ip/value | 1 + .../machines/sunken-ship | 1 + .../zerotier/zerotier-identity-secret/secret | 18 ++++++++++++ .../zerotier-identity-secret/users/danny | 1 + .../sunken-ship/zerotier/zerotier-ip/value | 1 + .../zerotier/zerotier-network-id/value | 1 + .../zerotier-identity-secret/secret | 14 +++++++++ .../zerotier-identity-secret/users/danny | 1 + .../zerotier-controller/zerotier-ip/value | 1 + .../zerotier-network-id/value | 1 + 23 files changed, 151 insertions(+), 2 deletions(-) create mode 100755 nixos/sops/machines/phantom-ship/key.json create mode 100755 nixos/sops/machines/sunken-ship/key.json create mode 100644 nixos/sops/secrets/phantom-ship-age.key/secret create mode 120000 nixos/sops/secrets/phantom-ship-age.key/users/danny create mode 100644 nixos/sops/secrets/sunken-ship-age.key/secret create mode 120000 nixos/sops/secrets/sunken-ship-age.key/users/danny create mode 100755 nixos/sops/users/danny/key.json create mode 120000 nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship create mode 100644 nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret create mode 120000 nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny create mode 100644 nixos/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value create mode 120000 nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship create mode 100644 nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret create mode 120000 nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny create mode 100644 nixos/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value create mode 100644 nixos/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value create mode 100644 nixos/vars/shared/zerotier-controller/zerotier-identity-secret/secret create mode 120000 nixos/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny create mode 100644 nixos/vars/shared/zerotier-controller/zerotier-ip/value create mode 100644 nixos/vars/shared/zerotier-controller/zerotier-network-id/value diff --git a/CLAUDE.md b/CLAUDE.md index 43d0038..43ff508 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,21 @@ Custom nix-darwin module at `nixos/ollama.nix` (upstream PR not yet merged). Ena Terminal colors follow **System Settings → Appearance**: `programs.alacritty` imports `~/.config/alacritty/active-colors.toml`; `scripts/alacritty-sync-system-theme.sh` copies Catppuccin latte/mocha there when the OS mode changes. **nix-darwin** `launchd.user.agents.alacritty-system-theme` polls every 30s; **fish** runs the same script on interactive startup. After changing Nix, one `darwin-rebuild switch`. Details: `assets/alacritty/README.md`. +## clan.lol + +**CLI invocation:** clan-cli is not installed globally. Run ad-hoc via: + +```bash +nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- machines list \ + --flake 'path:/Users/danny/dotfiles/nixos' +``` + +**Flake path quirk:** `--flake .` and `--flake git+…` both fail from a git worktree when the flake lives in a subdir (`nixos/`). Use `--flake 'path:…/nixos'` explicitly. May not be needed from the main checkout — retest. + +**`enableRecommendedDefaults = false`:** we opted out fleet-wide because clan's defaults flip to `systemd-networkd` + `systemd-resolved` + `boot.initrd.systemd`, which breaks dnsmasq (NAT DNS on phantom-ship) and navidrome's resolv.conf bind-mount on sunken-ship. Revisit per-service in a later pass — the defaults also include handy extras (tcpdump, htop, curl, jq, nixos-facter). Option defined in `nixosModules/clanCore/defaults.nix` + `nixosModules/clanCore/networking.nix` inside the `clan-core` flake. + +**Deployment:** `dotfiles-rebuild` timer (every 15 min pull) is still the source of truth. `clan machines update` works as a push escape hatch; dm-pull-deploy replaces the timer in a later stage. + ## Shell Fish is the default shell. Bash auto-execs fish unless the parent process is already fish. Vi keybindings with fzf integration. Zoxide aliased to `cd`. diff --git a/nixos/flake-modules/clan.nix b/nixos/flake-modules/clan.nix index 4a2dd3d..56defc6 100644 --- a/nixos/flake-modules/clan.nix +++ b/nixos/flake-modules/clan.nix @@ -20,12 +20,33 @@ in { clan = { meta.name = "homelab"; + # Inventory machines — required for `inventory.instances` role bindings + # to resolve. Host-specific NixOS config lives under `machines.` + # below. + inventory.machines.sunken-ship = { }; + inventory.machines.phantom-ship = { }; + + # ZeroTier mesh VPN. sunken-ship is the controller (manages network + # membership); phantom-ship is a peer. The mac joins manually as an + # external ZT client and is authorized on the controller by node ID. + inventory.instances.zerotier = { + module.name = "zerotier"; + module.input = "clan-core"; + roles.controller.machines.sunken-ship = { }; + roles.peer.machines.phantom-ship = { }; + roles.peer.machines.sunken-ship = { }; + }; + # Preserve current network / init stack (no systemd-networkd/resolved, # no boot.initrd.systemd, no extra debug packages). Revisit per-service # in later stages rather than flipping this fleet-wide. machines.sunken-ship = { imports = [ - { clan.core.enableRecommendedDefaults = false; } + { + clan.core.enableRecommendedDefaults = false; + clan.core.networking.targetHost = "danny@sunken-ship"; + clan.core.networking.buildHost = "danny@sunken-ship"; + } ../hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager @@ -39,7 +60,11 @@ in { machines.phantom-ship = { imports = [ - { clan.core.enableRecommendedDefaults = false; } + { + clan.core.enableRecommendedDefaults = false; + clan.core.networking.targetHost = "danny@phantom-ship"; + clan.core.networking.buildHost = "danny@phantom-ship"; + } inputs.nix-openclaw.nixosModules.openclaw-gateway ../hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild diff --git a/nixos/hosts/daniel-macbook-air.nix b/nixos/hosts/daniel-macbook-air.nix index a05ff6a..97f02c4 100644 --- a/nixos/hosts/daniel-macbook-air.nix +++ b/nixos/hosts/daniel-macbook-air.nix @@ -40,6 +40,7 @@ in { "google-chrome" "disk-inventory-x" # Apple Silicon uses Homebrew; nixpkgs package is x86_64-darwin only. "qflipper" # Flipper Zero firmware updater GUI + "zerotier-one" # Clan homelab overlay — authorize on sunken-ship controller # "uhk-agent" # Ultimate Hacking Keyboard configuration — removed, nixpkgs marks x86_64-linux only TODO ]; onActivation.cleanup = "zap"; diff --git a/nixos/sops/machines/phantom-ship/key.json b/nixos/sops/machines/phantom-ship/key.json new file mode 100755 index 0000000..1f3c1f0 --- /dev/null +++ b/nixos/sops/machines/phantom-ship/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "type": "age" + } +] \ No newline at end of file diff --git a/nixos/sops/machines/sunken-ship/key.json b/nixos/sops/machines/sunken-ship/key.json new file mode 100755 index 0000000..95be23b --- /dev/null +++ b/nixos/sops/machines/sunken-ship/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "type": "age" + } +] \ No newline at end of file diff --git a/nixos/sops/secrets/phantom-ship-age.key/secret b/nixos/sops/secrets/phantom-ship-age.key/secret new file mode 100644 index 0000000..eab510b --- /dev/null +++ b/nixos/sops/secrets/phantom-ship-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:43IKkW3YpbpEtECD3kXV4zWF6hB39knoWwqy5BGCqvYWSPccKIwwLD3ctCy3SeH806AatvE8Bl2dvHFvP++xtvFtw5PaHdnenn8=,iv:j7ODs5O0rbwD0LWkkv9BEk6O9ySl+uhCiEVa+GkRE3k=,tag:Bk/PkQjOvul8pP7hoh2cwQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVaVlVQ3pvRmpLaVB3WWtl\nYjBIdDBJK0VKeS90eE5YeFhFRnBPak5YckFZCkl5RkVMV3JxL0pSVkM4cjhRaUE3\nK24vSWM0YnFWeXNjc3ZSWDRBb1ZDeWsKLS0tIENabmsxVUl0UGZzN1pncWswTVdM\nWDBVTVMrYzJHUklKSVVjYXBBM2RuajgKCvrGjfjujmqq2lsbNAb8d1xUhv+es2uX\nydcfnqbFRF4pjrku41iRaOolWrZHDvl+PnMslk8bclZG23UKYbSkbA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-19T12:31:43Z", + "mac": "ENC[AES256_GCM,data:7/Z1Up1DZUgNMCuuBh2pnfTH3Ih6824yJqD1+w9clqgkSrFtKL6v5oo5EV4TF2FDJcrYQtbbAWQoEgJXfCKXfIYOPBIChfoQEG5N5XxNe57bklkipOMWJBm7448qBhLgy3yJQqAVFkQw6uHTuDrcngRFW5D3xHkCSilHC/xau9U=,iv:WL98Dcuxojg6BQ5tLOuhXYCfFHVXqpIBr680uriPXz0=,tag:FCl6wkBiLJUyMu1RnOqeIw==,type:str]", + "version": "3.12.2" + } +} diff --git a/nixos/sops/secrets/phantom-ship-age.key/users/danny b/nixos/sops/secrets/phantom-ship-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/nixos/sops/secrets/phantom-ship-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/nixos/sops/secrets/sunken-ship-age.key/secret b/nixos/sops/secrets/sunken-ship-age.key/secret new file mode 100644 index 0000000..bd9cc49 --- /dev/null +++ b/nixos/sops/secrets/sunken-ship-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:Mk4Vfs0PvKI4Ynwmz+8myrFtPW1swn9PdtQoeZw0xh9aCT+o6IWstAUypuCfwSgPYkj8PFPi2yq7ysTzglBkhrThV9Zto48U2dA=,iv:jL1WHTpN3mVNQJ/ltHBFd7zMtVtRmh9RIJAnh1SiGZc=,tag:zmRAQvcg6FW1+bEvZd8D6g==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6NzV5c2FDVndUSWRnZndR\ndXI2bEY2VGRiRndNbjZscHdjL0N0eHUrV1hZCmJMRllSdjNLWS8rcnlYLy94VUcy\ndDlXeUptaGdwb2ZsMW1UZHJoeW5CZzgKLS0tIDBkeUozUDd2YWpIRTFlK3M3K2RH\naW9CMnc1ZXRmM0x4MDYwVHVLZnVpR0UKZSowubfXrUemRSFNYo8hxSaeV6/egOi6\nmtmxPICosAV5VRbf8c5Hn3XGNGfOGVwwox+GmLjzqfpVsM9f2Qm9IQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-19T12:31:44Z", + "mac": "ENC[AES256_GCM,data:SaRWT7Q7joTgG7+LBL2icBQ4k2SJdFfDcPzV3IsBIMgVFC4kQNbkVr0BlTM4mgtfH+IxE8PBQu1v/JFo6kf43njnF3mD/Yzr/EsLxwVmD9U1DTpW+mr1EBUVLfiGqnVrTj2DhMdatKB1g8jRwAlpIcsmrlnsHIKjuSj5HKRIi7Q=,iv:YVV3BMhfh1ThIiYwW4uHUmUKqkHUtCy0i0owiAngKyg=,tag:f4UaL5ZjEp3Gkd6LGiq+uw==,type:str]", + "version": "3.12.2" + } +} diff --git a/nixos/sops/secrets/sunken-ship-age.key/users/danny b/nixos/sops/secrets/sunken-ship-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/nixos/sops/secrets/sunken-ship-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/nixos/sops/users/danny/key.json b/nixos/sops/users/danny/key.json new file mode 100755 index 0000000..dad414d --- /dev/null +++ b/nixos/sops/users/danny/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "type": "age" + } +] \ No newline at end of file diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..f0b0b9c --- /dev/null +++ b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:g6eYxa672pfIHJ6jaTAf63ubXIJMPg08GJU2vwnF3hsCK73s5zkbFTd2GiLOZxlk641SK0bIfedABmsybG63qzFW2BOMIaUree0dlDv/u0oaRGdKCrrrrboxi6YbBncKgJLJpiAsmHZ9dsTz4bpicmj0JOBJ6f5HsD95qfy62yMOTSGZD7vdH43cXfbXxg49mKE7Ku2TL8a8awDiFc+Dqk+8QmMxr1XmF/IhYna+Amc+3OtmGGNEfoR8z7yHz13YA0CjJOe0QT2/GgRSUn5B43OkKhpR3e8mwtq6TAFRlBExt5Ccb4P09INcCA2oeAnyi0SEtwHg7KyPIDRJpEYVQ7jWAEFbNtOseBEbnibs,iv:QGNEvG0eLzVFw4lEqDYaSoUK318TRap61rqLD5Djzb0=,tag:vm73BNMMcF+0fiIkugqwxg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLUEJqQXNHcitJNnBEZDZW\nbFdieDFOaDFEazk4cUY5aXYrMjJabnVmWndVCjU0WmRpemNsWUMxN1N0R2dpSTla\nNzliTFFOU1o4VlBwSTJLN1krSEZ0TWMKLS0tIElyd3ovRno0Y3pGd1FFTE5VN0tM\ndTU2c25WcWN4YW15cGErSUJvYmFuRXcKKjBQln8jyOSBa1X1EJJSUg528waFL/8F\nkCpket2TGmNCvMDSai+5Iqe6X222J86uzoXsrLPl2PZaOCXD4t+gRw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrSFNPWW92QkFFYURROGJG\nbTZoS0hFQk52RVNSTWJsclBYRWxIS3c3RTFZCldhQ0QvUmhlRzg5Q3lHUnBnTUtN\nWEZqbWpFOUZhMStzNldCRXdyQzlyWnMKLS0tIGV6anNjVktWamkvbkF3OUxVS1Ji\nUGMzc2FxeE5YTmdMVTRtUDNuMlFaTlkKoJcPcmoMgxVRvcLv7ejws4IJnQd+Yt7s\njqWi0q4iwGLZSLUPb8NUZpWEn0Jbji2edSpATzf67uws1TFHGAMmuA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-19T12:31:44Z", + "mac": "ENC[AES256_GCM,data:hjIn5hcpgQkrWFTsQ3BdmQFPKeubs90opSl06z2Dh7mzTcSqCSF3cQ/l8fj+GD2GuptEMbPczOIoiJfKNxoLDp5L4iHIou7XwFXVhhdjm8fqIMHusnZc/eQBI/H6J2fHWzJ1gxgsKlkazsY3cbWxLfqdz7vAV06SflW5/B5Hbto=,iv:BeB417C5r4uVHSKs9UzwJI+A4F9NrrpoTRn+X14sMtA=,tag:PcaaJrMXO0h6EzdKpVl9xg==,type:str]", + "version": "3.12.2" + } +} diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value new file mode 100644 index 0000000..a8786e8 --- /dev/null +++ b/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:936c:48a:bbdc \ No newline at end of file diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..c76e315 --- /dev/null +++ b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:6WHKA76dLKWJnGpNp45EAwf4gvHnoccXbGz1bCH5EYN/7o0zcl8KziabKjG+hY4BlG7CsNPCOVr2bWAVkWBjTQVoYNwaBNsQ2DF15E0/qxqCYUXKUNoZ5xkWvrcNbVCyEdDAZX9abpAyLenlOMRLFNaWlOsKVr44uG9j75KyMc8NNl4UvCjuBEdAvNLOhEOWuQaRJc73IJAet7pWxP7HkwkihR4+GVIft1UygNYmcThPr2A1+DdNf+IsCNJTR+FL2l3OupCIBawSR6/L/cjyBt1YvIu6fCSYs82r63+W2RKlIzpvoyupEH2vteSgiaLNQ8/j114f4MCZjSgJ3y8SKloZAQTPpsobsnHhYNUS,iv:oji4lQxeXdrvoERb/EtXJEC0LNqn4qBewxM2/rD1FfY=,tag:XQBCSwHw2MFiI4qRdX4klw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYZmhNdTVjSG91RGpyMnlv\nY2x0azE3YTZhTDlzTGZOUXdvbEJhTmNqbzFZCmZPWWtOZG52V1NLVFRlODA2N1dB\neCtsWXg5Q3I3MTJKWlJkeTBwOG00aUUKLS0tIDBnRFRrcXJ5SnZEUTN3REs5VTZH\nNlN3MTJ6aWdpMHVkTDJ5MVRuUTVEak0Kw8VPmgp0XiIVlADbjQjHqxdK31kAAAf0\nN/VCLirEK+DOzXJIkMguL7K9Xe7HyIOvtkJGBE2et1mia1pXkxClqA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQL1NLbFlWdk5TQXNUSUFF\nNTFqQk01RzI0WUJrdklBSlBGbkJYU0N2Qnl3CnRDakRiamtMREJLQkhTN1VPWEtz\neFlWVTlmU2NPWTVxdGtHVzROS1ZmV3MKLS0tIEJsVTRZMy9pWTNTK2k1aklXeGY3\nODZGMy9TQytYOG9kbExnMVg1bEFOYUkKwa9MG/IXjaXjB/wxR5xBYN9CtpQHP7pj\nyDBTqa68JQHcUkFgtxBojjumWWADkHO+LmExPSP8Q7Jk+raR2JawXw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-19T12:31:44Z", + "mac": "ENC[AES256_GCM,data:/GMdb0AGXxWFr9nBFwyRD9iiqXloZu4zTsrDINpfdvGVzp4bQgny2KqHeCtUj2yaPrtEq9dXlLKdgMMlfiXx9b6I1A9AUM/DGle6ZCWyY07598/kNsFL4+2Fr/Xp3wcwVpxDpo2590jb1yT+8FSXzyy6oKjLOCBKixKq70U9bwo=,iv:OyShn5yuTDOhSSSF1AfVOFktFdk6vVVsemMOg2XhjrY=,tag:F7bTHCyhrMG6VyVcYNAVHA==,type:str]", + "version": "3.12.2" + } +} diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value new file mode 100644 index 0000000..ba105d8 --- /dev/null +++ b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:93d5:53a2:de33 \ No newline at end of file diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value new file mode 100644 index 0000000..874c577 --- /dev/null +++ b/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value @@ -0,0 +1 @@ +d553a2de33d26964 \ No newline at end of file diff --git a/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/secret b/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/secret new file mode 100644 index 0000000..5b55168 --- /dev/null +++ b/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:NO63/4R06iLMuhWl5r6bWnVs9lt3i+yJlzWGV4ZiTqdxLHZdK/RO1cfdrfFr/YsYIrBzIYv1GghzmAjqg3InJsW5hEyYxddPdwxgowmHw44c8jOlCEvcMNuLw+Dh1njX6+mPcO6yPnSFeF3O72l/S4PYuesWUAnVSi4KcvDBsYSAITJkSEbNi8+63QVLPbumCVK/ZmGFWy59fepUe+0hA+IlCQBxKEFpku1bIpDfOlkOkaG9kyH0X3TNuvT5R785q5SBaIr9kez1GpcYtGlY4Mio1zemt9oYxzq7f6i1Ca4i5COUFq0TKtjU87T1fg6fEQ3cA28i57IWu7PMxPoV2Mpnwldg3BiCqGvbNMb3,iv:EcOrTwgJSuuP9suOzA+ZHwOHkX4fqYh77gTtV/W9DAQ=,tag:MSpfI+z5/rM0+cxUfEAAKg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNZ2xTdFlaU2JJVFdhM3Rq\nNmFQMEd4NWdwd3RKQUI4dXAxNk0rWnZkRWhnCmFFRXNrUGlRbWpUOXFuU3Z0QndV\nS2NUV2NPVldpWGFPS0NPRm1NNFVSWlUKLS0tIEFhNHE1YndxUXVJbUVDSWU2Qkt5\najI4ajNPN0F3Qmt4Tzhkd3hKY0xpSkEKgaguLuJhrnbPWqeTMFeJD8UET0be46sV\nZNQLyYcINE3mnAQNMGzWFnZ986IrPBI/L/Nsqxni6El764nN6A7hig==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-19T12:31:43Z", + "mac": "ENC[AES256_GCM,data:5vmwAXCxmSWh9HZWZGZfFglyDRdrRKiZOnPQrYCzMBYP5hMD9t8sW3xpnmD/2pz5eqP0rIZTE8SCXK4OIJNJ9YJdoRUPDNyTQ5LDIMLbHEcAQrAk3jYN7HnXCa9AUe2nPRQi0KsI0VbxTwIDthqwZSrQAQMZ8m4FYFoBV5tZVpA=,iv:UomaST+33fy6txRq2DpPs+6YUZsvpjC5nwHMfc3ik60=,tag:mQOig05Un2S7UV9nNPKEeQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny b/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/nixos/vars/shared/zerotier-controller/zerotier-ip/value b/nixos/vars/shared/zerotier-controller/zerotier-ip/value new file mode 100644 index 0000000..ba105d8 --- /dev/null +++ b/nixos/vars/shared/zerotier-controller/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:93d5:53a2:de33 \ No newline at end of file diff --git a/nixos/vars/shared/zerotier-controller/zerotier-network-id/value b/nixos/vars/shared/zerotier-controller/zerotier-network-id/value new file mode 100644 index 0000000..874c577 --- /dev/null +++ b/nixos/vars/shared/zerotier-controller/zerotier-network-id/value @@ -0,0 +1 @@ +d553a2de33d26964 \ No newline at end of file From 88c51399d08220a82d17031349654da86de2dfda Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 15:19:59 +0200 Subject: [PATCH 085/185] =?UTF-8?q?refactor(nix):=20move=20flake=20to=20re?= =?UTF-8?q?po=20root=20=F0=9F=9A=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clan-cli silently ignores the `?dir=` URL parameter when resolving a flake source, so with the flake at nixos/flake.nix `clan machines update` fails with "flake.nix does not exist". Move the flake tree up so the repo root contains flake.nix, flake.lock, flake-modules/, lib/, modules/, sops/, and vars/. Host-specific NixOS modules stay in nixos/{hosts,home,fish.nix,neovim.nix,…}; flake-module paths updated accordingly. - dotfiles-rebuild flakeRef is now "${dotfilesDir}#" (was "${dotfilesDir}/nixos#"). - CLAUDE.md build commands + clan section updated. nixupdate fish alias updated. sunken-ship hostsfile comment updated. - Existing /etc/dotfiles checkouts on the servers will pick up the new layout on the next `dotfiles-rebuild` timer tick; the rebuild service was pre-updated via rsync so its flakeRef matches before the pull. Also includes 4b follow-through: zerotier identities are now live on both servers (sunken-ship=d553a2de33 controller, phantom-ship=6c048abbdc peer) and IPv6 ping across the ZT mesh works. --- CLAUDE.md | 23 +++++++++++-------- .../flake-modules => flake-modules}/clan.nix | 4 ++-- .../daniel-macbook-air.nix | 6 ++--- .../installer-iso.nix | 2 +- .../nixos-modules.nix | 0 .../server-install.nix | 4 ++-- .../flake-modules => flake-modules}/wsl.nix | 6 ++--- nixos/flake.lock => flake.lock | 0 nixos/flake.nix => flake.nix | 0 {nixos/lib => lib}/home-manager-user.nix | 0 .../modules => modules}/dotfiles-rebuild.nix | 4 ++-- nixos/fish.nix | 2 +- nixos/hosts/sunken-ship.nix | 2 +- .../machines/phantom-ship/key.json | 0 .../machines/sunken-ship/key.json | 0 .../secrets/phantom-ship-age.key/secret | 0 .../secrets/phantom-ship-age.key/users/danny | 0 .../secrets/sunken-ship-age.key/secret | 0 .../secrets/sunken-ship-age.key/users/danny | 0 {nixos/sops => sops}/users/danny/key.json | 0 .../machines/phantom-ship | 0 .../zerotier/zerotier-identity-secret/secret | 0 .../zerotier-identity-secret/users/danny | 0 .../phantom-ship/zerotier/zerotier-ip/value | 0 .../machines/sunken-ship | 0 .../zerotier/zerotier-identity-secret/secret | 0 .../zerotier-identity-secret/users/danny | 0 .../sunken-ship/zerotier/zerotier-ip/value | 0 .../zerotier/zerotier-network-id/value | 0 .../zerotier-identity-secret/secret | 0 .../zerotier-identity-secret/users/danny | 0 .../zerotier-controller/zerotier-ip/value | 0 .../zerotier-network-id/value | 0 33 files changed, 29 insertions(+), 24 deletions(-) rename {nixos/flake-modules => flake-modules}/clan.nix (97%) rename {nixos/flake-modules => flake-modules}/daniel-macbook-air.nix (82%) rename {nixos/flake-modules => flake-modules}/installer-iso.nix (89%) rename {nixos/flake-modules => flake-modules}/nixos-modules.nix (100%) rename {nixos/flake-modules => flake-modules}/server-install.nix (78%) rename {nixos/flake-modules => flake-modules}/wsl.nix (80%) rename nixos/flake.lock => flake.lock (100%) rename nixos/flake.nix => flake.nix (100%) rename {nixos/lib => lib}/home-manager-user.nix (100%) rename {nixos/modules => modules}/dotfiles-rebuild.nix (90%) rename {nixos/sops => sops}/machines/phantom-ship/key.json (100%) rename {nixos/sops => sops}/machines/sunken-ship/key.json (100%) rename {nixos/sops => sops}/secrets/phantom-ship-age.key/secret (100%) rename {nixos/sops => sops}/secrets/phantom-ship-age.key/users/danny (100%) rename {nixos/sops => sops}/secrets/sunken-ship-age.key/secret (100%) rename {nixos/sops => sops}/secrets/sunken-ship-age.key/users/danny (100%) rename {nixos/sops => sops}/users/danny/key.json (100%) rename {nixos/vars => vars}/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship (100%) rename {nixos/vars => vars}/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret (100%) rename {nixos/vars => vars}/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny (100%) rename {nixos/vars => vars}/per-machine/phantom-ship/zerotier/zerotier-ip/value (100%) rename {nixos/vars => vars}/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship (100%) rename {nixos/vars => vars}/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret (100%) rename {nixos/vars => vars}/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny (100%) rename {nixos/vars => vars}/per-machine/sunken-ship/zerotier/zerotier-ip/value (100%) rename {nixos/vars => vars}/per-machine/sunken-ship/zerotier/zerotier-network-id/value (100%) rename {nixos/vars => vars}/shared/zerotier-controller/zerotier-identity-secret/secret (100%) rename {nixos/vars => vars}/shared/zerotier-controller/zerotier-identity-secret/users/danny (100%) rename {nixos/vars => vars}/shared/zerotier-controller/zerotier-ip/value (100%) rename {nixos/vars => vars}/shared/zerotier-controller/zerotier-network-id/value (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 43ff508..bb5dddb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,8 +2,10 @@ ## Build commands +The flake lives at the repo root (`~/dotfiles/flake.nix`) — clan-cli doesn't handle flakes in subdirs. + ```bash -# macOS (from ~/dotfiles/nixos) +# macOS (from ~/dotfiles) darwin-rebuild switch --flake . # NixOS servers (SSH from mac, or on server) @@ -11,13 +13,17 @@ sudo nixos-rebuild switch --flake .#sunken-ship sudo nixos-rebuild switch --flake .#phantom-ship # WSL -sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl +sudo nixos-rebuild switch --flake ~/dotfiles#wsl # Update flake + rebuild (fish alias: nixupdate) -cd ~/dotfiles/nixos && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles/nixos#Daniel-Macbook-Air +cd ~/dotfiles && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles#Daniel-Macbook-Air # Installer ISO (Linux only, cannot build on macOS) -cd ~/dotfiles/nixos && nix build .#installer-iso +cd ~/dotfiles && nix build .#installer-iso + +# Clan push update (from mac; builds on target so aarch64-darwin → x86_64-linux works) +nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- \ + machines update sunken-ship --flake ~/dotfiles ``` ## Rebuild protocol @@ -47,7 +53,7 @@ cd ~/dotfiles/nixos && nix build .#installer-iso ## Server (sunken-ship) - SSH: `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship` -- Remote rebuild: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'` +- Remote rebuild: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship'` - Auto-rebuild timer: `dotfiles-rebuild` — every 15 min. Check with `systemctl is-active dotfiles-rebuild.timer`. - WiFi connected; stays reachable when ethernet is unplugged. - Services: UxPlay (AirPlay receiver on Scarlett Solo) @@ -55,7 +61,7 @@ cd ~/dotfiles/nixos && nix build .#installer-iso ## Server (phantom-ship) - SSH: `ssh danny@phantom-ship` -- Remote rebuild: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#phantom-ship'` +- Remote rebuild: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#phantom-ship'` - Auto-rebuild timer: same pattern as sunken-ship. - Ethernet only (no WiFi). @@ -72,11 +78,10 @@ Terminal colors follow **System Settings → Appearance**: `programs.alacritty` **CLI invocation:** clan-cli is not installed globally. Run ad-hoc via: ```bash -nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- machines list \ - --flake 'path:/Users/danny/dotfiles/nixos' +nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- machines list --flake ~/dotfiles ``` -**Flake path quirk:** `--flake .` and `--flake git+…` both fail from a git worktree when the flake lives in a subdir (`nixos/`). Use `--flake 'path:…/nixos'` explicitly. May not be needed from the main checkout — retest. +Flake lives at the repo root (not `nixos/`) — clan-cli silently ignores `?dir=` so a subdir flake breaks `clan machines update`. **`enableRecommendedDefaults = false`:** we opted out fleet-wide because clan's defaults flip to `systemd-networkd` + `systemd-resolved` + `boot.initrd.systemd`, which breaks dnsmasq (NAT DNS on phantom-ship) and navidrome's resolv.conf bind-mount on sunken-ship. Revisit per-service in a later pass — the defaults also include handy extras (tcpdump, htop, curl, jq, nixos-facter). Option defined in `nixosModules/clanCore/defaults.nix` + `nixosModules/clanCore/networking.nix` inside the `clan-core` flake. diff --git a/nixos/flake-modules/clan.nix b/flake-modules/clan.nix similarity index 97% rename from nixos/flake-modules/clan.nix rename to flake-modules/clan.nix index 56defc6..3c6d5ba 100644 --- a/nixos/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -47,7 +47,7 @@ in { clan.core.networking.targetHost = "danny@sunken-ship"; clan.core.networking.buildHost = "danny@sunken-ship"; } - ../hosts/sunken-ship.nix + ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager (hmModule { @@ -66,7 +66,7 @@ in { clan.core.networking.buildHost = "danny@phantom-ship"; } inputs.nix-openclaw.nixosModules.openclaw-gateway - ../hosts/phantom-ship.nix + ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager (hmModule { diff --git a/nixos/flake-modules/daniel-macbook-air.nix b/flake-modules/daniel-macbook-air.nix similarity index 82% rename from nixos/flake-modules/daniel-macbook-air.nix rename to flake-modules/daniel-macbook-air.nix index 4729175..8bd1874 100644 --- a/nixos/flake-modules/daniel-macbook-air.nix +++ b/flake-modules/daniel-macbook-air.nix @@ -7,15 +7,15 @@ }) ]; } - ../hosts/daniel-macbook-air.nix - ../fish.nix + ../nixos/hosts/daniel-macbook-air.nix + ../nixos/fish.nix inputs.home-manager.darwinModules.home-manager (import ../lib/home-manager-user.nix { lib = inputs.nixpkgs.lib; user = "danny"; homeDirectory = "/Users/danny"; - userImports = [ ../home/danny/home.nix ]; + userImports = [ ../nixos/home/danny/home.nix ]; }) ]; }; diff --git a/nixos/flake-modules/installer-iso.nix b/flake-modules/installer-iso.nix similarity index 89% rename from nixos/flake-modules/installer-iso.nix rename to flake-modules/installer-iso.nix index f2f7163..fc18929 100644 --- a/nixos/flake-modules/installer-iso.nix +++ b/flake-modules/installer-iso.nix @@ -3,7 +3,7 @@ # Optional: add ./installer-wifi.nix (gitignored) to modules for live WiFi. flake.nixosConfigurations.installer-iso = inputs.nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - modules = [ ../installer-iso.nix ]; + modules = [ ../nixos/installer-iso.nix ]; }; flake.packages.x86_64-linux.installer-iso = diff --git a/nixos/flake-modules/nixos-modules.nix b/flake-modules/nixos-modules.nix similarity index 100% rename from nixos/flake-modules/nixos-modules.nix rename to flake-modules/nixos-modules.nix diff --git a/nixos/flake-modules/server-install.nix b/flake-modules/server-install.nix similarity index 78% rename from nixos/flake-modules/server-install.nix rename to flake-modules/server-install.nix index 1366ae9..e38d2c9 100644 --- a/nixos/flake-modules/server-install.nix +++ b/flake-modules/server-install.nix @@ -4,8 +4,8 @@ system = "x86_64-linux"; modules = [ inputs.disko.nixosModules.disko - ../disko-server.nix - ../hosts/server-install.nix + ../nixos/disko-server.nix + ../nixos/hosts/server-install.nix ]; }; } diff --git a/nixos/flake-modules/wsl.nix b/flake-modules/wsl.nix similarity index 80% rename from nixos/flake-modules/wsl.nix rename to flake-modules/wsl.nix index e9a491d..6cc2c44 100644 --- a/nixos/flake-modules/wsl.nix +++ b/flake-modules/wsl.nix @@ -4,15 +4,15 @@ modules = [ inputs.nixos-wsl.nixosModules.default inputs.vscode-server.nixosModules.default - ../hosts/wsl.nix - ../fish.nix + ../nixos/hosts/wsl.nix + ../nixos/fish.nix inputs.home-manager.nixosModules.home-manager (import ../lib/home-manager-user.nix { lib = inputs.nixpkgs.lib; user = "dth"; homeDirectory = "/home/dth"; - userImports = [ ../home/danny/home.nix ]; + userImports = [ ../nixos/home/danny/home.nix ]; }) ]; }; diff --git a/nixos/flake.lock b/flake.lock similarity index 100% rename from nixos/flake.lock rename to flake.lock diff --git a/nixos/flake.nix b/flake.nix similarity index 100% rename from nixos/flake.nix rename to flake.nix diff --git a/nixos/lib/home-manager-user.nix b/lib/home-manager-user.nix similarity index 100% rename from nixos/lib/home-manager-user.nix rename to lib/home-manager-user.nix diff --git a/nixos/modules/dotfiles-rebuild.nix b/modules/dotfiles-rebuild.nix similarity index 90% rename from nixos/modules/dotfiles-rebuild.nix rename to modules/dotfiles-rebuild.nix index 709ebaf..de6ac87 100644 --- a/nixos/modules/dotfiles-rebuild.nix +++ b/modules/dotfiles-rebuild.nix @@ -1,13 +1,13 @@ # Shared auto-rebuild-from-git service for homelab hosts. # # Every 15 min: git fetch origin, fast-forward main, and if there were any -# new commits run nixos-rebuild switch against `/nixos#`. +# new commits run nixos-rebuild switch against `#`. # # Assumes /etc/dotfiles is an already-cloned checkout of the dotfiles repo. { config, lib, pkgs, ... }: let dotfilesDir = "/etc/dotfiles"; - flakeRef = "${dotfilesDir}/nixos#${config.networking.hostName}"; + flakeRef = "${dotfilesDir}#${config.networking.hostName}"; in { environment.systemPackages = [ pkgs.git ]; diff --git a/nixos/fish.nix b/nixos/fish.nix index c32edaa..9d04f51 100644 --- a/nixos/fish.nix +++ b/nixos/fish.nix @@ -11,7 +11,7 @@ weather = "curl wttr.in/?T"; # TODO: rename and move 25_flakes into dotfiles nide = "nix develop ~/python-projects/25_flakes/$(basename (pwd)) -c $(which fish)"; - nixupdate = "cd ~/dotfiles/nixos && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles/nixos#Daniel-Macbook-Air"; + nixupdate = "cd ~/dotfiles && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles#Daniel-Macbook-Air"; }; interactiveShellInit = '' function fish_user_key_bindings diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 2e8175c..db7d5f3 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -2,7 +2,7 @@ # # One-time on server: clone repo to /etc/dotfiles (root needs git access). # If private repo: use SSH (ssh:// or git@) and add root's key to GitHub, or use HTTPS + token. -# Then: sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship +# Then: sudo nixos-rebuild switch --flake /etc/dotfiles#sunken-ship # If sudo git is not found: sudo nix run nixpkgs#git -- -C /etc/dotfiles pull origin main # Timer runs every 15 min: git fetch, pull if origin/main changed, rebuild. { config, lib, pkgs, ... }: diff --git a/nixos/sops/machines/phantom-ship/key.json b/sops/machines/phantom-ship/key.json similarity index 100% rename from nixos/sops/machines/phantom-ship/key.json rename to sops/machines/phantom-ship/key.json diff --git a/nixos/sops/machines/sunken-ship/key.json b/sops/machines/sunken-ship/key.json similarity index 100% rename from nixos/sops/machines/sunken-ship/key.json rename to sops/machines/sunken-ship/key.json diff --git a/nixos/sops/secrets/phantom-ship-age.key/secret b/sops/secrets/phantom-ship-age.key/secret similarity index 100% rename from nixos/sops/secrets/phantom-ship-age.key/secret rename to sops/secrets/phantom-ship-age.key/secret diff --git a/nixos/sops/secrets/phantom-ship-age.key/users/danny b/sops/secrets/phantom-ship-age.key/users/danny similarity index 100% rename from nixos/sops/secrets/phantom-ship-age.key/users/danny rename to sops/secrets/phantom-ship-age.key/users/danny diff --git a/nixos/sops/secrets/sunken-ship-age.key/secret b/sops/secrets/sunken-ship-age.key/secret similarity index 100% rename from nixos/sops/secrets/sunken-ship-age.key/secret rename to sops/secrets/sunken-ship-age.key/secret diff --git a/nixos/sops/secrets/sunken-ship-age.key/users/danny b/sops/secrets/sunken-ship-age.key/users/danny similarity index 100% rename from nixos/sops/secrets/sunken-ship-age.key/users/danny rename to sops/secrets/sunken-ship-age.key/users/danny diff --git a/nixos/sops/users/danny/key.json b/sops/users/danny/key.json similarity index 100% rename from nixos/sops/users/danny/key.json rename to sops/users/danny/key.json diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship b/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship similarity index 100% rename from nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship rename to vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/machines/phantom-ship diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret b/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret similarity index 100% rename from nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret rename to vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/secret diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny similarity index 100% rename from nixos/vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny rename to vars/per-machine/phantom-ship/zerotier/zerotier-identity-secret/users/danny diff --git a/nixos/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value b/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value similarity index 100% rename from nixos/vars/per-machine/phantom-ship/zerotier/zerotier-ip/value rename to vars/per-machine/phantom-ship/zerotier/zerotier-ip/value diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship b/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship similarity index 100% rename from nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship rename to vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/machines/sunken-ship diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret b/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret similarity index 100% rename from nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret rename to vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/secret diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny similarity index 100% rename from nixos/vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny rename to vars/per-machine/sunken-ship/zerotier/zerotier-identity-secret/users/danny diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value b/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value similarity index 100% rename from nixos/vars/per-machine/sunken-ship/zerotier/zerotier-ip/value rename to vars/per-machine/sunken-ship/zerotier/zerotier-ip/value diff --git a/nixos/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value b/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value similarity index 100% rename from nixos/vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value rename to vars/per-machine/sunken-ship/zerotier/zerotier-network-id/value diff --git a/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/secret b/vars/shared/zerotier-controller/zerotier-identity-secret/secret similarity index 100% rename from nixos/vars/shared/zerotier-controller/zerotier-identity-secret/secret rename to vars/shared/zerotier-controller/zerotier-identity-secret/secret diff --git a/nixos/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny b/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny similarity index 100% rename from nixos/vars/shared/zerotier-controller/zerotier-identity-secret/users/danny rename to vars/shared/zerotier-controller/zerotier-identity-secret/users/danny diff --git a/nixos/vars/shared/zerotier-controller/zerotier-ip/value b/vars/shared/zerotier-controller/zerotier-ip/value similarity index 100% rename from nixos/vars/shared/zerotier-controller/zerotier-ip/value rename to vars/shared/zerotier-controller/zerotier-ip/value diff --git a/nixos/vars/shared/zerotier-controller/zerotier-network-id/value b/vars/shared/zerotier-controller/zerotier-network-id/value similarity index 100% rename from nixos/vars/shared/zerotier-controller/zerotier-network-id/value rename to vars/shared/zerotier-controller/zerotier-network-id/value From c6cb19eff64ab9c9fc33226e6298d684927195dd Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 21:05:26 +0200 Subject: [PATCH 086/185] vars: update via generator cloudflare-tunnel (machine: sunken-ship) --- .../tunnel-token/machines/sunken-ship | 1 + .../cloudflare-tunnel/tunnel-token/secret | 18 ++++++++++++++++++ .../cloudflare-tunnel/tunnel-token/users/danny | 1 + 3 files changed, 20 insertions(+) create mode 120000 vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret create mode 120000 vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny diff --git a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret new file mode 100644 index 0000000..cb4cccd --- /dev/null +++ b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:+8Hm6/7+GXwltfCX9L4mEJP6mde8+a+kubvfjm+kIHTmd7uacrcO4LLJD43cSPUA04Enz/+gMEY1OGHKOsuOEu16UdGU6Msmh+J+gjtqQRjTXwitoLCJDAb5u785IcqhL9j0dyP0bwCV+NRIZ95n/YXaI9ykDgVKSWLzHgVFXRfXeG8Nbjvc7yJ77yFxXgszwzZTb4NLYl2+JC0zEhVBagSv6uJbFxuABd1tq+gpGTfOy/dWIoF8JvDuX9oKkpbQefRN606oHyOFjrXq19Z2cVvkyp8+WLZixKG+8lzBCot/htEqj4eS4w11rys88CVTWXPuKc2atJE=,iv:0saZY5dGAnDFYpTTgPi10ulF0TCtIwI6PLwxt0Wm9MQ=,tag:ugBpe40mpI1VsLgwLR24CQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2cE9hVUtKUU1la0w2YmtF\nc1RyNEM5c2t4cEZBU242bHhzNUl5WVB0Qkc4CkRhNlk1ZG9NSDdxOUUxaW5RajQx\naW5McFlCNmN2dmxRdDM0WGNQbUJYZk0KLS0tIDRMSFpGUlk5ZDVFanZvZkZrN1Fz\na0ZXa1dnNFkzOGtDYS9rcE5Zd2VXTVUKA1bV5ERPVOo4jRnZEt4A7HECyid2UomQ\nD1nc95fPZgy5tEpL/P2SveEitOsk9HEdvudxvWdHtnUbD4GdFIftEQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBldDYrQ0NSdHBkampCdG1k\na21DK0JyMmttYllnRkJLUWJQUXFmZzJqT3pZCnB0Z0puVGdEU0NGVEVCNmQ3TDFt\nRG9ZdTgyZldhRGl6bUVESmVISEFnUEkKLS0tIHJWWGtpU3dkL25LbWttUk96ZTJD\ncWVQMmZkclptM2RBWC9PZldaM2RlM0EKOg+Yn+Lq/6fUrVlXP+C8EdpGouyBM3Jk\nspZvUN4+nTD3zcIEz/pW42Q13icXcBj+3AA4Dz2awiO+00xhwPxerA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-19T19:05:26Z", + "mac": "ENC[AES256_GCM,data:bUQ2ZtBkaxpGLSvYTQOtutY6R0+2SWj3PoICgr/tN+sRbO2rAJSblUzbUwdfwZhHbHt05lPYmskvzfBmPc9X3FDeKJvxc8+W183EonuJRG4k2/irH0mTL1wTw+2ziFHQA6x+UpPDvmb1q06sB0ftEF3EoKgiPdsBQjdVVhb+BZs=,iv:wurrdazSS9sdh6RD1zkNPmb7503aksTr7fgVBVo91ZQ=,tag:7q/UypkYosmrMPbUE+y8Pw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file From 7d3fd2d8cf3bc888504d5150d0df54434e815c04 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 21:07:02 +0200 Subject: [PATCH 087/185] =?UTF-8?q?feat(sunken-ship):=20migrate=20cloudfla?= =?UTF-8?q?re-tunnel-token=20to=20clan=20vars=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare a clan.core.vars.generators.cloudflare-tunnel generator that prompts for the tunnel token on first run and stores it SOPS-encrypted under vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token. systemd.services.cloudflare-tunnel ExecStart now reads the decrypted secret at runtime from \${config.clan.core.vars...path} (lives at /run/secrets/vars/...) instead of the unmanaged /home/danny/.secrets/cloudflare-tunnel-token file. Stage 4c of the clan migration. The tunnel itself is slated for retirement in 4d — ZeroTier-only access after that. Cloudflare token was rotated during this migration; old value no longer valid. --- nixos/hosts/sunken-ship.nix | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index db7d5f3..f7d32b2 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -96,16 +96,32 @@ }; # Cloudflare Tunnel — exposes services to the internet without port forwarding. - # Token (not in repo): ~danny/.secrets/cloudflare-tunnel-token + # Token managed as a clan var (see generator below); prompted interactively + # on first `clan vars generate` and stored SOPS-encrypted under vars/. # Routes configured in Cloudflare Zero Trust dashboard: # music.dannydannydanny.me → http://localhost:4533 + # Scheduled for retirement in stage 4d — ZeroTier-only access after that. + clan.core.vars.generators.cloudflare-tunnel = { + files.tunnel-token = { + secret = true; + deploy = true; + owner = "danny"; + }; + prompts.tunnel-token = { + description = "Cloudflare Tunnel token (Zero Trust dashboard → Networks → Tunnels → your tunnel → refresh token)"; + type = "hidden"; + persist = true; + }; + script = "cp $prompts/tunnel-token $out/tunnel-token"; + }; + systemd.services.cloudflare-tunnel = { description = "Cloudflare Tunnel for sunken-ship"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "/bin/sh -c '${pkgs.cloudflared}/bin/cloudflared tunnel --no-autoupdate run --token $(cat /home/danny/.secrets/cloudflare-tunnel-token)'"; + ExecStart = "/bin/sh -c '${pkgs.cloudflared}/bin/cloudflared tunnel --no-autoupdate run --token $(cat ${config.clan.core.vars.generators.cloudflare-tunnel.files.tunnel-token.path})'"; Restart = "on-failure"; RestartSec = 10; User = "danny"; From 84da9ed8f52b1d292cdfc37067bff0bff29e5115 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 21:07:02 +0200 Subject: [PATCH 088/185] =?UTF-8?q?feat(ssh):=20add=20zerotier=20host=20al?= =?UTF-8?q?iases=20on=20mac=20=F0=9F=95=B8=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home-manager now writes a drop-in at ~/.ssh/config.d/zerotier with sunken-ship-zt and phantom-ship-zt aliases pointing at the ZT IPv6 addresses. Useful when off the LAN — the aliases route over the ZeroTier mesh. Requires a one-time \`Include ~/.ssh/config.d/*\` at the top of ~/.ssh/config. --- nixos/home/danny/home.nix | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index d3f346d..7a05998 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -9,6 +9,23 @@ # Import neovim configuration imports = [ ../../neovim.nix ]; + # ZeroTier SSH aliases — managed drop-in under ~/.ssh/config.d/. + # For this to take effect, your ~/.ssh/config must include: + # Include ~/.ssh/config.d/* + # near the top (before any host-specific blocks). + home.file.".ssh/config.d/zerotier".text = '' + Host sunken-ship-zt + HostName fdd5:53a2:de33:d269:6499:93d5:53a2:de33 + User danny + IdentityFile ~/.ssh/id_ed25519_sunken_ship + IdentitiesOnly yes + + Host phantom-ship-zt + HostName fdd5:53a2:de33:d269:6499:936c:48a:bbdc + User danny + IdentitiesOnly yes + ''; + # tmux (user-level; same config on macOS and NixOS if you reuse this file) programs.tmux = { enable = true; From 32cb3b7510ebf18a89d00e38367922aa360ee80c Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 19 Apr 2026 21:09:37 +0200 Subject: [PATCH 089/185] =?UTF-8?q?feat(clan):=20add=20internet=20networki?= =?UTF-8?q?ng=20instance=20for=20LAN=20reachability=20=F0=9F=9B=A3?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clan-cli preferred the zerotier networking export (priority 900, user defaulted to root@) over our clan.core.networking.targetHost setting, which broke \`clan machines update\` with "Host key verification failed" against the ZT IPv6 address as root@. Declaring an inventory.instances .internet instance with priority 2000 makes clan-cli prefer the LAN hostname and explicit danny@ user, so updates go over the LAN (ZT stays available for SSH aliases and service-level use). --- flake-modules/clan.nix | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 3c6d5ba..ddd9a94 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -37,6 +37,22 @@ in { roles.peer.machines.sunken-ship = { }; }; + # Direct SSH reachability on the LAN. Priority 2000 > ZT's 900, so + # `clan machines update` prefers LAN hostnames over ZT IPv6 — and uses + # the right user (ZT service defaults to root@). + inventory.instances.internet = { + module.name = "internet"; + module.input = "clan-core"; + roles.default.machines.sunken-ship.settings = { + host = "sunken-ship"; + user = "danny"; + }; + roles.default.machines.phantom-ship.settings = { + host = "phantom-ship"; + user = "danny"; + }; + }; + # Preserve current network / init stack (no systemd-networkd/resolved, # no boot.initrd.systemd, no extra debug packages). Revisit per-service # in later stages rather than flipping this fleet-wide. From b66dd1d30cd265fb078349b46f3386c78d555704 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 10:28:34 +0200 Subject: [PATCH 090/185] =?UTF-8?q?fix(ssh):=20phantom-ship-zt=20needs=20t?= =?UTF-8?q?he=20dedicated=20identity=20key=20=F0=9F=94=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/home/danny/home.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 7a05998..3a55cc4 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -23,6 +23,7 @@ Host phantom-ship-zt HostName fdd5:53a2:de33:d269:6499:936c:48a:bbdc User danny + IdentityFile ~/.ssh/id_ed25519_phantom_ship IdentitiesOnly yes ''; From 0cd4947282faafc860b5934f140bacd1c91b89c9 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 10:36:15 +0200 Subject: [PATCH 091/185] =?UTF-8?q?feat(sunken-ship):=20retire=20Cloudflar?= =?UTF-8?q?e=20Tunnel=20for=20navidrome=20=E2=98=81=EF=B8=8F=F0=9F=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4d of the clan migration. Navidrome is now reachable only over the ZeroTier mesh (port 4533 on sunken-ship's ZT IPv6 address, or via the sunken-ship-zt SSH alias). Dropped: - systemd.services.cloudflare-tunnel - clan.core.vars.generators.cloudflare-tunnel - cloudflared from environment.systemPackages - vars/per-machine/sunken-ship/cloudflare-tunnel/ Manual follow-ups still needed on sunken-ship: - rm /home/danny/.secrets/cloudflare-tunnel-token (old unmanaged token) - delete the tunnel itself in the Cloudflare Zero Trust dashboard - unlink the DNS record music.dannydannydanny.me if it was separate --- nixos/hosts/sunken-ship.nix | 38 +++---------------- .../tunnel-token/machines/sunken-ship | 1 - .../cloudflare-tunnel/tunnel-token/secret | 18 --------- .../tunnel-token/users/danny | 1 - 4 files changed, 5 insertions(+), 53 deletions(-) delete mode 120000 vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret delete mode 120000 vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index f7d32b2..0ec4783 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -60,7 +60,6 @@ brightnessctl # manual backlight; replaces removed `light` from nixpkgs uxplay # AirPlay mirroring receiver alsa-utils # aplay, amixer, arecord for audio debugging - cloudflared # Cloudflare Tunnel for external access ]; # Avahi (mDNS) — required for AirPlay discovery. @@ -95,38 +94,11 @@ options = [ "bind" "ro" ]; }; - # Cloudflare Tunnel — exposes services to the internet without port forwarding. - # Token managed as a clan var (see generator below); prompted interactively - # on first `clan vars generate` and stored SOPS-encrypted under vars/. - # Routes configured in Cloudflare Zero Trust dashboard: - # music.dannydannydanny.me → http://localhost:4533 - # Scheduled for retirement in stage 4d — ZeroTier-only access after that. - clan.core.vars.generators.cloudflare-tunnel = { - files.tunnel-token = { - secret = true; - deploy = true; - owner = "danny"; - }; - prompts.tunnel-token = { - description = "Cloudflare Tunnel token (Zero Trust dashboard → Networks → Tunnels → your tunnel → refresh token)"; - type = "hidden"; - persist = true; - }; - script = "cp $prompts/tunnel-token $out/tunnel-token"; - }; - - systemd.services.cloudflare-tunnel = { - description = "Cloudflare Tunnel for sunken-ship"; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "/bin/sh -c '${pkgs.cloudflared}/bin/cloudflared tunnel --no-autoupdate run --token $(cat ${config.clan.core.vars.generators.cloudflare-tunnel.files.tunnel-token.path})'"; - Restart = "on-failure"; - RestartSec = 10; - User = "danny"; - }; - }; + # Navidrome is now reachable only over the ZeroTier mesh — see the + # sunken-ship-zt SSH alias on the mac, or hit http://[fdd5:53a2:de33: + # d269:6499:93d5:53a2:de33]:4533 directly from any ZT-joined device. + # The Cloudflare Tunnel + its clan vars generator were retired in 4d; + # delete the tunnel itself in the Cloudflare Zero Trust dashboard. # UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA. # Runs as a system service (no PipeWire needed on a headless server). diff --git a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret deleted file mode 100644 index cb4cccd..0000000 --- a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:+8Hm6/7+GXwltfCX9L4mEJP6mde8+a+kubvfjm+kIHTmd7uacrcO4LLJD43cSPUA04Enz/+gMEY1OGHKOsuOEu16UdGU6Msmh+J+gjtqQRjTXwitoLCJDAb5u785IcqhL9j0dyP0bwCV+NRIZ95n/YXaI9ykDgVKSWLzHgVFXRfXeG8Nbjvc7yJ77yFxXgszwzZTb4NLYl2+JC0zEhVBagSv6uJbFxuABd1tq+gpGTfOy/dWIoF8JvDuX9oKkpbQefRN606oHyOFjrXq19Z2cVvkyp8+WLZixKG+8lzBCot/htEqj4eS4w11rys88CVTWXPuKc2atJE=,iv:0saZY5dGAnDFYpTTgPi10ulF0TCtIwI6PLwxt0Wm9MQ=,tag:ugBpe40mpI1VsLgwLR24CQ==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2cE9hVUtKUU1la0w2YmtF\nc1RyNEM5c2t4cEZBU242bHhzNUl5WVB0Qkc4CkRhNlk1ZG9NSDdxOUUxaW5RajQx\naW5McFlCNmN2dmxRdDM0WGNQbUJYZk0KLS0tIDRMSFpGUlk5ZDVFanZvZkZrN1Fz\na0ZXa1dnNFkzOGtDYS9rcE5Zd2VXTVUKA1bV5ERPVOo4jRnZEt4A7HECyid2UomQ\nD1nc95fPZgy5tEpL/P2SveEitOsk9HEdvudxvWdHtnUbD4GdFIftEQ==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBldDYrQ0NSdHBkampCdG1k\na21DK0JyMmttYllnRkJLUWJQUXFmZzJqT3pZCnB0Z0puVGdEU0NGVEVCNmQ3TDFt\nRG9ZdTgyZldhRGl6bUVESmVISEFnUEkKLS0tIHJWWGtpU3dkL25LbWttUk96ZTJD\ncWVQMmZkclptM2RBWC9PZldaM2RlM0EKOg+Yn+Lq/6fUrVlXP+C8EdpGouyBM3Jk\nspZvUN4+nTD3zcIEz/pW42Q13icXcBj+3AA4Dz2awiO+00xhwPxerA==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-19T19:05:26Z", - "mac": "ENC[AES256_GCM,data:bUQ2ZtBkaxpGLSvYTQOtutY6R0+2SWj3PoICgr/tN+sRbO2rAJSblUzbUwdfwZhHbHt05lPYmskvzfBmPc9X3FDeKJvxc8+W183EonuJRG4k2/irH0mTL1wTw+2ziFHQA6x+UpPDvmb1q06sB0ftEF3EoKgiPdsBQjdVVhb+BZs=,iv:wurrdazSS9sdh6RD1zkNPmb7503aksTr7fgVBVo91ZQ=,tag:7q/UypkYosmrMPbUE+y8Pw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny b/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file From 41b3d217f8a2626932243482d86dfb3ae2d436ad Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 10:39:24 +0200 Subject: [PATCH 092/185] =?UTF-8?q?feat(clan):=20use=20ZT=20IPv6=20as=20cl?= =?UTF-8?q?an=20networking=20target=20=F0=9F=9B=B0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clan-cli's upload / build / copy steps each resolve the SSH target independently. With `internet.host = "sunken-ship"` (bare hostname), off-LAN / missing-mDNS cases broke \`clan machines update\` because the mac couldn't resolve the hostname. Pin both the inventory internet instance's host AND clan.core.networking.{target,build}Host to each machine's stable ZT IPv6, so every update path works regardless of LAN DNS state — and the mac reaches the servers the same way it does for ssh sunken-ship-zt / phantom-ship-zt. --- flake-modules/clan.nix | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index ddd9a94..216f05b 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -37,18 +37,18 @@ in { roles.peer.machines.sunken-ship = { }; }; - # Direct SSH reachability on the LAN. Priority 2000 > ZT's 900, so - # `clan machines update` prefers LAN hostnames over ZT IPv6 — and uses - # the right user (ZT service defaults to root@). + # `clan machines update` connection target. Priority 2000 > ZT's 900 + # and overrides the ZT service's root@ default. Using the ZT IPv6 as + # the host makes updates work regardless of LAN DNS / mDNS state. inventory.instances.internet = { module.name = "internet"; module.input = "clan-core"; roles.default.machines.sunken-ship.settings = { - host = "sunken-ship"; + host = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; user = "danny"; }; roles.default.machines.phantom-ship.settings = { - host = "phantom-ship"; + host = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; user = "danny"; }; }; @@ -60,8 +60,8 @@ in { imports = [ { clan.core.enableRecommendedDefaults = false; - clan.core.networking.targetHost = "danny@sunken-ship"; - clan.core.networking.buildHost = "danny@sunken-ship"; + clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; + clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; } ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild @@ -78,8 +78,8 @@ in { imports = [ { clan.core.enableRecommendedDefaults = false; - clan.core.networking.targetHost = "danny@phantom-ship"; - clan.core.networking.buildHost = "danny@phantom-ship"; + clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; + clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; } inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix From 6846faa5f138edd594e88201fa904af3eaa46e5e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 11:38:01 +0200 Subject: [PATCH 093/185] =?UTF-8?q?feat(clan):=20data-mesher=20+=20dm-pull?= =?UTF-8?q?-deploy=20wiring=20=F0=9F=8C=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4e-a of the clan migration. Set up signed-file gossip (data-mesher, experimental, clan-core) and pull-based NixOS deploy (dm-pull-deploy, experimental, clan-community) across both servers. - sunken-ship is the data-mesher bootstrap node + dm-pull-deploy push role; phantom-ship joins via /dns/sunken-ship.clan/tcp/7946/... — the hostname resolves via /etc/hosts (clanHostsModule) to sunken-ship's ZT IPv6 since we don't run a DNS server for the clan domain. - Both machines run the dm-pull-deploy default role with action="switch": they watch /var/lib/data-mesher/files/home/ dm_pull_deploy/target and nixos-rebuild switch against the pushed git+…?rev=…&narHash=… flake ref on each change. - Signing keys (shared + per-host status) generated via clan vars generate, ran on sunken-ship because data-mesher isn't packaged for aarch64-darwin. The legacy dotfiles-rebuild timer stays installed as a fallback until dm-pull-deploy is proven; a smart push timer on sunken-ship (calls dm-send-deploy only when origin/main moves) comes next. --- flake-modules/clan.nix | 52 ++++++++ flake.lock | 122 +++++++++++++++--- flake.nix | 5 + .../identity.cert/machines/phantom-ship | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/phantom-ship | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/phantom-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../identity.cert/machines/sunken-ship | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/sunken-ship | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/sunken-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../data-mesher-network/network.key/secret | 14 ++ .../network.key/users/danny | 1 + .../data-mesher-network/network.pub/value | 3 + .../signing.key/machines/sunken-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + 34 files changed, 334 insertions(+), 20 deletions(-) create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value create mode 100644 vars/shared/data-mesher-network/network.key/secret create mode 120000 vars/shared/data-mesher-network/network.key/users/danny create mode 100644 vars/shared/data-mesher-network/network.pub/value create mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship create mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.key/secret create mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny create mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.pub/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 216f05b..dda821a 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -14,11 +14,31 @@ let import ../lib/home-manager-user.nix { inherit lib user homeDirectory stateVersion userImports; }; + + # ZT IPv6 addresses of the two clan machines. Clan publishes these as + # generated vars at vars/per-machine//zerotier/zerotier-ip/value, + # duplicated here for /etc/hosts use (clan's var values aren't trivially + # readable at module-eval time on the consuming host). + sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; + phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; + + # Shared across both servers: /etc/hosts entries so data-mesher's + # libp2p /dns/.clan/... multiaddrs resolve over ZT. + clanHostsModule = { + networking.hosts = { + "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; + "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; + }; + }; in { imports = [ inputs.clan-core.flakeModules.default ]; clan = { meta.name = "homelab"; + # data-mesher uses `.${domain}` as a libp2p /dns/ multiaddr. + # We don't run a DNS server for it; per-machine networking.hosts entries + # (set below via machineCommon) map ".clan" → the host's ZT IPv6. + meta.domain = "clan"; # Inventory machines — required for `inventory.instances` role bindings # to resolve. Host-specific NixOS config lives under `machines.` @@ -37,6 +57,36 @@ in { roles.peer.machines.sunken-ship = { }; }; + # data-mesher — signed-file gossip protocol over libp2p (port 7946). + # Underpins dm-pull-deploy below. Files are registered + their allowed + # signers managed automatically via clan service exports. + # sunken-ship is the bootstrap node; phantom-ship joins via its + # /dns/sunken-ship.clan/... multiaddr (resolved via /etc/hosts on each + # machine — see machineCommon below). + inventory.instances.data-mesher = { + module.name = "data-mesher"; + module.input = "clan-core"; + roles.default.machines.sunken-ship = { }; + roles.default.machines.phantom-ship = { }; + roles.bootstrap.machines.sunken-ship = { }; + }; + + # dm-pull-deploy — pull-based NixOS deploy via data-mesher gossip. + # sunken-ship is the push node (runs `dm-send-deploy` manually or on a + # timer); both servers watch for new targets and nixos-rebuild switch. + # Replaces the current dotfiles-rebuild timer once proven. `switch` is + # aggressive — rolls all default hosts immediately on any pushed target. + inventory.instances.dm-pull-deploy = { + module.name = "dm-pull-deploy"; + module.input = "clan-community"; + roles.push.machines.sunken-ship.settings = { + gitUrl = "https://github.com/DannyDannyDanny/dotfiles.git"; + branch = "main"; + }; + roles.default.machines.sunken-ship.settings.action = "switch"; + roles.default.machines.phantom-ship.settings.action = "switch"; + }; + # `clan machines update` connection target. Priority 2000 > ZT's 900 # and overrides the ZT service's root@ default. Using the ZT IPv6 as # the host makes updates work regardless of LAN DNS / mDNS state. @@ -63,6 +113,7 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; } + clanHostsModule ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager @@ -81,6 +132,7 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; } + clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild diff --git a/flake.lock b/flake.lock index 2de4459..336c0cb 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,29 @@ { "nodes": { + "clan-community": { + "inputs": { + "clan-core": [ + "clan-core" + ], + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1776431860, + "narHash": "sha256-OHZEcmUPo5ccqnUvL7YUPyfQ45U8pWs+Doa1uwNCfZ4=", + "rev": "d65c0d270a9142bf8c5ef7717b70c738ee851c08", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/clan-community/archive/d65c0d270a9142bf8c5ef7717b70c738ee851c08.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.clan.lol/clan/clan-community/archive/main.tar.gz" + } + }, "clan-core": { "inputs": { "data-mesher": "data-mesher", @@ -13,8 +37,8 @@ "nixpkgs" ], "sops-nix": "sops-nix", - "systems": "systems", - "treefmt-nix": "treefmt-nix" + "systems": "systems_2", + "treefmt-nix": "treefmt-nix_2" }, "locked": { "lastModified": 1776557977, @@ -113,6 +137,27 @@ } }, "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "clan-community", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { "inputs": { "nixpkgs-lib": [ "nixpkgs" @@ -134,7 +179,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1731533236, @@ -152,7 +197,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_3" + "systems": "systems_4" }, "locked": { "lastModified": 1681202837, @@ -425,9 +470,10 @@ }, "root": { "inputs": { + "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", - "flake-parts": "flake-parts", + "flake-parts": "flake-parts_2", "home-manager": "home-manager", "import-tree": "import-tree", "nix-darwin": "nix-darwin_2", @@ -460,6 +506,21 @@ } }, "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { "locked": { "lastModified": 1774449309, "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", @@ -475,21 +536,6 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "systems_3": { "locked": { "lastModified": 1681028828, @@ -505,7 +551,43 @@ "type": "github" } }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "clan-community", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775125835, + "narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "75925962939880974e3ab417879daffcba36c4a3", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { "inputs": { "nixpkgs": [ "clan-core", diff --git a/flake.nix b/flake.nix index 8b802e3..78d8cfc 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,11 @@ clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; + + # clan-community: dm-pull-deploy (and other user-curated services). + clan-community.url = "https://git.clan.lol/clan/clan-community/archive/main.tar.gz"; + clan-community.inputs.nixpkgs.follows = "nixpkgs"; + clan-community.inputs.clan-core.follows = "clan-core"; }; outputs = inputs @ { flake-parts, import-tree, ... }: diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..92c7432 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:zBvALOnnz8ubaEU5degFP9ySosybyXIMrDcWT5TuckgSiWZDlzZuRQyDIdSm+NglxV7+tIPf1W2CA28QWSm41y6ThFmmJEdXYRENbvpIX/LmAy8rTidVcmxjSt1nQR58PaG46YW6ODgeOyP0JZmieOdTzXrIZnfkugE+vvh8ZQKJqOqVeHCczqhHjNxghQyH1O3YKRhiNFftA4n6HJKCEbkMAz9rblQfZDXllt+dtdM+FpnAxQ2PD2lYiU+N2Z3F1SdgKWeIquQkqlxcYClWI5lVLjbwa8HpcVgP0PVHGAQu12Wk2brjYoJ/L3yq,iv:ZMZYHbjauV7RlclQdMEsgrcIoOVQGOESwWc9eleA8kI=,tag:K4rdm+/R/WrOtHJJIYVQHA==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyMFJHRXFiT1Y5RHVMRGV0\nSFBXYlorU2Nud25VNXhvcGpSUnRnNjZGVWk0CktreG9MaFBMai9FTWRYS3k0ekJt\neGlPem55RWNiTllpZjY3K1RMR05rTmcKLS0tIEhaMkVQaVpHTEs4dFFkOFhHY0Vq\nZTI1ZGlFS09LRWRxTlVaWFRKOG5UZlkKso5iv/TYlIkcXE0U9PQ7J0MpCJ5N+Bdx\nrClL1wZSsi1wlAWTxHqP5MvZjXT1EZb6jG5LOSHljUHxBCkPFWXQ+g==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQcHoxZUtHN3c5YU1YUks3\ndkZMTHJSWE02cU9XODRSRVdIK3MveUx4UFhrCm91RTlYRWtSY0RKUnBGUCtwZjJK\nbjZ3eE5vV3Y1WGpzWHJ1K3duWFVlZVkKLS0tIG1hcnlic050SFN4U0NYM2xYd0xX\ndGRVU2pMRlc4NGxjdlRsUW04a01LWXMKZZQCsP6fafmBN7aoyuMp6L0F8umVoZrG\nwsi+ZpANujBIPbq4Fpzqti8zit2aFfrc3k8xkP2GW0VmHC+m8AfAwg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:DxmbajDV37PDo3T8CLrFn5zEfCtjGzERZg1wpmIkUA4jDev+FJAXRQudxyeTAc+z6POKzsq8QT6W40C/1Nq9i2J0Ihjfkd0dVerZoYTwJ/h6+shyy37TxZBC89LqGL5gz9tzHh1xN4EO3/ioUipsBRORqyb0HOoAkJYLAahr8UM=,iv:knW5/8lyM4/LAV3p9b3p4nWJDblUKI7dqd3zyIIJ7qw=,tag:LJSUVZ6A3WUs1+9JT2ogdw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..32de132 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:Ow1yORsXj8qjXIs/ZXMVQDAQtz2Kfy5lBanVJDm6Ucs/S5ka3Gw6N1DYt+mDQ8Luh3i1uwMs0WcYH6pMZYG/01WhtywsiHbyoCZe9LlpHxHTzpCJgeCkMolMjIOHA6R/axyReEZomGQsPiF16MOwmvDhh2UbMns=,iv:mmAUWG9VxYMpsaFKWO6+BE3VxcV8qGeBJjNJtaHqHAQ=,tag:LIIYmL+nhea0tKa96k0VDw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1TG4xcWlvWGpvWk1ZUm1O\nZkRpcy9RWmlSUGR2UUZxeTJEdUxOSmczMW1VCi9FZHNHQU5aWEtkUGM2TnVZZ1pN\nU1lmL3hBbGpFa2d6SkZHMEtIa0lhWWsKLS0tIDdIMXI5V0dwUVVpMnhoSWp4dklG\nOEpzTldzODU1MEprcmhvSE5VbXpCZVkK4G0YIl+gST/RLXrYZGM6j4x4h0hJrOzy\nAvqSHDhxyGIdUCka0NxIe/soNLgzf1CrF0eYGQLP+LX742ml2TQqbQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0bCtFbGh5UkNUQTQ1MGpT\nbjIrM0xVRXB5NjUxMmtlTnplOUlVRjdXNVZJCnppNzVpZFJndllhZDY0NFB1S3JB\nZU5ReUc5OGpUTGxpcndiYStobUZrS2sKLS0tIGV1azFRQWI0OVZhS3pFUmlqOWVR\nOHM4eEsyQmJsa0x3MVUzVllBSXNvb00K4MklrqgKvLwmaEQ4LU7Q6nJGA504s9dY\n6Db1Hgqd1Tx4lHhlxCcTnA9MKC330r/9yPjvYuvPtDaVFOQK+cZaOQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:qhFvYR+paGi8CE8cSGHOVHRThvhcC5KD13p2W8x6lmQpXepeI++FzJAX6s8sSc+XCOBMcH4fr4CmgH0+9cowj0H6qRzhuRDt/nt7VJil8UhTZCXk/ATPqugOLYUTTKzcxeJJ+A5MdRkfCO7pX89DtF/Ktb82zhSL7JrGOmpgVgc=,iv:BI06oxGHGJkGXoV8WItRHFOCFtp14WzBfjl9SvXlmy0=,tag:H2wt1ySmLgMIkxaz5FMJgw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..a9f3dba --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAKMM01/WXRAgjHVlKdspWmV3kgkX42f3UpMo5xxIyrhQ= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..e04a63f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWCZV7p7FiinyNJQTMhu9Us2AnzK6hbZiaw9bmWsaHUDoZ \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..3682bb0 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:NoDGZvrn0A4hniARvQW53R+yI0CsvuUsrcHHe9iUIhtKp4JKiRjxEds+gsn8s2diVtgy7F6RPnm8KBTtXzRSs3EQPDDrZXdSC/4a+5va1mC98GVN1UqWRsH3dpF53eOHOQfWudFMQ1HKVp7eDoAZYmskjKmM71A=,iv:jeVDsZIKjqQx3+uzcZ5fF4cUinC2AHcz0tntp7CB5CY=,tag:kMwkWj2DhopOd127Q6ukWg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhWXFDRVZWQmRtNXkvZWxT\ndVdsZG1Bay9DdnlEK2hYZEVVNDc3a1BjSVU0Cm9rUjh4czRFZmNyRk9lcHFqSG5I\nQzdtdzROODA2dlBUSG5tODlqUFZsdE0KLS0tIE1ZQVFQTGNaTGVXRldPRmQwMUNL\nR1QzWEZyZk0rNDM0NjdJakowRXU1cTgKrwsFYYz/MVt9RNs/ck+md6/OKFjudvHp\nvEmOMkLRYvZL/Mi9mkwOebDj/kyi+aOW8d9C50OoDpNGy3x32UtQ3g==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwN3hTbjNscUdzTlJiR2xU\ncW9RWEFucktjSFZWdlRDeVVUUlQ0NDJWckdNCmc1YWw1dUFqQzM4eFlFVzJWbWxO\neGlTVlRCY1JtVXVhbyswYkxJd3ZCL2cKLS0tIGFzSXJ2THdpWm9UNElHWXNSeWhV\ndmlFYm44bGtEMG5sSUtwYUpqajlEZTgKR3cpnBggPo8/vwalbI76VUIa9QsNvdQV\nli2iEcP6ClbYFPYh3/EJ5gV0GBfFMO44gaynSVApR5hdDo/ev0ErDg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:03Z", + "mac": "ENC[AES256_GCM,data:jH2O5iyuTsy/ankCXKx2aEDfT/uwbPrcGZbjj1gGxL2cxxKdaMKKlFUfKY4Zgn5On4sQHSuPtV//XYiGqNh0v0G+6DGPAb7g6WiRYsuM4DSARZYRwhAQMOx9TWDURuOi0REx6htvc/Jl9lA+1pP/9MqoO1zsWC4WCnMxG3mxfD0=,iv:264mkPVu4f9hSY11Ab8MnueSq7LnF9rpBwrWOLlwLM4=,tag:PaxqOpw4qYSiSDk1c1Jt4A==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..1865b12 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAfA9rDsTSGAz7dDC3pOFMA+LPj08SjxMIc5BU4q5/in0= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..ed76391 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:HUvMiGEnMxlfYnnxX5RgZlw7SdETT/4BCkU7J1LhCeXH/BBN9PcT0jqhepyQ+3ybksk2zOTbxb0uiIodeaoSUKJM+jO1OKRElwtJObAVFPYw65x4TpH2n3j8JTWyIj9OdHFh7sXYGFK92GUsSGDWoZBV++AfzKa/KHw//8Zzy4ol3dgx6JPPQjvTvKIPoTaCre43RcB013UUdO2VRdh8x27KgybtlT8HXb6lAIRpuUS2cXCfbPW4E3ayinyKjVJ2iLUsmaSGSl8SltTk5GdGAYLEVTITH0Y1GNliZ04ENNuGdHVF5VlCIpuLcon9,iv:I2NUjIU5lUe8xpPMc1bYF0sHQ1pwlOO4Gz9ox/KCnrs=,tag:iqzJpTXMUdqUyp98hM/blw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBU2ZyRjRURkNjSjcyaENQ\ncVZ5d2tKTlJVemdRaFZZUURzbEZGVFNGRlFFClY5Y29pRCtSelhlY3VlV0x6eXJx\nNmF1T1YwVjdjL3ZSaGZUK0I3cHQzc0EKLS0tIGkzVE9mcldIbWpicU5YeEZjbHVG\nQ0dJQXd1cENLeFNtaUxHM3EzTDRHSFkKeXjt+AnbcQqWTpOw3TWJTbIH+Mu0q/Du\noE3Lv8b3LcVFPb/OQz3tNvd7FftjEbH6yArcLJfKz8YcKSG6/X+H9w==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHeThUamlhRGVnb0pHUUQ3\nTWtwV3QvZXF6ZmRyVkRrNXg4dVgwZk96MWlNClAxMWhua29JRkpCeUNXdE14ODNv\ndk9DVVVsaTJiQ29IL1BHdERHWUhVSDAKLS0tIEg1bTRSRklaTFBhN2FLd3NIek1i\na1ZEQ1FxYzhzUmhMQmVlQjJYZ2M4MWMKhUBYaEA09xLoc0GAShctrGPFUE4YUGGk\nYW86mPh4uudivrxs6CAhH0GVB7qwVtc9EGEw8bVA2STdNnCzr0JmMg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:yvwDLQ1unefwvtlad7/QBqKnWIsU3hALVB3ia6vYl5wnaRZgycE1IHrDLrHVV20ANAAs1gRKcZYUAAKLYKo4SqduBJgVSf3Hjk9t5VRjcBvRRFizwFbBa4rtWZMBrJS0cV99me6FoLioFLA+zGonRmmkiCEbWbBvSZdf1J04ixw=,iv:mD42aLv2IY8Dvt9qfTBQKH2ZHeI4537tRMlA8AXdyVk=,tag:Up/RwgH+fsJIpwPFzM6B9g==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..c68a058 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:AlD+xw5DFrH8VwsNXqRgX/bdU2+bD1BbMSNMl+h6MloEFgMWGBt0kypqpq3LXj/p9Vtqr7rCy9N4wNAazWpQHGpA3Dv3Oka0S9xK6ghWJJWqDy1NiKkQR6a459jO7GiOzF5EGAfSp5fa4EDk/0LkIE/jkhg1ckg=,iv:+1Nsq644xQaXRIFMGfa6hv/T68YaVdNG7Aj/clKJy/o=,tag:YPpAcKkoxc6PRYxCJDcm/Q==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVE15YnlYcFdQSnlxYWpi\nNmtHQ3JlZmltOHBRaC9iRlpUVDRiY0NHUGxzCjhrVU9jT29vRlFmeWJTNCt3VjR0\nVHFLNUVRS0k4eExrSHRPYmlZRHZkWncKLS0tIEFQSnUwWUxzbzY0TFREaWlPdUti\nVW1GTWpoVUxwdE9VT0h1Mm8wQ2JhaFEKkfS+cJVZ3aKJi+N4N76yilDJqutMLBKZ\nfaSHFyGwOkk9kS6pf53g6GHKmakJJa9KMVGF+d2FFLgnX0XX0H9qig==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzQVlkR080UmhuVHg0RlFP\nQ3phcisvY00zNTJZUFdLWUJ0ekZWRXFVRkNFCjdUc0U5ZEx3aU5ET2pIdkJzK050\nTEMzMlF4Zm9KNWR1RWY5TXBVZi9saWcKLS0tIDBHZWJWQUxoUU5BRkYvL1l0elV4\ndi90SWdkRDc1akV3ZGdiOHBhc3ppTXcKKs7RUx4WTNdEOL0J0aMZiiUD4xpnJQFC\n+Vj/6GQJmeenB1znvWZbgMUKDRhufzzg1gd8oDbjfaI1H6UP1MilNw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:44xy2P1es6SKe4s2aUo1MpwSlRH262TEp+TTRpOyB0ANAunJI5ViN0Db2aHaqq0aPSiYkquIvKeqzzX9pHYqZX9Dmr37PEvoCILP5wbarO5XRCc2N53vuarEfH6xyNQCoGNw2aujpqm5ero1jJbDkYlMu2dmrAtm7y4mazcjM7s=,iv:iGzaXP7DrsEXoyntt0e/f8/M998QG9hN1Vx4C92cshU=,tag:SDm2jnXPy/gN5woNWMRcPA==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..ce4108c --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAzYp9wRD7TpiZHYECCoYgMBdaYJWV31zbhN9u0xmRJIQ= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..69bdc4b --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWPeiEFGKFd58Q6CTbVyCmD5RMXJk7RtcuZfpsshYDxpmy \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..f032da2 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:E1Kds+yeaAIqu+az9a4QwyXb9yxIcq31IjrqqwLBnILp8oEIbPjaKbbCS/C8bbom9BeYixfeAlmo8iDE3IIdbq5W2vf/JNsLSulKF+9KnwA86aq1aNeI7akZGcs9Nvmly4lEUjCQH+rF3kGPNj/ji4uvNxm7FLU=,iv:LbOQMEBhSvwAWipVmF+NHW34CKXZla/YHYWPwySb/+M=,tag:EMHcLXPzUOABzy1+EGZw0Q==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwQVUxSGdkWFNZdm92OTFM\nZnZCeERNSEtWWnpiV0hLS2lsNzNCYkdBUDBJCm1kSzY1eFBGakVXVEV5TVFpdEts\nTkNiQmNHcnZZaTE1NnlVaCtFelF3clUKLS0tIHljYkM1QXVwKzlNNjJOOXlCT0FL\ncmdqcjRzeE8vR3VaU2JCcVdxTXJJd1kKR637rNaq3aceU7NxwBves13/2foYGzje\ncqnfkDARHDltiykBW0QXOfy8ws0XOrCXu5snd414WNeyGxgHuygXJQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZSTRBV1FGSkEzNHpoOHRE\nQU1rR0p5VVBtWXV0eXJ2L0ZWeW1iS3FDb21rCkpudnZiMjQrbEVzSWY1SEVhZk5v\naFV6NktldUkzaFlVQmdKNlFTalVoUEEKLS0tIDdrN2Vzejd6a3ByblJKcnhRMytR\nZm9DMFBFWSs1Z0IzeXlnWlVNWi9zbmMKULDR3ES98A2+BAXHuKvgod6IRQS9jbAK\nkGo8k2taP1+UhEIJbWY3WdfF621Eg/Ojc6+IQofrcrHTL+mu2MDN+g==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:YyEnzCEsCF2E5TuB9cbYaaMpDGqkCGYQ/fnS/+r/VsXINF9fVTRNbN8nYGWMsJvv3YOYkbGzfQXk0ydAEDYoQ7mFMjqtQjcLJqACkWLknkr366/CQ4eFnIvUMpiritjzPDX/8K7RFkCsWgtfIrKgKtCeoRkjbmxsKeP3gZWKqAI=,iv:G5zNZlVKmCS6297whHQpnSJXckwPS9pDpNcpFiAu8FE=,tag:VGaPrFF0swxqXTTsqZzgug==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..11c38e0 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAYFTwnobAoybi21WD7LbwId2Mt3RdMAg4j5i0gKJa9Rs= +-----END PUBLIC KEY----- diff --git a/vars/shared/data-mesher-network/network.key/secret b/vars/shared/data-mesher-network/network.key/secret new file mode 100644 index 0000000..262729a --- /dev/null +++ b/vars/shared/data-mesher-network/network.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:R4nCHoU/rU9JtO0++nwu+DWr9lB8iMb5CnH0YxG8kEp/UIFS8ZJrO/ZFR5f8NFAWLJQyhDb8rXyWehHbSBxqzEdT6topx0Jj7Ehx07pOsajLSaVLfHMBp3sgLWkMCNYFm76NRnq0aT7Fq6+eFACdU7K2ZgPPdGs=,iv:09RfU5lOKeXDVGQGI5qbWB9zEkUbS9sEQWqDo2TbIsA=,tag:VH4X0VKrzCaPYCM6wfvBrQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWQTgxOTQrQVVWbkhlMC9h\nYlFtdk9XdUlUQTVjeU1USTk1U3YzaXlMN2dzCmtaSWRlb2JudzR4U090OGlSa2hu\neUgxNTRLYmd4RTY2Vkp4ZlQwQjlxRHMKLS0tIFNHUEhpRGJPM0VIV1ZwRnFjU3Ri\nek9NT3Q4UnBqUWJzai84alpLMWpwREEKFA1LUso4N82+YlX2QtsC8JpUBde4Z1yQ\nbCP9joC8c7CuSwfJGVm7JfL2KlbxPeihyJsswhpWoupWjlTL4Nn2rg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:03Z", + "mac": "ENC[AES256_GCM,data:R6yaI8KB40un4P9aq6mQsVsr2gRh0LEKJ+c/DG4zydWVUxQOcLtPfYEsOOo9LNXOF0XvbnvXrVwwBSIe5aWFyPELft3mOyxFgVkxuFNfi1ppWduFaVe1EnnNwy1AiIzLsCfVr7CM8KYffaLXreZ5R3vc/QUF0ORxzhWzzbndPu8=,iv:O384W8d6nhVONeOkT7CF4Fa1bQroITsZWV4jaIP/LF4=,tag:a3zabV6K+VdSKBypOT9ofw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/shared/data-mesher-network/network.key/users/danny b/vars/shared/data-mesher-network/network.key/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/vars/shared/data-mesher-network/network.key/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/data-mesher-network/network.pub/value b/vars/shared/data-mesher-network/network.pub/value new file mode 100644 index 0000000..d212467 --- /dev/null +++ b/vars/shared/data-mesher-network/network.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAJrN3VxpiOwikr4TlcjkjbdguTD37fPpgIDOBtrLGOkY= +-----END PUBLIC KEY----- diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship new file mode 120000 index 0000000..38ff05e --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret new file mode 100644 index 0000000..1d36ca8 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:33EcsUpW/KKNFdpPihdj3Mv5hMEo3lBYNOzRe43H1SH/tpwy5dLGl2FR0KGavOyoBwWf8tL6E3rp2gSm0pt4mGCq4FpMZaa/D4rZ6WWpwNTuNFKUH4uGVs6vRULI6G5yMorzp91TAv2E3yoh6mOKcMdlvqr6K2A=,iv:UR8xtdl6iYCVH4EueD9HyL4BprBJPw9A1KBtPX41uAI=,tag:0c/LO8ggYvHiowTJTe0LdQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFOGRpaFBzYmJBZ092MnJw\nbXIremlST3k5UVY4ZkQ1SVc5amNjbjhTRng0CnYySmZ5Ym8xR09yOUhwVXoyM0tL\nTE9OaElDSXFUUG83dkhFVVlkTUxCb2MKLS0tIHM0WkovcGkvUW9OazZncFhsYTVN\nTnByV3QzeURhdUlyL3FJclpDL1J2T1kKMpSf2lxaEbOl9rcYixFkQoaL4fS0LLz+\nqtCzpbChDgsUZ4aAMowIgPVpH1es4WA412MTmmrhbznNXIdHxnZ40w==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiNTA4cHZjaVdtVllkcmo1\nVlFuWVk3eEpNTWtTVUQvNy8yTGF0NkNjc0dJCjlMcm5taWt4R3g4eklIQ3hlY1ow\nK2dsaUMvTU84VkJOVG1uRTJBbDJ0WkEKLS0tIDgyNjZtU3VyNHlianAxcFY5Q0Ra\nU2pHQzd0Y3dUc1VSNDZuZ2RXem1qVkkK0iK/h8nLWZnDbXSNSXh1133Sctia5qsJ\nTsgRZt8amU8IxMF6IlgTQ0voEu5HJbKWzWqrD1tpab1dRil8+b6ljg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:03Z", + "mac": "ENC[AES256_GCM,data:X/IjodUnCLcNfK251jkUuDU7YO5LXuau6zo5Aef7rX2XVsAFSUT4n+62RX8U4zNturCPkbInb3kUHVr+mCPgIG7k2GHRKZIFu0tbdb8aJlgg2IZ3Zswkd8yiRpqWygV/9rYGKpn58X7czj2aM1ydXr98qKUy8xgKcskB6CgN00E=,iv:2pjNT1o0QdwFRFKq4cTK3lDse652B58tMDHob9ppdM8=,tag:pxQLadQe5OfXscuKeHOa1w==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value new file mode 100644 index 0000000..e7bdad7 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAu7f60z9GVfxiyIJRmH3zlz6QBF/nDzICHHGUcAgUd0M= +-----END PUBLIC KEY----- From c4c40e80d53a72b6b1ab02876101ab59315b1d24 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 11:40:07 +0200 Subject: [PATCH 094/185] =?UTF-8?q?Revert=20"feat(clan):=20data-mesher=20+?= =?UTF-8?q?=20dm-pull-deploy=20wiring=20=F0=9F=8C=8A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 6846faa5f138edd594e88201fa904af3eaa46e5e. --- flake-modules/clan.nix | 52 -------- flake.lock | 122 +++--------------- flake.nix | 5 - .../identity.cert/machines/phantom-ship | 1 - .../identity.cert/secret | 18 --- .../identity.cert/users/danny | 1 - .../identity.key/machines/phantom-ship | 1 - .../identity.key/secret | 18 --- .../identity.key/users/danny | 1 - .../identity.pub/value | 3 - .../data-mesher-node-identity/peer.id/value | 1 - .../signing.key/machines/phantom-ship | 1 - .../signing.key/secret | 18 --- .../signing.key/users/danny | 1 - .../signing.pub/value | 3 - .../identity.cert/machines/sunken-ship | 1 - .../identity.cert/secret | 18 --- .../identity.cert/users/danny | 1 - .../identity.key/machines/sunken-ship | 1 - .../identity.key/secret | 18 --- .../identity.key/users/danny | 1 - .../identity.pub/value | 3 - .../data-mesher-node-identity/peer.id/value | 1 - .../signing.key/machines/sunken-ship | 1 - .../signing.key/secret | 18 --- .../signing.key/users/danny | 1 - .../signing.pub/value | 3 - .../data-mesher-network/network.key/secret | 14 -- .../network.key/users/danny | 1 - .../data-mesher-network/network.pub/value | 3 - .../signing.key/machines/sunken-ship | 1 - .../signing.key/secret | 18 --- .../signing.key/users/danny | 1 - .../signing.pub/value | 3 - 34 files changed, 20 insertions(+), 334 deletions(-) delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value delete mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship delete mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret delete mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny delete mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value delete mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret delete mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny delete mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value delete mode 100644 vars/shared/data-mesher-network/network.key/secret delete mode 120000 vars/shared/data-mesher-network/network.key/users/danny delete mode 100644 vars/shared/data-mesher-network/network.pub/value delete mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship delete mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.key/secret delete mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny delete mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.pub/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index dda821a..216f05b 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -14,31 +14,11 @@ let import ../lib/home-manager-user.nix { inherit lib user homeDirectory stateVersion userImports; }; - - # ZT IPv6 addresses of the two clan machines. Clan publishes these as - # generated vars at vars/per-machine//zerotier/zerotier-ip/value, - # duplicated here for /etc/hosts use (clan's var values aren't trivially - # readable at module-eval time on the consuming host). - sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; - phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; - - # Shared across both servers: /etc/hosts entries so data-mesher's - # libp2p /dns/.clan/... multiaddrs resolve over ZT. - clanHostsModule = { - networking.hosts = { - "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; - "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; - }; - }; in { imports = [ inputs.clan-core.flakeModules.default ]; clan = { meta.name = "homelab"; - # data-mesher uses `.${domain}` as a libp2p /dns/ multiaddr. - # We don't run a DNS server for it; per-machine networking.hosts entries - # (set below via machineCommon) map ".clan" → the host's ZT IPv6. - meta.domain = "clan"; # Inventory machines — required for `inventory.instances` role bindings # to resolve. Host-specific NixOS config lives under `machines.` @@ -57,36 +37,6 @@ in { roles.peer.machines.sunken-ship = { }; }; - # data-mesher — signed-file gossip protocol over libp2p (port 7946). - # Underpins dm-pull-deploy below. Files are registered + their allowed - # signers managed automatically via clan service exports. - # sunken-ship is the bootstrap node; phantom-ship joins via its - # /dns/sunken-ship.clan/... multiaddr (resolved via /etc/hosts on each - # machine — see machineCommon below). - inventory.instances.data-mesher = { - module.name = "data-mesher"; - module.input = "clan-core"; - roles.default.machines.sunken-ship = { }; - roles.default.machines.phantom-ship = { }; - roles.bootstrap.machines.sunken-ship = { }; - }; - - # dm-pull-deploy — pull-based NixOS deploy via data-mesher gossip. - # sunken-ship is the push node (runs `dm-send-deploy` manually or on a - # timer); both servers watch for new targets and nixos-rebuild switch. - # Replaces the current dotfiles-rebuild timer once proven. `switch` is - # aggressive — rolls all default hosts immediately on any pushed target. - inventory.instances.dm-pull-deploy = { - module.name = "dm-pull-deploy"; - module.input = "clan-community"; - roles.push.machines.sunken-ship.settings = { - gitUrl = "https://github.com/DannyDannyDanny/dotfiles.git"; - branch = "main"; - }; - roles.default.machines.sunken-ship.settings.action = "switch"; - roles.default.machines.phantom-ship.settings.action = "switch"; - }; - # `clan machines update` connection target. Priority 2000 > ZT's 900 # and overrides the ZT service's root@ default. Using the ZT IPv6 as # the host makes updates work regardless of LAN DNS / mDNS state. @@ -113,7 +63,6 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; } - clanHostsModule ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager @@ -132,7 +81,6 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; } - clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild diff --git a/flake.lock b/flake.lock index 336c0cb..2de4459 100644 --- a/flake.lock +++ b/flake.lock @@ -1,29 +1,5 @@ { "nodes": { - "clan-community": { - "inputs": { - "clan-core": [ - "clan-core" - ], - "flake-parts": "flake-parts", - "nixpkgs": [ - "nixpkgs" - ], - "systems": "systems", - "treefmt-nix": "treefmt-nix" - }, - "locked": { - "lastModified": 1776431860, - "narHash": "sha256-OHZEcmUPo5ccqnUvL7YUPyfQ45U8pWs+Doa1uwNCfZ4=", - "rev": "d65c0d270a9142bf8c5ef7717b70c738ee851c08", - "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-community/archive/d65c0d270a9142bf8c5ef7717b70c738ee851c08.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://git.clan.lol/clan/clan-community/archive/main.tar.gz" - } - }, "clan-core": { "inputs": { "data-mesher": "data-mesher", @@ -37,8 +13,8 @@ "nixpkgs" ], "sops-nix": "sops-nix", - "systems": "systems_2", - "treefmt-nix": "treefmt-nix_2" + "systems": "systems", + "treefmt-nix": "treefmt-nix" }, "locked": { "lastModified": 1776557977, @@ -137,27 +113,6 @@ } }, "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "clan-community", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1775087534, - "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-parts_2": { "inputs": { "nixpkgs-lib": [ "nixpkgs" @@ -179,7 +134,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_3" + "systems": "systems_2" }, "locked": { "lastModified": 1731533236, @@ -197,7 +152,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_4" + "systems": "systems_3" }, "locked": { "lastModified": 1681202837, @@ -470,10 +425,9 @@ }, "root": { "inputs": { - "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", - "flake-parts": "flake-parts_2", + "flake-parts": "flake-parts", "home-manager": "home-manager", "import-tree": "import-tree", "nix-darwin": "nix-darwin_2", @@ -506,21 +460,6 @@ } }, "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { "locked": { "lastModified": 1774449309, "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", @@ -536,6 +475,21 @@ "type": "github" } }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "systems_3": { "locked": { "lastModified": 1681028828, @@ -551,43 +505,7 @@ "type": "github" } }, - "systems_4": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "treefmt-nix": { - "inputs": { - "nixpkgs": [ - "clan-community", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1775125835, - "narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "75925962939880974e3ab417879daffcba36c4a3", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } - }, - "treefmt-nix_2": { "inputs": { "nixpkgs": [ "clan-core", diff --git a/flake.nix b/flake.nix index 78d8cfc..8b802e3 100644 --- a/flake.nix +++ b/flake.nix @@ -28,11 +28,6 @@ clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; - - # clan-community: dm-pull-deploy (and other user-curated services). - clan-community.url = "https://git.clan.lol/clan/clan-community/archive/main.tar.gz"; - clan-community.inputs.nixpkgs.follows = "nixpkgs"; - clan-community.inputs.clan-core.follows = "clan-core"; }; outputs = inputs @ { flake-parts, import-tree, ... }: diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship deleted file mode 120000 index 18e1a3f..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret deleted file mode 100644 index 92c7432..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:zBvALOnnz8ubaEU5degFP9ySosybyXIMrDcWT5TuckgSiWZDlzZuRQyDIdSm+NglxV7+tIPf1W2CA28QWSm41y6ThFmmJEdXYRENbvpIX/LmAy8rTidVcmxjSt1nQR58PaG46YW6ODgeOyP0JZmieOdTzXrIZnfkugE+vvh8ZQKJqOqVeHCczqhHjNxghQyH1O3YKRhiNFftA4n6HJKCEbkMAz9rblQfZDXllt+dtdM+FpnAxQ2PD2lYiU+N2Z3F1SdgKWeIquQkqlxcYClWI5lVLjbwa8HpcVgP0PVHGAQu12Wk2brjYoJ/L3yq,iv:ZMZYHbjauV7RlclQdMEsgrcIoOVQGOESwWc9eleA8kI=,tag:K4rdm+/R/WrOtHJJIYVQHA==,type:str]", - "sops": { - "age": [ - { - "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyMFJHRXFiT1Y5RHVMRGV0\nSFBXYlorU2Nud25VNXhvcGpSUnRnNjZGVWk0CktreG9MaFBMai9FTWRYS3k0ekJt\neGlPem55RWNiTllpZjY3K1RMR05rTmcKLS0tIEhaMkVQaVpHTEs4dFFkOFhHY0Vq\nZTI1ZGlFS09LRWRxTlVaWFRKOG5UZlkKso5iv/TYlIkcXE0U9PQ7J0MpCJ5N+Bdx\nrClL1wZSsi1wlAWTxHqP5MvZjXT1EZb6jG5LOSHljUHxBCkPFWXQ+g==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQcHoxZUtHN3c5YU1YUks3\ndkZMTHJSWE02cU9XODRSRVdIK3MveUx4UFhrCm91RTlYRWtSY0RKUnBGUCtwZjJK\nbjZ3eE5vV3Y1WGpzWHJ1K3duWFVlZVkKLS0tIG1hcnlic050SFN4U0NYM2xYd0xX\ndGRVU2pMRlc4NGxjdlRsUW04a01LWXMKZZQCsP6fafmBN7aoyuMp6L0F8umVoZrG\nwsi+ZpANujBIPbq4Fpzqti8zit2aFfrc3k8xkP2GW0VmHC+m8AfAwg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:DxmbajDV37PDo3T8CLrFn5zEfCtjGzERZg1wpmIkUA4jDev+FJAXRQudxyeTAc+z6POKzsq8QT6W40C/1Nq9i2J0Ihjfkd0dVerZoYTwJ/h6+shyy37TxZBC89LqGL5gz9tzHh1xN4EO3/ioUipsBRORqyb0HOoAkJYLAahr8UM=,iv:knW5/8lyM4/LAV3p9b3p4nWJDblUKI7dqd3zyIIJ7qw=,tag:LJSUVZ6A3WUs1+9JT2ogdw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship deleted file mode 120000 index 18e1a3f..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret deleted file mode 100644 index 32de132..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:Ow1yORsXj8qjXIs/ZXMVQDAQtz2Kfy5lBanVJDm6Ucs/S5ka3Gw6N1DYt+mDQ8Luh3i1uwMs0WcYH6pMZYG/01WhtywsiHbyoCZe9LlpHxHTzpCJgeCkMolMjIOHA6R/axyReEZomGQsPiF16MOwmvDhh2UbMns=,iv:mmAUWG9VxYMpsaFKWO6+BE3VxcV8qGeBJjNJtaHqHAQ=,tag:LIIYmL+nhea0tKa96k0VDw==,type:str]", - "sops": { - "age": [ - { - "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1TG4xcWlvWGpvWk1ZUm1O\nZkRpcy9RWmlSUGR2UUZxeTJEdUxOSmczMW1VCi9FZHNHQU5aWEtkUGM2TnVZZ1pN\nU1lmL3hBbGpFa2d6SkZHMEtIa0lhWWsKLS0tIDdIMXI5V0dwUVVpMnhoSWp4dklG\nOEpzTldzODU1MEprcmhvSE5VbXpCZVkK4G0YIl+gST/RLXrYZGM6j4x4h0hJrOzy\nAvqSHDhxyGIdUCka0NxIe/soNLgzf1CrF0eYGQLP+LX742ml2TQqbQ==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0bCtFbGh5UkNUQTQ1MGpT\nbjIrM0xVRXB5NjUxMmtlTnplOUlVRjdXNVZJCnppNzVpZFJndllhZDY0NFB1S3JB\nZU5ReUc5OGpUTGxpcndiYStobUZrS2sKLS0tIGV1azFRQWI0OVZhS3pFUmlqOWVR\nOHM4eEsyQmJsa0x3MVUzVllBSXNvb00K4MklrqgKvLwmaEQ4LU7Q6nJGA504s9dY\n6Db1Hgqd1Tx4lHhlxCcTnA9MKC330r/9yPjvYuvPtDaVFOQK+cZaOQ==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:qhFvYR+paGi8CE8cSGHOVHRThvhcC5KD13p2W8x6lmQpXepeI++FzJAX6s8sSc+XCOBMcH4fr4CmgH0+9cowj0H6qRzhuRDt/nt7VJil8UhTZCXk/ATPqugOLYUTTKzcxeJJ+A5MdRkfCO7pX89DtF/Ktb82zhSL7JrGOmpgVgc=,iv:BI06oxGHGJkGXoV8WItRHFOCFtp14WzBfjl9SvXlmy0=,tag:H2wt1ySmLgMIkxaz5FMJgw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value deleted file mode 100644 index a9f3dba..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAKMM01/WXRAgjHVlKdspWmV3kgkX42f3UpMo5xxIyrhQ= ------END PUBLIC KEY----- diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value deleted file mode 100644 index e04a63f..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value +++ /dev/null @@ -1 +0,0 @@ -12D3KooWCZV7p7FiinyNJQTMhu9Us2AnzK6hbZiaw9bmWsaHUDoZ \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship deleted file mode 120000 index 18e1a3f..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret deleted file mode 100644 index 3682bb0..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:NoDGZvrn0A4hniARvQW53R+yI0CsvuUsrcHHe9iUIhtKp4JKiRjxEds+gsn8s2diVtgy7F6RPnm8KBTtXzRSs3EQPDDrZXdSC/4a+5va1mC98GVN1UqWRsH3dpF53eOHOQfWudFMQ1HKVp7eDoAZYmskjKmM71A=,iv:jeVDsZIKjqQx3+uzcZ5fF4cUinC2AHcz0tntp7CB5CY=,tag:kMwkWj2DhopOd127Q6ukWg==,type:str]", - "sops": { - "age": [ - { - "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhWXFDRVZWQmRtNXkvZWxT\ndVdsZG1Bay9DdnlEK2hYZEVVNDc3a1BjSVU0Cm9rUjh4czRFZmNyRk9lcHFqSG5I\nQzdtdzROODA2dlBUSG5tODlqUFZsdE0KLS0tIE1ZQVFQTGNaTGVXRldPRmQwMUNL\nR1QzWEZyZk0rNDM0NjdJakowRXU1cTgKrwsFYYz/MVt9RNs/ck+md6/OKFjudvHp\nvEmOMkLRYvZL/Mi9mkwOebDj/kyi+aOW8d9C50OoDpNGy3x32UtQ3g==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwN3hTbjNscUdzTlJiR2xU\ncW9RWEFucktjSFZWdlRDeVVUUlQ0NDJWckdNCmc1YWw1dUFqQzM4eFlFVzJWbWxO\neGlTVlRCY1JtVXVhbyswYkxJd3ZCL2cKLS0tIGFzSXJ2THdpWm9UNElHWXNSeWhV\ndmlFYm44bGtEMG5sSUtwYUpqajlEZTgKR3cpnBggPo8/vwalbI76VUIa9QsNvdQV\nli2iEcP6ClbYFPYh3/EJ5gV0GBfFMO44gaynSVApR5hdDo/ev0ErDg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:03Z", - "mac": "ENC[AES256_GCM,data:jH2O5iyuTsy/ankCXKx2aEDfT/uwbPrcGZbjj1gGxL2cxxKdaMKKlFUfKY4Zgn5On4sQHSuPtV//XYiGqNh0v0G+6DGPAb7g6WiRYsuM4DSARZYRwhAQMOx9TWDURuOi0REx6htvc/Jl9lA+1pP/9MqoO1zsWC4WCnMxG3mxfD0=,iv:264mkPVu4f9hSY11Ab8MnueSq7LnF9rpBwrWOLlwLM4=,tag:PaxqOpw4qYSiSDk1c1Jt4A==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value deleted file mode 100644 index 1865b12..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAfA9rDsTSGAz7dDC3pOFMA+LPj08SjxMIc5BU4q5/in0= ------END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret deleted file mode 100644 index ed76391..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:HUvMiGEnMxlfYnnxX5RgZlw7SdETT/4BCkU7J1LhCeXH/BBN9PcT0jqhepyQ+3ybksk2zOTbxb0uiIodeaoSUKJM+jO1OKRElwtJObAVFPYw65x4TpH2n3j8JTWyIj9OdHFh7sXYGFK92GUsSGDWoZBV++AfzKa/KHw//8Zzy4ol3dgx6JPPQjvTvKIPoTaCre43RcB013UUdO2VRdh8x27KgybtlT8HXb6lAIRpuUS2cXCfbPW4E3ayinyKjVJ2iLUsmaSGSl8SltTk5GdGAYLEVTITH0Y1GNliZ04ENNuGdHVF5VlCIpuLcon9,iv:I2NUjIU5lUe8xpPMc1bYF0sHQ1pwlOO4Gz9ox/KCnrs=,tag:iqzJpTXMUdqUyp98hM/blw==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBU2ZyRjRURkNjSjcyaENQ\ncVZ5d2tKTlJVemdRaFZZUURzbEZGVFNGRlFFClY5Y29pRCtSelhlY3VlV0x6eXJx\nNmF1T1YwVjdjL3ZSaGZUK0I3cHQzc0EKLS0tIGkzVE9mcldIbWpicU5YeEZjbHVG\nQ0dJQXd1cENLeFNtaUxHM3EzTDRHSFkKeXjt+AnbcQqWTpOw3TWJTbIH+Mu0q/Du\noE3Lv8b3LcVFPb/OQz3tNvd7FftjEbH6yArcLJfKz8YcKSG6/X+H9w==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHeThUamlhRGVnb0pHUUQ3\nTWtwV3QvZXF6ZmRyVkRrNXg4dVgwZk96MWlNClAxMWhua29JRkpCeUNXdE14ODNv\ndk9DVVVsaTJiQ29IL1BHdERHWUhVSDAKLS0tIEg1bTRSRklaTFBhN2FLd3NIek1i\na1ZEQ1FxYzhzUmhMQmVlQjJYZ2M4MWMKhUBYaEA09xLoc0GAShctrGPFUE4YUGGk\nYW86mPh4uudivrxs6CAhH0GVB7qwVtc9EGEw8bVA2STdNnCzr0JmMg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:yvwDLQ1unefwvtlad7/QBqKnWIsU3hALVB3ia6vYl5wnaRZgycE1IHrDLrHVV20ANAAs1gRKcZYUAAKLYKo4SqduBJgVSf3Hjk9t5VRjcBvRRFizwFbBa4rtWZMBrJS0cV99me6FoLioFLA+zGonRmmkiCEbWbBvSZdf1J04ixw=,iv:mD42aLv2IY8Dvt9qfTBQKH2ZHeI4537tRMlA8AXdyVk=,tag:Up/RwgH+fsJIpwPFzM6B9g==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret deleted file mode 100644 index c68a058..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:AlD+xw5DFrH8VwsNXqRgX/bdU2+bD1BbMSNMl+h6MloEFgMWGBt0kypqpq3LXj/p9Vtqr7rCy9N4wNAazWpQHGpA3Dv3Oka0S9xK6ghWJJWqDy1NiKkQR6a459jO7GiOzF5EGAfSp5fa4EDk/0LkIE/jkhg1ckg=,iv:+1Nsq644xQaXRIFMGfa6hv/T68YaVdNG7Aj/clKJy/o=,tag:YPpAcKkoxc6PRYxCJDcm/Q==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVE15YnlYcFdQSnlxYWpi\nNmtHQ3JlZmltOHBRaC9iRlpUVDRiY0NHUGxzCjhrVU9jT29vRlFmeWJTNCt3VjR0\nVHFLNUVRS0k4eExrSHRPYmlZRHZkWncKLS0tIEFQSnUwWUxzbzY0TFREaWlPdUti\nVW1GTWpoVUxwdE9VT0h1Mm8wQ2JhaFEKkfS+cJVZ3aKJi+N4N76yilDJqutMLBKZ\nfaSHFyGwOkk9kS6pf53g6GHKmakJJa9KMVGF+d2FFLgnX0XX0H9qig==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzQVlkR080UmhuVHg0RlFP\nQ3phcisvY00zNTJZUFdLWUJ0ekZWRXFVRkNFCjdUc0U5ZEx3aU5ET2pIdkJzK050\nTEMzMlF4Zm9KNWR1RWY5TXBVZi9saWcKLS0tIDBHZWJWQUxoUU5BRkYvL1l0elV4\ndi90SWdkRDc1akV3ZGdiOHBhc3ppTXcKKs7RUx4WTNdEOL0J0aMZiiUD4xpnJQFC\n+Vj/6GQJmeenB1znvWZbgMUKDRhufzzg1gd8oDbjfaI1H6UP1MilNw==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:44xy2P1es6SKe4s2aUo1MpwSlRH262TEp+TTRpOyB0ANAunJI5ViN0Db2aHaqq0aPSiYkquIvKeqzzX9pHYqZX9Dmr37PEvoCILP5wbarO5XRCc2N53vuarEfH6xyNQCoGNw2aujpqm5ero1jJbDkYlMu2dmrAtm7y4mazcjM7s=,iv:iGzaXP7DrsEXoyntt0e/f8/M998QG9hN1Vx4C92cshU=,tag:SDm2jnXPy/gN5woNWMRcPA==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value deleted file mode 100644 index ce4108c..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAzYp9wRD7TpiZHYECCoYgMBdaYJWV31zbhN9u0xmRJIQ= ------END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value deleted file mode 100644 index 69bdc4b..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value +++ /dev/null @@ -1 +0,0 @@ -12D3KooWPeiEFGKFd58Q6CTbVyCmD5RMXJk7RtcuZfpsshYDxpmy \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret deleted file mode 100644 index f032da2..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:E1Kds+yeaAIqu+az9a4QwyXb9yxIcq31IjrqqwLBnILp8oEIbPjaKbbCS/C8bbom9BeYixfeAlmo8iDE3IIdbq5W2vf/JNsLSulKF+9KnwA86aq1aNeI7akZGcs9Nvmly4lEUjCQH+rF3kGPNj/ji4uvNxm7FLU=,iv:LbOQMEBhSvwAWipVmF+NHW34CKXZla/YHYWPwySb/+M=,tag:EMHcLXPzUOABzy1+EGZw0Q==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwQVUxSGdkWFNZdm92OTFM\nZnZCeERNSEtWWnpiV0hLS2lsNzNCYkdBUDBJCm1kSzY1eFBGakVXVEV5TVFpdEts\nTkNiQmNHcnZZaTE1NnlVaCtFelF3clUKLS0tIHljYkM1QXVwKzlNNjJOOXlCT0FL\ncmdqcjRzeE8vR3VaU2JCcVdxTXJJd1kKR637rNaq3aceU7NxwBves13/2foYGzje\ncqnfkDARHDltiykBW0QXOfy8ws0XOrCXu5snd414WNeyGxgHuygXJQ==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZSTRBV1FGSkEzNHpoOHRE\nQU1rR0p5VVBtWXV0eXJ2L0ZWeW1iS3FDb21rCkpudnZiMjQrbEVzSWY1SEVhZk5v\naFV6NktldUkzaFlVQmdKNlFTalVoUEEKLS0tIDdrN2Vzejd6a3ByblJKcnhRMytR\nZm9DMFBFWSs1Z0IzeXlnWlVNWi9zbmMKULDR3ES98A2+BAXHuKvgod6IRQS9jbAK\nkGo8k2taP1+UhEIJbWY3WdfF621Eg/Ojc6+IQofrcrHTL+mu2MDN+g==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:YyEnzCEsCF2E5TuB9cbYaaMpDGqkCGYQ/fnS/+r/VsXINF9fVTRNbN8nYGWMsJvv3YOYkbGzfQXk0ydAEDYoQ7mFMjqtQjcLJqACkWLknkr366/CQ4eFnIvUMpiritjzPDX/8K7RFkCsWgtfIrKgKtCeoRkjbmxsKeP3gZWKqAI=,iv:G5zNZlVKmCS6297whHQpnSJXckwPS9pDpNcpFiAu8FE=,tag:VGaPrFF0swxqXTTsqZzgug==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value deleted file mode 100644 index 11c38e0..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAYFTwnobAoybi21WD7LbwId2Mt3RdMAg4j5i0gKJa9Rs= ------END PUBLIC KEY----- diff --git a/vars/shared/data-mesher-network/network.key/secret b/vars/shared/data-mesher-network/network.key/secret deleted file mode 100644 index 262729a..0000000 --- a/vars/shared/data-mesher-network/network.key/secret +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:R4nCHoU/rU9JtO0++nwu+DWr9lB8iMb5CnH0YxG8kEp/UIFS8ZJrO/ZFR5f8NFAWLJQyhDb8rXyWehHbSBxqzEdT6topx0Jj7Ehx07pOsajLSaVLfHMBp3sgLWkMCNYFm76NRnq0aT7Fq6+eFACdU7K2ZgPPdGs=,iv:09RfU5lOKeXDVGQGI5qbWB9zEkUbS9sEQWqDo2TbIsA=,tag:VH4X0VKrzCaPYCM6wfvBrQ==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWQTgxOTQrQVVWbkhlMC9h\nYlFtdk9XdUlUQTVjeU1USTk1U3YzaXlMN2dzCmtaSWRlb2JudzR4U090OGlSa2hu\neUgxNTRLYmd4RTY2Vkp4ZlQwQjlxRHMKLS0tIFNHUEhpRGJPM0VIV1ZwRnFjU3Ri\nek9NT3Q4UnBqUWJzai84alpLMWpwREEKFA1LUso4N82+YlX2QtsC8JpUBde4Z1yQ\nbCP9joC8c7CuSwfJGVm7JfL2KlbxPeihyJsswhpWoupWjlTL4Nn2rg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:03Z", - "mac": "ENC[AES256_GCM,data:R6yaI8KB40un4P9aq6mQsVsr2gRh0LEKJ+c/DG4zydWVUxQOcLtPfYEsOOo9LNXOF0XvbnvXrVwwBSIe5aWFyPELft3mOyxFgVkxuFNfi1ppWduFaVe1EnnNwy1AiIzLsCfVr7CM8KYffaLXreZ5R3vc/QUF0ORxzhWzzbndPu8=,iv:O384W8d6nhVONeOkT7CF4Fa1bQroITsZWV4jaIP/LF4=,tag:a3zabV6K+VdSKBypOT9ofw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/shared/data-mesher-network/network.key/users/danny b/vars/shared/data-mesher-network/network.key/users/danny deleted file mode 120000 index dcece98..0000000 --- a/vars/shared/data-mesher-network/network.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/data-mesher-network/network.pub/value b/vars/shared/data-mesher-network/network.pub/value deleted file mode 100644 index d212467..0000000 --- a/vars/shared/data-mesher-network/network.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAJrN3VxpiOwikr4TlcjkjbdguTD37fPpgIDOBtrLGOkY= ------END PUBLIC KEY----- diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship deleted file mode 120000 index 38ff05e..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret deleted file mode 100644 index 1d36ca8..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:33EcsUpW/KKNFdpPihdj3Mv5hMEo3lBYNOzRe43H1SH/tpwy5dLGl2FR0KGavOyoBwWf8tL6E3rp2gSm0pt4mGCq4FpMZaa/D4rZ6WWpwNTuNFKUH4uGVs6vRULI6G5yMorzp91TAv2E3yoh6mOKcMdlvqr6K2A=,iv:UR8xtdl6iYCVH4EueD9HyL4BprBJPw9A1KBtPX41uAI=,tag:0c/LO8ggYvHiowTJTe0LdQ==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFOGRpaFBzYmJBZ092MnJw\nbXIremlST3k5UVY4ZkQ1SVc5amNjbjhTRng0CnYySmZ5Ym8xR09yOUhwVXoyM0tL\nTE9OaElDSXFUUG83dkhFVVlkTUxCb2MKLS0tIHM0WkovcGkvUW9OazZncFhsYTVN\nTnByV3QzeURhdUlyL3FJclpDL1J2T1kKMpSf2lxaEbOl9rcYixFkQoaL4fS0LLz+\nqtCzpbChDgsUZ4aAMowIgPVpH1es4WA412MTmmrhbznNXIdHxnZ40w==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiNTA4cHZjaVdtVllkcmo1\nVlFuWVk3eEpNTWtTVUQvNy8yTGF0NkNjc0dJCjlMcm5taWt4R3g4eklIQ3hlY1ow\nK2dsaUMvTU84VkJOVG1uRTJBbDJ0WkEKLS0tIDgyNjZtU3VyNHlianAxcFY5Q0Ra\nU2pHQzd0Y3dUc1VSNDZuZ2RXem1qVkkK0iK/h8nLWZnDbXSNSXh1133Sctia5qsJ\nTsgRZt8amU8IxMF6IlgTQ0voEu5HJbKWzWqrD1tpab1dRil8+b6ljg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:03Z", - "mac": "ENC[AES256_GCM,data:X/IjodUnCLcNfK251jkUuDU7YO5LXuau6zo5Aef7rX2XVsAFSUT4n+62RX8U4zNturCPkbInb3kUHVr+mCPgIG7k2GHRKZIFu0tbdb8aJlgg2IZ3Zswkd8yiRpqWygV/9rYGKpn58X7czj2aM1ydXr98qKUy8xgKcskB6CgN00E=,iv:2pjNT1o0QdwFRFKq4cTK3lDse652B58tMDHob9ppdM8=,tag:pxQLadQe5OfXscuKeHOa1w==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny deleted file mode 120000 index dcece98..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value deleted file mode 100644 index e7bdad7..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAu7f60z9GVfxiyIJRmH3zlz6QBF/nDzICHHGUcAgUd0M= ------END PUBLIC KEY----- From d184064bfd91a7468271c647372e44744f116b2f Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 14:28:12 +0200 Subject: [PATCH 095/185] =?UTF-8?q?Reapply=20"feat(clan):=20data-mesher=20?= =?UTF-8?q?+=20dm-pull-deploy=20wiring=20=F0=9F=8C=8A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c4c40e80d53a72b6b1ab02876101ab59315b1d24. --- flake-modules/clan.nix | 52 ++++++++ flake.lock | 122 +++++++++++++++--- flake.nix | 5 + .../identity.cert/machines/phantom-ship | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/phantom-ship | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/phantom-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../identity.cert/machines/sunken-ship | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/sunken-ship | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/sunken-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../data-mesher-network/network.key/secret | 14 ++ .../network.key/users/danny | 1 + .../data-mesher-network/network.pub/value | 3 + .../signing.key/machines/sunken-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + 34 files changed, 334 insertions(+), 20 deletions(-) create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value create mode 100644 vars/shared/data-mesher-network/network.key/secret create mode 120000 vars/shared/data-mesher-network/network.key/users/danny create mode 100644 vars/shared/data-mesher-network/network.pub/value create mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship create mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.key/secret create mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny create mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.pub/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 216f05b..dda821a 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -14,11 +14,31 @@ let import ../lib/home-manager-user.nix { inherit lib user homeDirectory stateVersion userImports; }; + + # ZT IPv6 addresses of the two clan machines. Clan publishes these as + # generated vars at vars/per-machine//zerotier/zerotier-ip/value, + # duplicated here for /etc/hosts use (clan's var values aren't trivially + # readable at module-eval time on the consuming host). + sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; + phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; + + # Shared across both servers: /etc/hosts entries so data-mesher's + # libp2p /dns/.clan/... multiaddrs resolve over ZT. + clanHostsModule = { + networking.hosts = { + "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; + "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; + }; + }; in { imports = [ inputs.clan-core.flakeModules.default ]; clan = { meta.name = "homelab"; + # data-mesher uses `.${domain}` as a libp2p /dns/ multiaddr. + # We don't run a DNS server for it; per-machine networking.hosts entries + # (set below via machineCommon) map ".clan" → the host's ZT IPv6. + meta.domain = "clan"; # Inventory machines — required for `inventory.instances` role bindings # to resolve. Host-specific NixOS config lives under `machines.` @@ -37,6 +57,36 @@ in { roles.peer.machines.sunken-ship = { }; }; + # data-mesher — signed-file gossip protocol over libp2p (port 7946). + # Underpins dm-pull-deploy below. Files are registered + their allowed + # signers managed automatically via clan service exports. + # sunken-ship is the bootstrap node; phantom-ship joins via its + # /dns/sunken-ship.clan/... multiaddr (resolved via /etc/hosts on each + # machine — see machineCommon below). + inventory.instances.data-mesher = { + module.name = "data-mesher"; + module.input = "clan-core"; + roles.default.machines.sunken-ship = { }; + roles.default.machines.phantom-ship = { }; + roles.bootstrap.machines.sunken-ship = { }; + }; + + # dm-pull-deploy — pull-based NixOS deploy via data-mesher gossip. + # sunken-ship is the push node (runs `dm-send-deploy` manually or on a + # timer); both servers watch for new targets and nixos-rebuild switch. + # Replaces the current dotfiles-rebuild timer once proven. `switch` is + # aggressive — rolls all default hosts immediately on any pushed target. + inventory.instances.dm-pull-deploy = { + module.name = "dm-pull-deploy"; + module.input = "clan-community"; + roles.push.machines.sunken-ship.settings = { + gitUrl = "https://github.com/DannyDannyDanny/dotfiles.git"; + branch = "main"; + }; + roles.default.machines.sunken-ship.settings.action = "switch"; + roles.default.machines.phantom-ship.settings.action = "switch"; + }; + # `clan machines update` connection target. Priority 2000 > ZT's 900 # and overrides the ZT service's root@ default. Using the ZT IPv6 as # the host makes updates work regardless of LAN DNS / mDNS state. @@ -63,6 +113,7 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; } + clanHostsModule ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager @@ -81,6 +132,7 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; } + clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild diff --git a/flake.lock b/flake.lock index 2de4459..336c0cb 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,29 @@ { "nodes": { + "clan-community": { + "inputs": { + "clan-core": [ + "clan-core" + ], + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1776431860, + "narHash": "sha256-OHZEcmUPo5ccqnUvL7YUPyfQ45U8pWs+Doa1uwNCfZ4=", + "rev": "d65c0d270a9142bf8c5ef7717b70c738ee851c08", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/clan-community/archive/d65c0d270a9142bf8c5ef7717b70c738ee851c08.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.clan.lol/clan/clan-community/archive/main.tar.gz" + } + }, "clan-core": { "inputs": { "data-mesher": "data-mesher", @@ -13,8 +37,8 @@ "nixpkgs" ], "sops-nix": "sops-nix", - "systems": "systems", - "treefmt-nix": "treefmt-nix" + "systems": "systems_2", + "treefmt-nix": "treefmt-nix_2" }, "locked": { "lastModified": 1776557977, @@ -113,6 +137,27 @@ } }, "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "clan-community", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { "inputs": { "nixpkgs-lib": [ "nixpkgs" @@ -134,7 +179,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1731533236, @@ -152,7 +197,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_3" + "systems": "systems_4" }, "locked": { "lastModified": 1681202837, @@ -425,9 +470,10 @@ }, "root": { "inputs": { + "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", - "flake-parts": "flake-parts", + "flake-parts": "flake-parts_2", "home-manager": "home-manager", "import-tree": "import-tree", "nix-darwin": "nix-darwin_2", @@ -460,6 +506,21 @@ } }, "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { "locked": { "lastModified": 1774449309, "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", @@ -475,21 +536,6 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "systems_3": { "locked": { "lastModified": 1681028828, @@ -505,7 +551,43 @@ "type": "github" } }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "clan-community", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775125835, + "narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "75925962939880974e3ab417879daffcba36c4a3", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { "inputs": { "nixpkgs": [ "clan-core", diff --git a/flake.nix b/flake.nix index 8b802e3..78d8cfc 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,11 @@ clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; + + # clan-community: dm-pull-deploy (and other user-curated services). + clan-community.url = "https://git.clan.lol/clan/clan-community/archive/main.tar.gz"; + clan-community.inputs.nixpkgs.follows = "nixpkgs"; + clan-community.inputs.clan-core.follows = "clan-core"; }; outputs = inputs @ { flake-parts, import-tree, ... }: diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..92c7432 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:zBvALOnnz8ubaEU5degFP9ySosybyXIMrDcWT5TuckgSiWZDlzZuRQyDIdSm+NglxV7+tIPf1W2CA28QWSm41y6ThFmmJEdXYRENbvpIX/LmAy8rTidVcmxjSt1nQR58PaG46YW6ODgeOyP0JZmieOdTzXrIZnfkugE+vvh8ZQKJqOqVeHCczqhHjNxghQyH1O3YKRhiNFftA4n6HJKCEbkMAz9rblQfZDXllt+dtdM+FpnAxQ2PD2lYiU+N2Z3F1SdgKWeIquQkqlxcYClWI5lVLjbwa8HpcVgP0PVHGAQu12Wk2brjYoJ/L3yq,iv:ZMZYHbjauV7RlclQdMEsgrcIoOVQGOESwWc9eleA8kI=,tag:K4rdm+/R/WrOtHJJIYVQHA==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyMFJHRXFiT1Y5RHVMRGV0\nSFBXYlorU2Nud25VNXhvcGpSUnRnNjZGVWk0CktreG9MaFBMai9FTWRYS3k0ekJt\neGlPem55RWNiTllpZjY3K1RMR05rTmcKLS0tIEhaMkVQaVpHTEs4dFFkOFhHY0Vq\nZTI1ZGlFS09LRWRxTlVaWFRKOG5UZlkKso5iv/TYlIkcXE0U9PQ7J0MpCJ5N+Bdx\nrClL1wZSsi1wlAWTxHqP5MvZjXT1EZb6jG5LOSHljUHxBCkPFWXQ+g==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQcHoxZUtHN3c5YU1YUks3\ndkZMTHJSWE02cU9XODRSRVdIK3MveUx4UFhrCm91RTlYRWtSY0RKUnBGUCtwZjJK\nbjZ3eE5vV3Y1WGpzWHJ1K3duWFVlZVkKLS0tIG1hcnlic050SFN4U0NYM2xYd0xX\ndGRVU2pMRlc4NGxjdlRsUW04a01LWXMKZZQCsP6fafmBN7aoyuMp6L0F8umVoZrG\nwsi+ZpANujBIPbq4Fpzqti8zit2aFfrc3k8xkP2GW0VmHC+m8AfAwg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:DxmbajDV37PDo3T8CLrFn5zEfCtjGzERZg1wpmIkUA4jDev+FJAXRQudxyeTAc+z6POKzsq8QT6W40C/1Nq9i2J0Ihjfkd0dVerZoYTwJ/h6+shyy37TxZBC89LqGL5gz9tzHh1xN4EO3/ioUipsBRORqyb0HOoAkJYLAahr8UM=,iv:knW5/8lyM4/LAV3p9b3p4nWJDblUKI7dqd3zyIIJ7qw=,tag:LJSUVZ6A3WUs1+9JT2ogdw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..32de132 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:Ow1yORsXj8qjXIs/ZXMVQDAQtz2Kfy5lBanVJDm6Ucs/S5ka3Gw6N1DYt+mDQ8Luh3i1uwMs0WcYH6pMZYG/01WhtywsiHbyoCZe9LlpHxHTzpCJgeCkMolMjIOHA6R/axyReEZomGQsPiF16MOwmvDhh2UbMns=,iv:mmAUWG9VxYMpsaFKWO6+BE3VxcV8qGeBJjNJtaHqHAQ=,tag:LIIYmL+nhea0tKa96k0VDw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1TG4xcWlvWGpvWk1ZUm1O\nZkRpcy9RWmlSUGR2UUZxeTJEdUxOSmczMW1VCi9FZHNHQU5aWEtkUGM2TnVZZ1pN\nU1lmL3hBbGpFa2d6SkZHMEtIa0lhWWsKLS0tIDdIMXI5V0dwUVVpMnhoSWp4dklG\nOEpzTldzODU1MEprcmhvSE5VbXpCZVkK4G0YIl+gST/RLXrYZGM6j4x4h0hJrOzy\nAvqSHDhxyGIdUCka0NxIe/soNLgzf1CrF0eYGQLP+LX742ml2TQqbQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0bCtFbGh5UkNUQTQ1MGpT\nbjIrM0xVRXB5NjUxMmtlTnplOUlVRjdXNVZJCnppNzVpZFJndllhZDY0NFB1S3JB\nZU5ReUc5OGpUTGxpcndiYStobUZrS2sKLS0tIGV1azFRQWI0OVZhS3pFUmlqOWVR\nOHM4eEsyQmJsa0x3MVUzVllBSXNvb00K4MklrqgKvLwmaEQ4LU7Q6nJGA504s9dY\n6Db1Hgqd1Tx4lHhlxCcTnA9MKC330r/9yPjvYuvPtDaVFOQK+cZaOQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:qhFvYR+paGi8CE8cSGHOVHRThvhcC5KD13p2W8x6lmQpXepeI++FzJAX6s8sSc+XCOBMcH4fr4CmgH0+9cowj0H6qRzhuRDt/nt7VJil8UhTZCXk/ATPqugOLYUTTKzcxeJJ+A5MdRkfCO7pX89DtF/Ktb82zhSL7JrGOmpgVgc=,iv:BI06oxGHGJkGXoV8WItRHFOCFtp14WzBfjl9SvXlmy0=,tag:H2wt1ySmLgMIkxaz5FMJgw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..a9f3dba --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAKMM01/WXRAgjHVlKdspWmV3kgkX42f3UpMo5xxIyrhQ= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..e04a63f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWCZV7p7FiinyNJQTMhu9Us2AnzK6hbZiaw9bmWsaHUDoZ \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..3682bb0 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:NoDGZvrn0A4hniARvQW53R+yI0CsvuUsrcHHe9iUIhtKp4JKiRjxEds+gsn8s2diVtgy7F6RPnm8KBTtXzRSs3EQPDDrZXdSC/4a+5va1mC98GVN1UqWRsH3dpF53eOHOQfWudFMQ1HKVp7eDoAZYmskjKmM71A=,iv:jeVDsZIKjqQx3+uzcZ5fF4cUinC2AHcz0tntp7CB5CY=,tag:kMwkWj2DhopOd127Q6ukWg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhWXFDRVZWQmRtNXkvZWxT\ndVdsZG1Bay9DdnlEK2hYZEVVNDc3a1BjSVU0Cm9rUjh4czRFZmNyRk9lcHFqSG5I\nQzdtdzROODA2dlBUSG5tODlqUFZsdE0KLS0tIE1ZQVFQTGNaTGVXRldPRmQwMUNL\nR1QzWEZyZk0rNDM0NjdJakowRXU1cTgKrwsFYYz/MVt9RNs/ck+md6/OKFjudvHp\nvEmOMkLRYvZL/Mi9mkwOebDj/kyi+aOW8d9C50OoDpNGy3x32UtQ3g==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwN3hTbjNscUdzTlJiR2xU\ncW9RWEFucktjSFZWdlRDeVVUUlQ0NDJWckdNCmc1YWw1dUFqQzM4eFlFVzJWbWxO\neGlTVlRCY1JtVXVhbyswYkxJd3ZCL2cKLS0tIGFzSXJ2THdpWm9UNElHWXNSeWhV\ndmlFYm44bGtEMG5sSUtwYUpqajlEZTgKR3cpnBggPo8/vwalbI76VUIa9QsNvdQV\nli2iEcP6ClbYFPYh3/EJ5gV0GBfFMO44gaynSVApR5hdDo/ev0ErDg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:03Z", + "mac": "ENC[AES256_GCM,data:jH2O5iyuTsy/ankCXKx2aEDfT/uwbPrcGZbjj1gGxL2cxxKdaMKKlFUfKY4Zgn5On4sQHSuPtV//XYiGqNh0v0G+6DGPAb7g6WiRYsuM4DSARZYRwhAQMOx9TWDURuOi0REx6htvc/Jl9lA+1pP/9MqoO1zsWC4WCnMxG3mxfD0=,iv:264mkPVu4f9hSY11Ab8MnueSq7LnF9rpBwrWOLlwLM4=,tag:PaxqOpw4qYSiSDk1c1Jt4A==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..1865b12 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAfA9rDsTSGAz7dDC3pOFMA+LPj08SjxMIc5BU4q5/in0= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..ed76391 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:HUvMiGEnMxlfYnnxX5RgZlw7SdETT/4BCkU7J1LhCeXH/BBN9PcT0jqhepyQ+3ybksk2zOTbxb0uiIodeaoSUKJM+jO1OKRElwtJObAVFPYw65x4TpH2n3j8JTWyIj9OdHFh7sXYGFK92GUsSGDWoZBV++AfzKa/KHw//8Zzy4ol3dgx6JPPQjvTvKIPoTaCre43RcB013UUdO2VRdh8x27KgybtlT8HXb6lAIRpuUS2cXCfbPW4E3ayinyKjVJ2iLUsmaSGSl8SltTk5GdGAYLEVTITH0Y1GNliZ04ENNuGdHVF5VlCIpuLcon9,iv:I2NUjIU5lUe8xpPMc1bYF0sHQ1pwlOO4Gz9ox/KCnrs=,tag:iqzJpTXMUdqUyp98hM/blw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBU2ZyRjRURkNjSjcyaENQ\ncVZ5d2tKTlJVemdRaFZZUURzbEZGVFNGRlFFClY5Y29pRCtSelhlY3VlV0x6eXJx\nNmF1T1YwVjdjL3ZSaGZUK0I3cHQzc0EKLS0tIGkzVE9mcldIbWpicU5YeEZjbHVG\nQ0dJQXd1cENLeFNtaUxHM3EzTDRHSFkKeXjt+AnbcQqWTpOw3TWJTbIH+Mu0q/Du\noE3Lv8b3LcVFPb/OQz3tNvd7FftjEbH6yArcLJfKz8YcKSG6/X+H9w==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHeThUamlhRGVnb0pHUUQ3\nTWtwV3QvZXF6ZmRyVkRrNXg4dVgwZk96MWlNClAxMWhua29JRkpCeUNXdE14ODNv\ndk9DVVVsaTJiQ29IL1BHdERHWUhVSDAKLS0tIEg1bTRSRklaTFBhN2FLd3NIek1i\na1ZEQ1FxYzhzUmhMQmVlQjJYZ2M4MWMKhUBYaEA09xLoc0GAShctrGPFUE4YUGGk\nYW86mPh4uudivrxs6CAhH0GVB7qwVtc9EGEw8bVA2STdNnCzr0JmMg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:yvwDLQ1unefwvtlad7/QBqKnWIsU3hALVB3ia6vYl5wnaRZgycE1IHrDLrHVV20ANAAs1gRKcZYUAAKLYKo4SqduBJgVSf3Hjk9t5VRjcBvRRFizwFbBa4rtWZMBrJS0cV99me6FoLioFLA+zGonRmmkiCEbWbBvSZdf1J04ixw=,iv:mD42aLv2IY8Dvt9qfTBQKH2ZHeI4537tRMlA8AXdyVk=,tag:Up/RwgH+fsJIpwPFzM6B9g==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..c68a058 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:AlD+xw5DFrH8VwsNXqRgX/bdU2+bD1BbMSNMl+h6MloEFgMWGBt0kypqpq3LXj/p9Vtqr7rCy9N4wNAazWpQHGpA3Dv3Oka0S9xK6ghWJJWqDy1NiKkQR6a459jO7GiOzF5EGAfSp5fa4EDk/0LkIE/jkhg1ckg=,iv:+1Nsq644xQaXRIFMGfa6hv/T68YaVdNG7Aj/clKJy/o=,tag:YPpAcKkoxc6PRYxCJDcm/Q==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVE15YnlYcFdQSnlxYWpi\nNmtHQ3JlZmltOHBRaC9iRlpUVDRiY0NHUGxzCjhrVU9jT29vRlFmeWJTNCt3VjR0\nVHFLNUVRS0k4eExrSHRPYmlZRHZkWncKLS0tIEFQSnUwWUxzbzY0TFREaWlPdUti\nVW1GTWpoVUxwdE9VT0h1Mm8wQ2JhaFEKkfS+cJVZ3aKJi+N4N76yilDJqutMLBKZ\nfaSHFyGwOkk9kS6pf53g6GHKmakJJa9KMVGF+d2FFLgnX0XX0H9qig==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzQVlkR080UmhuVHg0RlFP\nQ3phcisvY00zNTJZUFdLWUJ0ekZWRXFVRkNFCjdUc0U5ZEx3aU5ET2pIdkJzK050\nTEMzMlF4Zm9KNWR1RWY5TXBVZi9saWcKLS0tIDBHZWJWQUxoUU5BRkYvL1l0elV4\ndi90SWdkRDc1akV3ZGdiOHBhc3ppTXcKKs7RUx4WTNdEOL0J0aMZiiUD4xpnJQFC\n+Vj/6GQJmeenB1znvWZbgMUKDRhufzzg1gd8oDbjfaI1H6UP1MilNw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:44xy2P1es6SKe4s2aUo1MpwSlRH262TEp+TTRpOyB0ANAunJI5ViN0Db2aHaqq0aPSiYkquIvKeqzzX9pHYqZX9Dmr37PEvoCILP5wbarO5XRCc2N53vuarEfH6xyNQCoGNw2aujpqm5ero1jJbDkYlMu2dmrAtm7y4mazcjM7s=,iv:iGzaXP7DrsEXoyntt0e/f8/M998QG9hN1Vx4C92cshU=,tag:SDm2jnXPy/gN5woNWMRcPA==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..ce4108c --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAzYp9wRD7TpiZHYECCoYgMBdaYJWV31zbhN9u0xmRJIQ= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..69bdc4b --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWPeiEFGKFd58Q6CTbVyCmD5RMXJk7RtcuZfpsshYDxpmy \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..f032da2 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:E1Kds+yeaAIqu+az9a4QwyXb9yxIcq31IjrqqwLBnILp8oEIbPjaKbbCS/C8bbom9BeYixfeAlmo8iDE3IIdbq5W2vf/JNsLSulKF+9KnwA86aq1aNeI7akZGcs9Nvmly4lEUjCQH+rF3kGPNj/ji4uvNxm7FLU=,iv:LbOQMEBhSvwAWipVmF+NHW34CKXZla/YHYWPwySb/+M=,tag:EMHcLXPzUOABzy1+EGZw0Q==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwQVUxSGdkWFNZdm92OTFM\nZnZCeERNSEtWWnpiV0hLS2lsNzNCYkdBUDBJCm1kSzY1eFBGakVXVEV5TVFpdEts\nTkNiQmNHcnZZaTE1NnlVaCtFelF3clUKLS0tIHljYkM1QXVwKzlNNjJOOXlCT0FL\ncmdqcjRzeE8vR3VaU2JCcVdxTXJJd1kKR637rNaq3aceU7NxwBves13/2foYGzje\ncqnfkDARHDltiykBW0QXOfy8ws0XOrCXu5snd414WNeyGxgHuygXJQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZSTRBV1FGSkEzNHpoOHRE\nQU1rR0p5VVBtWXV0eXJ2L0ZWeW1iS3FDb21rCkpudnZiMjQrbEVzSWY1SEVhZk5v\naFV6NktldUkzaFlVQmdKNlFTalVoUEEKLS0tIDdrN2Vzejd6a3ByblJKcnhRMytR\nZm9DMFBFWSs1Z0IzeXlnWlVNWi9zbmMKULDR3ES98A2+BAXHuKvgod6IRQS9jbAK\nkGo8k2taP1+UhEIJbWY3WdfF621Eg/Ojc6+IQofrcrHTL+mu2MDN+g==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:04Z", + "mac": "ENC[AES256_GCM,data:YyEnzCEsCF2E5TuB9cbYaaMpDGqkCGYQ/fnS/+r/VsXINF9fVTRNbN8nYGWMsJvv3YOYkbGzfQXk0ydAEDYoQ7mFMjqtQjcLJqACkWLknkr366/CQ4eFnIvUMpiritjzPDX/8K7RFkCsWgtfIrKgKtCeoRkjbmxsKeP3gZWKqAI=,iv:G5zNZlVKmCS6297whHQpnSJXckwPS9pDpNcpFiAu8FE=,tag:VGaPrFF0swxqXTTsqZzgug==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..11c38e0 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAYFTwnobAoybi21WD7LbwId2Mt3RdMAg4j5i0gKJa9Rs= +-----END PUBLIC KEY----- diff --git a/vars/shared/data-mesher-network/network.key/secret b/vars/shared/data-mesher-network/network.key/secret new file mode 100644 index 0000000..262729a --- /dev/null +++ b/vars/shared/data-mesher-network/network.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:R4nCHoU/rU9JtO0++nwu+DWr9lB8iMb5CnH0YxG8kEp/UIFS8ZJrO/ZFR5f8NFAWLJQyhDb8rXyWehHbSBxqzEdT6topx0Jj7Ehx07pOsajLSaVLfHMBp3sgLWkMCNYFm76NRnq0aT7Fq6+eFACdU7K2ZgPPdGs=,iv:09RfU5lOKeXDVGQGI5qbWB9zEkUbS9sEQWqDo2TbIsA=,tag:VH4X0VKrzCaPYCM6wfvBrQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWQTgxOTQrQVVWbkhlMC9h\nYlFtdk9XdUlUQTVjeU1USTk1U3YzaXlMN2dzCmtaSWRlb2JudzR4U090OGlSa2hu\neUgxNTRLYmd4RTY2Vkp4ZlQwQjlxRHMKLS0tIFNHUEhpRGJPM0VIV1ZwRnFjU3Ri\nek9NT3Q4UnBqUWJzai84alpLMWpwREEKFA1LUso4N82+YlX2QtsC8JpUBde4Z1yQ\nbCP9joC8c7CuSwfJGVm7JfL2KlbxPeihyJsswhpWoupWjlTL4Nn2rg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:03Z", + "mac": "ENC[AES256_GCM,data:R6yaI8KB40un4P9aq6mQsVsr2gRh0LEKJ+c/DG4zydWVUxQOcLtPfYEsOOo9LNXOF0XvbnvXrVwwBSIe5aWFyPELft3mOyxFgVkxuFNfi1ppWduFaVe1EnnNwy1AiIzLsCfVr7CM8KYffaLXreZ5R3vc/QUF0ORxzhWzzbndPu8=,iv:O384W8d6nhVONeOkT7CF4Fa1bQroITsZWV4jaIP/LF4=,tag:a3zabV6K+VdSKBypOT9ofw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/shared/data-mesher-network/network.key/users/danny b/vars/shared/data-mesher-network/network.key/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/vars/shared/data-mesher-network/network.key/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/data-mesher-network/network.pub/value b/vars/shared/data-mesher-network/network.pub/value new file mode 100644 index 0000000..d212467 --- /dev/null +++ b/vars/shared/data-mesher-network/network.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAJrN3VxpiOwikr4TlcjkjbdguTD37fPpgIDOBtrLGOkY= +-----END PUBLIC KEY----- diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship new file mode 120000 index 0000000..38ff05e --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret new file mode 100644 index 0000000..1d36ca8 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:33EcsUpW/KKNFdpPihdj3Mv5hMEo3lBYNOzRe43H1SH/tpwy5dLGl2FR0KGavOyoBwWf8tL6E3rp2gSm0pt4mGCq4FpMZaa/D4rZ6WWpwNTuNFKUH4uGVs6vRULI6G5yMorzp91TAv2E3yoh6mOKcMdlvqr6K2A=,iv:UR8xtdl6iYCVH4EueD9HyL4BprBJPw9A1KBtPX41uAI=,tag:0c/LO8ggYvHiowTJTe0LdQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFOGRpaFBzYmJBZ092MnJw\nbXIremlST3k5UVY4ZkQ1SVc5amNjbjhTRng0CnYySmZ5Ym8xR09yOUhwVXoyM0tL\nTE9OaElDSXFUUG83dkhFVVlkTUxCb2MKLS0tIHM0WkovcGkvUW9OazZncFhsYTVN\nTnByV3QzeURhdUlyL3FJclpDL1J2T1kKMpSf2lxaEbOl9rcYixFkQoaL4fS0LLz+\nqtCzpbChDgsUZ4aAMowIgPVpH1es4WA412MTmmrhbznNXIdHxnZ40w==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiNTA4cHZjaVdtVllkcmo1\nVlFuWVk3eEpNTWtTVUQvNy8yTGF0NkNjc0dJCjlMcm5taWt4R3g4eklIQ3hlY1ow\nK2dsaUMvTU84VkJOVG1uRTJBbDJ0WkEKLS0tIDgyNjZtU3VyNHlianAxcFY5Q0Ra\nU2pHQzd0Y3dUc1VSNDZuZ2RXem1qVkkK0iK/h8nLWZnDbXSNSXh1133Sctia5qsJ\nTsgRZt8amU8IxMF6IlgTQ0voEu5HJbKWzWqrD1tpab1dRil8+b6ljg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T09:37:03Z", + "mac": "ENC[AES256_GCM,data:X/IjodUnCLcNfK251jkUuDU7YO5LXuau6zo5Aef7rX2XVsAFSUT4n+62RX8U4zNturCPkbInb3kUHVr+mCPgIG7k2GHRKZIFu0tbdb8aJlgg2IZ3Zswkd8yiRpqWygV/9rYGKpn58X7czj2aM1ydXr98qKUy8xgKcskB6CgN00E=,iv:2pjNT1o0QdwFRFKq4cTK3lDse652B58tMDHob9ppdM8=,tag:pxQLadQe5OfXscuKeHOa1w==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value new file mode 100644 index 0000000..e7bdad7 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAu7f60z9GVfxiyIJRmH3zlz6QBF/nDzICHHGUcAgUd0M= +-----END PUBLIC KEY----- From 1d4c6c8f4ffc9eea784f250e9914dcdcc48256f7 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 14:29:39 +0200 Subject: [PATCH 096/185] =?UTF-8?q?Revert=20"Reapply=20"feat(clan):=20data?= =?UTF-8?q?-mesher=20+=20dm-pull-deploy=20wiring=20=F0=9F=8C=8A""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d184064bfd91a7468271c647372e44744f116b2f. --- flake-modules/clan.nix | 52 -------- flake.lock | 122 +++--------------- flake.nix | 5 - .../identity.cert/machines/phantom-ship | 1 - .../identity.cert/secret | 18 --- .../identity.cert/users/danny | 1 - .../identity.key/machines/phantom-ship | 1 - .../identity.key/secret | 18 --- .../identity.key/users/danny | 1 - .../identity.pub/value | 3 - .../data-mesher-node-identity/peer.id/value | 1 - .../signing.key/machines/phantom-ship | 1 - .../signing.key/secret | 18 --- .../signing.key/users/danny | 1 - .../signing.pub/value | 3 - .../identity.cert/machines/sunken-ship | 1 - .../identity.cert/secret | 18 --- .../identity.cert/users/danny | 1 - .../identity.key/machines/sunken-ship | 1 - .../identity.key/secret | 18 --- .../identity.key/users/danny | 1 - .../identity.pub/value | 3 - .../data-mesher-node-identity/peer.id/value | 1 - .../signing.key/machines/sunken-ship | 1 - .../signing.key/secret | 18 --- .../signing.key/users/danny | 1 - .../signing.pub/value | 3 - .../data-mesher-network/network.key/secret | 14 -- .../network.key/users/danny | 1 - .../data-mesher-network/network.pub/value | 3 - .../signing.key/machines/sunken-ship | 1 - .../signing.key/secret | 18 --- .../signing.key/users/danny | 1 - .../signing.pub/value | 3 - 34 files changed, 20 insertions(+), 334 deletions(-) delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret delete mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value delete mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value delete mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship delete mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret delete mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny delete mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret delete mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value delete mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value delete mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship delete mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret delete mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny delete mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value delete mode 100644 vars/shared/data-mesher-network/network.key/secret delete mode 120000 vars/shared/data-mesher-network/network.key/users/danny delete mode 100644 vars/shared/data-mesher-network/network.pub/value delete mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship delete mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.key/secret delete mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny delete mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.pub/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index dda821a..216f05b 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -14,31 +14,11 @@ let import ../lib/home-manager-user.nix { inherit lib user homeDirectory stateVersion userImports; }; - - # ZT IPv6 addresses of the two clan machines. Clan publishes these as - # generated vars at vars/per-machine//zerotier/zerotier-ip/value, - # duplicated here for /etc/hosts use (clan's var values aren't trivially - # readable at module-eval time on the consuming host). - sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; - phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; - - # Shared across both servers: /etc/hosts entries so data-mesher's - # libp2p /dns/.clan/... multiaddrs resolve over ZT. - clanHostsModule = { - networking.hosts = { - "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; - "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; - }; - }; in { imports = [ inputs.clan-core.flakeModules.default ]; clan = { meta.name = "homelab"; - # data-mesher uses `.${domain}` as a libp2p /dns/ multiaddr. - # We don't run a DNS server for it; per-machine networking.hosts entries - # (set below via machineCommon) map ".clan" → the host's ZT IPv6. - meta.domain = "clan"; # Inventory machines — required for `inventory.instances` role bindings # to resolve. Host-specific NixOS config lives under `machines.` @@ -57,36 +37,6 @@ in { roles.peer.machines.sunken-ship = { }; }; - # data-mesher — signed-file gossip protocol over libp2p (port 7946). - # Underpins dm-pull-deploy below. Files are registered + their allowed - # signers managed automatically via clan service exports. - # sunken-ship is the bootstrap node; phantom-ship joins via its - # /dns/sunken-ship.clan/... multiaddr (resolved via /etc/hosts on each - # machine — see machineCommon below). - inventory.instances.data-mesher = { - module.name = "data-mesher"; - module.input = "clan-core"; - roles.default.machines.sunken-ship = { }; - roles.default.machines.phantom-ship = { }; - roles.bootstrap.machines.sunken-ship = { }; - }; - - # dm-pull-deploy — pull-based NixOS deploy via data-mesher gossip. - # sunken-ship is the push node (runs `dm-send-deploy` manually or on a - # timer); both servers watch for new targets and nixos-rebuild switch. - # Replaces the current dotfiles-rebuild timer once proven. `switch` is - # aggressive — rolls all default hosts immediately on any pushed target. - inventory.instances.dm-pull-deploy = { - module.name = "dm-pull-deploy"; - module.input = "clan-community"; - roles.push.machines.sunken-ship.settings = { - gitUrl = "https://github.com/DannyDannyDanny/dotfiles.git"; - branch = "main"; - }; - roles.default.machines.sunken-ship.settings.action = "switch"; - roles.default.machines.phantom-ship.settings.action = "switch"; - }; - # `clan machines update` connection target. Priority 2000 > ZT's 900 # and overrides the ZT service's root@ default. Using the ZT IPv6 as # the host makes updates work regardless of LAN DNS / mDNS state. @@ -113,7 +63,6 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; } - clanHostsModule ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager @@ -132,7 +81,6 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; } - clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild diff --git a/flake.lock b/flake.lock index 336c0cb..2de4459 100644 --- a/flake.lock +++ b/flake.lock @@ -1,29 +1,5 @@ { "nodes": { - "clan-community": { - "inputs": { - "clan-core": [ - "clan-core" - ], - "flake-parts": "flake-parts", - "nixpkgs": [ - "nixpkgs" - ], - "systems": "systems", - "treefmt-nix": "treefmt-nix" - }, - "locked": { - "lastModified": 1776431860, - "narHash": "sha256-OHZEcmUPo5ccqnUvL7YUPyfQ45U8pWs+Doa1uwNCfZ4=", - "rev": "d65c0d270a9142bf8c5ef7717b70c738ee851c08", - "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-community/archive/d65c0d270a9142bf8c5ef7717b70c738ee851c08.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://git.clan.lol/clan/clan-community/archive/main.tar.gz" - } - }, "clan-core": { "inputs": { "data-mesher": "data-mesher", @@ -37,8 +13,8 @@ "nixpkgs" ], "sops-nix": "sops-nix", - "systems": "systems_2", - "treefmt-nix": "treefmt-nix_2" + "systems": "systems", + "treefmt-nix": "treefmt-nix" }, "locked": { "lastModified": 1776557977, @@ -137,27 +113,6 @@ } }, "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "clan-community", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1775087534, - "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-parts_2": { "inputs": { "nixpkgs-lib": [ "nixpkgs" @@ -179,7 +134,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_3" + "systems": "systems_2" }, "locked": { "lastModified": 1731533236, @@ -197,7 +152,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_4" + "systems": "systems_3" }, "locked": { "lastModified": 1681202837, @@ -470,10 +425,9 @@ }, "root": { "inputs": { - "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", - "flake-parts": "flake-parts_2", + "flake-parts": "flake-parts", "home-manager": "home-manager", "import-tree": "import-tree", "nix-darwin": "nix-darwin_2", @@ -506,21 +460,6 @@ } }, "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { "locked": { "lastModified": 1774449309, "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", @@ -536,6 +475,21 @@ "type": "github" } }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "systems_3": { "locked": { "lastModified": 1681028828, @@ -551,43 +505,7 @@ "type": "github" } }, - "systems_4": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "treefmt-nix": { - "inputs": { - "nixpkgs": [ - "clan-community", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1775125835, - "narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "75925962939880974e3ab417879daffcba36c4a3", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } - }, - "treefmt-nix_2": { "inputs": { "nixpkgs": [ "clan-core", diff --git a/flake.nix b/flake.nix index 78d8cfc..8b802e3 100644 --- a/flake.nix +++ b/flake.nix @@ -28,11 +28,6 @@ clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; - - # clan-community: dm-pull-deploy (and other user-curated services). - clan-community.url = "https://git.clan.lol/clan/clan-community/archive/main.tar.gz"; - clan-community.inputs.nixpkgs.follows = "nixpkgs"; - clan-community.inputs.clan-core.follows = "clan-core"; }; outputs = inputs @ { flake-parts, import-tree, ... }: diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship deleted file mode 120000 index 18e1a3f..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret deleted file mode 100644 index 92c7432..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:zBvALOnnz8ubaEU5degFP9ySosybyXIMrDcWT5TuckgSiWZDlzZuRQyDIdSm+NglxV7+tIPf1W2CA28QWSm41y6ThFmmJEdXYRENbvpIX/LmAy8rTidVcmxjSt1nQR58PaG46YW6ODgeOyP0JZmieOdTzXrIZnfkugE+vvh8ZQKJqOqVeHCczqhHjNxghQyH1O3YKRhiNFftA4n6HJKCEbkMAz9rblQfZDXllt+dtdM+FpnAxQ2PD2lYiU+N2Z3F1SdgKWeIquQkqlxcYClWI5lVLjbwa8HpcVgP0PVHGAQu12Wk2brjYoJ/L3yq,iv:ZMZYHbjauV7RlclQdMEsgrcIoOVQGOESwWc9eleA8kI=,tag:K4rdm+/R/WrOtHJJIYVQHA==,type:str]", - "sops": { - "age": [ - { - "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyMFJHRXFiT1Y5RHVMRGV0\nSFBXYlorU2Nud25VNXhvcGpSUnRnNjZGVWk0CktreG9MaFBMai9FTWRYS3k0ekJt\neGlPem55RWNiTllpZjY3K1RMR05rTmcKLS0tIEhaMkVQaVpHTEs4dFFkOFhHY0Vq\nZTI1ZGlFS09LRWRxTlVaWFRKOG5UZlkKso5iv/TYlIkcXE0U9PQ7J0MpCJ5N+Bdx\nrClL1wZSsi1wlAWTxHqP5MvZjXT1EZb6jG5LOSHljUHxBCkPFWXQ+g==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQcHoxZUtHN3c5YU1YUks3\ndkZMTHJSWE02cU9XODRSRVdIK3MveUx4UFhrCm91RTlYRWtSY0RKUnBGUCtwZjJK\nbjZ3eE5vV3Y1WGpzWHJ1K3duWFVlZVkKLS0tIG1hcnlic050SFN4U0NYM2xYd0xX\ndGRVU2pMRlc4NGxjdlRsUW04a01LWXMKZZQCsP6fafmBN7aoyuMp6L0F8umVoZrG\nwsi+ZpANujBIPbq4Fpzqti8zit2aFfrc3k8xkP2GW0VmHC+m8AfAwg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:DxmbajDV37PDo3T8CLrFn5zEfCtjGzERZg1wpmIkUA4jDev+FJAXRQudxyeTAc+z6POKzsq8QT6W40C/1Nq9i2J0Ihjfkd0dVerZoYTwJ/h6+shyy37TxZBC89LqGL5gz9tzHh1xN4EO3/ioUipsBRORqyb0HOoAkJYLAahr8UM=,iv:knW5/8lyM4/LAV3p9b3p4nWJDblUKI7dqd3zyIIJ7qw=,tag:LJSUVZ6A3WUs1+9JT2ogdw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship deleted file mode 120000 index 18e1a3f..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret deleted file mode 100644 index 32de132..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:Ow1yORsXj8qjXIs/ZXMVQDAQtz2Kfy5lBanVJDm6Ucs/S5ka3Gw6N1DYt+mDQ8Luh3i1uwMs0WcYH6pMZYG/01WhtywsiHbyoCZe9LlpHxHTzpCJgeCkMolMjIOHA6R/axyReEZomGQsPiF16MOwmvDhh2UbMns=,iv:mmAUWG9VxYMpsaFKWO6+BE3VxcV8qGeBJjNJtaHqHAQ=,tag:LIIYmL+nhea0tKa96k0VDw==,type:str]", - "sops": { - "age": [ - { - "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1TG4xcWlvWGpvWk1ZUm1O\nZkRpcy9RWmlSUGR2UUZxeTJEdUxOSmczMW1VCi9FZHNHQU5aWEtkUGM2TnVZZ1pN\nU1lmL3hBbGpFa2d6SkZHMEtIa0lhWWsKLS0tIDdIMXI5V0dwUVVpMnhoSWp4dklG\nOEpzTldzODU1MEprcmhvSE5VbXpCZVkK4G0YIl+gST/RLXrYZGM6j4x4h0hJrOzy\nAvqSHDhxyGIdUCka0NxIe/soNLgzf1CrF0eYGQLP+LX742ml2TQqbQ==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0bCtFbGh5UkNUQTQ1MGpT\nbjIrM0xVRXB5NjUxMmtlTnplOUlVRjdXNVZJCnppNzVpZFJndllhZDY0NFB1S3JB\nZU5ReUc5OGpUTGxpcndiYStobUZrS2sKLS0tIGV1azFRQWI0OVZhS3pFUmlqOWVR\nOHM4eEsyQmJsa0x3MVUzVllBSXNvb00K4MklrqgKvLwmaEQ4LU7Q6nJGA504s9dY\n6Db1Hgqd1Tx4lHhlxCcTnA9MKC330r/9yPjvYuvPtDaVFOQK+cZaOQ==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:qhFvYR+paGi8CE8cSGHOVHRThvhcC5KD13p2W8x6lmQpXepeI++FzJAX6s8sSc+XCOBMcH4fr4CmgH0+9cowj0H6qRzhuRDt/nt7VJil8UhTZCXk/ATPqugOLYUTTKzcxeJJ+A5MdRkfCO7pX89DtF/Ktb82zhSL7JrGOmpgVgc=,iv:BI06oxGHGJkGXoV8WItRHFOCFtp14WzBfjl9SvXlmy0=,tag:H2wt1ySmLgMIkxaz5FMJgw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value deleted file mode 100644 index a9f3dba..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAKMM01/WXRAgjHVlKdspWmV3kgkX42f3UpMo5xxIyrhQ= ------END PUBLIC KEY----- diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value deleted file mode 100644 index e04a63f..0000000 --- a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value +++ /dev/null @@ -1 +0,0 @@ -12D3KooWCZV7p7FiinyNJQTMhu9Us2AnzK6hbZiaw9bmWsaHUDoZ \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship deleted file mode 120000 index 18e1a3f..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret deleted file mode 100644 index 3682bb0..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:NoDGZvrn0A4hniARvQW53R+yI0CsvuUsrcHHe9iUIhtKp4JKiRjxEds+gsn8s2diVtgy7F6RPnm8KBTtXzRSs3EQPDDrZXdSC/4a+5va1mC98GVN1UqWRsH3dpF53eOHOQfWudFMQ1HKVp7eDoAZYmskjKmM71A=,iv:jeVDsZIKjqQx3+uzcZ5fF4cUinC2AHcz0tntp7CB5CY=,tag:kMwkWj2DhopOd127Q6ukWg==,type:str]", - "sops": { - "age": [ - { - "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhWXFDRVZWQmRtNXkvZWxT\ndVdsZG1Bay9DdnlEK2hYZEVVNDc3a1BjSVU0Cm9rUjh4czRFZmNyRk9lcHFqSG5I\nQzdtdzROODA2dlBUSG5tODlqUFZsdE0KLS0tIE1ZQVFQTGNaTGVXRldPRmQwMUNL\nR1QzWEZyZk0rNDM0NjdJakowRXU1cTgKrwsFYYz/MVt9RNs/ck+md6/OKFjudvHp\nvEmOMkLRYvZL/Mi9mkwOebDj/kyi+aOW8d9C50OoDpNGy3x32UtQ3g==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwN3hTbjNscUdzTlJiR2xU\ncW9RWEFucktjSFZWdlRDeVVUUlQ0NDJWckdNCmc1YWw1dUFqQzM4eFlFVzJWbWxO\neGlTVlRCY1JtVXVhbyswYkxJd3ZCL2cKLS0tIGFzSXJ2THdpWm9UNElHWXNSeWhV\ndmlFYm44bGtEMG5sSUtwYUpqajlEZTgKR3cpnBggPo8/vwalbI76VUIa9QsNvdQV\nli2iEcP6ClbYFPYh3/EJ5gV0GBfFMO44gaynSVApR5hdDo/ev0ErDg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:03Z", - "mac": "ENC[AES256_GCM,data:jH2O5iyuTsy/ankCXKx2aEDfT/uwbPrcGZbjj1gGxL2cxxKdaMKKlFUfKY4Zgn5On4sQHSuPtV//XYiGqNh0v0G+6DGPAb7g6WiRYsuM4DSARZYRwhAQMOx9TWDURuOi0REx6htvc/Jl9lA+1pP/9MqoO1zsWC4WCnMxG3mxfD0=,iv:264mkPVu4f9hSY11Ab8MnueSq7LnF9rpBwrWOLlwLM4=,tag:PaxqOpw4qYSiSDk1c1Jt4A==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value deleted file mode 100644 index 1865b12..0000000 --- a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAfA9rDsTSGAz7dDC3pOFMA+LPj08SjxMIc5BU4q5/in0= ------END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret deleted file mode 100644 index ed76391..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:HUvMiGEnMxlfYnnxX5RgZlw7SdETT/4BCkU7J1LhCeXH/BBN9PcT0jqhepyQ+3ybksk2zOTbxb0uiIodeaoSUKJM+jO1OKRElwtJObAVFPYw65x4TpH2n3j8JTWyIj9OdHFh7sXYGFK92GUsSGDWoZBV++AfzKa/KHw//8Zzy4ol3dgx6JPPQjvTvKIPoTaCre43RcB013UUdO2VRdh8x27KgybtlT8HXb6lAIRpuUS2cXCfbPW4E3ayinyKjVJ2iLUsmaSGSl8SltTk5GdGAYLEVTITH0Y1GNliZ04ENNuGdHVF5VlCIpuLcon9,iv:I2NUjIU5lUe8xpPMc1bYF0sHQ1pwlOO4Gz9ox/KCnrs=,tag:iqzJpTXMUdqUyp98hM/blw==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBU2ZyRjRURkNjSjcyaENQ\ncVZ5d2tKTlJVemdRaFZZUURzbEZGVFNGRlFFClY5Y29pRCtSelhlY3VlV0x6eXJx\nNmF1T1YwVjdjL3ZSaGZUK0I3cHQzc0EKLS0tIGkzVE9mcldIbWpicU5YeEZjbHVG\nQ0dJQXd1cENLeFNtaUxHM3EzTDRHSFkKeXjt+AnbcQqWTpOw3TWJTbIH+Mu0q/Du\noE3Lv8b3LcVFPb/OQz3tNvd7FftjEbH6yArcLJfKz8YcKSG6/X+H9w==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHeThUamlhRGVnb0pHUUQ3\nTWtwV3QvZXF6ZmRyVkRrNXg4dVgwZk96MWlNClAxMWhua29JRkpCeUNXdE14ODNv\ndk9DVVVsaTJiQ29IL1BHdERHWUhVSDAKLS0tIEg1bTRSRklaTFBhN2FLd3NIek1i\na1ZEQ1FxYzhzUmhMQmVlQjJYZ2M4MWMKhUBYaEA09xLoc0GAShctrGPFUE4YUGGk\nYW86mPh4uudivrxs6CAhH0GVB7qwVtc9EGEw8bVA2STdNnCzr0JmMg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:yvwDLQ1unefwvtlad7/QBqKnWIsU3hALVB3ia6vYl5wnaRZgycE1IHrDLrHVV20ANAAs1gRKcZYUAAKLYKo4SqduBJgVSf3Hjk9t5VRjcBvRRFizwFbBa4rtWZMBrJS0cV99me6FoLioFLA+zGonRmmkiCEbWbBvSZdf1J04ixw=,iv:mD42aLv2IY8Dvt9qfTBQKH2ZHeI4537tRMlA8AXdyVk=,tag:Up/RwgH+fsJIpwPFzM6B9g==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret deleted file mode 100644 index c68a058..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:AlD+xw5DFrH8VwsNXqRgX/bdU2+bD1BbMSNMl+h6MloEFgMWGBt0kypqpq3LXj/p9Vtqr7rCy9N4wNAazWpQHGpA3Dv3Oka0S9xK6ghWJJWqDy1NiKkQR6a459jO7GiOzF5EGAfSp5fa4EDk/0LkIE/jkhg1ckg=,iv:+1Nsq644xQaXRIFMGfa6hv/T68YaVdNG7Aj/clKJy/o=,tag:YPpAcKkoxc6PRYxCJDcm/Q==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVE15YnlYcFdQSnlxYWpi\nNmtHQ3JlZmltOHBRaC9iRlpUVDRiY0NHUGxzCjhrVU9jT29vRlFmeWJTNCt3VjR0\nVHFLNUVRS0k4eExrSHRPYmlZRHZkWncKLS0tIEFQSnUwWUxzbzY0TFREaWlPdUti\nVW1GTWpoVUxwdE9VT0h1Mm8wQ2JhaFEKkfS+cJVZ3aKJi+N4N76yilDJqutMLBKZ\nfaSHFyGwOkk9kS6pf53g6GHKmakJJa9KMVGF+d2FFLgnX0XX0H9qig==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzQVlkR080UmhuVHg0RlFP\nQ3phcisvY00zNTJZUFdLWUJ0ekZWRXFVRkNFCjdUc0U5ZEx3aU5ET2pIdkJzK050\nTEMzMlF4Zm9KNWR1RWY5TXBVZi9saWcKLS0tIDBHZWJWQUxoUU5BRkYvL1l0elV4\ndi90SWdkRDc1akV3ZGdiOHBhc3ppTXcKKs7RUx4WTNdEOL0J0aMZiiUD4xpnJQFC\n+Vj/6GQJmeenB1znvWZbgMUKDRhufzzg1gd8oDbjfaI1H6UP1MilNw==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:44xy2P1es6SKe4s2aUo1MpwSlRH262TEp+TTRpOyB0ANAunJI5ViN0Db2aHaqq0aPSiYkquIvKeqzzX9pHYqZX9Dmr37PEvoCILP5wbarO5XRCc2N53vuarEfH6xyNQCoGNw2aujpqm5ero1jJbDkYlMu2dmrAtm7y4mazcjM7s=,iv:iGzaXP7DrsEXoyntt0e/f8/M998QG9hN1Vx4C92cshU=,tag:SDm2jnXPy/gN5woNWMRcPA==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value deleted file mode 100644 index ce4108c..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAzYp9wRD7TpiZHYECCoYgMBdaYJWV31zbhN9u0xmRJIQ= ------END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value deleted file mode 100644 index 69bdc4b..0000000 --- a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value +++ /dev/null @@ -1 +0,0 @@ -12D3KooWPeiEFGKFd58Q6CTbVyCmD5RMXJk7RtcuZfpsshYDxpmy \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship deleted file mode 120000 index 94c85c7..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret deleted file mode 100644 index f032da2..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:E1Kds+yeaAIqu+az9a4QwyXb9yxIcq31IjrqqwLBnILp8oEIbPjaKbbCS/C8bbom9BeYixfeAlmo8iDE3IIdbq5W2vf/JNsLSulKF+9KnwA86aq1aNeI7akZGcs9Nvmly4lEUjCQH+rF3kGPNj/ji4uvNxm7FLU=,iv:LbOQMEBhSvwAWipVmF+NHW34CKXZla/YHYWPwySb/+M=,tag:EMHcLXPzUOABzy1+EGZw0Q==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwQVUxSGdkWFNZdm92OTFM\nZnZCeERNSEtWWnpiV0hLS2lsNzNCYkdBUDBJCm1kSzY1eFBGakVXVEV5TVFpdEts\nTkNiQmNHcnZZaTE1NnlVaCtFelF3clUKLS0tIHljYkM1QXVwKzlNNjJOOXlCT0FL\ncmdqcjRzeE8vR3VaU2JCcVdxTXJJd1kKR637rNaq3aceU7NxwBves13/2foYGzje\ncqnfkDARHDltiykBW0QXOfy8ws0XOrCXu5snd414WNeyGxgHuygXJQ==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZSTRBV1FGSkEzNHpoOHRE\nQU1rR0p5VVBtWXV0eXJ2L0ZWeW1iS3FDb21rCkpudnZiMjQrbEVzSWY1SEVhZk5v\naFV6NktldUkzaFlVQmdKNlFTalVoUEEKLS0tIDdrN2Vzejd6a3ByblJKcnhRMytR\nZm9DMFBFWSs1Z0IzeXlnWlVNWi9zbmMKULDR3ES98A2+BAXHuKvgod6IRQS9jbAK\nkGo8k2taP1+UhEIJbWY3WdfF621Eg/Ojc6+IQofrcrHTL+mu2MDN+g==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:04Z", - "mac": "ENC[AES256_GCM,data:YyEnzCEsCF2E5TuB9cbYaaMpDGqkCGYQ/fnS/+r/VsXINF9fVTRNbN8nYGWMsJvv3YOYkbGzfQXk0ydAEDYoQ7mFMjqtQjcLJqACkWLknkr366/CQ4eFnIvUMpiritjzPDX/8K7RFkCsWgtfIrKgKtCeoRkjbmxsKeP3gZWKqAI=,iv:G5zNZlVKmCS6297whHQpnSJXckwPS9pDpNcpFiAu8FE=,tag:VGaPrFF0swxqXTTsqZzgug==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny deleted file mode 120000 index 48e5c60..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value deleted file mode 100644 index 11c38e0..0000000 --- a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAYFTwnobAoybi21WD7LbwId2Mt3RdMAg4j5i0gKJa9Rs= ------END PUBLIC KEY----- diff --git a/vars/shared/data-mesher-network/network.key/secret b/vars/shared/data-mesher-network/network.key/secret deleted file mode 100644 index 262729a..0000000 --- a/vars/shared/data-mesher-network/network.key/secret +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:R4nCHoU/rU9JtO0++nwu+DWr9lB8iMb5CnH0YxG8kEp/UIFS8ZJrO/ZFR5f8NFAWLJQyhDb8rXyWehHbSBxqzEdT6topx0Jj7Ehx07pOsajLSaVLfHMBp3sgLWkMCNYFm76NRnq0aT7Fq6+eFACdU7K2ZgPPdGs=,iv:09RfU5lOKeXDVGQGI5qbWB9zEkUbS9sEQWqDo2TbIsA=,tag:VH4X0VKrzCaPYCM6wfvBrQ==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWQTgxOTQrQVVWbkhlMC9h\nYlFtdk9XdUlUQTVjeU1USTk1U3YzaXlMN2dzCmtaSWRlb2JudzR4U090OGlSa2hu\neUgxNTRLYmd4RTY2Vkp4ZlQwQjlxRHMKLS0tIFNHUEhpRGJPM0VIV1ZwRnFjU3Ri\nek9NT3Q4UnBqUWJzai84alpLMWpwREEKFA1LUso4N82+YlX2QtsC8JpUBde4Z1yQ\nbCP9joC8c7CuSwfJGVm7JfL2KlbxPeihyJsswhpWoupWjlTL4Nn2rg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:03Z", - "mac": "ENC[AES256_GCM,data:R6yaI8KB40un4P9aq6mQsVsr2gRh0LEKJ+c/DG4zydWVUxQOcLtPfYEsOOo9LNXOF0XvbnvXrVwwBSIe5aWFyPELft3mOyxFgVkxuFNfi1ppWduFaVe1EnnNwy1AiIzLsCfVr7CM8KYffaLXreZ5R3vc/QUF0ORxzhWzzbndPu8=,iv:O384W8d6nhVONeOkT7CF4Fa1bQroITsZWV4jaIP/LF4=,tag:a3zabV6K+VdSKBypOT9ofw==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/shared/data-mesher-network/network.key/users/danny b/vars/shared/data-mesher-network/network.key/users/danny deleted file mode 120000 index dcece98..0000000 --- a/vars/shared/data-mesher-network/network.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/data-mesher-network/network.pub/value b/vars/shared/data-mesher-network/network.pub/value deleted file mode 100644 index d212467..0000000 --- a/vars/shared/data-mesher-network/network.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAJrN3VxpiOwikr4TlcjkjbdguTD37fPpgIDOBtrLGOkY= ------END PUBLIC KEY----- diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship deleted file mode 120000 index 38ff05e..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship +++ /dev/null @@ -1 +0,0 @@ -../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret deleted file mode 100644 index 1d36ca8..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:33EcsUpW/KKNFdpPihdj3Mv5hMEo3lBYNOzRe43H1SH/tpwy5dLGl2FR0KGavOyoBwWf8tL6E3rp2gSm0pt4mGCq4FpMZaa/D4rZ6WWpwNTuNFKUH4uGVs6vRULI6G5yMorzp91TAv2E3yoh6mOKcMdlvqr6K2A=,iv:UR8xtdl6iYCVH4EueD9HyL4BprBJPw9A1KBtPX41uAI=,tag:0c/LO8ggYvHiowTJTe0LdQ==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFOGRpaFBzYmJBZ092MnJw\nbXIremlST3k5UVY4ZkQ1SVc5amNjbjhTRng0CnYySmZ5Ym8xR09yOUhwVXoyM0tL\nTE9OaElDSXFUUG83dkhFVVlkTUxCb2MKLS0tIHM0WkovcGkvUW9OazZncFhsYTVN\nTnByV3QzeURhdUlyL3FJclpDL1J2T1kKMpSf2lxaEbOl9rcYixFkQoaL4fS0LLz+\nqtCzpbChDgsUZ4aAMowIgPVpH1es4WA412MTmmrhbznNXIdHxnZ40w==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiNTA4cHZjaVdtVllkcmo1\nVlFuWVk3eEpNTWtTVUQvNy8yTGF0NkNjc0dJCjlMcm5taWt4R3g4eklIQ3hlY1ow\nK2dsaUMvTU84VkJOVG1uRTJBbDJ0WkEKLS0tIDgyNjZtU3VyNHlianAxcFY5Q0Ra\nU2pHQzd0Y3dUc1VSNDZuZ2RXem1qVkkK0iK/h8nLWZnDbXSNSXh1133Sctia5qsJ\nTsgRZt8amU8IxMF6IlgTQ0voEu5HJbKWzWqrD1tpab1dRil8+b6ljg==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2026-04-20T09:37:03Z", - "mac": "ENC[AES256_GCM,data:X/IjodUnCLcNfK251jkUuDU7YO5LXuau6zo5Aef7rX2XVsAFSUT4n+62RX8U4zNturCPkbInb3kUHVr+mCPgIG7k2GHRKZIFu0tbdb8aJlgg2IZ3Zswkd8yiRpqWygV/9rYGKpn58X7czj2aM1ydXr98qKUy8xgKcskB6CgN00E=,iv:2pjNT1o0QdwFRFKq4cTK3lDse652B58tMDHob9ppdM8=,tag:pxQLadQe5OfXscuKeHOa1w==,type:str]", - "version": "3.12.2" - } -} diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny deleted file mode 120000 index dcece98..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny +++ /dev/null @@ -1 +0,0 @@ -../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value deleted file mode 100644 index e7bdad7..0000000 --- a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAu7f60z9GVfxiyIJRmH3zlz6QBF/nDzICHHGUcAgUd0M= ------END PUBLIC KEY----- From 22808f39faccb861bce5ed50e222699583ae1ec1 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 19:58:16 +0200 Subject: [PATCH 097/185] =?UTF-8?q?feat(clan):=20re-enable=20dm-pull-deplo?= =?UTF-8?q?y=20via=20forked=20clan-community=20=F0=9F=8C=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4e, take 2. Point the clan-community input at our fork's branch that sanitizes machine.name for data-mesher's file-name validator (upstream PR: clan/clan-community#25). Revisit this pin once merged. - flake.nix: clan-community.url → fork + fix branch - flake-modules/clan.nix: re-adds meta.domain = "clan", inventory.instances.data-mesher (sunken-ship bootstrap, both default), inventory.instances.dm-pull-deploy (sunken-ship push, both default action="switch"), and clanHostsModule that puts /etc/hosts entries for .clan → each machine's ZT IPv6 so libp2p multiaddr resolution works without a clan-domain DNS server. - Generator vars for data-mesher + dm-pull-deploy signing keys were regenerated on sunken-ship (data-mesher isn't packaged for aarch64-darwin, so clan vars generate runs on Linux). --- flake-modules/clan.nix | 50 +++++++ flake.lock | 125 +++++++++++++++--- flake.nix | 7 + .../identity.cert/machines/phantom-ship | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/phantom-ship | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/phantom-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../identity.cert/machines/sunken-ship | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/sunken-ship | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/sunken-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../data-mesher-network/network.key/secret | 14 ++ .../network.key/users/danny | 1 + .../data-mesher-network/network.pub/value | 3 + .../signing.key/machines/sunken-ship | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + 34 files changed, 337 insertions(+), 20 deletions(-) create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship create mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship create mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value create mode 100644 vars/shared/data-mesher-network/network.key/secret create mode 120000 vars/shared/data-mesher-network/network.key/users/danny create mode 100644 vars/shared/data-mesher-network/network.pub/value create mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship create mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.key/secret create mode 120000 vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny create mode 100644 vars/shared/dm-pull-deploy-signing-key/signing.pub/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 216f05b..3a1c1df 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -14,11 +14,30 @@ let import ../lib/home-manager-user.nix { inherit lib user homeDirectory stateVersion userImports; }; + + # ZT IPv6 addresses of the two clan machines. Clan publishes these as + # generated vars at vars/per-machine//zerotier/zerotier-ip/value; + # duplicated here so we can drop them into /etc/hosts at module-eval time. + sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; + phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; + + # Shared across both servers: /etc/hosts entries so data-mesher's + # libp2p /dns/.clan/... bootstrap multiaddrs resolve over ZT. + clanHostsModule = { + networking.hosts = { + "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; + "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; + }; + }; in { imports = [ inputs.clan-core.flakeModules.default ]; clan = { meta.name = "homelab"; + # data-mesher uses `.${domain}` as a libp2p /dns/ multiaddr. + # We don't run a DNS server for "clan" — per-machine networking.hosts + # entries (via clanHostsModule) resolve it to the host's ZT IPv6. + meta.domain = "clan"; # Inventory machines — required for `inventory.instances` role bindings # to resolve. Host-specific NixOS config lives under `machines.` @@ -37,6 +56,35 @@ in { roles.peer.machines.sunken-ship = { }; }; + # data-mesher — signed-file gossip protocol over libp2p (port 7946). + # Underpins dm-pull-deploy below. Files are registered + their allowed + # signers managed automatically via clan service exports. + # sunken-ship is the bootstrap node; phantom-ship joins via its + # /dns/sunken-ship.clan/... multiaddr (resolved via /etc/hosts). + inventory.instances.data-mesher = { + module.name = "data-mesher"; + module.input = "clan-core"; + roles.default.machines.sunken-ship = { }; + roles.default.machines.phantom-ship = { }; + roles.bootstrap.machines.sunken-ship = { }; + }; + + # dm-pull-deploy — pull-based NixOS deploy via data-mesher gossip. + # Our clan-community input is pinned to the branch that sanitizes + # machine.name for the status file name (upstream PR pending). + # sunken-ship is the push node; both servers run the default watcher + # with action="switch". + inventory.instances.dm-pull-deploy = { + module.name = "dm-pull-deploy"; + module.input = "clan-community"; + roles.push.machines.sunken-ship.settings = { + gitUrl = "https://github.com/DannyDannyDanny/dotfiles.git"; + branch = "main"; + }; + roles.default.machines.sunken-ship.settings.action = "switch"; + roles.default.machines.phantom-ship.settings.action = "switch"; + }; + # `clan machines update` connection target. Priority 2000 > ZT's 900 # and overrides the ZT service's root@ default. Using the ZT IPv6 as # the host makes updates work regardless of LAN DNS / mDNS state. @@ -63,6 +111,7 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]"; } + clanHostsModule ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild inputs.home-manager.nixosModules.home-manager @@ -81,6 +130,7 @@ in { clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]"; } + clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild diff --git a/flake.lock b/flake.lock index 2de4459..0ad4b41 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,32 @@ { "nodes": { + "clan-community": { + "inputs": { + "clan-core": [ + "clan-core" + ], + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1776692203, + "narHash": "sha256-UtLlE2m4gaSjpN98/JjzPZqnSe0pTZvCXL1TsT0/Et4=", + "ref": "fix/dm-pull-deploy-hyphen-hostnames", + "rev": "ea5f670cb8690d5c209efd4777af4d391daa1ec2", + "revCount": 45, + "type": "git", + "url": "https://git.clan.lol/dannydannydanny/clan-community.git" + }, + "original": { + "ref": "fix/dm-pull-deploy-hyphen-hostnames", + "type": "git", + "url": "https://git.clan.lol/dannydannydanny/clan-community.git" + } + }, "clan-core": { "inputs": { "data-mesher": "data-mesher", @@ -13,8 +40,8 @@ "nixpkgs" ], "sops-nix": "sops-nix", - "systems": "systems", - "treefmt-nix": "treefmt-nix" + "systems": "systems_2", + "treefmt-nix": "treefmt-nix_2" }, "locked": { "lastModified": 1776557977, @@ -113,6 +140,27 @@ } }, "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "clan-community", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { "inputs": { "nixpkgs-lib": [ "nixpkgs" @@ -134,7 +182,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1731533236, @@ -152,7 +200,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_3" + "systems": "systems_4" }, "locked": { "lastModified": 1681202837, @@ -425,9 +473,10 @@ }, "root": { "inputs": { + "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", - "flake-parts": "flake-parts", + "flake-parts": "flake-parts_2", "home-manager": "home-manager", "import-tree": "import-tree", "nix-darwin": "nix-darwin_2", @@ -460,6 +509,21 @@ } }, "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { "locked": { "lastModified": 1774449309, "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", @@ -475,21 +539,6 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "systems_3": { "locked": { "lastModified": 1681028828, @@ -505,7 +554,43 @@ "type": "github" } }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "clan-community", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775125835, + "narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "75925962939880974e3ab417879daffcba36c4a3", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { "inputs": { "nixpkgs": [ "clan-core", diff --git a/flake.nix b/flake.nix index 8b802e3..30e7d71 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,13 @@ clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; + + # clan-community: dm-pull-deploy etc. Pinned to our fork's fix branch + # until clan/clan-community#25 (machine.name hyphen sanitization) lands. + # Swap back to `archive/main.tar.gz` when merged. + clan-community.url = "git+https://git.clan.lol/dannydannydanny/clan-community.git?ref=fix/dm-pull-deploy-hyphen-hostnames"; + clan-community.inputs.nixpkgs.follows = "nixpkgs"; + clan-community.inputs.clan-core.follows = "clan-core"; }; outputs = inputs @ { flake-parts, import-tree, ... }: diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..8dade2c --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:tWPgk97Zn1F81EuITs0GWe/hycZGreo0S1oYemuOXxpqdbq/kwKwdZydSxs4cZfXOVzf9nL8V8R01/prbfAMFBJl/OgWW5qFqv5Mj7mBDZWYOrDVW481hhkMuZGzlc+2f2Q6Enmugw1tKQa277KV6t9lZ1mrPYvzVqXh1ZJm4Ez8FJidwwmHkHxAeK8q1AkMXZKMNBBvt86VfTFmWlkhav53CUpGIvchdyAPw8c97sGTTd58L7r9xImR2ZtSgXl237q1NZC40+oUDMS6QB+l9JtAPus2BROQ84U0zImcLEuDh+cHGUnlowXPRvTL,iv:3E+iqOJy7mKBQE40doDEhlVS7yU6tsB2W99defE48gc=,tag:d0SnUTshXNewGECH2sTuJw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2Z21ES1dTMmZYN3pWTkpo\nY1dBVTY5K1UrT09xNnBWNWhTcWRQSHpyeDBzClBSRVJDeWxtM2ZjWHllWXV5ZHFM\nQ3BYbmhhUTF2dDlwYkRFbTBneGxoVFEKLS0tIHhvQWpTSGJvN2EvM29aenQxTG5L\nZUsvaUM0YWN6czUxM2FHWnhlL3pZREUKF3sKxQqPebE5DZQgYeChHlWcf89peqD1\nVTaZP8CDmLPxxXMOPt31PgOPwP36CHrYM12Fil3lXY31tM7fGw1hVA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUHBUQnZzc1ZXKzl5RlpG\ndWpKcEIrWE5RS01kOHZVWFNsNjFML3VwT21zCk1KV01JaWI4cHVVR1pOTCtPUU5n\nbVJPbWZmUzNTZ3dETWwwb3Nvb2t4MGsKLS0tIGFKRlB4ZzAvZEpWemZqYjJlT2pC\nOEhSYXRZOE9oUkRqVzUrWUxpK3dwb28KMrhCm/S1zF8ZqSkKE/dy2A/xiFwBOs0Y\nGtl9HJEAA/xx9gHAy7cuGe3sV8KLSySFUbdL3UE7OhqtuGJwt67hGg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:26Z", + "mac": "ENC[AES256_GCM,data:8W+KPrzQwbdXi+haUjvYjVjfN+/WmlQso4imTBI9RBYLJwX9e//sfvq4Jd2HOplb8lfIvPqbMUELKma95J3qbSmBTuU6JHP07D53KRz50ZfVH+f9U9yo35nmhLQzlSWAZY4mHF23eq8pyt2EJ5b0M1TLs5bG00FmjWG1ir8avLU=,iv:I6rMyCjLUCRwZOX2YiETgb73wlRhpkPA+8psgfWZnR0=,tag:0wieM5ylopWzr3g9QUfZzg==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..760b95d --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:vhKOKajk8ZG8Xjtj3dqgYETBjLIhvECvmVKP4LdrHa0sSK2IAm2dBO0vBaeHHst5pSzpRJuNJwwNhAZlO404puXSh2ul9/AopKSgGqr6DqDlgQDemSduCSmQRcQUfygQYB086mnACl+TUo4U0l7j/oDCVbIcvMM=,iv:RnjAM8KlFsdKvZ5h3l4PfXP3GzSun5cSQnfQL3KwOpw=,tag:7vp61F375LY4WOLHg9pTPg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5Y3JXOTAzQzlWdjE3b0oz\nUDBCb1dEY1BQUXltc0ZPUUttSDJmOStVRkFBCnliR0tPbEViTzZPZkt4ZGJoMGli\neWg4a3E5OE5taitsRzZQQkZ3dmJubW8KLS0tIDVGRW5TcStrVzY3ay91VGVGREFl\nMm5sWUpJY1JGOUtLSGxreHovN3RnWU0KqLtu8z+hm5S8M2U+ApZ5Wgw7HoiImamu\nVf9ARzgQ1G4kDKU7CW3nH7Oun876HoA1hcPnKgTlDSHMh95nMASC5A==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBybHM4Mzh6UWFKU0JzdHdU\ncmpEM2VqRk9GRDRsL24wSmw1Y28vYStna3g4CjZhaEs2T3kzZTc1VmptWlBMVnpp\nTzJ3bnZtd2NSK2tiaDJHdUxFdWQ0ZzgKLS0tIHA0MFZPSjZYYUdUYmxLaGhsSVdn\nT20xVWczdWlPbFM3VXo2MkNJcTM1L2cKfOO02LLAxJPd1zsxj2fpntGRXSA3Lg3m\nW+1LoGFoFofZ0ds9MVls1ILZMdaBpHdQ4eF5B7IJt5B5Up9rk//p9g==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:27Z", + "mac": "ENC[AES256_GCM,data:2qoVM/dTkE/qn2BPhDlUIFHg9nObArUh4yPVCzUng+wiLF9Umppl7DfzG0F+JNsKR087nMKYSEsN/yWeWdDaTfR6tOnTsxBj7ybNjUokNCTT5h4eN1RH1aAdOVXtFoF6EV1pkYN+uXuc0HhC04qVVgnFtLRO97oyI9CjgH7eSjI=,iv:MhW2NwpNlkahGw8V803NF9lEchQBue5BbDlJSv3HYTs=,tag:MyYA1QwKfxbwpyCsvXc76w==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..7b15405 --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEACJQ3sNPsTp/ZnTQ3npwQKqYKH9Qw0Tqq+OYusLlGOyE= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..84c7d2d --- /dev/null +++ b/vars/per-machine/phantom-ship/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWAPrWoqCMyrh4m1gYty5X9kANicXKnGwpiuLHEeeneKPe \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship new file mode 120000 index 0000000..18e1a3f --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/machines/phantom-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/phantom-ship \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..ff816b6 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:AabxVdVuPRrmARrCTedX0uwlVm1GVSCBJUo6fMCaHujCcPgx98lvj3o4RCBzfO/mFQ9W/cKbtisJjpoTBr4NRmldKCGQRmcKqeOfPIISjDLKe7nPxvQzmcZNySzkGMLlhgCPX9o0GZBWRKKPJdZaj8piXEFOijI=,iv:fyj9aqjf20QYFP/FBMWHe570lBaW+i33yKtnI3LIZ9c=,tag:iS6727dL1BK3DYGc3wYrKw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArdnRRaVo4b2pLU3BSR212\nNVErNjVWUzZLQTVITVpYUXJKcWtjb3NEQ1FVCjRJUFBtS3NlUHlEUXViTkNaMmxy\nTTZyaWZWSlhZVG1zMjdCUnpYL3gyaVkKLS0tIGhkd01JQnpWeXdOSXBoeGJpVkgr\ndmh6RFJISnI3VUFpbXRPWnFicG8wTTQK1/1lyVDvmEmA/FaYb4Xh8u4ni/1bLszK\nVfyeFyJAIWlZmrvdDDovhdF1kRoywMd1e4yOsA3nzgRRSKmL8oLo/Q==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwVUFXUmlPbUdzam42Uzlp\nT0FBekRpS0djS1ArWGw1aFhrUDNDcGFZaTNRClZpTWY3SE1ld3JzZGdkODVnOStI\nVDRDODRBQ3Z3Q2wvVk1kVE94NmtjUVEKLS0tIGN2c29yR041bWJNOVlvYVVtTW12\nNnk2VjJkV0Vjb0UwSWorWVdRK21TZ3MKdLb8G7L0UG79OQ7RPooeXnRMV+cPA9Na\n/AeBv/dfrO1ScRxUb5px8EDtO2NAr32JdSNrgb98CQeveC2MuPSeXA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:26Z", + "mac": "ENC[AES256_GCM,data:UYlO50InICGG4KGIe+YB/Cu3d47u8hoby3mNZvwigkn86bfQImbAQdAlcyR3aGN6fzY41STDNEgZ+aP16gQzX8/MyIy17qHLOQs4SXcd9YW7uL8KmxkBcEAgEQ8XzH6eo8Y0I2nHxrhPATXVGfGV8qB2kky4PIMTl5pH4myZLgI=,iv:eF6ktICycC+IuYv6b1rTC7SsZkb96ev4zhmDbyU763w=,tag:w1ljE1/Iq/C+9bx+IyuAhw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..b2ecb64 --- /dev/null +++ b/vars/per-machine/phantom-ship/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAY4BNAbhjAHOXLg5kaHTxQ/fsYnwZaCGoaNT4MfwhIik= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..8387bc5 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:OmnzynETGrQK8os5sSZ2Uo6+Ssurkt8wqnJU3f5pNDNBwBPLlcYfDzslkHu4eDGLpiJnVwyFJPdzwSRuTqQOIRN3Z6stSf4ISVWQnwRDDAaYfm8tj9xL6vbjXugQW9F++weMhO9gvyraDmJP/wlaCAdfgiXo/LjVneRQDBozUfPxF7KKBb8GTjP4PGILep3/n8HyxEl3ibAvgUplsdh5ybW2IeZeHwI9fuRY1EE09vGgIi7TSyHD3bfkNwQTsotNsgO3Z5SBPWPDIN2n0NYtVcUfkpJJZbp3TjWUhZuciHtsXggw4/aeW4xQdpNV,iv:LxoBD3dV1JLGJt8f8O70heIg+oVuSKrdLAv2AxFgfc0=,tag:79cQmt6gGuMO5cfYfeZ4HQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGTkZMQnZmelNKanJ4cjhu\nbGNDOENpOGoydWFFM21VNytYYkFabkE0WmdFCkNPb2ZyZHRxd2l0WnN3c24yNlZH\nRFp4ZStaY1JCUmFxL0xxVERjTk5yWW8KLS0tIE1KQURtbkl1Q0RROGZTZ2xMK0tI\nWlFQd2VFcmU5dC9FRXZ0cW1adDFCa0kK8gko5J+LsnVTuSj8yS60L05bYruuno9d\nUYtAHcfbnZ9VfrAFH+uWfsPUoloqDOiRR0BQx6m6epWlAIRILni2kw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEa2dHTWJ6RGVPTy9CRVdD\nN3dvcVd6RjRQdjJqdXlMdzNML25tYlpKTG1BCkR2ZUZqMHg2cG01UUNGOEttUTBM\nMVlpaUNTdEozd0MvN281QWczUEhLNHcKLS0tIFdGSG8vY3J1bktMM2tRekdmWFJJ\nNW5TaHNMVmc3Nm1OeE05Mk1xVVRxWFEKKpqi4WjKwb44Y3LrisJ0Nq5ftQljnrEN\nE/ardHaxYUCZaAuiBFSZeOG3+zrOc5+cNx74/ULAX4QaqHshJQpdKQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:27Z", + "mac": "ENC[AES256_GCM,data:Sc+5RNJuKJtsp9c22piVnUlUFvBeLGOKw1kwiyvUb48Hy9xVAP1YyzbopN7gVurdegoMvovz6iaM5pBC0xOlvGrHr6MvV5ET76N9hdacGCoFfSviKUQnDNQbic/yj9lJx5Nwpo05nf12R/NUCcXWCHkxv1jedpVRHclL8bEta5Y=,iv:WUVIqLnh81afVwdueOn59hK5+ijUI5iWrZKpqTLYSiM=,tag:I3uNmPwrnTbRWGDTBEjVRA==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..6dc938c --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:UhTbEIclwoIKcQaAMS/ZNkGrgg3HqkA3SHVBSHJ+wBI6x4i3dnAfIi08NstykkaKfMSJX0ELADiXu9rAqC/XHLHChYJ9yp9bdV/FzhaHHiJ61uVPSpgeJf9lPmirG5cNx15my3vFu9iElNuVoj1SrQJCOHdtw98=,iv:dJV6lBytRAyVyuLIGu8I7rYYd4ybKea7/j9ZMMRB5ck=,tag:QgTQApvKJAsFN2Gpxyse0g==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAybjQ2MDMwTUtOZDRsc1pV\nYnhTK1FMZkVZcElSVVQwNkc0V0w1aGY1VDE4ClUwMlA3VlBLTTF0bDRBWEZsTDZK\nNzlTcWtFU0w3dEdMcmVSVWJQSndNbXcKLS0tIEZEUXFwd2JaUDNDVDh5aFVWL20y\nSGFJUVUvMmZwOTFsZEhhZHFHRXJKODgKbySNJmTEDJ+0HxsmFuaIUWzLZ47HvATg\nxuYsiSXJGnermo4yxgRuisX68wLzaTVMRfdV7cK9Mqzq0gDOi6s/VQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2Q3R6L2Q3ZFRKMmFqbkdT\nK1lTOGRtK2tvVk5yZXpsN1hvR0laWTZHQ1dFCkphNzB1dE5TbWJrQU8va202UXRT\nSkdZM3FpZ2VWOExhaVVNMHROd1drb2sKLS0tIGZtZHZ2VzZJMi9tcHFMSGpodFk4\nbjlnd3NvMHdXSEovQ0txT2J2N1lzeTAKxpCIqP3TNazm96Zxsk+rhwY9CVj9cIDl\nBelBCK7fOSodayheZkAIcnOWUDGOOMuFqXGDo/HC2HmzyJsco5KkMg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:27Z", + "mac": "ENC[AES256_GCM,data:uHHOi9nYi+6QKOxjmLeOUH+0oVWvSYLWc9Vkryx1ADo58GjS4BwgFcXJaw+bnGPy1lvZzA0MdqSFL5Fb/9mFVZpt0PMGD1YjGkr8dZN2eZOYbH7ysBjv91GIDewGOpKGJQKQhmMuwfE1MkaBk6WfRD5IAK6fged+0Jr+CGE81jc=,iv:Wu0aPXx41nIr4MQGAW/4rpSbHNtdlgu+IxEP6oISINg=,tag:qLRQhOma2ly3kkRHVMYgbA==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..7451d16 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAs7iffRVJb66z4p0hBZkEFdYx/YG4V8QLrd1GfXx2gLA= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..2934a53 --- /dev/null +++ b/vars/per-machine/sunken-ship/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWMuvRnpEv9XEqyYnFcqKZALBS3Vhqi3zHSn4t4vwedkF9 \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship new file mode 120000 index 0000000..94c85c7 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..59985c1 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:C5ryB5qyQuwORAVwjQkPq/Lq6eGPWNVmVmI/ltsTDBEiNxb6MR1EUOykNeBdEv6P9CyLDp4J1c/5KzcBj/Ice1xu6qi7Bk2y2SyNLzCWscegNHDzDs59y2e9GuOHcIF3RTr/pPThe1nBIFulbmt49IEynjzqJlY=,iv:EtR0kaVQFMx7T9PqetFXnSrCr0O8SP2D2ZqXM+/rxcw=,tag:ITBNfT0DrkTGr/CcX9OHxA==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsQ21BS2xNSExrcXFWTWhl\nWkZ1OU9NUXRVSisyWFB4T2J1U0FaZ2lTZERVCllZNnVMdUdmcFNQVE5PVGpPb0tL\nbjBPaitlVUh5RkRPNWtVQ2FxQmdiRGsKLS0tIFNVUWxnNHBzNkVYanBIWHhnVGsv\nTzh0Z3hWUkRiNHlZc0ZibHNKRmNwK1UKsf3GK7garT04sC2U2xT3vvGrmKv8K9jv\nzEAVeJl6V2gDyz5oKEj9q8g/6tf+G4DUx9dOD6N/UbkRElKFSmbNnQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArNndjQnNjSFl0cllRTHdH\nSGJlNE5lMlpwMkFDRmViZ0JQelpkdXZtMVZ3CnlyNytrSmlwQlgzZ2VuWUJjbEI1\nVzh5S0FsWE5kVE1TWWFrOHU5eHdDVkUKLS0tIFJwdmp5R1luVjVDMFNjTDBPNzln\nUTN0aVlPTXNCUzBMazltRGRDSVRaVVEKnm/nokIWdRz/niLzC4WKE1BWVuy+KtQ5\nXZl7/aFGywxI7ux8dA+REz6FI6ZZb8B/lJ6+SkWP4s4/Z5DQ+Vkq2w==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:26Z", + "mac": "ENC[AES256_GCM,data:O+tJvNVJQ/+7zy5W5dxUVSKSUjiziKdqxv8Y/0GKoY8gJUzAOFD8UiUoFiQind+JOeH/jiSrLQPcq0jarWu1Cb7hGN+bwlySEhIyx4H9rYtZb99EueBCemQIQIsnoD8sSpif7CQdALedjPltJvs5ST4QqMvNPVVktExwATFdvYI=,iv:Y/NvE3i5guJoX8mA+mbAupo06OK2OiY+2YjgcLa/zcA=,tag:z+U7w2D9j6Fmn3UTevkLjg==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..079ec76 --- /dev/null +++ b/vars/per-machine/sunken-ship/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAFXu2waW6Rrc2Ro8iIcx1PH1oS45zNbfHg+F49VPYkeg= +-----END PUBLIC KEY----- diff --git a/vars/shared/data-mesher-network/network.key/secret b/vars/shared/data-mesher-network/network.key/secret new file mode 100644 index 0000000..676be36 --- /dev/null +++ b/vars/shared/data-mesher-network/network.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:N5diXVSyLS5354gi5UcSK7iWflVH549PGf4ugg/mIk0utPBscDV/9HaRCn5KWvprBeLmAgnSrFs9syU17Zwwf7FHPE1RfhalN+Pi0sesttMIBc9Bni7IcmVpFY+pXJxoqGdIkM7grEtWBquJWh1cSL1ICbG4Vz0=,iv:jdEXN0pyyYnsCtr0qEI5e3e8s1GS6Lk0HnZBI6q0Wb8=,tag:JPLfvnOFdfRtWREumJ3sow==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqaGt1dGY3RmZJaW9icGJw\nZ1RGQStFN3Z4R3BrcXVHRmZGOE1FcjFhRUM4Cmd5UnNmMFVpYU5CbFNicTBBS0tQ\nWUdOMnBMZk1Qc3NrY1NIMjJFc01PcU0KLS0tIG1sOFk1VEFyQmZuczZUeC9hSWZ3\nL0FXelZ2b3RrbVZMSGJOSDk2bDZkNU0KNVGQqSX5jSxLqAcvjyofkDP8rudeusEN\nrfrrqmvP6NUgiuQiBAWpXFS6Vt8aNIWwhpk/Ij3zw0rNWH4wdV5YSg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:25Z", + "mac": "ENC[AES256_GCM,data:LB+xpBT4SsjEZKMBE5JMATxOKGmLITAurL5ZAtCT1eVBJ+2kf7hp/EdmW29RrqeRZhQe3vVVo37qsud+jkACMYODmMBAzhwz7i2c2OQBFoz65MKth74B1BaV1AIfiCxghNbFExqfwV4SCnV4XntHxYelG97GfV9hWCLRdykaD10=,iv:fnkmHHJiwtfIC4F3FXXXH4vDmFls72F1UqhL45pvAsI=,tag:8fvUupt25Ta+CA9QExtpzg==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/shared/data-mesher-network/network.key/users/danny b/vars/shared/data-mesher-network/network.key/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/vars/shared/data-mesher-network/network.key/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/data-mesher-network/network.pub/value b/vars/shared/data-mesher-network/network.pub/value new file mode 100644 index 0000000..9795e07 --- /dev/null +++ b/vars/shared/data-mesher-network/network.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAdB1Fz8Y5HZxHcr+gvbpjG14MWVYZsHxDDRymsd0sQU8= +-----END PUBLIC KEY----- diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship new file mode 120000 index 0000000..38ff05e --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/machines/sunken-ship @@ -0,0 +1 @@ +../../../../../sops/machines/sunken-ship \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/secret b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret new file mode 100644 index 0000000..ebac713 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:SFWdcWNOT/lPnlk/03UrsejGiUiSE4540V0UVmd/2KxOnPf2463wlbCnx1pg4Y3dOScohNyM6MIU60/E8AZrQWUSYPM6RPVvW9Bpgb8WByUUABlxH3CxZth3ygpJGez/fchWFjkFUyydhcVWpm6UxmNnjhTwvq8=,iv:/n1pHl1DHy4pCVn1QuEK/+ud14ZWd0E5JfiMe8tczy0=,tag:NG8sO8Y8dujHVSh5d/ugFw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjNmVGOUhDdHNlUm5GRjJo\nZFJJbUQyZys4ZjFiRUlyWUxuUnBuWmkyckVFCi8wZmdSeVBzbis4b1JZcU5QSDRp\nYXJBNzVIeXhSSnIyK1FTUDhUNFR2SVEKLS0tIFZtV0ZsRjdaaXMxbXF6S0VjV1J3\nelBUYWx0ZXpNMVZoK0dKdFdMYUpaMUEK1H4mOKs8CEtJ2P2aFkd9C2I7BRoqdZX9\nCowzfdUPQK3XwYxK0e+qM/eSPMsQr+IgPX4GtpIrkPUtkr9yNlXdTA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNeHB1d0hrenZxZVVaWDgr\nbjYzcEY5VjNqL3FIR2NyYkd3bzBXNmFnNFZnCkY5TlplcEhqd2JUOFdoZzdtTWJs\ncW5rbWNkUnZ6SXJzd3ltM0FDeGIyZzAKLS0tIDZKdjZuaGdLUFVrMUE4RG1SSi83\nTVM1b1dTeVppQ3ZmLzExOVFob3VxR0EKziFYIaTcseeGJgLzb4ZgTnAnwlPavqGZ\nQ2/zQjn3i1vu1Et/qlhxQGg4AxmP976b23iwm+JYn901i00VClOuJg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-20T17:57:26Z", + "mac": "ENC[AES256_GCM,data:Qb3eJPq4m95zvteRXmDqReXiNrGDIHsh4PLYC/BRVJKLH2NJcygFj7DRuMHsM8TITEewvee96wInBim8porAGnNPGVl9GZpKU8gVT1JPWbbWYa28AegFoTgLw7nsGfyGYtAso6NfN9uuBggFf6zHBeSbg1p87hUbyRhjnp7xnOg=,iv:ugJKegaaH/FKsGrvWXdwVojb/yAwKtREF3Ng/2qgsIY=,tag:jsZ2QnHuje683C/wM/OGHg==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny new file mode 120000 index 0000000..dcece98 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/shared/dm-pull-deploy-signing-key/signing.pub/value b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value new file mode 100644 index 0000000..0f96494 --- /dev/null +++ b/vars/shared/dm-pull-deploy-signing-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA2tgf1jPcQ0GEIsEU4/iVsrg4Wi9WSgCpbKarBKhpxPQ= +-----END PUBLIC KEY----- From 754cb0d274c96f3840754b80b1b554770f492dab Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 20:06:08 +0200 Subject: [PATCH 098/185] =?UTF-8?q?chore(flake):=20bump=20clan-community?= =?UTF-8?q?=20fork=20(dm-send-deploy=20narHash=20skip)=20=F0=9F=94=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake.lock | 8 ++++---- nixos/home/danny/home.nix | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 0ad4b41..106a124 100644 --- a/flake.lock +++ b/flake.lock @@ -13,11 +13,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1776692203, - "narHash": "sha256-UtLlE2m4gaSjpN98/JjzPZqnSe0pTZvCXL1TsT0/Et4=", + "lastModified": 1776708356, + "narHash": "sha256-Smv2algQmojsu0m9EEXs+Oy0Tg/SjwI5WN66u/BaxYs=", "ref": "fix/dm-pull-deploy-hyphen-hostnames", - "rev": "ea5f670cb8690d5c209efd4777af4d391daa1ec2", - "revCount": 45, + "rev": "796ee625b60941bb959039924bfc39e5d13481cc", + "revCount": 46, "type": "git", "url": "https://git.clan.lol/dannydannydanny/clan-community.git" }, diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 3a55cc4..16d9adf 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -14,13 +14,13 @@ # Include ~/.ssh/config.d/* # near the top (before any host-specific blocks). home.file.".ssh/config.d/zerotier".text = '' - Host sunken-ship-zt + Host sunken-ship-zt fdd5:53a2:de33:d269:6499:93d5:53a2:de33 HostName fdd5:53a2:de33:d269:6499:93d5:53a2:de33 User danny IdentityFile ~/.ssh/id_ed25519_sunken_ship IdentitiesOnly yes - Host phantom-ship-zt + Host phantom-ship-zt fdd5:53a2:de33:d269:6499:936c:48a:bbdc HostName fdd5:53a2:de33:d269:6499:936c:48a:bbdc User danny IdentityFile ~/.ssh/id_ed25519_phantom_ship From b0c8664f5c10e74598cc3d013028ccc475523327 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 20 Apr 2026 20:28:05 +0200 Subject: [PATCH 099/185] =?UTF-8?q?docs:=20update=20stale=20dotfiles/nixos?= =?UTF-8?q?=20flake=20paths=20=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4f cleanup. The flake moved from ~/dotfiles/nixos/ to ~/dotfiles/ in 88c5139; docs and install scripts hadn't been refreshed. Point all rebuild / flake references at the new root: - AGENTS.md, README.md, server-quickstart.md, docs/server-installer-usb.md, docs/sunken-ship-wifi.md, nixos/readme.md — rebuild command paths. - scripts/nixos-server-install.sh — auto-detect now looks for flake.nix at repo root (was nixos/flake.nix). - scripts/post-install-provision.sh — first-rebuild hint path. `nixos/hosts/-hardware.nix` and friends stay where they are — host-specific NixOS modules still live under nixos/; only the flake entry-points + sops/ + vars/ + lib/ + modules/ + flake-modules/ moved. nixos/readme.md rewritten to reflect the split (flake at root, per-host modules under nixos/). --- AGENTS.md | 4 ++-- README.md | 2 +- docs/server-installer-usb.md | 8 +++---- docs/sunken-ship-wifi.md | 4 ++-- nixos/readme.md | 35 ++++++++++++++++++------------- scripts/nixos-server-install.sh | 8 +++---- scripts/post-install-provision.sh | 2 +- server-quickstart.md | 2 +- 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1a11b77..dcf9ab5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,11 +10,11 @@ From the Mac, agents can SSH to sunken-ship: ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship 'hostname; ip addr' ``` -Rebuild on the server: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'`. The server has WiFi; it remains reachable when ethernet is unplugged. +Rebuild on the server: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship'`. The server has WiFi; it remains reachable when ethernet is unplugged. Preferred from the mac: `nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- machines update sunken-ship --flake ~/dotfiles`. ## Server installer USB (new machines only) -Build from **Linux**: `cd ~/dotfiles/nixos && nix build .#installer-iso` (x86_64-linux only; cannot build on macOS). Or use official NixOS minimal ISO, write to USB, boot server, clone repo, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) when building custom ISO on Linux. +Build from **Linux**: `cd ~/dotfiles && nix build .#installer-iso` (x86_64-linux only; cannot build on macOS). Or use official NixOS minimal ISO, write to USB, boot server, clone repo, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) when building custom ISO on Linux. ## Learnings (NixOS server) diff --git a/README.md b/README.md index a181f19..201e803 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ nix-shell -p gh git gh auth login gh repo clone dannydannydanny/dotfiles && cd dotfiles # git checkout # if needed -sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl +sudo nixos-rebuild switch --flake ~/dotfiles#wsl ``` ### Clone via SSH diff --git a/docs/server-installer-usb.md b/docs/server-installer-usb.md index 4377f22..4c69d53 100644 --- a/docs/server-installer-usb.md +++ b/docs/server-installer-usb.md @@ -50,7 +50,7 @@ Bootable USB that installs NixOS on a new server with disk encryption (LUKS). Th 8. SSH in: `ssh danny@phantom-ship` 9. First rebuild to switch from generic `server-install` to `phantom-ship` config: ```bash - cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#phantom-ship + cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#phantom-ship ``` 10. Commit the generated `phantom-ship-hardware.nix` back to the repo. @@ -87,7 +87,7 @@ Adds WiFi kernel modules for servers that need WiFi on the live system. ### Build directly on Linux ```bash -cd ~/dotfiles/nixos && nix build .#installer-iso +cd ~/dotfiles && nix build .#installer-iso # Write to USB: sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M ``` @@ -117,7 +117,7 @@ sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi.json INSTALLER_HOSTNAME=my-serve ```bash sudo nix run github:nix-community/disko/latest#disko-install -- \ - --flake 'path:/tmp/dotfiles/nixos#server-install' \ + --flake 'path:/tmp/dotfiles#server-install' \ --disk main /dev/sda \ --system-config '{"networking":{"hostName":"my-server"}}' ``` @@ -130,5 +130,5 @@ sudo nix run github:nix-community/disko/latest#disko-install -- \ | **Boot** | Boot new server from USB, plug Ethernet | | **Install** | `curl ... \| INSTALLER_HOSTNAME=phantom-ship SSH_PUBKEY_FILE=/tmp/key.pub sudo -E bash` | | **Reboot** | Remove USB, unlock LUKS | -| **First rebuild** | `sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#phantom-ship` | +| **First rebuild** | `sudo nixos-rebuild switch --flake /etc/dotfiles#phantom-ship` | | **Commit** | Push generated `phantom-ship-hardware.nix` to repo | diff --git a/docs/sunken-ship-wifi.md b/docs/sunken-ship-wifi.md index aa8f06c..92410b6 100644 --- a/docs/sunken-ship-wifi.md +++ b/docs/sunken-ship-wifi.md @@ -42,10 +42,10 @@ nix shell nixpkgs#wpa_supplicant -c wpa_passphrase "YOUR_SSID" "YOUR_PASSWORD" ## Rebuild (after changing Nix config) -From the server (flake is in `nixos/`): +From the server (flake is at the repo root): ```bash -cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship +cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship ``` ## Verify diff --git a/nixos/readme.md b/nixos/readme.md index 34e6316..7dc6432 100644 --- a/nixos/readme.md +++ b/nixos/readme.md @@ -1,32 +1,39 @@ -# NixOS flake +# NixOS modules -Rebuild from dotfiles dir: +Host-specific NixOS and home-manager modules live under this dir: + +- `hosts/.nix` + `hosts/-hardware.nix` +- `home/danny/home.nix` (home-manager) +- `fish.nix`, `neovim.nix`, `ollama.nix`, `installer-iso.nix`, `disko-server.nix` + +The flake itself (`flake.nix`, `flake.lock`, `flake-modules/`, `lib/`, `modules/`, `sops/`, `vars/`) lives at the **repo root**, not here. See [CLAUDE.md](../CLAUDE.md) at the repo root for rebuild commands, clan.lol operations, and the `dotfiles-rebuild` timer. + +## Quick rebuild reference ```bash # macOS -cd ~/dotfiles/nixos && darwin-rebuild switch --flake . +cd ~/dotfiles && darwin-rebuild switch --flake . # WSL -sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl +sudo nixos-rebuild switch --flake ~/dotfiles#wsl -# sunken-ship (on server) -sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship +# Servers (via clan from mac) +nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- \ + machines update sunken-ship --flake ~/dotfiles ``` -## Server (sunken-ship) - -One-time bootstrap (no git until first rebuild): +## Server bootstrap (one-time) ```bash -nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- clone https://github.com/DannyDannyDanny/dotfiles.git /tmp/dotfiles +nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \ + clone https://github.com/DannyDannyDanny/dotfiles.git /tmp/dotfiles sudo mv /tmp/dotfiles /etc/dotfiles -sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship --option accept-flake-config true +sudo nixos-rebuild switch --flake /etc/dotfiles#sunken-ship \ + --option accept-flake-config true ``` -If the daemon doesn't have flakes: copy [server-configuration-with-flakes.nix](server-configuration-with-flakes.nix) to `/etc/nixos/configuration.nix`, run `sudo nixos-rebuild switch`, then build and switch to the flake (see [server-quickstart.md](../server-quickstart.md) for SSH keys). +If the daemon doesn't have flakes: copy [server-configuration-with-flakes.nix](server-configuration-with-flakes.nix) to `/etc/nixos/configuration.nix`, `sudo nixos-rebuild switch`, then build the flake. SSH keys (not in repo): `scp ~/.ssh/*.pub danny@server:/tmp/`, then on server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`. See [docs/ssh-and-secrets.md](../docs/ssh-and-secrets.md). -Timer: every 15 min the server pulls and rebuilds when `main` changes. Config: `hosts/sunken-ship.nix`, `hosts/sunken-ship-hardware.nix`. - No git in PATH: `sudo nix run nixpkgs#git -- -C /etc/dotfiles pull origin main`. diff --git a/scripts/nixos-server-install.sh b/scripts/nixos-server-install.sh index d8370ed..dbb690c 100644 --- a/scripts/nixos-server-install.sh +++ b/scripts/nixos-server-install.sh @@ -16,12 +16,12 @@ set -euo pipefail FLAKE_REF="${FLAKE_REF:-}" if [[ -z "$FLAKE_REF" ]]; then - if [[ -d "$(dirname "$0")/../nixos" ]] && [[ -f "$(dirname "$0")/../nixos/flake.nix" ]]; then + if [[ -f "$(dirname "$0")/../flake.nix" ]]; then REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" - FLAKE_REF="path:${REPO_ROOT}/nixos" + FLAKE_REF="path:${REPO_ROOT}" else echo "FLAKE_REF not set and not running from dotfiles repo. Example:" - echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles/nixos" + echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles" exit 1 fi fi @@ -197,5 +197,5 @@ echo "" echo "=== Done! ===" echo "Remove the USB and reboot. After unlocking LUKS:" echo " 1. SSH in: ssh danny@${hostname}" -echo " 2. First rebuild: cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#${hostname}" +echo " 2. First rebuild: cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#${hostname}" echo " 3. Commit ${hostname}-hardware.nix back to the repo" diff --git a/scripts/post-install-provision.sh b/scripts/post-install-provision.sh index 2e67ccc..d59792b 100755 --- a/scripts/post-install-provision.sh +++ b/scripts/post-install-provision.sh @@ -57,5 +57,5 @@ cryptsetup close crypted 2>/dev/null || true echo "" echo "=== Done! Remove USB and reboot. ===" echo "After unlocking LUKS, SSH in: ssh danny@${HOSTNAME}" -echo "Then: cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#${HOSTNAME}" +echo "Then: cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#${HOSTNAME}" echo "Commit ${HOSTNAME}-hardware.nix from the USB back to the repo." diff --git a/server-quickstart.md b/server-quickstart.md index 1cad215..866ac9f 100644 --- a/server-quickstart.md +++ b/server-quickstart.md @@ -44,7 +44,7 @@ Optional: `services.openssh.settings = { PasswordAuthentication = false; PermitR ```bash sudo nixos-rebuild switch -# or: sudo nixos-rebuild switch --flake /path/to/dotfiles/nixos#hostname +# or: sudo nixos-rebuild switch --flake /path/to/dotfiles#hostname ``` Then from your main machine: `ssh danny@myserver` From e8d4bbf24b4b8710ff5a3a3be0d1c57fbee3e545 Mon Sep 17 00:00:00 2001 From: Hara Date: Fri, 24 Apr 2026 10:37:53 +0200 Subject: [PATCH 100/185] Add danny to openclaw group for secret file access --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 60668e7..6a6c6e9 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -61,7 +61,7 @@ in users.users.danny = { isNormalUser = true; - extraGroups = [ "wheel" ]; + extraGroups = [ "wheel" "openclaw" ]; # Password is locked (key-only SSH). Use NixOS installer or recovery to reset if needed. }; From 6ef7112ae0f862fc7a471265af32dcfffcfbc17b Mon Sep 17 00:00:00 2001 From: Hara Date: Fri, 24 Apr 2026 11:24:27 +0200 Subject: [PATCH 101/185] revert: remove danny from openclaw group Widened access unnecessarily - use sudo -n instead for one-off secret reads. Co-Authored-By: Claude Sonnet 4.6 --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 6a6c6e9..60668e7 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -61,7 +61,7 @@ in users.users.danny = { isNormalUser = true; - extraGroups = [ "wheel" "openclaw" ]; + extraGroups = [ "wheel" ]; # Password is locked (key-only SSH). Use NixOS installer or recovery to reset if needed. }; From 47fc658523c766880469074e388e3617285805a5 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 24 Apr 2026 13:43:21 +0200 Subject: [PATCH 102/185] =?UTF-8?q?feat(clan):=20add=20vps-relay=20+=20str?= =?UTF-8?q?ip=20bbbot=20cloudflared=20=F0=9F=9A=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4.5: declare a Hetzner-hosted reverse-proxy VPS as a clan machine. - nixos/hosts/vps-relay.nix: Debian→NixOS cx23 in hel1. Caddy at public 80/443 reverse-proxies navidrome.dannydannydanny.me and bbbot.dannydannydanny.me over ZT to sunken-ship. - nixos/disko-cloud.nix: simple GPT + ext4 root, no LUKS — cloud provider has physical disk anyway and there's no operator at boot. - flake-modules/clan.nix: register vps-relay as an inventory machine, zerotier peer, internet networking target at its clan-generated ZT IPv6, and add vps-relay.clan to clanHostsModule /etc/hosts. - sunken-ship fitness-bot: drop pkgs.cloudflared from PATH + set WEBAPP_URL=https://bbbot.dannydannydanny.me. Paired with the bbbot upstream patch (start.py honors env WEBAPP_URL and skips cloudflared when set) — once the 15-min fitness-bot-pull timer pulls that change, bbbot will stop churning trycloudflare.com URLs. Vars (zerotier identity/ip + sops machine key) generated on sunken-ship because clan's hermetic sandbox on macOS fails to run the zerotier identity generator (same workaround as for data-mesher earlier). VPS install flow: Hetzner-created Debian box, then `clan machines install vps-relay --target-host root@` reinstalls to NixOS; subsequent updates go over ZT. --- flake-modules/clan.nix | 27 +++++++ nixos/disko-cloud.nix | 34 ++++++++ nixos/hosts/sunken-ship.nix | 8 +- nixos/hosts/vps-relay.nix | 77 +++++++++++++++++++ sops/machines/vps-relay/key.json | 6 ++ sops/secrets/vps-relay-age.key/secret | 14 ++++ sops/secrets/vps-relay-age.key/users/danny | 1 + .../machines/vps-relay | 1 + .../zerotier/zerotier-identity-secret/secret | 18 +++++ .../zerotier-identity-secret/users/danny | 1 + .../vps-relay/zerotier/zerotier-ip/value | 1 + 11 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 nixos/disko-cloud.nix create mode 100644 nixos/hosts/vps-relay.nix create mode 100755 sops/machines/vps-relay/key.json create mode 100644 sops/secrets/vps-relay-age.key/secret create mode 120000 sops/secrets/vps-relay-age.key/users/danny create mode 120000 vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/machines/vps-relay create mode 100644 vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/secret create mode 120000 vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/users/danny create mode 100644 vars/per-machine/vps-relay/zerotier/zerotier-ip/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 3a1c1df..6b1926e 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -20,6 +20,7 @@ let # duplicated here so we can drop them into /etc/hosts at module-eval time. sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; + vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; # Shared across both servers: /etc/hosts entries so data-mesher's # libp2p /dns/.clan/... bootstrap multiaddrs resolve over ZT. @@ -27,6 +28,7 @@ let networking.hosts = { "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; + "${vpsRelayZTv6}" = [ "vps-relay.clan" ]; }; }; in { @@ -44,6 +46,7 @@ in { # below. inventory.machines.sunken-ship = { }; inventory.machines.phantom-ship = { }; + inventory.machines.vps-relay = { }; # ZeroTier mesh VPN. sunken-ship is the controller (manages network # membership); phantom-ship is a peer. The mac joins manually as an @@ -54,6 +57,7 @@ in { roles.controller.machines.sunken-ship = { }; roles.peer.machines.phantom-ship = { }; roles.peer.machines.sunken-ship = { }; + roles.peer.machines.vps-relay = { }; }; # data-mesher — signed-file gossip protocol over libp2p (port 7946). @@ -99,6 +103,10 @@ in { host = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; user = "danny"; }; + roles.default.machines.vps-relay.settings = { + host = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; + user = "danny"; + }; }; # Preserve current network / init stack (no systemd-networkd/resolved, @@ -123,6 +131,25 @@ in { ]; }; + machines.vps-relay = { + imports = [ + { + clan.core.enableRecommendedDefaults = false; + # Initial install uses --target-host override; subsequent + # updates go over ZT IPv6 (set once generated, via the + # internet instance above). + } + clanHostsModule + ../nixos/hosts/vps-relay.nix + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + machines.phantom-ship = { imports = [ { diff --git a/nixos/disko-cloud.nix b/nixos/disko-cloud.nix new file mode 100644 index 0000000..dc0a33d --- /dev/null +++ b/nixos/disko-cloud.nix @@ -0,0 +1,34 @@ +# Disko layout for cloud VPS installs (e.g. Hetzner Cloud). +# No LUKS — the provider has physical disk access anyway and there's +# no operator present at boot to enter a passphrase. +{ + disko.devices = { + disk.main = { + type = "disk"; + device = "/dev/sda"; + content = { + type = "gpt"; + partitions = { + ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "fmask=0022" "dmask=0022" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 0ec4783..442534e 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -120,6 +120,11 @@ # Code: https://github.com/DannyDannyDanny/bigbiggerbiggestbot cloned at /home/danny/tg_fitness_bot # Bot token: ~danny/.secrets/bigbiggerbiggestbot # Deployment: fitness-bot-pull timer below runs every 15 min, git pulls, restarts service on changes. + # + # Mini App URL is fronted by Caddy on the vps-relay host at + # https://bbbot.dannydannydanny.me (VPS → ZeroTier → localhost:8080). + # The bot's start.py honors WEBAPP_URL to skip starting its own + # cloudflared Quick Tunnel when we've got a stable URL from the VPS. systemd.services.fitness-bot = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ python-telegram-bot @@ -131,7 +136,8 @@ after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pythonEnv pkgs.cloudflared ]; + path = [ pythonEnv ]; + environment.WEBAPP_URL = "https://bbbot.dannydannydanny.me"; serviceConfig = { WorkingDirectory = "/home/danny/tg_fitness_bot"; ExecStart = "${pythonEnv}/bin/python start.py"; diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix new file mode 100644 index 0000000..8147561 --- /dev/null +++ b/nixos/hosts/vps-relay.nix @@ -0,0 +1,77 @@ +# Hetzner Cloud VPS — public reverse proxy into the clan. +# +# Role: terminates public TLS via Caddy + Let's Encrypt, reverse-proxies +# each declared subdomain over ZeroTier to the appropriate homelab host. +# No navidrome/bbbot data ever hits disk here; this box is a relay. +{ config, lib, pkgs, ... }: +{ + imports = [ ../disko-cloud.nix ]; + + nixpkgs.hostPlatform = "x86_64-linux"; + + # Hetzner Cloud boots EFI with systemd-boot. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + # Cloud provisioners add the initial root SSH key via cloud-init or + # equivalent; we don't run cloud-init. All config is baked at install. + networking.hostName = "vps-relay"; + networking.useDHCP = lib.mkDefault true; + time.timeZone = "Europe/Copenhagen"; + + # --- User + SSH ------------------------------------------------------ + users.users.danny = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + openssh.authorizedKeys.keys = [ + # Same pubkey used to reach sunken-ship; set at install via clan. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship" + ]; + }; + users.users.root.openssh.authorizedKeys.keys = + config.users.users.danny.openssh.authorizedKeys.keys; + + security.sudo.wheelNeedsPassword = false; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + PermitRootLogin = "prohibit-password"; + }; + }; + + # --- Firewall -------------------------------------------------------- + # Public: 22 (SSH), 80 + 443 (Caddy). + # ZT interface: trusted (set in the clan ZT module). + networking.firewall.enable = true; + networking.firewall.allowedTCPPorts = [ 22 80 443 ]; + + # --- Caddy reverse proxy -------------------------------------------- + # Subdomains → clan backends over ZeroTier. IPs are sunken-ship's / + # phantom-ship's ZT IPv6; brackets required in URLs. + services.caddy = { + enable = true; + email = "powerhouseplayer@gmail.com"; + # Tell ACME to use Let's Encrypt's production endpoint (Caddy default). + virtualHosts = { + "navidrome.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:4533 + ''; + "bbbot.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8080 + ''; + }; + }; + + # --- Basic tooling --------------------------------------------------- + environment.systemPackages = with pkgs; [ + git + htop + tcpdump + ]; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + system.stateVersion = "25.11"; +} diff --git a/sops/machines/vps-relay/key.json b/sops/machines/vps-relay/key.json new file mode 100755 index 0000000..5c30825 --- /dev/null +++ b/sops/machines/vps-relay/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1mlljsdpqf054p4nav9s855rtd5szwyl9av8w2lvg86j59cdtugxqylcn6k", + "type": "age" + } +] \ No newline at end of file diff --git a/sops/secrets/vps-relay-age.key/secret b/sops/secrets/vps-relay-age.key/secret new file mode 100644 index 0000000..aca274e --- /dev/null +++ b/sops/secrets/vps-relay-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:+Cd3Hxr5KzX6J/74M2IZ6VOE6KEDsK8NoVyTleSB7UdsDWWGAS+mgdNZTiVBJEIBx+cmMKMcNj2rNu6T4Z2OCvqH/o6GBAhKBmM=,iv:RllA6vH/qWsx08gTEi5Nl4VkvoeI00Bw56IwPp1TOLk=,tag:PdQJpm0oaYZUZvc1y9Cmcw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxSmVLKzl3akVZNlE0QWJr\namc0T3NPb0pzM2hvR3VlSEo2TDJ6VDJOQmhBCnJOTkZaOVM2RXpTOEdYUEtWTUht\nS0ZmNDVoVDJzajYxRDVWVFVkTkJLbkUKLS0tICsrUGx0Q2FmZk04NHBVb2wvaU1p\nSktZNVl5bUtKZEJLWm1kYm9wSFl5ZXMKEb+0fq1idxA4mpJAxt3DUWX8kYp8HwYN\nwUQ7SFAlj3k611jfVFwRYdqJZQLYQ0iVbEwy5BfJw4tnqZFeaEBueA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-24T11:41:47Z", + "mac": "ENC[AES256_GCM,data:AjAgVpuV7QvCh1E4AvTSP+Oxg/M1at8X08s76C9OxmdCR0Evd67Hb5TaPkujhtX87Qs9IHoOK6yY+aQv2exLXWt6U4uRzapsVIpcofdyA7EUF2q0UaykrqtKLGYW3IY8fXL4XwMMFJ+wmThmwKnJVJO8hUug8AceA83/QVYNccM=,iv:JuxpYvmTROZPv7zawPQ/NpfbWAQqwRfBRp+zhNQnm5I=,tag:v6IMgQTbPP+XEeCSrpVTxg==,type:str]", + "version": "3.12.2" + } +} diff --git a/sops/secrets/vps-relay-age.key/users/danny b/sops/secrets/vps-relay-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/sops/secrets/vps-relay-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/machines/vps-relay b/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/machines/vps-relay new file mode 120000 index 0000000..5393939 --- /dev/null +++ b/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/machines/vps-relay @@ -0,0 +1 @@ +../../../../../../sops/machines/vps-relay \ No newline at end of file diff --git a/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/secret b/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..624a47c --- /dev/null +++ b/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:pKvAwWARYI+t2dx3E90ime8VWT1LlTaHtkfCbwPzus7GNpOPKDXzhh3aVICSy0FOlonKBuKB35DLialWGb6rLI9T5ITIh+DJj6ijoxjSWJrWmZg2Du7xq89ZtV23HpNfuJnhnF4Wo3/WNDS+vnIynJSS9lx9NXe64Nd52NGSEufHM2HHkWIIgGR7Vgs0EoGxmxrDcxQ21MA2uOMKAYwCWmDJsRO6iXy+t01tTwFjUDg8203GtQjQaR99lY0GLrJybraaowa2bn0gyJvpnFt/zQ7kHNV6jPbdNj4wo4OwZ5JHA3zO5Ep6ePUQm7g8cjVD83eV8HZ/1Hb7LF4S4628CYwydaxHmHJsdbvVz7I1,iv:ydc0gUdniYXGeW4WkQypkWz8C0yZ0GcA2srYWgV165U=,tag:Q06ScyNT7zatscjJqje+lQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBESE1Nd000SEovTFJQQlhH\ndFBPTmJsK3ZObG9NR0ppWUxqbkhrVmk1eFRNCnlXTUNTejFPZEdmVEROSGZSNmh2\ncEM0NG92Q01MODNZSDFOdE9MMlpmak0KLS0tIHFneDMyMFhMZm1GNDdRYmFmZU5n\nOTRuZTFhaEJxREZ3UkpVSUZqazJ5MUUK4hKiYzkoNhsxYqK0fDP7zweQLFet4WMD\nnQVUYpQIGjxK1fQEImGMybIwGRjIxfsYI51GI7qTkwUAPaLxXUjs+Q==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1mlljsdpqf054p4nav9s855rtd5szwyl9av8w2lvg86j59cdtugxqylcn6k", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxVVJGN3NjQUN3aXRQaVlr\nd1Biei9mV3ZiTWRYRUxyRnhhcHlINjd1RDI0ClZETXJ0V1l6NGViVk43Y3NxSUYz\nZ3JtcDAzcXNZMnNXanl5cEVYaHRLVDAKLS0tIDVIZ2tiblNpVTlnVGJtbloyd3I1\nalZETWRvSVRRM3o2WnBmbGpFalNEQlEK3FkoqpSRrlce/4wFOdF26tUCeY8g1RD2\npYvz/giE8ULnWxYfG2HOTMQkUyUjYFiY0JPJT7oGyhQs4QmkVwhBWg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-04-24T11:41:47Z", + "mac": "ENC[AES256_GCM,data:RlbV4iY8ekGfe4I53Zn8nGaAou8D+jUYW4DXi8EfwdDTiSM3+szyMe8YNMbetM0jiPe2sAVqxTMgkLe99G5lZwZBY6pOrlBljiMPtvHb0NseRr9cnMUySfX9QAhEyD62bWCQyp33jCK7bJjAtmEATnIslYePQCJhmf0OEMO95NI=,iv:XK939qjL9wwZqrJaywnfBziuY6LFI+fAH3d4rNIbdRs=,tag:uhDWeOkpyHom3/5wtEaHrw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/vps-relay/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/vps-relay/zerotier/zerotier-ip/value b/vars/per-machine/vps-relay/zerotier/zerotier-ip/value new file mode 100644 index 0000000..6f577e1 --- /dev/null +++ b/vars/per-machine/vps-relay/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:9305:339f:2ed3 \ No newline at end of file From 914a82558744c69d2480de3adb8afb1797c33361 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 24 Apr 2026 13:47:38 +0200 Subject: [PATCH 103/185] =?UTF-8?q?feat(sunken-ship):=20trust=20danny=20fo?= =?UTF-8?q?r=20nix=20remote=20builds=20=F0=9F=8F=97=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 442534e..a8b96bf 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -55,6 +55,10 @@ # Passwordless sudo for wheel. security.sudo.wheelNeedsPassword = false; + + # Trust `danny` for Nix remote builds (so the mac can delegate + # x86_64-linux builds here via ssh-ng://danny@sunken-ship-zt). + nix.settings.trusted-users = [ "root" "danny" ]; environment.systemPackages = with pkgs; [ git # clone/bootstrap and dotfiles-rebuild timer brightnessctl # manual backlight; replaces removed `light` from nixpkgs From f4738584c367ce6986034011c0bd49d7dfc101cc Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 24 Apr 2026 14:51:41 +0200 Subject: [PATCH 104/185] =?UTF-8?q?fix(vps-relay):=20add=20virtio=20module?= =?UTF-8?q?s=20to=20initrd=20so=20it=20boots=20on=20Hetzner=20=F0=9F=9B=B0?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/vps-relay.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 8147561..d589cee 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -13,6 +13,20 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; + # Hetzner Cloud cx23 uses QEMU virtio-scsi for the disk and virtio-net + # for the NIC. Without these modules in initrd, the kernel can't find + # the root partition and hangs during boot. + boot.initrd.availableKernelModules = [ + "virtio_pci" + "virtio_scsi" + "virtio_net" + "virtio_blk" + "ata_piix" + "sd_mod" + "sr_mod" + ]; + boot.kernelModules = [ "virtio_pci" "virtio_scsi" "virtio_net" ]; + # Cloud provisioners add the initial root SSH key via cloud-init or # equivalent; we don't run cloud-init. All config is baked at install. networking.hostName = "vps-relay"; From 244988d52dbda684973e3c41e451c3f13603896e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 24 Apr 2026 16:05:27 +0200 Subject: [PATCH 105/185] =?UTF-8?q?fix(vps-relay):=20switch=20to=20GRUB/BI?= =?UTF-8?q?OS=20=E2=80=94=20Hetzner=20Cloud=20is=20not=20UEFI=20?= =?UTF-8?q?=F0=9F=A7=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/disko-cloud.nix | 15 ++++++--------- nixos/hosts/vps-relay.nix | 11 ++++++++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/nixos/disko-cloud.nix b/nixos/disko-cloud.nix index dc0a33d..9caa26d 100644 --- a/nixos/disko-cloud.nix +++ b/nixos/disko-cloud.nix @@ -1,4 +1,5 @@ # Disko layout for cloud VPS installs (e.g. Hetzner Cloud). +# GPT with a 1MB BIOS boot partition (for GRUB on a BIOS system) + root. # No LUKS — the provider has physical disk access anyway and there's # no operator present at boot to enter a passphrase. { @@ -9,15 +10,11 @@ content = { type = "gpt"; partitions = { - ESP = { - size = "512M"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = [ "fmask=0022" "dmask=0022" ]; - }; + # GRUB BIOS boot partition — holds stage-1.5 bootloader code. + # Type EF02. No filesystem. + BIOSBOOT = { + size = "1M"; + type = "EF02"; }; root = { size = "100%"; diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index d589cee..00ee58a 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -9,9 +9,14 @@ nixpkgs.hostPlatform = "x86_64-linux"; - # Hetzner Cloud boots EFI with systemd-boot. - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; + # Hetzner Cloud vServers boot in BIOS mode (confirmed via rescue: + # /sys/firmware/efi doesn't exist, product_name=vServer). systemd-boot + # is UEFI-only, so use GRUB with BIOS MBR support instead. + boot.loader.grub = { + enable = true; + device = "/dev/sda"; + efiSupport = false; + }; # Hetzner Cloud cx23 uses QEMU virtio-scsi for the disk and virtio-net # for the NIC. Without these modules in initrd, the kernel can't find From ba277b3f49d2adc2cb762a6202c5e4a82bd1827f Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 24 Apr 2026 17:43:00 +0200 Subject: [PATCH 106/185] =?UTF-8?q?fix(vps-relay):=20grub=20config=20force?= =?UTF-8?q?-override=20to=20resolve=20dup=20in=20mirroredBoots=20?= =?UTF-8?q?=F0=9F=90=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/vps-relay.nix | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 00ee58a..c5b19c8 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -11,12 +11,15 @@ # Hetzner Cloud vServers boot in BIOS mode (confirmed via rescue: # /sys/firmware/efi doesn't exist, product_name=vServer). systemd-boot - # is UEFI-only, so use GRUB with BIOS MBR support instead. - boot.loader.grub = { - enable = true; - device = "/dev/sda"; - efiSupport = false; - }; + # is UEFI-only, so use GRUB/BIOS. disko's EF02 BIOS boot partition + # already tells GRUB where to embed stage-1.5; we just enable grub + + # set the install device list. + boot.loader.systemd-boot.enable = lib.mkForce false; + boot.loader.grub.enable = lib.mkForce true; + boot.loader.grub.efiSupport = lib.mkForce false; + boot.loader.grub.devices = lib.mkForce [ "/dev/sda" ]; + # Ensure no default-set .device slips through and duplicates mirroredBoots. + boot.loader.grub.device = lib.mkForce "nodev"; # Hetzner Cloud cx23 uses QEMU virtio-scsi for the disk and virtio-net # for the NIC. Without these modules in initrd, the kernel can't find From 4332dfcbb52f6233ebd111a79841f1f29e45eeca Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 24 Apr 2026 17:48:52 +0200 Subject: [PATCH 107/185] =?UTF-8?q?chore(clan):=20point=20vps-relay=20at?= =?UTF-8?q?=20public=20IPv4=20while=20ZT=20identity=20bootstraps=20?= =?UTF-8?q?=F0=9F=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/clan.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 6b1926e..706aa0f 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -103,8 +103,12 @@ in { host = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; user = "danny"; }; + # Using public IPv4 while ZT identity is being bootstrapped on the + # VPS. Swap to ZT IPv6 (fdd5:53a2:de33:d269:6499:9305:339f:2ed3) + # after the first clan update uploads SOPS keys and zerotierone + # restarts with the clan-managed identity. roles.default.machines.vps-relay.settings = { - host = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; + host = "89.167.39.251"; user = "danny"; }; }; From bce34985ebdd28a76b58a6328851a4f26ff2f04c Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 25 Apr 2026 13:15:59 +0200 Subject: [PATCH 108/185] =?UTF-8?q?feat(sunken-ship):=20open=20firewall=20?= =?UTF-8?q?:8080=20for=20bbbot=20via=20vps-relay=20=F0=9F=94=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index a8b96bf..976baf5 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -73,9 +73,11 @@ publish = { enable = true; userServices = true; }; }; - # Open firewall for AirPlay (mDNS + UxPlay default ports) + Navidrome. + # Open firewall for AirPlay (mDNS + UxPlay default ports) + Navidrome + # + bbbot HTTP backend (proxied by Caddy on vps-relay over ZT). + # TODO 4g: tighten to only the VPS's ZT IPv6 instead of any source. networking.firewall = { - allowedTCPPorts = [ 7000 7001 7100 4533 ]; + allowedTCPPorts = [ 7000 7001 7100 4533 8080 ]; allowedUDPPorts = [ 5353 6000 6001 7011 ]; }; From 3b5288a48c9f5e136471fc25db8828feca9c5506 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 25 Apr 2026 13:17:27 +0200 Subject: [PATCH 109/185] =?UTF-8?q?feat(sunken-ship):=20bbbot=20bind=20dua?= =?UTF-8?q?l-stack=20so=20VPS=20Caddy=20reaches=20it=20via=20ZT=20IPv6=20?= =?UTF-8?q?=F0=9F=AA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 976baf5..9f14475 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -144,6 +144,8 @@ wantedBy = [ "multi-user.target" ]; path = [ pythonEnv ]; environment.WEBAPP_URL = "https://bbbot.dannydannydanny.me"; + # Bind dual-stack so the VPS Caddy can reach us over ZT IPv6. + environment.API_HOST = "::"; serviceConfig = { WorkingDirectory = "/home/danny/tg_fitness_bot"; ExecStart = "${pythonEnv}/bin/python start.py"; From 644420481e81b5903700638ee3eecf6574b8bb42 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 25 Apr 2026 13:26:37 +0200 Subject: [PATCH 110/185] =?UTF-8?q?fix(sunken-ship):=20bbbot=208080=20only?= =?UTF-8?q?=20allowed=20on=20ZT=20interface=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/sunken-ship.nix | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 9f14475..609bb83 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -73,12 +73,17 @@ publish = { enable = true; userServices = true; }; }; - # Open firewall for AirPlay (mDNS + UxPlay default ports) + Navidrome - # + bbbot HTTP backend (proxied by Caddy on vps-relay over ZT). - # TODO 4g: tighten to only the VPS's ZT IPv6 instead of any source. + # Open firewall for AirPlay (mDNS + UxPlay default ports) + Navidrome. + # bbbot's HTTP backend (port 8080) is intentionally NOT in the global + # allowedTCPPorts — it's only allowed on the ZeroTier interface + # (clan-managed name; matches anything starting with `zt`) so the + # vps-relay Caddy can reach it via the ZT mesh. Same trick could lock + # 4533 down later but Navidrome stays globally accessible for now (LAN + # convenience). networking.firewall = { - allowedTCPPorts = [ 7000 7001 7100 4533 8080 ]; + allowedTCPPorts = [ 7000 7001 7100 4533 ]; allowedUDPPorts = [ 5353 6000 6001 7011 ]; + interfaces."zt+".allowedTCPPorts = [ 8080 ]; }; # Navidrome — self-hosted music streaming server (Subsonic API). From b8bc17f385795ecd2c85cf3c393e627de720fa61 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 25 Apr 2026 13:30:40 +0200 Subject: [PATCH 111/185] =?UTF-8?q?feat(servers):=20declare=20SSH=20author?= =?UTF-8?q?izedKeys=20+=20root=20mac=20admin=20trust=20=F0=9F=94=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the imperative SSH-key-related scars accumulated during the clan/VPS rollout into nix config so future installs and rebuilds reproduce the same state: - danny@sunken-ship + danny@phantom-ship: trust the mac admin key (id_ed25519_ on Daniel-Macbook-Air) and the host's own self-loopback key (used by clan ssh-ng:// nix-copy-closure back to the same host during `clan machines update`). - root@sunken-ship + root@phantom-ship: trust the mac admin key so `clan machines update` can run its SOPS-key upload step that SSHes to root@ to write /var/lib/sops-nix/key.txt. Existing key files (~/.ssh/id_ed25519 on each host) stay where they are; the keypair was generated once during initial bootstrap and the public side is now declared above. Reinstalls would regenerate and need the pubkey re-pinned here. --- nixos/hosts/phantom-ship.nix | 12 ++++++++++++ nixos/hosts/sunken-ship.nix | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 60668e7..e89f231 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -63,8 +63,20 @@ in isNormalUser = true; extraGroups = [ "wheel" ]; # Password is locked (key-only SSH). Use NixOS installer or recovery to reset if needed. + openssh.authorizedKeys.keys = [ + # Mac admin (~/.ssh/id_ed25519_phantom_ship on Daniel-Macbook-Air). + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDNl6PrKcEhmYJVqSXNcFU6cba3neekLBGnQCkD7lWAc danny@phantom-ship" + # Self-loopback (clan ssh-ng:// back to this host). + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyEX8De/b+sMAxUZIqqiPphcrWCoAsN5p8gRFubzqvB danny@phantom-ship" + ]; }; + # root needs the mac admin key so `clan machines update` can SSH to + # root@ for SOPS upload. + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDNl6PrKcEhmYJVqSXNcFU6cba3neekLBGnQCkD7lWAc danny@phantom-ship" + ]; + # Key-only auth; no password or keyboard-interactive. services.openssh = { enable = true; diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 609bb83..e305c03 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -39,10 +39,22 @@ users.users.danny = { isNormalUser = true; extraGroups = [ "wheel" "video" "audio" ]; # video: backlight; audio: sound devices - # SSH keys: push via scp, don't commit. NixOS does not manage authorized_keys so scp'd keys persist. - # Example: scp ~/.ssh/id_ed25519_sunken_ship.pub danny@server:/tmp/ then on server: mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys + openssh.authorizedKeys.keys = [ + # Mac admin (~/.ssh/id_ed25519_sunken_ship on Daniel-Macbook-Air). + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship" + # Self-loopback (used by clan ssh-ng:// during nix-copy-closure + # back to this same host on `clan machines update`). Pubkey of the + # /home/danny/.ssh/id_ed25519 that lives on this host. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship self" + ]; }; + # root needs the mac admin key so `clan machines update` can SSH to + # root@ to upload SOPS keys (sops-install-secrets bootstrap). + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship" + ]; + # Key-only auth; no password or keyboard-interactive. services.openssh = { enable = true; From 771cc58076d97e0906969a1c688c51dde60b1130 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 25 Apr 2026 13:51:19 +0200 Subject: [PATCH 112/185] =?UTF-8?q?feat:=20vps=20fail2ban=20+=20shared=20s?= =?UTF-8?q?erver-debug-tools=20module=20=F0=9F=9B=A1=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VPS public SSH: enable fail2ban with bantime-increment so brute-force probers get evicted with exponential backoff (1h → 4h → 16h → 2.7d → 10.7d, capped at 30d). Default jail covers sshd; maxretry=5 in 10m. server-debug-tools: htop, tcpdump, dnsutils, jq, curl. Imported by sunken-ship + phantom-ship via flake.nixosModules.server-debug-tools. These are the practical bits we'd otherwise pick up by enabling clan.core.enableRecommendedDefaults — but the full clan defaults flip systemd-networkd/resolved on, which broke dnsmasq + navidrome's resolv .conf bind-mount on the homelab servers, so we cherry-pick instead. --- flake-modules/clan.nix | 2 ++ flake-modules/nixos-modules.nix | 1 + modules/server-debug-tools.nix | 15 +++++++++++++++ nixos/hosts/vps-relay.nix | 17 +++++++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 modules/server-debug-tools.nix diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 706aa0f..f8b1293 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -126,6 +126,7 @@ in { clanHostsModule ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild + config.flake.nixosModules.server-debug-tools inputs.home-manager.nixosModules.home-manager (hmModule { user = "danny"; @@ -165,6 +166,7 @@ in { inputs.nix-openclaw.nixosModules.openclaw-gateway ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild + config.flake.nixosModules.server-debug-tools inputs.home-manager.nixosModules.home-manager (hmModule { user = "danny"; diff --git a/flake-modules/nixos-modules.nix b/flake-modules/nixos-modules.nix index c982dd9..a466a58 100644 --- a/flake-modules/nixos-modules.nix +++ b/flake-modules/nixos-modules.nix @@ -4,4 +4,5 @@ # modules = [ config.flake.nixosModules.dotfiles-rebuild ]; { ... }: { flake.nixosModules.dotfiles-rebuild = ../modules/dotfiles-rebuild.nix; + flake.nixosModules.server-debug-tools = ../modules/server-debug-tools.nix; } diff --git a/modules/server-debug-tools.nix b/modules/server-debug-tools.nix new file mode 100644 index 0000000..4d35198 --- /dev/null +++ b/modules/server-debug-tools.nix @@ -0,0 +1,15 @@ +# A small set of network/process debugging tools that we'd otherwise +# pick up from `clan.core.enableRecommendedDefaults = true`. The full +# clan defaults also flip systemd-networkd / systemd-resolved on, which +# breaks dnsmasq + navidrome's resolv.conf bind-mount, so we opted out +# fleet-wide and added just the useful packages explicitly here. +{ pkgs, ... }: +{ + environment.systemPackages = with pkgs; [ + htop # process monitor + tcpdump # packet capture + dnsutils # dig, nslookup, host + jq # JSON parser + curl # HTTP client + ]; +} diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index c5b19c8..f7bf7b0 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -70,6 +70,23 @@ networking.firewall.enable = true; networking.firewall.allowedTCPPorts = [ 22 80 443 ]; + # fail2ban — public SSH gets brute-force probed within minutes of any + # cloud VM being created. Ban offending IPs after a few failures. + services.fail2ban = { + enable = true; + bantime = "1h"; + bantime-increment = { + enable = true; + multipliers = "1 4 16 64 256"; # 1h, 4h, 16h, ~2.7d, ~10.7d + maxtime = "30d"; + }; + jails.sshd.settings = { + enabled = true; + maxretry = 5; + findtime = "10m"; + }; + }; + # --- Caddy reverse proxy -------------------------------------------- # Subdomains → clan backends over ZeroTier. IPs are sunken-ship's / # phantom-ship's ZT IPv6; brackets required in URLs. From af9f735abc6bd9aeb6e84bec1a382099aeca8878 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 2 May 2026 14:15:10 +0200 Subject: [PATCH 113/185] =?UTF-8?q?feat(phantom-ship):=20hara-gmail-mcp=20?= =?UTF-8?q?server=20(path=201,=20IMAP+SMTP)=20=F0=9F=93=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an MCP server exposing read tools (list_inbox, search, read_email) across three personal Gmail accounts using existing app passwords in /etc/openclaw/. Wired into claude-channels via --mcp-config. Slated for replacement by an OAuth2 Gmail+Calendar server in path 2. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 47 ++++- nixos/pkgs/hara-gmail-mcp/default.nix | 22 +++ nixos/pkgs/hara-gmail-mcp/module.nix | 71 +++++++ nixos/pkgs/hara-gmail-mcp/pyproject.toml | 18 ++ .../src/hara_gmail_mcp/__init__.py | 0 .../src/hara_gmail_mcp/__main__.py | 6 + .../src/hara_gmail_mcp/accounts.py | 112 +++++++++++ .../src/hara_gmail_mcp/imap_client.py | 184 ++++++++++++++++++ .../src/hara_gmail_mcp/server.py | 102 ++++++++++ 9 files changed, 559 insertions(+), 3 deletions(-) create mode 100644 nixos/pkgs/hara-gmail-mcp/default.nix create mode 100644 nixos/pkgs/hara-gmail-mcp/module.nix create mode 100644 nixos/pkgs/hara-gmail-mcp/pyproject.toml create mode 100644 nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__init__.py create mode 100644 nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__main__.py create mode 100644 nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/accounts.py create mode 100644 nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py create mode 100644 nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index e89f231..a298360 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -2,13 +2,27 @@ { config, lib, pkgs, ... }: let - # Telegram user ID(s) — gitignored, not committed to public repo. + # Telegram user ID(s) - gitignored, not committed to public repo. # Create openclaw-allow-from.nix with e.g.: [ 12345678 ] allowFromPath = ./openclaw-allow-from.nix; openclawAllowFrom = if builtins.pathExists allowFromPath then import allowFromPath else [ ]; + + haraGmailMcp = pkgs.callPackage ../pkgs/hara-gmail-mcp { }; + haraMcpServersJson = builtins.toJSON { + mcpServers = { + gmail = { + command = "${haraGmailMcp}/bin/hara-gmail-mcp"; + args = [ ]; + env = { }; + }; + }; + }; in { - imports = [ ./phantom-ship-hardware.nix ]; + imports = [ + ./phantom-ship-hardware.nix + ../pkgs/hara-gmail-mcp/module.nix + ]; networking.hostName = "phantom-ship"; networking.useDHCP = lib.mkDefault true; @@ -140,7 +154,7 @@ in # claude needs a PTY; wrap with script(1). /dev/null discards the typescript. # Permission bypass lives in ~/.claude/settings.json (permissions.defaultMode) # — using the CLI flag triggers an interactive warning dialog at startup. - ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official" /dev/null''; + ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official --mcp-config /etc/hara/mcp-servers.json" /dev/null''; Restart = "always"; RestartSec = 5; }; @@ -152,6 +166,33 @@ in "d /var/lib/openclaw/repos 0750 openclaw openclaw - -" ]; + # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 + # Gmail+Calendar server in path 2. + services.hara-gmail-mcp = { + enable = true; + package = haraGmailMcp; + accounts = [ + { + email = "powerhouseplayer@gmail.com"; + password_file = "/etc/openclaw/gmail-powerhouseplayer-app-password"; + } + { + email = "wildstylewarrior@gmail.com"; + password_file = "/etc/openclaw/gmail-wildstylewarrior-app-password"; + } + { + email = "danielth95@gmail.com"; + password_file = "/etc/openclaw/gmail-danielth95-app-password"; + } + ]; + }; + + # MCP server registry consumed by claude-channels via --mcp-config. + environment.etc."hara/mcp-servers.json" = { + text = haraMcpServersJson; + mode = "0644"; + }; + # Git config for the openclaw user: credential helper reads PAT from file. # PAT (not in repo): /etc/openclaw/github-token (fine-grained, scoped to specific repos) environment.etc."openclaw/gitconfig" = { diff --git a/nixos/pkgs/hara-gmail-mcp/default.nix b/nixos/pkgs/hara-gmail-mcp/default.nix new file mode 100644 index 0000000..e82523e --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/default.nix @@ -0,0 +1,22 @@ +# Gmail MCP server for Hara. +# +# Path 1 implementation: IMAP for read/sort, SMTP for reply. +# Slated for replacement by an OAuth2 + Gmail API + Calendar API server later. +{ python3Packages }: + +python3Packages.buildPythonApplication { + pname = "hara-gmail-mcp"; + version = "0.1.0"; + pyproject = true; + src = ./.; + nativeBuildInputs = [ python3Packages.setuptools ]; + propagatedBuildInputs = [ python3Packages.mcp ]; + + # The server is launched via stdio by Claude Code; no tests yet. + doCheck = false; + + meta = { + description = "Gmail MCP server for Hara (IMAP+SMTP, throwaway pre-OAuth2)"; + mainProgram = "hara-gmail-mcp"; + }; +} diff --git a/nixos/pkgs/hara-gmail-mcp/module.nix b/nixos/pkgs/hara-gmail-mcp/module.nix new file mode 100644 index 0000000..5c9ae38 --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/module.nix @@ -0,0 +1,71 @@ +# NixOS module for the Hara Gmail MCP server. +# +# Generates /etc/hara/gmail-accounts.json from declarative options and +# exposes the server binary through the dotfiles flake's pkgs set. Wiring +# the server into the claude-channels systemd service ExecStart is done +# by the host (phantom-ship.nix) so this module stays composable. +{ config, lib, pkgs, ... }: + +let + cfg = config.services.hara-gmail-mcp; + package = pkgs.callPackage ./. { }; + accountsJson = builtins.toJSON { + accounts = map (a: { + inherit (a) email password_file; + imap_host = a.imapHost; + imap_port = a.imapPort; + smtp_host = a.smtpHost; + smtp_port = a.smtpPort; + }) cfg.accounts; + }; +in +{ + options.services.hara-gmail-mcp = { + enable = lib.mkEnableOption "Hara Gmail MCP server (IMAP+SMTP)"; + + package = lib.mkOption { + type = lib.types.package; + default = package; + description = "The hara-gmail-mcp package to use."; + }; + + accounts = lib.mkOption { + description = "Gmail accounts the MCP server should expose."; + type = lib.types.listOf (lib.types.submodule { + options = { + email = lib.mkOption { + type = lib.types.str; + example = "user@example.com"; + }; + password_file = lib.mkOption { + type = lib.types.path; + description = "Path to the file containing the IMAP/SMTP app password."; + }; + imapHost = lib.mkOption { + type = lib.types.str; + default = "imap.gmail.com"; + }; + imapPort = lib.mkOption { + type = lib.types.port; + default = 993; + }; + smtpHost = lib.mkOption { + type = lib.types.str; + default = "smtp.gmail.com"; + }; + smtpPort = lib.mkOption { + type = lib.types.port; + default = 465; + }; + }; + }); + }; + }; + + config = lib.mkIf cfg.enable { + environment.etc."hara/gmail-accounts.json" = { + text = accountsJson; + mode = "0644"; + }; + }; +} diff --git a/nixos/pkgs/hara-gmail-mcp/pyproject.toml b/nixos/pkgs/hara-gmail-mcp/pyproject.toml new file mode 100644 index 0000000..b2a985e --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "hara-gmail-mcp" +version = "0.1.0" +description = "Gmail MCP server for Hara (IMAP+SMTP, throwaway pre-OAuth2)" +requires-python = ">=3.11" +dependencies = [ + "mcp>=1.0.0", +] + +[project.scripts] +hara-gmail-mcp = "hara_gmail_mcp.__main__:main" + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__init__.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__main__.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__main__.py new file mode 100644 index 0000000..6372ee6 --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__main__.py @@ -0,0 +1,6 @@ +"""Entry point for `python -m hara_gmail_mcp` and the `hara-gmail-mcp` script.""" +from .server import main + + +if __name__ == "__main__": + main() diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/accounts.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/accounts.py new file mode 100644 index 0000000..ea3fa7d --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/accounts.py @@ -0,0 +1,112 @@ +"""Account config loader. + +Reads a JSON file (default: /etc/hara/gmail-accounts.json) listing the Gmail +accounts Hara can act on, and the path to each account's IMAP/SMTP app +password. Passwords are loaded once via `sudo -n cat` because the password +files are root:991 0640 and the MCP server process runs as `danny`. The +result is cached in memory for the process lifetime. + +Schema: + { + "accounts": [ + { + "email": "user@example.com", + "password_file": "/etc/openclaw/gmail-user-app-password", + "imap_host": "imap.gmail.com", + "imap_port": 993, + "smtp_host": "smtp.gmail.com", + "smtp_port": 465 + } + ] + } +""" +from __future__ import annotations + +import json +import os +import shutil +import subprocess +from dataclasses import dataclass +from pathlib import Path + +DEFAULT_CONFIG_PATH = "/etc/hara/gmail-accounts.json" +# NixOS keeps the setuid sudo wrapper at /run/wrappers/bin; non-NixOS distros +# put it in /usr/bin or /bin. We try $PATH first, then fall back to these. +_SUDO_FALLBACKS = ["/run/wrappers/bin/sudo", "/usr/bin/sudo", "/bin/sudo"] + + +def _find_sudo() -> str: + found = shutil.which("sudo") + if found: + return found + for candidate in _SUDO_FALLBACKS: + if Path(candidate).exists(): + return candidate + raise RuntimeError( + "sudo not found on PATH or in known locations; " + "cannot read group-restricted password files" + ) + + +@dataclass(frozen=True) +class Account: + email: str + password_file: str + imap_host: str + imap_port: int + smtp_host: str + smtp_port: int + + +class AccountStore: + """Holds account metadata and lazily resolves passwords on first use.""" + + def __init__(self, accounts: list[Account]) -> None: + self._accounts = {a.email: a for a in accounts} + self._password_cache: dict[str, str] = {} + + @classmethod + def from_config_file(cls, path: str | os.PathLike[str] | None = None) -> "AccountStore": + config_path = Path(path or os.environ.get("HARA_GMAIL_CONFIG", DEFAULT_CONFIG_PATH)) + with config_path.open() as f: + data = json.load(f) + accounts = [ + Account( + email=a["email"], + password_file=a["password_file"], + imap_host=a.get("imap_host", "imap.gmail.com"), + imap_port=int(a.get("imap_port", 993)), + smtp_host=a.get("smtp_host", "smtp.gmail.com"), + smtp_port=int(a.get("smtp_port", 465)), + ) + for a in data.get("accounts", []) + ] + return cls(accounts) + + def emails(self) -> list[str]: + return list(self._accounts.keys()) + + def get(self, email: str) -> Account: + try: + return self._accounts[email] + except KeyError: + raise ValueError(f"Unknown account: {email!r}. Configured: {self.emails()}") + + def password_for(self, email: str) -> str: + if email in self._password_cache: + return self._password_cache[email] + account = self.get(email) + # Prefer direct read if the file is reachable (e.g. after path 2 + # migration where the daemon owns its own creds), fall back to + # `sudo -n cat` for the current /etc/openclaw/ layout. + try: + value = Path(account.password_file).read_text().strip() + except PermissionError: + value = subprocess.check_output( + [_find_sudo(), "-n", "cat", account.password_file], + text=True, + ).strip() + if not value: + raise RuntimeError(f"Empty password file for {email}: {account.password_file}") + self._password_cache[email] = value + return value diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py new file mode 100644 index 0000000..34f2e29 --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py @@ -0,0 +1,184 @@ +"""Minimal IMAP wrapper over stdlib imaplib. + +One short-lived IMAP connection per call. Good enough for v1; if latency +hurts when Hara fans out across three accounts in a summary, swap to a +connection pool keyed by account email. +""" +from __future__ import annotations + +import email +import email.policy +import imaplib +from contextlib import contextmanager +from dataclasses import dataclass, field +from email.message import EmailMessage +from typing import Iterator + +from .accounts import Account, AccountStore + + +@dataclass +class MessageSummary: + uid: str + subject: str + sender: str + date: str + snippet: str = "" + flags: list[str] = field(default_factory=list) + + +@dataclass +class FullMessage: + uid: str + subject: str + sender: str + to: str + date: str + body_text: str + body_html: str + flags: list[str] = field(default_factory=list) + + +@contextmanager +def _open(account: Account, password: str, mailbox: str = "INBOX") -> Iterator[imaplib.IMAP4_SSL]: + conn = imaplib.IMAP4_SSL(account.imap_host, account.imap_port) + try: + conn.login(account.email, password) + # SELECT for read-write, EXAMINE for read-only. Use SELECT so we can + # add/remove flags later (label/archive). Most reads still tolerate + # the implicit \Seen behaviour Gmail applies; we set PEEK below. + typ, _ = conn.select(mailbox) + if typ != "OK": + raise RuntimeError(f"SELECT {mailbox} failed for {account.email}") + yield conn + finally: + try: + conn.logout() + except Exception: + pass + + +def _decode_header(raw: str | None) -> str: + if not raw: + return "" + parts = email.header.decode_header(raw) + out = [] + for chunk, enc in parts: + if isinstance(chunk, bytes): + try: + out.append(chunk.decode(enc or "utf-8", errors="replace")) + except LookupError: + out.append(chunk.decode("utf-8", errors="replace")) + else: + out.append(chunk) + return "".join(out) + + +def list_inbox( + store: AccountStore, + email_addr: str, + limit: int = 20, + mailbox: str = "INBOX", +) -> list[MessageSummary]: + account = store.get(email_addr) + password = store.password_for(email_addr) + with _open(account, password, mailbox) as conn: + typ, data = conn.uid("search", None, "ALL") + if typ != "OK": + raise RuntimeError(f"SEARCH ALL failed for {email_addr}") + uids = data[0].split()[-limit:][::-1] # most recent first + return [_fetch_summary(conn, uid.decode()) for uid in uids] + + +def search( + store: AccountStore, + email_addr: str, + query: str, + limit: int = 20, + mailbox: str = "INBOX", +) -> list[MessageSummary]: + """Run an IMAP SEARCH. `query` is a raw IMAP search expression, e.g. + `FROM alice@example.com`, `UNSEEN`, `SUBJECT "invoice"`, `SINCE 1-Jan-2026`. + """ + account = store.get(email_addr) + password = store.password_for(email_addr) + with _open(account, password, mailbox) as conn: + typ, data = conn.uid("search", None, query) + if typ != "OK": + raise RuntimeError(f"SEARCH {query!r} failed for {email_addr}") + uids = data[0].split()[-limit:][::-1] + return [_fetch_summary(conn, uid.decode()) for uid in uids] + + +def read_email( + store: AccountStore, + email_addr: str, + uid: str, + mailbox: str = "INBOX", +) -> FullMessage: + account = store.get(email_addr) + password = store.password_for(email_addr) + with _open(account, password, mailbox) as conn: + # BODY.PEEK[] avoids setting \Seen automatically. + typ, data = conn.uid("fetch", uid, "(FLAGS BODY.PEEK[])") + if typ != "OK" or not data or data[0] is None: + raise RuntimeError(f"FETCH uid={uid} failed for {email_addr}") + meta, raw = data[0] + flags = _parse_flags(meta.decode() if isinstance(meta, bytes) else meta) + msg: EmailMessage = email.message_from_bytes(raw, policy=email.policy.default) + body_text = "" + body_html = "" + if msg.is_multipart(): + for part in msg.walk(): + ctype = part.get_content_type() + if ctype == "text/plain" and not body_text: + body_text = part.get_content() + elif ctype == "text/html" and not body_html: + body_html = part.get_content() + else: + ctype = msg.get_content_type() + if ctype == "text/html": + body_html = msg.get_content() + else: + body_text = msg.get_content() + return FullMessage( + uid=uid, + subject=_decode_header(msg["Subject"]), + sender=_decode_header(msg["From"]), + to=_decode_header(msg["To"]), + date=_decode_header(msg["Date"]), + body_text=body_text, + body_html=body_html, + flags=flags, + ) + + +def _fetch_summary(conn: imaplib.IMAP4_SSL, uid: str) -> MessageSummary: + typ, data = conn.uid( + "fetch", + uid, + "(FLAGS BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)])", + ) + if typ != "OK" or not data or data[0] is None: + return MessageSummary(uid=uid, subject="(fetch failed)", sender="", date="") + meta, raw = data[0] + flags = _parse_flags(meta.decode() if isinstance(meta, bytes) else meta) + headers = email.message_from_bytes(raw, policy=email.policy.default) + return MessageSummary( + uid=uid, + subject=_decode_header(headers["Subject"]), + sender=_decode_header(headers["From"]), + date=_decode_header(headers["Date"]), + flags=flags, + ) + + +def _parse_flags(meta: str) -> list[str]: + # meta looks like: b' (FLAGS (\\Seen \\Answered) BODY[...] {1234}' + start = meta.find("FLAGS (") + if start < 0: + return [] + end = meta.find(")", start) + if end < 0: + return [] + return meta[start + len("FLAGS (") : end].split() diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py new file mode 100644 index 0000000..797d41c --- /dev/null +++ b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py @@ -0,0 +1,102 @@ +"""Hara Gmail MCP server. + +Exposes a small toolset for reading and (later) replying to mail across +the configured Gmail accounts. v1 ships read-only tools; reply/archive/label +follow once Hara is using these reliably. + +Tools: + list_accounts() list configured accounts + list_inbox(email, limit) recent messages from an account + search(email, query, limit) IMAP SEARCH wrapper + read_email(email, uid) full body of one message +""" +from __future__ import annotations + +import json +import logging +import os +import sys +from dataclasses import asdict + +from mcp.server.fastmcp import FastMCP + +from .accounts import AccountStore +from .imap_client import list_inbox, read_email, search + +logger = logging.getLogger("hara_gmail_mcp") + +mcp = FastMCP("hara-gmail-mcp") +_store: AccountStore | None = None + + +def _get_store() -> AccountStore: + global _store + if _store is None: + _store = AccountStore.from_config_file() + return _store + + +@mcp.tool() +def list_accounts() -> list[str]: + """Return the email addresses of all Gmail accounts Hara can access.""" + return _get_store().emails() + + +@mcp.tool() +def gmail_list_inbox(email: str, limit: int = 20) -> str: + """List the most recent messages in INBOX for the given account. + + Args: + email: which configured account to read (use list_accounts to see options) + limit: max number of messages to return, newest first (default 20, cap 100) + + Returns: + JSON list of {uid, subject, sender, date, flags}. + """ + limit = max(1, min(int(limit), 100)) + msgs = list_inbox(_get_store(), email, limit=limit) + return json.dumps([asdict(m) for m in msgs], ensure_ascii=False) + + +@mcp.tool() +def gmail_search(email: str, query: str, limit: int = 20) -> str: + """Run an IMAP SEARCH against the given account's INBOX. + + Args: + email: which configured account to search + query: raw IMAP search expression, e.g. 'UNSEEN', 'FROM alice@x.com', + 'SUBJECT "invoice"', 'SINCE 1-Jan-2026'. Quote arguments as needed. + limit: max results (default 20, cap 100) + + Returns: + JSON list of {uid, subject, sender, date, flags}. + """ + limit = max(1, min(int(limit), 100)) + msgs = search(_get_store(), email, query=query, limit=limit) + return json.dumps([asdict(m) for m in msgs], ensure_ascii=False) + + +@mcp.tool() +def gmail_read_email(email: str, uid: str) -> str: + """Fetch the full body of one message by IMAP UID. + + Args: + email: which configured account + uid: the message UID (returned by gmail_list_inbox or gmail_search) + + Returns: + JSON object with subject, sender, to, date, body_text, body_html, flags. + BODY.PEEK is used so reading does not auto-mark the message as seen. + """ + msg = read_email(_get_store(), email, uid=uid) + return json.dumps(asdict(msg), ensure_ascii=False) + + +def main() -> None: + logging.basicConfig( + level=os.environ.get("HARA_GMAIL_LOG_LEVEL", "INFO"), + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + stream=sys.stderr, + ) + logger.info("hara-gmail-mcp starting") + mcp.run() From a7dd6284d894639fe816e96b4f7398f04177b067 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 06:25:54 +0200 Subject: [PATCH 114/185] phantom-ship: add Caddy + shelfish FastAPI service Caddy fronts 80/443 with auto-Let's-Encrypt; reverse-proxies shelfish.dannydannydanny.me to the local shelfish service on 127.0.0.1:8081. ACME issues the cert once the subdomain A-records to this host's static IP. Shelfish service mirrors shipyard's pattern: nix-built python env, SHIPYARD_BOT_TOKEN_FILE pointed at the existing secret, DB stored outside the rsynced code dir at ~/.local/share/shelfish/ so deploys don't clobber state. Code itself is rsync'd from ~/python-projects/27_shelfish/ to /home/danny/shelfish/ (same convention as shipyard). Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index a298360..123c6e9 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -248,6 +248,52 @@ in }; }; + # Caddy reverse proxy in front of the local HTTP services (shelfish, etc). + # Auto-Let's-Encrypt; ACME requires the subdomain to A-record to this host. + networking.firewall.allowedTCPPorts = [ 80 443 ]; + services.caddy = { + enable = true; + email = "powerhouseplayer@gmail.com"; + virtualHosts."shelfish.dannydannydanny.me".extraConfig = '' + reverse_proxy 127.0.0.1:8081 + ''; + }; + + # Shelfish — Goodreads-flavoured book club Mini App. + # Code deployed out-of-band via rsync to /home/danny/shelfish/ + # (staying in-tree in ~/python-projects/27_shelfish/ until spun out). + # Auth: validates Telegram WebApp initData against shipyard's bot token + # (the bot that publishes shelfish via shipyard's project list). + # DB lives outside the rsynced code dir so deploys don't clobber state. + systemd.tmpfiles.rules = (lib.mkAfter [ + "d /home/danny/.local/share/shelfish 0755 danny users - -" + ]); + systemd.services.shelfish = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + httpx + python-telegram-bot + ]); + in { + description = "Shelfish FastAPI server (book club Mini App)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + SH_DB_PATH = "/home/danny/.local/share/shelfish/shelfish.db"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/shelfish"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host 127.0.0.1 --port 8081"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From d787b0ea4836e8ab2acd27ffc7697a7dab787a8e Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 06:27:21 +0200 Subject: [PATCH 115/185] phantom-ship: merge shelfish data dir into existing tmpfiles rules Fixes nixos-rebuild error: systemd.tmpfiles.rules was set twice. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 123c6e9..ea44e0e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -160,10 +160,12 @@ in }; }; - # OpenClaw gateway needs write access to its config dir and repo clones. + # OpenClaw gateway needs write access to its config dir and repo clones; + # shelfish wants its DB outside the rsynced code dir. systemd.tmpfiles.rules = [ "d /etc/openclaw 0775 root openclaw - -" "d /var/lib/openclaw/repos 0750 openclaw openclaw - -" + "d /home/danny/.local/share/shelfish 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -265,9 +267,7 @@ in # Auth: validates Telegram WebApp initData against shipyard's bot token # (the bot that publishes shelfish via shipyard's project list). # DB lives outside the rsynced code dir so deploys don't clobber state. - systemd.tmpfiles.rules = (lib.mkAfter [ - "d /home/danny/.local/share/shelfish 0755 danny users - -" - ]); + # (tmpfiles rule for the DB dir is bundled into the OpenClaw block above.) systemd.services.shelfish = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ fastapi From 2aec4d4d5ecf8c1cefd98cc0c1b40f8a734ae001 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 06:29:48 +0200 Subject: [PATCH 116/185] shelfish: front via vps-relay (don't expose phantom-ship public IP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit added Caddy directly on phantom-ship and opened ports 80/443 — that would have exposed the home connection's public IP via DNS. Reverting that and using the existing relay pattern instead: vps-relay (Hetzner) terminates public TLS and reverse-proxies over ZeroTier to phantom-ship's ZT IPv6 on 8081. phantom-ship now just runs shelfish.service bound to 127.0.0.1:8081; it accepts connections only from the ZT mesh interface (since caddy/firewall changes are gone, the only listeners are the existing trusted-interface ones plus this loopback). vps-relay gets a third virtualHost alongside navidrome and bbbot. DNS: shelfish.dannydannydanny.me → 89.167.39.251 (vps-relay public IP), NOT phantom-ship's home IP. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index ea44e0e..f870929 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -250,18 +250,10 @@ in }; }; - # Caddy reverse proxy in front of the local HTTP services (shelfish, etc). - # Auto-Let's-Encrypt; ACME requires the subdomain to A-record to this host. - networking.firewall.allowedTCPPorts = [ 80 443 ]; - services.caddy = { - enable = true; - email = "powerhouseplayer@gmail.com"; - virtualHosts."shelfish.dannydannydanny.me".extraConfig = '' - reverse_proxy 127.0.0.1:8081 - ''; - }; - # Shelfish — Goodreads-flavoured book club Mini App. + # Public traffic comes through vps-relay's Caddy → ZeroTier → here. + # See vps-relay.nix for the public-facing virtualHost. We never expose + # this host's IP directly. # Code deployed out-of-band via rsync to /home/danny/shelfish/ # (staying in-tree in ~/python-projects/27_shelfish/ until spun out). # Auth: validates Telegram WebApp initData against shipyard's bot token From 0b20c375b51c332dd84427457d77d05ece75e93e Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 06:30:07 +0200 Subject: [PATCH 117/185] =?UTF-8?q?vps-relay:=20add=20shelfish.dannydannyd?= =?UTF-8?q?anny.me=20vhost=20=E2=86=92=20phantom-ship=20ZT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/vps-relay.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index f7bf7b0..6ace54b 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -101,6 +101,10 @@ "bbbot.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8080 ''; + # Shelfish — phantom-ship's ZT IPv6. + "shelfish.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8081 + ''; }; }; From f599a76aba1a7924a09f1d4e357bfea787643455 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 06:39:57 +0200 Subject: [PATCH 118/185] phantom-ship: open shelfish (:8081) on ZT iface, bind 0.0.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shelfish was only listening on 127.0.0.1 — vps-relay's Caddy couldn't reach it over the ZT mesh. Bind 0.0.0.0 and allow 8081 inbound on \`zt+\` interfaces (not the global firewall — same pattern sunken-ship uses for bbbot). Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index f870929..87efe78 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -48,6 +48,11 @@ in }; networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; + # Shelfish HTTP (8081) is reachable only over the ZeroTier mesh — the + # vps-relay Caddy reverse-proxies into it. Same pattern as sunken-ship's + # bbbot. Not in global allowedTCPPorts, so the WAN side stays closed. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8081 ]; + hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware boot.kernelParams = [ "consoleblank=60" ]; # blank TTY after 60s to reduce burn-in @@ -279,7 +284,7 @@ in }; serviceConfig = { WorkingDirectory = "/home/danny/shelfish"; - ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host 127.0.0.1 --port 8081"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host 0.0.0.0 --port 8081"; Restart = "on-failure"; RestartSec = 10; User = "danny"; From 8056e510c58122568d1f218347c871d9d77e5cd0 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 06:41:04 +0200 Subject: [PATCH 119/185] phantom-ship: bind shelfish to '::' so it listens on both IPv4 and IPv6 ZT mesh addresses are IPv6; uvicorn on 0.0.0.0 only listens on IPv4 so vps-relay's caddy got 'connection refused' over the mesh. --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 87efe78..9df8375 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -284,7 +284,7 @@ in }; serviceConfig = { WorkingDirectory = "/home/danny/shelfish"; - ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host 0.0.0.0 --port 8081"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8081"; Restart = "on-failure"; RestartSec = 10; User = "danny"; From 4d2e40455d412a71acae8af0657492042635942d Mon Sep 17 00:00:00 2001 From: Hara Date: Sun, 3 May 2026 07:14:42 +0200 Subject: [PATCH 120/185] hara-gmail-mcp: add mark_read and archive tools (v0.2.0) Adds two write-capable IMAP tools: - gmail_mark_read: sets \Seen flag on a message - gmail_archive: copies to [Gmail]/All Mail and removes from INBOX The IMAP connection already used SELECT (read-write mode); this just exposes the mutation surface through MCP. Co-Authored-By: Claude Sonnet 4.6 --- nixos/pkgs/hara-gmail-mcp/default.nix | 2 +- nixos/pkgs/hara-gmail-mcp/pyproject.toml | 2 +- .../src/hara_gmail_mcp/imap_client.py | 28 +++++++++++++ .../src/hara_gmail_mcp/server.py | 39 +++++++++++++++++-- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/nixos/pkgs/hara-gmail-mcp/default.nix b/nixos/pkgs/hara-gmail-mcp/default.nix index e82523e..6d62d10 100644 --- a/nixos/pkgs/hara-gmail-mcp/default.nix +++ b/nixos/pkgs/hara-gmail-mcp/default.nix @@ -6,7 +6,7 @@ python3Packages.buildPythonApplication { pname = "hara-gmail-mcp"; - version = "0.1.0"; + version = "0.2.0"; pyproject = true; src = ./.; nativeBuildInputs = [ python3Packages.setuptools ]; diff --git a/nixos/pkgs/hara-gmail-mcp/pyproject.toml b/nixos/pkgs/hara-gmail-mcp/pyproject.toml index b2a985e..fb1db6d 100644 --- a/nixos/pkgs/hara-gmail-mcp/pyproject.toml +++ b/nixos/pkgs/hara-gmail-mcp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hara-gmail-mcp" -version = "0.1.0" +version = "0.2.0" description = "Gmail MCP server for Hara (IMAP+SMTP, throwaway pre-OAuth2)" requires-python = ">=3.11" dependencies = [ diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py index 34f2e29..de204e6 100644 --- a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py +++ b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py @@ -153,6 +153,34 @@ def read_email( ) +def mark_read( + store: AccountStore, + email_addr: str, + uid: str, + mailbox: str = "INBOX", +) -> None: + """Mark a message as read by adding the \\Seen flag.""" + account = store.get(email_addr) + password = store.password_for(email_addr) + with _open(account, password, mailbox) as conn: + conn.uid("store", uid, "+FLAGS", r"(\Seen)") + + +def archive( + store: AccountStore, + email_addr: str, + uid: str, + mailbox: str = "INBOX", +) -> None: + """Archive a message: copy to All Mail then delete from INBOX.""" + account = store.get(email_addr) + password = store.password_for(email_addr) + with _open(account, password, mailbox) as conn: + conn.uid("copy", uid, "[Gmail]/All Mail") + conn.uid("store", uid, "+FLAGS", r"(\Deleted)") + conn.expunge() + + def _fetch_summary(conn: imaplib.IMAP4_SSL, uid: str) -> MessageSummary: typ, data = conn.uid( "fetch", diff --git a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py index 797d41c..0310786 100644 --- a/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py +++ b/nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py @@ -1,14 +1,15 @@ """Hara Gmail MCP server. -Exposes a small toolset for reading and (later) replying to mail across -the configured Gmail accounts. v1 ships read-only tools; reply/archive/label -follow once Hara is using these reliably. +Exposes a small toolset for reading and writing mail across the configured +Gmail accounts. Tools: list_accounts() list configured accounts list_inbox(email, limit) recent messages from an account search(email, query, limit) IMAP SEARCH wrapper read_email(email, uid) full body of one message + mark_read(email, uid) mark a message as read + archive(email, uid) archive a message (remove from INBOX) """ from __future__ import annotations @@ -21,7 +22,7 @@ from dataclasses import asdict from mcp.server.fastmcp import FastMCP from .accounts import AccountStore -from .imap_client import list_inbox, read_email, search +from .imap_client import archive, list_inbox, mark_read, read_email, search logger = logging.getLogger("hara_gmail_mcp") @@ -92,6 +93,36 @@ def gmail_read_email(email: str, uid: str) -> str: return json.dumps(asdict(msg), ensure_ascii=False) +@mcp.tool() +def gmail_mark_read(email: str, uid: str) -> str: + """Mark a message as read (sets the \\Seen flag). + + Args: + email: which configured account + uid: the message UID (returned by gmail_list_inbox or gmail_search) + + Returns: + JSON object with ok and uid. + """ + mark_read(_get_store(), email, uid=uid) + return json.dumps({"ok": True, "uid": uid}) + + +@mcp.tool() +def gmail_archive(email: str, uid: str) -> str: + """Archive a message (copies to All Mail, removes from INBOX). + + Args: + email: which configured account + uid: the message UID (returned by gmail_list_inbox or gmail_search) + + Returns: + JSON object with ok and uid. + """ + archive(_get_store(), email, uid=uid) + return json.dumps({"ok": True, "uid": uid}) + + def main() -> None: logging.basicConfig( level=os.environ.get("HARA_GMAIL_LOG_LEVEL", "INFO"), From 6d9ccf5d4e47d0b760cf49d41d632e3759a75ec2 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 07:26:17 +0200 Subject: [PATCH 121/185] phantom-ship + vps-relay: add scuttle service + vhost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scuttle.service mirrors shelfish — fastapi/uvicorn/httpx/python-telegram-bot plus websockets, runs uvicorn --host :: --port 8082, DB at ~/.local/share/scuttle/scuttle.db (tmpfiles rule + zt+ firewall port added alongside shelfish's). vps-relay gets a fourth virtualHost (scuttle.dannydannydanny.me) reverse-proxying to phantom-ship over ZeroTier. WebSocket upgrade is transparent under Caddy's reverse_proxy. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 42 ++++++++++++++++++++++++++++++++---- nixos/hosts/vps-relay.nix | 5 +++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 9df8375..3a673ca 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -48,10 +48,11 @@ in }; networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; - # Shelfish HTTP (8081) is reachable only over the ZeroTier mesh — the - # vps-relay Caddy reverse-proxies into it. Same pattern as sunken-ship's - # bbbot. Not in global allowedTCPPorts, so the WAN side stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8081 ]; + # Shelfish (:8081) and Scuttle (:8082) are reachable only over the + # ZeroTier mesh — the vps-relay Caddy reverse-proxies into them. Same + # pattern as sunken-ship's bbbot. Not in global allowedTCPPorts, so + # the WAN side stays closed. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8081 8082 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -171,6 +172,7 @@ in "d /etc/openclaw 0775 root openclaw - -" "d /var/lib/openclaw/repos 0750 openclaw openclaw - -" "d /home/danny/.local/share/shelfish 0755 danny users - -" + "d /home/danny/.local/share/scuttle 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -291,6 +293,38 @@ in }; }; + # Scuttle — topdown tilt-to-move multiplayer Mini App. + # Same vps-relay-fronted ZT path as shelfish; binds to :: so the + # ZeroTier IPv6 address can reach it. + # Code rsync'd from ~/python-projects/26_scuttle/ to /home/danny/scuttle/ + # DB at ~/.local/share/scuttle/scuttle.db. + systemd.services.scuttle = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + httpx + websockets + python-telegram-bot + ]); + in { + description = "Scuttle FastAPI + WebSocket game server"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + SC_DB_PATH = "/home/danny/.local/share/scuttle/scuttle.db"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/scuttle"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8082"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 6ace54b..bedf32e 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -105,6 +105,11 @@ "shelfish.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8081 ''; + # Scuttle — same backend, different port. WebSocket upgrade is + # transparent under reverse_proxy. + "scuttle.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8082 + ''; }; }; From 08495161aea0bb05ea4098c79cdf05ea6c910ea2 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 07:58:12 +0200 Subject: [PATCH 122/185] phantom-ship + vps-relay: add bananasimulator service + vhost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bananasimulator.service mirrors shelfish/scuttle (fastapi + uvicorn + httpx + python-telegram-bot). Port 8083. ENV BS_RIPE_MIN_PER_STAGE=2 in prod (30 min total banana lifetime); preview uses 0.5 for fast testing. vps-relay gets a fifth vhost (bananasimulator.dannydannydanny.me) reverse-proxying to phantom-ship over ZeroTier. The shipyard manifest has been pointing at this URL as a placeholder since day one — now it's actually live. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 41 +++++++++++++++++++++++++++++++----- nixos/hosts/vps-relay.nix | 4 ++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 3a673ca..1d138f1 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -48,11 +48,11 @@ in }; networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; - # Shelfish (:8081) and Scuttle (:8082) are reachable only over the - # ZeroTier mesh — the vps-relay Caddy reverse-proxies into them. Same - # pattern as sunken-ship's bbbot. Not in global allowedTCPPorts, so - # the WAN side stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8081 8082 ]; + # Shelfish (:8081), Scuttle (:8082), Bananasimulator (:8083) are + # reachable only over the ZeroTier mesh — the vps-relay Caddy + # reverse-proxies into them. Same pattern as sunken-ship's bbbot. + # Not in global allowedTCPPorts, so the WAN side stays closed. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8081 8082 8083 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -173,6 +173,7 @@ in "d /var/lib/openclaw/repos 0750 openclaw openclaw - -" "d /home/danny/.local/share/shelfish 0755 danny users - -" "d /home/danny/.local/share/scuttle 0755 danny users - -" + "d /home/danny/.local/share/bananasimulator 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -325,6 +326,36 @@ in }; }; + # Bananasimulator — the actual project at https://bananasimulator.dannydannydanny.me + # (was a placeholder in shipyard's apps.json for ages). You ARE a banana. + # Code rsync'd from ~/python-projects/26_bananasimulator/ to /home/danny/bananasimulator/ + systemd.services.bananasimulator = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + httpx + python-telegram-bot + ]); + in { + description = "Bananasimulator FastAPI server"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + BS_DB_PATH = "/home/danny/.local/share/bananasimulator/bananasimulator.db"; + BS_RIPE_MIN_PER_STAGE = "2"; # 2 min/stage → 30 min to compost in production + }; + serviceConfig = { + WorkingDirectory = "/home/danny/bananasimulator"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8083"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index bedf32e..70ec646 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -110,6 +110,10 @@ "scuttle.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8082 ''; + # Bananasimulator — same backend, port 8083. + "bananasimulator.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8083 + ''; }; }; From f419fed7ebcd0d8cf5f040b48c7d9f261b75f997 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 18:56:52 +0200 Subject: [PATCH 123/185] phantom-ship + vps-relay: KomTolk service + vhost (was translate-platform) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KomTolk is the rebranded translate-platform — same Copenhagen translation gigs Mini App, new name. Service on port 8080, mirrors shelfish/scuttle/banana setup. New tmpfiles dir + zt+ firewall opening + caddy vhost. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 37 ++++++++++++++++++++++++++++++++---- nixos/hosts/vps-relay.nix | 4 ++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 1d138f1..bef7db3 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -48,11 +48,11 @@ in }; networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; - # Shelfish (:8081), Scuttle (:8082), Bananasimulator (:8083) are - # reachable only over the ZeroTier mesh — the vps-relay Caddy - # reverse-proxies into them. Same pattern as sunken-ship's bbbot. + # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator + # (:8083) are reachable only over the ZeroTier mesh — the vps-relay + # Caddy reverse-proxies into them. Same pattern as sunken-ship's bbbot. # Not in global allowedTCPPorts, so the WAN side stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8081 8082 8083 ]; + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8080 8081 8082 8083 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -174,6 +174,7 @@ in "d /home/danny/.local/share/shelfish 0755 danny users - -" "d /home/danny/.local/share/scuttle 0755 danny users - -" "d /home/danny/.local/share/bananasimulator 0755 danny users - -" + "d /home/danny/.local/share/komtolk 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -356,6 +357,34 @@ in }; }; + # KomTolk (formerly translate-platform) — Copenhagen translation gigs Mini App. + # Code rsync'd from ~/python-projects/26_komtolk/ to /home/danny/komtolk/ + systemd.services.komtolk = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + httpx + python-telegram-bot + ]); + in { + description = "KomTolk FastAPI server (Copenhagen translation gigs)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + KT_DB_PATH = "/home/danny/.local/share/komtolk/komtolk.db"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/komtolk"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8080"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 70ec646..e94b116 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -114,6 +114,10 @@ "bananasimulator.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8083 ''; + # KomTolk (formerly translate-platform) — same backend, port 8080. + "komtolk.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8080 + ''; }; }; From 3604c086507dc94b204f2179a1d3606f987a2392 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 3 May 2026 19:22:28 +0200 Subject: [PATCH 124/185] phantom-ship: scuttle gets SC_TILES_DIR + tmpfiles for OSM tile cache --- nixos/hosts/phantom-ship.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index bef7db3..7caccf9 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -175,6 +175,7 @@ in "d /home/danny/.local/share/scuttle 0755 danny users - -" "d /home/danny/.local/share/bananasimulator 0755 danny users - -" "d /home/danny/.local/share/komtolk 0755 danny users - -" + "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -309,7 +310,7 @@ in python-telegram-bot ]); in { - description = "Scuttle FastAPI + WebSocket game server"; + description = "Scuttle FastAPI + WebSocket game server (geo: Østerbro)"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; @@ -317,6 +318,7 @@ in environment = { SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; SC_DB_PATH = "/home/danny/.local/share/scuttle/scuttle.db"; + SC_TILES_DIR = "/home/danny/.local/share/scuttle/tiles"; }; serviceConfig = { WorkingDirectory = "/home/danny/scuttle"; From 69d982d0fa9400f757fc8fa7fc7814bc5bbe34e6 Mon Sep 17 00:00:00 2001 From: Hara Date: Mon, 4 May 2026 12:51:33 +0200 Subject: [PATCH 125/185] hara: add morning heartbeat systemd service + timer Daily 08:07 CEST oneshot: runs claude -p with Gmail MCP to check email, sends a morning Telegram ping via Bot API. Persistent timer survives reboots. --- nixos/hosts/phantom-ship.nix | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 7caccf9..13a8dce 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -387,6 +387,48 @@ in }; }; + # Hara morning heartbeat — daily email check + Telegram good-morning ping. + # Runs claude in print mode with the Gmail MCP, then sends output via Bot API. + # Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin). + systemd.services.hara-heartbeat = { + description = "Hara morning heartbeat (email check + Telegram ping)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + path = [ pkgs.claude-code pkgs.curl pkgs.jq ]; + environment = { + HOME = "/home/danny"; + }; + serviceConfig = { + Type = "oneshot"; + User = "danny"; + Group = "users"; + WorkingDirectory = "/home/danny"; + EnvironmentFile = "/etc/claude-channels/env"; + }; + script = '' + set -euo pipefail + CHAT_ID="66070351" + BOT_TOKEN=$(grep '^TELEGRAM_BOT_TOKEN=' /home/danny/.claude/channels/telegram/.env | cut -d= -f2-) + MSG=$(${pkgs.claude-code}/bin/claude -p \ + "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for both accounts (danielth95 and powerhouseplayer) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short morning message for Danny: flag urgent emails if any, otherwise just say good morning. One message, very short, cat energy." \ + --mcp-config /etc/hara/mcp-servers.json \ + 2>/dev/null) + ${pkgs.curl}/bin/curl -sf -X POST \ + "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \ + -H "Content-Type: application/json" \ + -d "{\"chat_id\": $CHAT_ID, \"text\": $(echo "$MSG" | ${pkgs.jq}/bin/jq -Rs .)}" \ + > /dev/null + ''; + }; + + systemd.timers.hara-heartbeat = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "Europe/Copenhagen *-*-* 08:07:00"; + Persistent = true; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 9ad8d71f6194609786b0e8d81f489ceeaaffcced Mon Sep 17 00:00:00 2001 From: Danny Date: Mon, 4 May 2026 18:25:19 +0200 Subject: [PATCH 126/185] phantom-ship: set SHIPYARD_OWNER_ID for owner-only /admin commands --- nixos/hosts/phantom-ship.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 13a8dce..10aa133 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -250,6 +250,8 @@ in path = [ pythonEnv ]; environment = { SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + # Owner-only commands (/admin, /grant, /revoke) — anyone else gets ignored. + SHIPYARD_OWNER_ID = "66070351"; # @DannyDannyDanny }; serviceConfig = { WorkingDirectory = "/home/danny/shipyard"; From c04b463ad002388f8166b217a14ac135427e3f56 Mon Sep 17 00:00:00 2001 From: Hara Date: Mon, 4 May 2026 18:26:57 +0200 Subject: [PATCH 127/185] hara-heartbeat: fix OnCalendar timezone syntax, fire every 4h (08/12/16/20) --- nixos/hosts/phantom-ship.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 10aa133..886b9b1 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -426,7 +426,8 @@ in systemd.timers.hara-heartbeat = { wantedBy = [ "timers.target" ]; timerConfig = { - OnCalendar = "Europe/Copenhagen *-*-* 08:07:00"; + OnCalendar = "08,12,16,20:07"; + Timezone = "Europe/Copenhagen"; Persistent = true; }; }; From e952667623840c15a3e434c281cd06dda4853258 Mon Sep 17 00:00:00 2001 From: Hara Date: Mon, 4 May 2026 18:28:00 +0200 Subject: [PATCH 128/185] hara-heartbeat: shift schedule to 06/10/14/18 Copenhagen --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 886b9b1..91b3d2e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -426,7 +426,7 @@ in systemd.timers.hara-heartbeat = { wantedBy = [ "timers.target" ]; timerConfig = { - OnCalendar = "08,12,16,20:07"; + OnCalendar = "06,10,14,18:07"; Timezone = "Europe/Copenhagen"; Persistent = true; }; From a9bb775b7d8564ee2c11e57d892c36763f3762e4 Mon Sep 17 00:00:00 2001 From: Hara Date: Mon, 4 May 2026 18:56:38 +0200 Subject: [PATCH 129/185] hara-heartbeat: check all 3 Gmail accounts (add wildstylewarrior) --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 91b3d2e..b0698da 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -412,7 +412,7 @@ in CHAT_ID="66070351" BOT_TOKEN=$(grep '^TELEGRAM_BOT_TOKEN=' /home/danny/.claude/channels/telegram/.env | cut -d= -f2-) MSG=$(${pkgs.claude-code}/bin/claude -p \ - "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for both accounts (danielth95 and powerhouseplayer) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short morning message for Danny: flag urgent emails if any, otherwise just say good morning. One message, very short, cat energy." \ + "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for all three accounts (danielth95, powerhouseplayer, wildstylewarrior) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short message for Danny: flag urgent emails if any, otherwise just a brief check-in. One message, very short, cat energy." \ --mcp-config /etc/hara/mcp-servers.json \ 2>/dev/null) ${pkgs.curl}/bin/curl -sf -X POST \ From d0e9b3f9072df17afd58100d68bf1f5592ae1c1c Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 4 May 2026 21:34:13 +0200 Subject: [PATCH 130/185] phantom-ship + vps-relay: Forgejo on git.dannydannydanny.me MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of the de-platform-from-GitHub roadmap (vimwiki/diary/2026-05-03.md). - phantom-ship: services.forgejo bound to 0.0.0.0:3000, sqlite, lfs on, registration disabled, sign-in required. - phantom-ship: add :3000 to the existing zt+ allowedTCPPorts list (joins shelfish/scuttle — never exposed on WAN/Wi-Fi). - vps-relay: Caddy vhost git.dannydannydanny.me reverse-proxies over ZT to phantom-ship:3000. Manual steps before reachable: 1. GoDaddy A record git.dannydannydanny.me -> 89.167.39.251 2. clan machines update phantom-ship && clan machines update vps-relay 3. On phantom-ship: bootstrap admin (registration is disabled) --- nixos/hosts/phantom-ship.nix | 39 ++++++++++++++++++++++++++++++++---- nixos/hosts/vps-relay.nix | 5 +++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index b0698da..bab6b2d 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -49,10 +49,11 @@ in networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator - # (:8083) are reachable only over the ZeroTier mesh — the vps-relay - # Caddy reverse-proxies into them. Same pattern as sunken-ship's bbbot. - # Not in global allowedTCPPorts, so the WAN side stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 8080 8081 8082 8083 ]; + # (:8083), Forgejo (:3000) are reachable only over the ZeroTier mesh — + # the vps-relay Caddy reverse-proxies into them. Same pattern as + # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side + # stays closed. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -331,6 +332,7 @@ in }; }; +<<<<<<< HEAD # Bananasimulator — the actual project at https://bananasimulator.dannydannydanny.me # (was a placeholder in shipyard's apps.json for ages). You ARE a banana. # Code rsync'd from ~/python-projects/26_bananasimulator/ to /home/danny/bananasimulator/ @@ -429,6 +431,35 @@ in OnCalendar = "06,10,14,18:07"; Timezone = "Europe/Copenhagen"; Persistent = true; +======= + # Forgejo — self-hosted Git forge. Phase 1 of the de-platform-from-GitHub + # roadmap (vimwiki/diary/2026-05-03.md). Public URL git.dannydannydanny.me + # is fronted by Caddy on vps-relay reverse-proxying over ZT to :3000 here. + # Auth for now: HTTPS + PAT (osxkeychain credential helper on the Mac). + # SSH disabled in Phase 1; revisit if push-via-https gets annoying. + # Backups: TODO — snapshot /var/lib/forgejo/ once it's up. + services.forgejo = { + enable = true; + database.type = "sqlite3"; # personal scale; one user, plenty + lfs.enable = true; + settings = { + DEFAULT.APP_NAME = "git.dannydannydanny.me"; + server = { + DOMAIN = "git.dannydannydanny.me"; + ROOT_URL = "https://git.dannydannydanny.me/"; + # Bind to all interfaces — firewall above scopes inbound to ZT. + HTTP_ADDR = "0.0.0.0"; + HTTP_PORT = 3000; + DISABLE_SSH = true; + }; + service = { + DISABLE_REGISTRATION = true; # admin-bootstrapped only + REQUIRE_SIGNIN_VIEW = true; # no anonymous browsing + }; + session.COOKIE_SECURE = true; + log.LEVEL = "Info"; + repository.DEFAULT_BRANCH = "main"; +>>>>>>> 0a9124e (phantom-ship + vps-relay: Forgejo on git.dannydannydanny.me) }; }; diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index e94b116..4f40143 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -118,6 +118,11 @@ "komtolk.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8080 ''; + # Forgejo on phantom-ship — Phase 1 of the de-platform-from-GitHub + # roadmap (vimwiki/diary/2026-05-03.md). + "git.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:3000 + ''; }; }; From 4600a8e5ca63c305ef8c037b3d19dbd7b497e52a Mon Sep 17 00:00:00 2001 From: Hara Date: Mon, 4 May 2026 23:23:57 +0200 Subject: [PATCH 131/185] escape-hormuz: add service (port 8090) + escapehormuz.dannydannydanny.me vhost --- nixos/hosts/phantom-ship.nix | 38 ++++++++++++++++++++++++++++++++---- nixos/hosts/vps-relay.nix | 4 ++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index bab6b2d..0465ba0 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -53,7 +53,7 @@ in # the vps-relay Caddy reverse-proxies into them. Same pattern as # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side # stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 ]; + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -176,6 +176,7 @@ in "d /home/danny/.local/share/scuttle 0755 danny users - -" "d /home/danny/.local/share/bananasimulator 0755 danny users - -" "d /home/danny/.local/share/komtolk 0755 danny users - -" + "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" ]; @@ -332,7 +333,6 @@ in }; }; -<<<<<<< HEAD # Bananasimulator — the actual project at https://bananasimulator.dannydannydanny.me # (was a placeholder in shipyard's apps.json for ages). You ARE a banana. # Code rsync'd from ~/python-projects/26_bananasimulator/ to /home/danny/bananasimulator/ @@ -391,6 +391,35 @@ in }; }; + # Escape Hormuz — turn-based boat racing Mini App through the Strait of Hormuz. + # Code rsync'd from ~/python-projects/28_escape_hormuz/ to /home/danny/escape_hormuz/ + # DB at ~/.local/share/escape_hormuz/escape_hormuz.db. + systemd.services.escape-hormuz = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + python-telegram-bot + ]); + in { + description = "Escape Hormuz FastAPI server (turn-based boat race)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + DB_PATH = "/home/danny/.local/share/escape_hormuz/escape_hormuz.db"; + MINIAPP_URL = "https://escapehormuz.dannydannydanny.me"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/escape_hormuz"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8090"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Hara morning heartbeat — daily email check + Telegram good-morning ping. # Runs claude in print mode with the Gmail MCP, then sends output via Bot API. # Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin). @@ -431,7 +460,9 @@ in OnCalendar = "06,10,14,18:07"; Timezone = "Europe/Copenhagen"; Persistent = true; -======= + }; + }; + # Forgejo — self-hosted Git forge. Phase 1 of the de-platform-from-GitHub # roadmap (vimwiki/diary/2026-05-03.md). Public URL git.dannydannydanny.me # is fronted by Caddy on vps-relay reverse-proxying over ZT to :3000 here. @@ -459,7 +490,6 @@ in session.COOKIE_SECURE = true; log.LEVEL = "Info"; repository.DEFAULT_BRANCH = "main"; ->>>>>>> 0a9124e (phantom-ship + vps-relay: Forgejo on git.dannydannydanny.me) }; }; diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 4f40143..d9b3edf 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -123,6 +123,10 @@ "git.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:3000 ''; + # Escape Hormuz — turn-based boat race, port 8090. + "escapehormuz.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8090 + ''; }; }; From 8a91f3db883e48be95c54886877e41a24a8e4488 Mon Sep 17 00:00:00 2001 From: Danny Date: Tue, 5 May 2026 09:39:07 +0200 Subject: [PATCH 132/185] phantom-ship + vps-relay: declare escape-hormuz service + vhost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hara (openclaw) shipped escape_hormuz imperatively — service runs but firewall + Caddy vhost weren't declared, so the public URL didn't resolve and the firewall rule would've been wiped on next dotfiles-rebuild. Bring it under nix: phantom-ship.nix - systemd.services.escape-hormuz on port 8090, binds :: for ZT - 8090 added to zt+ allowedTCPPorts - tmpfiles entry for /home/danny/.local/share/escape_hormuz vps-relay.nix - Caddy vhost escapehormuz.dannydannydanny.me → ZT [::]:8090 --- nixos/hosts/phantom-ship.nix | 38 ++++++++++++++++++++++++++++++++---- nixos/hosts/vps-relay.nix | 2 +- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 0465ba0..d78345f 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -49,10 +49,10 @@ in networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator - # (:8083), Forgejo (:3000) are reachable only over the ZeroTier mesh — - # the vps-relay Caddy reverse-proxies into them. Same pattern as - # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side - # stays closed. + # (:8083), Forgejo (:3000), Escape Hormuz (:8090) are reachable only + # over the ZeroTier mesh — the vps-relay Caddy reverse-proxies into + # them. Same pattern as sunken-ship's bbbot. Not in global + # allowedTCPPorts, so the WAN side stays closed. networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -178,6 +178,7 @@ in "d /home/danny/.local/share/komtolk 0755 danny users - -" "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" + "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -363,6 +364,35 @@ in }; }; + # Escape Hormuz — turn-based boat-race Mini App (Hara's first build). + # Code lives at /home/danny/escape_hormuz/. Same vps-relay-fronted ZT path + # as the others; binds :: so the ZeroTier IPv6 address is reachable. + systemd.services.escape-hormuz = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + python-telegram-bot + ]); + in { + description = "Escape Hormuz FastAPI server (turn-based boat race)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + DB_PATH = "/home/danny/.local/share/escape_hormuz/escape_hormuz.db"; + MINIAPP_URL = "https://escapehormuz.dannydannydanny.me"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/escape_hormuz"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8090"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # KomTolk (formerly translate-platform) — Copenhagen translation gigs Mini App. # Code rsync'd from ~/python-projects/26_komtolk/ to /home/danny/komtolk/ systemd.services.komtolk = let diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index d9b3edf..24c4d73 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -123,7 +123,7 @@ "git.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:3000 ''; - # Escape Hormuz — turn-based boat race, port 8090. + # Escape Hormuz — turn-based boat-race Mini App, port 8090. "escapehormuz.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8090 ''; From 4e01e62cc0f1cf16630c81744210cb3fc1e2f8f7 Mon Sep 17 00:00:00 2001 From: Danny Date: Tue, 5 May 2026 09:41:17 +0200 Subject: [PATCH 133/185] phantom-ship: dedupe escape-hormuz tmpfiles + service block (rebase artifact) --- nixos/hosts/phantom-ship.nix | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index d78345f..6c81646 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -178,7 +178,6 @@ in "d /home/danny/.local/share/komtolk 0755 danny users - -" "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" - "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -421,35 +420,6 @@ in }; }; - # Escape Hormuz — turn-based boat racing Mini App through the Strait of Hormuz. - # Code rsync'd from ~/python-projects/28_escape_hormuz/ to /home/danny/escape_hormuz/ - # DB at ~/.local/share/escape_hormuz/escape_hormuz.db. - systemd.services.escape-hormuz = let - pythonEnv = pkgs.python3.withPackages (ps: with ps; [ - fastapi - uvicorn - python-telegram-bot - ]); - in { - description = "Escape Hormuz FastAPI server (turn-based boat race)"; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pythonEnv ]; - environment = { - SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; - DB_PATH = "/home/danny/.local/share/escape_hormuz/escape_hormuz.db"; - MINIAPP_URL = "https://escapehormuz.dannydannydanny.me"; - }; - serviceConfig = { - WorkingDirectory = "/home/danny/escape_hormuz"; - ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8090"; - Restart = "on-failure"; - RestartSec = 10; - User = "danny"; - }; - }; - # Hara morning heartbeat — daily email check + Telegram good-morning ping. # Runs claude in print mode with the Gmail MCP, then sends output via Bot API. # Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin). From 7f8badf1d10f04360f11fe344200d358e9213ffa Mon Sep 17 00:00:00 2001 From: Hara Date: Tue, 5 May 2026 14:10:57 +0200 Subject: [PATCH 134/185] =?UTF-8?q?hara-heartbeat:=20plain=20text=20only?= =?UTF-8?q?=20prompt=20=E2=80=94=20no=20markdown=20asterisks=20in=20Telegr?= =?UTF-8?q?am=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 6c81646..de500da 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -443,7 +443,7 @@ in CHAT_ID="66070351" BOT_TOKEN=$(grep '^TELEGRAM_BOT_TOKEN=' /home/danny/.claude/channels/telegram/.env | cut -d= -f2-) MSG=$(${pkgs.claude-code}/bin/claude -p \ - "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for all three accounts (danielth95, powerhouseplayer, wildstylewarrior) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short message for Danny: flag urgent emails if any, otherwise just a brief check-in. One message, very short, cat energy." \ + "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for all three accounts (danielth95, powerhouseplayer, wildstylewarrior) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short message for Danny: flag urgent emails if any, otherwise just a brief check-in. One message, very short, cat energy. IMPORTANT: use plain text only — no markdown, no asterisks, no bold syntax. The message is sent via Telegram Bot API plain text mode." \ --mcp-config /etc/hara/mcp-servers.json \ 2>/dev/null) ${pkgs.curl}/bin/curl -sf -X POST \ From 3de1747e92a41f8aae9809ccbde7f6e0c535245c Mon Sep 17 00:00:00 2001 From: Hara Date: Tue, 5 May 2026 14:22:26 +0200 Subject: [PATCH 135/185] hara-heartbeat: strip markdown asterisks/underscores via sed before sending --- nixos/hosts/phantom-ship.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index de500da..5e267b6 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -427,7 +427,7 @@ in description = "Hara morning heartbeat (email check + Telegram ping)"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; - path = [ pkgs.claude-code pkgs.curl pkgs.jq ]; + path = [ pkgs.claude-code pkgs.curl pkgs.jq pkgs.gnused ]; environment = { HOME = "/home/danny"; }; @@ -443,9 +443,9 @@ in CHAT_ID="66070351" BOT_TOKEN=$(grep '^TELEGRAM_BOT_TOKEN=' /home/danny/.claude/channels/telegram/.env | cut -d= -f2-) MSG=$(${pkgs.claude-code}/bin/claude -p \ - "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for all three accounts (danielth95, powerhouseplayer, wildstylewarrior) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short message for Danny: flag urgent emails if any, otherwise just a brief check-in. One message, very short, cat energy. IMPORTANT: use plain text only — no markdown, no asterisks, no bold syntax. The message is sent via Telegram Bot API plain text mode." \ + "You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for all three accounts (danielth95, powerhouseplayer, wildstylewarrior) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short message for Danny: flag urgent emails if any, otherwise just a brief check-in. One message, very short, cat energy." \ --mcp-config /etc/hara/mcp-servers.json \ - 2>/dev/null) + 2>/dev/null | ${pkgs.gnused}/bin/sed 's/\*\*//g; s/\*//g; s/__//g; s/_//g') ${pkgs.curl}/bin/curl -sf -X POST \ "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \ -H "Content-Type: application/json" \ From 1744d776e29aaf4a3b569efd8a1922e93afe496d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 5 May 2026 21:10:49 +0200 Subject: [PATCH 136/185] sunken-ship: mulbo-server systemd service + pull timer + ZT port 8091 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 5 of the mulbo Navidrome-pivot — companion HTTP service co- located with Navidrome that owns uploads + the dedup index + the real on-disk folder layout (which Navidrome's tag-virtual API can't expose). Wire spec lives in the mulbo repo at 20_mulbo/SERVER_API.md. Runs as `danny` so writes pass through to /home/danny/music/mulbo- uploads via the existing /srv/music ro bind-mount — no mount changes needed. Bound to [::]:8091 (8090 was taken by escape-hormuz on phantom-ship); firewall scopes it to the ZT mesh, same trick bbbot uses on 8080. Pulls the python-projects repo via SSH using sunken-ship's id_ed25519 (registered as a read-only deploy key on the repo). Auto-pull timer runs every 15 min, offset from fitness-bot-pull and dotfiles-rebuild. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 76 ++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index e305c03..663a242 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -95,7 +95,10 @@ networking.firewall = { allowedTCPPorts = [ 7000 7001 7100 4533 ]; allowedUDPPorts = [ 5353 6000 6001 7011 ]; - interfaces."zt+".allowedTCPPorts = [ 8080 ]; + # 8080: bbbot HTTP backend. 8091: mulbo-server companion service. + # Both ZT-only — see vps-relay.nix for reverse proxy if exposing + # publicly later. + interfaces."zt+".allowedTCPPorts = [ 8080 8091 ]; }; # Navidrome — self-hosted music streaming server (Subsonic API). @@ -200,6 +203,77 @@ timerConfig.RandomizedDelaySec = "2min"; }; + # Mulbo companion service (Phase 5: uploads + dedup index + folders). + # Wire spec: ~danny/python-projects/20_mulbo/SERVER_API.md. + # Bootstrap (one-time): git clone git@github.com:DannyDannyDanny/python-projects.git /home/danny/python-projects + # (uses sunken-ship's id_ed25519 as a read-only deploy key on the repo) + # ZT-only via the firewall rule above (port 8091). Runs as `danny` so + # writes go through to /home/danny/music/mulbo-uploads, which Navidrome + # reads via the existing /srv/music ro bind-mount with no mount changes. + systemd.tmpfiles.rules = [ + "d /home/danny/music/mulbo-uploads 0755 danny users -" + ]; + + systemd.services.mulbo-server = let + pythonEnv = pkgs.python312.withPackages (ps: with ps; [ + fastapi + uvicorn + python-multipart + ]); + in { + description = "Mulbo companion service (uploads, dedup, folders)"; + after = [ "network-online.target" "navidrome.service" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads"; + MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; + MULBO_NAVIDROME_URL = "http://localhost:4533"; + MULBO_BIND_HOST = "::"; + MULBO_BIND_PORT = "8091"; + PYTHONUNBUFFERED = "1"; # immediate journal output + }; + serviceConfig = { + WorkingDirectory = "/home/danny/python-projects/20_mulbo"; + ExecStart = "${pythonEnv}/bin/python mulbo_server/app.py"; + Restart = "on-failure"; + RestartSec = 5; + User = "danny"; + StateDirectory = "mulbo-server"; # /var/lib/mulbo-server, owned by danny + }; + }; + + # Pull mulbo (python-projects repo) and restart service if repo changed. + # Repo lives at /home/danny/python-projects (must be cloned manually first + # — see bootstrap note above). DBs/state live in /var/lib/mulbo-server, + # not in the repo, so they survive pulls. + systemd.services.mulbo-pull = { + description = "Pull mulbo repo and restart mulbo-server if changed"; + path = with pkgs; [ git systemd ]; + environment = { + GIT_CONFIG_COUNT = "1"; + GIT_CONFIG_KEY_0 = "safe.directory"; + GIT_CONFIG_VALUE_0 = "/home/danny/python-projects"; + }; + script = '' + set -euo pipefail + cd /home/danny/python-projects + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then + exit 0 + fi + git pull origin main + systemctl restart mulbo-server + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.mulbo-pull = { + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*-*-* *:11/15:00"; # every 15 min, offset from fitness-bot-pull and dotfiles-rebuild + timerConfig.RandomizedDelaySec = "2min"; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 4debab6f69e097ca3b5a96cbcb9298cd3c1ebafa Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 5 May 2026 21:22:37 +0200 Subject: [PATCH 137/185] sunken-ship: mulbo-server creds via EnvironmentFile + MULBO_MUSIC_ROOT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds: - MULBO_MUSIC_ROOT=/srv/music (for the /folders fs walk) - EnvironmentFile=/home/danny/.secrets/mulbo-server-navidrome (creds for Subsonic API calls — file is mode 600, owned by danny, not in source control) Required for the new /folders endpoint and the upcoming POST /tracks which needs to call search3.view + startScan.view. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 663a242..840062b 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -228,6 +228,7 @@ environment = { MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads"; MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; + MULBO_MUSIC_ROOT = "/srv/music"; # for /folders fs walk MULBO_NAVIDROME_URL = "http://localhost:4533"; MULBO_BIND_HOST = "::"; MULBO_BIND_PORT = "8091"; @@ -240,6 +241,12 @@ RestartSec = 5; User = "danny"; StateDirectory = "mulbo-server"; # /var/lib/mulbo-server, owned by danny + # Navidrome credentials — file format: KEY=value lines. + # Required keys: MULBO_NAVIDROME_USER, MULBO_NAVIDROME_PASS. + # Created manually on sunken-ship (mode 600, owned by danny): + # echo -e "MULBO_NAVIDROME_USER=DannyDannyDanny\nMULBO_NAVIDROME_PASS=..." > ~/.secrets/mulbo-server-navidrome + # chmod 600 ~/.secrets/mulbo-server-navidrome + EnvironmentFile = "/home/danny/.secrets/mulbo-server-navidrome"; }; }; From 73d4225f9b24e47117656b6817b27ba22f367dd6 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 6 May 2026 15:06:51 +0200 Subject: [PATCH 138/185] sunken-ship: grant mulbo-server read on navidrome.db mulbo-server's /folders endpoint reads navidrome.db directly because the Subsonic API's path field is tag-virtual (not real fs paths). Three pieces: - services.navidrome UMask = 0027 (force) so future DB writes are group-readable; default was 0077. - tmpfiles z-rules to chmod 0640 the existing navidrome.db, -wal, -shm (created under the old umask). - mulbo-server gets SupplementaryGroups=[navidrome] so the unit's process can read those files. Trade-off: couples mulbo-server to Navidrome's schema (specifically media_file.id + media_file.path). Acceptable given Navidrome 0.61.1 has been stable on these columns; we'll catch breakage at the /health navidrome_db_readable probe. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 840062b..c05f12b 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -113,6 +113,13 @@ }; }; + # Navidrome's Subsonic API path field is tag-virtual; only the internal + # SQLite has real fs paths. mulbo-server reads navidrome.db ro to + # power /folders + POST /tracks resolution. UMask=0027 makes new DB + # files (and WAL rotations) group-readable; the tmpfile rule fixes the + # existing files written under the previous 0600 umask. + systemd.services.navidrome.serviceConfig.UMask = lib.mkForce "0027"; + # Persist the bind mount so navidrome can read music outside ProtectHome. fileSystems."/srv/music" = { device = "/home/danny/music"; @@ -212,6 +219,12 @@ # reads via the existing /srv/music ro bind-mount with no mount changes. systemd.tmpfiles.rules = [ "d /home/danny/music/mulbo-uploads 0755 danny users -" + # One-time fix for the existing navidrome.db (+ WAL/SHM) created + # under the old 0600 umask. UMask=0027 above keeps future writes + # group-readable. + "z /var/lib/navidrome/navidrome.db 0640 navidrome navidrome -" + "z /var/lib/navidrome/navidrome.db-wal 0640 navidrome navidrome -" + "z /var/lib/navidrome/navidrome.db-shm 0640 navidrome navidrome -" ]; systemd.services.mulbo-server = let @@ -235,18 +248,21 @@ PYTHONUNBUFFERED = "1"; # immediate journal output }; serviceConfig = { - WorkingDirectory = "/home/danny/python-projects/20_mulbo"; - ExecStart = "${pythonEnv}/bin/python mulbo_server/app.py"; - Restart = "on-failure"; - RestartSec = 5; - User = "danny"; - StateDirectory = "mulbo-server"; # /var/lib/mulbo-server, owned by danny + WorkingDirectory = "/home/danny/python-projects/20_mulbo"; + ExecStart = "${pythonEnv}/bin/python mulbo_server/app.py"; + Restart = "on-failure"; + RestartSec = 5; + User = "danny"; + # Read-only access to navidrome.db (+WAL/SHM) — see UMask override + # on the navidrome service above. + SupplementaryGroups = [ "navidrome" ]; + StateDirectory = "mulbo-server"; # /var/lib/mulbo-server, owned by danny # Navidrome credentials — file format: KEY=value lines. # Required keys: MULBO_NAVIDROME_USER, MULBO_NAVIDROME_PASS. # Created manually on sunken-ship (mode 600, owned by danny): # echo -e "MULBO_NAVIDROME_USER=DannyDannyDanny\nMULBO_NAVIDROME_PASS=..." > ~/.secrets/mulbo-server-navidrome # chmod 600 ~/.secrets/mulbo-server-navidrome - EnvironmentFile = "/home/danny/.secrets/mulbo-server-navidrome"; + EnvironmentFile = "/home/danny/.secrets/mulbo-server-navidrome"; }; }; From 082529dac95d4e05b070b9abd4dbf5ee0ca8af58 Mon Sep 17 00:00:00 2001 From: Danny Date: Thu, 7 May 2026 22:12:03 +0200 Subject: [PATCH 139/185] phantom-ship + vps-relay: declare bon service + vhost (port 8091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bon — receipt scanner Mini App. Snap a receipt with the device camera, upload, list. MVP only captures + stores; OCR/categorization later. phantom-ship.nix - systemd.services.bon on port 8091, binds :: for ZT - 8091 added to zt+ allowedTCPPorts - tmpfiles for /home/danny/.local/share/bon/{,images} - python env adds python-multipart (form upload) + pillow (image validate + downscale to 2400px JPEG) vps-relay.nix - Caddy vhost bon.dannydannydanny.me → ZT [::]:8091 --- nixos/hosts/phantom-ship.nix | 43 +++++++++++++++++++++++++++++++----- nixos/hosts/vps-relay.nix | 5 +++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 5e267b6..9bed76e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -49,11 +49,11 @@ in networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator - # (:8083), Forgejo (:3000), Escape Hormuz (:8090) are reachable only - # over the ZeroTier mesh — the vps-relay Caddy reverse-proxies into - # them. Same pattern as sunken-ship's bbbot. Not in global - # allowedTCPPorts, so the WAN side stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 ]; + # (:8083), Forgejo (:3000), Escape Hormuz (:8090), bon (:8091) are + # reachable only over the ZeroTier mesh — the vps-relay Caddy + # reverse-proxies into them. Same pattern as sunken-ship's bbbot. Not + # in global allowedTCPPorts, so the WAN side stays closed. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -178,6 +178,8 @@ in "d /home/danny/.local/share/komtolk 0755 danny users - -" "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" + "d /home/danny/.local/share/bon 0755 danny users - -" + "d /home/danny/.local/share/bon/images 0755 danny users - -" ]; # Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2 @@ -392,6 +394,37 @@ in }; }; + # bon — receipt scanner Mini App (camera capture + gallery). + # Code rsync'd from ~/python-projects/26_bon/ to /home/danny/bon/ + # Images on disk under /home/danny/.local/share/bon/images// + systemd.services.bon = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + python-telegram-bot + python-multipart + pillow + ]); + in { + description = "bon FastAPI server (receipt scanner)"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + BON_DB_PATH = "/home/danny/.local/share/bon/bon.db"; + BON_IMAGES_DIR = "/home/danny/.local/share/bon/images"; + }; + serviceConfig = { + WorkingDirectory = "/home/danny/bon"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8091"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # KomTolk (formerly translate-platform) — Copenhagen translation gigs Mini App. # Code rsync'd from ~/python-projects/26_komtolk/ to /home/danny/komtolk/ systemd.services.komtolk = let diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 24c4d73..3d34a1a 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -127,6 +127,11 @@ "escapehormuz.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8090 ''; + # bon — receipt scanner Mini App, port 8091. Camera capture in + # the WebView needs HTTPS, which Caddy terminates here. + "bon.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8091 + ''; }; }; From 4525e73f1a6ae03b5e50761ef1dc99cb52f7623e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Thu, 7 May 2026 22:30:05 +0200 Subject: [PATCH 140/185] sunken-ship: mulbo-server-backfill systemd oneshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion oneshot for mulbo-server: populates the dedup index (tracks_index) from Navidrome's existing 15k tracks. Without it, GET /tracks/by-hash misses for every existing offshore track and the upload path duplicates content. Inherits same User/SupplementaryGroups as the running service. chromaprint added to PATH for fpcalc. TimeoutSec=8h covers full 274 GB hashing run with headroom. Triggered manually — not auto-scheduled: sudo systemctl start mulbo-server-backfill journalctl -fu mulbo-server-backfill Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index c05f12b..d1714bf 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -297,6 +297,38 @@ timerConfig.RandomizedDelaySec = "2min"; }; + # One-shot backfill: walks Navidrome's media_file, computes + # (sha256, chromaprint) per file, populates mulbo-server's tracks_index + # with the corresponding navidrome_track_id. Idempotent — existing rows + # left alone. Without this, /tracks/by-hash misses for every existing + # offshore track and `mulbo reconcile-local` duplicates content. + # + # Trigger manually: sudo systemctl start mulbo-server-backfill + # Follow progress: journalctl -fu mulbo-server-backfill + systemd.services.mulbo-server-backfill = let + pythonEnv = pkgs.python312.withPackages (ps: with ps; [ ]); + in { + description = "Backfill mulbo-server tracks_index from Navidrome catalog"; + after = [ "mulbo-server.service" ]; + requires = [ "mulbo-server.service" ]; + path = [ pkgs.chromaprint ]; # provides fpcalc + environment = { + MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; + MULBO_NAVIDROME_DB = "/var/lib/navidrome/navidrome.db"; + MULBO_MUSIC_ROOT = "/srv/music"; + PYTHONUNBUFFERED = "1"; + }; + serviceConfig = { + Type = "oneshot"; + WorkingDirectory = "/home/danny/python-projects/20_mulbo"; + ExecStart = "${pythonEnv}/bin/python mulbo_server/backfill.py"; + User = "danny"; + SupplementaryGroups = [ "navidrome" ]; # ro access to navidrome.db + StateDirectory = "mulbo-server"; # so /var/lib/mulbo-server/index.db stays writable + TimeoutSec = "8h"; # full backfill on 274 GB ≈ 1h, leave headroom + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 647d748d30fca06d674c6616c8b079973700c025 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 8 May 2026 06:57:06 +0200 Subject: [PATCH 141/185] phantom-ship: add tesseract to bon service for OCR --- nixos/hosts/phantom-ship.nix | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 9bed76e..ad9169e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -394,9 +394,10 @@ in }; }; - # bon — receipt scanner Mini App (camera capture + gallery). + # bon — receipt scanner Mini App (camera capture + gallery + OCR). # Code rsync'd from ~/python-projects/26_bon/ to /home/danny/bon/ # Images on disk under /home/danny/.local/share/bon/images// + # OCR via tesseract (binary on PATH; server uses subprocess directly). systemd.services.bon = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ fastapi @@ -405,12 +406,18 @@ in python-multipart pillow ]); + # English-only for now — Danish receipts in DK are mostly English chars + # plus prices, which `eng` handles fine. Add more languages later if + # vyscul or other testers report missed text. + tesseractEng = pkgs.tesseract.override { + enableLanguages = [ "eng" ]; + }; in { description = "bon FastAPI server (receipt scanner)"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pythonEnv ]; + path = [ pythonEnv tesseractEng ]; environment = { SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; BON_DB_PATH = "/home/danny/.local/share/bon/bon.db"; From 327bdc11fe7e01c4b688e667ffd6916a3161fbb7 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 8 May 2026 07:23:08 +0200 Subject: [PATCH 142/185] phantom-ship: services.ollama + qwen2.5:3b-instruct for bon extraction --- nixos/hosts/phantom-ship.nix | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index ad9169e..90df6de 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -394,10 +394,22 @@ in }; }; - # bon — receipt scanner Mini App (camera capture + gallery + OCR). + # Ollama — local LLM runtime, used by bon's structured-data extraction + # step. Listens on 127.0.0.1:11434 only (not exposed over ZT). The + # qwen2.5:3b-instruct model is pre-pulled at boot via loadModels. + services.ollama = { + enable = true; + host = "127.0.0.1"; + port = 11434; + # ~2.5 GB on disk after Q4_K_M quantization. Phantom-ship has plenty. + loadModels = [ "qwen2.5:3b-instruct" ]; + }; + + # bon — receipt scanner Mini App (camera capture + gallery + OCR + extract). # Code rsync'd from ~/python-projects/26_bon/ to /home/danny/bon/ # Images on disk under /home/danny/.local/share/bon/images// # OCR via tesseract (binary on PATH; server uses subprocess directly). + # Structured extraction via local Ollama (qwen2.5:3b-instruct). systemd.services.bon = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ fastapi @@ -405,6 +417,7 @@ in python-telegram-bot python-multipart pillow + httpx # for the Ollama HTTP call from extract.py ]); # English-only for now — Danish receipts in DK are mostly English chars # plus prices, which `eng` handles fine. Add more languages later if @@ -414,7 +427,7 @@ in }; in { description = "bon FastAPI server (receipt scanner)"; - after = [ "network-online.target" ]; + after = [ "network-online.target" "ollama.service" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; path = [ pythonEnv tesseractEng ]; @@ -422,6 +435,8 @@ in SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; BON_DB_PATH = "/home/danny/.local/share/bon/bon.db"; BON_IMAGES_DIR = "/home/danny/.local/share/bon/images"; + BON_OLLAMA_URL = "http://127.0.0.1:11434"; + BON_OLLAMA_MODEL = "qwen2.5:3b-instruct"; }; serviceConfig = { WorkingDirectory = "/home/danny/bon"; From eee28d3e9a160e7325581c2ccc3c7297b88e4cae Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 8 May 2026 07:23:09 +0200 Subject: [PATCH 143/185] phantom-ship + vps-relay: declare notes service + vhosts (port 8092) notes serves both notes.dannydannydanny.me (blog) and dannydannydanny.me (apex landing) from the same FastAPI process, switching on Host header. Source rsync'd from ~/python-projects/26_notes/ to /home/danny/notes/. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 38 +++++++++++++++++++++++++++++++----- nixos/hosts/vps-relay.nix | 8 ++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 90df6de..b6b269f 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -49,11 +49,12 @@ in networking.firewall.trustedInterfaces = [ "enp0s31f6" ]; # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator - # (:8083), Forgejo (:3000), Escape Hormuz (:8090), bon (:8091) are - # reachable only over the ZeroTier mesh — the vps-relay Caddy - # reverse-proxies into them. Same pattern as sunken-ship's bbbot. Not - # in global allowedTCPPorts, so the WAN side stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 ]; + # (:8083), Forgejo (:3000), Escape Hormuz (:8090), bon (:8091), + # notes (:8092) are reachable only over the ZeroTier mesh — the + # vps-relay Caddy reverse-proxies into them. Same pattern as + # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side + # stays closed. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -475,6 +476,33 @@ in }; }; + # notes — tiny markdown blog + apex landing page. + # One service serves two hostnames via Host-header switch: + # notes.dannydannydanny.me → blog + # dannydannydanny.me → landing + # Code rsync'd from ~/python-projects/26_notes/ to /home/danny/notes/ + systemd.services.notes = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + markdown + jinja2 + ]); + in { + description = "notes — markdown blog + landing page"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + serviceConfig = { + WorkingDirectory = "/home/danny/notes"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8092"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Hara morning heartbeat — daily email check + Telegram good-morning ping. # Runs claude in print mode with the Gmail MCP, then sends output via Bot API. # Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin). diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 3d34a1a..306c127 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -132,6 +132,14 @@ "bon.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8091 ''; + # notes — markdown blog (notes.X) + apex landing (X). Same backend + # service on phantom :8092 routes by Host header. + "notes.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 + ''; + "dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 + ''; }; }; From ccf9eb28595018043cf61debd831605dd47dca60 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 8 May 2026 15:28:52 +0200 Subject: [PATCH 144/185] phantom-ship: bon switches to qwen2.5:7b-instruct for extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3B was making column-parsing mistakes on real receipts (conflating qty/price, nominating line items as merchant). 7B Q4_K_M is ~3x slower on phantom-ship CPU (~5min vs ~1.5min per receipt) but materially better at structured extraction. Background task — speed isn't critical. Keep 3B in loadModels as a fallback knob (BON_OLLAMA_MODEL env). --- nixos/hosts/phantom-ship.nix | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index b6b269f..138633c 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -396,14 +396,18 @@ in }; # Ollama — local LLM runtime, used by bon's structured-data extraction - # step. Listens on 127.0.0.1:11434 only (not exposed over ZT). The - # qwen2.5:3b-instruct model is pre-pulled at boot via loadModels. + # step. Listens on 127.0.0.1:11434 only (not exposed over ZT). + # We pre-pull both 3B and 7B Qwen2.5; bon currently runs 7B for better + # column-parsing accuracy on receipts (3B mis-conflates qty/price + # columns and over-eagerly nominates line items as merchants). services.ollama = { enable = true; host = "127.0.0.1"; port = 11434; - # ~2.5 GB on disk after Q4_K_M quantization. Phantom-ship has plenty. - loadModels = [ "qwen2.5:3b-instruct" ]; + loadModels = [ + "qwen2.5:3b-instruct" # ~2.5 GB — kept as fast fallback + "qwen2.5:7b-instruct" # ~4.7 GB — current default, slower but better + ]; }; # bon — receipt scanner Mini App (camera capture + gallery + OCR + extract). @@ -437,7 +441,7 @@ in BON_DB_PATH = "/home/danny/.local/share/bon/bon.db"; BON_IMAGES_DIR = "/home/danny/.local/share/bon/images"; BON_OLLAMA_URL = "http://127.0.0.1:11434"; - BON_OLLAMA_MODEL = "qwen2.5:3b-instruct"; + BON_OLLAMA_MODEL = "qwen2.5:7b-instruct"; }; serviceConfig = { WorkingDirectory = "/home/danny/bon"; From 814993e66bcebb0f614f2fd0199a8aa4dc3a863d Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 8 May 2026 20:39:31 +0200 Subject: [PATCH 145/185] phantom-ship: revert bon to 3B model (7B too slow on CPU) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A/B-tested 7B vs 3B on a real NETTO receipt. 7B took 3.6 min/receipt vs ~30s for 3B. Accuracy gain was minimal — 7B still picked a line item ('ARLA SEOMELK 1.') as merchant when the OCR header was missing, just a different one than 3B picked ('REJESALAT'). The merchant problem isn't a model-size problem; it's an OCR problem (Tesseract missed the NETTO logo entirely on this receipt). Keeping both models in loadModels so we can flip back via env var without a fresh pull. --- nixos/hosts/phantom-ship.nix | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 138633c..90886cc 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -397,16 +397,17 @@ in # Ollama — local LLM runtime, used by bon's structured-data extraction # step. Listens on 127.0.0.1:11434 only (not exposed over ZT). - # We pre-pull both 3B and 7B Qwen2.5; bon currently runs 7B for better - # column-parsing accuracy on receipts (3B mis-conflates qty/price - # columns and over-eagerly nominates line items as merchants). + # 3B is bon's default — 7B was tested but ran ~3.6 min/receipt vs ~30s + # for 3B on phantom-ship CPU, with no real accuracy gain (still picked + # line items as merchant on header-less OCR; that's an OCR problem, + # not a model problem). Both kept loaded so we can A/B without a pull. services.ollama = { enable = true; host = "127.0.0.1"; port = 11434; loadModels = [ - "qwen2.5:3b-instruct" # ~2.5 GB — kept as fast fallback - "qwen2.5:7b-instruct" # ~4.7 GB — current default, slower but better + "qwen2.5:3b-instruct" # ~2.5 GB — current default + "qwen2.5:7b-instruct" # ~4.7 GB — A/B testing only ]; }; @@ -441,7 +442,7 @@ in BON_DB_PATH = "/home/danny/.local/share/bon/bon.db"; BON_IMAGES_DIR = "/home/danny/.local/share/bon/images"; BON_OLLAMA_URL = "http://127.0.0.1:11434"; - BON_OLLAMA_MODEL = "qwen2.5:7b-instruct"; + BON_OLLAMA_MODEL = "qwen2.5:3b-instruct"; }; serviceConfig = { WorkingDirectory = "/home/danny/bon"; From c5cabe7531e74cad5824beda30b795bb3f97fbb0 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 9 May 2026 22:43:40 +0200 Subject: [PATCH 146/185] sunken-ship: MULBO_MUSIC_WRITE_ROOT for mulbo-server dedup /srv/music is RO bind-mount; deletes/quarantines have to go through the underlying /home/danny/music. New env var separates the read-side (MUSIC_ROOT, used for hashing) from the write-side (MUSIC_WRITE_ROOT, used for unlink + move-to-quarantine). Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index d1714bf..4c0f13b 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -239,9 +239,10 @@ wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; environment = { - MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads"; - MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; - MULBO_MUSIC_ROOT = "/srv/music"; # for /folders fs walk + MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads"; + MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; + MULBO_MUSIC_ROOT = "/srv/music"; # ro view via bind-mount; reads + hashing + MULBO_MUSIC_WRITE_ROOT = "/home/danny/music"; # underlying rw path; deletes + quarantines MULBO_NAVIDROME_URL = "http://localhost:4533"; MULBO_BIND_HOST = "::"; MULBO_BIND_PORT = "8091"; From fb99ef3cffc111c8172c4eb75ebea8d3955c5bd0 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 10 May 2026 12:48:26 +0200 Subject: [PATCH 147/185] sunken-ship: add fitness-bot-shipyard staging instance Mirrors the prod fitness-bot setup but watches origin/staging, runs in /home/danny/tg_fitness_bot_shipyard, listens on port 8081, and loads its bot token from /home/danny/.secrets/bigbiggerbiggestbot-shipyard.env via EnvironmentFile (separate from prod's secrets file). ConditionPathExists keeps the service from start-looping until the secrets file is written. No WEBAPP_URL set, so start.py boots an ephemeral cloudflared Quick Tunnel; the bot updates its Telegram menu button to that URL on every start (same as prod was originally). Pull-timer fires every 15 min on the :13/28/43/58 offset to spread load against the existing fitness-bot-pull (:07/15) and mulbo-server-pull (:11/15) timers. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 4c0f13b..4188a41 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -210,6 +210,71 @@ timerConfig.RandomizedDelaySec = "2min"; }; + # ── Shipyard staging — second instance for verifying changes pre-prod ─ + # Working dir: /home/danny/tg_fitness_bot_shipyard (separate clone of the same repo). + # Branch: origin/staging (push there to deploy here; push to origin/main for prod). + # Bot token (separate from prod): /home/danny/.secrets/bigbiggerbiggestbot-shipyard.env + # File contents: BOT_TOKEN= + # Service won't start until this file exists (ConditionPathExists). + # Mini App URL: ephemeral cloudflared Quick Tunnel (no VPS Caddy). + # Workflow: git push origin :staging → wait ~15 min → /start the + # shipyard bot in Telegram → test → git push origin :main. + systemd.services.fitness-bot-shipyard = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + python-telegram-bot + python-dotenv + aiohttp + ]); + in { + description = "BigBiggerBiggestBot — SHIPYARD STAGING instance"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv pkgs.cloudflared ]; + environment.API_HOST = "::"; + environment.API_PORT = "8081"; + # No WEBAPP_URL — start.py spins up its own ephemeral cloudflared tunnel. + unitConfig.ConditionPathExists = "/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env"; + serviceConfig = { + WorkingDirectory = "/home/danny/tg_fitness_bot_shipyard"; + EnvironmentFile = "/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env"; + ExecStart = "${pythonEnv}/bin/python start.py"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + + systemd.services.fitness-bot-shipyard-pull = { + description = "Pull shipyard fitness bot from origin/staging and restart if changed"; + path = with pkgs; [ git systemd ]; + environment.GIT_CONFIG_COUNT = "1"; + environment.GIT_CONFIG_KEY_0 = "safe.directory"; + environment.GIT_CONFIG_VALUE_0 = "/home/danny/tg_fitness_bot_shipyard"; + script = '' + set -euo pipefail + if [ ! -d /home/danny/tg_fitness_bot_shipyard/.git ]; then + echo "Shipyard working dir not bootstrapped yet — skipping pull." + exit 0 + fi + cd /home/danny/tg_fitness_bot_shipyard + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/staging)" ]; then + exit 0 + fi + git pull origin staging + systemctl restart fitness-bot-shipyard + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.fitness-bot-shipyard-pull = { + wantedBy = [ "timers.target" ]; + # Offset from prod (07/15), mulbo (11/15), and dotfiles-rebuild. + timerConfig.OnCalendar = "*-*-* *:13/15:00"; + timerConfig.RandomizedDelaySec = "2min"; + }; + # Mulbo companion service (Phase 5: uploads + dedup index + folders). # Wire spec: ~danny/python-projects/20_mulbo/SERVER_API.md. # Bootstrap (one-time): git clone git@github.com:DannyDannyDanny/python-projects.git /home/danny/python-projects From 851ee8ea1df311cac2b87b48b0b87bb6ec2b6727 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 10 May 2026 13:01:05 +0200 Subject: [PATCH 148/185] sunken-ship: mulbo-server-enrich oneshot (Phase 7.5) Companion oneshot for mulbo-server. python312 env adds mutagen (tag writeback); pkgs.yt-dlp on PATH for SoundCloud lookups. Same User/SupplementaryGroups/EnvironmentFile/StateDirectory as mulbo-server-backfill. TimeoutSec=8h covers a full library pass. Trigger: sudo systemctl start mulbo-server-enrich Follow: journalctl -fu mulbo-server-enrich Add MULBO_ACOUSTID_KEY to /home/danny/.secrets/mulbo-server-navidrome to enable the AcoustID source; the yt-dlp + filename sources need no keys. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 4188a41..c66010f 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -395,6 +395,45 @@ }; }; + # Phase 7.5 enrichment one-shot. For tracks where Navidrome's tags + # are empty/Unknown, runs three sources (filename heuristics, yt-dlp + # for SoundCloud `[]` patterns, AcoustID+MusicBrainz), votes the + # results, and writes back via mutagen with strict-replacement + # (never touches user-set tags). + # + # Trigger: sudo systemctl start mulbo-server-enrich + # Follow progress: journalctl -fu mulbo-server-enrich + systemd.services.mulbo-server-enrich = let + pythonEnv = pkgs.python312.withPackages (ps: with ps; [ + mutagen # tag writeback + ]); + in { + description = "Enrich Navidrome tracks with empty/Unknown metadata"; + after = [ "mulbo-server.service" ]; + requires = [ "mulbo-server.service" ]; + path = with pkgs; [ yt-dlp ]; # provides yt-dlp for SoundCloud lookups + environment = { + MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; + MULBO_NAVIDROME_DB = "/var/lib/navidrome/navidrome.db"; + MULBO_MUSIC_ROOT = "/srv/music"; + MULBO_MUSIC_WRITE_ROOT = "/home/danny/music"; + PYTHONUNBUFFERED = "1"; + }; + serviceConfig = { + Type = "oneshot"; + WorkingDirectory = "/home/danny/python-projects/20_mulbo"; + ExecStart = "${pythonEnv}/bin/python mulbo_server/enrich.py"; + User = "danny"; + SupplementaryGroups = [ "navidrome" ]; + StateDirectory = "mulbo-server"; + # Add MULBO_ACOUSTID_KEY to the secrets file to enable the + # AcoustID source. yt-dlp source needs no key. Filename source + # needs nothing. + EnvironmentFile = "/home/danny/.secrets/mulbo-server-navidrome"; + TimeoutSec = "8h"; + }; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 067bab125b77a1dfb9db7cdc099908d7621684f9 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 10 May 2026 13:12:09 +0200 Subject: [PATCH 149/185] sunken-ship: shipyard staging uses shipyard_poc_bot token shipyard_poc_bot is the shared "POC slot" Telegram bot that hosts whatever experiment is currently being staged; B3Bot staging is just the current tenant. Repoint EnvironmentFile and ConditionPathExists at /home/danny/.secrets/shipyard_poc_bot.env. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index c66010f..39e54bf 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -213,12 +213,14 @@ # ── Shipyard staging — second instance for verifying changes pre-prod ─ # Working dir: /home/danny/tg_fitness_bot_shipyard (separate clone of the same repo). # Branch: origin/staging (push there to deploy here; push to origin/main for prod). - # Bot token (separate from prod): /home/danny/.secrets/bigbiggerbiggestbot-shipyard.env - # File contents: BOT_TOKEN= + # Bot: shipyard_poc_bot — the shared "POC slot" Telegram bot. While B3Bot + # staging is the active POC, shipyard_poc_bot polls into this service. + # Token file: /home/danny/.secrets/shipyard_poc_bot.env + # File contents: BOT_TOKEN= # Service won't start until this file exists (ConditionPathExists). # Mini App URL: ephemeral cloudflared Quick Tunnel (no VPS Caddy). - # Workflow: git push origin :staging → wait ~15 min → /start the - # shipyard bot in Telegram → test → git push origin :main. + # Workflow: git push origin :staging → wait ~15 min → /start + # shipyard_poc_bot in Telegram → test → git push origin :main. systemd.services.fitness-bot-shipyard = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ python-telegram-bot @@ -234,10 +236,10 @@ environment.API_HOST = "::"; environment.API_PORT = "8081"; # No WEBAPP_URL — start.py spins up its own ephemeral cloudflared tunnel. - unitConfig.ConditionPathExists = "/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env"; + unitConfig.ConditionPathExists = "/home/danny/.secrets/shipyard_poc_bot.env"; serviceConfig = { WorkingDirectory = "/home/danny/tg_fitness_bot_shipyard"; - EnvironmentFile = "/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env"; + EnvironmentFile = "/home/danny/.secrets/shipyard_poc_bot.env"; ExecStart = "${pythonEnv}/bin/python start.py"; Restart = "on-failure"; RestartSec = 10; From 83dd92d7384c775f3072e41abc063c8872fc4614 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 10 May 2026 14:00:39 +0200 Subject: [PATCH 150/185] shipyard staging gets a stable URL: b3.dannydannydanny.me MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the cloudflared Quick Tunnel (URL changed on every restart → unworkable for shipyard's apps.json). Move to the same pattern every other tenant uses: - vps-relay Caddy: new virtualHost b3.dannydannydanny.me → reverse_proxy to sunken-ship's ZT IPv6 :8081. - sunken-ship: open port 8081 on the zt+ firewall interface (was 8080 + 8091, now 8080 + 8081 + 8091). - fitness-bot-shipyard service: set WEBAPP_URL=https://b3... so start.py skips its own tunnel attempt; drop pkgs.cloudflared from path now that nothing in the unit needs it. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 33 ++++++++++++++++++++------------- nixos/hosts/vps-relay.nix | 5 +++++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 39e54bf..75ddc1d 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -95,10 +95,10 @@ networking.firewall = { allowedTCPPorts = [ 7000 7001 7100 4533 ]; allowedUDPPorts = [ 5353 6000 6001 7011 ]; - # 8080: bbbot HTTP backend. 8091: mulbo-server companion service. - # Both ZT-only — see vps-relay.nix for reverse proxy if exposing - # publicly later. - interfaces."zt+".allowedTCPPorts = [ 8080 8091 ]; + # 8080: bbbot HTTP backend. 8081: bbbot SHIPYARD STAGING (B3Bot beta). + # 8091: mulbo-server companion service. All ZT-only — see vps-relay.nix + # for the reverse proxies that expose them publicly. + interfaces."zt+".allowedTCPPorts = [ 8080 8081 8091 ]; }; # Navidrome — self-hosted music streaming server (Subsonic API). @@ -210,17 +210,22 @@ timerConfig.RandomizedDelaySec = "2min"; }; - # ── Shipyard staging — second instance for verifying changes pre-prod ─ - # Working dir: /home/danny/tg_fitness_bot_shipyard (separate clone of the same repo). + # ── Shipyard staging — B3Bot beta tenant under shipyard_poc_bot ────── + # Mini-App-only HTTP server (no Telegram polling — shipyard_poc_bot on + # phantom-ship owns the polling loop; this service only validates Telegram + # WebApp initData HMACs against the shared bot token). + # + # Working dir: /home/danny/tg_fitness_bot_shipyard (separate clone of the + # same repo, gitignored workouts.db kept across pulls). # Branch: origin/staging (push there to deploy here; push to origin/main for prod). - # Bot: shipyard_poc_bot — the shared "POC slot" Telegram bot. While B3Bot - # staging is the active POC, shipyard_poc_bot polls into this service. # Token file: /home/danny/.secrets/shipyard_poc_bot.env # File contents: BOT_TOKEN= # Service won't start until this file exists (ConditionPathExists). - # Mini App URL: ephemeral cloudflared Quick Tunnel (no VPS Caddy). - # Workflow: git push origin :staging → wait ~15 min → /start - # shipyard_poc_bot in Telegram → test → git push origin :main. + # Mini App URL: https://b3.dannydannydanny.me (vps-relay Caddy → + # ZT IPv6 → here:8081). Stable across restarts — listed in + # ~/python-projects/26_shipyard/apps.json. + # Workflow: git push origin :staging → wait ~15 min → tap B3Bot + # beta in shipyard_poc_bot's launcher → test → git push :main. systemd.services.fitness-bot-shipyard = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ python-telegram-bot @@ -232,10 +237,12 @@ after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pythonEnv pkgs.cloudflared ]; + path = [ pythonEnv ]; environment.API_HOST = "::"; environment.API_PORT = "8081"; - # No WEBAPP_URL — start.py spins up its own ephemeral cloudflared tunnel. + # Stable URL fronted by vps-relay's Caddy → ZT → here:8081. + # WEBAPP_URL set tells start.py to skip cloudflared entirely. + environment.WEBAPP_URL = "https://b3.dannydannydanny.me"; unitConfig.ConditionPathExists = "/home/danny/.secrets/shipyard_poc_bot.env"; serviceConfig = { WorkingDirectory = "/home/danny/tg_fitness_bot_shipyard"; diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 306c127..1914698 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -101,6 +101,11 @@ "bbbot.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8080 ''; + # B3Bot beta — bbbot's staging tenant under shipyard_poc_bot. + # Same backend host as bbbot prod, port 8081. + "b3.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8081 + ''; # Shelfish — phantom-ship's ZT IPv6. "shelfish.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8081 From 40cc62f65bb04acc305377d5cde52ee57c38b847 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 10 May 2026 16:02:42 +0200 Subject: [PATCH 151/185] sunken-ship: chromaprint on PATH for mulbo-server-enrich AcoustID needs fpcalc -plain output (re-fingerprinted on-demand since tracks_index stores -raw for dedup). chromaprint added alongside the existing yt-dlp. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 75ddc1d..095b986 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -420,7 +420,7 @@ description = "Enrich Navidrome tracks with empty/Unknown metadata"; after = [ "mulbo-server.service" ]; requires = [ "mulbo-server.service" ]; - path = with pkgs; [ yt-dlp ]; # provides yt-dlp for SoundCloud lookups + path = with pkgs; [ yt-dlp chromaprint ]; # yt-dlp for SC/YT lookups, chromaprint for AcoustID's -plain fingerprint environment = { MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; MULBO_NAVIDROME_DB = "/var/lib/navidrome/navidrome.db"; From 3b6f4545b4d72cfe62c4b41782f8d120aa2983ff Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 10 May 2026 16:12:08 +0200 Subject: [PATCH 152/185] monitoring: prometheus + alertmanager + grafana on sunken-ship node_exporter on all three hosts (port 9100, ZT-only). Prometheus server scrapes via the clan ZT IPv6s. Alertmanager routes alerts to @HarakatBot (chat 66070351); critical repeats every 1h, others 4h. Starter rule: HostDown when up==0 for 5m. Grafana on :3000 over ZT, provisioned with the local Prometheus as default datasource. Manual secrets on sunken-ship: /etc/alertmanager/telegram-token and /etc/grafana/secret-key. Co-Authored-By: Claude Opus 4.7 (1M context) --- flake-modules/clan.nix | 4 + flake-modules/nixos-modules.nix | 2 + modules/monitoring-node-exporter.nix | 12 ++ modules/monitoring-prometheus-server.nix | 134 +++++++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 modules/monitoring-node-exporter.nix create mode 100644 modules/monitoring-prometheus-server.nix diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index f8b1293..6b4a5d0 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -127,6 +127,8 @@ in { ../nixos/hosts/sunken-ship.nix config.flake.nixosModules.dotfiles-rebuild config.flake.nixosModules.server-debug-tools + config.flake.nixosModules.monitoring-node-exporter + config.flake.nixosModules.monitoring-prometheus-server inputs.home-manager.nixosModules.home-manager (hmModule { user = "danny"; @@ -146,6 +148,7 @@ in { } clanHostsModule ../nixos/hosts/vps-relay.nix + config.flake.nixosModules.monitoring-node-exporter inputs.home-manager.nixosModules.home-manager (hmModule { user = "danny"; @@ -167,6 +170,7 @@ in { ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild config.flake.nixosModules.server-debug-tools + config.flake.nixosModules.monitoring-node-exporter inputs.home-manager.nixosModules.home-manager (hmModule { user = "danny"; diff --git a/flake-modules/nixos-modules.nix b/flake-modules/nixos-modules.nix index a466a58..3f6bf96 100644 --- a/flake-modules/nixos-modules.nix +++ b/flake-modules/nixos-modules.nix @@ -5,4 +5,6 @@ { ... }: { flake.nixosModules.dotfiles-rebuild = ../modules/dotfiles-rebuild.nix; flake.nixosModules.server-debug-tools = ../modules/server-debug-tools.nix; + flake.nixosModules.monitoring-node-exporter = ../modules/monitoring-node-exporter.nix; + flake.nixosModules.monitoring-prometheus-server = ../modules/monitoring-prometheus-server.nix; } diff --git a/modules/monitoring-node-exporter.nix b/modules/monitoring-node-exporter.nix new file mode 100644 index 0000000..402f44d --- /dev/null +++ b/modules/monitoring-node-exporter.nix @@ -0,0 +1,12 @@ +# Prometheus node_exporter — exposes host metrics on :9100, scoped to the +# ZeroTier mesh so only sunken-ship (the Prometheus server) can scrape it. +{ ... }: { + services.prometheus.exporters.node = { + enable = true; + port = 9100; + listenAddress = "::"; + enabledCollectors = [ "systemd" ]; + }; + + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 9100 ]; +} diff --git a/modules/monitoring-prometheus-server.nix b/modules/monitoring-prometheus-server.nix new file mode 100644 index 0000000..6b02d14 --- /dev/null +++ b/modules/monitoring-prometheus-server.nix @@ -0,0 +1,134 @@ +# Prometheus + Alertmanager + Grafana on sunken-ship. +# +# Scrape targets are the clan ZeroTier IPv6s — kept in sync with +# vars/per-machine//zerotier/zerotier-ip/value. +# +# Telegram receiver uses the existing @HarakatBot. Drop the bot token at +# /etc/alertmanager/telegram-token (mode 0400, root) before rebuild — same +# manual-secret pattern as the other Telegram bots in the repo. +# +# Routing: critical alerts repeat every 1h, everything else every 4h. +{ ... }: +let + sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; + phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; + vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; + + target = ip: "[${ip}]:9100"; +in { + services.prometheus = { + enable = true; + port = 9090; + listenAddress = "[::1]"; + + globalConfig = { + scrape_interval = "30s"; + evaluation_interval = "30s"; + }; + + scrapeConfigs = [{ + job_name = "node"; + static_configs = [{ + targets = [ + (target sunkenShipZTv6) + (target phantomShipZTv6) + (target vpsRelayZTv6) + ]; + labels.job = "node"; + }]; + }]; + + ruleFiles = [ + (builtins.toFile "host-rules.yml" (builtins.toJSON { + groups = [{ + name = "hosts"; + rules = [{ + alert = "HostDown"; + expr = ''up{job="node"} == 0''; + for = "5m"; + labels.severity = "critical"; + annotations = { + summary = "{{ $labels.instance }} is down"; + description = "{{ $labels.instance }} has been unreachable for 5 minutes."; + }; + }]; + }]; + })) + ]; + + alertmanagers = [{ + static_configs = [{ targets = [ "127.0.0.1:9093" ]; }]; + }]; + + alertmanager = { + enable = true; + port = 9093; + listenAddress = "[::1]"; + configuration = { + route = { + receiver = "telegram-default"; + group_by = [ "alertname" ]; + group_wait = "30s"; + group_interval = "5m"; + repeat_interval = "4h"; + routes = [{ + matchers = [ ''severity="critical"'' ]; + receiver = "telegram-critical"; + group_wait = "10s"; + group_interval = "1m"; + repeat_interval = "1h"; + }]; + }; + receivers = [ + { + name = "telegram-default"; + telegram_configs = [{ + bot_token_file = "/etc/alertmanager/telegram-token"; + chat_id = 66070351; + api_url = "https://api.telegram.org"; + parse_mode = ""; + }]; + } + { + name = "telegram-critical"; + telegram_configs = [{ + bot_token_file = "/etc/alertmanager/telegram-token"; + chat_id = 66070351; + api_url = "https://api.telegram.org"; + parse_mode = ""; + message = '' + CRITICAL: {{ .CommonLabels.alertname }} + {{ range .Alerts }}{{ .Annotations.summary }} + {{ .Annotations.description }} + {{ end }}''; + }]; + } + ]; + }; + }; + }; + + services.grafana = { + enable = true; + settings.server = { + http_addr = "::"; + http_port = 3000; + domain = "sunken-ship.clan"; + }; + # Drop a random 32+ char string at /etc/grafana/secret-key (mode 0400, + # owned by grafana:grafana) before rebuild — same manual-secret pattern + # as /etc/alertmanager/telegram-token. Used to encrypt secrets stored + # in Grafana's DB; nothing to rotate on a fresh install. + settings.security.secret_key = "$__file{/etc/grafana/secret-key}"; + provision.datasources.settings.datasources = [{ + name = "Prometheus"; + type = "prometheus"; + url = "http://[::1]:9090"; + isDefault = true; + }]; + }; + + # Grafana on the ZeroTier mesh only. Prometheus + Alertmanager bind to + # localhost so they're not reachable off-host. + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 ]; +} From dc7895e3b26528312aac0a366b6d7a307fe96884 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 10 May 2026 16:17:28 +0200 Subject: [PATCH 153/185] monitoring: bracket IPv6 listenAddress for node_exporter The NixOS module concatenates listenAddress and port as `${a}:${p}`, so "::" became ":::9100" and node_exporter rejected it ("too many colons in address"). Use "[::]" so the result is "[::]:9100". Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/monitoring-node-exporter.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring-node-exporter.nix b/modules/monitoring-node-exporter.nix index 402f44d..7e08ae0 100644 --- a/modules/monitoring-node-exporter.nix +++ b/modules/monitoring-node-exporter.nix @@ -4,7 +4,7 @@ services.prometheus.exporters.node = { enable = true; port = 9100; - listenAddress = "::"; + listenAddress = "[::]"; enabledCollectors = [ "systemd" ]; }; From e8158e6c0f525e9324ebd61e95d81100fbbef374 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 10 May 2026 16:47:37 +0200 Subject: [PATCH 154/185] =?UTF-8?q?monitoring:=20fix=20prometheus=20?= =?UTF-8?q?=E2=86=92=20alertmanager=20loopback=20(IPv4=20vs=20IPv6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alertmanager binds [::1]:9093 but Prometheus was dialing 127.0.0.1:9093 — connection refused, so alerts fired internally but never reached Alertmanager. Switch the target to [::1]:9093 to match the bind. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/monitoring-prometheus-server.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring-prometheus-server.nix b/modules/monitoring-prometheus-server.nix index 6b02d14..9aedc14 100644 --- a/modules/monitoring-prometheus-server.nix +++ b/modules/monitoring-prometheus-server.nix @@ -57,7 +57,7 @@ in { ]; alertmanagers = [{ - static_configs = [{ targets = [ "127.0.0.1:9093" ]; }]; + static_configs = [{ targets = [ "[::1]:9093" ]; }]; }]; alertmanager = { From fc9894c32f29dab890ac53fc722f7849c25e6922 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 12 May 2026 10:13:11 +0200 Subject: [PATCH 155/185] feat: install zed-editor :sparkles: --- nixos/home/danny/home.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 16d9adf..739abc2 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -228,7 +228,7 @@ # alacritty # TODO: configured via programs.alacritty above, so not needed here # warp-terminal # TODO: Bloat # vscodium # TODO: Bloat - # zed-editor # TODO: Bloat + zed-editor code-cursor cursor-cli dfu-util # USB DFU firmware flasher (Flipper Zero etc.) From 4fab9a28a20fe6a19c05df5a40fb425dbf0ca11b Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 12 May 2026 13:57:36 +0200 Subject: [PATCH 156/185] chore: update flake.lock :arrow_up: --- flake.lock | 157 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 65 deletions(-) diff --git a/flake.lock b/flake.lock index 106a124..c524fd2 100644 --- a/flake.lock +++ b/flake.lock @@ -13,11 +13,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1776708356, - "narHash": "sha256-Smv2algQmojsu0m9EEXs+Oy0Tg/SjwI5WN66u/BaxYs=", + "lastModified": 1778267301, + "narHash": "sha256-/SEnX0wGQuvQ78EKWTIDA8nCUaJhCD0nVbtP5evFdSQ=", "ref": "fix/dm-pull-deploy-hyphen-hostnames", - "rev": "796ee625b60941bb959039924bfc39e5d13481cc", - "revCount": 46, + "rev": "bd2f9c63ed5613eb52a03116df88b06275171f55", + "revCount": 47, "type": "git", "url": "https://git.clan.lol/dannydannydanny/clan-community.git" }, @@ -44,11 +44,11 @@ "treefmt-nix": "treefmt-nix_2" }, "locked": { - "lastModified": 1776557977, - "narHash": "sha256-j+UWg3fR6jWKPqkPoqRf1a6nR1b/AnZXDuh04H+voUc=", - "rev": "e9ced950bedc726492e5cb52139bf5f17258dc69", + "lastModified": 1778462753, + "narHash": "sha256-/9qWZbrwoVWP0YWuC1Z5HMEb/oy6rNsjypUKTuk1PB4=", + "rev": "09551fdb27a7e5712bef371e9271034d503242ed", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/e9ced950bedc726492e5cb52139bf5f17258dc69.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/09551fdb27a7e5712bef371e9271034d503242ed.tar.gz" }, "original": { "type": "tarball", @@ -71,11 +71,11 @@ ] }, "locked": { - "lastModified": 1776506822, - "narHash": "sha256-WlxAhXEoDHbkfFw3uNYra0CXce7pBk314x9chPu7ycE=", - "rev": "c3f48f5931b27bb9cc58de8799d36ecefb867d98", + "lastModified": 1776654564, + "narHash": "sha256-5bpzOOXsaAr4g25/ghtKdYO17xg0l+MieCcWgqx24eY=", + "rev": "ad23733ebc47284dc1158db43218cf4027824aee", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/c3f48f5931b27bb9cc58de8799d36ecefb867d98.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/ad23733ebc47284dc1158db43218cf4027824aee.tar.gz" }, "original": { "type": "tarball", @@ -90,11 +90,11 @@ ] }, "locked": { - "lastModified": 1773889306, - "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", + "lastModified": 1776613567, + "narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=", "owner": "nix-community", "repo": "disko", - "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", + "rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d", "type": "github" }, "original": { @@ -110,11 +110,11 @@ ] }, "locked": { - "lastModified": 1773889306, - "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", + "lastModified": 1777713215, + "narHash": "sha256-8GzXDOXckDWwST8TY5DbwYFjdvQLlP7K9CLSVx6iTTo=", "owner": "nix-community", "repo": "disko", - "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", + "rev": "63b4e7e6cf75307c1d26ac3762b886b5b0247267", "type": "github" }, "original": { @@ -167,11 +167,11 @@ ] }, "locked": { - "lastModified": 1775087534, - "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", + "lastModified": 1777988971, + "narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", + "rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", "type": "github" }, "original": { @@ -223,11 +223,11 @@ ] }, "locked": { - "lastModified": 1776184304, - "narHash": "sha256-No6QGBmIv5ChiwKCcbkxjdEQ/RO2ZS1gD7SFy6EZ7rc=", + "lastModified": 1778444552, + "narHash": "sha256-f18pIiR9q/p1vHY93gmAum7aHhQOG49oGvAB9+lptRo=", "owner": "nix-community", "repo": "home-manager", - "rev": "3c7524c68348ef79ce48308e0978611a050089b2", + "rev": "dcebe66f958673729896eec2de4abfd86ef22d21", "type": "github" }, "original": { @@ -265,11 +265,11 @@ ] }, "locked": { - "lastModified": 1774991950, - "narHash": "sha256-kScKj3qJDIWuN9/6PMmgy5esrTUkYinrO5VvILik/zw=", + "lastModified": 1777594677, + "narHash": "sha256-h90sHwoRJLRvaTpZroTvU2JRHDFj0czUafM8eqLe1RI=", "owner": "nix-community", "repo": "home-manager", - "rev": "f2d3e04e278422c7379e067e323734f3e8c585a7", + "rev": "899c08a15beae5da51a5cecd6b2b994777a948da", "type": "github" }, "original": { @@ -321,11 +321,11 @@ ] }, "locked": { - "lastModified": 1775037210, - "narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=", + "lastModified": 1777780666, + "narHash": "sha256-8wURyQMdDkGUarSTKOGdCuFfYiwa3HbzwscUfn3STDE=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "06648f4902343228ce2de79f291dd5a58ee12146", + "rev": "8c62fba0854ba15c8917aed18894dbccb48a3777", "type": "github" }, "original": { @@ -339,17 +339,18 @@ "inputs": { "flake-utils": "flake-utils", "home-manager": "home-manager_2", - "nix-steipete-tools": "nix-steipete-tools", + "nix-openclaw-tools": "nix-openclaw-tools", "nixpkgs": [ "nixpkgs" - ] + ], + "qmd": "qmd" }, "locked": { - "lastModified": 1776183358, - "narHash": "sha256-uRWaRXGhkyGWMbNgQcmx0+RPzPLenVGopkNHgAEfmBQ=", + "lastModified": 1778353239, + "narHash": "sha256-g0yC+loN19X3Xyn6RuBHeWzevH7Qymt0REW+kyGuCLY=", "owner": "openclaw", "repo": "nix-openclaw", - "rev": "53aac0dce0810c40c75793fdad3d41b0f7e7baaf", + "rev": "e2ea91056fdd0836bef96326a2b687277dbe3e1c", "type": "github" }, "original": { @@ -358,6 +359,24 @@ "type": "github" } }, + "nix-openclaw-tools": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1778060041, + "narHash": "sha256-tXWkN1VnwFG8XlRqW/e7VwbKnUfyU9tB7YDm9QHJXTY=", + "owner": "openclaw", + "repo": "nix-openclaw-tools", + "rev": "4c1cee3c7eaf68f9de0f756be1484534f5bb5f34", + "type": "github" + }, + "original": { + "owner": "openclaw", + "repo": "nix-openclaw-tools", + "type": "github" + } + }, "nix-select": { "locked": { "lastModified": 1763303120, @@ -371,35 +390,17 @@ "url": "https://git.clan.lol/clan/nix-select/archive/main.tar.gz" } }, - "nix-steipete-tools": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1773561580, - "narHash": "sha256-wT0bKTp45YnMkc4yXQvk943Zz/rksYiIjEXGdWzxnic=", - "owner": "openclaw", - "repo": "nix-steipete-tools", - "rev": "cd4c429ff3b3aaef9f92e59812cf2baf5704b86f", - "type": "github" - }, - "original": { - "owner": "openclaw", - "repo": "nix-steipete-tools", - "type": "github" - } - }, "nixos-wsl": { "inputs": { "flake-compat": "flake-compat", "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1776255237, - "narHash": "sha256-LQjlc0VEn55WAT4BiI8sIsokb/2FNlcbBD+Xr3MTE24=", + "lastModified": 1777732699, + "narHash": "sha256-2uX/XtOWZ/oy2rerRynVhqVA//ZXZ3Fo60PikLHEPQc=", "owner": "nix-community", "repo": "NixOS-WSL", - "rev": "9a8c2a85f1ffdcecfb0f9c52c5a73c49ceb43911", + "rev": "5482f113fd31ebac131d1ebeb2ae90bf0d5e41f5", "type": "github" }, "original": { @@ -427,11 +428,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1773734432, - "narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=", + "lastModified": 1776169885, + "narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cda48547b432e8d3b18b4180ba07473762ec8558", + "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", "type": "github" }, "original": { @@ -443,11 +444,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1776255774, - "narHash": "sha256-psVTpH6PK3q1htMJpmdz1hLF5pQgEshu7gQWgKO6t6Y=", + "lastModified": 1778274207, + "narHash": "sha256-I4puXmX1iovcCHZlRmztO3vW0mAbbRvq4F8wgIMQ1MM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "566acc07c54dc807f91625bb286cb9b321b5f42a", + "rev": "b3da656039dc7a6240f27b2ef8cc6a3ef3bccae7", "type": "github" }, "original": { @@ -471,6 +472,32 @@ "type": "indirect" } }, + "qmd": { + "inputs": { + "flake-utils": [ + "nix-openclaw", + "flake-utils" + ], + "nixpkgs": [ + "nix-openclaw", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775429264, + "narHash": "sha256-bqIVaNRTa8H5vrw3RwsD7QdtTa0xNvRuEVzlzE1hIBQ=", + "owner": "tobi", + "repo": "qmd", + "rev": "65cd1b3fd02891d1ee0eefa751620918664fa321", + "type": "github" + }, + "original": { + "owner": "tobi", + "ref": "v2.1.0", + "repo": "qmd", + "type": "github" + } + }, "root": { "inputs": { "clan-community": "clan-community", @@ -638,11 +665,11 @@ ] }, "locked": { - "lastModified": 1776317517, - "narHash": "sha256-JP1XVRabZquf7pnXvRUjp7DV+EBrB6Qmp3+vG3HMy/k=", + "lastModified": 1778394798, + "narHash": "sha256-/jR8bModWv0ji305ecMgAB+2eaXLZiYdH+9Z4JIRkuA=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "0a7be59e988bb2cb452080f59aaabae70bc415ae", + "rev": "45bc54456044b96492923739bfae633e1a4352e1", "type": "github" }, "original": { From 0f34d2508de2299794bada37ee7f195aabb61e0b Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 17 May 2026 12:55:58 +0200 Subject: [PATCH 157/185] feat: add kf.dannydannydanny.me portfolio vhost :sparkles: Routes the new subdomain to the existing notes service on phantom-ship :8092 (Host-header routed). Serves Kyranna Fardi's architecture portfolio. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/vps-relay.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 1914698..bd93b25 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -145,6 +145,11 @@ "dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 ''; + # kf — Kyranna Fardi architecture portfolio. Same notes service on + # phantom :8092, routed by Host header (PORTFOLIO_HOST). + "kf.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 + ''; }; }; From 5d4f2048a63ddae41c3a92d3fe37435c7a13983b Mon Sep 17 00:00:00 2001 From: Hara Date: Wed, 20 May 2026 15:37:31 +0200 Subject: [PATCH 158/185] hara: heartbeat timer reduced to once daily at 06:07 --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 90886cc..4e71c54 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -545,7 +545,7 @@ in systemd.timers.hara-heartbeat = { wantedBy = [ "timers.target" ]; timerConfig = { - OnCalendar = "06,10,14,18:07"; + OnCalendar = "06:07"; Timezone = "Europe/Copenhagen"; Persistent = true; }; From 0c11628f734e2c83e330247ad6edefd83605fcbb Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 20 May 2026 18:44:51 +0200 Subject: [PATCH 159/185] phantom-ship: Catppuccin theme for Forgejo (mocha + mauve) Adds catppuccin flake input and wires its NixOS module into phantom-ship's imports via clan.nix. Enables catppuccin.forgejo with mocha flavor + mauve accent on the running Forgejo instance. Module ref: https://nix.catppuccin.com/options/main/nixos/catppuccin.forgejo/ --- flake-modules/clan.nix | 1 + flake.lock | 21 +++++++++++++++++++++ flake.nix | 6 ++++++ nixos/hosts/phantom-ship.nix | 8 ++++++++ 4 files changed, 36 insertions(+) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 6b4a5d0..d8fe760 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -167,6 +167,7 @@ in { } clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway + inputs.catppuccin.nixosModules.catppuccin ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.dotfiles-rebuild config.flake.nixosModules.server-debug-tools diff --git a/flake.lock b/flake.lock index c524fd2..6ae35fc 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,25 @@ { "nodes": { + "catppuccin": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1779125773, + "narHash": "sha256-F34zmAgMQXHwvFb9SpCilX4cAIfF4+KvpzrJqnkNLJE=", + "owner": "catppuccin", + "repo": "nix", + "rev": "86996e2c4ee6a091fddb10de56dd21a1a5972bcb", + "type": "github" + }, + "original": { + "owner": "catppuccin", + "repo": "nix", + "type": "github" + } + }, "clan-community": { "inputs": { "clan-core": [ @@ -500,6 +520,7 @@ }, "root": { "inputs": { + "catppuccin": "catppuccin", "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", diff --git a/flake.nix b/flake.nix index 30e7d71..b0b6579 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,12 @@ nix-openclaw.url = "github:openclaw/nix-openclaw"; nix-openclaw.inputs.nixpkgs.follows = "nixpkgs"; + # Catppuccin NixOS module — used for theming Forgejo (and any future + # NixOS-level services). Home-manager/neovim/alacritty Catppuccin lives + # outside this input (separate nixpkgs packages). + catppuccin.url = "github:catppuccin/nix"; + catppuccin.inputs.nixpkgs.follows = "nixpkgs"; + clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 90886cc..d3ebf6f 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -581,6 +581,14 @@ in }; }; + # Catppuccin theme for Forgejo — module wired in via clan.nix's + # phantom-ship.imports (inputs.catppuccin.nixosModules.catppuccin). + catppuccin.forgejo = { + enable = true; + flavor = "mocha"; + accent = "mauve"; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 2e9441f367e92f12b35851778e1957b385c08c42 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 20 May 2026 19:31:22 +0200 Subject: [PATCH 160/185] Retire dotfiles-rebuild, switch to dm-pull-deploy push timer - Drop modules/dotfiles-rebuild.nix and its imports in clan.nix; sunken-ship + phantom-ship no longer ship the legacy 15-min rebuild-from-git timer. - Add dm-pull-deploy-push systemd timer on sunken-ship: every 15min runs dm-send-deploy to announce origin/main rev via data-mesher gossip (sunken is the dm-pull-deploy push node). - Fix mulbo-pull service path: add openssh so 'git fetch' over an SSH remote stops failing with 'cannot run ssh'. - vps-relay authorized_keys: rename Mac key comment to mac-admin, add sunken-ship's actual ed25519 key for ZT mesh debugging. - home.nix: add cinny-desktop (Matrix client). - neovim: enable cursorline. --- flake-modules/clan.nix | 2 -- flake-modules/nixos-modules.nix | 3 +-- modules/dotfiles-rebuild.nix | 44 --------------------------------- nixos/home/danny/home.nix | 1 + nixos/hosts/sunken-ship.nix | 40 +++++++++++++++++++++++++++--- nixos/hosts/vps-relay.nix | 9 +++++-- nixos/neovim.nix | 1 + 7 files changed, 46 insertions(+), 54 deletions(-) delete mode 100644 modules/dotfiles-rebuild.nix diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index d8fe760..2207db4 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -125,7 +125,6 @@ in { } clanHostsModule ../nixos/hosts/sunken-ship.nix - config.flake.nixosModules.dotfiles-rebuild config.flake.nixosModules.server-debug-tools config.flake.nixosModules.monitoring-node-exporter config.flake.nixosModules.monitoring-prometheus-server @@ -169,7 +168,6 @@ in { inputs.nix-openclaw.nixosModules.openclaw-gateway inputs.catppuccin.nixosModules.catppuccin ../nixos/hosts/phantom-ship.nix - config.flake.nixosModules.dotfiles-rebuild config.flake.nixosModules.server-debug-tools config.flake.nixosModules.monitoring-node-exporter inputs.home-manager.nixosModules.home-manager diff --git a/flake-modules/nixos-modules.nix b/flake-modules/nixos-modules.nix index 3f6bf96..3dd7929 100644 --- a/flake-modules/nixos-modules.nix +++ b/flake-modules/nixos-modules.nix @@ -1,9 +1,8 @@ # Expose reusable NixOS modules via `flake.nixosModules`. # # Consume from a host's flake-module via: -# modules = [ config.flake.nixosModules.dotfiles-rebuild ]; +# modules = [ config.flake.nixosModules.server-debug-tools ]; { ... }: { - flake.nixosModules.dotfiles-rebuild = ../modules/dotfiles-rebuild.nix; flake.nixosModules.server-debug-tools = ../modules/server-debug-tools.nix; flake.nixosModules.monitoring-node-exporter = ../modules/monitoring-node-exporter.nix; flake.nixosModules.monitoring-prometheus-server = ../modules/monitoring-prometheus-server.nix; diff --git a/modules/dotfiles-rebuild.nix b/modules/dotfiles-rebuild.nix deleted file mode 100644 index de6ac87..0000000 --- a/modules/dotfiles-rebuild.nix +++ /dev/null @@ -1,44 +0,0 @@ -# Shared auto-rebuild-from-git service for homelab hosts. -# -# Every 15 min: git fetch origin, fast-forward main, and if there were any -# new commits run nixos-rebuild switch against `#`. -# -# Assumes /etc/dotfiles is an already-cloned checkout of the dotfiles repo. -{ config, lib, pkgs, ... }: -let - dotfilesDir = "/etc/dotfiles"; - flakeRef = "${dotfilesDir}#${config.networking.hostName}"; -in { - environment.systemPackages = [ pkgs.git ]; - - # Trust /etc/dotfiles as root even though it's owned by `danny`. - # nix/libgit2 reads safe.directory from /etc/gitconfig; the GIT_CONFIG_* - # env vars on the service only affect the git CLI, not nix. - programs.git.enable = true; - programs.git.config.safe.directory = [ dotfilesDir ]; - - systemd.services.dotfiles-rebuild = { - description = "Pull dotfiles and run nixos-rebuild if repo changed"; - path = with pkgs; [ git nix nixos-rebuild ]; - environment.GIT_CONFIG_COUNT = "1"; - environment.GIT_CONFIG_KEY_0 = "safe.directory"; - environment.GIT_CONFIG_VALUE_0 = dotfilesDir; - script = '' - set -euo pipefail - cd ${dotfilesDir} - git fetch origin - if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then - exit 0 - fi - git pull origin main - exec nixos-rebuild switch --flake ${flakeRef} - ''; - serviceConfig.Type = "oneshot"; - }; - - systemd.timers.dotfiles-rebuild = { - wantedBy = [ "timers.target" ]; - timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes - timerConfig.RandomizedDelaySec = "2min"; - }; -} diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 739abc2..c110a83 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -231,6 +231,7 @@ zed-editor code-cursor cursor-cli + cinny-desktop # Matrix client (Tauri wrapper around the Cinny web app) dfu-util # USB DFU firmware flasher (Flipper Zero etc.) discord mapscii diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 095b986..fd14e1b 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -72,7 +72,7 @@ # x86_64-linux builds here via ssh-ng://danny@sunken-ship-zt). nix.settings.trusted-users = [ "root" "danny" ]; environment.systemPackages = with pkgs; [ - git # clone/bootstrap and dotfiles-rebuild timer + git # clone/bootstrap, repo-pull timers, dm-pull-deploy push brightnessctl # manual backlight; replaces removed `light` from nixpkgs uxplay # AirPlay mirroring receiver alsa-utils # aplay, amixer, arecord for audio debugging @@ -347,7 +347,10 @@ # not in the repo, so they survive pulls. systemd.services.mulbo-pull = { description = "Pull mulbo repo and restart mulbo-server if changed"; - path = with pkgs; [ git systemd ]; + # openssh: `git fetch origin` over an SSH remote forks `ssh`; without + # it git dies with "cannot run ssh: No such file or directory" and the + # unit fails (shows up as system `degraded`). + path = with pkgs; [ git openssh systemd ]; environment = { GIT_CONFIG_COUNT = "1"; GIT_CONFIG_KEY_0 = "safe.directory"; @@ -372,6 +375,33 @@ timerConfig.RandomizedDelaySec = "2min"; }; + # dm-pull-deploy push automation. sunken-ship is the push node for the + # clan dm-pull-deploy instance (wired in flake-modules/clan.nix), but + # the upstream module only ships a manual `dm-send-deploy` binary — no + # scheduler. This timer announces the latest origin/main rev over + # data-mesher gossip; the watchers (dm-pull-deploy.path on sunken + + # phantom) compare and only rebuild when the rev actually changes, so + # re-announcing the same rev is a cheap no-op. This is the replacement + # for the legacy dotfiles-rebuild pull timer (being retired). + # + # dm-send-deploy self-discovers the rev via `git ls-remote` and signs + # with /run/secrets/vars/dm-pull-deploy-signing-key — needs root. + systemd.services.dm-pull-deploy-push = { + description = "Announce latest origin/main rev via data-mesher (dm-pull-deploy push)"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "/run/current-system/sw/bin/dm-send-deploy"; + User = "root"; + }; + }; + + systemd.timers.dm-pull-deploy-push = { + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*-*-* *:04/15:00"; # every 15 min, offset from the other pull timers + timerConfig.RandomizedDelaySec = "2min"; + timerConfig.Persistent = true; + }; + # One-shot backfill: walks Navidrome's media_file, computes # (sha256, chromaprint) per file, populates mulbo-server's tracks_index # with the corresponding navidrome_track_id. Idempotent — existing rows @@ -443,6 +473,8 @@ }; }; - # Auto-rebuild service/timer + safe.directory provided by the - # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). + # Deploys now flow through clan dm-pull-deploy: the dm-pull-deploy-push + # timer above announces origin/main, and the dm-pull-deploy.path watcher + # rebuilds on change. The legacy pull-based dotfiles-rebuild module was + # retired 2026-05-19. } diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index bd93b25..85ed329 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -46,8 +46,13 @@ isNormalUser = true; extraGroups = [ "wheel" ]; openssh.authorizedKeys.keys = [ - # Same pubkey used to reach sunken-ship; set at install via clan. - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship" + # Mac admin key (~/.ssh/id_ed25519_sunken_ship on the laptop — the + # key the Mac uses to reach the fleet). Used for `clan machines + # update vps-relay` from the Mac and at install via clan. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin" + # sunken-ship's own key, so the push node can SSH into vps-relay + # over ZeroTier for mesh introspection / debugging. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship" ]; }; users.users.root.openssh.authorizedKeys.keys = diff --git a/nixos/neovim.nix b/nixos/neovim.nix index 59a6f85..75b2335 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -41,6 +41,7 @@ end -- General options + vim.opt.cursorline = true vim.opt.mouse = "a" vim.opt.listchars = { tab = "→ ", space = "·", nbsp = "␣", trail = "•", eol = "¶", precedes = "«", extends = "»" } vim.opt.clipboard:append("unnamedplus") From cbf0defa34acc5965b8230fc08746dfeaf13cc69 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 20 May 2026 19:31:22 +0200 Subject: [PATCH 161/185] phantom-ship/forgejo: switch to catppuccin-mauve-auto (light in light mode) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The catppuccin nix module only generates the static flavor+accent combinations and sets DEFAULT_THEME to e.g. catppuccin-mocha-mauve. The auto-switching CSS files (catppuccin--auto) ship in the gitea-theme assets but aren't wired into THEMES. Override DEFAULT_THEME to catppuccin-mauve-auto so the browser's prefers-color-scheme decides — latte (light) in light mode, mocha (dark) in dark mode. Append all auto variants + the four mauve flavor variants to THEMES so users can still pick from the appearance settings. --- nixos/hosts/phantom-ship.nix | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index d91e229..ec3a6b5 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -583,12 +583,37 @@ in # Catppuccin theme for Forgejo — module wired in via clan.nix's # phantom-ship.imports (inputs.catppuccin.nixosModules.catppuccin). + # The module's `flavor` option picks ONE static flavor; the auto + # variants (catppuccin--auto) follow prefers-color-scheme — + # latte in light mode, mocha in dark. We override DEFAULT_THEME + + # THEMES below to use the auto variant. catppuccin.forgejo = { enable = true; - flavor = "mocha"; + flavor = "mocha"; # static fallback; auto-variant takes precedence below accent = "mauve"; }; + # Default to the auto-switching mauve theme (light in light mode, dark + # in dark mode). Replace THEMES with builtins + every catppuccin auto + # variant + the four mauve flavor variants, so the user-level + # appearance picker can still offer them. + services.forgejo.settings.ui = { + DEFAULT_THEME = lib.mkForce "catppuccin-mauve-auto"; + THEMES = lib.mkForce (lib.concatStringsSep "," [ + # Forgejo builtins + "forgejo-auto" "forgejo-light" "forgejo-dark" + # Auto-switching catppuccin (light in light mode, dark in dark mode) + "catppuccin-blue-auto" "catppuccin-flamingo-auto" "catppuccin-green-auto" + "catppuccin-lavender-auto" "catppuccin-maroon-auto" "catppuccin-mauve-auto" + "catppuccin-peach-auto" "catppuccin-pink-auto" "catppuccin-red-auto" + "catppuccin-rosewater-auto" "catppuccin-sapphire-auto" "catppuccin-sky-auto" + "catppuccin-teal-auto" "catppuccin-yellow-auto" + # Static mauve variants for manual override + "catppuccin-latte-mauve" "catppuccin-frappe-mauve" + "catppuccin-macchiato-mauve" "catppuccin-mocha-mauve" + ]); + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 9793d5ef7cdecf41985697bc02549b3ee93baf9d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 20 May 2026 20:13:44 +0200 Subject: [PATCH 162/185] Revert "phantom-ship/forgejo: switch to catppuccin-mauve-auto (light in light mode)" This reverts commit cbf0defa34acc5965b8230fc08746dfeaf13cc69. --- nixos/hosts/phantom-ship.nix | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index ec3a6b5..d91e229 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -583,37 +583,12 @@ in # Catppuccin theme for Forgejo — module wired in via clan.nix's # phantom-ship.imports (inputs.catppuccin.nixosModules.catppuccin). - # The module's `flavor` option picks ONE static flavor; the auto - # variants (catppuccin--auto) follow prefers-color-scheme — - # latte in light mode, mocha in dark. We override DEFAULT_THEME + - # THEMES below to use the auto variant. catppuccin.forgejo = { enable = true; - flavor = "mocha"; # static fallback; auto-variant takes precedence below + flavor = "mocha"; accent = "mauve"; }; - # Default to the auto-switching mauve theme (light in light mode, dark - # in dark mode). Replace THEMES with builtins + every catppuccin auto - # variant + the four mauve flavor variants, so the user-level - # appearance picker can still offer them. - services.forgejo.settings.ui = { - DEFAULT_THEME = lib.mkForce "catppuccin-mauve-auto"; - THEMES = lib.mkForce (lib.concatStringsSep "," [ - # Forgejo builtins - "forgejo-auto" "forgejo-light" "forgejo-dark" - # Auto-switching catppuccin (light in light mode, dark in dark mode) - "catppuccin-blue-auto" "catppuccin-flamingo-auto" "catppuccin-green-auto" - "catppuccin-lavender-auto" "catppuccin-maroon-auto" "catppuccin-mauve-auto" - "catppuccin-peach-auto" "catppuccin-pink-auto" "catppuccin-red-auto" - "catppuccin-rosewater-auto" "catppuccin-sapphire-auto" "catppuccin-sky-auto" - "catppuccin-teal-auto" "catppuccin-yellow-auto" - # Static mauve variants for manual override - "catppuccin-latte-mauve" "catppuccin-frappe-mauve" - "catppuccin-macchiato-mauve" "catppuccin-mocha-mauve" - ]); - }; - # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From b11add852543b7820c405a21622f62308d82544e Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Wed, 20 May 2026 20:13:44 +0200 Subject: [PATCH 163/185] Revert "Merge add-catppuccin-forgejo: Catppuccin theme on Forgejo" This reverts commit 1b0eb5835ddef99d159b6d6fb20ce9853c30eab5, reversing changes made to 5d4f2048a63ddae41c3a92d3fe37435c7a13983b. --- flake-modules/clan.nix | 1 - flake.lock | 21 --------------------- flake.nix | 6 ------ nixos/hosts/phantom-ship.nix | 8 -------- 4 files changed, 36 deletions(-) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 2207db4..30fe4c9 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -166,7 +166,6 @@ in { } clanHostsModule inputs.nix-openclaw.nixosModules.openclaw-gateway - inputs.catppuccin.nixosModules.catppuccin ../nixos/hosts/phantom-ship.nix config.flake.nixosModules.server-debug-tools config.flake.nixosModules.monitoring-node-exporter diff --git a/flake.lock b/flake.lock index 6ae35fc..c524fd2 100644 --- a/flake.lock +++ b/flake.lock @@ -1,25 +1,5 @@ { "nodes": { - "catppuccin": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1779125773, - "narHash": "sha256-F34zmAgMQXHwvFb9SpCilX4cAIfF4+KvpzrJqnkNLJE=", - "owner": "catppuccin", - "repo": "nix", - "rev": "86996e2c4ee6a091fddb10de56dd21a1a5972bcb", - "type": "github" - }, - "original": { - "owner": "catppuccin", - "repo": "nix", - "type": "github" - } - }, "clan-community": { "inputs": { "clan-core": [ @@ -520,7 +500,6 @@ }, "root": { "inputs": { - "catppuccin": "catppuccin", "clan-community": "clan-community", "clan-core": "clan-core", "disko": "disko_2", diff --git a/flake.nix b/flake.nix index b0b6579..30e7d71 100644 --- a/flake.nix +++ b/flake.nix @@ -25,12 +25,6 @@ nix-openclaw.url = "github:openclaw/nix-openclaw"; nix-openclaw.inputs.nixpkgs.follows = "nixpkgs"; - # Catppuccin NixOS module — used for theming Forgejo (and any future - # NixOS-level services). Home-manager/neovim/alacritty Catppuccin lives - # outside this input (separate nixpkgs packages). - catppuccin.url = "github:catppuccin/nix"; - catppuccin.inputs.nixpkgs.follows = "nixpkgs"; - clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index d91e229..4e71c54 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -581,14 +581,6 @@ in }; }; - # Catppuccin theme for Forgejo — module wired in via clan.nix's - # phantom-ship.imports (inputs.catppuccin.nixosModules.catppuccin). - catppuccin.forgejo = { - enable = true; - flavor = "mocha"; - accent = "mauve"; - }; - # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). } From 3dcbdd408a37143b2c16eb516178c1f388a444bd Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 22 May 2026 21:15:20 +0200 Subject: [PATCH 164/185] =?UTF-8?q?chore:=20unpin=20clan-community=20now?= =?UTF-8?q?=20that=20dm-pull-deploy=20fix=20merged=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR clan/clan-community#25 (machine.name hyphen sanitization) merged upstream, so swap clan-community.url from the fork branch back to clan/clan-community/archive/main.tar.gz and update flake.lock to upstream rev 81e4c9c. Eval confirms byte-identical host closures. Also finishes the dotfiles-rebuild retirement: phantom-ship.nix still referenced the now-deleted modules/dotfiles-rebuild.nix in comments. --- flake.lock | 43 ++++++++++-------------------------- flake.nix | 7 +++--- nixos/hosts/phantom-ship.nix | 7 +++--- 3 files changed, 19 insertions(+), 38 deletions(-) diff --git a/flake.lock b/flake.lock index c524fd2..59c41ec 100644 --- a/flake.lock +++ b/flake.lock @@ -9,22 +9,18 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems", "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1778267301, - "narHash": "sha256-/SEnX0wGQuvQ78EKWTIDA8nCUaJhCD0nVbtP5evFdSQ=", - "ref": "fix/dm-pull-deploy-hyphen-hostnames", - "rev": "bd2f9c63ed5613eb52a03116df88b06275171f55", - "revCount": 47, - "type": "git", - "url": "https://git.clan.lol/dannydannydanny/clan-community.git" + "lastModified": 1779453564, + "narHash": "sha256-q7iVGGhZYtAwsjf7sIKcYD5IgsTTTobWP/EStaDCUZc=", + "rev": "81e4c9cded645d0384812dd6b8f05bd2475ffe64", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/clan-community/archive/81e4c9cded645d0384812dd6b8f05bd2475ffe64.tar.gz" }, "original": { - "ref": "fix/dm-pull-deploy-hyphen-hostnames", - "type": "git", - "url": "https://git.clan.lol/dannydannydanny/clan-community.git" + "type": "tarball", + "url": "https://git.clan.lol/clan/clan-community/archive/main.tar.gz" } }, "clan-core": { @@ -40,7 +36,7 @@ "nixpkgs" ], "sops-nix": "sops-nix", - "systems": "systems_2", + "systems": "systems", "treefmt-nix": "treefmt-nix_2" }, "locked": { @@ -182,7 +178,7 @@ }, "flake-utils": { "inputs": { - "systems": "systems_3" + "systems": "systems_2" }, "locked": { "lastModified": 1731533236, @@ -200,7 +196,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_4" + "systems": "systems_3" }, "locked": { "lastModified": 1681202837, @@ -536,21 +532,6 @@ } }, "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { "locked": { "lastModified": 1774449309, "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", @@ -566,7 +547,7 @@ "type": "github" } }, - "systems_3": { + "systems_2": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -581,7 +562,7 @@ "type": "github" } }, - "systems_4": { + "systems_3": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", diff --git a/flake.nix b/flake.nix index 30e7d71..2970510 100644 --- a/flake.nix +++ b/flake.nix @@ -29,10 +29,9 @@ clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.flake-parts.follows = "flake-parts"; - # clan-community: dm-pull-deploy etc. Pinned to our fork's fix branch - # until clan/clan-community#25 (machine.name hyphen sanitization) lands. - # Swap back to `archive/main.tar.gz` when merged. - clan-community.url = "git+https://git.clan.lol/dannydannydanny/clan-community.git?ref=fix/dm-pull-deploy-hyphen-hostnames"; + # clan-community: dm-pull-deploy etc. Back on upstream main since + # clan/clan-community#25 (machine.name hyphen sanitization) merged. + clan-community.url = "https://git.clan.lol/clan/clan-community/archive/main.tar.gz"; clan-community.inputs.nixpkgs.follows = "nixpkgs"; clan-community.inputs.clan-core.follows = "clan-core"; }; diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 4e71c54..f232e63 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -111,7 +111,7 @@ in # Passwordless sudo for wheel. security.sudo.wheelNeedsPassword = false; environment.systemPackages = with pkgs; [ - git # clone/bootstrap and dotfiles-rebuild timer + git # clone/bootstrap and dm-pull-deploy nodejs # npm for openclaw plugin installs python3 # node-gyp dependency for openclaw plugins wakeonlan # wake rusty-anchor: wakeonlan 00:16:cb:87:20:ba @@ -581,6 +581,7 @@ in }; }; - # Auto-rebuild service/timer + safe.directory provided by the - # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). + # Deploys flow through clan dm-pull-deploy: the dm-pull-deploy.path + # watcher rebuilds when sunken-ship announces a new origin/main rev. + # The legacy pull-based dotfiles-rebuild module was retired 2026-05-19. } From cda9c4cf0fa99118e425360ac25476753b62071c Mon Sep 17 00:00:00 2001 From: Danny Date: Sat, 23 May 2026 11:51:20 +0200 Subject: [PATCH 165/185] sunken-ship: drop python-telegram-bot from fitness-bot pythonEnvs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bot.py was deleted upstream — neither prod nor shipyard launches a polling bot anymore. server.py only needs python-dotenv + aiohttp. Also refresh the prod section's comment + service description to reflect the Mini-App-only architecture. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/server-installer-usb.md | 37 +++++++++++++++++++++--- flake-modules/installer-iso.nix | 8 +++-- nixos/hosts/sunken-ship.nix | 16 +++++----- scripts/build-installer-iso-on-server.sh | 35 +++++++++++++++++----- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/docs/server-installer-usb.md b/docs/server-installer-usb.md index 4c69d53..295f227 100644 --- a/docs/server-installer-usb.md +++ b/docs/server-installer-usb.md @@ -94,16 +94,45 @@ sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M ## Live-system WiFi (optional, custom ISO only) -Create `nixos/installer-wifi.nix` (gitignored): +The minimal installer ISO runs NetworkManager, so live-system WiFi must be a +declarative NetworkManager profile. `networking.wireless` / wpa_supplicant does +**not** work here — NixOS asserts you cannot combine `networking.networkmanager` +with `networking.wireless.networks`. + +Create `nixos/installer-wifi.nix` (gitignored — it holds the PSK): ```nix { - networking.wireless.enable = true; - networking.wireless.networks."YourSSID".psk = "your-password"; + networking.networkmanager.ensureProfiles.profiles.installer-wifi = { + connection = { + id = "installer-wifi"; + type = "wifi"; + }; + wifi = { + mode = "infrastructure"; + ssid = "YourSSID"; + }; + wifi-security = { + auth-alg = "open"; + key-mgmt = "wpa-psk"; + psk = "your-password"; + }; + ipv4.method = "auto"; + ipv6.method = "auto"; + }; } ``` -Add to flake's installer-iso modules, rebuild ISO on Linux. +`flake-modules/installer-iso.nix` auto-includes this file when present (via a +`builtins.pathExists` check) — no flake edit needed. Because the file is +gitignored, the flake only sees it once it is staged: + +- **`build-installer-iso-on-server.sh`** copies the file to the build host and + runs `git add -f` automatically. +- For a **direct `nix build`**, run `git add -f nixos/installer-wifi.nix` first + (staging is enough — never commit it; it contains the PSK). + +Then rebuild the ISO on Linux. ## Installed-system WiFi (optional) diff --git a/flake-modules/installer-iso.nix b/flake-modules/installer-iso.nix index fc18929..03609ab 100644 --- a/flake-modules/installer-iso.nix +++ b/flake-modules/installer-iso.nix @@ -1,9 +1,13 @@ { inputs, self, ... }: { # Custom minimal installer ISO (build with: nix build .#installer-iso). - # Optional: add ./installer-wifi.nix (gitignored) to modules for live WiFi. + # nixos/installer-wifi.nix (gitignored) is auto-included when present, to + # preconfigure live-system WiFi. See docs/server-installer-usb.md. flake.nixosConfigurations.installer-iso = inputs.nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - modules = [ ../nixos/installer-iso.nix ]; + modules = [ ../nixos/installer-iso.nix ] + ++ inputs.nixpkgs.lib.optional + (builtins.pathExists ../nixos/installer-wifi.nix) + ../nixos/installer-wifi.nix; }; flake.packages.x86_64-linux.installer-iso = diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index fd14e1b..0ab8f33 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -149,23 +149,26 @@ }; }; - # BigBiggerBiggestBot — Telegram fitness tracker with Mini App. + # BigBiggerBiggestBot — Mini App backend (no Telegram polling). # Code: https://github.com/DannyDannyDanny/bigbiggerbiggestbot cloned at /home/danny/tg_fitness_bot - # Bot token: ~danny/.secrets/bigbiggerbiggestbot + # Bot token (used only for validating Telegram WebApp initData HMACs): + # ~danny/.secrets/bigbiggerbiggestbot # Deployment: fitness-bot-pull timer below runs every 15 min, git pulls, restarts service on changes. # # Mini App URL is fronted by Caddy on the vps-relay host at # https://bbbot.dannydannydanny.me (VPS → ZeroTier → localhost:8080). - # The bot's start.py honors WEBAPP_URL to skip starting its own - # cloudflared Quick Tunnel when we've got a stable URL from the VPS. + # start.py honors WEBAPP_URL to skip starting its own cloudflared + # Quick Tunnel when the stable URL from the VPS is already set. + # + # The slash-command bot (bot.py) was removed in May 2026 — the Mini App + # is now the only interface. No python-telegram-bot dependency required. systemd.services.fitness-bot = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ - python-telegram-bot python-dotenv aiohttp ]); in { - description = "BigBiggerBiggestBot Telegram fitness tracker"; + description = "BigBiggerBiggestBot Mini App backend"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; @@ -228,7 +231,6 @@ # beta in shipyard_poc_bot's launcher → test → git push :main. systemd.services.fitness-bot-shipyard = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ - python-telegram-bot python-dotenv aiohttp ]); diff --git a/scripts/build-installer-iso-on-server.sh b/scripts/build-installer-iso-on-server.sh index d969b68..e7bd002 100755 --- a/scripts/build-installer-iso-on-server.sh +++ b/scripts/build-installer-iso-on-server.sh @@ -5,12 +5,17 @@ # host: SSH host (default: sunken-ship) # output_dir: where to save the ISO on your Mac (default: .) # Override SSH key: SSH_KEY=~/.ssh/my_key ./scripts/build-installer-iso-on-server.sh +# +# If nixos/installer-wifi.nix exists locally (gitignored), it is copied into +# the build and the ISO gets preconfigured live-system WiFi. flake-modules/ +# installer-iso.nix auto-includes it via a builtins.pathExists check. set -euo pipefail HOST="${1:-sunken-ship}" OUT="${2:-.}" +REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) -# Use sunken-ship key if not set (AGENTS.md) +# Default to the sunken-ship SSH key when targeting that host. if [[ -n "${SSH_KEY:-}" ]]; then SSH_OPTS=(-i "$SSH_KEY") elif [[ "$HOST" == "sunken-ship" ]] && [[ -f ~/.ssh/id_ed25519_sunken_ship ]]; then @@ -19,23 +24,37 @@ else SSH_OPTS=() fi -echo "Pushing branch so server can pull..." -git push origin server-installer-usb 2>/dev/null || true +echo "Pushing main so the server can clone the latest..." +git -C "$REPO_ROOT" push origin main 2>/dev/null || true -echo "On $HOST: clone branch, build ISO..." +echo "On $HOST: clone main into ~/dotfiles-iso-build..." ssh "${SSH_OPTS[@]}" "$HOST" 'set -e BUILD_DIR=~/dotfiles-iso-build rm -rf "$BUILD_DIR" - git clone --branch server-installer-usb https://github.com/DannyDannyDanny/dotfiles.git "$BUILD_DIR" - cd "$BUILD_DIR/nixos" + git clone --branch main https://github.com/DannyDannyDanny/dotfiles.git "$BUILD_DIR" +' + +# Optional live-system WiFi: the module is gitignored, so a fresh clone never +# has it. Copy it in and stage it (git add -f) so the flake sees it -- a flake +# build only includes git-tracked files. +if [[ -f "$REPO_ROOT/nixos/installer-wifi.nix" ]]; then + echo "Found nixos/installer-wifi.nix - including live-system WiFi in the ISO." + scp "${SSH_OPTS[@]}" "$REPO_ROOT/nixos/installer-wifi.nix" \ + "$HOST:dotfiles-iso-build/nixos/installer-wifi.nix" + ssh "${SSH_OPTS[@]}" "$HOST" 'cd ~/dotfiles-iso-build && git add -f nixos/installer-wifi.nix' +fi + +echo "On $HOST: build ISO (flake is at the repo root)..." +ssh "${SSH_OPTS[@]}" "$HOST" 'set -e + cd ~/dotfiles-iso-build nix build .#installer-iso ls -la result/iso/ ' -ISO_NAME=$(ssh "${SSH_OPTS[@]}" "$HOST" 'ls ~/dotfiles-iso-build/nixos/result/iso/*.iso 2>/dev/null | head -1') +ISO_NAME=$(ssh "${SSH_OPTS[@]}" "$HOST" 'ls ~/dotfiles-iso-build/result/iso/*.iso 2>/dev/null | head -1') ISO_NAME=$(basename "$ISO_NAME") echo "Copying $ISO_NAME to $OUT ..." -scp "${SSH_OPTS[@]}" "$HOST:~/dotfiles-iso-build/nixos/result/iso/$ISO_NAME" "$OUT/" +scp "${SSH_OPTS[@]}" "$HOST:dotfiles-iso-build/result/iso/$ISO_NAME" "$OUT/" echo "Done. ISO at $OUT/$ISO_NAME" echo "Write to USB: diskutil unmountDisk diskN && sudo dd if=$OUT/$ISO_NAME of=/dev/rdiskN bs=4m" From 1204584ae4f0346b84545bea07473d13b764a2e0 Mon Sep 17 00:00:00 2001 From: Danny Date: Sat, 23 May 2026 12:01:56 +0200 Subject: [PATCH 166/185] fitness-bot: ExecStartPost runs set-bot-presence.py Re-publishes the bot's menu button + description on every restart so @BBBot's chat experience stays in sync with $WEBAPP_URL. Errors are non-fatal. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 0ab8f33..f25a4c4 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -162,6 +162,9 @@ # # The slash-command bot (bot.py) was removed in May 2026 — the Mini App # is now the only interface. No python-telegram-bot dependency required. + # ExecStartPost re-publishes the bot's chat-side presence (menu button, + # description, cleared command list) every time the service starts. + # Idempotent against the Telegram API. Errors are non-fatal (`-` prefix). systemd.services.fitness-bot = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ python-dotenv @@ -179,6 +182,7 @@ serviceConfig = { WorkingDirectory = "/home/danny/tg_fitness_bot"; ExecStart = "${pythonEnv}/bin/python start.py"; + ExecStartPost = "-${pythonEnv}/bin/python scripts/set-bot-presence.py"; Restart = "on-failure"; RestartSec = 10; User = "danny"; From 8fcb43f279c6e6d4c6977781d52a1fd0231e7650 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 24 May 2026 10:35:07 +0200 Subject: [PATCH 167/185] sunken-ship: navidrome Scanner.PurgeMissing = missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stops `missing=1` rows accumulating in media_file. After Phase 7 dedupe, Navidrome's watcher minted ~4k track IDs for files briefly present in /home/danny/music/.mulbo-quarantine; after rm -rf'ing the quarantine, those rows stayed flagged-missing forever — and Substreamer's cached queue then hit 500s on every play attempt ("Internal Server Error: open /srv/music/.mulbo-quarantine/...: no such file or directory"). Cleaned the 4135 quarantine rows manually via SQL; this config prevents recurrence. Trade-off: missing rows used to preserve play-history across "file disappeared, came back" cycles. We prefer client-cache hygiene. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index f25a4c4..f6faa05 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -110,6 +110,12 @@ Address = "0.0.0.0"; Port = 4533; MusicFolder = "/srv/music"; + # Auto-delete `missing=1` rows during scan so transient files + # (e.g. mulbo dedupe quarantine ones) don't accumulate as stale + # track IDs that Substreamer caches and then 500s on. Without + # this, Navidrome keeps missing rows forever (default behaviour + # preserves play history; we trade that for client-cache hygiene). + Scanner.PurgeMissing = "missing"; }; }; From b2df891b20fc2ff8559b305d46598ed9018d75ef Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 24 May 2026 10:35:59 +0200 Subject: [PATCH 168/185] sunken-ship: PurgeMissing = always (valid value; 'missing' was rejected by navidrome 0.61.2) Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index f6faa05..ad78f70 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -115,7 +115,10 @@ # track IDs that Substreamer caches and then 500s on. Without # this, Navidrome keeps missing rows forever (default behaviour # preserves play history; we trade that for client-cache hygiene). - Scanner.PurgeMissing = "missing"; + # Valid values: never | always | full. `always` purges on every + # scan (selective + full); risk on transient missing is fine + # here (stable local disk). + Scanner.PurgeMissing = "always"; }; }; From ba51b6bcf724783a007dfa401190c61bb04ed5f8 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 24 May 2026 20:48:00 +0200 Subject: [PATCH 169/185] tmux: add resurrect + continuum so force-quits don't nuke sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twice in the last few sessions a Love2D force-quit cascaded into killing the tmux server and losing every window. Resurrect snapshots windows / panes / cwd / pane contents (with capture-pane-contents on) to ~/.local/share/tmux/resurrect/last. Continuum auto-saves every 15 min and auto-restores on tmux server start — so the next force-quit just costs up to 15 min of recent activity, not the whole workspace. Manual save: prefix + Ctrl-s. Manual restore: prefix + Ctrl-r. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/home/danny/home.nix | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index c110a83..21c10da 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -88,6 +88,28 @@ catppuccin tmux-fzf extrakto + # tmux-resurrect: prefix + Ctrl-s saves, prefix + Ctrl-r restores. + # Snapshot lives at ~/.local/share/tmux/resurrect/last (window + # layout, working dirs, pane contents if enabled). Survives + # force-quits / reboots / kernel panics. + { + plugin = resurrect; + extraConfig = '' + set -g @resurrect-capture-pane-contents 'on' + set -g @resurrect-strategy-nvim 'session' + ''; + } + # tmux-continuum: auto-saves every 15min and auto-restores on + # tmux server start. With this, the next force-quit just costs + # you up to 15min of recent terminal activity, not the whole + # workspace. + { + plugin = continuum; + extraConfig = '' + set -g @continuum-restore 'on' + set -g @continuum-save-interval '15' + ''; + } ]; }; From 09d25a1899e8574f5891a0f1b94f940aa20e3cee Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 25 May 2026 19:17:05 +0200 Subject: [PATCH 170/185] sunken-ship: add mutagen to mulbo-server env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /enrich/revert endpoint shipped in 20_mulbo commit 5d4e9466 calls enrich.write_tags, which imports mutagen. The main mulbo-server's pythonEnv only had fastapi/uvicorn/python-multipart — first revert attempt 500'd with "ModuleNotFoundError: No module named 'mutagen'". (The enrich oneshot has its own env with mutagen; that's why batch enrichment worked.) Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index ad78f70..768a0df 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -321,6 +321,8 @@ fastapi uvicorn python-multipart + mutagen # tag writeback (enrich.write_tags); needed by the + # /enrich/revert endpoint which reuses enrich.py. ]); in { description = "Mulbo companion service (uploads, dedup, folders)"; From dc7ef476811728321480d100eb6cc71b22ab38b1 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Thu, 4 Jun 2026 12:35:22 +0200 Subject: [PATCH 171/185] sunken-ship: add numpy to mulbo-server env For FFT-based spectral-rolloff analysis (quality.py) used by the chromaprint-dupe winner picker. Effective bitrate alone can't tell a real lossless file from a re-encoded-128kbps-MP3-saved-as-WAV; spectral rolloff catches the upsampled fakes (rolloff < 17kHz = came from lossy source). Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 768a0df..f7b99b3 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -323,6 +323,8 @@ python-multipart mutagen # tag writeback (enrich.write_tags); needed by the # /enrich/revert endpoint which reuses enrich.py. + numpy # FFT for spectral-rolloff analysis (quality.py); used + # by chromaprint-dupe winner picker in --spectral mode. ]); in { description = "Mulbo companion service (uploads, dedup, folders)"; From e43a5eb8809b9f889e5ab4987a594ebddc576a7f Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Thu, 4 Jun 2026 12:40:45 +0200 Subject: [PATCH 172/185] sunken-ship: add ffmpeg to mulbo-server PATH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit quality.py's spectral-rolloff probe shells out to ffmpeg to extract a 30s PCM clip. Without ffmpeg on PATH, subprocess fails silently and get_or_compute_rolloff returns 0.0 — picker degrades to bitrate ranking (which is what we were trying to fix). Add ffmpeg via systemd unit `path = with pkgs; [ ffmpeg ];`. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index f7b99b3..c929d84 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -331,6 +331,10 @@ after = [ "network-online.target" "navidrome.service" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; + # ffmpeg: PCM extraction for quality.py's spectral-rolloff probe + # (chromaprint-dupe winner picker in --spectral mode). Without it, + # the subprocess silently fails and rolloff returns 0Hz. + path = with pkgs; [ ffmpeg ]; environment = { MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads"; MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; From 9283643e07b89525cbcd44573f5c53c45e89f727 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 5 Jun 2026 17:18:57 +0200 Subject: [PATCH 173/185] =?UTF-8?q?feat(fish):=20add=20gco=20=E2=80=94=20s?= =?UTF-8?q?mart=20checkout=20that=20cds=20into=20worktrees=20=F0=9F=8C=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the target branch is already checked out in another worktree, `gco ` cds there instead of erroring with "already used by worktree at". Falls through to plain `git checkout` otherwise. --- assets/zed/settings.json | 53 ++++++++++++++++++++++++++++++++++++++++ nixos/fish.nix | 32 ++++++++++++++++++++++++ nixos/neovim.nix | 35 ++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 assets/zed/settings.json diff --git a/assets/zed/settings.json b/assets/zed/settings.json new file mode 100644 index 0000000..e48db6f --- /dev/null +++ b/assets/zed/settings.json @@ -0,0 +1,53 @@ +// Zed settings — tracked in dotfiles, symlinked into ~/.config/zed/settings.json +// by home-manager (xdg.configFile in nixos/home/danny/home.nix). +// +// Because this is a symlink to a nix-store file, editing it from inside Zed +// will fail (read-only). Edit THIS file in dotfiles, commit, and rebuild +// (`darwin-rebuild switch --flake .`). To see Zed's full default settings, +// run `zed: open default settings` from the command palette. +{ + "sticky_scroll": { + "enabled": true + }, + "edit_predictions": { + "provider": "ollama" + }, + "buffer_font_family": "JetBrains Mono", + "cli_default_open_behavior": "existing_window", + "project_panel": { + "dock": "left" + }, + "outline_panel": { + "dock": "left" + }, + "collaboration_panel": { + "dock": "left" + }, + "git_panel": { + "dock": "left" + }, + "agent": { + "dock": "right", + "default_model": { + "provider": "ollama", + "model": "llama3.2:latest" + } + }, + "disable_ai": false, + "minimap": { + "show": "auto" + }, + "telemetry": { + "diagnostics": false, + "metrics": false + }, + "base_keymap": "VSCode", + "vim_mode": true, + "ui_font_size": 16, + "buffer_font_size": 15, + "theme": { + "mode": "system", + "light": "One Light", + "dark": "One Dark" + } +} diff --git a/nixos/fish.nix b/nixos/fish.nix index 9d04f51..1d72d26 100644 --- a/nixos/fish.nix +++ b/nixos/fish.nix @@ -24,6 +24,38 @@ set fish_greeting 🐟: (set_color yellow; date +%T; set_color green; date --iso-8601 2>/dev/null; or date +%F; set_color normal) + # gco: smart `git checkout` — if the branch is checked out in another + # worktree, cd there instead of failing with "already used by worktree at". + function gco --description 'git checkout, but cd into worktree if the branch lives there' + if test (count $argv) -eq 0 + git checkout + return $status + end + + set -l branch $argv[1] + set -l target_ref "refs/heads/$branch" + set -l wt_path "" + set -l current_wt "" + for line in (git worktree list --porcelain 2>/dev/null) + switch $line + case 'worktree *' + set current_wt (string replace -r '^worktree ' "" -- $line) + case "branch $target_ref" + set wt_path $current_wt + break + end + end + + set -l here (git rev-parse --show-toplevel 2>/dev/null) + if test -n "$wt_path"; and test "$wt_path" != "$here" + echo "→ cd $wt_path (branch '$branch' is checked out in another worktree)" + cd $wt_path + return $status + end + + git checkout $argv + end + # Alacritty palette follows macOS appearance; refresh when opening a shell (LaunchAgent also polls). if test (uname -s) = Darwin bash ~/dotfiles/scripts/alacritty-sync-system-theme.sh >/dev/null 2>&1 & diff --git a/nixos/neovim.nix b/nixos/neovim.nix index 75b2335..51ae100 100644 --- a/nixos/neovim.nix +++ b/nixos/neovim.nix @@ -58,6 +58,39 @@ end, }) + -- Treesitter highlighting: parser-driven syntax highlighting (richer + -- than the regex-based default). Leaving `indent` off — it's still + -- buggy in several languages (python, yaml). + require'nvim-treesitter.configs'.setup { + highlight = { enable = true }, + } + + -- Sticky scroll: pin enclosing scopes (functions, classes, YAML keys, + -- etc.) to the top of the window as you scroll deeper. Same idea as + -- Zed/VS Code's "Sticky Scroll". `mode = 'topline'` matches Zed's + -- "scrolled past" feel; switch to 'cursor' if you'd rather it track + -- the cursor instead of the viewport. + require'treesitter-context'.setup { + enable = true, + max_lines = 5, + mode = 'topline', + trim_scope = 'outer', + } + + -- Fish: expand tabs to spaces. Fish renders raw \t in the commandline + -- as the Unicode glyph ␉ (U+2409) and wrap-indents each line to the + -- column of the opening quote, which mangles Alt-E multiline edits. + -- Using spaces sidesteps the issue entirely. + vim.api.nvim_create_autocmd("FileType", { + pattern = "fish", + callback = function() + vim.opt_local.expandtab = true + vim.opt_local.tabstop = 2 + vim.opt_local.shiftwidth = 2 + vim.opt_local.softtabstop = 2 + end, + }) + -- Keymaps vim.keymap.set("n", "S", ":%s//g", { desc = "Replace all" }) vim.keymap.set("n", "w", ":w", { desc = "Save file" }) @@ -73,6 +106,8 @@ catppuccin-nvim # theme goyo-vim # write prose limelight-vim # prose paragraph highlighter + nvim-treesitter.withAllGrammars # parsers (also makes vim.treesitter.foldexpr work for markdown) + nvim-treesitter-context # sticky scroll: pin parent scopes at top of window ]; }; } From 592e989b03465ed413910d272efdca5804392ab7 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Fri, 5 Jun 2026 17:19:38 +0200 Subject: [PATCH 174/185] =?UTF-8?q?fix(home):=20resurrect=20process=20list?= =?UTF-8?q?=20+=20track=20zed=20settings=20in=20dotfiles=20=F0=9F=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tmux-resurrect only restores programs in its allow-list; nvim/claude/ssh were missing so restored panes came back as bare fish prompts. Adds the three programs with argv-aware restart patterns. Also wires ~/.config/zed/settings.json as an xdg.configFile symlink so Zed config survives machine rebuilds alongside the rest of dotfiles. --- nixos/home/danny/home.nix | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/nixos/home/danny/home.nix b/nixos/home/danny/home.nix index 21c10da..b57def5 100644 --- a/nixos/home/danny/home.nix +++ b/nixos/home/danny/home.nix @@ -89,14 +89,21 @@ tmux-fzf extrakto # tmux-resurrect: prefix + Ctrl-s saves, prefix + Ctrl-r restores. - # Snapshot lives at ~/.local/share/tmux/resurrect/last (window - # layout, working dirs, pane contents if enabled). Survives - # force-quits / reboots / kernel panics. + # Snapshot lives at ~/.tmux/resurrect/last (window layout, working + # dirs, pane contents if enabled). Survives force-quits / reboots + # / kernel panics. + # + # @resurrect-processes: programs to restart on restore. Default + # list covers vim/emacs/less/top/etc. but NOT nvim, claude, or + # ssh. The "~name->cmd" form re-runs the original argv; bare + # names match argv-less invocations. Without this, restored panes + # come back as plain fish prompts in the right directory. { plugin = resurrect; extraConfig = '' set -g @resurrect-capture-pane-contents 'on' set -g @resurrect-strategy-nvim 'session' + set -g @resurrect-processes 'nvim "~nvim->nvim *" claude "~claude->claude --continue" ssh "~ssh->ssh *"' ''; } # tmux-continuum: auto-saves every 15min and auto-restores on @@ -164,6 +171,11 @@ xdg.configFile."alacritty/catppuccin-mocha-colors.toml".source = ../../../assets/alacritty/catppuccin-mocha-colors.toml; + # Zed: settings.json is a read-only symlink to assets/zed/settings.json. + # To change a setting, edit the asset file and rebuild — editing via Zed's + # UI will fail because the target is in the nix store. + xdg.configFile."zed/settings.json".source = ../../../assets/zed/settings.json; + # Alacritty: base config + imported active-colors.toml (updated without rebuild) programs.alacritty = { enable = true; From 680c20483ca4b68e1893a297fe632d60e9e80479 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 13:05:55 +0200 Subject: [PATCH 175/185] feat: add map.dannydannydanny.me vhost :world_map: Curated-architecture world map by Kyranna, served by the same notes service on phantom :8092 (routed by Host header, MAP_HOST). Mirrors the kf vhost. Co-Authored-By: Claude Opus 4.8 --- nixos/hosts/vps-relay.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 85ed329..9f8c3f5 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -155,6 +155,11 @@ "kf.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 ''; + # map — curated-architecture world map by Kyranna. Same notes + # service on phantom :8092, routed by Host header (MAP_HOST). + "map.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 + ''; }; }; From cc8cc05a08364c230a723ac8667b77d44fa78fdd Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 13:16:38 +0200 Subject: [PATCH 176/185] phantom-ship/shipyard: add media-processing tools for feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feedback now accepts photos, voice notes, video, documents etc. Phase A captures + stores raw files (Pillow for EXIF strip); Phase B derives OCR text, speech transcripts, poster frames, PDF text — all via subprocess so each tool degrades gracefully if absent. Wire the following into the shipyard service: - python3Packages.pillow → EXIF strip on captured photos - ffmpeg → poster frames + audio→16kHz WAV for whisper - tesseract (eng + rus) → OCR (vyscul writes in Russian) - whisper-cpp → speech-to-text for voice / audio / video - poppler_utils → pdftotext for document attachments Co-Authored-By: Claude Opus 4.7 (1M context) --- flake-modules/distant-shore.nix | 14 +++ nixos/disko-distant-shore.nix | 37 ++++++++ nixos/hosts/distant-shore-hardware.nix | 18 ++++ nixos/hosts/distant-shore.nix | 114 +++++++++++++++++++++++++ nixos/hosts/phantom-ship.nix | 23 ++++- 5 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 flake-modules/distant-shore.nix create mode 100644 nixos/disko-distant-shore.nix create mode 100644 nixos/hosts/distant-shore-hardware.nix create mode 100644 nixos/hosts/distant-shore.nix diff --git a/flake-modules/distant-shore.nix b/flake-modules/distant-shore.nix new file mode 100644 index 0000000..6c5a023 --- /dev/null +++ b/flake-modules/distant-shore.nix @@ -0,0 +1,14 @@ +# Standalone nixosSystem registration for distant-shore. +# Temporary: clan integration (zerotier/data-mesher/dm-pull-deploy) needs +# vars generated via sops on the admin machine. Until that runs, this +# keeps the box buildable without clan deps. Delete this file when +# distant-shore moves into flake-modules/clan.nix. +{ inputs, ... }: { + flake.nixosConfigurations.distant-shore = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.disko.nixosModules.disko + ../nixos/hosts/distant-shore.nix + ]; + }; +} diff --git a/nixos/disko-distant-shore.nix b/nixos/disko-distant-shore.nix new file mode 100644 index 0000000..ab35aac --- /dev/null +++ b/nixos/disko-distant-shore.nix @@ -0,0 +1,37 @@ +# Declarative disk layout for distant-shore (ThinkPad X13 Gen 2 — 256 GB +# SK Hynix NVMe). UEFI/systemd-boot, no encryption: it's a headless, +# WiFi-only server that must reboot unattended (clan dm-pull-deploy), so +# a LUKS passphrase prompt at boot would hang it. Mirrors sunken-ship's +# plain-ext4 choice. Device is wiped + repartitioned at install time by +# clan/nixos-anywhere. +{ + disko.devices = { + disk.main = { + type = "disk"; + device = "/dev/nvme0n1"; + content = { + type = "gpt"; + partitions = { + ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "fmask=0022" "dmask=0022" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos/hosts/distant-shore-hardware.nix b/nixos/hosts/distant-shore-hardware.nix new file mode 100644 index 0000000..3c52633 --- /dev/null +++ b/nixos/hosts/distant-shore-hardware.nix @@ -0,0 +1,18 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/nixos/hosts/distant-shore.nix b/nixos/hosts/distant-shore.nix new file mode 100644 index 0000000..33a7026 --- /dev/null +++ b/nixos/hosts/distant-shore.nix @@ -0,0 +1,114 @@ +# NixOS server on a ThinkPad X13 Gen 2 (Intel i5-1145G7, 16 GB). +# WiFi-only, headless, unattended auto-rebuild via clan dm-pull-deploy. +# No LUKS (mirrors sunken-ship) so reboots don't block on a passphrase. +# +# Blank-slate server for now — no application services. Give it a purpose +# later (just add services here and let dm-pull-deploy roll it out). +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./distant-shore-hardware.nix + ../disko-distant-shore.nix + ]; + + boot.loader.systemd-boot.enable = true; + # Secure Boot is enforced and the BIOS supervisor password is unknown, so + # we can't enrol our own SB keys. Instead, shim (MS-signed) is placed on + # the ESP and chain-loads systemd-boot; the NVRAM boot entry points at + # shim. We manage that entry imperatively via efibootmgr; letting bootctl + # touch EFI variables would replace it on every rebuild. + boot.loader.efi.canTouchEfiVariables = false; + boot.loader.efi.efiSysMountPoint = "/boot"; # matches disko ESP mountpoint + + # --- Secure Boot via shim + MOK (no firmware key enrolment possible) ------ + # The firmware trusts Microsoft-signed shim; shim trusts our enrolled MOK. + # On every bootloader install we: (1) sign systemd-boot with the MOK and + # drop it where shim chain-loads it (grubx64.efi), (2) install shim as the + # firmware-booted binary (+ MokManager), (3) MOK-sign every kernel image + # systemd-boot will hand off to (shim verifies them via the shim-lock + # protocol). Re-runs on each nixos-rebuild so auto-deployed generations + # stay bootable. Keys + shim live in /etc/secrets (outside the repo). + boot.loader.systemd-boot.extraInstallCommands = '' + # NixOS's bootloader-install systemd unit runs with a minimal PATH that + # doesn't include coreutils, so use absolute paths for cp/mv. + KEY=/etc/secrets/MOK.key + CRT=/etc/secrets/MOK.crt + sb() { ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$2" "$1"; } + # systemd-boot -> shim's chain-load target + sb /boot/EFI/systemd/systemd-bootx64.efi /boot/EFI/BOOT/grubx64.efi + # shim (MS-signed) is what the firmware boots; MokManager beside it + ${pkgs.coreutils}/bin/cp -f /etc/secrets/shimx64.efi /boot/EFI/BOOT/BOOTX64.EFI + ${pkgs.coreutils}/bin/cp -f /etc/secrets/mmx64.efi /boot/EFI/BOOT/mmx64.efi + # MOK-sign each kernel (skip already-signed; never touch initrds) + for k in /boot/EFI/nixos/*bzImage.efi; do + [ -e "$k" ] || continue + if ! ${pkgs.sbsigntool}/bin/sbverify --cert "$CRT" "$k" >/dev/null 2>&1; then + ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$k.tmp" "$k" \ + && ${pkgs.coreutils}/bin/mv -f "$k.tmp" "$k" + fi + done + ''; + + networking.hostName = "distant-shore"; + # WiFi via NetworkManager. The wpa_supplicant stack hit two issues on this + # box: (1) it strips CAP_CHOWN so wpa couldn't create its ctrl_interface, + # and (2) dhcpcd didn't grab a lease after the (late) association at boot, + # needing a manual restart — fatal for an unattended headless server. NM + # handles association + DHCP atomically and connected first-try here. + # The PSK stays out of the repo: it's substituted from /etc/secrets/nm.env + # ($PSK_INTENO) into the declared profile at activation. + networking.networkmanager.enable = true; + networking.networkmanager.ensureProfiles.environmentFiles = [ "/etc/secrets/nm.env" ]; + networking.networkmanager.ensureProfiles.profiles."Inteno-89FE" = { + connection = { id = "Inteno-89FE"; type = "wifi"; autoconnect = true; }; + wifi = { ssid = "Inteno-89FE"; mode = "infrastructure"; }; + wifi-security = { key-mgmt = "wpa-psk"; psk = "$PSK_INTENO"; }; + ipv4.method = "auto"; + ipv6.method = "auto"; + }; + hardware.enableRedistributableFirmware = true; # iwlwifi for the Intel AX201 WiFi + time.timeZone = "Europe/Copenhagen"; + + # It's a laptop acting as a server: keep running with the lid shut. + services.logind.settings.Login.HandleLidSwitch = "ignore"; + services.logind.settings.Login.HandleLidSwitchExternalPower = "ignore"; + + # Reduce screen burn-in / power: blank the TTY after a minute. + boot.kernelParams = [ "consoleblank=60" ]; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) + nix.settings.trusted-users = [ "root" "danny" ]; + system.stateVersion = "25.11"; + + users.users.danny = { + isNormalUser = true; + extraGroups = [ "wheel" "video" "audio" ]; + openssh.authorizedKeys.keys = [ + # Mac admin / fleet key (~/.ssh/id_ed25519_sunken_ship) — the key the + # Mac uses to reach the fleet; clan machines update relies on it. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin" + # Per-host key (~/.ssh/id_ed25519_distant_shore) — plain `ssh distant-shore`. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH61JOiOOPrAXekakAwTJg5yCSDfOIjlSvMYkpXrarAf distant-shore" + # sunken-ship (dm-pull-deploy push node) — reach distant-shore over ZT. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship" + ]; + }; + users.users.root.openssh.authorizedKeys.keys = + config.users.users.danny.openssh.authorizedKeys.keys; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + security.sudo.wheelNeedsPassword = false; + + # mokutil — manage MOK enrolment for the shim chain; sbsigntool — inspect + # signatures on bootloader/kernel images when debugging. + environment.systemPackages = with pkgs; [ git mokutil sbsigntool ]; +} diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index f232e63..1386fd3 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -241,18 +241,37 @@ in # Code deployed out-of-band via rsync to /home/danny/shipyard/ # (staying in-tree in ~/python-projects/26_shipyard/ until spun out to its own repo). # Bot token (not in repo): ~danny/.secrets/telegram-bot-token-shipyard - # Data (feedback.jsonl, pointer cache): ~danny/.local/share/shipyard/ + # Data (feedback.jsonl, feedback.db, pointer cache, feedback_media/): + # ~danny/.local/share/shipyard/ + # + # Feedback now accepts photos / voice / video / docs / stickers etc. + # Phase A captures + stores raw files; Phase B derives OCR text + # (tesseract), speech transcripts (whisper-cpp), poster frames + # (ffmpeg) and PDF text (pdftotext) — all via subprocess, so each + # tool degrades gracefully if missing. systemd.services.shipyard = let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ python-telegram-bot httpx + pillow # EXIF strip on captured photos ]); + # tesseract with English + Russian tessdata — vyscul writes in + # Russian, screenshots can land in either language. + tesseractWithLangs = pkgs.tesseract.override { + enableLanguages = [ "eng" "rus" ]; + }; in { description = "Shipyard Telegram bot (mini-app launcher + feedback)"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pythonEnv ]; + path = [ + pythonEnv + pkgs.ffmpeg # video/animation posters, sticker decode + tesseractWithLangs # photo OCR + pkgs.whisper-cpp # voice/audio transcription + pkgs.poppler_utils # pdftotext (document handling) + ]; environment = { SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; # Owner-only commands (/admin, /grant, /revoke) — anyone else gets ignored. From 05896f6d3bd75d8005f904a5b52d9bea47276faf Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 13:17:33 +0200 Subject: [PATCH 177/185] =?UTF-8?q?phantom-ship/shipyard:=20rename=20poppl?= =?UTF-8?q?er=5Futils=20=E2=86=92=20poppler-utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nixpkgs renamed it; the old attr is now an error alias. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 1386fd3..e018a73 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -270,7 +270,7 @@ in pkgs.ffmpeg # video/animation posters, sticker decode tesseractWithLangs # photo OCR pkgs.whisper-cpp # voice/audio transcription - pkgs.poppler_utils # pdftotext (document handling) + pkgs.poppler-utils # pdftotext (document handling) ]; environment = { SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; From 09f191d10b9b48b31c4353a350747478ccf24abe Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 15:25:16 +0200 Subject: [PATCH 178/185] feat: add studio.dannydannydanny.me vhost :art: Kyranna's private art-learning archive ("Studio"), served by the same notes service on phantom :8092 (routed by Host header, STUDIO_HOST). Mirrors the map/kf vhosts. Co-Authored-By: Claude Opus 4.8 --- nixos/hosts/vps-relay.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 9f8c3f5..3387aa5 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -160,6 +160,11 @@ "map.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 ''; + # studio — Kyranna's private art-learning archive. Same notes + # service on phantom :8092, routed by Host header (STUDIO_HOST). + "studio.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 + ''; }; }; From bbe05c971d7a2e094ec9be6952d999b9e31c34d3 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 6 Jun 2026 16:23:09 +0200 Subject: [PATCH 179/185] =?UTF-8?q?feat(distant-shore):=20add=20X13=20Gen?= =?UTF-8?q?=202=20as=20clan=20machine=20w/=20shim+MOK=20secure=20boot=20?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ThinkPad X13 Gen 2, BIOS-locked + Secure Boot enforced. Boots NixOS via Microsoft-signed shim chain-loading MOK-signed systemd-boot + kernel (re-signed each rebuild). WiFi via NetworkManager. Migrated from the standalone install module into clan (zerotier/data-mesher/dm-pull-deploy). --- flake-modules/clan.nix | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 30fe4c9..3b9b45c 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -47,6 +47,7 @@ in { inventory.machines.sunken-ship = { }; inventory.machines.phantom-ship = { }; inventory.machines.vps-relay = { }; + inventory.machines.distant-shore = { }; # ZeroTier mesh VPN. sunken-ship is the controller (manages network # membership); phantom-ship is a peer. The mac joins manually as an @@ -58,6 +59,7 @@ in { roles.peer.machines.phantom-ship = { }; roles.peer.machines.sunken-ship = { }; roles.peer.machines.vps-relay = { }; + roles.peer.machines.distant-shore = { }; }; # data-mesher — signed-file gossip protocol over libp2p (port 7946). @@ -70,6 +72,7 @@ in { module.input = "clan-core"; roles.default.machines.sunken-ship = { }; roles.default.machines.phantom-ship = { }; + roles.default.machines.distant-shore = { }; roles.bootstrap.machines.sunken-ship = { }; }; @@ -87,6 +90,7 @@ in { }; roles.default.machines.sunken-ship.settings.action = "switch"; roles.default.machines.phantom-ship.settings.action = "switch"; + roles.default.machines.distant-shore.settings.action = "switch"; }; # `clan machines update` connection target. Priority 2000 > ZT's 900 @@ -111,6 +115,12 @@ in { host = "89.167.39.251"; user = "danny"; }; + # distant-shore: LAN IP for the first update (not yet on ZT). Swap to + # its generated ZT IPv6 after it joins the mesh, like the others. + roles.default.machines.distant-shore.settings = { + host = "192.168.1.182"; + user = "danny"; + }; }; # Preserve current network / init stack (no systemd-networkd/resolved, @@ -157,6 +167,29 @@ in { ]; }; + # distant-shore — ThinkPad X13 Gen 2, WiFi, Secure Boot via shim+MOK + # (installed standalone, then migrated into clan). targetHost is the LAN + # IP for the first `clan machines update`; switch to its ZT IPv6 once the + # mesh is up. Builds on the box itself (it has nix + internet). + machines.distant-shore = { + imports = [ + { + clan.core.enableRecommendedDefaults = false; + clan.core.networking.targetHost = "danny@192.168.1.182"; + clan.core.networking.buildHost = "danny@192.168.1.182"; + } + clanHostsModule + ../nixos/hosts/distant-shore.nix + config.flake.nixosModules.monitoring-node-exporter + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + machines.phantom-ship = { imports = [ { From df18b1cfaf68bfff265798816196b9b0cfefad85 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 18:38:30 +0200 Subject: [PATCH 180/185] =?UTF-8?q?feat(distant-shore):=20generate=20clan?= =?UTF-8?q?=20vars=20(zerotier/data-mesher/dm-pull-deploy)=20+=20ZT=20host?= =?UTF-8?q?=20entry=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/clan.nix | 2 ++ sops/machines/distant-shore/key.json | 6 ++++++ sops/secrets/distant-shore-age.key/secret | 14 ++++++++++++++ sops/secrets/distant-shore-age.key/users/danny | 1 + .../identity.cert/machines/distant-shore | 1 + .../identity.cert/secret | 18 ++++++++++++++++++ .../identity.cert/users/danny | 1 + .../identity.key/machines/distant-shore | 1 + .../identity.key/secret | 18 ++++++++++++++++++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 +++ .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/distant-shore | 1 + .../signing.key/secret | 18 ++++++++++++++++++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 +++ .../machines/distant-shore | 1 + .../zerotier/zerotier-identity-secret/secret | 18 ++++++++++++++++++ .../zerotier-identity-secret/users/danny | 1 + .../distant-shore/zerotier/zerotier-ip/value | 1 + 20 files changed, 111 insertions(+) create mode 100755 sops/machines/distant-shore/key.json create mode 100644 sops/secrets/distant-shore-age.key/secret create mode 120000 sops/secrets/distant-shore-age.key/users/danny create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret create mode 120000 vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny create mode 100644 vars/per-machine/distant-shore/zerotier/zerotier-ip/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 3b9b45c..2e974d5 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -21,6 +21,7 @@ let sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; + distantShoreZTv6 = "fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3"; # Shared across both servers: /etc/hosts entries so data-mesher's # libp2p /dns/.clan/... bootstrap multiaddrs resolve over ZT. @@ -29,6 +30,7 @@ let "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; "${vpsRelayZTv6}" = [ "vps-relay.clan" ]; + "${distantShoreZTv6}" = [ "distant-shore.clan" ]; }; }; in { diff --git a/sops/machines/distant-shore/key.json b/sops/machines/distant-shore/key.json new file mode 100755 index 0000000..f580056 --- /dev/null +++ b/sops/machines/distant-shore/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "type": "age" + } +] \ No newline at end of file diff --git a/sops/secrets/distant-shore-age.key/secret b/sops/secrets/distant-shore-age.key/secret new file mode 100644 index 0000000..27a5780 --- /dev/null +++ b/sops/secrets/distant-shore-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:WTerGWNmve9/q+TLYi8HoGUQI0UgYMZN2zuC/FABX0MC6VuUsz9Doz36X8lsy+MRJzcHNPqdaHmAHopY/hODHLBirfUPLVZjEKI=,iv:ilp+cJivxY2us1jO85dWUHAqLJSsJ7ZKpmYMyi2476I=,tag:H0k2CZDhcH9lYSxz6BAPrg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZS2ZhQlJacmR4R3JMek5l\nZlFRajM4VllmK2R6NFlRMEkwNUJOL05OUUhzCmpWQ0gxQ1BHUkZOVm80QzRUc1BY\nTDNRZDZOL3EyS1FWK1A4UUd6MTFaTjAKLS0tIEUxU3hBSkZqRmc5d0dXZm0rNTYw\nQ0hrZUF5dDJLN0MvM2RQZlVFZkNPc28Kvq8yV+VwqQIuG1SPI/mMYbGwuD7oUOeR\nCzAuZvqGtludjW7+wX5uIwRzHMudU/yP/iME8vsDC3dL6sf75+arHg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:36Z", + "mac": "ENC[AES256_GCM,data:g35f5YmoneVewxmTh3E8ECDGAl0OwUj4v/2bjFs9Dd7MaT3in7PHvu30jJ4WHalYC8pkT5IlpBwsp1nVUnKsgh+2V+jN4JiGizlvTwByaYoalOoGZStIyQa+k8XRQqoUDbV3ESdI5q+dwS5PCWYIOH3MoA0o5b42iQPghrViaeY=,iv:v0UUy4LtQ5SRLB01vbcfNpcm8zgs1Vp3KCK552peXlA=,tag:45b8czXYtNh02q7P42FJmQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/sops/secrets/distant-shore-age.key/users/danny b/sops/secrets/distant-shore-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/sops/secrets/distant-shore-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..58ead86 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:esTlopK7VkLLnWvxsLoZtAGgbYKWKfu0XJde2fzxDuOaf9yUCU6NHpnyRAZnChceEZ3frwS7Lh/LWqX9CTKQ1LHTV8HrJERSERDzrQDHbIXFLtDbeF+qN7M1wYFEwCUa8PVAg4XHMN/ZGy6H71+J8UrktcbxcHUr+8L3pj4DZb5930kT3U02rzSoan8zb4zMhGqA0keq9QJ04uNJEN2Bly1kCBvdgc7kVUBNHwS78s+jfsa3PyOiLy5AI4CEbQ5r/xBjNgY/aSEOzRMoZtVWUFlh5Kxc47gz7MlK2x/2iXyCIAw3qeTIxor30GIL,iv:QbSPukR5aMrhBfYOM6lOb0qSEPm4oEqqQp59WDv0p6Y=,tag:KrMyGleLjIhT1LTlS3S63g==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyWlpRS1hhQThqaFUyNE4y\nYU1YVDFtazZnSHpTOWRFQkZYVThsRk9RQ1RZCnI4ZlFacTRRSHlub2hWVTNSSkhN\ndWExR202RG1nZ2dQTzQ5LzBNNW1kc2sKLS0tIHZlZXNhSm9wdElTZzRXZjQxaDAx\nRXpvcEkwK3dMNHZ2M21OSFluWnhDOFEKv0/yC/Llmhsm3+kV3AUJ2PPF817rOyL5\n6GkqTrb/gB8q8jnQabDr2sHUz7AB4w7zlQaNLRSo3Ba8KFbW7GZNRg==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcEdVdTBsUThiOE1EVWZW\nYXh2NUNCZTVieUZKbjByY2dSZVU2c0Q4L2hBCm1mNzVrcTRTTFpUTkJDZlArYTBZ\nWXhEMERmd1J3VTYxa2dWTlFxOW45N00KLS0tIHNLVzRCdDJGdWk2K0JoY1dJbzIz\nQU9DR2tXU3l2aU9YMGd1RjBGbUJYM0UKYmdAj535wvaGxN6m2VBBVtWRAD5RzQ7K\nbiJjvf8NH4A0aO9RVTFCevqRXUOKBu7jNIpFFfEyUEGHEUCWOuVSlA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:36Z", + "mac": "ENC[AES256_GCM,data:QVkNUsAO6BsVoPAL5GG1/DProapF8ryaUGDr8Y8mYPpD1Y2YXAF2sBRJ4FWkFZkWl4L2sp5DLXfqs+z0tpvi6rpG0jfpgJzy3Du2QKnk5W78WENlK+M74tSzAUfCUPn6RodykLJ8ik+EvxR+yxRmfjStAWsS6eqoTYowa4TGeJ0=,iv:qousMcaNKMtl8hGcfiS1WYBe0ftyb9ohHdBG+gqTio0=,tag:j64zgZpB7cmDfPcq4csjMQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..50edf19 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:kAzaF+nxyux0zwjoqC5QYrx5UyEhMPW0v9hGcYUXExZl6ShMMgCWhKN82al2jY6OnU/CQ7UT9USH6PC+eecimyM6A5YXQ0GvvU3uus0t46GKqXqcGVl4BdgVO6tm8ienIcfjF6ml3LyvMXirjDdIluVkrH/P0vM=,iv:BSQrtg9kgBHRkCV8+nODNyPX3PchkTEjPPTYy5JZrfo=,tag:dPtjxWqDh1Bce9rlW6czyw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMNkR1YTZNU1FyU2VWaUJR\nbjBOc0RSMW1SL1ZkZ1ozVHRmcVdkS01sdkZnCndTbGJlOVFrdDJHVDUxS1JFUmcy\nZS9jWGhRbWFCeGZOMHQwdzYxTFlrSjgKLS0tIEFaZmFzOXdXVjVOeUJuMDdpQ3hK\ndnRkUytmZk1zaXhUTSt1OTljUkNYK2MKpe6f3GHGCfduiidzYh0qaKEBaKyBZY4s\ne/f5QvZVApMiI4HFkOwFmNITOv6JdjGMQOw+OI6po0nqg0mqVnNIVA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpa0p1NCtUeGFLRzJFOFQv\nVXh4MThqOVR4TU9SK3Mrc21Ga1BPdUZrM1c0CmJxQzNyam56aTdUVVB5NVhEenlV\nOTkwb2YyRWdoVXc4K2VEaXhwZXM3TEUKLS0tIEFBajAycEQzelNoR2tCU3l6cVJo\nc3lHbWJZQWFQTkVxd0lBamxlQStZWlkKopG1Z2E0Smt/z/y1+cQeTKUKyJKBXzZr\nCQNkGfi1Dk/7n/WeKwePHWVF/19WqVfOIZW0E3tOKOIqDQZa0Io1Nw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:36Z", + "mac": "ENC[AES256_GCM,data:B6UAFOrK0QIngkf5OA3+BnLAouBvsr0AbW8lKI8RH7ylGQNOyXfnN06fYshi+jQyu5EAZBqovfSZzgcTDm7MDRAjzzmTToT5ekHPZnquleU/F7pF/D7JF78M6rQyw3uG0nwhnJcRqlCAXy+56++kTJhoKEW+B5fUsbvlHTmxwLk=,iv:BXDbLObPBXsL3Uj+TRwIFtNDRzWYJeM0mJyDDluz70s=,tag:eTANaLmNaUUSYBNcIhuIFQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..8f2058b --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAABhcRTNvFEyWkyRBX17KkM5nDuqOvR1xTY5vDqTygvk= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value b/vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..f748b68 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooW9pjiKnqmnHSwGRhgyUqKeFydDUE8RvYJDAqHb5PZvzue \ No newline at end of file diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..7c824e7 --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:tLR5iZ7Iro3BuBJlpvkKO7RrA9X2pO2H9Isi6jc8y8krh+a89Eug0PCNb4U/aSASjQgDfZgwg9+SU1y4iIoc3qC4sxw3f4uTdjCWRDEgfAvY3DVWiWI/EbWcfX7bVvl/GCQtHSwBW5z3KwhJV2McLK6Fpblx6fM=,iv:exFXncN3SA9zqSTFxX6o3kstwMGL9y8x0IOqJVNqK+I=,tag:dEkDG3meaWoq74hkRHbplg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3YWliNzFKWnZqdnZYeHRs\nVy8wdk1ZZWpJTlg2WWc4eEpUM2diZEFoamg0CnJnVDZJT3lWaUZlV3NHY1NpN0tW\nMXdRTnNGSjBhSFpLY0xvaER6UDI5RlEKLS0tIDloRHJFV2I1RVN6TXh6dmd5dzV0\nZ1AvZmpOM0VkaVZjNHlFdFBNd0FhTVkKEVFjtN66i+8f7P03ODYgoWZsTUiEcPiL\nYaV4UZKbjnp3SKTAeWk1P/lEj5DkicW3hq0ONQf2xrYriCpAc3/gKw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0VG1wZE52RFQ2ekl1R1ZY\nN2pmYWd1cStGZzU0Tk1LTmNuMnc5c1UwMnlVCmJrMjB6Qzc5ZUE3aXhmUmVuTTN1\neVFWbGhOUUNYUFJJVHF0OGtaR1FvVG8KLS0tIFp4aHgwN1QvWVVnaTNDVG42SXVB\nYVlLRndmQ1ovQjFMcHZYU0dqNS9ML2sKtHjmgLODafDcmrpYQyXRc/ajAR2saGs8\nlh4NVYYYwoXE6sNKSXwzgXXSjGEXGTVLxVvp9OKnSloI5/LsbrxANQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:37Z", + "mac": "ENC[AES256_GCM,data:20RiSc6b3o3xy23NDQRw4pcSf/akdcUMO6ciSFSZMQrhreYPBEa+Tb85qqqZ0dqQHRQFanzE3Usomp+Ux4FhFfSsCxljxdOjkQCAfkQKrg+GQ7/NOUhgdVQtep2+gT7MFrEzo5Jv8kctNuT18kqUjv5CwCOR35QJ98yHeAUULoo=,iv:ocUDNN4vhOX9pCUJKqQiBRhjTHbdRdw96csN6EWFdUg=,tag:Lps0aJy0ctWU5ilCUn9Uww==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..ea47b41 --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAPVF7m/+s1YroGdvSMxPwKmenJjk4yNrP8tNtZGHEhJI= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..ed43256 --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:9BN/+IBbsAmgABYuTEZvgB3cJOwiZ1aKu5GqcBEvCBoY3K4T5lDPqHrwdH48msu9/KD435SSz336+Stq8bQB87AXdfDMEhVIUwi8SV/CQg3urXvyqp0+lkbbrP9xyFzcH16L7NDmfD/SlZeFXQoPA3YHLvoYSsWnfjzHqrt0600IhAgq0TK+c+5hCzke9k89pgOrO6ypueHV+6GMx0g4JMcwq17bqT3fOQZ+hHSp9uOWDP1kJrO2TktwR/9AWAN+IG1sjUcaKYg+W34pG4XDkNPnp30NPfXSGMXjrM++MkIxyow1zFeSRI+bP5iLQEFpm1AvFFRdYIGN66hQVCgv0kxaOEJknlrG4QT4TyEJ,iv:MUsdjMEBvuaFkJJ6t3NNDrgECjheLJ0FtdrBsztOKZ8=,tag:lTcmyWAoKYPUhDjkHTd+Iw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLQ0Y3Y1N5aEo5ZDQwZ2g4\neXBDMldtWU42cUFaaTBmS1B6YW5QWktNcVZvCjBMYmNKWjR6cmVIRjhNK2Y2aWg5\nM093ZFhFWW0yZnVrOUxGQ3MzSGY5UkUKLS0tICtTbHFTMUtGQWEycGNDNFlXcTBS\nWmNWbDZSNE5sWUpzQ0dTNTgyemhNdzgKdPZIFY/m3IpEMH1PGsYToyLe9Qzj6LpW\nJhOTJbT9L0dTfE3OzdaG8BkwCkb8XCWxzveLPTLPCOvbP8DmOpjjHA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcXQ4NjE4d00ramdsemhI\nb1dxcldHS040TkVyL2lxUjdxL3J1WUlCdEZNCnExMDRqcmh5MGUxNFpJd3k4MzZT\nMXljSW5ncWxlSGRsYlJBdkoxQjIyZHMKLS0tIEhUSkRpeXhOM3BnTEsrNEpDb1I2\nUlhvZzFjRVNCcng2c3lsYS8vZHVHN00KFMMGm6BJY7/cn5WSP/RgjK6bVo4r7ps2\nkMcPoyMyenPiZrzWdL4iIb5azFB3CI8DAQS84Mt6KPR/wkYNoErxJg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:38Z", + "mac": "ENC[AES256_GCM,data:Cy3KGFXu58LAWSCUYJGpMeJxBboQxEPS1TzoK8iCFUyTT7Xfak9M9omaBd2r2fEel61iuSDVoDvQbZgNy2RwuiG0HhTXliMXR6G4oOheQIsSQix81tOWoPipu77qoeVkOSUDRhBzHdQVQQmiN7VJvw1kHvCq20u2ZM0057vf91g=,iv:uAmwqd0gpCD7pTFWwgKdkKjjxVadnHeRYUEv+vUgvL8=,tag:iDbx80+08AqhvdZIXJzdgQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-ip/value b/vars/per-machine/distant-shore/zerotier/zerotier-ip/value new file mode 100644 index 0000000..1de93b8 --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3 \ No newline at end of file From 0cdb4b8697b814895525fbe8fe2ac3fb08ae086d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 18:46:31 +0200 Subject: [PATCH 181/185] =?UTF-8?q?fix(distant-shore):=20build=20on=20sunk?= =?UTF-8?q?en-ship=20(avoids=20self-SSH=20on=20closure=20copy)=20?= =?UTF-8?q?=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/clan.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 2e974d5..cc4b8fe 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -172,13 +172,15 @@ in { # distant-shore — ThinkPad X13 Gen 2, WiFi, Secure Boot via shim+MOK # (installed standalone, then migrated into clan). targetHost is the LAN # IP for the first `clan machines update`; switch to its ZT IPv6 once the - # mesh is up. Builds on the box itself (it has nix + internet). + # mesh is up. buildHost = sunken-ship: it's an x86_64 builder whose key is + # already in distant-shore's authorized_keys, so the closure copy works + # (building on distant-shore itself needs a fragile self-SSH). machines.distant-shore = { imports = [ { clan.core.enableRecommendedDefaults = false; clan.core.networking.targetHost = "danny@192.168.1.182"; - clan.core.networking.buildHost = "danny@192.168.1.182"; + clan.core.networking.buildHost = "danny@sunken-ship"; } clanHostsModule ../nixos/hosts/distant-shore.nix From 610454f0d2488a9627c47845c3be9afa24fb69c3 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 20:27:34 +0200 Subject: [PATCH 182/185] =?UTF-8?q?fix(distant-shore):=20drop=20duplicate?= =?UTF-8?q?=20standalone=20flake-module=20(clan-managed=20now)=20?= =?UTF-8?q?=F0=9F=A9=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/distant-shore.nix | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 flake-modules/distant-shore.nix diff --git a/flake-modules/distant-shore.nix b/flake-modules/distant-shore.nix deleted file mode 100644 index 6c5a023..0000000 --- a/flake-modules/distant-shore.nix +++ /dev/null @@ -1,14 +0,0 @@ -# Standalone nixosSystem registration for distant-shore. -# Temporary: clan integration (zerotier/data-mesher/dm-pull-deploy) needs -# vars generated via sops on the admin machine. Until that runs, this -# keeps the box buildable without clan deps. Delete this file when -# distant-shore moves into flake-modules/clan.nix. -{ inputs, ... }: { - flake.nixosConfigurations.distant-shore = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - inputs.disko.nixosModules.disko - ../nixos/hosts/distant-shore.nix - ]; - }; -} From e2cf93e7d64504d965755730b734b264ffdc378a Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 21:43:28 +0200 Subject: [PATCH 183/185] feat(foreign-port): add WiFi-only laptop as clan machine :sparkles: Mirrors the distant-shore pattern: clan-managed (no standalone flake-module), wired into zerotier/data-mesher/dm-pull-deploy with the generated vars. WiFi via NetworkManager (PSK from /etc/secrets/nm.env); locally-signed boot chain (shim chain-loads sbsign-signed systemd-boot + kernel, refreshed every nixos-rebuild). targetHost is the LAN IP for the first push, switch to ZT IPv6 once on the mesh. buildHost = sunken-ship to avoid self-SSH on the closure copy. --- flake-modules/clan.nix | 36 ++++++ nixos/disko-foreign-port.nix | 36 ++++++ nixos/hosts/foreign-port-hardware.nix | 18 +++ nixos/hosts/foreign-port.nix | 111 ++++++++++++++++++ sops/machines/foreign-port/key.json | 6 + sops/secrets/foreign-port-age.key/secret | 14 +++ sops/secrets/foreign-port-age.key/users/danny | 1 + .../identity.cert/machines/foreign-port | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/foreign-port | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/foreign-port | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../machines/foreign-port | 1 + .../zerotier/zerotier-identity-secret/secret | 18 +++ .../zerotier-identity-secret/users/danny | 1 + .../foreign-port/zerotier/zerotier-ip/value | 1 + 23 files changed, 310 insertions(+) create mode 100644 nixos/disko-foreign-port.nix create mode 100644 nixos/hosts/foreign-port-hardware.nix create mode 100644 nixos/hosts/foreign-port.nix create mode 100755 sops/machines/foreign-port/key.json create mode 100644 sops/secrets/foreign-port-age.key/secret create mode 120000 sops/secrets/foreign-port-age.key/users/danny create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret create mode 120000 vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny create mode 100644 vars/per-machine/foreign-port/zerotier/zerotier-ip/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index cc4b8fe..e4a7944 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -22,6 +22,7 @@ let phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; distantShoreZTv6 = "fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3"; + foreignPortZTv6 = "fdd5:53a2:de33:d269:6499:9389:9b18:6c52"; # Shared across both servers: /etc/hosts entries so data-mesher's # libp2p /dns/.clan/... bootstrap multiaddrs resolve over ZT. @@ -31,6 +32,7 @@ let "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; "${vpsRelayZTv6}" = [ "vps-relay.clan" ]; "${distantShoreZTv6}" = [ "distant-shore.clan" ]; + "${foreignPortZTv6}" = [ "foreign-port.clan" ]; }; }; in { @@ -50,6 +52,7 @@ in { inventory.machines.phantom-ship = { }; inventory.machines.vps-relay = { }; inventory.machines.distant-shore = { }; + inventory.machines.foreign-port = { }; # ZeroTier mesh VPN. sunken-ship is the controller (manages network # membership); phantom-ship is a peer. The mac joins manually as an @@ -62,6 +65,7 @@ in { roles.peer.machines.sunken-ship = { }; roles.peer.machines.vps-relay = { }; roles.peer.machines.distant-shore = { }; + roles.peer.machines.foreign-port = { }; }; # data-mesher — signed-file gossip protocol over libp2p (port 7946). @@ -75,6 +79,7 @@ in { roles.default.machines.sunken-ship = { }; roles.default.machines.phantom-ship = { }; roles.default.machines.distant-shore = { }; + roles.default.machines.foreign-port = { }; roles.bootstrap.machines.sunken-ship = { }; }; @@ -93,6 +98,7 @@ in { roles.default.machines.sunken-ship.settings.action = "switch"; roles.default.machines.phantom-ship.settings.action = "switch"; roles.default.machines.distant-shore.settings.action = "switch"; + roles.default.machines.foreign-port.settings.action = "switch"; }; # `clan machines update` connection target. Priority 2000 > ZT's 900 @@ -123,6 +129,12 @@ in { host = "192.168.1.182"; user = "danny"; }; + # foreign-port: WiFi-only LAN IP for the first update; swap to its + # generated ZT IPv6 after it joins the mesh. + roles.default.machines.foreign-port.settings = { + host = "192.168.1.223"; + user = "danny"; + }; }; # Preserve current network / init stack (no systemd-networkd/resolved, @@ -194,6 +206,30 @@ in { ]; }; + # foreign-port — WiFi-only laptop server, locally-signed boot chain + # (installed standalone, migrated into clan). targetHost is the LAN IP + # for the first `clan machines update`; switch to its ZT IPv6 once the + # mesh is up. buildHost = sunken-ship for the closure copy (avoids + # self-SSH). + machines.foreign-port = { + imports = [ + { + clan.core.enableRecommendedDefaults = false; + clan.core.networking.targetHost = "danny@192.168.1.223"; + clan.core.networking.buildHost = "danny@sunken-ship"; + } + clanHostsModule + ../nixos/hosts/foreign-port.nix + config.flake.nixosModules.monitoring-node-exporter + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + machines.phantom-ship = { imports = [ { diff --git a/nixos/disko-foreign-port.nix b/nixos/disko-foreign-port.nix new file mode 100644 index 0000000..a928620 --- /dev/null +++ b/nixos/disko-foreign-port.nix @@ -0,0 +1,36 @@ +# Declarative disk layout for distant-shore. UEFI/systemd-boot, no +# encryption: it's a headless, WiFi-only server that must reboot +# unattended (clan dm-pull-deploy), so a LUKS passphrase prompt at boot +# would hang it. Mirrors sunken-ship's plain-ext4 choice. Device is wiped +# + repartitioned at install time by clan/nixos-anywhere. +{ + disko.devices = { + disk.main = { + type = "disk"; + device = "/dev/nvme0n1"; + content = { + type = "gpt"; + partitions = { + ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "fmask=0022" "dmask=0022" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos/hosts/foreign-port-hardware.nix b/nixos/hosts/foreign-port-hardware.nix new file mode 100644 index 0000000..3c52633 --- /dev/null +++ b/nixos/hosts/foreign-port-hardware.nix @@ -0,0 +1,18 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/nixos/hosts/foreign-port.nix b/nixos/hosts/foreign-port.nix new file mode 100644 index 0000000..9705b6e --- /dev/null +++ b/nixos/hosts/foreign-port.nix @@ -0,0 +1,111 @@ +# NixOS laptop server. WiFi-only, headless, unattended auto-rebuild via +# clan dm-pull-deploy. No LUKS (mirrors sunken-ship) so reboots don't +# block on a passphrase. +# +# Blank-slate server for now — no application services. Give it a purpose +# later (just add services here and let dm-pull-deploy roll it out). +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./foreign-port-hardware.nix + ../disko-foreign-port.nix + ]; + + boot.loader.systemd-boot.enable = true; + # Firmware-locked Secure Boot: we can't enrol our own keys into the + # firmware key DB, so a vendor-signed shim is the firmware-booted binary + # and chain-loads a locally-signed systemd-boot + kernel. The NVRAM + # entry points at shim; bootctl is kept away from EFI variables so + # rebuilds don't clobber the entry. + boot.loader.efi.canTouchEfiVariables = false; + boot.loader.efi.efiSysMountPoint = "/boot"; # matches disko ESP mountpoint + + # --- Locally-signed boot chain -------------------------------------------- + # On every bootloader install: re-sign systemd-boot and every kernel + # image, refresh the shim binary on the ESP, and place the helper binary + # beside it. Re-runs on each nixos-rebuild so auto-deployed generations + # stay bootable. Signing material lives in /etc/secrets, never the repo. + boot.loader.systemd-boot.extraInstallCommands = '' + # NixOS's bootloader-install systemd unit runs with a minimal PATH that + # doesn't include coreutils, so use absolute paths for cp/mv. + KEY=/etc/secrets/MOK.key + CRT=/etc/secrets/MOK.crt + sb() { ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$2" "$1"; } + # systemd-boot -> shim's chain-load target + sb /boot/EFI/systemd/systemd-bootx64.efi /boot/EFI/BOOT/grubx64.efi + # shim is the firmware-booted binary; helper binary sits beside it + ${pkgs.coreutils}/bin/cp -f /etc/secrets/shimx64.efi /boot/EFI/BOOT/BOOTX64.EFI + ${pkgs.coreutils}/bin/cp -f /etc/secrets/mmx64.efi /boot/EFI/BOOT/mmx64.efi + # sign each kernel (skip if already signed; leave initrds untouched) + for k in /boot/EFI/nixos/*bzImage.efi; do + [ -e "$k" ] || continue + if ! ${pkgs.sbsigntool}/bin/sbverify --cert "$CRT" "$k" >/dev/null 2>&1; then + ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$k.tmp" "$k" \ + && ${pkgs.coreutils}/bin/mv -f "$k.tmp" "$k" + fi + done + ''; + + networking.hostName = "foreign-port"; + # WiFi via NetworkManager. The wpa_supplicant stack hit two issues on this + # box: (1) it strips CAP_CHOWN so wpa couldn't create its ctrl_interface, + # and (2) dhcpcd didn't grab a lease after the (late) association at boot, + # needing a manual restart — fatal for an unattended headless server. NM + # handles association + DHCP atomically and connected first-try here. + # The PSK stays out of the repo: it's substituted from /etc/secrets/nm.env + # ($PSK_INTENO) into the declared profile at activation. + networking.networkmanager.enable = true; + networking.networkmanager.ensureProfiles.environmentFiles = [ "/etc/secrets/nm.env" ]; + networking.networkmanager.ensureProfiles.profiles."Inteno-89FE-5GHz" = { + connection = { id = "Inteno-89FE-5GHz"; type = "wifi"; autoconnect = true; }; + wifi = { ssid = "Inteno-89FE-5GHz"; mode = "infrastructure"; }; + wifi-security = { key-mgmt = "wpa-psk"; psk = "$PSK_INTENO"; }; + ipv4.method = "auto"; + ipv6.method = "auto"; + }; + hardware.enableRedistributableFirmware = true; # WiFi firmware blobs + time.timeZone = "Europe/Copenhagen"; + + # It's a laptop acting as a server: keep running with the lid shut. + services.logind.settings.Login.HandleLidSwitch = "ignore"; + services.logind.settings.Login.HandleLidSwitchExternalPower = "ignore"; + + # Reduce screen burn-in / power: blank the TTY after a minute. + boot.kernelParams = [ "consoleblank=60" ]; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) + nix.settings.trusted-users = [ "root" "danny" ]; + system.stateVersion = "25.11"; + + users.users.danny = { + isNormalUser = true; + extraGroups = [ "wheel" "video" "audio" ]; + openssh.authorizedKeys.keys = [ + # Mac admin / fleet key (~/.ssh/id_ed25519_sunken_ship) — the key the + # Mac uses to reach the fleet; clan machines update relies on it. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin" + # TODO: add a per-host key (~/.ssh/id_ed25519_foreign_port) for + # plain `ssh foreign-port`. Generate when convenient. + # sunken-ship (dm-pull-deploy push node) — reach foreign-port over ZT. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship" + ]; + }; + users.users.root.openssh.authorizedKeys.keys = + config.users.users.danny.openssh.authorizedKeys.keys; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + security.sudo.wheelNeedsPassword = false; + + # mokutil + sbsigntool — manage the shim trust chain and inspect signed + # bootloader/kernel images when debugging. + environment.systemPackages = with pkgs; [ git mokutil sbsigntool ]; +} diff --git a/sops/machines/foreign-port/key.json b/sops/machines/foreign-port/key.json new file mode 100755 index 0000000..6aa3307 --- /dev/null +++ b/sops/machines/foreign-port/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "type": "age" + } +] \ No newline at end of file diff --git a/sops/secrets/foreign-port-age.key/secret b/sops/secrets/foreign-port-age.key/secret new file mode 100644 index 0000000..2ba1f0f --- /dev/null +++ b/sops/secrets/foreign-port-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:MH/ib8WAbzucbm2dhhoo6ESSSLKtKMWmjUwtpAOZhU7KyhOoechpJRSkBBmFV4LzbSP1qeaFbid6USJBnRsxkoz6XvhMzP0kzS0=,iv:9sPwc/JIlo5mzxelNzLCB26k2f+n2C9tB8Y/HEdPvHw=,tag:hJBhzTMsTWd9PDydS4aosg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6L2JGejlzQlhiNFhES1lT\nNTZsNFFMT1NzZEV0T28rNDA3STJ1UXNRcUZFCkxoenpFYWJicHpGVDhtMUdwNXBo\nS29EazVsRGFST2ZodDJMTkxQN2I1RjAKLS0tIFpib0RoOTJ6bkU0b1F6NnRaV3lF\nVHhvYjNOUUtMbGF5ejdaVk5WdGt2d1kKNU5JR1nIYPQLALUM3wRO945Sk6GLxJpn\nTVmVUEgXcwHcSij10a/cQOyPXNNnsfIC+WJFMJcjHfsjBnwS5W/Bgw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:mVobAhXUhbs49+g0bXfi4TjPG667F7pM8Kk518a7kRZ/HtN2kLYcSyl3XpspTosAs4x3QbFQUbFCgsBgqx+gS6xlw3OAJXM3iG2fNu2qoj9Q7viAEHoWVHwT+ftjA0qVTUf0BDD1r4ow6BNhe6kQy5bQqVu0MhjDfsK9BNTXAu4=,iv:aFHo3bQKgr1XSnwGUajkSFa4UftTWdZbPtXY05N7qOM=,tag:VymYJf4XFLaEGvxQmvF6rA==,type:str]", + "version": "3.12.2" + } +} diff --git a/sops/secrets/foreign-port-age.key/users/danny b/sops/secrets/foreign-port-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/sops/secrets/foreign-port-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..768ab41 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:wnNPCB0+f3dcxMW1/pcFZFauUVYTC1mfWoWBV2EJmyRzZS3Uux5Un3R/GbYQeDSFZDLzLH+zCZFaxq3mpb3NGTTUzF8vnGMk/OnjlolA8OjAfiODI0mahTiQA7WcWSk1hkkZ15Ri1o+uyumx9hmvJU3dIsKIJe7AizCzwP5bHg1jgRhG2wPKKyIDWKoh4JTlR6SxK6/tOaUPx2gb2ddz2Lk56Xdw7GCbb/9I9D6sRwxdWMCoWFKdTllLsdsD48b8Jfq4ewD+LudYEtiVByk5SpyOjQoAmMLYaGlD+nxFgZz53hePRIXnp0fL0pm4,iv:fA607yxD/yHJatEiGh1SVGDcqKxB+EFeyCUQeF/Z5hA=,tag:glaq+MBCp6ptKqDsw4RM/Q==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0MklDUkpWbEdFcFgxTVJZ\ndEs1OTJtZFhVaEsyb2pobGlUOGhtcTY4RWpVCjFDV3lqRmNGclZMbTR3UXlhcjJv\nVEY1Tjk1YWR4Tmt0SmgvR3laZnNIRUkKLS0tIHB1TURnYmVzZW4xSERMR0ZrRXl5\nbWVJbW1keGkyUkhuQXE0MEFTaXFsS1EKHlsS3FDr9RuMBRU5r4T3bCZWZn38V3k+\nfLUfuZK2IF+xyD7kEiBuATB57wwfd8RzZ1lBwz4fD4jlb+fz0BXoJQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6TnNqc284WkZqdXNLVFBU\nRTJndEFmNjY0Q1YyUnRPLy9jWllpSy9ZaFNFCkFkNmpYenQ2dk1Fb2dRZTNvM0Jl\nemNqUmdjQmpJQUF4M3ZNRmo4UEhXOHcKLS0tIFp4OTZJTGR1algxTEVWemdkQTB5\nME4xTTdlelN6bXJiTGRSM1VSWG5vZUEKOYc71rLx7RTq4DR6ZggrtgllK58sYJ6h\ngw156OTQl3fKWxlrKDd1l4o72M1qmfAIQ1z5YJJ+CfNPk/iMz/R3rQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:AkcOoNTxMNkpF0SrwFlNujBrB8fxL1diu+mGq/kbsiWIj6UqvVD+dimDSvTgVqvnU4HF7/7b9zKriC6SbG42Kz8zScFv7m3idD2tHr+7SE/iR7CowDQs70CRMo1b85wLq8WAxhfQb93NHdum6I2biNVIf0ZXs1+kZ2iNBxtjqfQ=,iv:kWOCWCe953ekq0n0HLe3S2JprIBnBe9QXwIzDFyQMH8=,tag:tLz7VZwj7RrbpJ7QTrBqcg==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..a79bb25 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:1Hq98rN3U+8DcxIFJpYkvv31gUpSm0WBjfZxivYn7/ZkH6zbJ57fzeU+9PH9SRF6QBuekZKZNIBup3fteI5VqQ/moEyQE9aSvnqGCrkcamDwDQfN5GwKX+rb7W96atESRm/VqhgDWC2KTc3892515gBPpkDG+nc=,iv:tAlghG1jpDPcYgTvEzAlnB2upAetl8mz8IIQercHe4k=,tag:mz3fvVlKolg5JzrjhBNPaw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlaEk2Z1NMZnVtNlJPdXgw\nTWNaTFBCRXo3T2JRUEY2Q2hBY0xpMVV6ckE0ClJOVUpKNDZTcEhGS2RzQm1tSjNp\ndmxQWjl5aHord0RUMHRvTlhyMkVqc1UKLS0tIHlDRXlReUgzZVdLcE9kMFhsTDRq\nOGxpZE9KcUR0VEhyOE9VUkVUVlIyRlEKsnU17famN/qr2M8BdvVpRl5bSWseegrZ\nnB9yljvm+pxsE55xM1WyguNfUwXtHj0YTiVgBl5PIUolj3/J8R76sg==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjTGdDVlB0RGlTcng3M3pE\nNzEvNFpBUzF0aDJDaUJFTFFGWlB3bEVVdHhFClZOZGNDanlMTkxIMk9lbzVGRzAv\nZG93NUFFL3NIM3Z0TlhucFlMTTYwc3MKLS0tIFFQcTIwekNEM0k0MElGZys2QldS\nMDZpRVk5OVNZYVVWSWJDTFZqVFdiRWcKgwuwZgKhKx1PiQwH2CgMoCl0WUQR5Rv9\nx4mpZgkoD5pkEx896117CyAy2BRzrDWo+4SsjEijSMlDynYsbxLReA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:DX9+9MH8ZPtc6sPbYSc+54soAIXJWWEoEWBZdbJ6gT5RhVdzUjMHuEbmb9eMcb+nVu4KSUCoXiJOT9XActSU2dcTNIIiLX1lqpw0aWRS2sAWM+Go4hT4/P98z/0vcsdN/uQOBl3cDlygqKhN9GSoPfJTMT+QTSZsVjxwYxW1pPM=,iv:B9RiMMX+yS1Y+3E1ifTJI30pvLrah5SCPwW6CZKZGNU=,tag:MA007hv+nMIMutOdl5ewkQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..b450f2b --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAZqy+mwYOfJy3GSHfeC80TFn1c0kYte5zzzbwrP8xww0= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value b/vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..f9982a8 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWGjAXheQGEfy13JQJP8pSrwcivxoXw5ijRzesfXVDFuyW \ No newline at end of file diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..cd27ea5 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:dDO6hu8prxHvoP41Oxky0mGGbrwqcCcrrkg0tbr/Sv8K16gNoQaX2wvaRDExOmt0BZkv5Oe8p5pvKudmm5JN0AS7oaPexW0lE+vFJ+zrRpq01c5BbCYZ0SuuafJ3VmRS/dlYU0/SZ4MyK3eijLzX3rGHPOi3b0g=,iv:hbh49ExGMYyshxcus/5sTIs/ZcOL9pod/3H/oHG1Qs8=,tag:fjHnl2uunGEU0i2FtgZB+g==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZNTlkTWVleld5K3Q5Vklm\nMlphdVduQ0RKY0pEVGdVTm5scHRWR0lNVjAwClV4V3drQnFLUkhpUVk1ZElGcFM1\ncit3UTdURExTRDVjVW1ZdklTZzRINDAKLS0tIHFMYnNycmh1Y0h4OC9UNUtHUmMw\nVXdpVk9QWHlBYmtCS3FOam9SWnRFZG8KDnggBRH/wSh1tfiCGOn1sF/Fdfxkf1us\n7Lzxexrmh+lllns/KY2of9L2HUgDavp+ju/5QVFfT7O3SuSTB6aoow==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5eVpNUmV5QlllaXlPaGgr\ndXZYMURzT3I4UWxWSHBSbnAyZVNsOWNaZ2xJClhkRmZ2ejBYVCtkTVBZZE82YXE5\nWkdZWFJFM0lVQXFFYm5rYnRVZDFEdlkKLS0tIHZ5OUgzcFRLZnFWK3pDUUtWUUJj\nWFF4Zk5IeDl5VFNQWlVsTk1lQWlLQmMKJzaOm0cwOshmwoO+eHovf6i6mGkezjIP\ncXJlDaJyxfPKJxc36XlJ5KT9c4RqTX7WFOifHoKRh4EN58KnvtFj+A==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:UX7265pubBBssugQk4pZsQH5WedsmnqFa77bJQZwu2ixNUTkO9VfR8r9CUiugDOmbDj9Y7TJtoN4JR+v6hBmDOnjHO5w0WO5dONNJebGmO+pGU7r/K6WwSGi5nPANiYjGuHqYZwq7PJe8ZCF/vu/ZI8q7iJijw6xGWuGHaP/Gvw=,iv:Ezo1z5n+pHPdhjh9l+HvmsgElEwJR4eoMPtZKdDhHAI=,tag:57yLRXReSRz098sDxyiQZQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..6131304 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA6xYjcIT5B5NDduIARf2EAoE+vsnZK+NWcyiI0fQc0Fg= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..5fd8d79 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:PO0Thn6D7kcIGWr7MwmS8H58+9JYSDDGQZlx28B7T6noXTA6tWqMJlqY4aMn1dXJ1CKAqV4q5VZpd/kP9KQvSL4DRnRrFteRe0C+k/mlLfwsWVqLGFY7eqoG1QTZwc4w8cw3FB7R0YUfxRlHq3mIyrbf+8POX2Rq2r5L5GNWVkGTKZOPRtNawPxTrUgfVM4B9ksc1vtTZeWn1GymSwevnt4KPX/8efFAgIclTUHh+Eh+F9xSU9efnkT+Phsh3QLf+3+UHiXQXlpMgwuKrvBJdHWLxJz/3aTpU2+nByqv0IANhGhR8ut0EbFXr8Zr1pIYrt4mWCAyYJvnwxR6iljQ1zyhI0GXUNAHJPQ7wRYq,iv:yDOBYu2+HK/KfS/hbR5QgOi2QHp9RzGPiKxojQX2s8c=,tag:q6s6LemFyoFBEq+ojd4D6A==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1bEkwSUhxR0JQR2psZ3Uy\nUlFpWi93NTBhZ0s0TlpkQ2VkVUdDWHIyNmhFClBhVFBnb1h0c2o2cm9OODZpZWMy\nQTB2YmxnWmN5Ylo4M1JHMVVVdklWeWMKLS0tIDBSY2NQdmRTZnA1QUtnaHloUFJJ\nb0VvZGlwSko0UitTa2t6TDZ4bnhsSWMKt5awUoFdny/Qg5krgUAzHeqIoIhprPmF\nBNleiSJdAvSsK53a7CT2rGInnl3dcrtpkEWluK7WJlFTJBdekMwQuA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6TFZjRjk4Wm8vcEcxN0ZP\na05yd282UmR3NllXM2MyeUpSb0VuWURvTHp3CmJzL0cwcU5WWGJuME1KcmtxSFVw\nL1lFdzg3Z2t4TXBiaWduZ2tSZXc3bjAKLS0tIEp6NWpIMlhoSEtvQ3IyNXJNVnE1\nb1lSczR2eG1JY1NScnkyNWMxWWN0aWcKrnfv9dGrWpmBjt8u+FdtwojU5hLDyV/Z\n6vgaW35SvFYLYR53Zo18MPkYbqGcaNldyr68qbYMLxqVdQUJwv3LSg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:19Z", + "mac": "ENC[AES256_GCM,data:joT4cUsVDxTVJqF9OJyETkC0lxQ6sT3XonBIjy80/PZ6cs7lcEyboWWSVuBcG+CTPzcUv1uXmdNjUBNc/TDdF8P0vEGnMBgmNRnSrxb0OwENW+c08GOB+c4AJev58H+V1wmzmyr9NJAKxpvQaE/cWIS1wS7c5QdiKAj8HsYd2ns=,iv:H2xSAU0jTH0bKS+P5W+FwbOtzl/Wb5xTfirkZMmtPq8=,tag:o+b9ESO3d8XnIU/bcH09zw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-ip/value b/vars/per-machine/foreign-port/zerotier/zerotier-ip/value new file mode 100644 index 0000000..79b3db0 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:9389:9b18:6c52 \ No newline at end of file From f8a873bd06b106a69e5d62eb1b4bd0efac968143 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 8 Jun 2026 22:27:32 +0200 Subject: [PATCH 184/185] nixos: add tdpixi service (port 8093) + vps-relay vhost Idle Tower Defence Mini App by @plasmagoat forked from github.com/plasmagoat/TDPixi. Pure static FastAPI serve, no DB. Proxied at tdpixi.dannydannydanny.me. Co-Authored-By: Claude Sonnet 4.6 --- nixos/hosts/phantom-ship.nix | 29 ++++++++++++++++++++++++++--- nixos/hosts/vps-relay.nix | 4 ++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index e018a73..99bec0e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -50,11 +50,11 @@ in # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator # (:8083), Forgejo (:3000), Escape Hormuz (:8090), bon (:8091), - # notes (:8092) are reachable only over the ZeroTier mesh — the - # vps-relay Caddy reverse-proxies into them. Same pattern as + # notes (:8092), TDPixi (:8093) are reachable only over the ZeroTier mesh — + # the vps-relay Caddy reverse-proxies into them. Same pattern as # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side # stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 ]; + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 8093 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -527,6 +527,29 @@ in }; }; + # TDPixi — Idle Tower Defence Telegram Mini App by @plasmagoat. + # Pure static serve, no DB. Code rsync'd to /home/danny/tdpixi/. + # Upstream: https://github.com/plasmagoat/TDPixi + systemd.services.tdpixi = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + ]); + in { + description = "tdpixi — Idle Tower Defence Mini App"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + serviceConfig = { + WorkingDirectory = "/home/danny/tdpixi"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8093"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Hara morning heartbeat — daily email check + Telegram good-morning ping. # Runs claude in print mode with the Gmail MCP, then sends output via Bot API. # Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin). diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 3387aa5..207e8b8 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -142,6 +142,10 @@ "bon.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8091 ''; + # TDPixi — Idle Tower Defence Mini App by @plasmagoat, port 8093. + "tdpixi.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8093 + ''; # notes — markdown blog (notes.X) + apex landing (X). Same backend # service on phantom :8092 routes by Host header. "notes.dannydannydanny.me".extraConfig = '' From 0eab0d47ae1c7c3ff3fbc5f058df653a40a636bb Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 8 Jun 2026 23:25:34 +0200 Subject: [PATCH 185/185] nixos: add bananasimulator-beta service + vhost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cheat instance for the bananasim project. Mirrors the production service on phantom-ship but: - own DB at /home/danny/.local/share/bananasimulator-beta/ - own working dir /home/danny/bananasimulator-beta/ - port 8084 (added to zt+ firewall allowlist + new vps-relay vhost at bananasimulator-beta.dannydannydanny.me) - BS_RIPE_MIN_PER_STAGE=0.2 so a banana cycles in ~3 min (testable) - BS_BETA_MODE=1 so the server exposes /api/cheat/* + sets beta:true in /api/me, which makes the frontend render the 🧪 cheat menu Same code base; deploy with the same tar-over-ssh ritual into the sibling dir. apps.json gets a private 'bananasim (beta)' entry that only my user sees. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 37 +++++++++++++++++++++++++++++++++++- nixos/hosts/vps-relay.nix | 5 +++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 99bec0e..26b3e90 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -54,7 +54,7 @@ in # the vps-relay Caddy reverse-proxies into them. Same pattern as # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side # stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 8093 ]; + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8084 8090 8091 8092 8093 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -176,6 +176,7 @@ in "d /home/danny/.local/share/shelfish 0755 danny users - -" "d /home/danny/.local/share/scuttle 0755 danny users - -" "d /home/danny/.local/share/bananasimulator 0755 danny users - -" + "d /home/danny/.local/share/bananasimulator-beta 0755 danny users - -" "d /home/danny/.local/share/komtolk 0755 danny users - -" "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" @@ -385,6 +386,40 @@ in }; }; + # Bananasimulator BETA — cheat-instance for testing the full progression + # end-to-end. Separate DB, exposes /api/cheat/* (gated by BS_BETA_MODE=1) + # so the frontend cheat menu can seed canonical states and reset. + # Faster ripening (0.2 min/stage = ~3 min to compost) so cycles are + # testable in real time. Same code base; deploy to a sibling dir. + # vhost in vps-relay.nix → bananasimulator-beta.dannydannydanny.me. + systemd.services.bananasimulator-beta = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + httpx + python-telegram-bot + ]); + in { + description = "Bananasimulator BETA (cheat instance) FastAPI server"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + BS_DB_PATH = "/home/danny/.local/share/bananasimulator-beta/bananasimulator.db"; + BS_RIPE_MIN_PER_STAGE = "0.2"; # ~3 min to compost — testable in real time + BS_BETA_MODE = "1"; # exposes /api/cheat/* + flips beta=true in /api/me + }; + serviceConfig = { + WorkingDirectory = "/home/danny/bananasimulator-beta"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8084"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Escape Hormuz — turn-based boat-race Mini App (Hara's first build). # Code lives at /home/danny/escape_hormuz/. Same vps-relay-fronted ZT path # as the others; binds :: so the ZeroTier IPv6 address is reachable. diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 207e8b8..cedcbfa 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -124,6 +124,11 @@ "bananasimulator.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8083 ''; + # Bananasimulator BETA — separate service on port 8084 with + # BS_BETA_MODE=1 (cheat menu + faster ripening for testing). + "bananasimulator-beta.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8084 + ''; # KomTolk (formerly translate-platform) — same backend, port 8080. "komtolk.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8080