Archived
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5beed2d75c | ||
|
|
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
|
||||
- *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$
|
||||
key_groups:
|
||||
- age:
|
||||
@@ -136,12 +126,72 @@ creation_rules:
|
||||
- *linode-gui
|
||||
- *proxmox-gui
|
||||
|
||||
# Host keytab for tailscale-router FreeIPA enrollment (binary sops file).
|
||||
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
||||
# IPA host keytabs (binary sops files).
|
||||
# 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$
|
||||
key_groups:
|
||||
- age:
|
||||
- *admin
|
||||
- *linode-tailscale-router
|
||||
- *lxc-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
|
||||
|
||||
Generated
+6
-6
@@ -173,11 +173,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784350909,
|
||||
"narHash": "sha256-ZWyzLbS1yKUTeFJLmdVuWNnHttL333/ldJbEE+KzCrM=",
|
||||
"lastModified": 1785119570,
|
||||
"narHash": "sha256-Rgs2xKnGLFWQscxUaXX07oyZeuMDOHEbqDOsgliLFGM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "4ce190229c73d44536caa7072f6308fb2d8feeb3",
|
||||
"rev": "d4fd24667c8cbef124bb70a20380cab75ec8474d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -259,11 +259,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1784432872,
|
||||
"narHash": "sha256-n3gKTBIV4ZA5VQpUakffBe3KGu4+mhPoA34rrqS0GkA=",
|
||||
"lastModified": 1785104993,
|
||||
"narHash": "sha256-eKbrvPoAOFutbYMdbB3r5EQVmFxKv24iKqHPPUXA0gM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fd1462031fdee08f65fd0b4c6b64e22239a77870",
|
||||
"rev": "8623c4c20aa4ca2f5fb81510d2944066c3fb0d96",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
};
|
||||
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
|
||||
# be bumped on an already-installed machine.
|
||||
system.stateVersion = "25.05";
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
name = "nix-cache";
|
||||
sopsFile = ../../secrets/nix-cache.yaml;
|
||||
})
|
||||
(import ../../modules/ipa/client.nix {
|
||||
keytabSopsFile = ../../secrets/nix-cache.keytab;
|
||||
caCertFile = ../../certs/ipa-ca.crt;
|
||||
})
|
||||
];
|
||||
|
||||
networking = {
|
||||
hostName = vars.nixCacheHost;
|
||||
domain = vars.homeDomain;
|
||||
useDHCP = false;
|
||||
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
||||
address = vars.nixCacheIp;
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
name = "tailscale-router";
|
||||
sopsFile = ../../secrets/tailscale-router.yaml;
|
||||
})
|
||||
(import ../../modules/ipa/client.nix {
|
||||
keytabSopsFile = ../../secrets/tailscale-router.keytab;
|
||||
caCertFile = ../../certs/ipa-ca.crt;
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
networking = {
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
{ 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; [
|
||||
inputs.nixos-conf-editor.packages.${pkgs.stdenv.hostPlatform.system}.nixos-conf-editor
|
||||
nodejs
|
||||
@@ -18,7 +29,7 @@
|
||||
];
|
||||
|
||||
boot.loader.grub.useOSProber = true;
|
||||
programs.direnv.enable = true;
|
||||
programs.direnv.enable = true;
|
||||
services = {
|
||||
xserver = {
|
||||
enable = true;
|
||||
|
||||
@@ -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 = {
|
||||
enable = true;
|
||||
shellAliases = {
|
||||
"Switch-nix" = mySwitchCmd;
|
||||
"Test-nix" = myTestCmd;
|
||||
};
|
||||
initExtra = buildImageFn;
|
||||
};
|
||||
# Switch-nix, Test-nix, and buildImage are defined system-wide in
|
||||
# modules/common/configuration.nix so all users (including IPA accounts)
|
||||
# get them. Add any Home-Manager-only per-user shell config here.
|
||||
}
|
||||
|
||||
@@ -1,12 +1,47 @@
|
||||
{ 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 =
|
||||
[
|
||||
# Include the results of the hardware scan.
|
||||
# ./hardware-configuration.nix
|
||||
./set-locale.nix
|
||||
../ipa/client.nix
|
||||
];
|
||||
|
||||
# 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;
|
||||
};
|
||||
# Use the GRUB 2 boot loader.
|
||||
# boot.loader.grub.enable = true;
|
||||
#boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ pkgs, ... }:
|
||||
{ lib, pkgs, vars, ... }:
|
||||
|
||||
{
|
||||
# virtualisation.docker.enable = true;
|
||||
@@ -15,7 +15,13 @@
|
||||
# experimental = true;
|
||||
# };
|
||||
};
|
||||
|
||||
# Pin the docker group GID to match the IPA "docker-access" group so that
|
||||
# IPA group membership alone grants access to the Docker socket. Any user
|
||||
# whose supplementary groups (resolved by SSSD from IPA) include GID
|
||||
# vars.dockerAccessGid will pass the socket group-permission check without
|
||||
# any per-host users.groups.docker.members entry.
|
||||
users.groups.docker.gid = lib.mkForce vars.dockerAccessGid;
|
||||
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
|
||||
environment.systemPackages = with pkgs; [
|
||||
docker-compose
|
||||
docker-buildx
|
||||
|
||||
+163
-44
@@ -1,70 +1,155 @@
|
||||
# Fully declarative FreeIPA domain membership.
|
||||
#
|
||||
# Configures security.ipa (SSSD, Kerberos, PAM, NSSwitch) and places a
|
||||
# pre-provisioned host keytab via sops-nix so no imperative ipa-client-install
|
||||
# step is needed after deployment.
|
||||
# Imported by modules/common/configuration.nix — no per-host wiring needed.
|
||||
# Enables itself automatically on any host that has a sops-encrypted keytab
|
||||
# at secrets/<hostname>.keytab; is a no-op for all other hosts.
|
||||
#
|
||||
# Usage (in a host.nix imports list):
|
||||
# (import ../../modules/ipa/client.nix {
|
||||
# keytabSopsFile = ../../secrets/nix-cache.keytab;
|
||||
# caCertFile = ../../certs/ipa-ca.crt;
|
||||
# })
|
||||
# To enroll a new host:
|
||||
# 0. scripts/secrets/sync-host-keys.sh <flake-target>
|
||||
# 1. scripts/ipa/create-nixos-ipa-host-account.sh [--ip <addr>] <hostname>
|
||||
# (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):
|
||||
#
|
||||
# 1. Fetch the IPA CA certificate (public — safe to commit):
|
||||
# curl -o certs/ipa-ca.crt http://<ipa-server>/ipa/config/ca.crt
|
||||
# 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):
|
||||
# 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
|
||||
# sops -e --input-type binary -i secrets/<host>.keytab
|
||||
# Add secrets/<host>.keytab to .sops.yaml with the host's age key as a
|
||||
# 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.
|
||||
# d. Commit secrets/<host>.keytab and the updated .sops.yaml, then deploy.
|
||||
#
|
||||
# 4. nixos-rebuild switch (or create-proxmox-resource.sh) — no further
|
||||
# manual enrollment steps required.
|
||||
#
|
||||
# vars dependencies: homeDomain, ipaServer
|
||||
# vars dependencies: homeDomain, ipaServer, domainControllerIp, ipaUser
|
||||
|
||||
{ keytabSopsFile, caCertFile }:
|
||||
{ config, lib, pkgs, vars, ... }:
|
||||
|
||||
let
|
||||
keytabPath = ../../secrets + "/${config.networking.hostName}.keytab";
|
||||
enabled = builtins.pathExists keytabPath;
|
||||
|
||||
realm = lib.strings.toUpper vars.homeDomain;
|
||||
fqdn = "${config.networking.hostName}.${vars.homeDomain}";
|
||||
# "sweet.home" -> "dc=sweet,dc=home"
|
||||
basedn = lib.strings.concatMapStringsSep "," (c: "dc=${c}") (lib.strings.splitString "." vars.homeDomain);
|
||||
# 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
|
||||
{
|
||||
security.ipa = {
|
||||
enable = true;
|
||||
domain = vars.homeDomain;
|
||||
realm = realm;
|
||||
server = vars.ipaServer;
|
||||
certificate = caCertPkg;
|
||||
basedn = basedn;
|
||||
ipaHostname = fqdn;
|
||||
offlinePasswords = true;
|
||||
cacheCredentials = true;
|
||||
lib.mkIf enabled {
|
||||
networking.domain = lib.mkDefault vars.homeDomain;
|
||||
networking.nameservers = lib.mkDefault [ vars.domainControllerIp ];
|
||||
|
||||
security = {
|
||||
ipa = {
|
||||
enable = true;
|
||||
domain = vars.homeDomain;
|
||||
inherit realm;
|
||||
server = vars.ipaServer;
|
||||
certificate = caCertPkg;
|
||||
inherit basedn;
|
||||
ipaHostname = fqdn;
|
||||
offlinePasswords = true;
|
||||
cacheCredentials = true;
|
||||
};
|
||||
|
||||
# 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.
|
||||
# lightdm also needs this so the GUI login path can create the home dir
|
||||
# if it was not pre-seeded by the tmpfiles rule above (e.g. on first boot
|
||||
# before SSSD has resolved the user).
|
||||
pam.services = {
|
||||
sshd.makeHomeDir = true;
|
||||
lightdm.makeHomeDir = true;
|
||||
};
|
||||
|
||||
# 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.
|
||||
sudo.extraRules = [{
|
||||
users = [ vars.ipaUser ];
|
||||
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
|
||||
}];
|
||||
};
|
||||
|
||||
systemd = {
|
||||
# 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.
|
||||
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} - -"
|
||||
];
|
||||
|
||||
# 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.
|
||||
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-<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.
|
||||
services."home-manager-${vars.ipaUser}".unitConfig.ConditionPathExists =
|
||||
"/home/${vars.ipaUser}";
|
||||
};
|
||||
|
||||
services.openssh.extraConfig = ''
|
||||
AuthorizedKeysCommand /usr/local/bin/sss_ssh_authorizedkeys %u
|
||||
AuthorizedKeysCommandUser nobody
|
||||
'';
|
||||
|
||||
# Host keytab: pre-provisioned on the IPA server, sops-encrypted binary.
|
||||
# Placed at /etc/krb5.keytab before SSSD starts so the host authenticates
|
||||
# to IPA without running ipa-client-install.
|
||||
sops.secrets."ipa-host-keytab" = {
|
||||
sopsFile = keytabSopsFile;
|
||||
sopsFile = keytabPath;
|
||||
format = "binary";
|
||||
path = "/etc/krb5.keytab";
|
||||
owner = "root";
|
||||
@@ -72,4 +157,38 @@ in
|
||||
mode = "0600";
|
||||
restartUnits = [ "sssd.service" ];
|
||||
};
|
||||
|
||||
# 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).
|
||||
users.users.${vars.ipaUser} = {
|
||||
isNormalUser = true;
|
||||
group = "users";
|
||||
extraGroups = [ "wheel" ];
|
||||
createHome = false;
|
||||
};
|
||||
|
||||
# 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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
# Options:
|
||||
# --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).
|
||||
# --dc-user <u> SSH user on the domain controller. Default: wayne.
|
||||
# --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
|
||||
# can decrypt its own keytab on boot. This script adds the .sops.yaml
|
||||
# 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`.
|
||||
|
||||
set -euo pipefail
|
||||
@@ -52,7 +54,7 @@ DC_HOST="${IPA_SERVER}"
|
||||
DC_USER="wayne"
|
||||
IP_ADDR=""
|
||||
DRY_RUN=false
|
||||
HOSTNAME=""
|
||||
TARGET=""
|
||||
|
||||
usage() {
|
||||
sed -n '/^# Usage:/,/^[^#]/{ /^#/{ s/^# \?//; p } }' "$0"
|
||||
@@ -68,22 +70,29 @@ while [[ $# -gt 0 ]]; do
|
||||
-h|--help) usage 0 ;;
|
||||
-*) echo "Unknown flag: $1" >&2; usage 1 ;;
|
||||
*)
|
||||
if [[ -n "${HOSTNAME}" ]]; then echo "Unexpected argument: $1" >&2; usage 1; fi
|
||||
HOSTNAME="$1"; shift
|
||||
if [[ -n "${TARGET}" ]]; then echo "Unexpected argument: $1" >&2; usage 1; fi
|
||||
TARGET="$1"; shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${HOSTNAME}" ]]; then
|
||||
if [[ -z "${TARGET}" ]]; then
|
||||
echo "Error: hostname required." >&2
|
||||
usage 1
|
||||
fi
|
||||
|
||||
FQDN="${HOSTNAME}.${HOME_DOMAIN}"
|
||||
REALM="${HOME_DOMAIN^^}" # uppercase: SWEET.HOME
|
||||
KEYTAB_SECRET="${REPO_ROOT}/secrets/${HOSTNAME}.keytab"
|
||||
# Reject FQDNs passed by mistake — the script appends HOME_DOMAIN itself.
|
||||
# "nixos.sweet.home" → FQDN would become "nixos.sweet.home.sweet.home".
|
||||
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.
|
||||
DC_TMP="/tmp/nixos-keytab-${HOSTNAME}-$$.keytab"
|
||||
DC_TMP="/tmp/nixos-keytab-${TARGET}-$$.keytab"
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
@@ -112,8 +121,8 @@ dc_run() {
|
||||
if command -v sops &>/dev/null; then
|
||||
SOPS_CMD=(sops)
|
||||
else
|
||||
log "sops not in PATH — will use 'nix run github:NixOS/nixpkgs/nixos-25.11#sops'"
|
||||
SOPS_CMD=(nix run "github:NixOS/nixpkgs/nixos-25.11#sops" --)
|
||||
log "sops not in PATH — will use 'nix run nixpkgs#sops'"
|
||||
SOPS_CMD=(nix run "nixpkgs#sops" --)
|
||||
fi
|
||||
|
||||
# --- Preflight checks ---
|
||||
@@ -131,13 +140,13 @@ cd "${REPO_ROOT}"
|
||||
# also exist at that point or sops will refuse with "no matching creation
|
||||
# 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
|
||||
# Match "path_regex: secrets/<hostname>...keytab" — using .*keytab rather
|
||||
# than \.keytab because the file stores the regex verbatim (\.keytab = two
|
||||
# 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
|
||||
logn "Rule already exists — skipping addition."
|
||||
fi
|
||||
@@ -149,12 +158,20 @@ if ! $RULE_EXISTS; then
|
||||
# that have been registered get added as recipients.
|
||||
RECIPIENTS=("*admin")
|
||||
for platform in lxc proxmox linode; do
|
||||
anchor="${platform}-${HOSTNAME}"
|
||||
anchor="${platform}-${TARGET}"
|
||||
if grep -q "^ - &${anchor} " .sops.yaml; then
|
||||
RECIPIENTS+=("*${anchor}")
|
||||
fi
|
||||
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.
|
||||
RECIPIENT_YAML=""
|
||||
for r in "${RECIPIENTS[@]}"; do
|
||||
@@ -163,9 +180,9 @@ if ! $RULE_EXISTS; then
|
||||
RECIPIENT_YAML="${RECIPIENT_YAML%$'\n'}" # strip trailing newline
|
||||
|
||||
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.
|
||||
- path_regex: secrets/${HOSTNAME}\\.keytab\$
|
||||
- path_regex: secrets/${TARGET}\\.keytab\$
|
||||
key_groups:
|
||||
- age:
|
||||
${RECIPIENT_YAML}"
|
||||
@@ -206,23 +223,37 @@ IP_FLAG=""
|
||||
[[ -n "${IP_ADDR}" ]] && IP_FLAG="--ip-address=${IP_ADDR}"
|
||||
|
||||
# --force: create the host record even if DNS doesn't resolve it yet.
|
||||
# Pipe through grep to suppress the "already exists" warning without
|
||||
# hiding real errors (ipa exits 1 for real errors, 0 for already-exists).
|
||||
HOST_ADD_CMD="sudo ipa host-add '${FQDN}' ${IP_FLAG} --force 2>&1 | \
|
||||
tee /dev/stderr | grep -q 'already exists' && echo '(host already registered)' || true"
|
||||
dc_run "bash -c \"${HOST_ADD_CMD}\""
|
||||
if $DRY_RUN; then
|
||||
echo "[dry-run] ssh ${DC_USER}@${DC_HOST} sudo ipa host-add '${FQDN}' ${IP_FLAG} --force"
|
||||
else
|
||||
HOST_ADD_OUT=$(ssh "${DC_USER}@${DC_HOST}" "sudo ipa host-add '${FQDN}' ${IP_FLAG} --force 2>&1") \
|
||||
&& 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 ---
|
||||
|
||||
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
|
||||
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
|
||||
logn "Streaming keytab from ${DC_HOST}:${DC_TMP} → secrets/${HOSTNAME}.keytab"
|
||||
# scp can't read a root-owned temp file as wayne; pipe through sudo cat instead.
|
||||
logn "Streaming keytab from ${DC_HOST}:${DC_TMP} → secrets/${TARGET}.keytab"
|
||||
# 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}"
|
||||
|
||||
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
|
||||
# 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}"
|
||||
|
||||
# Encryption succeeded — the file is now sops-encrypted; cancel the cleanup trap.
|
||||
trap - EXIT
|
||||
|
||||
# --- Done ---
|
||||
|
||||
if ! $DRY_RUN; then
|
||||
echo ""
|
||||
echo "Done. secrets/${HOSTNAME}.keytab is sops-encrypted and ready."
|
||||
echo "Done. secrets/${TARGET}.keytab is sops-encrypted and ready."
|
||||
echo ""
|
||||
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 " git add secrets/${HOSTNAME}.keytab .sops.yaml"
|
||||
echo " git commit -m 'secrets: add IPA keytab for ${HOSTNAME}'"
|
||||
echo " 3. Add the module to hosts/${HOSTNAME}/host.nix:"
|
||||
echo " (import ../../modules/ipa/client.nix {"
|
||||
echo " keytabSopsFile = ../../secrets/${HOSTNAME}.keytab;"
|
||||
echo " caCertFile = ../../certs/ipa-ca.crt;"
|
||||
echo " })"
|
||||
echo " git add secrets/${TARGET}.keytab .sops.yaml"
|
||||
echo " git commit -m 'secrets: add IPA keytab for ${TARGET}'"
|
||||
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 " keytabSopsFile = ../../secrets/${TARGET}.keytab;"
|
||||
echo " caCertFile = ../../certs/ipa-ca.crt;"
|
||||
echo " })"
|
||||
echo " ];"
|
||||
echo ""
|
||||
echo " 4. Deploy: nixos-rebuild switch (or create-proxmox-resource.sh)"
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,17 @@
|
||||
# one-line change.
|
||||
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";
|
||||
|
||||
# GID of the IPA "docker-access" group (GID 50010 on the IPA server).
|
||||
# The local "docker" group is pinned to this GID on every host that runs
|
||||
# Docker so that IPA group membership alone grants docker socket access -
|
||||
# no per-host users.groups.docker.members entry for the IPA user needed.
|
||||
dockerAccessGid = 50010;
|
||||
|
||||
# HA file server cluster
|
||||
# LAN IPs (vmbr0 / ens18) — client-facing: iSCSI initiators, NFS, management.
|
||||
# Storage IPs (vmbr1 / ens19) — isolated internal bridge, used for DRBD
|
||||
|
||||
Reference in New Issue
Block a user