Commit 353c02e31fcb

Vincent Demeester <vincent@sbr.pm>
2026-09-10 13:49:41
feat(nagoya): add Determinate Nix builder bootstrap
Replaced the unused system-manager path with a minimal Debian-native bootstrap and Home Manager profile so Nagoya can become a maintainable native ARM build host without disrupting its existing services. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent d2b5610
home/common/profiles/headless-builder.nix
@@ -0,0 +1,18 @@
+{ pkgs, config, ... }:
+{
+  home.packages = with pkgs; [
+    (config.custom.nss.wrap nh)
+    nix-output-monitor
+    nix-prefetch-scripts
+    nix-update
+    nixpkgs-fmt
+    nixfmt-plus
+    nixfmt
+    nurl
+    statix
+    deadnix
+    nix-init
+    nil
+    nixd
+  ];
+}
imperative/nagoya/bootstrap.sh
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+
+# Nagoya (Debian) bootstrap script.
+# Installs Determinate Nix, checks out this repository, and applies the
+# minimal host and Home Manager configurations. It intentionally leaves
+# Debian-managed networking and services unchanged.
+
+if [[ "${BASH_SOURCE[0]:-}" == "${0}" ]]; then
+	set -euo pipefail
+fi
+
+readonly GREEN='\033[0;32m'
+readonly YELLOW='\033[1;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:-nagoya}"
+
+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
+	log_info "Nix installed: $(nix --version)"
+}
+
+configure_nix() {
+	local nix_conf="${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf"
+	mkdir -p "$(dirname "$nix_conf")"
+
+	if [[ ! -f "$nix_conf" ]] || ! grep -q '^experimental-features[[:space:]]*=' "$nix_conf"; then
+		echo 'experimental-features = nix-command flakes' >>"$nix_conf"
+	fi
+	if ! grep -q '^use-xdg-base-directories[[:space:]]*=' "$nix_conf"; then
+		echo 'use-xdg-base-directories = true' >>"$nix_conf"
+	fi
+}
+
+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 ".#hostConfigs.${SYSTEM_CONFIG}" --no-link --print-out-paths)
+	sudo "${config_path}/deploy"
+}
+
+setup_home_manager() {
+	cd "$REPO_PATH"
+	nix run home-manager -- switch --flake ".#vincent@${SYSTEM_CONFIG}"
+}
+
+main() {
+	log_info "Bootstrapping Nagoya..."
+	log_info "  Repo:   $REPO_URL โ†’ $REPO_PATH"
+	log_info "  Config: $SYSTEM_CONFIG"
+
+	check_root
+	install_nix
+	configure_nix
+	clone_repo
+	setup_host_configs
+	setup_home_manager
+
+	log_info "Nagoya bootstrap completed. Existing Debian networking and services were left unchanged."
+}
+
+if [[ "${BASH_SOURCE[0]:-}" == "${0}" ]]; then
+	main "$@"
+fi
imperative/nagoya/README.md
@@ -1,96 +1,51 @@
-# Nagoya Configuration
+# Nagoya โ€” Debian + Determinate Nix
 
-> ๐Ÿšง **Needs rework:** Nagoya is managed with Nix `system-manager`
-> (`~/src/home/systems/nagoya/system.nix`, exposed as `systemConfigs.nagoya`
-> in `flake.nix`). The imperative `apply.sh` below predates that and is kept
-> only as a bootstrap fallback; it still needs to be reconciled with the
-> system-manager config.
->
-> For deployment, see `AGENTS.md` at the repo root.
+Nagoya is a Raspberry Pi 5 running Debian on `aarch64-linux`. Debian continues
+to manage packages, services, networking, and WireGuard. Determinate Nix
+provides the Nix daemon; Home Manager provides the small interactive builder
+environment.
 
-Configuration for the Nagoya host, a Debian-based aarch64 server.
+## Bootstrap
 
-## Overview
+The bootstrap is safe to rerun. It installs Nix when absent, clones or
+fast-forwards `~/src/home`, deploys only `/etc/hosts` and `/etc/nix/nix.custom.conf`,
+and switches the `vincent@nagoya` Home Manager profile. It does not install
+Debian packages, change services, or modify WireGuard.
 
-This directory contains the legacy idempotent `apply.sh` script for the Nagoya
-host. The current source of truth is the Nix `system-manager` configuration in
-`systems/nagoya/`.
-
-### System Information
-
-- **Hostname:** nagoya
-- **OS:** Debian
-- **Type:** Server
-- **Architecture:** aarch64
-- **VPN Address:** 10.100.0.80/24
-
-## Files
-
-### `apply.sh`
-
-The main configuration script that sets up the system. This script is designed to be run multiple times safely (idempotent).
-
-## Setup Components
-
-The `apply.sh` script configures the following:
-
-### Wireguard VPN
-- Configures wireguard VPN client
-- Creates `/etc/wireguard/wg0.conf` with VPN settings
-- Connects to VPN endpoint at 167.99.17.238:51820
-- Assigns client IP: 10.100.0.80/24
-- Requires `WG_PRIVATE_KEY` environment variable
-
-### Docker
-- Removes old Docker packages (docker.io, podman-docker, etc.)
-- Installs official Docker CE from docker.com repository
-- Installs docker-ce, docker-ce-cli, containerd.io, and docker-buildx-plugin
-- Configures Docker APT repository with proper GPG keys
-
-### Kind (Kubernetes in Docker)
-- Installs Kind v0.30.0 for ARM64
-- Installs to `/usr/local/bin/kind`
-- Used for local Kubernetes development
-
-### Syncthing
-- Installs Syncthing from official APT repository
-- Configures GPG keys for package verification
-- Note: User service setup needs to be completed (see TODOs)
-
-## Usage
-
-### Running the Script
-
-From the repository root:
+From a host with SSH access:
 
 ```bash
-# Basic run (without wireguard configuration)
-sudo ./imperative/nagoya/apply.sh
-
-# With wireguard private key
-sudo WG_PRIVATE_KEY="your-private-key-here" ./imperative/nagoya/apply.sh
+ssh -i ~/.ssh/id_homelab_sk vincent@nagoya.vpn 'bash -s' \
+  < imperative/nagoya/bootstrap.sh
 ```
 
-### First-Time Setup
+Or run it on Nagoya after obtaining the repository:
 
-1. Ensure you have root/sudo access
-2. Have your wireguard private key ready
-3. Run the script with the WG_PRIVATE_KEY environment variable
+```bash
+cd ~/src/home
+bash imperative/nagoya/bootstrap.sh
+```
 
-### Updating Configuration
+## Routine updates
 
-Simply re-run the script. It's designed to be idempotent, meaning running it multiple times will bring the system to the desired state without causing issues.
+On Nagoya:
 
-## TODOs
+```bash
+cd ~/src/home
+make host/nagoya/build
+make host/nagoya/switch
+```
 
-- [ ] Complete Syncthing user service setup
-- [ ] Add config.txt configuration (diff with default, nvme settings)
-- [ ] Add error handling for missing Kind download
-- [ ] Consider adding Docker post-install steps (user groups, etc.)
+`host/nagoya/switch` requires sudo only to install the generated host files.
 
-## Notes
+## Managed configuration
 
-- The script uses `set -euo pipefail` for strict error handling
-- All setup functions are prefixed with `setup.`
-- Logging functions provide colored output for better readability
-- Shellcheck directives are used where system files are sourced
+- [`systems/nagoya/gen-configs.nix`](../../systems/nagoya/gen-configs.nix)
+  generates `/etc/hosts` and `/etc/nix/nix.custom.conf`.
+- [`systems/nagoya/home.nix`](../../systems/nagoya/home.nix) selects the
+  minimal headless builder profile: shell tooling, direnv, and Nix authoring
+  tools. It intentionally omits desktop, mail, Syncthing, containers, and
+  automatic cleanup.
+- Existing WireGuard configuration is deliberately out of scope. Its Debian
+  package and service remain host-managed, and its private key remains outside
+  the Nix store.
imperative/nagoya/test-bootstrap.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
+bootstrap="$repo_root/imperative/nagoya/bootstrap.sh"
+
+[[ -x "$bootstrap" ]]
+grep -q 'install.determinate.systems/nix' "$bootstrap"
+grep -q 'nix run home-manager -- switch --flake' "$bootstrap"
+grep -q 'git clone' "$bootstrap"
+if grep -qE '\b(apt|systemctl|wg|wireguard)\b' "$bootstrap"; then
+	exit 1
+fi
+
+cd "$repo_root"
+nix eval --raw '.#homeConfigurations."vincent@nagoya".activationPackage.drvPath' >/dev/null
+nix build .#hostConfigs.nagoya --dry-run >/dev/null
imperative/README.md
@@ -9,12 +9,11 @@ distribution (Fedora, Debian) instead of NixOS. Two complementary approaches
 live here:
 
 1. **Nix on top of the host OS** โ€” using the [Determinate Nix installer] with
-   `system-manager` (system services) and `home-manager` (user environment).
-   This is the current, preferred approach. See [aomi](aomi/) and
-   [nagoya](nagoya/).
-2. **Idempotent bash scripts** โ€” self-contained `apply.sh` scripts that bring a
-   host to the desired state. Kept as a fallback / bootstrap aid (e.g. the
-   legacy nagoya `apply.sh`).
+   Home Manager for the user environment and Nix-generated host files where
+   needed. This is the current, preferred approach. See [aomi](aomi/),
+   [kyushu](kyushu/), and [nagoya](nagoya/).
+2. **Idempotent bash scripts** โ€” self-contained bootstrap scripts for initial
+   Nix installation and repository checkout.
 
 [Determinate Nix installer]: https://github.com/DeterminateSystems/nix-installer
 
@@ -34,9 +33,9 @@ imperative/
 โ”œโ”€โ”€ aomi/               # Fedora CSB laptop โ€” Nix (system-manager + home-manager)
 โ”‚   โ”œโ”€โ”€ README.md
 โ”‚   โ””โ”€โ”€ bootstrap.sh
-โ””โ”€โ”€ nagoya/             # Debian server โ€” Nix (system-manager), bash fallback
+โ””โ”€โ”€ nagoya/             # Debian server โ€” Determinate Nix + Home Manager
     โ”œโ”€โ”€ README.md
-    โ””โ”€โ”€ apply.sh
+    โ””โ”€โ”€ bootstrap.sh
 ```
 
 ## Current Hosts
@@ -56,40 +55,38 @@ See [aomi/README.md](aomi/README.md) for details.
 
 - **OS:** Debian, aarch64
 - **Type:** Server
-- **Approach:** Nix `system-manager` (`systems/nagoya/system.nix`), with a
-  legacy idempotent `apply.sh` bash script as fallback
-- **Components:** Docker, Kind, WireGuard, Syncthing
+- **Approach:** Determinate Nix with a minimal Home Manager builder profile
+  and Nix-generated host files
+- **Components:** shell, Nix development tools, and generated `/etc/hosts` /
+  `/etc/nix/nix.custom.conf`; Debian manages existing services and networking
 - **VPN IP:** 10.100.0.80/24
-- **Status:** ๐Ÿšง Needs rework โ€” being consolidated onto the system-manager
-  config; the `apply.sh` script remains as a bootstrap fallback.
+- **Status:** Active โ€” bootstrap is intentionally limited to Nix, repository,
+  generated host files, and Home Manager to preserve existing WireGuard setup.
 
 See [nagoya/README.md](nagoya/README.md) for details.
 
 ## Usage
 
-### Nix-managed hosts (aomi)
+### Nix-managed hosts
 
 ```bash
 cd ~/src/home
 
-# System-manager (WireGuard, Syncthing, system services)
-nix build .#systemConfigs.<hostname> && sudo ./result/bin/activate
+# Build and deploy generated host files where the host provides an artifact
+nix build .#hostConfigs.<hostname>
+sudo ./result/deploy
 
-# Home-manager (shell, editors, dev tools)
-home-manager switch --flake .#vdemeest@<hostname>
+# Home Manager (shell, editors, development tools)
+home-manager switch --flake .#<user>@<hostname>
 ```
 
 Initial provisioning is done via the host's `bootstrap.sh`. See the host
 README for the exact invocation.
 
-### Script-managed hosts (nagoya)
+### Bootstrap a Nix-managed host
 
 ```bash
-# Apply configuration for a specific host
-sudo ./imperative/<hostname>/apply.sh
-
-# With environment variables (e.g. wireguard key)
-sudo WG_PRIVATE_KEY="..." ./imperative/<hostname>/apply.sh
+bash ./imperative/<hostname>/bootstrap.sh
 ```
 
 ## Adding a New Host
@@ -98,11 +95,11 @@ Pick the approach that fits the machine:
 
 ### Nix-managed (preferred)
 
-1. Add a `systemConfigs.<hostname>` (system-manager) and/or
-   `homeConfigurations."vdemeest@<hostname>"` entry in `flake.nix`.
-2. Create `imperative/<hostname>/bootstrap.sh` to install Nix and run the first
-   activation.
-3. Document the host in `imperative/<hostname>/README.md`.
+1. Add a `homeConfigurations."<user>@<hostname>"` entry in `flake.nix`.
+2. Add a `hostConfigs.<hostname>` output when generated host files are needed.
+3. Create `imperative/<hostname>/bootstrap.sh` to install Nix, check out the
+   repository, and activate the minimal required configuration.
+4. Document the host in `imperative/<hostname>/README.md`.
 
 ### Script-managed (fallback)
 
lib/default.nix
@@ -122,47 +122,4 @@
       ];
     };
 
-  # Function to create a system manager
-  mkSystemManager =
-    {
-      system ? "x86_64-linux",
-      hostname,
-      desktop ? null,
-      pkgsInput ? inputs.nixpkgs,
-      homeInput ? inputs.home-manager,
-    }:
-    let
-      globals = import ../globals.nix {
-        inherit (pkgsInput) lib;
-        inherit hostname;
-      };
-      extraSpecialArgs = {
-        inherit
-          self
-          inputs
-          outputs
-          stateVersion
-          hostname
-          desktop
-          globals
-          ;
-        libx = import ./functions.nix { inherit (pkgsInput) lib; };
-        monitoring = import ./monitoring.nix { inherit (pkgsInput) lib; };
-      };
-    in
-    inputs.system-manager.lib.makeSystemConfig {
-      inherit extraSpecialArgs;
-      modules = [
-        # self.nixosModules.wireguard-client
-        # inputs.agenix.nixosModules.default
-        homeInput.nixosModules.home-manager
-        {
-          config = {
-            nixpkgs.hostPlatform = system;
-            system-manager.allowAnyDistro = true;
-          };
-        }
-        ../systems/system-manager.nix
-      ];
-    };
 }
systems/nagoya/gen-configs.nix
@@ -0,0 +1,38 @@
+# Generate the small set of host files Nagoya needs outside Home Manager.
+# Debian continues to manage packages, services, and networking.
+{
+  pkgs,
+  globals,
+  libx,
+  ...
+}:
+let
+  hostsEntries = libx.hostConfigs globals.machines;
+  hosts = pkgs.writeText "hosts" ''
+    127.0.0.1 localhost
+    ::1 localhost
+    ${pkgs.lib.concatStringsSep "\n" (
+      pkgs.lib.mapAttrsToList (ip: names: "${ip} ${pkgs.lib.concatStringsSep " " names}") hostsEntries
+    )}
+  '';
+
+  nixCustomConf = pkgs.writeText "nix.custom.conf" ''
+    trusted-users = root vincent
+    connect-timeout = 5
+    download-attempts = 2
+    builders-use-substitutes = true
+  '';
+
+  deploy = pkgs.writeShellScript "deploy-nagoya-configs" ''
+    set -euo pipefail
+    install -m 0644 ${hosts} /etc/hosts
+    install -d -m 0755 /etc/nix
+    install -m 0644 ${nixCustomConf} /etc/nix/nix.custom.conf
+  '';
+in
+pkgs.runCommand "nagoya-configs" { } ''
+  mkdir -p "$out/etc/nix"
+  cp ${hosts} "$out/etc/hosts"
+  cp ${nixCustomConf} "$out/etc/nix/nix.custom.conf"
+  cp ${deploy} "$out/deploy"
+''
systems/nagoya/home.nix
@@ -1,7 +1,5 @@
-_: {
-  # Syncthing will be configured here via home-manager
-  # For now, just enable the user service
-  services.syncthing = {
-    enable = true;
-  };
+{
+  imports = [
+    ../../home/common/profiles/headless-builder.nix
+  ];
 }
systems/nagoya/system.nix
@@ -1,97 +0,0 @@
-{ pkgs, ... }:
-{
-  config = {
-    # Platform
-    nixpkgs.hostPlatform = "aarch64-linux";
-
-    # System packages
-    environment.systemPackages = with pkgs; [
-      docker
-      docker-compose
-      kind
-      wireguard-tools
-      syncthing
-      vim
-      htop
-      curl
-      git
-    ];
-
-    # Docker systemd service
-    systemd.services.docker = {
-      description = "Docker Application Container Engine";
-      wants = [ "network-online.target" ];
-      after = [
-        "network-online.target"
-        "containerd.service"
-      ];
-      wantedBy = [ "system-manager.target" ];
-      path = [
-        pkgs.docker
-        pkgs.kmod
-        pkgs.iptables
-      ];
-      serviceConfig = {
-        Type = "notify";
-        ExecStart = "${pkgs.docker}/bin/dockerd";
-        ExecReload = "${pkgs.coreutils}/bin/kill -s HUP $MAINPID";
-        TimeoutStartSec = "0";
-        RestartSec = "2";
-        Restart = "always";
-        Delegate = "yes";
-        KillMode = "process";
-        LimitNOFILE = "infinity";
-        LimitNPROC = "infinity";
-        TasksMax = "infinity";
-      };
-    };
-
-    # Containerd systemd service (required by Docker)
-    systemd.services.containerd = {
-      description = "containerd container runtime";
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      wantedBy = [ "system-manager.target" ];
-      serviceConfig = {
-        Type = "notify";
-        ExecStart = "${pkgs.docker}/bin/containerd";
-        Restart = "always";
-        Delegate = "yes";
-        KillMode = "process";
-        LimitNOFILE = "1048576";
-        TasksMax = "infinity";
-      };
-    };
-
-    # WireGuard wg0 service
-    systemd.services.wireguard-wg0 = {
-      description = "WireGuard VPN (wg0)";
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      wantedBy = [ "system-manager.target" ];
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        ExecStart = "${pkgs.wireguard-tools}/bin/wg-quick up wg0";
-        ExecStop = "${pkgs.wireguard-tools}/bin/wg-quick down wg0";
-      };
-    };
-
-    # WireGuard configuration file
-    # NOTE: Private key must be added manually to /etc/wireguard/private.key
-    environment.etc."wireguard/wg0.conf" = {
-      text = ''
-        [Interface]
-        PrivateKey = PLACEHOLDER_REPLACE_MANUALLY
-        Address = 10.100.0.80/24
-
-        [Peer]
-        PublicKey = PQD1dtxhy9NMbmfy2OQPeLiQg0Alcfa1Mo4HVN5WqgA=
-        AllowedIPs = 10.100.0.0/24
-        Endpoint = 46.224.100.116:51820
-        PersistentKeepalive = 25
-      '';
-      mode = "0600";
-    };
-  };
-}
systems/system-manager.nix
@@ -1,114 +0,0 @@
-{
-  config,
-  hostname,
-  inputs,
-  lib,
-  outputs,
-  ...
-}:
-{
-  imports = [
-    (./. + "/${hostname}/system.nix")
-
-    # ./common/base
-  ];
-
-  nixpkgs = {
-    # NOTE: Overlays might cause infinite recursion in system-manager
-    # Only apply them for NixOS systems
-    overlays = lib.optionals (!(config.system-manager.allowAnyDistro or false)) [
-      # Our own flake exports (from overlays and pkgs dir)
-      outputs.overlays.additions
-      outputs.overlays.modifications
-      outputs.overlays.unstable-packages
-
-      # And from other flakes
-      inputs.emacs-overlay.overlay
-      inputs.chapeau-rouge.overlays.openshift
-      inputs.chick-group.overlays.default
-      inputs.agenix.overlays.default
-
-      # Migrate to "modifications"
-      (_: prev: {
-        inherit (inputs.buildkit-tekton.packages.${prev.stdenv.hostPlatform.system}) tkn-local;
-      })
-    ];
-    config = {
-      allowUnfree = true;
-    };
-  };
-  nix =
-    lib.optionalAttrs (!(config.system-manager.allowAnyDistro or false)) {
-      # This will add each flake input as a registry
-      # To make nix3 commands consistent with your flake
-      # NOTE: These options only exist in NixOS, not in system-manager
-      registry = lib.mkForce (lib.mapAttrs (_: value: { flake = value; }) inputs);
-
-      # This will additionally add your inputs to the system's legacy channels
-      # Making legacy nix commands consistent as well, awesome!
-      nixPath = lib.mkForce (
-        lib.mapAttrsToList (key: value: "${key}=${value.to.path}") config.nix.registry
-      );
-    }
-    // lib.optionalAttrs (!(config.system-manager.allowAnyDistro or false)) {
-      # NOTE: optimise only exists in NixOS, not in system-manager
-      optimise = {
-        automatic = true;
-        dates = [
-          "01:10"
-          "12:10"
-        ];
-      };
-    }
-    // {
-
-      settings = {
-        auto-optimise-store = true;
-        experimental-features = [
-          "nix-command"
-          "flakes"
-        ];
-        sandbox = true;
-        allowed-users = [
-          "@wheel"
-        ];
-        trusted-users = [
-          "root"
-          "@wheel"
-        ];
-        # See https://nixos.org/manual/nix/stable/command-ref/conf-file#conf-use-xdg-base-directories
-        use-xdg-base-directories = true;
-
-        # Add some "caches" (substituters)
-        substituters = [
-          "https://cache.nixos.org/"
-          "https://r-ryantm.cachix.org"
-          "https://shortbrain.cachix.org"
-          "https://vdemeester.cachix.org"
-          "https://chapeau-rouge.cachix.org"
-        ];
-        trusted-public-keys = [
-          "r-ryantm.cachix.org-1:gkUbLkouDAyvBdpBX0JOdIiD2/DP1ldF3Z3Y6Gqcc4c="
-          "shortbrain.cachix.org-1:dqXcXzM0yXs3eo9ChmMfmob93eemwNyhTx7wCR4IjeQ="
-          "mic92.cachix.org-1:gi8IhgiT3CYZnJsaW7fxznzTkMUOn1RY4GmXdT/nXYQ="
-          "chapeau-rouge.cachix.org-1:r34IG766Ez4Eeanr7Zx+egzXLE2Zgvc+XRspYZPDAn8="
-          "vdemeester.cachix.org-1:eZWNOrLR9A9szeMahn9ENaoT9DB3WgOos8va+d2CU44="
-        ];
-      };
-
-      extraOptions = ''
-        connect-timeout = 20
-        build-cores = 0
-        keep-outputs = true
-        keep-derivations = true
-        builders-use-substitutes = true
-      '';
-    }
-    // lib.optionalAttrs (!(config.system-manager.allowAnyDistro or false)) {
-      # On laptops at least, make the daemon and builders low priority
-      # to have a responding system while building
-      # NOTE: These options only exist in NixOS, not in system-manager
-      daemonIOSchedClass = "idle";
-      daemonCPUSchedPolicy = "idle";
-    };
-}
flake.lock
@@ -361,22 +361,6 @@
         "type": "github"
       }
     },
-    "flake-compat_4": {
-      "flake": false,
-      "locked": {
-        "lastModified": 1767039857,
-        "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
-        "owner": "edolstra",
-        "repo": "flake-compat",
-        "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
-        "type": "github"
-      },
-      "original": {
-        "owner": "edolstra",
-        "repo": "flake-compat",
-        "type": "github"
-      }
-    },
     "flake-parts": {
       "inputs": {
         "nixpkgs-lib": "nixpkgs-lib"
@@ -413,28 +397,6 @@
         "type": "github"
       }
     },
-    "flake-parts_3": {
-      "inputs": {
-        "nixpkgs-lib": [
-          "system-manager",
-          "userborn",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1768135262,
-        "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
-        "owner": "hercules-ci",
-        "repo": "flake-parts",
-        "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
-        "type": "github"
-      },
-      "original": {
-        "owner": "hercules-ci",
-        "repo": "flake-parts",
-        "type": "github"
-      }
-    },
     "flake-utils": {
       "locked": {
         "lastModified": 1614513358,
@@ -452,7 +414,7 @@
     },
     "flake-utils_2": {
       "inputs": {
-        "systems": "systems_5"
+        "systems": "systems_4"
       },
       "locked": {
         "lastModified": 1731533236,
@@ -468,29 +430,6 @@
         "type": "github"
       }
     },
-    "gitignore": {
-      "inputs": {
-        "nixpkgs": [
-          "system-manager",
-          "userborn",
-          "pre-commit-hooks-nix",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1709087332,
-        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
-        "owner": "hercules-ci",
-        "repo": "gitignore.nix",
-        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
-        "type": "github"
-      },
-      "original": {
-        "owner": "hercules-ci",
-        "repo": "gitignore.nix",
-        "type": "github"
-      }
-    },
     "go-org-readwise": {
       "inputs": {
         "flake-parts": [
@@ -1012,34 +951,6 @@
         "type": "github"
       }
     },
-    "pre-commit-hooks-nix": {
-      "inputs": {
-        "flake-compat": [
-          "system-manager",
-          "userborn",
-          "flake-compat"
-        ],
-        "gitignore": "gitignore",
-        "nixpkgs": [
-          "system-manager",
-          "userborn",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1769069492,
-        "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
-        "owner": "cachix",
-        "repo": "pre-commit-hooks.nix",
-        "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
-        "type": "github"
-      },
-      "original": {
-        "owner": "cachix",
-        "repo": "pre-commit-hooks.nix",
-        "type": "github"
-      }
-    },
     "radian": {
       "inputs": {
         "flake-parts": [
@@ -1093,7 +1004,6 @@
         "praetorian": "praetorian",
         "pre-commit-hooks": "pre-commit-hooks",
         "radian": "radian",
-        "system-manager": "system-manager",
         "voxtype": "voxtype"
       }
     },
@@ -1118,28 +1028,6 @@
         "type": "github"
       }
     },
-    "system-manager": {
-      "inputs": {
-        "flake-compat": "flake-compat_4",
-        "nixpkgs": [
-          "nixpkgs"
-        ],
-        "userborn": "userborn"
-      },
-      "locked": {
-        "lastModified": 1788643028,
-        "narHash": "sha256-llyR4NuuJLDS49y8j9zbGi5pzjSuuc6CB8ipPJlXfIk=",
-        "owner": "numtide",
-        "repo": "system-manager",
-        "rev": "41d9628959faa2d12f65057aeb6f566c4ccfbd3d",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "system-manager",
-        "type": "github"
-      }
-    },
     "systems": {
       "locked": {
         "lastModified": 1681028828,
@@ -1200,21 +1088,6 @@
         "type": "github"
       }
     },
-    "systems_5": {
-      "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": [
@@ -1257,35 +1130,6 @@
         "type": "github"
       }
     },
-    "userborn": {
-      "inputs": {
-        "flake-compat": [
-          "system-manager",
-          "flake-compat"
-        ],
-        "flake-parts": "flake-parts_3",
-        "nixpkgs": [
-          "system-manager",
-          "nixpkgs"
-        ],
-        "pre-commit-hooks-nix": "pre-commit-hooks-nix",
-        "systems": "systems_4"
-      },
-      "locked": {
-        "lastModified": 1770377964,
-        "narHash": "sha256-q2pnlX2IW0kg80GLFnwWd/GigIpkuZnyKPLhrgJql3E=",
-        "owner": "jfroche",
-        "repo": "userborn",
-        "rev": "55c2cd7952c207a62736a5bbd9499ea73da18d24",
-        "type": "github"
-      },
-      "original": {
-        "owner": "jfroche",
-        "ref": "system-manager",
-        "repo": "userborn",
-        "type": "github"
-      }
-    },
     "voxtype": {
       "inputs": {
         "flake-utils": "flake-utils_2",
flake.nix
@@ -64,6 +64,11 @@
           hostname = "kyushu";
           desktop = "niri";
         };
+        "vincent@nagoya" = libx.mkHome {
+          username = "vincent";
+          hostname = "nagoya";
+          system = "aarch64-linux";
+        };
         # headless machine
         "vincent@aion" = libx.mkHome {
           username = "vincent";
@@ -162,12 +167,20 @@
           aomi = mkFedoraConfigs "aomi";
         };
 
-      # system-manager configurations
-      systemConfigs = {
-        nagoya = libx.mkSystemManager {
-          hostname = "nagoya";
-          system = "aarch64-linux";
-        };
+      hostConfigs = {
+        nagoya =
+          let
+            pkgs = import inputs.nixpkgs { system = "aarch64-linux"; };
+            globals = import ./globals.nix {
+              inherit (inputs.nixpkgs) lib;
+              hostname = "nagoya";
+            };
+            hostLibx = import ./lib/functions.nix { inherit (inputs.nixpkgs) lib; };
+          in
+          import ./systems/nagoya/gen-configs.nix {
+            inherit pkgs globals;
+            libx = hostLibx;
+          };
       };
 
       images = {
@@ -454,9 +467,6 @@
     harmonia.url = "github:nix-community/harmonia";
     harmonia.inputs.nixpkgs.follows = "nixpkgs";
 
-    system-manager.url = "github:numtide/system-manager";
-    system-manager.inputs.nixpkgs.follows = "nixpkgs";
-
     nix-github-actions.url = "github:nix-community/nix-github-actions";
     nix-github-actions.inputs.nixpkgs.follows = "nixpkgs";
 
Makefile
@@ -30,9 +30,16 @@ 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/nagoya/boot
-host/nagoya/boot:
-	nixos-rebuild --target-host root@192.168.1.80 --flake .#nagoya boot
+.PHONY: host/nagoya/build
+host/nagoya/build: ## Build Home Manager and generated host files for Nagoya
+	nix build .#homeConfigurations.vincent@nagoya.activationPackage --no-link
+	nix build .#hostConfigs.nagoya --no-link
+
+.PHONY: host/nagoya/switch
+host/nagoya/switch: ## Deploy generated host files and switch Home Manager on Nagoya
+	nix build .#hostConfigs.nagoya
+	sudo ./result/deploy
+	nix run home-manager -- switch --flake .#vincent@nagoya -b backup
 
 .PHONY: host/kobe/boot
 host/kobe/boot:
README.md
@@ -21,7 +21,7 @@ This repository manages:
 โ”œโ”€โ”€ pkgs/             # Custom Nix packages (exposed via overlays)
 โ”œโ”€โ”€ modules/          # Custom NixOS modules (wireguard, govanityurl, gosmee, ...)
 โ”œโ”€โ”€ overlays/         # Nix overlays (additions, modifications, unstable)
-โ”œโ”€โ”€ lib/              # Flake helpers (mkHost, mkHome, mkSystemManager)
+โ”œโ”€โ”€ lib/              # Flake helpers (mkHost, mkHome)
 โ”œโ”€โ”€ tools/            # Custom tools and scripts
 โ”œโ”€โ”€ dots/             # Dotfiles managed outside home-manager
 โ”œโ”€โ”€ keyboards/        # QMK/ZMK keyboard firmware (Moonlander, Corne)