Technical reference for the castle library itself. For operating your
own castle, see the README that nix flake init -t github:reflection-dev/castle
copies into your instance.
nixosModules.default— aggregator; imports every NixOS castle module plus sops-nix. Everything is opt-in viacastle.*.enable.nixosModules.{nix-defaults,hetzner-cloud,zfs,initrd-ssh,ssh,sops,identities,caddy,postgres,tower,dev-tunnel,services-forgejo,services-discourse}— individual leaves, useful if you want to consume one piece without the aggregator.darwinModules.default— nix-darwin aggregator for Mac towers.darwinModules.{identities,tower}— individual leaves for darwin.diskoConfigs.zfs-single—/dev/sdalayout:bios_boot(1M) +/bootext4 (1G) +rpoolZFS (aes-256-gcm+zstd) with datasetsroot,nix,home,reserved.lib.mkNixosConfigs { humans ? {}, agents ? {}, hosts }— takes the payload returned by an instance'shosts.nixand yieldsnixosConfigurations. Auto-importsnixosModules.defaultand the chosendiskoConfigs.<disk>(defaultzfs-single) into each host, and injectscastle.humans = humans; castle.agents = agentsso the global registries propagate everywhere. Skips hosts whosecastle.host.archis darwin (those go through the darwin path).lib.mkDarwinConfigs { humans ? {}, agents ? {}, hosts }— same payload, producesdarwinConfigurations. Pulls in home-manager and the darwin aggregator. Only picks up hosts whosecastle.host.archisx86_64-darwinoraarch64-darwin.lib.mkDeploy nixosConfigurations— turns eachnixosConfigurationinto adeploy.nodes.<name>entry for deploy-rs. Darwin hosts are activated withdarwin-rebuild switch --flake .#<name>for now; deploy-rs integration for darwin is deferred.apps.<system>.install— wrapsnixos-anywhereand pre-flight secret handling.packages.<system>.kimi-code— the Kimi Code CLI as a prebuilt-binary derivation (seepkgs/kimi-code.nix).nix run .#kimi-codeto try it.templates.default— the skeleton copied bynix flake init.
castle.tower.devTunnel.enable (requires castle.tower.enable) gives every
account in castle.tower.accounts, on that tower:
- a VS Code tunnel — a per-user
vscode-tunnel-<user>systemd service runningcode tunnel(from the unfreepkgs.vscode), reachable from vscode.dev / desktop VS Code with no inbound ports; and - the Kimi Code CLI (
kimi) in the account's packages.
Both are prebuilt dynamic binaries (Kimi's native single-file executable; the
vscode-server that code tunnel fetches on demand), so the module enables
programs.nix-ld with the libraries they need. It also allows the single
unfree package vscode via an allowUnfreePredicate (mkDefault, so a host
with its own unfree policy wins).
First-run auth is interactive and per-user (device-code flow) — by design, no tokens in the store:
- VS Code tunnel: the service prints a device code to its journal and waits.
Complete it once with
journalctl -u vscode-tunnel-<user> -f, open the URL, enter the code. The token persists under~/.vscode. - Kimi Code: run
kimias the user and/login(Kimi OAuth or a Moonshot API key).
By default both the first install and subsequent deploys build the system
closure locally, then copy it to the host. When your workstation's
architecture differs from the host's (e.g. deploying an x86_64-linux box
from an aarch64-linux machine) that means a slow emulated / cross build —
or none at all if no remote builder is configured. Build on the target box
instead:
- First install:
install-host <name> --build-on-remote(passes--build-on-remotetonixos-anywhere). - Subsequent deploys:
activate <name> --remote-build(passes--remote-buildtodeploy-rs).
Both are opt-in; omit them to keep building locally.
castle/
├── flake.nix
├── darwinModules/
│ ├── default.nix
│ ├── identities.nix
│ └── tower.nix
├── modules/
│ ├── default.nix # aggregator + castle.host options
│ ├── nix-defaults.nix
│ ├── hetzner-cloud.nix
│ ├── zfs.nix
│ ├── initrd-ssh.nix
│ ├── ssh.nix
│ ├── sops.nix
│ ├── identities.nix
│ ├── caddy.nix
│ ├── postgres.nix
│ ├── tower.nix
│ └── services/
│ ├── forgejo.nix
│ └── discourse.nix
├── disko/
│ └── zfs-single.nix
├── lib/
│ ├── identitySubmodule.nix
│ ├── mkNixosConfigs.nix
│ ├── mkDarwinConfigs.nix
│ └── mkDeploy.nix
├── install.sh # runs as `install-host` in the devShell
├── update-secrets.sh # runs as `update-secrets` in the devShell
└── templates/default/ # copied by `nix flake init`
| option | default |
|---|---|
castle.host.ipv4 |
(required) |
castle.host.sshKeys |
[] |
castle.hetzner.enable |
true |
castle.zfs.enable |
true |
castle.zfs.autoScrub |
true |
castle.initrdSsh.enable |
true |
castle.initrdSsh.port |
2222 |
castle.ssh.enable |
true |
castle.ssh.port |
22 |
castle.nixDefaults.enable |
true |
castle.nixDefaults.timeZone |
"UTC" |
castle.humans.<name>.email |
(required) |
castle.humans.<name>.admin |
false |
castle.humans.<name>.uid |
null |
castle.humans.<name>.sshKeys |
[] |
castle.humans.<name>.shell |
null |
castle.humans.<name>.editor |
null |
castle.humans.<name>.tools |
[] |
castle.humans.<name>.extraPackages |
null |
castle.agents.<name>.* |
same as humans |
castle.host.arch |
"x86_64-linux" |
castle.tower.enable |
false |
castle.tower.accounts |
[] |
castle.tower.defaultTools |
curated list |
castle.caddy.enable |
false, auto |
castle.postgres.enable |
false, auto |
castle.postgres.package |
postgresql_15 |
castle.services.forgejo.enable |
false |
castle.services.forgejo.domain |
(required) |
castle.services.discourse.enable |
false |
castle.services.discourse.domain |
(required) |
castle.services.discourse.smtp.host |
null |
castle.services.discourse.s3.* |
(required) |
Options marked auto enable themselves when a service that needs them turns on. You never set them by hand.