Commit 5e2c380e4726

Vincent Demeester <vincent@sbr.pm>
2026-09-16 17:11:46
feat: prepare Sakhalin Fedora bootstrap
Prepared the minimal Fedora and Home Manager migration path while retaining the current NixOS source for service migration reference. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 43fb6df
imperative/sakhalin/bootstrap.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+
+# Sakhalin (Fedora Workstation) bootstrap script.
+# Installs Determinate Nix, deploys minimal host files, and activates Home Manager.
+
+if [[ "${BASH_SOURCE[0]:-}" == "${0}" ]]; then
+	set -euo pipefail
+fi
+
+readonly GREEN='\033[0;32m'
+readonly YELLOW='\033[0;33m'
+readonly RED='\033[0;31m'
+readonly NC='\033[0m'
+
+REPO_URL="${REPO_URL:-https://git.sbr.pm/home.git}"
+REPO_PATH="${REPO_PATH:-$HOME/src/home}"
+SYSTEM_CONFIG="${SYSTEM_CONFIG:-sakhalin}"
+
+log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
+log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
+log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
+
+check_root() {
+	if [[ $EUID -eq 0 ]]; then
+		log_error "Run as your regular user, not root (sudo is used when needed)"
+		exit 1
+	fi
+}
+
+source_nix() {
+	if ! command -v nix &>/dev/null && [[ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]]; then
+		# shellcheck disable=SC1091
+		. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
+	fi
+}
+
+install_nix() {
+	if command -v nix &>/dev/null; then
+		log_info "Nix already installed: $(nix --version)"
+		return 0
+	fi
+
+	log_info "Installing Nix (Determinate Systems installer)..."
+	curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
+	source_nix
+}
+
+clone_repo() {
+	if [[ -d "$REPO_PATH/.git" ]]; then
+		log_info "Repository exists at $REPO_PATH, pulling..."
+		git -C "$REPO_PATH" pull --ff-only || log_warn "Pull failed, continuing with existing checkout"
+		return 0
+	fi
+
+	log_info "Cloning $REPO_URL → $REPO_PATH"
+	mkdir -p "$(dirname "$REPO_PATH")"
+	git clone "$REPO_URL" "$REPO_PATH"
+}
+
+setup_host_configs() {
+	local config_path
+
+	cd "$REPO_PATH"
+	config_path=$(nix build ".#fedoraConfigs.${SYSTEM_CONFIG}" --no-link --print-out-paths)
+	sudo "${config_path}/deploy"
+}
+
+enable_wireguard() {
+	log_info "Enabling WireGuard for a future boot after private-key restoration..."
+	sudo systemctl enable wg-quick@wg0.service
+}
+
+setup_home_manager() {
+	cd "$REPO_PATH"
+	nix run home-manager -- switch --flake ".#vincent@${SYSTEM_CONFIG}" -b backup
+}
+
+main() {
+	log_info "Bootstrapping Sakhalin..."
+	check_root
+	install_nix
+	clone_repo
+	setup_host_configs
+	enable_wireguard
+	setup_home_manager
+	log_info "Sakhalin bootstrap completed. Restore /etc/wireguard/private.key before starting WireGuard."
+}
+
+if [[ "${BASH_SOURCE[0]:-}" == "${0}" ]]; then
+	main "$@"
+fi
imperative/sakhalin/README.md
@@ -0,0 +1,43 @@
+# Sakhalin — Fedora Workstation + Determinate Nix
+
+Sakhalin is intended to become a Fedora Workstation family machine. Fedora owns
+its packages, desktop, and networking; Determinate Nix provides Nix and
+standalone Home Manager manages the small shared user configuration.
+
+## Bootstrap
+
+After Fedora is installed, run as `vincent`:
+
+```bash
+bash imperative/sakhalin/bootstrap.sh
+```
+
+The script installs Nix when necessary, obtains `~/src/home`, deploys generated
+`/etc/hosts`, `/etc/nix/nix.custom.conf`, and `/etc/wireguard/wg0.conf`, enables
+`wg-quick@wg0`, and activates `vincent@sakhalin` through Home Manager.
+
+WireGuard is enabled but not started by the bootstrap. Restore the preserved
+private key with mode `0600`, then start it:
+
+```bash
+sudo install -m 0600 /path/to/private.key /etc/wireguard/private.key
+sudo systemctl start wg-quick@wg0.service
+```
+
+## Routine updates
+
+```bash
+make host/sakhalin/build
+make host/sakhalin/switch
+```
+
+`host/sakhalin/switch` deploys the generated host files with sudo, then switches
+Home Manager. It does not start WireGuard.
+
+## Managed configuration
+
+- [`systems/sakhalin/gen-configs.nix`](../../systems/sakhalin/gen-configs.nix)
+  generates the small Fedora host-file set.
+- [`systems/sakhalin/home.nix`](../../systems/sakhalin/home.nix) omits the
+  NixOS-only mail services for the standalone profile.
+- WireGuard private-key material remains outside the repository and Nix store.
imperative/sakhalin/test-bootstrap.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
+bootstrap="$repo_root/imperative/sakhalin/bootstrap.sh"
+generator="$repo_root/systems/sakhalin/gen-configs.nix"
+
+[[ -x "$bootstrap" ]]
+# shellcheck disable=SC2016 # Literal shell fragments are intentional assertions.
+grep -q 'SYSTEM_CONFIG="${SYSTEM_CONFIG:-sakhalin}"' "$bootstrap"
+# shellcheck disable=SC2016 # Literal shell fragments are intentional assertions.
+grep -q 'fedoraConfigs.${SYSTEM_CONFIG}' "$bootstrap"
+# shellcheck disable=SC2016 # Literal shell fragments are intentional assertions.
+grep -q 'home-manager -- switch --flake ".#vincent@${SYSTEM_CONFIG}" -b backup' "$bootstrap"
+grep -q 'systemctl enable wg-quick@wg0.service' "$bootstrap"
+if grep -q 'enable --now wg-quick@wg0' "$bootstrap"; then
+  exit 1
+fi
+
+grep -q '"vincent@sakhalin"' "$repo_root/flake.nix"
+grep -q 'import ./systems/sakhalin/gen-configs.nix' "$repo_root/flake.nix"
+grep -q '# sakhalin = libx.mkHost' "$repo_root/flake.nix"
+if grep -q 'nmDispatcher' "$generator"; then
+  exit 1
+fi
+grep -q 'host/sakhalin/build' "$repo_root/Makefile"
+grep -q 'host/sakhalin/switch' "$repo_root/Makefile"
+
+cd "$repo_root"
+nix eval --raw '.#homeConfigurations."vincent@sakhalin".activationPackage.drvPath' >/dev/null
+nix build .#fedoraConfigs.sakhalin --dry-run >/dev/null
imperative/README.md
@@ -33,7 +33,10 @@ imperative/
 ├── aomi/               # Fedora CSB laptop — Nix (system-manager + home-manager)
 │   ├── README.md
 │   └── bootstrap.sh
-└── nagoya/             # Debian server — Determinate Nix + Home Manager
+├── nagoya/             # Debian server — Determinate Nix + Home Manager
+│   ├── README.md
+│   └── bootstrap.sh
+└── sakhalin/           # Fedora family workstation — minimal Nix + Home Manager
     ├── README.md
     └── bootstrap.sh
 ```
@@ -65,6 +68,17 @@ See [aomi/README.md](aomi/README.md) for details.
 
 See [nagoya/README.md](nagoya/README.md) for details.
 
+### sakhalin — Fedora Workstation
+
+- **OS:** Fedora Workstation
+- **Type:** Family workstation
+- **Approach:** Determinate Nix with minimal standalone Home Manager and
+  Nix-generated `/etc/hosts`, `/etc/nix/nix.custom.conf`, and WireGuard config
+- **Components:** shared shell baseline and Syncthing; WireGuard is enabled once
+  its externally preserved private key is restored
+
+See [sakhalin/README.md](sakhalin/README.md) for details.
+
 ## Usage
 
 ### Nix-managed hosts
lib/default.nix
@@ -14,6 +14,7 @@
       hostname,
       username,
       desktop ? null,
+      standaloneFedora ? false,
       system ? "x86_64-linux",
     }:
     let
@@ -56,6 +57,7 @@
           stateVersion
           hostname
           desktop
+          standaloneFedora
           globals
           username
           system
systems/sakhalin/gen-configs.nix
@@ -0,0 +1,53 @@
+# Generate the small set of host files Sakhalin needs outside Home Manager.
+# Fedora manages packages, services, and networking.
+{
+  pkgs,
+  globals,
+  libx,
+  ...
+}:
+let
+  machine = globals.machines.sakhalin;
+  hostsEntries = libx.hostConfigs globals.machines;
+  hosts = pkgs.writeText "hosts" ''
+    127.0.0.1 localhost sakhalin
+    ::1 localhost
+    ${pkgs.lib.concatStringsSep "\n" (
+      pkgs.lib.mapAttrsToList (ip: names: "${ip} ${pkgs.lib.concatStringsSep " " names}") hostsEntries
+    )}
+  '';
+
+  wgConf = pkgs.writeText "wg0.conf" ''
+    [Interface]
+    PostUp = wg set %i private-key /etc/wireguard/private.key
+    Address = ${builtins.head machine.net.vpn.ips}/24
+
+    [Peer]
+    PublicKey = ${globals.machines.carthage.net.vpn.pubkey}
+    AllowedIPs = 10.100.0.0/24
+    Endpoint = ${globals.net.vpn.endpoint}:51820
+    PersistentKeepalive = 25
+  '';
+
+  nixCustomConf = pkgs.writeText "nix.custom.conf" ''
+    trusted-users = root @wheel
+    connect-timeout = 5
+    download-attempts = 2
+    builders-use-substitutes = true
+  '';
+
+  deploy = pkgs.writeShellScript "deploy-sakhalin-configs" ''
+    set -euo pipefail
+    install -m 0644 ${hosts} /etc/hosts
+    install -d -m 0755 /etc/nix /etc/wireguard
+    install -m 0644 ${nixCustomConf} /etc/nix/nix.custom.conf
+    install -m 0600 ${wgConf} /etc/wireguard/wg0.conf
+  '';
+in
+pkgs.runCommand "sakhalin-configs" { } ''
+  mkdir -p "$out/etc/nix" "$out/etc/wireguard"
+  cp ${hosts} "$out/etc/hosts"
+  cp ${nixCustomConf} "$out/etc/nix/nix.custom.conf"
+  cp ${wgConf} "$out/etc/wireguard/wg0.conf"
+  cp ${deploy} "$out/deploy"
+''
systems/sakhalin/home.nix
@@ -1,6 +1,10 @@
-{ ... }:
 {
-  imports = [
+  lib,
+  standaloneFedora ? false,
+  ...
+}:
+{
+  imports = lib.optionals (!standaloneFedora) [
     ../../home/common/services/imapfilter.nix
     ../../home/common/services/imapfilter-rules-updater.nix
     ../../home/common/services/mail-monitor.nix
flake.nix
@@ -69,6 +69,11 @@
           hostname = "nagoya";
           system = "aarch64-linux";
         };
+        "vincent@sakhalin" = libx.mkHome {
+          username = "vincent";
+          hostname = "sakhalin";
+          standaloneFedora = true;
+        };
         # headless machine
         "vincent@aion" = libx.mkHome {
           username = "vincent";
@@ -88,9 +93,9 @@
           desktop = "niri"; # or "sway"
         };
         # Servers (unstable)
-        sakhalin = libx.mkHost {
-          hostname = "sakhalin";
-        };
+        # sakhalin = libx.mkHost {
+        #   hostname = "sakhalin";
+        # };
         shikoku = libx.mkHost {
           hostname = "shikoku";
           pkgsInput = inputs.nixpkgs-26_05;
@@ -168,6 +173,19 @@
         {
           kyushu = mkFedoraConfigs "kyushu";
           aomi = mkFedoraConfigs "aomi";
+          sakhalin =
+            let
+              pkgs = import inputs.nixpkgs { system = "x86_64-linux"; };
+              globals = import ./globals.nix {
+                inherit (inputs.nixpkgs) lib;
+                hostname = "sakhalin";
+              };
+              fedoraLibx = import ./lib/functions.nix { inherit (inputs.nixpkgs) lib; };
+            in
+            import ./systems/sakhalin/gen-configs.nix {
+              inherit pkgs globals;
+              libx = fedoraLibx;
+            };
         };
 
       hostConfigs = {
Makefile
@@ -30,6 +30,17 @@ host/%/switch: FORCE ## Deploy to remote host and activate immediately (e.g., ma
 	nixos-rebuild --target-host root@$*.sbr.pm --flake .#$* switch
 
 # Host-specific overrides (non-standard DNS/network)
+.PHONY: host/sakhalin/build
+host/sakhalin/build: ## Build Home Manager and generated host files for Sakhalin
+	nix build .#homeConfigurations.vincent@sakhalin.activationPackage --no-link
+	nix build .#fedoraConfigs.sakhalin --no-link
+
+.PHONY: host/sakhalin/switch
+host/sakhalin/switch: ## Deploy generated host files and switch Home Manager on Sakhalin
+	nix build .#fedoraConfigs.sakhalin
+	sudo ./result/deploy
+	nix run home-manager -- switch --flake .#vincent@sakhalin -b backup
+
 .PHONY: host/nagoya/build
 host/nagoya/build: ## Build Home Manager and generated host files for Nagoya
 	nix build .#homeConfigurations.vincent@nagoya.activationPackage --no-link