Tiefen-Validierung via 'nix build .#nixosConfigurations.AZ-TC-NN.config.
system.build.toplevel' hat mehrere reale Bugs gefunden, die 'nix flake
check' nicht sah. Alle drei Pilot-Hosts bauen jetzt sauber durch.
Gefixst:
- freerdp3 → freerdp (umbenannt in nixpkgs-unstable)
- SDDM Theme.Logo will INI-Atom (string), nicht Nix-path → '${path}'
- security.pam.mount.extraVolumes will list-of-string, nicht ein String
- sudoers: 'domain admins' muss als 'domain\ admins' escaped werden
- agenix file-Pfade: ../../secrets/ → ../../../secrets/ (Tiefe korrigiert)
- snapper: config.services.snapper.package gibt es nicht → pkgs.snapper
- samba4Full entfernt (blockiert durch ceph-common python metadata issue
in nixpkgs-unstable; Thin Clients brauchen es nicht — cifs-utils reicht)
- corp-wifi-ca.pem durch gültiges Dummy-PEM ersetzt (openssl-generiert,
mit明显 REPLACE-MARKER; build kann PEM parsen)
Functional gemacht:
- Alloy: echtes River-Config mit loki.source.journal + loki.write statt
barem logging-stub. Journal-Logs mit host/unit/severity-Labels nach
Loki.
- Snipe-IT: echte Check-in-Logik via curl + jq. Lookup by asset_tag,
PATCH falls exists, POST falls neu. startAt täglich 03:30. API-Token
via agenix (snipeit-api-token.age).
- node_exporter push: realer curl-Push alle 60s mit retry on failure.
Safety:
- Placeholder-Assertions in roles/thin-client/default.nix: build schlägt
fehl, wenn wifi.ssid/hotline/company noch Placeholder sind (außer
site='staging'). Pilot-Hosts haben site='staging' bis echte Werte da.
- assets/corp-wifi-ca.pem hat deutlich sichtbaren REPLACE-Hinweis.
Neue Options:
- az.tc.monitoring.snipeItUrl (default: snipeit.az-group.local)
Neue Secrets (Placeholder .age-Files zum Ausfüllen):
- snipeit-api-token.age (fleet-wide shared)
9.3 KiB
roles/thin-client — AZ-NIX-CLIENTS Thin Client Fleet
NixOS role for the AZ-TC-NN fleet. Replaces a Windows 10 workstation
with a locked-down NixOS + KDE Plasma client that authenticates against
Active Directory and provides pre-configured RDP, browser, and Office-Web
access.
This document describes the provisioning workflow for a new Fleet Host.
Decision summary
All 20 design decisions from the grilling session are captured in the
top-level default.nix comments and the individual submodule headers.
Quick reference:
| Area | Decision |
|---|---|
| Hardware | Dell OptiPlex Micro (3020/3040/5040/7040) + generic-x86_64-uefi fallback |
| Deployment | Stock Linux ISO + nixos-anywhere |
| Disk | BTRFS (@root, @home, @nix, @persist, @snapshots), zstd |
| AD | Realm AZ-GROUP / DNS az-group.local, pre-created computer accounts + keytab via agenix |
| Shell | bash for domain users |
| Session | KDE Plasma 6, Wayland, SDDM, no autologin |
| Sudo | %domain admins with password; local sascha.koenig + jannik.mueller as break-glass |
| WiFi | 802.1X EAP-TLS per-machine, NetworkManager + wpa_supplicant (not iwd) |
| SMB | pam_mount, Kerberos, DFS namespace, lax offline |
| RDP | System-wide Remmina, Kerberos SSO, fullscreen multi-monitor, audio local only |
| Office | 5 .desktop shortcuts via chromium --app=URL; Outlook + Teams autostart |
| Chromium | ManagedBookmarks, sync disabled, uBlock Origin + Bitwarden, no local passwords |
| RustDesk | Client + daemon, self-hosted server, pre-shared key, Wayland portal pre-authorized |
| OBS | Pre-configured "AZ-Default" profile, output to ~/Videos/OBS/ |
| NetBird | Per-host setup key, corp DNS + NetBird DNS parallel, accept-routes |
| Printers | Single Pull-Print queue, direct IPPS, Avahi off |
| Branding | Light: corporate wallpaper + SDDM logo + property footer, no banner |
| Updates | system.autoUpgrade daily at 03:00, reboot window 03:00-05:00 |
| Monitoring | node_exporter → Pushgateway, Alloy → Loki (journal), Snipe-IT asset check-in (daily 03:30) |
| Rollout | Pilot: 3 hosts, 2 weeks; then 5 → 10 → rest |
Provisioning workflow for a new Fleet Host
Assuming you've copied hosts/AZ-TC-01/default.nix to
hosts/AZ-TC-NN/default.nix, updated networking.hostName, added the
flake entry, and committed.
Step 1 — Pre-create the AD computer account + keytab
Run from a host that can reach the AD DC (e.g. AZ-LT-NIX):
# Pre-create the computer object in the ThinClients OU
adcli precreate --computer-name=AZ-TC-01\$ \
--domain=az-group.local \
--host-fqdn=AZ-TC-01.az-group.local \
--login-type=computer \
--os-name="NixOS" --os-version="25.11" \
--domain-ou="OU=ThinClients,DC=az-group,DC=local" \
administrator@AZ-GROUP
# Join offline and produce a keytab (no real join happens)
adcli join --computer-name=AZ-TC-01\$ \
--domain=az-group.local \
--host-fqdn=AZ-TC-01.az-group.local \
--login-type=computer \
--user=administrator --verbose \
-K /tmp/AZ-TC-01.keytab
# Provision the agenix secret
agenix -e secrets/AZ-TC-01-krb5-keytab.age
# In the editor: paste /tmp/AZ-TC-01.keytab contents, save, exit.
rm /tmp/AZ-TC-01.keytab
Step 2 — Provision NetBird setup key
In the NetBird UI (https://netbird.az-group.local):
- Go to Setup Keys → Generate new key.
- Name:
AZ-TC-01, Type: one-time (or reusable for fleet), Expires: 90d. - Copy the key value.
agenix -e secrets/AZ-TC-01-netbird-setupkey.age→ paste the key.
Step 3 — Provision WiFi client certificate
In AD CS (https://certsrv.az-group.local/certsrv):
-
Request a Certificate → Advanced certificate request.
-
Template: Workstation Authentication (or your corp 802.1X template).
-
Subject:
CN=AZ-TC-01$. -
Export the cert + private key as PKCS#12 (
.pfx), with exportable key. -
Convert to combined PEM (cert + key in one file):
openssl pkcs12 -in AZ-TC-01.pfx -out AZ-TC-01.pem -nodes -
agenix -e secrets/AZ-TC-01-wifi-client-cert.age→ paste the PEM.
Step 4 — Provision RustDesk credentials
# Generate a strong permanent password per host
PW=$(openssl rand -base64 24)
echo -n "$PW" > /tmp/AZ-TC-01-rustdesk-pw.txt
agenix -e secrets/AZ-TC-01-rustdesk-password.age
# Paste /tmp/AZ-TC-01-rustdesk-pw.txt contents.
rm /tmp/AZ-TC-01-rustdesk-pw.txt
The shared rustdesk-psk.age is one-time setup, reused for all hosts.
Step 5 — Provision the host
Boot the OptiPlex from a stock Linux ISO (any modern NixOS installer ISO or Ubuntu live ISO works). From the admin workstation:
# Bootstrap nixos-anywhere into the booted ISO (via SSH or physical console)
# Then run the install:
nixos-anywhere \
--flake .#AZ-TC-01 \
--disko-config ./roles/thin-client/deployment/disko.nix \
root@<target-ip>
The host will:
- Partition the disk via disko (BTRFS layout).
- Install NixOS from the flake.
- Reboot into the new system.
- On first boot: agenix decrypts secrets, sssd starts with keytab, NetBird enrolls via setup key, WiFi connects via EAP-TLS.
Step 6 — Capture the host's SSH host key
After first boot:
ssh-keyscan -t ed25519 AZ-TC-01.netbird | awk '{print $2 " " $3}'
Update secrets.nix with the real AZ-TC-01 SSH key value (replacing
the PLACEHOLDER line). Rebuild and redeploy.
Option reference
All tunable parameters live under az.tc.*:
| Option | Default | Description |
|---|---|---|
az.tc.enable |
false |
Activate the thin-client role |
az.tc.hardwareClass |
generic-x86_64-uefi |
One of dell-optiplex-micro or generic-x86_64-uefi |
az.tc.site |
default |
Site identifier (informational) |
az.tc.ad.ou |
OU=ThinClients,DC=az-group,DC=local |
AD Organizational Unit for computer objects |
az.tc.wifi.ssid |
AZ-CORP |
Corp WiFi SSID |
az.tc.wifi.caCert |
./assets/corp-wifi-ca.pem |
Corp CA cert (NOT secret) |
az.tc.netbird.managementUrl |
https://netbird.az-group.local:443 |
NetBird server URL |
az.tc.smb.dfsNamespace |
\\\\az-group.local\\dfs |
DFS namespace root |
az.tc.smb.userShare |
users/%u |
Per-user share path |
az.tc.smb.commonShares |
[public software] |
Common share names |
az.tc.smb.mountRoot |
/mnt/az-dfs |
Local mount root |
az.tc.printing.pullPrintEndpoint |
ipps://pull-print.az-group.local:443/ipp/print |
Pull-Print IPPS URI |
az.tc.printing.queueName |
Pull-Print |
Local CUPS queue name |
az.tc.rdp.servers |
[{name="TS Berlin"; fqdn="ts-berlin.az-group.local";}] |
TS endpoints |
az.tc.chromium.homepage |
https://www.office.com |
Browser homepage |
az.tc.chromium.bookmarks |
[Outlook, Teams, SharePoint, Office] |
Managed bookmarks |
az.tc.chromium.bitwardenServerUrl |
https://vault.bitwarden.com |
Bitwarden URL |
az.tc.rustdesk.serverHost |
rustdesk.az-group.local |
RustDesk server host |
az.tc.branding.hotline |
+49 30 1234567 |
IT hotline for footer |
az.tc.branding.wallpaper |
./assets/wallpaper.svg |
Wallpaper path |
az.tc.branding.logo |
./assets/logo.svg |
Logo path |
az.tc.branding.company |
AzIntec GmbH |
Company name |
az.tc.monitoring.prometheusPushGateway |
pushgateway.az-group.local:9091 |
Pushgateway URL |
az.tc.monitoring.lokiUrl |
http://loki.az-group.local:3100 |
Loki URL |
az.tc.monitoring.snipeItUrl |
https://snipeit.az-group.local |
Snipe-IT base URL |
az.tc.monitoring.assetTool |
snipe-it |
Asset tool integration |
az.tc.deployment.diskDevice |
/dev/sda |
Disko target disk |
az.tc.deployment.swapSizeGB |
4 |
Swap size in GB |
Open items before pilot deploy
These placeholders need real values before pilot deploy. They're flagged
with # TODO: in the respective modules.
- Real corp WiFi SSID
- Real CA root cert (replace
corp-wifi-ca.pem) - Real DFS namespace path
- Real common share names
- Real Terminal Server endpoints
- Real Chromium bookmarks (Intranet, Helpdesk, ERP, HR)
- Real Bitwarden server URL (if self-hosted)
- Real RustDesk server hostname
- Real Pull-Print IPPS endpoint
- Real NetBird management URL
- Real Prometheus Pushgateway URL
- Real Loki URL
- Real IT-Hotline phone number
- Real corporate wallpaper asset (replace placeholder SVG)
- Real corporate logo asset (replace placeholder SVG)
- Real AD DCs (
az-dc01.az-group.local,az-dc02.az-group.local— currently guessed) - Real AD Computer OU DN
Build validation
To validate the config without deploying:
# Eval-check all hosts
nix flake check
# Build a pilot host closure (full closure, takes ~20 min uncached)
nix build --no-link .#nixosConfigurations.AZ-TC-01.config.system.build.toplevel
# Try a VM boot
nix run .#nixosConfigurations.AZ-TC-01.config.system.build.vm
All three pilot hosts pass both nix flake check and full closure build.
Placeholder assertions
The role ships with placeholder assertions that fire at build time if
operator-relevant values are still defaults. To silence for lab/staging
hosts, set az.tc.site = "staging" (see hosts/AZ-TC-01/default.nix).
Currently asserted:
az.tc.wifi.ssid != "AZ-CORP"— real Corp SSID must be setaz.tc.branding.hotline != "+49 30 1234567"— real hotline must be setaz.tc.branding.company != "AzIntec GmbH"— real company name must be set
Add more assertions as customer-specific values firm up.