docs+skills: felhom-{build-deploy,ui-design,testing} skills + install_skills.py (junction); CLAUDE.md refresh (version-free); consolidated REPORT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: felhom-build-deploy
|
||||
description: Build, deploy, publish, or verify ANY Felhom artifact — felhom-controller image (guest 9201 bootstrap deploy), felhom-agent binary (felhom-pve), felhom-hub (GitOps/ArgoCD), the felhom.eu website (git-sync), or the app catalog. Use whenever the task says build, deploy, ship, release, publish, bump version, restart the controller/agent/hub, or verify what version is live. Contains the exact verified commands and the gotchas that silently break deploys.
|
||||
---
|
||||
|
||||
# Felhom build & deploy runbooks
|
||||
|
||||
All repos live in `E:\git\` (Git Bash: `/e/git/`). Trunk-based: commit+push to `main` first, always.
|
||||
Update the repo's `CHANGELOG.md` (+ `REUSE.md` if a shared helper changed) in the same commit.
|
||||
|
||||
## Session invariants (set once, every session)
|
||||
|
||||
```bash
|
||||
SSH=/c/Windows/System32/OpenSSH/ssh.exe # Git Bash's /usr/bin/ssh can't reach the Windows agent — fails silently
|
||||
export MSYS_NO_PATHCONV=1 # before any ssh felhom-pve command with absolute paths (pct etc.)
|
||||
```
|
||||
|
||||
| Host | Access | Role |
|
||||
|---|---|---|
|
||||
| Build server (k3s) | `$SSH kisfenyo@192.168.0.180` | build+push images/binaries (`~/build/felhom-{controller,hub,agent}`), `sudo kubectl` |
|
||||
| Demo Proxmox host | `$SSH felhom-pve` (root@192.168.0.162) | agent deploy, `pct` into guests |
|
||||
| Demo guest 9201 | via `pct exec 9201 -- bash -c '...'` on felhom-pve | the live controller |
|
||||
| felhotest (legacy) | `$SSH -p 33022 kisfenyo@router.abonet.hu` | OLD /opt/docker compose mechanism — not the 9201 flow |
|
||||
|
||||
Version bumps are ldflags-only (`-X main.version` / `-X main.Version`) — build scripts inject them; no source edit.
|
||||
|
||||
## Controller (felhom-controller → guest 9201)
|
||||
|
||||
9201 is golden/bootstrap-managed — **NO compose file**. `felhom-controller-bootstrap.service` docker-runs
|
||||
the tag written in `/etc/felhom-controller-image` (anonymous Gitea pull). Data volume + encryption key persist.
|
||||
|
||||
```bash
|
||||
# 1. commit+push the repo
|
||||
# 2. build+push image (build.sh does NOT git-pull — the explicit pull is load-bearing)
|
||||
$SSH kisfenyo@192.168.0.180 "cd ~/build/felhom-controller && git -C ~/git/felhom-controller pull && ./build.sh <VER> --push"
|
||||
# 3. deploy in the guest
|
||||
$SSH felhom-pve "pct exec 9201 -- bash -c 'docker pull gitea.dooplex.hu/admin/felhom-controller:<VER> && echo gitea.dooplex.hu/admin/felhom-controller:<VER> > /etc/felhom-controller-image && systemctl restart felhom-controller-bootstrap.service'"
|
||||
# 4. verify
|
||||
$SSH felhom-pve "pct exec 9201 -- docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}'"
|
||||
```
|
||||
|
||||
Check current live version first: same `docker ps` command, or `cat /etc/felhom-controller-image`.
|
||||
|
||||
## Agent (felhom-agent → felhom-pve)
|
||||
|
||||
Runs as the NON-ROOT `felhom-agent` user: `/usr/local/bin/felhom-agent --config /etc/felhom-agent/agent.json`
|
||||
(systemd `felhom-agent.service`). Sudoers allowlist at `/etc/sudoers.d/felhom-agent`.
|
||||
|
||||
```bash
|
||||
# build on 180 (pull first!)
|
||||
$SSH kisfenyo@192.168.0.180 "cd ~/git/felhom-agent && git pull && go build -ldflags '-X main.version=<VER>' -o /tmp/felhom-agent-<VER> ./cmd/felhom-agent"
|
||||
# fetch to local, then push to the PVE host (Windows scp needs cygpath -w for the LOCAL path)
|
||||
scp kisfenyo@192.168.0.180:/tmp/felhom-agent-<VER> "$(cygpath -w /tmp/felhom-agent-<VER>)"
|
||||
scp "$(cygpath -w /tmp/felhom-agent-<VER>)" felhom-pve:/tmp/
|
||||
# install with backup + restart
|
||||
$SSH felhom-pve "cp /usr/local/bin/felhom-agent /usr/local/bin/felhom-agent.bak-\$(/usr/local/bin/felhom-agent --version | awk '{print \$2}') && install -m0755 /tmp/felhom-agent-<VER> /usr/local/bin/felhom-agent && systemctl restart felhom-agent && sleep 3 && /usr/local/bin/felhom-agent --version && journalctl -u felhom-agent -n 20 --no-pager"
|
||||
```
|
||||
|
||||
**Ship the sudoers + guarded-mkfs wrapper WITH the binary whenever `configs/` changed** — several Go
|
||||
guards exist only if the deployed sudoers/wrapper match the binary (globs must match `stageTemp`
|
||||
patterns). Beware CRLF when scp-ing config files through Windows. After restart, check the journal
|
||||
for a clean `ReassertGuestBinds` and no capability-probe degradations.
|
||||
|
||||
Publish to Gitea (so Day-0 self-install can fetch it): `scripts/publish-agent.sh <ver> <binary>` with
|
||||
`REGISTRY_*` creds. The hub's Day-0 artifact manifest must then vouch the new version — that UI is
|
||||
operator-password-gated (CC cannot); flag it as an operator follow-up.
|
||||
|
||||
## Hub (felhom.eu/hub → k3s, GitOps via ArgoCD app `felhom`)
|
||||
|
||||
**The manifest is the truth.** A code push + image build deploys NOTHING until `manifests/hub.yaml`'s
|
||||
`image:` tag changes in git AND the app is synced (auto-sync is OFF). Never `kubectl set image`
|
||||
(reverted on next sync), never `:latest`. The live image can lag the CHANGELOG — reconcile via the manifest.
|
||||
|
||||
```bash
|
||||
# 1. commit+push code 2. build+push image
|
||||
$SSH kisfenyo@192.168.0.180 "cd ~/build/felhom-hub && ./build.sh <VER> --push"
|
||||
# 3. bump manifests/hub.yaml image tag → <VER>, commit, push
|
||||
# 4. hard-refresh + sync (argocd CLI on 180 is not logged in — drive the Application CR)
|
||||
$SSH kisfenyo@192.168.0.180 "sudo kubectl -n argocd annotate application felhom argocd.argoproj.io/refresh=hard --overwrite; sleep 8; sudo kubectl -n argocd get application felhom -o jsonpath='{.status.sync.status} {.status.sync.revision}{\"\n\"}'"
|
||||
$SSH kisfenyo@192.168.0.180 "sudo kubectl -n argocd patch application felhom --type merge -p '{\"operation\":{\"initiatedBy\":{\"username\":\"cc\"},\"sync\":{\"syncStrategy\":{\"apply\":{}}}}}'"
|
||||
# 5. verify: Synced/Healthy + rollout + image tag + startup log
|
||||
$SSH kisfenyo@192.168.0.180 "sudo kubectl -n argocd get application felhom -o jsonpath='sync={.status.sync.status} health={.status.health.status}{\"\n\"}'; sudo kubectl -n felhom-system rollout status deploy/hub --timeout=90s; sudo kubectl -n felhom-system get deploy hub -o jsonpath='{.spec.template.spec.containers[0].image}'; echo; sudo kubectl -n felhom-system logs -l app=hub --tail 10"
|
||||
```
|
||||
|
||||
Green gate before any hub commit: `go build ./... && go vet ./... && go test ./...` in `hub/`.
|
||||
|
||||
## Website (felhom.eu/website)
|
||||
|
||||
Push to `main` = deployed (git-sync sidecar, live in ~1–2 min). **Run `python scripts/site_gates.py`
|
||||
first, after ANY website change** (BOM, emoji, nav parity, cache-bust `?v=N` — bump it when css/svg
|
||||
change). New pages must be added to the script's `PAGES` list. Emergency edits: https://files.felhom.eu.
|
||||
|
||||
## App catalog (app-catalog-felhom.eu)
|
||||
|
||||
Push to `main` = deploy: the controller's git-sync picks it up within 15 min, or trigger via the
|
||||
dashboard "Sablonok frissítése" button / `POST /api/sync` (30s debounce). Only `docker-compose.yml` +
|
||||
`.felhom.yml` sync; deployed `app.yaml` is never overwritten. Conventions: `<repo>/REUSE.md`.
|
||||
|
||||
## Other k8s manifests (felhom.eu/manifests)
|
||||
|
||||
Same GitOps rule as the hub: edit in git, push, deliberate ArgoCD sync of app `felhom`. Never
|
||||
`kubectl apply` directly. Secrets: out-of-band `kubectl create secret` + `secretKeyRef` — never inline
|
||||
`stringData` (see felhom.eu/REUSE.md §3).
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: felhom-testing
|
||||
description: Felhom testing doctrine — use when writing or reviewing ANY Go test in felhom-controller, felhom-agent, or the felhom.eu hub, and for EVERY correctness or security fix (the red-proof is mandatory there). Triggers - "write a test", "add tests", reviewing a diff that changes logic, fixing a bug, hardening a guard, or validating a fix live. Contains the non-hollow rules, the companion red-proof procedure, seam locations, and the green-gate command.
|
||||
---
|
||||
|
||||
# Felhom testing doctrine
|
||||
|
||||
## Non-hollow rule (the cardinal one)
|
||||
|
||||
A test must assert the **effect**, not the absence of error. Wrong: HTTP 200 came back, `err == nil`,
|
||||
"function ran". Right: the stored row has the expected value, the rendered HTML contains the badge,
|
||||
the state file transitioned, the fake recorded the exact command. If deleting the fix wouldn't fail
|
||||
the test, the test is hollow.
|
||||
|
||||
## Companion red-proof (mandatory for every correctness/security fix)
|
||||
|
||||
Prove the test detects the bug it guards against:
|
||||
|
||||
1. Temporarily restore the PRE-FIX shape (revert the fixed line, or model the old predicate inline).
|
||||
2. Run the test → it must **FAIL**, with the wrong value visible in the failure message.
|
||||
3. Restore the fix → test passes. `git diff` clean.
|
||||
4. Record the red-proof outcome in `REPORT.md` (what failed, with what value).
|
||||
|
||||
In-tree exemplars (verified):
|
||||
- `felhom.eu/hub/internal/notify/dispatcher_test.go` `TestSeverityNotifies` (~L27–49) — models the
|
||||
pre-fix `warning||error` predicate inline and asserts the fix routes what it dropped.
|
||||
- `felhom.eu/hub/internal/api/event_test.go` `TestHandleEvent_CriticalPreserved` — asserts the STORED
|
||||
severity; its red-proof was run by reverting the one-line switch (stored `"info"` → FAIL).
|
||||
|
||||
## Seams over shell-outs
|
||||
|
||||
Never let a unit test touch docker/pct/real /dev. Every repo's **`REUSE.md` §4** lists its seams and
|
||||
existing fakes — inject there:
|
||||
- controller: `diskAgent` (`mockAgent`), `quiesce.Backend/Stacks`, `channelhealth.Probe/Sink`,
|
||||
`selfupdate.AgentSwapper`, `offboxRunner`, `bootstrap.PullFunc`.
|
||||
- agent: `proxmox.Runner` (`mockRunner`), `storage.HostOps/HostReader`, localapi `Options` fakes,
|
||||
Server seam funcs (`reresolveWipe`, `deviceDurableID` — override in tests, no real /dev).
|
||||
- hub: `Dispatcher.sendEmailFn`, `mailrelay.Sender`, `mailRateLimiter.now` (clock),
|
||||
`monitor.EventNotifyFunc`, provider interfaces on the api Handler.
|
||||
|
||||
Test harness conventions: real store on `t.TempDir()` DB (`hub/internal/api/host_test.go`
|
||||
`newTestHandler` pattern); `t.Cleanup` for teardown; table-driven where natural; pure
|
||||
classifier functions get fixture tables (agent `classifyClaim` style).
|
||||
|
||||
## What every test suite should also cover
|
||||
|
||||
- **Negative cases:** the 400/401/403/refusal paths, not just the happy path (e.g. unknown
|
||||
event_type → 400 AND nothing stored).
|
||||
- **Idempotency:** re-running the op is a clean no-op where the contract says so (registry add,
|
||||
intent set, mount ensure).
|
||||
- **Fail-safe direction:** for guards, ambiguity must refuse (agent claim/role classifiers are the
|
||||
canon — any read error ⇒ most-protected verdict).
|
||||
|
||||
## Green gate (run before every commit that touches Go code)
|
||||
|
||||
```bash
|
||||
go build ./... && go vet ./... && go test ./...
|
||||
```
|
||||
Run it in the module dir: `felhom.eu/hub/`, `felhom-controller/controller/`, `felhom-agent/` root.
|
||||
Known flake: agent `TestGenerateRecoveryCode_EntropyAndFormat` fails ~1/5 (hyphenated wordlist word) —
|
||||
re-run before diagnosing; it is not a regression.
|
||||
|
||||
## Live validation doctrine (after unit-land)
|
||||
|
||||
Exercise the SERVER-SIDE PIPELINE a real user triggers, end-to-end — never hand-set state around it
|
||||
(the F9 lesson). Invoking the exact endpoint the UI invokes is an acceptable proxy when a browser
|
||||
isn't available; the residual is client-side rendering only — SAY which method was used. Test crash
|
||||
behavior with kill -9 / OOM, never `docker kill` (containers' restart policy masks the difference).
|
||||
Don't echo API keys/tokens into logs or REPORT — extract into shell vars, print lengths only.
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: felhom-ui-design
|
||||
description: The Felhom design system v2 — use for ANY work on controller dashboard templates/CSS, hub web UI, the felhom.eu website HTML/CSS, customer-facing copy, or status badges/colors. Triggers - editing any *.html/*.css in felhom-controller, felhom.eu hub templates or website; adding a page, badge, button, icon, or color; writing Hungarian customer copy or operator alert text. Contains the token palette, the hard rules (2px/no-shadow/no-emoji/BOM), and which mechanical gate script must run after each surface.
|
||||
---
|
||||
|
||||
# Felhom design system v2
|
||||
|
||||
One visual language across three surfaces: `felhom.eu/website/` (public), the hub UI
|
||||
(`felhom.eu/hub/internal/web/templates/`), the controller UI
|
||||
(`felhom-controller/controller/internal/web/templates/`). Deep rationale:
|
||||
`felhom.eu/documentation/_design-review.md`. Canonical patterns per repo: each `REUSE.md` §2.
|
||||
|
||||
## Token palette (verified identical in all three `:root` blocks — reconfirm in the target CSS before use)
|
||||
|
||||
```css
|
||||
--bg-0: #0A1220; --bg-1: #0F1B2E; --bg-2: #16263F; /* navy backgrounds, darkest first */
|
||||
--line: #22344F; --line-soft: #1A2A42; /* hairline borders */
|
||||
--text-1: #EDF2F9; --text-2: #94A6BF; --text-3: #5E7392; /* text hierarchy */
|
||||
--blue: #0083D8; --blue-bright: #2EA8F5; --blue-dim: rgba(0,131,216,.13);
|
||||
--warn: #E0A93E; --warn-dim: rgba(224,169,62,.12);
|
||||
--crit: #E5534B; --crit-dim: rgba(229,83,75,.12);
|
||||
--radius: 2px;
|
||||
--font-ui: 'Plus Jakarta Sans', ...; --font-data: 'JetBrains Mono', ...;
|
||||
```
|
||||
|
||||
Files: `website/assets/site.css`, `hub/internal/web/templates/style.css`,
|
||||
`controller/internal/web/templates/style.css`. Always tokens — never raw hexes, never inline styles.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- **Exception-color principle:** healthy/nominal = blue/neutral. Amber (`--warn`) and red (`--crit`)
|
||||
appear ONLY on deviation. A stopped-but-intentional state is NEUTRAL, not red.
|
||||
- **Shape:** `--radius` (2px) everywhere; **no box-shadows**; hairline `--line`/`--line-soft` borders;
|
||||
website sections are boxless (rules + spacing, not cards).
|
||||
- **Two-tone H1:** last word wrapped in `<span>` (renders `--blue-bright`). H1 only — never H2+.
|
||||
- **Icons:** monochrome sprite (`icons.svg`, `<use href="...#name">`) / Lucide-style. **No emoji
|
||||
anywhere** — enforced by gates; never hunt emoji with grep (Windows grep false-negatives multibyte;
|
||||
Python gates only).
|
||||
- **Fonts:** vendored woff2 (latin-ext for Hungarian) — **no CDN fonts** (gate-enforced on the website).
|
||||
- **Language:** Hungarian for everything customer-facing (controller UI, customer emails); English for
|
||||
operator surfaces (hub UI, operator alerts). Hungarian text lives in maps like `severityLabels` /
|
||||
`customerMessages` (`hub/internal/notify/templates.go`) — add entries when adding event types.
|
||||
- **Encoding:** `website/*.html` is UTF-8 **with BOM** (preserve it); Go source + hub/controller
|
||||
templates are plain UTF-8, no BOM.
|
||||
- **Cache-bust:** website `site.css` / `icons.svg` references carry `?v=N` — bump N when the asset changes.
|
||||
|
||||
## Status vocabularies (class SUFFIXES, defined in the surface's style.css)
|
||||
|
||||
- Hub `statusColor`: `nominal / warn / crit / neutral` (server.go funcMap). Severity badges:
|
||||
`severity-{critical,error,warning,info,ok}`.
|
||||
- Controller `stateColor`: `run / progress / warn / neutral / off` (funcmap.go). `stateLabel` copy is
|
||||
frozen byte-identical (unit-tested) — don't reword casually.
|
||||
- New template funcs go ONLY into the surface's funcMap (hub `server.go` / controller `funcmap.go`).
|
||||
|
||||
## Gates — run after every change to the matching surface
|
||||
|
||||
| Surface | Gate | When |
|
||||
|---|---|---|
|
||||
| website/*.html + site.css | `python scripts/site_gates.py` (in felhom.eu) | after ANY website change; add new pages to its `PAGES` list in the same commit |
|
||||
| controller templates | `python controller/scripts/template_id_gate.py` + `python controller/scripts/emoji_gate.py` (in felhom-controller) | after ANY controller template change |
|
||||
| hub templates | render tests: `go test ./internal/web/` (render_test.go, funcmap_test.go) | after template/funcmap changes |
|
||||
|
||||
## Canonical patterns to copy (don't reinvent)
|
||||
|
||||
- Website page skeleton (nav/footer byte-identical across pages, only `class="active"` differs):
|
||||
`website/index.html`.
|
||||
- Hub badge/count chain: `hub/internal/web/templates/dashboard.html` events cell.
|
||||
- Controller deploy progress-poll panel (3-step, 3s poll): `controller/internal/web/templates/deploy.html`.
|
||||
- Controller wizard flow (two-step confirm, flash messages via `?flash=`):
|
||||
`controller/internal/web/storage_handlers.go` + its templates.
|
||||
- Nested sidebar sub-links: controller `.nav-links-nested` pattern (base template).
|
||||
Reference in New Issue
Block a user