Compare commits

...
Author SHA1 Message Date
beatzaplenty 55ba283c82 Merge branch 'main' into worktree-docker-gui-fix
Check NixOS configurations / eval-hosts (pull_request) Successful in 10m24s
2026-07-28 00:32:06 +00:00
beatzaplenty 2d46d25a67 Merge pull request 'fix(ipa): harden script and update module docs' (#82) from worktree-ipa-client-module into main
Check NixOS configurations / eval-hosts (push) Failing after 9m45s
Reviewed-on: #82
2026-07-28 00:25:13 +00:00
beatzaplentyandClaude Sonnet 4.6 f5d29be041 fix(ipa): harden script and update module docs
Check NixOS configurations / eval-hosts (pull_request) Failing after 9m53s
Script fixes:
- Rename HOSTNAME variable to TARGET (shadowed the bash builtin)
- Fix ipa-getkeytab -s to always use IPA_SERVER, not DC_HOST (diverge if
  --dc is overridden to a jump host)
- Remove dead REALM variable
- Add EXIT trap to delete the plaintext keytab if the script aborts before
  sops encryption completes; cleared after successful encrypt
- Distinguish real ipa host-add failures from "already exists" instead of
  swallowing all errors with || true
- Warn explicitly when no platform age keys exist for the target (keytab
  would be admin-only and the host couldn't decrypt it at boot)
- Fix sops fallback from pinned nixos-25.11 channel to nixpkgs (uses the
  repo's own flake.lock)
- Expand "next steps" output to include networking.domain and nameservers
  lines that host.nix requires for IPA membership

Module docs:
- Point to the script as the primary setup path; move manual steps to a
  fallback section
- Note that certs/ipa-ca.crt is already committed (no need to re-fetch)
- Document the networking.domain and nameservers requirements in the header
- Add sync-host-keys.sh as explicit step 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 10:24:23 +10:00
beatzaplenty e10a1572c3 Merge branch 'main' of https://gitea.lan.ddnsgeek.com/beatzaplenty/nixos
Check NixOS configurations / eval-hosts (push) Successful in 10m25s
2026-07-28 10:23:34 +10:00
beatzaplenty 578ef70aa9 updated flake.lock 2026-07-28 10:23:23 +10:00
beatzaplenty e9fcbbbbcb Merge pull request 'Worktree ipa client module' (#80) from worktree-ipa-client-module into main
Check NixOS configurations / eval-hosts (push) Failing after 9m47s
Reviewed-on: #80
2026-07-28 00:13:08 +00:00
beatzaplentyandClaude Sonnet 4.6 f46ae18672 fix(ipa): work around OpenSSH 10 AuthorizedKeysCommand path check
Check NixOS configurations / eval-hosts (pull_request) Failing after 9m43s
OpenSSH 10.0 tightened AuthorizedKeysCommand security by checking every
path component of the command binary for group/world-write permission.
/nix/store is 1775 (group-writable by nixbld), so sshd silently skips
the command for any binary in the Nix store — causing IPA pubkey auth to
silently fail with no diagnostic.

Fix: copy sss_ssh_authorizedkeys to /usr/local/bin via systemd tmpfiles
(C+ copies the file rather than symlinking, so the path at runtime is
root-owned/755 throughout), and point AuthorizedKeysCommand at the copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 10:08:57 +10:00
beatzaplenty fc277294f3 add vars to module
Check NixOS configurations / eval-hosts (push) Successful in 10m32s
2026-07-28 10:02:58 +10:00
beatzaplenty f3e5ea67a0 add docker group to user in enable docker service module
Check NixOS configurations / eval-hosts (push) Successful in 10m33s
2026-07-28 09:59:48 +10:00
beatzaplenty 7a8aebf679 Merge branch 'worktree-docker-gui-fix' 2026-07-28 09:59:40 +10:00
beatzaplenty a8d95aad02 enable docker
Check NixOS configurations / eval-hosts (push) Failing after 9m45s
2026-07-28 09:49:14 +10:00
beatzaplentyandClaude Sonnet 4.6 dac5fbd574 feat(gui): enable docker service on workstation
Check NixOS configurations / eval-hosts (pull_request) Failing after 9m45s
Imports docker/enable-service.nix and pins virtualisation.docker to
docker_29 via a nixpkgs overlay. Fixes indentation on `imports` block
and `programs.direnv.enable` line so nixpkgs-fmt passes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 09:48:59 +10:00
beatzaplenty da0c651c60 Merge branch 'worktree-ipa-client-module' 2026-07-28 09:48:55 +10:00
beatzaplentyandClaude Sonnet 4.6 97019205da fix(ipa): create home dir on first login + AuthorizedKeysCommand
Check NixOS configurations / eval-hosts (pull_request) Failing after 9m45s
- security.pam.services.sshd.makeHomeDir: IPA users have no pre-created
  home directory on the host; without this, sshd opens a session to a
  missing directory and resets the connection immediately after auth
- AuthorizedKeysCommand was already added in previous commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 09:47:11 +10:00
beatzaplentyandClaude Sonnet 4.6 5487490b8e feat(ipa): add AuthorizedKeysCommand + enroll tailscale-router
- modules/ipa/client.nix: add AuthorizedKeysCommand so sshd fetches SSH
  public keys from IPA via sss_ssh_authorizedkeys, enabling pubkey login
  without per-host authorized_keys files

- hosts/tailscale-router/host.nix: add IPA client module + networking.domain
  so SSSD runs and wayne can authenticate on this host

- secrets/tailscale-router.keytab: sops-encrypted keytab for
  tailscale-router.sweet.home (generated by create-nixos-ipa-host-account.sh)

- .sops.yaml: creation rule for secrets/tailscale-router.keytab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 09:42:38 +10:00
beatzaplenty 543ea432f0 Merge pull request 'fix(tailscale-router): stop dnsmasq from intercepting host DNS queries' (#79) from worktree-peaceful-stirring-noodle into main
Check NixOS configurations / eval-hosts (push) Successful in 10m33s
Reviewed-on: #79
2026-07-27 23:27:34 +00:00
7 changed files with 153 additions and 67 deletions
+10
View File
@@ -127,6 +127,16 @@ creation_rules:
# scripts/secrets/sync-host-keys.sh yet, so whichever variant is actually # scripts/secrets/sync-host-keys.sh yet, so whichever variant is actually
# deployed next needs its recipient added here (and `sops updatekeys` rerun) # deployed next needs its recipient added here (and `sops updatekeys` rerun)
# before it can decrypt this. # before it can decrypt this.
# Host keytab for tailscale-router FreeIPA enrollment (binary sops file).
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
- path_regex: secrets/tailscale-router\.keytab$
key_groups:
- age:
- *admin
- *lxc-tailscale-router
- *proxmox-tailscale-router
- *linode-tailscale-router
- path_regex: secrets/gui\.yaml$ - path_regex: secrets/gui\.yaml$
key_groups: key_groups:
- age: - age:
Generated
+6 -6
View File
@@ -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": {
+1 -1
View File
@@ -10,11 +10,11 @@
keytabSopsFile = ../../secrets/tailscale-router.keytab; keytabSopsFile = ../../secrets/tailscale-router.keytab;
caCertFile = ../../certs/ipa-ca.crt; caCertFile = ../../certs/ipa-ca.crt;
}) })
]; ];
networking = { networking = {
hostName = "tailscale-router"; hostName = "tailscale-router";
domain = vars.homeDomain;
useDHCP = false; useDHCP = false;
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{ interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
address = vars.tailscaleRouterIp; address = vars.tailscaleRouterIp;
+12 -1
View File
@@ -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;
+2 -2
View File
@@ -1,4 +1,4 @@
{ pkgs, ... }: { pkgs, vars, ... }:
{ {
# virtualisation.docker.enable = true; # virtualisation.docker.enable = true;
@@ -15,7 +15,7 @@
# experimental = true; # experimental = true;
# }; # };
}; };
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
docker-compose docker-compose
docker-buildx docker-buildx
+48 -20
View File
@@ -6,35 +6,39 @@
# #
# Usage (in a host.nix imports list): # Usage (in a host.nix imports list):
# (import ../../modules/ipa/client.nix { # (import ../../modules/ipa/client.nix {
# keytabSopsFile = ../../secrets/nix-cache.keytab; # keytabSopsFile = ../../secrets/<hostname>.keytab;
# caCertFile = ../../certs/ipa-ca.crt; # caCertFile = ../../certs/ipa-ca.crt; # already committed — do not re-fetch
# }) # })
# #
# The host.nix networking block must also set:
# networking.domain = vars.homeDomain; # needed for Kerberos FQDN
# networking.nameservers = [ vars.domainControllerIp ]; # IPA DNS
#
# One-time operator setup per host (do this BEFORE deploying): # One-time operator setup per host (do this BEFORE deploying):
# #
# 1. Fetch the IPA CA certificate (public — safe to commit): # 0. Generate SSH host keys and the host's age key for sops:
# curl -o certs/ipa-ca.crt http://<ipa-server>/ipa/config/ca.crt # scripts/secrets/sync-host-keys.sh <flake-target>
# Replace the placeholder at certs/ipa-ca.crt and commit it. # This must run before step 1 so the host age key is in .sops.yaml
# and the keytab can be encrypted for the host to read at boot.
# #
# 2. On the FreeIPA server, add the host and generate a keytab: # 1. Add the IPA host account and produce the sops-encrypted keytab:
# ipa host-add <fqdn> --ip-address=<ip> # scripts/ipa/create-nixos-ipa-host-account.sh [--ip <addr>] <hostname>
# ipa-getkeytab -s <ipa-server> -p host/<fqdn> -k /tmp/<host>.keytab # The script handles ipa host-add, ipa-getkeytab, .sops.yaml patching,
# and sops encryption in one step. See the script header for details.
# #
# 3. sops-encrypt the keytab as a binary secret from your admin machine # 2. Wire up the host (see "Usage" above), then deploy:
# (must run from repo root; sops matches creation rules against the file # nixos-rebuild switch (or create-proxmox-resource.sh)
# path, so copy to secrets/ first and encrypt in-place): # No further manual enrollment steps are required after deployment.
#
# Manual fallback (if the script isn't usable):
# a. On the FreeIPA server: ipa host-add <fqdn> [--ip-address=<ip>] --force
# b. On the FreeIPA server: ipa-getkeytab -s <ipa-server> -p host/<fqdn> -k /tmp/<host>.keytab
# c. From the repo root (path must match for sops creation rule to apply):
# 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
# manual enrollment steps required.
#
# vars dependencies: homeDomain, ipaServer
{ keytabSopsFile, caCertFile }: { keytabSopsFile, caCertFile }:
{ config, lib, pkgs, vars, ... }: { config, lib, pkgs, vars, ... }:
@@ -60,6 +64,30 @@ in
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"
];
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.
+70 -33
View File
@@ -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,21 @@ 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}" FQDN="${TARGET}.${HOME_DOMAIN}"
REALM="${HOME_DOMAIN^^}" # uppercase: SWEET.HOME KEYTAB_SECRET="${REPO_ROOT}/secrets/${TARGET}.keytab"
KEYTAB_SECRET="${REPO_ROOT}/secrets/${HOSTNAME}.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 +113,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 +132,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 +150,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 +172,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 +215,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 +258,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