# 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`): ```bash # 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`): 1. Go to **Setup Keys** → **Generate new key**. 2. Name: `AZ-TC-01`, Type: **one-time** (or reusable for fleet), Expires: 90d. 3. Copy the key value. 4. `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`): 1. **Request a Certificate** → **Advanced certificate request**. 2. Template: **Workstation Authentication** (or your corp 802.1X template). 3. Subject: `CN=AZ-TC-01$`. 4. Export the cert + private key as PKCS#12 (`.pfx`), with exportable key. 5. Convert to combined PEM (cert + key in one file): ```bash openssl pkcs12 -in AZ-TC-01.pfx -out AZ-TC-01.pem -nodes ``` 6. `agenix -e secrets/AZ-TC-01-wifi-client-cert.age` → paste the PEM. ### Step 4 — Provision RustDesk credentials ```bash # 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: ```bash # 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@ ``` The host will: 1. Partition the disk via disko (BTRFS layout). 2. Install NixOS from the flake. 3. Reboot into the new system. 4. 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: ```bash 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: ```bash # 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 set - `az.tc.branding.hotline != "+49 30 1234567"` — real hotline must be set - `az.tc.branding.company != "AzIntec GmbH"` — real company name must be set Add more assertions as customer-specific values firm up.