xetup/CLAUDE.md
X9 Dev 65c98ebd81
All checks were successful
release / build-and-release (push) Successful in 35s
fix(runreport): resolve "failed" label id by name instead of hardcoding 1
The label id was hardcoded (failedLabel = 1), so deleting/recreating the
label in x9/xetup-runs (new id) would make issue creation drop the label or
fail. Look the id up by name at runtime; on any lookup failure (network,
auth, decode, or label absent) file the issue unlabeled rather than not at
all. Verified end-to-end: label resolved and attached via lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:11:15 +02:00

11 KiB

CLAUDE.md - Instructions for Claude Code

Project context

MSP deployment tool for X9.cz - automated preparation of new Windows 10/11 computers. Go GUI launcher (xetup.exe) embeds PowerShell scripts, runs them sequentially, handles reboot cycles for Windows Update, and sends an email report when done.

Key parameters:

  • Target OS: Windows 10 and Windows 11 (x64), including unsupported HW
  • Execution: as Administrator on already-installed Windows (not WinPE/autounattend, not OOBE)
  • Volume: ~20 machines per month, various clients
  • Operator: MSP technician on-site at client
  • Entry point: xetup.exe only (no CLI script entry point)

Communication

  • Communicate with the user in Czech
  • Code, comments, log messages: English only (no diacritics rule still applies)

Repo structure

xetup/
├── CLAUDE.md                     <- this file
├── SPEC.md                       <- technical specification
├── embed.go                      <- embeds scripts/ and assets/ into binary
├── cmd/xetup/
│   ├── main.go                   <- entry point: extract, load config, launch GUI
│   └── app.manifest              <- Windows manifest (requireAdministrator)
├── internal/
│   ├── buildinfo/buildinfo.go    <- ldflags-injected Version + RunsToken (empty on local builds)
│   ├── config/config.go          <- Config struct, Load/Save, DefaultConfig
│   ├── gui/gui.go                <- Walk GUI: form → run → summary (3 phases)
│   ├── runner/runner.go          <- sequential PS script executor with log streaming
│   ├── state/state.go            <- JSON state file for reboot-resume persistence
│   ├── prereboot/                <- autologon + X9-Resume scheduled task for reboot cycle
│   ├── preflight/                <- pre-run checks (admin, winget, network, disk)
│   ├── report/report.go          <- HTML email report via SMTP2Go (+ zipped log, tracker links)
│   └── runreport/runreport.go    <- files a Forgejo issue in x9/xetup-runs on failed runs
├── scripts/
│   ├── common.ps1                <- shared functions (Write-Log, Get-Feature, Load-Config)
│   ├── 00-admin-account.ps1      <- create hidden admin account (adminx9, no password)
│   ├── 01-bloatware.ps1          <- remove AppX, Capabilities, Features
│   ├── 02-software.ps1           <- parallel winget installs + Adobe PDF default + Atera
│   ├── 03-system-registry.ps1    <- HKLM tweaks, Edge policies, OneDrive, powercfg
│   ├── 04-default-profile.ps1    <- NTUSER.DAT + HKCU + personalization (merged)
│   ├── 07-backinfo.ps1           <- deploy BackInfo.exe + startup shortcut
│   ├── 08-activation.ps1         <- Windows activation (OA3 → config key → GVLK)
│   ├── 09-pc-identity.ps1        <- rename PC + C:\X9 folder (exit 9 on rename)
│   ├── 10-network.ps1            <- Private profile, ping, Network Discovery
│   ├── 11-dell-update.ps1        <- Dell Command | Update (auto-skip on non-Dell)
│   └── 12-windows-update.ps1     <- PSWindowsUpdate reboot cycle (exit 9)
├── config/
│   └── config.json               <- default config template
├── assets/
│   ├── Backinfo/                  <- BackInfo.exe + .ini
│   └── Logo/                      <- X9-ikona.ico, X9-logo.jpeg
├── tests/
│   └── Test-Deployment.ps1       <- post-deployment verification
└── web/                           <- xetup.x9.cz static site

Execution flow

xetup.exe start
  → extract scripts/ and assets/ to temp dir
  → state file exists? → resume mode (skip form, run pending steps)
  → normal mode:
      1. Pre-flight checks (admin, winget, network, disk) shown in GUI
      2. Config form (PC name, key, profile, step checkboxes)
      3. Write runtime config JSON (reflects GUI selections)
      4. Run steps sequentially via powershell.exe -File -ConfigPath -LogFile
      5. Step exits 9? → save state, setup autologon + X9-Resume task, reboot
      6. After reboot → xetup resumes, runs remaining steps
      7. All done → cleanup autologon, send email report, show summary

Step execution order

00 Admin account (adminx9)
08 Windows activation
01 Bloatware removal
02 Software (parallel winget + Atera + PDF default)
03 System Registry (HKLM + Edge policies)
04 Default Profile + Personalization (single hive load)
07 BackInfo
10 Network discovery
11 Dell Command | Update
09 PC identity (rename triggers reboot via exit 9)
12 Windows Update (reboot cycle via exit 9)

Conventions and rules

PowerShell

  • All scripts use common.ps1 (dot-sourced): Write-Log, Get-Feature, Load-Config
  • Scripts receive -ConfigPath (path to JSON) and -LogFile params
  • Scripts parse config themselves via Load-Config $ConfigPath
  • $ErrorActionPreference = "Continue" - scripts survive partial failures
  • Exit code 9 = "reboot required" - runner saves state and triggers restart
  • Log to C:\Windows\Setup\Scripts\Deploy.log
  • NO diacritics anywhere (encoding issues across systems)
  • NO emoticons

Go / GUI

  • Walk-based GUI (Windows only, CGO required)
  • Cross-compile: CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64
  • Three phases: config form → live log → summary with reboot countdown
  • Features system: steps can have sub-features (checkboxes in GUI), controlled via config.features

Config

  • config.json is the template, config-runtime.json is written to temp at runtime
  • GUI regenerates runtime config before starting the run
  • DefaultConfig() in config.go provides sensible defaults when config.json is absent
  • Features default to true when missing from config

Testing

  • Test VM: Windows 10/11 x64 on VMware ESXi
  • Before each test: take snapshot, after test: revert
  • Dev environment: x64 VM only (not ARM)

Key implementation details

Admin account (adminx9)

  • No password (empty), hidden from login screen, Administrators group
  • FullName = "X9.cz s.r.o." (via ADSI)
  • Also used by prereboot for autologon during reboot cycles

Edge policies

  • Mandatory (Policies\Microsoft\Edge): HideFirstRunExperience, DefaultBrowserSettingEnabled, DiagnosticData, FeedbackSurveysEnabled
  • Recommended (Policies\Microsoft\Edge\Recommended): everything else (user can override)

PDF default

  • Adobe Reader set via HKCR.pdf after install
  • UCPD driver stopped during association write, restarted after

Reboot-resume cycle

  • prereboot_windows.go: copies xetup.exe to stable path, sets autologon for adminx9, registers X9-Resume scheduled task
  • state.go: persists pending steps + accumulated results across reboots
  • Steps 09 (pcIdentity on rename) and 12 (windowsUpdate) can trigger exit 9
  • Cleanup: disables autologon, removes X9-Resume task

Email report

  • Sent via SMTP2Go (mail-eu.smtp2go.com:2525) at end of deployment
  • From: xetup@x9.cz, To: net@x9.cz
  • Subject: "xetup report HOSTNAME"
  • HTML body with per-step status table
  • Zipped Deploy.log attached as <host>-Deploy.log.zip (multipart/mixed; omitted if log unreadable)
  • Footer links to the x9/xetup-runs tracker: this run's failure issue (if any) + runs filtered to this machine
  • report.go splits buildMessage (testable MIME assembly) from sendMail; see report_test.go

Run failure reporting (runreport)

  • On any ERROR step, files an issue in the private x9/xetup-runs repo; successful runs stay silent
  • Title FAILED <host> - <version> - <date>, label failed (id resolved by name at runtime), body = step table + tail-capped Deploy.log (60 KB)
  • Filed by the xetup-bot Forgejo account (collaborator on xetup-runs only) via a write:issue token
  • Token + version injected at build via -ldflags -X internal/buildinfo.{RunsToken,Version}; empty token -> no-op
  • Best-effort/non-blocking (15s HTTP timeout, 3 retries); the email report is the fallback
  • Returns the issue URL so the email can link to it

Parallel winget

  • 02-software.ps1 launches all winget installs as background jobs (Start-Job)
  • Jobs run simultaneously, results collected after all complete
  • Every install passes --source winget (msstore source fails on fresh Win11 ISOs: 0x8a15005e)

Atera agent

  • Installed under NT AUTHORITY\SYSTEM via a one-shot scheduled task (msiexec /qn) - under SYSTEM there is no interactive MFA prompt. Verified via the AteraAgent service (path check fallback).

Keyboard + accent (step 04)

  • Keyboard: CZ primary + US secondary (Set-WinUserLanguageList + Preload in Default hive and HKU.DEFAULT)
  • Accent #223B47 needs AccentColor AND AccentPalette (REG_BINARY) or Win11 drops it on Start/taskbar
  • Theme written to Default hive + current HKCU + HKU.DEFAULT so all profiles match
  • File Explorer pinned via AUMID (Microsoft.Windows.Explorer), not a custom .lnk (avoids 2nd Explorer)
  • BackInfo.ini BackgroundColor is 0xRRGGBB decimal (RGB, not COLORREF): #223B47 = 2243399

Code signing

  • CI signs xetup.exe via Azure Trusted Signing (cert "X9.cz s.r.o.") + jsign + RFC3161 timestamp
  • Certs are short-lived (~3 days); the timestamp keeps signatures valid past expiry
  • Only AZURE_CLIENT_SECRET is a Forgejo Actions secret; the SP is shared across X9 projects - do NOT rotate
  • jsign auth needs the Trusted Signing token; runner-config mounts the docker socket for the deploy.json step

Forgejo run reporting secret

  • FORGEJO_RUNS_TOKEN (Actions secret) is the xetup-bot write:issue token, baked into xetup.exe via ldflags
  • Unlike the signing SP, this token IS safe to rotate/revoke: regenerate for xetup-bot, update the secret, rebuild
  • Blast radius is limited to opening issues in x9/xetup-runs (bot is collaborator there only)

Workflow

  • Do NOT start coding until explicitly approved - discuss the plan first
  • Propose changes, wait for confirmation, then implement

Pushing to Forgejo

TOKEN=$(sudo docker exec -u git xetup-forgejo forgejo admin user generate-access-token \
  --username x9 --token-name "push-$(date +%s)" --raw --scopes "write:repository")
git push "http://x9:${TOKEN}@localhost:3100/x9/xetup.git" main

DO NOT

  • Do not use $ErrorActionPreference = "Stop" - scripts must survive partial failure
  • Do not remove Calculator (Microsoft.WindowsCalculator)
  • Do not remove Photos (Microsoft.Windows.Photos)
  • Do not use ARM VM for testing
  • Do not write scripts depending on specific username
  • Do not use hardcoded paths that do not exist on clean Windows
  • NO diacritics in any file
  • NO emoticons
  • Do not remove OneDrive policy-block-free (M365 must be able to reinstall it)
  • Do not remove RDP/RDS or Microsoft-RemoteDesktopConnection
  • Do not create Deploy-Windows.ps1 or other CLI entry points (xetup.exe is sole entry point)
  • Do not make x9/xetup-runs public (run logs contain client hostnames and machine names)

Open questions

# Question Status
1 Complete SW list for winget TODO - list may be incomplete