Archived
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e62e9c9a6a | ||
|
|
fe7fc55c04 | ||
|
|
6cdae391f4 | ||
|
|
95eb494370 | ||
|
|
9294d2fd25 | ||
|
|
0fe7ddf6e8 | ||
|
|
5c2d61d35a | ||
|
|
186e9187ce | ||
|
|
e6f15404f5 | ||
|
|
44a0acc18f | ||
|
|
1fc6e63178 | ||
|
|
3589fc31d7 | ||
|
|
89746718a9 | ||
|
|
232d0e7c40 | ||
|
|
0b60d3ff2d | ||
|
|
ff82e8885d | ||
|
|
9a7411d1dd | ||
|
|
2f829ba3e7 | ||
|
|
dedd69dc42 | ||
|
|
f7f670ca4c | ||
|
|
55ba283c82 | ||
|
|
2d46d25a67 | ||
|
|
f5d29be041 | ||
|
|
e10a1572c3 | ||
|
|
578ef70aa9 | ||
|
|
e9fcbbbbcb | ||
|
|
f46ae18672 | ||
|
|
fc277294f3 | ||
|
|
f3e5ea67a0 | ||
|
|
7a8aebf679 | ||
|
|
a8d95aad02 | ||
|
|
dac5fbd574 | ||
|
|
da0c651c60 | ||
|
|
97019205da | ||
|
|
5487490b8e | ||
|
|
543ea432f0 |
+63
-13
@@ -63,16 +63,6 @@ creation_rules:
|
|||||||
- *lxc-nix-cache
|
- *lxc-nix-cache
|
||||||
- *proxmox-nix-cache
|
- *proxmox-nix-cache
|
||||||
|
|
||||||
# Host keytab for nix-cache FreeIPA enrollment (binary sops file).
|
|
||||||
# Generate with: sops -e --input-type binary /tmp/nix-cache.keytab > secrets/nix-cache.keytab
|
|
||||||
- path_regex: secrets/nix-cache\.keytab$
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *admin
|
|
||||||
- *linode-nix-cache
|
|
||||||
- *lxc-nix-cache
|
|
||||||
- *proxmox-nix-cache
|
|
||||||
|
|
||||||
- path_regex: secrets/server\.yaml$
|
- path_regex: secrets/server\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
@@ -136,12 +126,72 @@ creation_rules:
|
|||||||
- *linode-gui
|
- *linode-gui
|
||||||
- *proxmox-gui
|
- *proxmox-gui
|
||||||
|
|
||||||
# Host keytab for tailscale-router FreeIPA enrollment (binary sops file).
|
# IPA host keytabs (binary sops files).
|
||||||
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
# Each keytab is encrypted for all platform variants of that host so any
|
||||||
|
# deployed variant can decrypt it at boot. Run
|
||||||
|
# scripts/ipa/create-nixos-ipa-host-account.sh <hostname> to enroll a new
|
||||||
|
# host and produce the keytab; this section is updated by that script.
|
||||||
|
|
||||||
|
- path_regex: secrets/nix-cache\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *linode-nix-cache
|
||||||
|
- *lxc-nix-cache
|
||||||
|
- *proxmox-nix-cache
|
||||||
|
|
||||||
- path_regex: secrets/tailscale-router\.keytab$
|
- path_regex: secrets/tailscale-router\.keytab$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *admin
|
- *admin
|
||||||
|
- *linode-tailscale-router
|
||||||
- *lxc-tailscale-router
|
- *lxc-tailscale-router
|
||||||
- *proxmox-tailscale-router
|
- *proxmox-tailscale-router
|
||||||
- *linode-tailscale-router
|
|
||||||
|
- path_regex: secrets/pxe-boot\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *lxc-pxe-boot
|
||||||
|
- *proxmox-pxe-boot
|
||||||
|
|
||||||
|
# nixos = the workstation (hosts/nixos/host.nix). All gui platform variants
|
||||||
|
# share the hostname "nixos" and must be able to decrypt at boot.
|
||||||
|
- path_regex: secrets/nixos\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *baremetal-gui
|
||||||
|
- *lxc-gui
|
||||||
|
- *proxmox-gui
|
||||||
|
- *linode-gui
|
||||||
|
|
||||||
|
- path_regex: secrets/server\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *linode-server
|
||||||
|
- *lxc-server
|
||||||
|
- *proxmox-server
|
||||||
|
|
||||||
|
- path_regex: secrets/docker\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *linode-docker
|
||||||
|
- *lxc-docker
|
||||||
|
- *proxmox-docker
|
||||||
|
|
||||||
|
- path_regex: secrets/tor-relay\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *lxc-tor-relay
|
||||||
|
|
||||||
|
- path_regex: secrets/nix-minimal\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *lxc-minimal
|
||||||
|
- *proxmox-minimal
|
||||||
|
- *linode-minimal
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ This repository contains flake-based NixOS configurations for Wayne's LAN
|
|||||||
servers and workstation.
|
servers and workstation.
|
||||||
|
|
||||||
The flake exposes NixOS configurations named `<platform>-<buildtype>`
|
The flake exposes NixOS configurations named `<platform>-<buildtype>`
|
||||||
(platforms: `linode`, `proxmox`, `lxc`; build types: `minimal`, `nix-cache`,
|
(platforms: `linode`, `proxmox`, `lxc`, `baremetal`; build types: `minimal`,
|
||||||
`server`, `docker`, `gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay`), generated from `modules/platforms/*`
|
`nix-cache`, `server`, `docker`, `gui`, `pxe-boot`, `tailscale-router`,
|
||||||
and `modules/build-types/*` by the `mkTarget` function in `flake.nix`. Not
|
`tor-relay`, `ha-server`), generated from `modules/platforms/*` and
|
||||||
every combination is built — `pxe-boot` has no `linode` variant. See
|
`modules/build-types/*` by the `mkTarget` function in `flake.nix`. Not every
|
||||||
`README.md` for the full current target list; treat `flake.nix` as the
|
combination is built — `pxe-boot` has no `linode` variant, `ha-server` only
|
||||||
source of truth since this list can drift.
|
exists on `proxmox`, and `tor-relay` only exists on `lxc`. See `README.md`
|
||||||
|
for the full current target list; treat `flake.nix` as the source of truth
|
||||||
|
since this list can drift.
|
||||||
|
|
||||||
Do not deploy, switch, reboot, repartition, format disks, or run destructive
|
Do not deploy, switch, reboot, repartition, format disks, or run destructive
|
||||||
install commands from this repository unless explicitly asked.
|
install commands from this repository unless explicitly asked.
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ pieces composed in `flake.nix`:
|
|||||||
|
|
||||||
- **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`, `baremetal`
|
- **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`, `baremetal`
|
||||||
- **Build types** (what it's for): `minimal`, `nix-cache`, `server`, `docker`,
|
- **Build types** (what it's for): `minimal`, `nix-cache`, `server`, `docker`,
|
||||||
`gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay`
|
`gui`, `pxe-boot`, `tailscale-router`, `tor-relay`, `ha-server`
|
||||||
|
|
||||||
Not every combination exists — `pxe-boot` has no `linode` variant, since
|
Not every combination exists — `pxe-boot` has no `linode` variant, since
|
||||||
PXE/DHCP/TFTP need LAN L2 adjacency that a Linode VPS doesn't have,
|
PXE/DHCP/TFTP need LAN L2 adjacency that a Linode VPS doesn't have,
|
||||||
`tor-relay` currently only exists as `lxc-tor-relay`, and `baremetal`
|
`tor-relay` and `ha-server` currently only exist on `lxc`/`proxmox`, and
|
||||||
currently only exists as `baremetal-gui` (the real gui-host hardware). The
|
`baremetal` currently only exists as `baremetal-gui` (the real gui-host
|
||||||
full list:
|
hardware). The full list:
|
||||||
|
|
||||||
| Target | Purpose |
|
| Target | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -29,8 +29,9 @@ full list:
|
|||||||
| `linode-gui` / `proxmox-gui` / `lxc-gui` | Cinnamon desktop workstation — previously the flat `nixos` target |
|
| `linode-gui` / `proxmox-gui` / `lxc-gui` | Cinnamon desktop workstation — previously the flat `nixos` target |
|
||||||
| `baremetal-gui` | Same Cinnamon desktop workstation, on the real gui-host hardware — ZFS RAID0 root, systemd-boot |
|
| `baremetal-gui` | Same Cinnamon desktop workstation, on the real gui-host hardware — ZFS RAID0 root, systemd-boot |
|
||||||
| `proxmox-pxe-boot` / `lxc-pxe-boot` | HTTP/iPXE boot asset host — previously the flat `pxe-boot` target |
|
| `proxmox-pxe-boot` / `lxc-pxe-boot` | HTTP/iPXE boot asset host — previously the flat `pxe-boot` target |
|
||||||
| `linode-tailscale-exit-node` / `proxmox-tailscale-exit-node` / `lxc-tailscale-exit-node` | Tailscale exit node |
|
| `linode-tailscale-router` / `proxmox-tailscale-router` / `lxc-tailscale-router` | Tailscale subnet router + MagicDNS forwarder for the LAN |
|
||||||
| `lxc-tor-relay` | Tor middle relay |
|
| `lxc-tor-relay` | Tor middle relay |
|
||||||
|
| `proxmox-ha-server-1` / `proxmox-ha-server-2` | HA file-server cluster nodes — DRBD + XFS + iSCSI + NFS, managed by Corosync + Pacemaker |
|
||||||
|
|
||||||
Which variant of a given buildtype is actually deployed isn't tracked
|
Which variant of a given buildtype is actually deployed isn't tracked
|
||||||
anywhere in this repo — that's live infrastructure state, not something a
|
anywhere in this repo — that's live infrastructure state, not something a
|
||||||
@@ -47,8 +48,7 @@ section for which is which.
|
|||||||
|
|
||||||
Each buildtype's `hosts/<name>/host.nix` carries the per-machine identity
|
Each buildtype's `hosts/<name>/host.nix` carries the per-machine identity
|
||||||
(hostname, hostId, per-machine secrets, `system.stateVersion`) that must stay
|
(hostname, hostId, per-machine secrets, `system.stateVersion`) that must stay
|
||||||
fixed regardless of which platform it's built for — see
|
fixed regardless of which platform it's built for. Every deployed host
|
||||||
`flake-target-refactor-spec.md` for the full rationale. Every deployed host
|
|
||||||
stamps its own active target name into `/etc/flake-target` at build time, so
|
stamps its own active target name into `/etc/flake-target` at build time, so
|
||||||
`nixos-rebuild switch --flake .#$(cat /etc/flake-target)` always picks up the
|
`nixos-rebuild switch --flake .#$(cat /etc/flake-target)` always picks up the
|
||||||
right one even after a platform migration changes the flake attribute name.
|
right one even after a platform migration changes the flake attribute name.
|
||||||
@@ -167,7 +167,6 @@ per-boot host key for sops-nix to derive from on ephemeral media) — see
|
|||||||
(`vars/per-machine/<target>/openssh/`, committed and sops-encrypted) for
|
(`vars/per-machine/<target>/openssh/`, committed and sops-encrypted) for
|
||||||
their SSH host keys.
|
their SSH host keys.
|
||||||
|
|
||||||
This repository's git *history* still contains secrets committed before this
|
This repository's git *history* still contains secrets committed before the
|
||||||
migration (see `remove-sensetive-info-refactor.md`) — those are being
|
sops-nix migration — those are being scrubbed and rotated separately; don't
|
||||||
scrubbed and rotated separately; don't treat the repo as safe to make public
|
treat the repo as safe to make public until that's finished.
|
||||||
until that's finished.
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ see "LXC hosts" immediately below for why those are different.**
|
|||||||
## LXC hosts
|
## LXC hosts
|
||||||
|
|
||||||
`lxc-*` targets (`lxc-minimal`, `lxc-nix-cache`, `lxc-server`, `lxc-docker`,
|
`lxc-*` targets (`lxc-minimal`, `lxc-nix-cache`, `lxc-server`, `lxc-docker`,
|
||||||
`lxc-gui`, `lxc-pxe-boot`, `lxc-tailscale-exit-node`, `lxc-tor-relay`) are **not** installed via `auto-install.sh` — the
|
`lxc-gui`, `lxc-pxe-boot`, `lxc-tailscale-router`, `lxc-tor-relay`) are **not** installed via `auto-install.sh` — the
|
||||||
interactive menu deliberately excludes them. Don't try to select one there;
|
interactive menu deliberately excludes them. Don't try to select one there;
|
||||||
`nixos-install` would bind-mount `/` onto `/mnt` (LXC containers have no raw
|
`nixos-install` would bind-mount `/` onto `/mnt` (LXC containers have no raw
|
||||||
disk to partition) and then refuse to touch the filesystem it's currently
|
disk to partition) and then refuse to touch the filesystem it's currently
|
||||||
@@ -134,12 +134,14 @@ Flake outputs:
|
|||||||
nixosConfigurations.installer # ISO/netboot installer image
|
nixosConfigurations.installer # ISO/netboot installer image
|
||||||
|
|
||||||
packages.x86_64-linux.iso # installer ISO/netboot image
|
packages.x86_64-linux.iso # installer ISO/netboot image
|
||||||
packages.x86_64-linux.pxe # netboot-ipxe + netboot-initrd + netboot-kernel, bundled
|
packages.x86_64-linux.pxe # auto-installer netboot bundle (kernel + initrd + ipxe script)
|
||||||
|
packages.x86_64-linux.pxe-minimal # vanilla NixOS minimal netboot bundle (no installer wiring)
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix build .#iso
|
nix build .#iso
|
||||||
nix build .#pxe
|
nix build .#pxe
|
||||||
|
nix build .#pxe-minimal
|
||||||
```
|
```
|
||||||
|
|
||||||
There's no `nixosConfigurations.proxmox-lxc` (installer-boots-as-an-LXC-
|
There's no `nixosConfigurations.proxmox-lxc` (installer-boots-as-an-LXC-
|
||||||
|
|||||||
Generated
+6
-6
@@ -173,11 +173,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784350909,
|
"lastModified": 1785119570,
|
||||||
"narHash": "sha256-ZWyzLbS1yKUTeFJLmdVuWNnHttL333/ldJbEE+KzCrM=",
|
"narHash": "sha256-Rgs2xKnGLFWQscxUaXX07oyZeuMDOHEbqDOsgliLFGM=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "4ce190229c73d44536caa7072f6308fb2d8feeb3",
|
"rev": "d4fd24667c8cbef124bb70a20380cab75ec8474d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -259,11 +259,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784432872,
|
"lastModified": 1785104993,
|
||||||
"narHash": "sha256-n3gKTBIV4ZA5VQpUakffBe3KGu4+mhPoA34rrqS0GkA=",
|
"narHash": "sha256-eKbrvPoAOFutbYMdbB3r5EQVmFxKv24iKqHPPUXA0gM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "fd1462031fdee08f65fd0b4c6b64e22239a77870",
|
"rev": "8623c4c20aa4ca2f5fb81510d2944066c3fb0d96",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
};
|
};
|
||||||
boot.zfs.forceImportRoot = false;
|
boot.zfs.forceImportRoot = false;
|
||||||
|
|
||||||
|
# Only advertise the LAN interface to IPA DNS. Without this, SSSD registers
|
||||||
|
# every Docker bridge (172.x.x.x) as an A record for docker.sweet.home —
|
||||||
|
# the default dyndns.interface = "*" catches them all.
|
||||||
|
security.ipa.dyndns.interface = vars.lxcLanInterface; # eth0
|
||||||
|
|
||||||
# Preserved from the pre-refactor `docker` target — stateVersion must never
|
# Preserved from the pre-refactor `docker` target — stateVersion must never
|
||||||
# be bumped on an already-installed machine.
|
# be bumped on an already-installed machine.
|
||||||
system.stateVersion = "25.05";
|
system.stateVersion = "25.05";
|
||||||
|
|||||||
@@ -6,15 +6,10 @@
|
|||||||
name = "nix-cache";
|
name = "nix-cache";
|
||||||
sopsFile = ../../secrets/nix-cache.yaml;
|
sopsFile = ../../secrets/nix-cache.yaml;
|
||||||
})
|
})
|
||||||
(import ../../modules/ipa/client.nix {
|
|
||||||
keytabSopsFile = ../../secrets/nix-cache.keytab;
|
|
||||||
caCertFile = ../../certs/ipa-ca.crt;
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
hostName = vars.nixCacheHost;
|
hostName = vars.nixCacheHost;
|
||||||
domain = vars.homeDomain;
|
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
||||||
address = vars.nixCacheIp;
|
address = vars.nixCacheIp;
|
||||||
@@ -25,7 +20,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.beszel.agent.environment = {
|
services.beszel.agent.environment = {
|
||||||
#DOCKER_HOST = "tcp://docker-socket-proxy:2375";
|
|
||||||
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.beszel.agent.environment = {
|
services.beszel.agent.environment = {
|
||||||
#DOCKER_HOST = "tcp://docker-socket-proxy:2375";
|
|
||||||
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
||||||
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
|
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
|
||||||
LOG_LEVEL = "debug";
|
LOG_LEVEL = "debug";
|
||||||
|
|||||||
@@ -6,11 +6,6 @@
|
|||||||
name = "tailscale-router";
|
name = "tailscale-router";
|
||||||
sopsFile = ../../secrets/tailscale-router.yaml;
|
sopsFile = ../../secrets/tailscale-router.yaml;
|
||||||
})
|
})
|
||||||
(import ../../modules/ipa/client.nix {
|
|
||||||
keytabSopsFile = ../../secrets/tailscale-router.keytab;
|
|
||||||
caCertFile = ../../certs/ipa-ca.crt;
|
|
||||||
})
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
|
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
|
||||||
|
# Grant the IPA domain user docker access via the local group so that
|
||||||
|
# `wayne` can manage containers without sudo.
|
||||||
|
users.groups.docker.members = [ "wayne" ];
|
||||||
services.openssh.settings.PermitRootLogin = "yes";
|
services.openssh.settings.PermitRootLogin = "yes";
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
{ config, pkgs, lib, inputs, vars, ... }:
|
{ config, pkgs, lib, inputs, vars, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
imports = [
|
||||||
|
../docker/enable-service.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
nixpkgs.overlays = [
|
||||||
|
(final: prev: {
|
||||||
|
docker = prev.docker_29;
|
||||||
|
docker_cli = prev.docker_29;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
inputs.nixos-conf-editor.packages.${pkgs.stdenv.hostPlatform.system}.nixos-conf-editor
|
inputs.nixos-conf-editor.packages.${pkgs.stdenv.hostPlatform.system}.nixos-conf-editor
|
||||||
nodejs
|
nodejs
|
||||||
@@ -18,7 +29,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
boot.loader.grub.useOSProber = true;
|
boot.loader.grub.useOSProber = true;
|
||||||
programs.direnv.enable = true;
|
programs.direnv.enable = true;
|
||||||
services = {
|
services = {
|
||||||
xserver = {
|
xserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -15,6 +15,16 @@
|
|||||||
# under services.beszel.agent.environment.KEY once the hub accepts the
|
# under services.beszel.agent.environment.KEY once the hub accepts the
|
||||||
# new agents, following the pattern in hosts/server/host.nix.
|
# new agents, following the pattern in hosts/server/host.nix.
|
||||||
{ lib, vars, ... }:
|
{ lib, vars, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Generates /etc/exports lines for all nfsShares data entries. Shared
|
||||||
|
# pattern with modules/build-types/server.nix — both export the same
|
||||||
|
# set of shares, differing only in the storage root they serve from.
|
||||||
|
mkNfsExports = storageRoot:
|
||||||
|
lib.concatMapStrings
|
||||||
|
(share: " ${storageRoot}/${share.subpath} ${vars.lanCidr}${vars.nfsShares.options}\n")
|
||||||
|
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares));
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../ha/pacemaker-stack.nix
|
../ha/pacemaker-stack.nix
|
||||||
@@ -25,16 +35,7 @@
|
|||||||
|
|
||||||
services.nfs.server = {
|
services.nfs.server = {
|
||||||
enable = true;
|
enable = true;
|
||||||
exports = ''
|
exports = mkNfsExports vars.haStorageRoot;
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.dockerConfig.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.dockerVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.dockerDatabases.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.nextcloudData.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.raspiVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.proxmoxIsos.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.proxmoxLxcImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.pxebootImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Pacemaker controls nfs-server — prevent systemd from starting it at boot
|
# Pacemaker controls nfs-server — prevent systemd from starting it at boot
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ let
|
|||||||
lib.concatMap (share: ancestors share.subpath)
|
lib.concatMap (share: ancestors share.subpath)
|
||||||
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares))
|
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Generates /etc/exports lines for all nfsShares data entries (every
|
||||||
|
# attrset value — excludes the bare `options` string). Both server and
|
||||||
|
# ha-server export the same share set from different storage roots, so
|
||||||
|
# this helper is the single source of truth for the export line format.
|
||||||
|
mkNfsExports = storageRoot:
|
||||||
|
lib.concatMapStrings
|
||||||
|
(share: " ${storageRoot}/${share.subpath} ${vars.lanCidr}${vars.nfsShares.options}\n")
|
||||||
|
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares));
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -95,16 +104,7 @@ in
|
|||||||
|
|
||||||
services.nfs.server = {
|
services.nfs.server = {
|
||||||
enable = true;
|
enable = true;
|
||||||
exports = ''
|
exports = mkNfsExports vars.storageRoot;
|
||||||
${vars.storageRoot}/${vars.nfsShares.dockerConfig.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.dockerDatabases.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.nextcloudData.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.raspiVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.proxmoxIsos.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.proxmoxLxcImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.pxebootImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# mountd (20048) is needed for showmount/NFSv3 mount protocol — without it
|
# mountd (20048) is needed for showmount/NFSv3 mount protocol — without it
|
||||||
|
|||||||
@@ -1,50 +1,7 @@
|
|||||||
{ config, pkgs, lib, vars, ... }:
|
_:
|
||||||
|
|
||||||
let
|
|
||||||
# Flake attribute names are now <platform>-<buildtype> (e.g. proxmox-docker)
|
|
||||||
# and no longer match networking.hostName, since a host's hostname stays
|
|
||||||
# fixed while the platform backing it can change. Each nixosConfiguration
|
|
||||||
# stamps its own active target name into /etc/flake-target at build time.
|
|
||||||
mySwitchCmd = ''
|
|
||||||
sudo nixos-rebuild switch \
|
|
||||||
--no-write-lock-file \
|
|
||||||
--refresh \
|
|
||||||
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
|
||||||
'';
|
|
||||||
myTestCmd = ''
|
|
||||||
sudo nixos-rebuild test \
|
|
||||||
--no-write-lock-file \
|
|
||||||
--refresh \
|
|
||||||
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
|
||||||
'';
|
|
||||||
|
|
||||||
# lxc-* hosts pre-seed their SSH host key at build time (see
|
|
||||||
# modules/platforms/lxc.nix) so sops-nix's .sops.yaml recipient matches on
|
|
||||||
# first boot -- without it, secrets permanently fail to decrypt (see that
|
|
||||||
# file's comment for the confirmed failure). That requires --impure plus
|
|
||||||
# NIXOS_HOST_KEYS_DIR pointing at the repo's host-keys/ dir, same pattern
|
|
||||||
# docs/auto-installer.md uses for the installer ISO. A function, not a
|
|
||||||
# shellAlias, since the target name has to interpolate into the middle of
|
|
||||||
# the flake attribute path, not just append after it. Must be run from the
|
|
||||||
# repo root, same as every other host-keys/ command in this repo.
|
|
||||||
buildImageFn = ''
|
|
||||||
buildImage() {
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo "usage: buildImage <flake-target> (e.g. lxc-docker)" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build --impure \
|
|
||||||
".#nixosConfigurations.$1.config.system.build.tarball"
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
programs.bash = {
|
# Switch-nix, Test-nix, and buildImage are defined system-wide in
|
||||||
enable = true;
|
# modules/common/configuration.nix so all users (including IPA accounts)
|
||||||
shellAliases = {
|
# get them. Add any Home-Manager-only per-user shell config here.
|
||||||
"Switch-nix" = mySwitchCmd;
|
|
||||||
"Test-nix" = myTestCmd;
|
|
||||||
};
|
|
||||||
initExtra = buildImageFn;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,45 @@
|
|||||||
{ config, lib, pkgs, vars, ... }:
|
{ config, lib, pkgs, vars, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
switchCmd = ''
|
||||||
|
sudo nixos-rebuild switch \
|
||||||
|
--no-write-lock-file \
|
||||||
|
--refresh \
|
||||||
|
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
||||||
|
'';
|
||||||
|
testCmd = ''
|
||||||
|
sudo nixos-rebuild test \
|
||||||
|
--no-write-lock-file \
|
||||||
|
--refresh \
|
||||||
|
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
||||||
|
'';
|
||||||
|
buildImageFn = ''
|
||||||
|
buildImage() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "usage: buildImage <flake-target> (e.g. lxc-docker)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build --impure \
|
||||||
|
".#nixosConfigurations.$1.config.system.build.tarball"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[
|
|
||||||
# Include the results of the hardware scan.
|
|
||||||
# ./hardware-configuration.nix
|
|
||||||
./set-locale.nix
|
./set-locale.nix
|
||||||
|
../ipa/client.nix
|
||||||
];
|
];
|
||||||
# Use the GRUB 2 boot loader.
|
|
||||||
# boot.loader.grub.enable = true;
|
|
||||||
#boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
|
|
||||||
|
|
||||||
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
|
# System-wide shell config so all users (including IPA accounts) get the
|
||||||
|
# same management aliases as the local nixos user's Home Manager provides.
|
||||||
|
programs.bash = {
|
||||||
|
shellAliases = {
|
||||||
|
"Switch-nix" = switchCmd;
|
||||||
|
"Test-nix" = testCmd;
|
||||||
|
};
|
||||||
|
interactiveShellInit = buildImageFn;
|
||||||
|
};
|
||||||
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
# Recommended over the true default (bypasses ZFS's own import safeguards)
|
# Recommended over the true default (bypasses ZFS's own import safeguards)
|
||||||
# per the option's own docs; matches hosts/docker/host.nix and
|
# per the option's own docs; matches hosts/docker/host.nix and
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Shared activation-script logic to preserve the SSH host key across
|
||||||
|
# nixos-rebuild on platforms that embed the key via environment.etc (lxc and
|
||||||
|
# proxmox). When NIXOS_HOST_KEYS_DIR is not set the key is absent from
|
||||||
|
# environment.etc, and NixOS's etc activation removes any /etc file not in
|
||||||
|
# the new generation — which would destroy the live key and break sops-nix
|
||||||
|
# decryption permanently. These scripts save the key to /run before etc
|
||||||
|
# removes it, then restore it afterward.
|
||||||
|
#
|
||||||
|
# Explicit deps enforce the correct ordering: without them the topological
|
||||||
|
# sort places preserveSshHostKey after etc (confirmed live on lxc-tor-relay:
|
||||||
|
# position 7 vs etc's position 5), so the key is gone before it can be saved.
|
||||||
|
_: {
|
||||||
|
system.activationScripts = {
|
||||||
|
preserveSshHostKey = ''
|
||||||
|
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
||||||
|
cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp
|
||||||
|
cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
restoreSshHostKey = {
|
||||||
|
deps = [ "etc" ];
|
||||||
|
text = ''
|
||||||
|
if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then
|
||||||
|
install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key
|
||||||
|
install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub
|
||||||
|
fi
|
||||||
|
rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
etc = { deps = [ "preserveSshHostKey" ]; };
|
||||||
|
setupSecrets = { deps = [ "restoreSshHostKey" ]; };
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,21 +1,11 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, vars, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# virtualisation.docker.enable = true;
|
|
||||||
virtualisation.docker = {
|
virtualisation.docker = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.docker;
|
package = pkgs.docker;
|
||||||
# listenOptions = [
|
|
||||||
# "unix:///var/run/docker.sock"
|
|
||||||
# "tcp://0.0.0.0:2375"
|
|
||||||
#];
|
|
||||||
|
|
||||||
# daemon.settings = {
|
|
||||||
# metrics-addr = "0.0.0.0:9323";
|
|
||||||
# experimental = true;
|
|
||||||
# };
|
|
||||||
};
|
};
|
||||||
|
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
docker-compose
|
docker-compose
|
||||||
docker-buildx
|
docker-buildx
|
||||||
|
|||||||
+149
-36
@@ -1,70 +1,89 @@
|
|||||||
# Fully declarative FreeIPA domain membership.
|
# Fully declarative FreeIPA domain membership.
|
||||||
#
|
#
|
||||||
# Configures security.ipa (SSSD, Kerberos, PAM, NSSwitch) and places a
|
# Imported by modules/common/configuration.nix — no per-host wiring needed.
|
||||||
# pre-provisioned host keytab via sops-nix so no imperative ipa-client-install
|
# Enables itself automatically on any host that has a sops-encrypted keytab
|
||||||
# step is needed after deployment.
|
# at secrets/<hostname>.keytab; is a no-op for all other hosts.
|
||||||
#
|
#
|
||||||
# Usage (in a host.nix imports list):
|
# To enroll a new host:
|
||||||
# (import ../../modules/ipa/client.nix {
|
# 0. scripts/secrets/sync-host-keys.sh <flake-target>
|
||||||
# keytabSopsFile = ../../secrets/nix-cache.keytab;
|
# 1. scripts/ipa/create-nixos-ipa-host-account.sh [--ip <addr>] <hostname>
|
||||||
# caCertFile = ../../certs/ipa-ca.crt;
|
# (adds .sops.yaml rule, runs ipa host-add, encrypts keytab in one step)
|
||||||
# })
|
# 2. git add secrets/<hostname>.keytab .sops.yaml && git commit
|
||||||
|
# 3. Deploy — no further steps required.
|
||||||
#
|
#
|
||||||
# One-time operator setup per host (do this BEFORE deploying):
|
# Manual fallback (if the script isn't usable):
|
||||||
#
|
# a. On the FreeIPA server: ipa host-add <fqdn> [--ip-address=<ip>] --force
|
||||||
# 1. Fetch the IPA CA certificate (public — safe to commit):
|
# b. On the FreeIPA server: ipa-getkeytab -s <ipa-server> -p host/<fqdn> -k /tmp/<host>.keytab
|
||||||
# curl -o certs/ipa-ca.crt http://<ipa-server>/ipa/config/ca.crt
|
# c. From the repo root (path must match for sops creation rule to apply):
|
||||||
# Replace the placeholder at certs/ipa-ca.crt and commit it.
|
|
||||||
#
|
|
||||||
# 2. On the FreeIPA server, add the host and generate a keytab:
|
|
||||||
# ipa host-add <fqdn> --ip-address=<ip>
|
|
||||||
# ipa-getkeytab -s <ipa-server> -p host/<fqdn> -k /tmp/<host>.keytab
|
|
||||||
#
|
|
||||||
# 3. sops-encrypt the keytab as a binary secret from your admin machine
|
|
||||||
# (must run from repo root; sops matches creation rules against the file
|
|
||||||
# path, so copy to secrets/ first and encrypt in-place):
|
|
||||||
# cp /tmp/<host>.keytab secrets/<host>.keytab
|
# cp /tmp/<host>.keytab secrets/<host>.keytab
|
||||||
# sops -e --input-type binary -i secrets/<host>.keytab
|
# sops -e --input-type binary -i secrets/<host>.keytab
|
||||||
# Add secrets/<host>.keytab to .sops.yaml with the host's age key as a
|
# d. Commit secrets/<host>.keytab and the updated .sops.yaml, then deploy.
|
||||||
# recipient (see the nix-cache.keytab entry for the pattern), then run:
|
|
||||||
# scripts/secrets/sync-host-keys.sh <target> # if not done yet
|
|
||||||
# sops updatekeys secrets/<host>.keytab
|
|
||||||
# Commit the encrypted file.
|
|
||||||
#
|
#
|
||||||
# 4. nixos-rebuild switch (or create-proxmox-resource.sh) — no further
|
# vars dependencies: homeDomain, ipaServer, domainControllerIp, ipaUser
|
||||||
# manual enrollment steps required.
|
|
||||||
#
|
|
||||||
# vars dependencies: homeDomain, ipaServer
|
|
||||||
|
|
||||||
{ keytabSopsFile, caCertFile }:
|
|
||||||
{ config, lib, pkgs, vars, ... }:
|
{ config, lib, pkgs, vars, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
keytabPath = ../../secrets + "/${config.networking.hostName}.keytab";
|
||||||
|
enabled = builtins.pathExists keytabPath;
|
||||||
|
|
||||||
realm = lib.strings.toUpper vars.homeDomain;
|
realm = lib.strings.toUpper vars.homeDomain;
|
||||||
fqdn = "${config.networking.hostName}.${vars.homeDomain}";
|
fqdn = "${config.networking.hostName}.${vars.homeDomain}";
|
||||||
# "sweet.home" -> "dc=sweet,dc=home"
|
# "sweet.home" -> "dc=sweet,dc=home"
|
||||||
basedn = lib.strings.concatMapStringsSep "," (c: "dc=${c}") (lib.strings.splitString "." vars.homeDomain);
|
basedn = lib.strings.concatMapStringsSep "," (c: "dc=${c}") (lib.strings.splitString "." vars.homeDomain);
|
||||||
# security.ipa.certificate expects a derivation (package), not a raw path.
|
# security.ipa.certificate expects a derivation (package), not a raw path.
|
||||||
caCertPkg = pkgs.writeText "ipa-ca.crt" (builtins.readFile caCertFile);
|
caCertPkg = pkgs.writeText "ipa-ca.crt" (builtins.readFile ../../certs/ipa-ca.crt);
|
||||||
in
|
in
|
||||||
{
|
lib.mkIf enabled {
|
||||||
|
networking.domain = lib.mkDefault vars.homeDomain;
|
||||||
|
networking.nameservers = lib.mkDefault [ vars.domainControllerIp ];
|
||||||
|
|
||||||
security.ipa = {
|
security.ipa = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = vars.homeDomain;
|
domain = vars.homeDomain;
|
||||||
realm = realm;
|
inherit realm;
|
||||||
server = vars.ipaServer;
|
server = vars.ipaServer;
|
||||||
certificate = caCertPkg;
|
certificate = caCertPkg;
|
||||||
basedn = basedn;
|
inherit basedn;
|
||||||
ipaHostname = fqdn;
|
ipaHostname = fqdn;
|
||||||
offlinePasswords = true;
|
offlinePasswords = true;
|
||||||
cacheCredentials = true;
|
cacheCredentials = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Fetch SSH public keys from IPA so users can log in with the key stored
|
||||||
|
# in their IPA profile rather than needing ~/.ssh/authorized_keys on every
|
||||||
|
# host. sss_ssh_authorizedkeys queries SSSD (which queries IPA LDAP).
|
||||||
|
#
|
||||||
|
# /nix/store is 1775 (group-writable by nixbld). OpenSSH 10.0+ rejects
|
||||||
|
# AuthorizedKeysCommand binaries whose path contains any group-writable
|
||||||
|
# component, silently skipping the command. Copy to /usr/local/bin (all
|
||||||
|
# components root-owned, 755) so the path passes sshd's safety check.
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /usr/local 0755 root root - -"
|
||||||
|
"d /usr/local/bin 0755 root root - -"
|
||||||
|
"C+ /usr/local/bin/sss_ssh_authorizedkeys 0555 root root - ${pkgs.sssd}/bin/sss_ssh_authorizedkeys"
|
||||||
|
# Pre-create the IPA user's home dir so Home Manager activation succeeds
|
||||||
|
# even before their first login. On a fresh system SSSD may not have
|
||||||
|
# resolved the user yet — tmpfiles warns and skips in that case (non-fatal),
|
||||||
|
# and pam_mkhomedir covers the first-login path as a fallback.
|
||||||
|
"d /home/${vars.ipaUser} 0700 ${vars.ipaUser} ${vars.ipaUser} - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
services.openssh.extraConfig = ''
|
||||||
|
AuthorizedKeysCommand /usr/local/bin/sss_ssh_authorizedkeys %u
|
||||||
|
AuthorizedKeysCommandUser nobody
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Create the home directory on first login if it doesn't exist yet.
|
||||||
|
# IPA users have no pre-created home on the host; without this sshd
|
||||||
|
# opens a session to a non-existent directory and resets the connection.
|
||||||
|
security.pam.services.sshd.makeHomeDir = true;
|
||||||
|
|
||||||
# Host keytab: pre-provisioned on the IPA server, sops-encrypted binary.
|
# Host keytab: pre-provisioned on the IPA server, sops-encrypted binary.
|
||||||
# Placed at /etc/krb5.keytab before SSSD starts so the host authenticates
|
# Placed at /etc/krb5.keytab before SSSD starts so the host authenticates
|
||||||
# to IPA without running ipa-client-install.
|
# to IPA without running ipa-client-install.
|
||||||
sops.secrets."ipa-host-keytab" = {
|
sops.secrets."ipa-host-keytab" = {
|
||||||
sopsFile = keytabSopsFile;
|
sopsFile = keytabPath;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
path = "/etc/krb5.keytab";
|
path = "/etc/krb5.keytab";
|
||||||
owner = "root";
|
owner = "root";
|
||||||
@@ -72,4 +91,98 @@ in
|
|||||||
mode = "0600";
|
mode = "0600";
|
||||||
restartUnits = [ "sssd.service" ];
|
restartUnits = [ "sssd.service" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# security.ipa enables Kerberos (security.krb5) which causes systemd to
|
||||||
|
# start auth-rpcgss-module.service and rpc-gssd.service for Kerberos NFS
|
||||||
|
# authentication. LXC containers can't load the auth_rpcgss kernel module
|
||||||
|
# and don't have /var/lib/nfs/rpc_pipefs, so both services fail.
|
||||||
|
#
|
||||||
|
# The NixOS IPA module already adds a drop-in for auth-rpcgss-module.service
|
||||||
|
# with ConditionPathExists=/etc/krb5.keytab. We use lib.mkForce to win the
|
||||||
|
# text conflict and add ConditionVirtualization=!container alongside it so
|
||||||
|
# the service is skipped (not failed) in containers that do have a keytab.
|
||||||
|
# Same fix for rpc-gssd.service which also fails in containers.
|
||||||
|
systemd.units = lib.mkIf config.boot.isContainer {
|
||||||
|
"auth-rpcgss-module.service" = {
|
||||||
|
overrideStrategy = "asDropinIfExists";
|
||||||
|
text = lib.mkForce ''
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=
|
||||||
|
ConditionPathExists=/etc/krb5.keytab
|
||||||
|
ConditionVirtualization=!container
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# rpc-gssd also has ConditionPathExists from the NixOS IPA module (and an
|
||||||
|
# X-Restart-Triggers store path from systemd.nix). Use mkForce to win;
|
||||||
|
# omit X-Restart-Triggers since this service is skipped in containers anyway.
|
||||||
|
"rpc-gssd.service" = {
|
||||||
|
overrideStrategy = "asDropinIfExists";
|
||||||
|
text = lib.mkForce ''
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=
|
||||||
|
ConditionPathExists=/etc/krb5.keytab
|
||||||
|
ConditionVirtualization=!container
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Home Manager config for the IPA primary user, applied on every enrolled
|
||||||
|
# host. Manages what IPA doesn't: dotfiles, user-scoped packages, session
|
||||||
|
# variables. Switch-nix/Test-nix/buildImage are system-wide (configuration.nix)
|
||||||
|
# so they don't need to be repeated here.
|
||||||
|
# NixOS requires isNormalUser/isSystemUser + group on any entry in
|
||||||
|
# users.users. HM with useUserPackages = true (set in flake.nix) adds a stub
|
||||||
|
# entry for each HM user so it can install packages to
|
||||||
|
# /etc/profiles/per-user/<name>/. This definition satisfies those assertions.
|
||||||
|
# With security.ipa setting "passwd: sss files" in nsswitch, SSSD's IPA entry
|
||||||
|
# takes priority for NSS lookups — this local stub is only a fallback when
|
||||||
|
# SSSD is unreachable (at which point auth fails anyway).
|
||||||
|
# HM with useUserPackages = true (flake.nix) sets users.users.${ipaUser}.packages,
|
||||||
|
# which forces the stub into /etc/passwd. pam_sss.so with the "localusers" flag
|
||||||
|
# (added by NixOS when SSSD is enabled) then skips SSSD for any user it finds in
|
||||||
|
# local /etc/passwd — including this stub — falling through to pam_unix, which has
|
||||||
|
# no password for the stub → sudo auth always fails.
|
||||||
|
#
|
||||||
|
# Fix: NOPASSWD for the IPA user. The IPA user already authenticated to reach a
|
||||||
|
# shell (SSH public key from IPA or Kerberos), so re-prompting via a broken PAM
|
||||||
|
# path is security theater on a single-admin homelab.
|
||||||
|
users.users.${vars.ipaUser} = {
|
||||||
|
isNormalUser = true;
|
||||||
|
group = "users";
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
createHome = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# home-manager-<user>.service fails on first enrollment because /home/wayne
|
||||||
|
# doesn't exist until the user's first login (pam_mkhomedir creates it then).
|
||||||
|
# ConditionPathExists makes systemd skip the service (exit 0, condition not
|
||||||
|
# met) instead of failing. After first login the dir exists and subsequent
|
||||||
|
# rebuilds activate HM normally.
|
||||||
|
systemd.services."home-manager-${vars.ipaUser}".unitConfig.ConditionPathExists =
|
||||||
|
"/home/${vars.ipaUser}";
|
||||||
|
|
||||||
|
security.sudo.extraRules = [{
|
||||||
|
users = [ vars.ipaUser ];
|
||||||
|
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
|
||||||
|
}];
|
||||||
|
|
||||||
|
# Home Manager config for the IPA primary user, applied on every enrolled
|
||||||
|
# host. Manages what IPA doesn't: dotfiles, user-scoped packages, session
|
||||||
|
# variables. Switch-nix/Test-nix/buildImage are system-wide (configuration.nix)
|
||||||
|
# so they don't need to be repeated here.
|
||||||
|
#
|
||||||
|
# homeDirectory uses mkForce because HM's NixOS integration module sets it to
|
||||||
|
# "/var/empty" for users not found in config.users.users at eval time (SSSD
|
||||||
|
# users aren't visible there).
|
||||||
|
home-manager.users.${vars.ipaUser} = { pkgs, ... }: {
|
||||||
|
home = {
|
||||||
|
username = vars.ipaUser;
|
||||||
|
homeDirectory = lib.mkForce "/home/${vars.ipaUser}";
|
||||||
|
stateVersion = "26.05";
|
||||||
|
packages = with pkgs; [ tmux sshfs ];
|
||||||
|
sessionVariables.EDITOR = "nano";
|
||||||
|
};
|
||||||
|
programs.home-manager.enable = true;
|
||||||
|
programs.bash.enable = true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ in
|
|||||||
# LXC container does).
|
# LXC container does).
|
||||||
imports = [
|
imports = [
|
||||||
(modulesPath + "/virtualisation/proxmox-lxc.nix")
|
(modulesPath + "/virtualisation/proxmox-lxc.nix")
|
||||||
|
../common/preserve-ssh-host-key.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
proxmoxLXC = {
|
proxmoxLXC = {
|
||||||
@@ -105,49 +106,6 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# NixOS's etc activation removes any /etc file that was in the previous
|
|
||||||
# generation's environment.etc but is absent from the current one — even
|
|
||||||
# real (non-symlink) copies. On every routine nixos-rebuild switch/test that
|
|
||||||
# lacks NIXOS_HOST_KEYS_DIR the key is absent from environment.etc, so it
|
|
||||||
# gets removed as "obsolete". sops-nix derives its age decryption key from
|
|
||||||
# /etc/ssh/ssh_host_ed25519_key; deletion cascades into every sops secret
|
|
||||||
# failing with "Error getting data key: 0 successful groups required, got 0".
|
|
||||||
#
|
|
||||||
# Fix: activation scripts that bracket the etc step, with explicit deps
|
|
||||||
# to enforce the correct ordering. Without deps the topological sort places
|
|
||||||
# preserveSshHostKey AFTER etc (confirmed live on a deployed lxc-tor-relay:
|
|
||||||
# position 7 vs etc's position 5) -- the key is already gone by the time it
|
|
||||||
# tries to save it. The etc/setupSecrets entries ADD to existing deps
|
|
||||||
# (types.listOf concatenates across module definitions).
|
|
||||||
system.activationScripts = {
|
|
||||||
# Saves the live key to /run before etc can delete it.
|
|
||||||
preserveSshHostKey = ''
|
|
||||||
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Reinstalls the key after etc runs if it was removed as "obsolete".
|
|
||||||
# The resulting file is not registered in environment.etc for either
|
|
||||||
# generation, so subsequent rebuilds leave it alone permanently.
|
|
||||||
restoreSshHostKey = {
|
|
||||||
deps = [ "etc" ];
|
|
||||||
text = ''
|
|
||||||
if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then
|
|
||||||
install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
fi
|
|
||||||
rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Force etc to wait until the key is saved, and sops to wait until the
|
|
||||||
# key is restored. Without these the topological sort breaks the chain.
|
|
||||||
etc = { deps = [ "preserveSshHostKey" ]; };
|
|
||||||
setupSecrets = { deps = [ "restoreSshHostKey" ]; };
|
|
||||||
};
|
|
||||||
|
|
||||||
# virtualisation/proxmox-lxc.nix (imported above) registers the Nix
|
# virtualisation/proxmox-lxc.nix (imported above) registers the Nix
|
||||||
# store DB via a systemd service (register-nix-paths) -- it never runs
|
# store DB via a systemd service (register-nix-paths) -- it never runs
|
||||||
# an activation script at all. Confirmed live this means neither
|
# an activation script at all. Confirmed live this means neither
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ in
|
|||||||
../hardware-configuration/vm/proxmox.nix
|
../hardware-configuration/vm/proxmox.nix
|
||||||
../boot/efi.nix
|
../boot/efi.nix
|
||||||
../disko/proxmox.nix
|
../disko/proxmox.nix
|
||||||
|
../common/preserve-ssh-host-key.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.etc = lib.mkIf hasKeyForThisTarget {
|
environment.etc = lib.mkIf hasKeyForThisTarget {
|
||||||
@@ -46,36 +47,4 @@ in
|
|||||||
mode = "0644";
|
mode = "0644";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# NixOS's etc activation removes any /etc file that was in the previous
|
|
||||||
# generation's environment.etc but is absent from the current one. Since
|
|
||||||
# the SSH key is only in environment.etc during the --impure build (when
|
|
||||||
# NIXOS_HOST_KEYS_DIR is set), normal rebuilds would remove it as
|
|
||||||
# "obsolete". These scripts mirror lxc.nix's approach: save the live key
|
|
||||||
# before etc runs, restore it after. Without the explicit deps, the
|
|
||||||
# topological sort places preserveSshHostKey after etc (confirmed live on
|
|
||||||
# lxc-tor-relay: position 7 vs etc's position 5), so the key is gone
|
|
||||||
# before it can be saved.
|
|
||||||
system.activationScripts = {
|
|
||||||
preserveSshHostKey = ''
|
|
||||||
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
restoreSshHostKey = {
|
|
||||||
deps = [ "etc" ];
|
|
||||||
text = ''
|
|
||||||
if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then
|
|
||||||
install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
fi
|
|
||||||
rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
etc = { deps = [ "preserveSshHostKey" ]; };
|
|
||||||
setupSecrets = { deps = [ "restoreSshHostKey" ]; };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# --ip <addr> Register this IP with the IPA host record (optional).
|
# --ip <addr> Register this IP with the IPA host record (optional).
|
||||||
# --dc <host> SSH to this host for ipa-getkeytab.
|
# --dc <host> SSH to this host to run IPA commands.
|
||||||
# Default: $IPA_SERVER (from env.sh / environment).
|
# Default: $IPA_SERVER (from env.sh / environment).
|
||||||
# --dc-user <u> SSH user on the domain controller. Default: wayne.
|
# --dc-user <u> SSH user on the domain controller. Default: wayne.
|
||||||
# --dry-run Print what would be done without making any changes.
|
# --dry-run Print what would be done without making any changes.
|
||||||
@@ -35,7 +35,9 @@
|
|||||||
# scripts/secrets/sync-host-keys.sh <flake-target> first so the host
|
# scripts/secrets/sync-host-keys.sh <flake-target> first so the host
|
||||||
# can decrypt its own keytab on boot. This script adds the .sops.yaml
|
# can decrypt its own keytab on boot. This script adds the .sops.yaml
|
||||||
# creation rule for secrets/<hostname>.keytab automatically, but the
|
# creation rule for secrets/<hostname>.keytab automatically, but the
|
||||||
# host age key anchor (&lxc-<hostname> etc.) must already exist.
|
# host age key anchor (&lxc-<hostname> etc.) must already exist —
|
||||||
|
# otherwise only the admin key can decrypt the keytab and the deployed
|
||||||
|
# host will fail to read it.
|
||||||
# 4. sops in PATH, or Nix available to run it via `nix run`.
|
# 4. sops in PATH, or Nix available to run it via `nix run`.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -52,7 +54,7 @@ DC_HOST="${IPA_SERVER}"
|
|||||||
DC_USER="wayne"
|
DC_USER="wayne"
|
||||||
IP_ADDR=""
|
IP_ADDR=""
|
||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
HOSTNAME=""
|
TARGET=""
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
sed -n '/^# Usage:/,/^[^#]/{ /^#/{ s/^# \?//; p } }' "$0"
|
sed -n '/^# Usage:/,/^[^#]/{ /^#/{ s/^# \?//; p } }' "$0"
|
||||||
@@ -68,22 +70,29 @@ while [[ $# -gt 0 ]]; do
|
|||||||
-h|--help) usage 0 ;;
|
-h|--help) usage 0 ;;
|
||||||
-*) echo "Unknown flag: $1" >&2; usage 1 ;;
|
-*) echo "Unknown flag: $1" >&2; usage 1 ;;
|
||||||
*)
|
*)
|
||||||
if [[ -n "${HOSTNAME}" ]]; then echo "Unexpected argument: $1" >&2; usage 1; fi
|
if [[ -n "${TARGET}" ]]; then echo "Unexpected argument: $1" >&2; usage 1; fi
|
||||||
HOSTNAME="$1"; shift
|
TARGET="$1"; shift
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "${HOSTNAME}" ]]; then
|
if [[ -z "${TARGET}" ]]; then
|
||||||
echo "Error: hostname required." >&2
|
echo "Error: hostname required." >&2
|
||||||
usage 1
|
usage 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FQDN="${HOSTNAME}.${HOME_DOMAIN}"
|
# Reject FQDNs passed by mistake — the script appends HOME_DOMAIN itself.
|
||||||
REALM="${HOME_DOMAIN^^}" # uppercase: SWEET.HOME
|
# "nixos.sweet.home" → FQDN would become "nixos.sweet.home.sweet.home".
|
||||||
KEYTAB_SECRET="${REPO_ROOT}/secrets/${HOSTNAME}.keytab"
|
if [[ "${TARGET}" == *"."* ]]; then
|
||||||
|
echo "Error: <hostname> must be the short name (e.g. 'nixos'), not a FQDN." >&2
|
||||||
|
echo " The FQDN is derived automatically as ${TARGET}.${HOME_DOMAIN}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FQDN="${TARGET}.${HOME_DOMAIN}"
|
||||||
|
KEYTAB_SECRET="${REPO_ROOT}/secrets/${TARGET}.keytab"
|
||||||
# Temp path on the domain controller — use a name that won't collide.
|
# Temp path on the domain controller — use a name that won't collide.
|
||||||
DC_TMP="/tmp/nixos-keytab-${HOSTNAME}-$$.keytab"
|
DC_TMP="/tmp/nixos-keytab-${TARGET}-$$.keytab"
|
||||||
|
|
||||||
# --- Helpers ---
|
# --- Helpers ---
|
||||||
|
|
||||||
@@ -112,8 +121,8 @@ dc_run() {
|
|||||||
if command -v sops &>/dev/null; then
|
if command -v sops &>/dev/null; then
|
||||||
SOPS_CMD=(sops)
|
SOPS_CMD=(sops)
|
||||||
else
|
else
|
||||||
log "sops not in PATH — will use 'nix run github:NixOS/nixpkgs/nixos-25.11#sops'"
|
log "sops not in PATH — will use 'nix run nixpkgs#sops'"
|
||||||
SOPS_CMD=(nix run "github:NixOS/nixpkgs/nixos-25.11#sops" --)
|
SOPS_CMD=(nix run "nixpkgs#sops" --)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Preflight checks ---
|
# --- Preflight checks ---
|
||||||
@@ -131,13 +140,13 @@ cd "${REPO_ROOT}"
|
|||||||
# also exist at that point or sops will refuse with "no matching creation
|
# also exist at that point or sops will refuse with "no matching creation
|
||||||
# rules found."
|
# rules found."
|
||||||
|
|
||||||
log "Checking .sops.yaml for creation rule: secrets/${HOSTNAME}.keytab"
|
log "Checking .sops.yaml for creation rule: secrets/${TARGET}.keytab"
|
||||||
|
|
||||||
RULE_EXISTS=false
|
RULE_EXISTS=false
|
||||||
# Match "path_regex: secrets/<hostname>...keytab" — using .*keytab rather
|
# Match "path_regex: secrets/<hostname>...keytab" — using .*keytab rather
|
||||||
# than \.keytab because the file stores the regex verbatim (\.keytab = two
|
# than \.keytab because the file stores the regex verbatim (\.keytab = two
|
||||||
# chars: backslash + dot), which a BRE \. (= escaped literal dot) won't span.
|
# chars: backslash + dot), which a BRE \. (= escaped literal dot) won't span.
|
||||||
if grep -q "path_regex: secrets/${HOSTNAME}.*keytab" .sops.yaml 2>/dev/null; then
|
if grep -q "path_regex: secrets/${TARGET}.*keytab" .sops.yaml 2>/dev/null; then
|
||||||
RULE_EXISTS=true
|
RULE_EXISTS=true
|
||||||
logn "Rule already exists — skipping addition."
|
logn "Rule already exists — skipping addition."
|
||||||
fi
|
fi
|
||||||
@@ -149,12 +158,20 @@ if ! $RULE_EXISTS; then
|
|||||||
# that have been registered get added as recipients.
|
# that have been registered get added as recipients.
|
||||||
RECIPIENTS=("*admin")
|
RECIPIENTS=("*admin")
|
||||||
for platform in lxc proxmox linode; do
|
for platform in lxc proxmox linode; do
|
||||||
anchor="${platform}-${HOSTNAME}"
|
anchor="${platform}-${TARGET}"
|
||||||
if grep -q "^ - &${anchor} " .sops.yaml; then
|
if grep -q "^ - &${anchor} " .sops.yaml; then
|
||||||
RECIPIENTS+=("*${anchor}")
|
RECIPIENTS+=("*${anchor}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [[ ${#RECIPIENTS[@]} -eq 1 ]]; then
|
||||||
|
echo "Warning: no platform age keys found for '${TARGET}' in .sops.yaml." >&2
|
||||||
|
echo " Run scripts/secrets/sync-host-keys.sh <flake-target> first," >&2
|
||||||
|
echo " otherwise only the admin key can decrypt the keytab and the" >&2
|
||||||
|
echo " deployed host won't be able to read it at boot." >&2
|
||||||
|
echo " Continuing with admin-only encryption..." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
# Build the indented recipient list for the YAML block.
|
# Build the indented recipient list for the YAML block.
|
||||||
RECIPIENT_YAML=""
|
RECIPIENT_YAML=""
|
||||||
for r in "${RECIPIENTS[@]}"; do
|
for r in "${RECIPIENTS[@]}"; do
|
||||||
@@ -163,9 +180,9 @@ if ! $RULE_EXISTS; then
|
|||||||
RECIPIENT_YAML="${RECIPIENT_YAML%$'\n'}" # strip trailing newline
|
RECIPIENT_YAML="${RECIPIENT_YAML%$'\n'}" # strip trailing newline
|
||||||
|
|
||||||
NEW_RULE="
|
NEW_RULE="
|
||||||
# Host keytab for ${HOSTNAME} FreeIPA enrollment (binary sops file).
|
# Host keytab for ${TARGET} FreeIPA enrollment (binary sops file).
|
||||||
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
||||||
- path_regex: secrets/${HOSTNAME}\\.keytab\$
|
- path_regex: secrets/${TARGET}\\.keytab\$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
${RECIPIENT_YAML}"
|
${RECIPIENT_YAML}"
|
||||||
@@ -206,23 +223,37 @@ IP_FLAG=""
|
|||||||
[[ -n "${IP_ADDR}" ]] && IP_FLAG="--ip-address=${IP_ADDR}"
|
[[ -n "${IP_ADDR}" ]] && IP_FLAG="--ip-address=${IP_ADDR}"
|
||||||
|
|
||||||
# --force: create the host record even if DNS doesn't resolve it yet.
|
# --force: create the host record even if DNS doesn't resolve it yet.
|
||||||
# Pipe through grep to suppress the "already exists" warning without
|
if $DRY_RUN; then
|
||||||
# hiding real errors (ipa exits 1 for real errors, 0 for already-exists).
|
echo "[dry-run] ssh ${DC_USER}@${DC_HOST} sudo ipa host-add '${FQDN}' ${IP_FLAG} --force"
|
||||||
HOST_ADD_CMD="sudo ipa host-add '${FQDN}' ${IP_FLAG} --force 2>&1 | \
|
else
|
||||||
tee /dev/stderr | grep -q 'already exists' && echo '(host already registered)' || true"
|
HOST_ADD_OUT=$(ssh "${DC_USER}@${DC_HOST}" "sudo ipa host-add '${FQDN}' ${IP_FLAG} --force 2>&1") \
|
||||||
dc_run "bash -c \"${HOST_ADD_CMD}\""
|
&& HOST_ADD_RC=0 || HOST_ADD_RC=$?
|
||||||
|
if [[ $HOST_ADD_RC -eq 0 ]]; then
|
||||||
|
echo "${HOST_ADD_OUT}"
|
||||||
|
elif echo "${HOST_ADD_OUT}" | grep -q "already exists"; then
|
||||||
|
logn "(host already registered)"
|
||||||
|
else
|
||||||
|
echo "Error: ipa host-add failed (exit ${HOST_ADD_RC}):" >&2
|
||||||
|
echo "${HOST_ADD_OUT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Step 3: Fetch the keytab from the domain controller ---
|
# --- Step 3: Fetch the keytab from the domain controller ---
|
||||||
|
|
||||||
log "Fetching keytab for host/${FQDN}"
|
log "Fetching keytab for host/${FQDN}"
|
||||||
|
|
||||||
dc_run "sudo ipa-getkeytab -s '${DC_HOST}' -p 'host/${FQDN}' -k '${DC_TMP}'"
|
# Remove the plaintext keytab if the script aborts before encryption completes.
|
||||||
|
# The trap is cleared at the end of step 4 once sops has encrypted it in-place.
|
||||||
|
trap 'rm -f "${KEYTAB_SECRET}"' EXIT
|
||||||
|
|
||||||
|
dc_run "sudo ipa-getkeytab -s '${IPA_SERVER}' -p 'host/${FQDN}' -k '${DC_TMP}'"
|
||||||
|
|
||||||
if $DRY_RUN; then
|
if $DRY_RUN; then
|
||||||
echo "[dry-run] Would stream ${DC_USER}@${DC_HOST}:${DC_TMP} → secrets/${HOSTNAME}.keytab"
|
echo "[dry-run] Would stream ${DC_USER}@${DC_HOST}:${DC_TMP} → secrets/${TARGET}.keytab"
|
||||||
else
|
else
|
||||||
logn "Streaming keytab from ${DC_HOST}:${DC_TMP} → secrets/${HOSTNAME}.keytab"
|
logn "Streaming keytab from ${DC_HOST}:${DC_TMP} → secrets/${TARGET}.keytab"
|
||||||
# scp can't read a root-owned temp file as wayne; pipe through sudo cat instead.
|
# scp can't read a root-owned temp file as ${DC_USER}; pipe through sudo cat instead.
|
||||||
ssh "${DC_USER}@${DC_HOST}" "sudo cat '${DC_TMP}'" > "${KEYTAB_SECRET}"
|
ssh "${DC_USER}@${DC_HOST}" "sudo cat '${DC_TMP}'" > "${KEYTAB_SECRET}"
|
||||||
|
|
||||||
logn "Removing temp file on ${DC_HOST}"
|
logn "Removing temp file on ${DC_HOST}"
|
||||||
@@ -235,24 +266,38 @@ fi
|
|||||||
# sops matches the creation rule by path. Using -i (in-place) rather than
|
# sops matches the creation rule by path. Using -i (in-place) rather than
|
||||||
# stdout redirect keeps the path intact through the encrypt call.
|
# stdout redirect keeps the path intact through the encrypt call.
|
||||||
|
|
||||||
log "Encrypting secrets/${HOSTNAME}.keytab in-place with sops"
|
log "Encrypting secrets/${TARGET}.keytab in-place with sops"
|
||||||
run "${SOPS_CMD[@]}" -e --input-type binary -i "${KEYTAB_SECRET}"
|
run "${SOPS_CMD[@]}" -e --input-type binary -i "${KEYTAB_SECRET}"
|
||||||
|
|
||||||
|
# Encryption succeeded — the file is now sops-encrypted; cancel the cleanup trap.
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
# --- Done ---
|
# --- Done ---
|
||||||
|
|
||||||
if ! $DRY_RUN; then
|
if ! $DRY_RUN; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Done. secrets/${HOSTNAME}.keytab is sops-encrypted and ready."
|
echo "Done. secrets/${TARGET}.keytab is sops-encrypted and ready."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
echo " 1. Verify: grep '\"data\": \"ENC' secrets/${HOSTNAME}.keytab"
|
echo " 1. Verify: grep '\"data\": \"ENC' secrets/${TARGET}.keytab"
|
||||||
echo " 2. Stage and commit:"
|
echo " 2. Stage and commit:"
|
||||||
echo " git add secrets/${HOSTNAME}.keytab .sops.yaml"
|
echo " git add secrets/${TARGET}.keytab .sops.yaml"
|
||||||
echo " git commit -m 'secrets: add IPA keytab for ${HOSTNAME}'"
|
echo " git commit -m 'secrets: add IPA keytab for ${TARGET}'"
|
||||||
echo " 3. Add the module to hosts/${HOSTNAME}/host.nix:"
|
echo " 3. Add to hosts/${TARGET}/host.nix (networking block and imports):"
|
||||||
|
echo ""
|
||||||
|
echo " networking = {"
|
||||||
|
echo " hostName = \"${TARGET}\";"
|
||||||
|
echo " domain = vars.homeDomain; # required for Kerberos FQDN"
|
||||||
|
echo " nameservers = [ vars.domainControllerIp ]; # IPA DNS"
|
||||||
|
echo " ..."
|
||||||
|
echo " };"
|
||||||
|
echo ""
|
||||||
|
echo " imports = ["
|
||||||
echo " (import ../../modules/ipa/client.nix {"
|
echo " (import ../../modules/ipa/client.nix {"
|
||||||
echo " keytabSopsFile = ../../secrets/${HOSTNAME}.keytab;"
|
echo " keytabSopsFile = ../../secrets/${TARGET}.keytab;"
|
||||||
echo " caCertFile = ../../certs/ipa-ca.crt;"
|
echo " caCertFile = ../../certs/ipa-ca.crt;"
|
||||||
echo " })"
|
echo " })"
|
||||||
|
echo " ];"
|
||||||
|
echo ""
|
||||||
echo " 4. Deploy: nixos-rebuild switch (or create-proxmox-resource.sh)"
|
echo " 4. Deploy: nixos-rebuild switch (or create-proxmox-resource.sh)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data://9IEHIVCfHjyMNao5sCu2zNlZ/CaW+JyxVpGpD/vab2qvURunCUY7eMfSOyvOx/2WPXnWWlkoVJPCR1ec/yUg09EaSMfxrvqlu4UJI3Sxvu9xNuDszMwMMD/sCulJDiMWFNLp8qaYt7UGzexp4+GlGiqWDxk3ZEu/iLmSApzBrpciTF0lfehT4qblDovo9QXG2KDWFhCt2SwEKmHJ61Yl3pVAQnPLyTWaNhwWD/mYL76mIiDVKq7DJlvi9MBxzi3aYh/ttuHgRCvnCL0C9oUvOyT2cljwra0LXuOrum7FhPIXXboA7WTEbTHJm1LB5yLfxDrnWCrGxQzFQAwNixVHIcjuvjjvA/LifTvbj5FYM8x7an+DwXPfPhruFrDew1paAWsEGNNYXSmAlju2QLI/OwLHFFLuDyLnBKQ6RLtZbCEAOUKf2h4iZj7nH86eb/aGU=,iv:rj76+MJBCpiYyx2Ogut5UxJj3Gn+bygvu2mtuY5hFLA=,tag:yQmTvWVu8ZLug/7fo6RnwA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqcURBS2tIMGl1aWxLMHFQ\nS3ZGVTE0cnYzTmpnQ0daL1F3QnlpemRCMmtjClJiZzIrc2syN29sRXVoaXlPRUNF\nakw4RndmbEduTFg5ZVRKUTVwRWpGRjgKLS0tIHZ5ZFlyODlGWTJoNGpXR3RhY0ZH\nOGpPdDZQVmt6UWZXbHkxQTBoeW1JencKbsfH1V1lUj8mmHyLNj36VaRDgaBojcDU\ndoQWmSEXxjticJqdadbVKb3UABpvzAZxASCy81sa3wH0gT+7zLaNyQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrbFdaMmRjcHgyV3Y0eWZL\neG1uMEtpQVUxQmNTU0Z3aC95Zi9uQlgvMEhRCmJiVlRTR2NocGlnbVU1UmlLVVA2\neERiMXpiQlVPNVhRU09XVGxrY0Ixa2sKLS0tIFRuOHdoelJxOXNRRHBJNnlhSE9j\nUUZHZmQzOVFwRmhFV3pvdnpRMTMraGcKRHBuSUpbHaEzH2tuSBE5MsLJDCuH3vUx\nO0jnDldCWkCw7Wvr/tQAkaDI8axZcYVDUkCEk+xqAdxDozLCPhEO6g==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age17e89ty6p0fw24daanen57wg8uald9s025t3wwxsw269svwpmgvrshfvfvt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZMW5taXh2QkJkT0JjaVpq\nQlBjVFpFYUhXTHhOT3prbFcvUFB0YnRGUzMwCjhHT01lY3dPWDE2dDZWZnJza1hN\nS1AvZWIvdjZoYmRjWlliK1hiOEdrT00KLS0tIDljWWY0NlQveS9VR2hOSUNyTUtH\nK2gvbEVibmZSdktPemdEQ2p5U0Y4d2MKKitoTi2vbxJ41IoWMlj4vO91Ahpj0hHP\nrKCPyx7ws/IUMmKGyvDLpZ3pYqHD9jl5pLfB05Hh4Emhv4lA1qtwwQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age17jqc66x9yeshfgd9v78mj483r4zzarqdtuxtrkxe4x5mw679gphshd94th"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3K2dVV1BHbDk2Z3FhNmdV\nZEN2Um94K0pma1k4b1V5ZUJxQ1hWU1grZjJNCi9iRVVqOVpEMmtBUi80NThlYldY\nTUtZOVErT2VSWk43NndpVzBlL1lQaTgKLS0tIEpCM3Q4NjM3N1lpUE56N04rTXN0\nZkZ6TkV1TU9OV3dpc25RNWRpVnprM00KItUzKBdShakOfX8Sr+k906nsvYPl8QLb\nge//1GA+ukGsaS9rcChOY89vFdm61JDmj1jXSJ0CN4wLMW9/eblZRw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1arhf2q45zw6wf2uevju4savp575x3m2tfvved5zzq3ay92ynua9s3cm92c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:28Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:efFXIqbauOURR7lrVpK7kqRIPgYjgWfenBYoX7mUrQ/thFc+ApcAx58Fi1zg4biwIs7NarJAgDqAxZi+yKb2Sll8H/wlsEjWDU4iQlLJdIQw7wey9eDW8pgBLd+6E7FXrgn1Vh49dS5GXlC6clAYk1lCiB4NvfzPDy5Ktpl+Chs=,iv:+zCqj5I1MLJfRRiIrqgocYB49PZnlV45PUTH4gYlfrQ=,tag:WxY1sF4kFOTEzbSFcfJFbQ==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:apiijrtrqd77CTizITg0R35BfCi8PBnufpxIyC+hLYqwoBzP//3z/yjFyHPLG98m/c/qywoi3Kn+zsaTT7MjP++9OMhhX94YKlSHV1/cHB76OkwsNc+ClqWxl6vpaFX29Qvh3gFX9c/NR3xvYQutYwrIrQ9NR+t/M52IMC8hvtR1LQy0ak3VIuXJlSnG2r4kF2Ym1iP7phjuq39Gd245Axzw8OB7yGvOjNxSdTPxW/qL0fMlzNcMrjr9hw15WlqnZfWPOsB1+gZjHXpGfPD5BCbAAMoTRJd75vhKKXP/ERhIffewuuH2x/QHfSFvXVB3QyhBQMxd2b8QEEE5cjvcExOST3tkj6QARkzoUpRT7AE3jhl3XZ0uA2qu9SwyrSvbr0tBRKxCdK0g2E2/hqwcK/Tck5GB1eKb4aN+UkqxOblNDH+B1RfDoyNAuN+KEg==,iv:0p+ScrKpP4kQvO52gBAlwAis6oAzZ0EHFnU74hYPrn4=,tag:ON7qOjztF52xsJWAou7ogg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlYXB3cVAzb2xEZ2pGa1RJ\nbS9ZVTc4Ums5eUZJUjEvd1g1aGVyNUNramowCnptZXFOZVB3MFRFcUtzSXBEZk1B\neEtKcDdLS0h0b1h3VjRjRXRvV3V5V3MKLS0tIHBDemkyUnV6ZXhTeE5VOVVOMlky\nWWMzVGVzZlAxMjZYUGpQUCs5QmxiYkkKcuBshCgWX4TwfVlQ5lHikzvwWdLEXWD1\n/uSiy0J6yMSiu8u6cg2SxeFrlKJ3j47dDlT6WHCxS0PfeEA0bJb3LA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGa2VTc2NWdkFRNUJxelVR\nWFk4RWxoelYzNHo1UFVhU2ZkLzEySlRWN2xNCmNmcmJod2crL3NMRlVsSmpmVkU2\nMjlXMktjc3piUVNhUXlTdnVGTWJkUTQKLS0tIGQrMUxrNDlNTkRCSUtFWkxRdXgw\nRlV4ZmtYSGhPQU84eWtiQXVqTmxUK3cKk5fn72UZPH68t5ZappfAhZJwzpLkfKmT\ny9TbUPIr4Pbrexau6YiH43QIbDQFdwYPfkBjGkd57zCg8AVo1+MBRw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1adur9g330gua4l6ndk8cqjg35qc8yxwgme6wrl2hpylcc7vxm38q05ejuy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArNWkwQXFWT1RpaDNvbzYy\nQWc1aHhmNHFEUXVsQjZqb0EzM0wrV0dwN1hnCjFsUFJiT3REK05uSGRWTEw2SFE4\nY1FleE1XVjhBbndiMmZxTWNTYmhYeVEKLS0tIEJiZzJvS3BsYzB3cHIxa2k5N1Ro\nenFFZDVaODNnVGdBZTBOYWJwRjQzc1kKlXJgee8wTSN4Beq4P0t9cYbk0BWHCseQ\nyaWpiPT9aZBEGLFmuEd3zKABc8lrilX/ySTmOG49vRg6CPmr7cT0Wg==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1rrxqea6q6pn39sw8y5te63h2py8jgjl9v0jyper86w3ggtn67upqg3ah39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEa0JYenFMNmNzWnVmcXdz\ndG90ZUZ0WWlIU0FCZG9OaWpBM3ZDWnFhZFhNCjhuV1FTOTJ2WVJGa2RuNVV2MjR0\naVNXa3diaWxWUlJtdkNOQXZ2R2NsQkUKLS0tIDcyYXh3N3B2QmNiK3dzemFFMGV1\nNTZpTk5yNGV5YVo3cGswK0NLWFQxQlEKIe0N5OxooWXzt1cUViBmjihmGEe3G6/f\nkz2/IscnG78ZvNgYKjdoG1jlsyje/3zI4C8aWXLq2DnIyxUyAhPgsQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age19mn8zrxl8zpps9yvrh4euquvygpp4fp8queg7xc6qhtnl4ng8c9qx02qwn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2b0NVVm9QNm9waUUzcXBi\nWnZWU0JETjZRZmprZVRUL1h6ZHB4cXkrdm5rCm9GZ0VnTXB3S1BYSmlGWFJVcDhJ\naUl3RjR0ak9BRmQvVk1GRnQxNmtYM00KLS0tIFlTU1p2OHhWUGlOWngwbE56NEhF\nRW5QSkVVUWZpdDZXWEIxZ1BkbzVwclEK2P25nBgf8255vaKW/+T97aNTecRgNjLu\nedIUiPdXbFATCe3v/YRo6sqzFwIsvM6Bl9yHh/SXo6Ftc7eWZZd8zQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1hrx8qj02fj2ea6d4g9vqhyj9hl7fppkjqfdx2l37py3h6pdkr95s8n8rvs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:24Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:J0D8bEs5mHLraLS6TvYuCgfiNU1xKM2Yfb5Y0f/q/4wM4LzXufNzv3+SWDHumTe328U8UnNXLqjNHEKL0bZi0coxpU5hVM+BvPcmqD72vscETzbQ2hnU05sfW+XjfZhcN8/ke0bpLt7nP0crD5hsZv3esV1E2UWvzjEiYtWzFHY=,iv:lcmXYG2H469UKBYDndWKMO+GP0mSGLztenm+kBaUdYI=,tag:ujiXbLM9CUsuoFwQQWI84Q==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:Q++XWxg9tvY7ugT8+8FWCC5jgOfQ1+LYLsnqN0/WGxTf5XT2VmoXvszcE/ow2BUOBG3qBTXS3OHYFPwmj1GgaBHB8rpdXX6+LveSBE2gmx1VR+NUDTxy+0/DBq411MK9n/J9eIYcybdFIE11biFSAob9EgfxBF5roCDXIPDPyVCSe6LyhNvqYPnGQsfHCbejSewLTcRQEiguP9BX96CpMPIpmaB9fHN25t5RWCgMI7MtacrRxRsyKg44+2FZstXdZrp2Wv9u86BxqdAFqtZE8qpPeGdrdzluZx9jhnw0wZPzHdKg5wS7/UrLCb0UxIQiDxDMDuuBuLGkKXfAhb6SGZU41wWAWYk548iGRUaG+79BO4HhRZNObOFvfsjpMXEfcH/Vbv/wHVY4OpJUPe/ZWK4wpL9YrY4nT0t62HH7MirOy1uhwLE50h2+6dHKj6ur5G7thkSqgzVWXQ==,iv:zaRBwS+gfXLhH30havn6Q2+oPWuLV3qBfbOj00kewlQ=,tag:Vr5iEQ2u+9YNahryhgzwSw==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArTGJnaVhleUtsWnlITE1s\nNmRCaW1QWTFNSG5LRFdmb1lzRlV5NWl3YTBNCmF6eUN6RkJBZzc0MmJJa0dKTW01\ncVh4K1VLR2lURUpKQXpxNGpQNnpSUlUKLS0tIFJONnJFWkNCR3pqalRsUW9POVBj\nQ0pFQ3ltKzBETTVXTW5sV1ppWTFJc1kKzxUboNZO+Nwn2eTWy11VP9w1pRswCHaJ\nE2dYU0oUOClVzc0oSuIJxraG6TPj1N4WGC24gS+UmpkmSuCiOeZBsw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDSmxNR1I3dUFKT0xUVG9h\nWkRIakNVeWRQOEN3blNRVjZlWHF4K2NRa0hBClRiOXlmTTJ4T2JTUEw2c1l0R2N2\nMnRwdDA5bEZlQWJRTm9vUmNKclBSU1EKLS0tIFcyeDFjbTZyVEVDUjN1VzU1VHly\nNWNDMW9rTXY2bHNWYVR0SmtMckovUzQKhTWr6yFVW9am3okCiIswwqR5+/p9OLmB\nWCgPtwoFaBt1RjUXPK4/eS4LlucR2K6V/mNMn4xVsnkIl193U9632g==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age16j42pdc5dr6wnj7xayhkqdj2rny9u68fcqejs50hqq42scssh4gsnrrnlt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2ME9CbmVGcENRWksyL0pW\nOEJyUGhpbGMwWlBhVXBSeXQ2MW1EWnFuR0E4CnMwU0pjdk1YMzF5ZEhTVFlBaHZq\nam94UWVGbjhZSEx6VHBmem9JRWgwYzAKLS0tIHFJZWRyRjRHNzhXdDJSYWN3bDlR\nYVp3eGJWWkh3Y09ZWElyclZQN1ZSVFkKjR32//EcFAdMjVlNgky5zvVkwXwEN68D\nrkTuHKjiO5aV7yAQGPkdNw0UM0oRGF0u4YF3oOUcZfSvnKgDeoi2Zw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1ug787sgt6st6k82fgkrug2lzltw4qsukrrqqs3w27ewwqj8rg4hsxcmylz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T00:27:46Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:AbJIHYcFpeanQsJ3x7RPL9Yjlg5BJgkepKax0fL9L/PpA03Antab93iUNG95Mp6k/duovp8Jm445lbuppDZq1dh9ij/deBa8GbzJ50wwEe9zMc3EwRKScpqZEhRPF7KlJsIjsHJyd8NkcI5ji49XkHb4Ae1//8zG5HpVgy+3b04=,iv:uxQCbMwMIfP5S1dbsvIx3F79YWEguxwox8T0YZvUBdc=,tag:3utjmBGDmPc8q4JjaXvCkA==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:aqlMnoVkGtH9z3fJRweeC0OYf7LGqJU2sWA9Q25dKK6NuNyJd4BvjPtpfeg/WhVtJsaOtcbwVm4WAhVK9FARE8g8j+vmq0f6BAU4s6mx0ZIhl+mP+/hIpt//LOdd+9YezelJxdpzUyZbdAngU99rsTluLRe2XmZ7Fquxd8yH/OHenSDY6dizp9+5jfEi8EU+EmuXvuWMPY59xnlnqYNPfSFxs43/pS402LzJoJ5H+cBPprddkUBVzy4cBQvMnrRFUSjnqp74ovZkfIWFqDWQ5YgSU2PjatBg18oulZ7wNRhQ6OLqj6gsu+xrMjNFwnp7rMlA3X//hIidxTkVcYITycXd8KzuMIaofUpnwoyT34fy6+H35/39iiEyG4LRTrOOKRDzXkY2rhJUxFSZ8GlhNhMd0RlkmLngVYrtjsswJ9meIwoAFLPYt7BC61PJf0TXdtk=,iv:mCA819J9LpAOj8QxFAkrHI9wFJIy8qVxv31D6IwWFnk=,tag:qqMtM0eyHbQEyl6ND/wf7g==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZVpRYmNMQ1E4ZlFXMDVq\nbW1FcnYzWTBHQmdhSjlsZUtZK3dzNUZjY1FvCkV4VXU4MTcwNVZoZUpVVS9JeHE5\nbGk3UzE3YmpZQ1JISHV0RHJqSG9ZNXcKLS0tIHhBdnIvMjJkeWlVRE9Fb0FhWjNX\nUjBVWDltK2w0akkwOTJaTGNSWWNrRXMKqZRNnHiXvn1QBoSGdABp7vOqNlsEN6Xr\nDp3NByXow6PuRuWvQXHzd+WC+ADkwNaaiT6TUrbZcd/Pl8Ges9kcZg==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2VFBxc2ZMUFp0YkJhVFhW\nc0ZNbnJUcGtkT1VLR2YrVGhRRFVTZFlyZXlJCmVnblNBNGxIdHh4Q0IweWR1b0dq\nRWFyOURuWkdkRE1RTnJRMEpXdk9HaUUKLS0tIFhKV3FoWW1zRk1pMlhuWWlhV2E5\nQzJTRHAyc0JtSjd2NHlJODZVbndaVDgKFA4565X/4FqNq/fZDZTg81/55hZi4c7b\nTti2AnyE3OcY/kurXJFHRinVMqURQf1fx9MxqUYRitiCz4qe5zFF+A==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1sweerhrga9yf8x6sv0apz4ed4g48rnlcq34rpv20t0rcelwgpgeqwvndzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwRWlKTFNocnBRN01pZ0hm\na3pyRUkxMWM3dE1iTkZZcGRYUFcyb1RDb2h3Ckt4blpGZHBjcnZ4SVE5UnF4cmY4\nWW0rZG5wUkZINVk5a2lmSzN2L3JJY1kKLS0tIExpWGlBRFZJVGpGbFhxRE5ZYjBo\nQVliTlFIZ1U5dE9xbDhHMUtxenpBSm8KY6sIFEfK8p+70IXsC4Jwb9Lm/pd9+V6K\n4JAzGrpA6mAuIwwSNnbdcA5j8FmBhCpK6nLBWmFhGm9Y+MRTaM7Jrw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1nruncs4l0ufk7yuc4des8p99c0alfndl0lhsws8tycl5pplfp56s30af5f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoV0pIUXlyOFFTUHVTdllu\neG10VENOYWl5Wk0ycnpLUytTbnlrQ25pMkFRCnhyRU5xWW9vQXJJOHdFMGZqMkR1\nSktqT3lOdVVPRUN4YTlveGp0NXpqd0kKLS0tIGRaTktra0ZtSVpzSHBrY3VSSUph\nRVRZOSsxTTNmMmltMlJnVy9oT2VEWU0Krxf49B1BsrWn05fqg+cZ0k0PtfJJNfn0\nUL44RUWXWbK2igQHaIct9DfYe7DEonBJeROuxDYm8g7yNOv15S+P4Q==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1529taqdwr6t0w7cvzmty0d5y5593wffl0krt48j6uc4u39k56g2qf6ywtp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:26Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:eyOSn03dzHSgkshPzLVwc95382eEFaDQarHs9l83dtcsb1Ui9CjkKipl2DVSUb6bdMUH1qKYmXqJhwFnAZbFZFjT4VTKtutNtM+OkhVXfT+fJs+1+u7k+ZUYFL9HuxKA6AWpwX3eJ8vmJDJZaAayJbm4PRzOyJywvKeneQqdUS0=,iv:QRKltR4qzVofo/Elt2Us/lrHlD7BenX605X31x+Ng78=,tag:SZ97CcfgMJeu3yqNk8Y/cA==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:QvViw/s1N7eIN3CoD27llEjriuSrohOou4Cv310nigcW8xMnA2SDN2id3H3AoKii1JlJ+qWpKn+gUmt5HOM0UlbeNe3os2RVwiX38O7eN9xFH9F5kA3TFs6Umqq3EoI586PwIVmB2LyxDnTeEEXVd7v5PFkBcfu7u8YIcNF7lpcj+6rOyHMB8uxPhrGep3yiKawFd9c9wWD0hlSSatV5tMHA1qmdK8VmDbCU/iuGwIoMzN1eZwGAXzG6LkCA63bUfdxU6yGuTboD+kN2Wbo+GZB0EACmiZoofl2wqlXuiw6qwTvlXkyauc9O5EG//PUkIECzDwiXcX+qSOM9DIBlZNth4ebhtic/PskyF09etL/gICz5YvV4ph5lyrWHq41KxljSU7QXOkGhzagruuMrYzhZb35wFH4Tie1ee2DXbGhreJr8V3Zse/zTMaD+iM57V8bvNcarTzOFXyKfp7Y=,iv:XBPhj2wT0k/yRCRHU4d+BQA/k00ZHWSKOucnZ5+PGys=,tag:Nh5MOxZIUXAYVZY5SZh/JA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0aUtETXlBMmRlTC9vK3NF\nRW4zS1FrSmo1dDN0OGN4SmV5ODd5MTRVejFJCnpPQkM2MVJ5WFlYRW1NVjduV1hv\nUWUzY0hHV05LV1BROVZVUlM2NldsTlEKLS0tIHFsTzI2SVZzYUtJWTM0MmFiUlVQ\neHEvUXgzc1pxSU1OZFo0cXhSZDdGUVEKpLVfzQEnntluUGsblnkHZJ9Jezu8tFte\nxEoV96GVHxUca6TFWpTLMqdR7NtuQGCkx295W3i1tkp58DP1OzRTUA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4ZW5ybVBvTkxibWJMWGRL\nZ1lnWEkxZ3l6d0FWbmxMY2VsTVlzOHJJM3owClNYd3BlQi9pL1lIdzJmekJUVEVi\ncWhSK3ZkZEMyeTNoZlpTT0NMUXZFWVEKLS0tIHVkVlhmQXdRQktTK3J5dXZ4aTNt\ncUw3WCt5dXJhTkdUbVpmeWoxWkoxNnMKj4XtdwmgFVOiVsIJs2Du7QJ09A9tv/Lf\nkFOq8y4tlZe0nCwRjq43sVz7hdCTdQ0rsaWjBGY90LLkJbOA+f+Wrw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age16kqfmvz4e23hmdlqresnyw69ej604s320mmd49h4hm3fhqchtgyqrws0k2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:30Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:zR2WLWX7NaHA15gi4kX0jDvzUIe9jtz5bMCAggbPW+IXOEedPrddAHZ8OfPErVMfx8O1pJKkAKSzoPTAbEle54FisSLMHXp8fI0297MByJrF9pOsMFpVcDy/L4Q+pBzmB7aS9r7+u7KRVVTZT3QwG1rFWZaDs5dFTP80RhtCbWQ=,iv:ZzPoO+h7ebS+jsSH7tWMx6QK8umpa2/HFQmx9dnJN+Y=,tag:vG9+ifxw4HaBE6YsmOwXcg==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-1
@@ -16,7 +16,7 @@
|
|||||||
serverIp = "192.168.2.226"; # server (NFS/ZFS) Proxmox VM LAN IP
|
serverIp = "192.168.2.226"; # server (NFS/ZFS) Proxmox VM LAN IP
|
||||||
dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP
|
dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP
|
||||||
pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed)
|
pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed)
|
||||||
domainControllerIp = "192.168.2.253"; # FreeIPA domain controller / primary DNS (not NixOS-managed)
|
domainControllerIp = "192.168.2.253"; # FreeIPA domain controller — authoritative DNS for sweet.home (not NixOS-managed)
|
||||||
ipaServer = "domain-controller.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP)
|
ipaServer = "domain-controller.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP)
|
||||||
|
|
||||||
# Cross-host references (LAN hostnames/users other hosts reach over the network)
|
# Cross-host references (LAN hostnames/users other hosts reach over the network)
|
||||||
@@ -80,6 +80,11 @@
|
|||||||
# one-line change.
|
# one-line change.
|
||||||
primaryUser = "nixos";
|
primaryUser = "nixos";
|
||||||
|
|
||||||
|
# Primary IPA/domain user. Home Manager is configured for this user on every
|
||||||
|
# IPA-enrolled host (see modules/ipa/client.nix) to manage the environment
|
||||||
|
# that IPA itself doesn't cover: dotfiles, user packages, session variables.
|
||||||
|
ipaUser = "wayne";
|
||||||
|
|
||||||
# HA file server cluster
|
# HA file server cluster
|
||||||
# LAN IPs (vmbr0 / ens18) — client-facing: iSCSI initiators, NFS, management.
|
# LAN IPs (vmbr0 / ens18) — client-facing: iSCSI initiators, NFS, management.
|
||||||
# Storage IPs (vmbr1 / ens19) — isolated internal bridge, used for DRBD
|
# Storage IPs (vmbr1 / ens19) — isolated internal bridge, used for DRBD
|
||||||
|
|||||||
Reference in New Issue
Block a user