0cc0b5064d
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)
232 lines
9.3 KiB
Markdown
232 lines
9.3 KiB
Markdown
# 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@<target-ip>
|
|
```
|
|
|
|
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.
|