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:
@@ -1,14 +1,22 @@
|
||||
# CLAUDE.md — Project Instructions for Claude Code (`felhom.eu`)
|
||||
|
||||
> Read automatically by Claude Code when it works in this repo. Keep it updated as the project evolves. Cross-repo orientation (the felhom system, artifact taxonomy, access) lives in the workspace-root `e:\git\CLAUDE.md`; this file is `felhom.eu`-specific.
|
||||
> Read automatically when Claude Code works in this repo. Stable orientation only — **current state
|
||||
> lives in `CONTEXT.md` and the tops of `hub/CHANGELOG.md` / `scripts/CHANGELOG.md` /
|
||||
> `website/CHANGELOG.md`**, never here. Cross-repo orientation (the felhom system, artifact
|
||||
> taxonomy, access): workspace-root `e:\git\CLAUDE.md`; this file is `felhom.eu`-specific.
|
||||
|
||||
## Project overview
|
||||
|
||||
This repo (`felhom.eu`) contains:
|
||||
This repo contains:
|
||||
- **Website** (`website/`) — static HTML at felhom.eu, served via k3s nginx + git-sync sidecar.
|
||||
- **Hub** (`hub/`) — Go application (felhom-hub) — the **operator backend**, on k3s at `hub.felhom.eu`.
|
||||
- **Hub** (`hub/`) — Go application (felhom-hub), the **operator backend**, on k3s at `hub.felhom.eu`.
|
||||
- **K8s manifests** (`manifests/`) — k3s deployment manifests for felhom-system services.
|
||||
- **Architecture docs** (`documentation/`) — the **authoritative design home for the whole Felhom system**: `architecture/01..05-*.md` (topology/trust, controller module map, host-agent, signing, hub), `proxmox-platform.md`, and `tests/phase{0,1-2,3,4}-findings.md`. Read these before designing.
|
||||
- **Architecture docs** (`documentation/`) — the **authoritative design home for the whole Felhom
|
||||
system**: `architecture/01..05-*.md`, `proxmox-platform.md`, `tests/phase*-findings.md`,
|
||||
runbooks, audits. Read these before designing.
|
||||
- **Skills** (`skills/`) — the versioned source of the Claude Code skills
|
||||
(`felhom-build-deploy`, `felhom-ui-design`, `felhom-testing`); install/update with
|
||||
`python scripts/install_skills.py` (junction into `~/.claude/skills/`).
|
||||
|
||||
See `README.md` for full architecture/DNS/email/SEO docs. See `TASK.md` for the current task (if any).
|
||||
See `REUSE.md` before writing new code.
|
||||
@@ -16,18 +24,22 @@ See `REUSE.md` before writing new code.
|
||||
## The Felhom system (so the hub's role is in context)
|
||||
|
||||
Felhom is **Proxmox-based**, with a locked **three-component model**:
|
||||
- **Hub** (this repo, `hub/`) — operator backend. Authors operator *intent*; mirrors box *reality*; holds **no data-plane role** and never connects inbound to a box.
|
||||
- **Hub** (this repo, `hub/`) — operator backend. Authors operator *intent*; mirrors box *reality*;
|
||||
holds **no data-plane role** and never connects inbound to a box.
|
||||
- **Host agent** (repo `felhom-agent/`) — one per Proxmox host; owns all Proxmox interaction.
|
||||
- **In-guest controller** (repo `felhom-controller/`) — one per customer LXC; Docker-only.
|
||||
|
||||
The hub is **not** just controller monitoring anymore. As of slice 3 it ingests **two report streams**: the agent's host-domain report (`POST /api/v1/host-report`, the heartbeat) and the legacy controller report (`POST /api/v1/report`). The controller path is **frozen and retires at the slice-10 cutover** — do not modify it until then.
|
||||
## Hub — architecture (version-free; current version = `manifests/hub.yaml` image tag)
|
||||
|
||||
## Hub — current state (v0.7.x)
|
||||
|
||||
- **Tables:** `customer_configs`, `events`, `app_telemetry`/`app_log_issues`, the legacy `reports`, and the slice-3 host-domain additions `hosts` / `guests` / `host_reports` (additive; columns marked inert exist for the slice-10 cutover but are unused now).
|
||||
- **Auth:** Bearer — global key, per-customer key (legacy), and per-host key (`GetHostByAPIKey`, slice 3). Provisional global-key host mint at `POST /api/v1/admin/hosts`.
|
||||
- **Monitoring:** the controller `StalenessChecker` (over `reports`) AND a sibling `HostStalenessChecker` (over `host_reports`, emitting `host_stale`/`host_down`/`host_recovered`).
|
||||
- Two-tier notifications (operator English / customer Hungarian, Resend, cooldowns); `events` audit.
|
||||
The hub ingests two report streams — the agent's host-domain report (`POST /api/v1/host-report`, the
|
||||
heartbeat/dead-man's-switch) and the legacy controller report (`POST /api/v1/report`, **frozen until
|
||||
the slice-10 cutover — do not modify**) — plus structured controller events (`POST /api/v1/event`,
|
||||
gated by `allowedEventTypes`). Around them: staleness/disk/storage-fill/leaf/capability monitor
|
||||
checkers, the two-tier notification dispatcher (operator English / customer Hungarian, Resend,
|
||||
cooldowns), the app-mail relay, customer-config + Day-0 artifact-manifest management (the checksum
|
||||
trust root the host bootstrap verifies against), assets serving, and the password-gated operator web
|
||||
UI. Package map, helpers, seams, extension points: **`REUSE.md`** (e.g. new event types must enter
|
||||
`allowedEventTypes` + `customerMessages` together).
|
||||
|
||||
## Code quality rules
|
||||
|
||||
@@ -35,80 +47,83 @@ The hub is **not** just controller monitoring anymore. As of slice 3 it ingests
|
||||
- Handle edge cases without overcomplicating.
|
||||
- Add debug capabilities (logging, verbose output).
|
||||
- If you need more input or troubleshooting output, **ask first — don't guess**.
|
||||
- Testing doctrine (non-hollow tests, red-proofs, seams): use the `felhom-testing` skill.
|
||||
- UI/design work (tokens, gates, copy rules): use the `felhom-ui-design` skill.
|
||||
|
||||
## Workflow & artifacts
|
||||
|
||||
The planning/architecture assistant ("project Claude", in claude.ai) writes specs and validates pushes; **you (Claude Code) implement**. A file being open in the editor is NOT an instruction.
|
||||
The planning/architecture assistant ("project Claude", in claude.ai) writes specs and validates
|
||||
pushes; **you (Claude Code) implement**. A file being open in the editor is NOT an instruction.
|
||||
|
||||
- **`TASK.md` / `TASK-*.md`** — a spec for you to implement. Then push and update this repo's changelog (`hub/CHANGELOG.md`) and root `REPORT.md` per the convention below.
|
||||
- **`RUNBOOK-*.md`** — an operational procedure. CC executes the steps it has access and capability for, including live validation on the demo nodes and the demo Proxmox host (CC has root@felhom-pve SSH + the felhom-agent token). A step is human-only only when it genuinely needs physical presence, a real-world decision, or credentials CC truly lacks — mark those steps HUMAN. Do not decline a whole procedure because it touches a live host or a privileged token. (Judgment still applies: confirm before irreversible ops on real customer data — but demo scratch guests are fair game.)
|
||||
- **`TASK.md` / `TASK-*.md`** — a spec for you to implement. Then push and update `hub/CHANGELOG.md`
|
||||
and root `REPORT.md` per the convention below.
|
||||
- **`RUNBOOK-*.md`** — an operational procedure. CC executes the steps it has access and capability
|
||||
for, including live validation on the demo nodes and the demo Proxmox host (CC has root@felhom-pve
|
||||
SSH + the felhom-agent token). Mark a step HUMAN only when it genuinely needs physical presence, a
|
||||
real-world decision, or credentials CC truly lacks.
|
||||
- Validation of a push against a spec's criteria is project Claude's job, not yours, unless asked.
|
||||
- **Browser automation available** (`claude-in-chrome` bridge installed): CC can drive the hub
|
||||
dashboard (`hub.felhom.eu`), the website, or any `*.demo-felhom.eu` UI in a real browser for live
|
||||
validation + screenshots. Caveat: the browser tools attach only to a session STARTED AFTER the bridge
|
||||
connected — if missing, open a fresh task (don't assume it's broken); `/mcp` should show
|
||||
`claude-in-chrome · connected`, else `/chrome` → reconnect.
|
||||
- **Browser automation available** (`claude-in-chrome`): CC can drive `hub.felhom.eu`, the website,
|
||||
or any `*.demo-felhom.eu` UI for live validation + screenshots. Caveat: tools attach only to a
|
||||
session STARTED AFTER the bridge connected. Note: the hub UI is operator-password-gated — CC
|
||||
cannot log in; verify UI changes via render tests instead.
|
||||
|
||||
> **In every repository where you make a change, update both files in that repo:**
|
||||
> - **`CHANGELOG.md`** — a cumulative log of **all** changes; newest entry on top.
|
||||
> - **`REPORT.md`** — **overwrite** with a summary of the **most recent** implementation (or significant validation/operational run) only; not cumulative.
|
||||
> - **`CHANGELOG.md`** — cumulative log, newest on top (here: per-area `hub/`, `scripts/`, `website/`).
|
||||
> - **`REPORT.md`** — **overwrite** with the most recent implementation/validation summary only.
|
||||
>
|
||||
> **Never write secrets** — tokens, passwords, private keys, API keys — into `CHANGELOG.md`, `REPORT.md`, or any committed file. Reference them as "stored out-of-band" instead.
|
||||
> **Never write secrets** into any committed file — reference them as "stored out-of-band".
|
||||
|
||||
- Update `REUSE.md` if you added/changed/deprecated a shared helper or pattern (same commit).
|
||||
|
||||
## Tech stack (Hub)
|
||||
|
||||
- **Language:** Go 1.24+ (build server is go1.26.0).
|
||||
- **Web:** stdlib `net/http` + `html/template`. **DB:** SQLite via `modernc.org/sqlite` (pure Go).
|
||||
- **Auth:** bcrypt + Bearer tokens. **Deploy:** Docker on k3s (felhom-system ns).
|
||||
- **Storage:** Longhorn PVC at `/data/` (SQLite DB). **Config:** YAML via ConfigMap at `/etc/felhom-hub/hub.yaml`.
|
||||
- **Language:** Go (stdlib `net/http` + `html/template`, no frameworks). **DB:** SQLite via
|
||||
`modernc.org/sqlite` (pure Go). **Auth:** bcrypt + Bearer tokens + session cookies + CSRF.
|
||||
- **Deploy:** Docker on k3s (`felhom-system` ns). **Storage:** Longhorn PVC at `/data/` (SQLite DB).
|
||||
- **Config:** YAML via ConfigMap at `/etc/felhom-hub/hub.yaml`. Secrets via out-of-band
|
||||
`secretKeyRef` (never inline stringData — REUSE.md §3).
|
||||
|
||||
## SSH access
|
||||
|
||||
Use the Windows OpenSSH binary (Git Bash's `/usr/bin/ssh` can't reach the Windows agent and fails silently): `SSH=/c/Windows/System32/OpenSSH/ssh.exe`. All SSH commands below use `$SSH`.
|
||||
Use the Windows OpenSSH binary (Git Bash's ssh fails silently): `SSH=/c/Windows/System32/OpenSSH/ssh.exe`.
|
||||
|
||||
| Host | IP | User | Role |
|
||||
|------|----|------|------|
|
||||
| Build server (k3s node) | 192.168.0.180 | kisfenyo | Build + push images, kubectl (needs `sudo`) |
|
||||
| Demo Proxmox host | 192.168.0.162 | root@pam (SSH alias felhom-pve, root, no sudo) | pveum/pct + live Proxmox validation — available to CC |
|
||||
| Build server (k3s node) | 192.168.0.180 | kisfenyo | Build + push images, `sudo kubectl` |
|
||||
| Demo Proxmox host | 192.168.0.162 | root@pam (SSH alias `felhom-pve`) | pveum/pct + live Proxmox validation |
|
||||
|
||||
## Build & deploy — Hub (GitOps via ArgoCD)
|
||||
|
||||
The whole k3s cluster is GitOps via a **single ArgoCD app named `felhom`** (`argocd.dooplex.hu`) that syncs this repo's **`manifests/`** to the **`felhom-system`** namespace. **There is no separate `hub` ArgoCD app** — the hub is one `Deployment` (`manifests/hub.yaml`) *inside* the `felhom` app. **Auto-sync is OFF**: deploys are a deliberate manual sync. ArgoCD's source of truth is the **manifest**, so:
|
||||
**Full runbook: use the `felhom-build-deploy` skill.** The load-bearing rules:
|
||||
|
||||
- **A code change + CHANGELOG version bump does NOT deploy anything.** The running image only changes when `manifests/hub.yaml`'s `image:` tag changes in git and the app is synced.
|
||||
- **Pin explicit versions, never `:latest`.** A `:latest` re-push wouldn't change the manifest, so ArgoCD wouldn't redeploy, and Synced / History / Rollback would all misreport what's actually live.
|
||||
The whole cluster is GitOps via a **single ArgoCD app `felhom`** syncing this repo's `manifests/`
|
||||
to `felhom-system`. **Auto-sync is OFF** — deploys are a deliberate manual sync. ArgoCD's source of
|
||||
truth is the **manifest**:
|
||||
|
||||
After a code change to `hub/`, to deploy:
|
||||
- **A code change + CHANGELOG bump deploys NOTHING.** The running image changes only when
|
||||
`manifests/hub.yaml`'s `image:` tag changes in git and the app is synced.
|
||||
- **Pin explicit versions, never `:latest`.** Never bare `kubectl set image`/`kubectl apply`
|
||||
(reverted on next sync).
|
||||
- **The live image can lag the CHANGELOG** when a bump was committed but the manifest/sync step never
|
||||
happened — reconcile via the manifest, not the changelog.
|
||||
- Green gate before any hub commit: `go build ./... && go vet ./... && go test ./...` in `hub/`.
|
||||
|
||||
1. **Commit + push the code:** `cd /e/git/felhom.eu && git add -A && git commit -m "<msg>" && git push`
|
||||
2. **Build + push the image** (build script lives on the build server, not in this repo): `$SSH kisfenyo@192.168.0.180 "cd ~/build/felhom-hub && ./build.sh <NEW_VERSION> --push"` (pulls latest from Gitea, builds version into `main.Version` via ldflags, pushes `gitea.dooplex.hu/admin/felhom-hub:<VER>`). Pin `<VER>`; don't rely on `:latest`.
|
||||
3. **Bump the manifest:** set the `image:` tag in `manifests/hub.yaml` to `:<NEW_VERSION>`, commit to `main`, push. The `felhom` app now shows **OutOfSync**.
|
||||
4. **Sync** (auto-sync is off, so this is required). Easiest is the ArgoCD UI → app `felhom` → **Sync**. From the shell, the `argocd` CLI on 180 is **not logged in** (no server session) and `--core` looks in the wrong namespace under `sudo` (env is stripped) — so the reliable scripted path is to drive the Application CR with `kubectl`:
|
||||
```bash
|
||||
# a) hard-refresh so ArgoCD picks up the new commit, then confirm OutOfSync:
|
||||
$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\"}'"
|
||||
# b) trigger the sync via the .operation field (the app controller runs it):
|
||||
$SSH kisfenyo@192.168.0.180 "sudo kubectl -n argocd patch application felhom --type merge -p '{\"operation\":{\"initiatedBy\":{\"username\":\"cc\"},\"sync\":{\"syncStrategy\":{\"apply\":{}}}}}'"
|
||||
```
|
||||
(If you do log the CLI in: `argocd app sync felhom` is the one-liner equivalent.)
|
||||
5. **Verify:** `$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"` (expect Synced/Healthy + the new tag + `[INFO] felhom-hub <VERSION> starting`).
|
||||
|
||||
> A bare `kubectl set image` would be reverted on the next sync (the manifest is the truth) — always go through `manifests/hub.yaml`. **The live image can lag the CHANGELOG** when version bumps were committed but step 3/4 was never done; reconcile via the manifest, not by assuming the changelog reflects what's running.
|
||||
Steps: commit+push code → `./build.sh <VER> --push` on 180 (`~/build/felhom-hub`) → bump
|
||||
`manifests/hub.yaml` tag + push → ArgoCD hard-refresh + sync (kubectl-patch method in the skill) →
|
||||
verify Synced/Healthy + rollout + image + startup log.
|
||||
|
||||
## Build & deploy — Website / Manifests
|
||||
|
||||
- **Website** auto-deploys via git-sync; just push to `main` (live in 1–2 min). Emergency edits: FileBrowser at `https://files.felhom.eu`.
|
||||
- **Manifests** (`manifests/`) are GitOps via the `felhom` ArgoCD app — commit to `main`, then sync (auto-sync is off): UI Sync or `argocd app sync felhom`. Do **not** `kubectl apply` them directly (a later sync reverts drift; the manifest in git is the truth).
|
||||
- **Website** auto-deploys via git-sync; just push to `main` (live in 1–2 min). **Run
|
||||
`python scripts/site_gates.py` after ANY website change**; new pages go into its `PAGES` list.
|
||||
Emergency edits: https://files.felhom.eu. All `website/` HTML is **UTF-8 with BOM** — preserve it.
|
||||
- **Manifests** are GitOps via the `felhom` app — commit to `main`, then deliberate sync.
|
||||
|
||||
## Key patterns
|
||||
|
||||
- Hub ingests **host-reports from agents** (`POST /api/v1/host-report`, Bearer per-host) and legacy **controller reports** (`POST /api/v1/report`). The host-report `received_at` is the dead-man's-switch liveness signal.
|
||||
- Status logic: OK (report < 30m), WARN (30m–1h or health=warn), DOWN (> 1h or health=fail).
|
||||
- SQLite timestamps vary in format — use `parseSQLiteTime()`.
|
||||
- Dashboard/detail auto-refresh every 60s via `<meta http-equiv="refresh">`. Geo-restricted to Hungary via nginx ingress annotation.
|
||||
|
||||
## File encoding
|
||||
|
||||
All `website/` HTML is **UTF-8 with BOM** — preserve it. Hub Go source is standard UTF-8 (no BOM).
|
||||
- Hub status logic: OK (report < 30m), WARN (30m–1h or health=warn), DOWN (> 1h or health=fail);
|
||||
host liveness thresholds shared between UI and checker (never invent a second definition).
|
||||
- SQLite timestamps vary in format — always `parseSQLiteTime()`.
|
||||
- Dashboard/detail auto-refresh every 60s via meta refresh. Geo-restricted to Hungary via nginx
|
||||
ingress annotation.
|
||||
- Helpers, seams, extension points, traps: **`REUSE.md`** — the map is maintained same-commit.
|
||||
|
||||
@@ -3,4 +3,12 @@
|
||||
> Created with the REUSE.md rollout (2026-07-03). Authoritative history: `hub/CHANGELOG.md` (hub),
|
||||
> `website/CHANGELOG.md`, `scripts/CHANGELOG.md`; end-of-task detail in `REPORT.md`.
|
||||
|
||||
- **2026-07-03 — hub v0.31.0 LIVE** (critical-severity ingest fix + UI badges; ArgoCD Synced/Healthy;
|
||||
live probe: critical event stored + operator email delivered).
|
||||
- **2026-07-03 — Felhom skills exist**: `skills/{felhom-build-deploy,felhom-ui-design,felhom-testing}`,
|
||||
installed via `scripts/install_skills.py` — **junction mode** (repo edits live immediately; no
|
||||
re-install needed after editing). Fresh-session discovery verified.
|
||||
- **2026-07-03 — CLAUDE.md refreshed** (all four repos): no version-pinned state in CLAUDE.md —
|
||||
current state lives here / CHANGELOG / REUSE.md.
|
||||
|
||||
- **2026-07-03 — `REUSE.md` exists at the repo root** (hub helpers / website conventions / gate scripts / manifest patterns, code-verified); maintenance rule active: update it in the same commit that changes a shared helper. `scripts/reuse_refs_check.py` machine-checks its citations across all four felhom repos.
|
||||
|
||||
@@ -4,104 +4,106 @@
|
||||
|
||||
---
|
||||
|
||||
# REPORT — docs: REUSE.md introduced across all four felhom repos (consolidated)
|
||||
# REPORT — hub v0.31.0 critical-severity fix + Felhom skills + CLAUDE.md refresh (consolidated)
|
||||
|
||||
**Date:** 2026-07-03 · **Class:** docs-only (no code change, no version bumps, no builds, no deploys)
|
||||
**Deliverable:** a per-repo `REUSE.md` reuse map (canonical helpers / patterns / traps / seams /
|
||||
extension points / known duplication), code-verified at file+symbol, plus `scripts/reuse_refs_check.py`
|
||||
as the staleness gate, plus CLAUDE.md maintenance hooks.
|
||||
**Date:** 2026-07-03 · **Class:** implementation (hub v0.31.0, deployed) + docs/skills across all repos
|
||||
|
||||
## 1. Baselines used (all matched the task's expected hashes; trees clean, pulled fresh)
|
||||
## 1. Baselines & commits
|
||||
|
||||
| Repo | `main` @ baseline | State |
|
||||
All four repos matched the task's expected baselines (pulled clean).
|
||||
|
||||
| Repo | Baseline | Commits pushed to `main` |
|
||||
|---|---|---|
|
||||
| felhom-controller | `d0c70a7da6f4060422a545cea2396f9df5f7d61f` | v0.98.3 |
|
||||
| felhom-agent | `0db09bf34fe2eff4b80ac77ed19afa18aef7675f` | v0.61.0 |
|
||||
| felhom.eu | `b0de6b34f61fef2e14807fba2c6176b888dc3327` | hub v0.30.1 |
|
||||
| app-catalog-felhom.eu | `bb81cd414afcb89e28ac60f405133f40195e6c43` | n/a |
|
||||
| felhom.eu | `d331eb26d1` | `b5f0050` (fix+tests+REUSE), `4a18306` (manifest→0.31.0), `ad61e96` (render test), + the docs/skills commit carrying this REPORT |
|
||||
| felhom-controller | `eda33400da` | `a518023` (CLAUDE.md refresh) |
|
||||
| felhom-agent | `2a024b6039` | `3e760a5` (CLAUDE.md refresh) |
|
||||
| app-catalog-felhom.eu | `c5a3d1b15b` | `15cdf7b` (CLAUDE.md expansion) |
|
||||
|
||||
## 2. Files created/modified per repo
|
||||
## 2. Part 1 — hub v0.31.0 (the critical-severity fix)
|
||||
|
||||
- **felhom-controller:** `REUSE.md` (new, 210 lines); `CLAUDE.md` (+2 hook lines); `CHANGELOG.md`
|
||||
(docs entry); `CONTEXT.md` (note); `REPORT.md` (overwritten).
|
||||
- **felhom-agent:** `REUSE.md` (new, 160 lines); `CLAUDE.md` (+2 hook lines); `CHANGELOG.md`;
|
||||
`CONTEXT.md`; `REPORT.md`.
|
||||
- **felhom.eu:** `REUSE.md` (new, 151 lines — hub + website + scripts + manifests);
|
||||
`scripts/reuse_refs_check.py` (new gate); `CLAUDE.md` (+2 hook lines); `hub/CHANGELOG.md` +
|
||||
`scripts/CHANGELOG.md` (docs entries); `CONTEXT.md` (new, minimal — repo had none); `REPORT.md`
|
||||
(this file).
|
||||
- **app-catalog-felhom.eu:** `REUSE.md` (new, 58 lines, light §2/§5 variant per spec); `CLAUDE.md`
|
||||
(new, minimal — repo had none; full CLAUDE.md is a separate task); `CHANGELOG.md`; `CONTEXT.md`
|
||||
(new, minimal); `REPORT.md`.
|
||||
**Change:** `handleEvent` severity switch gains `"critical"` (unknown values still coerce to `"info"`,
|
||||
exact-match lowercase); `severityLabels["critical"] = "Kritikus hiba"` added; dashboard consumer
|
||||
gains `EventCriticals`; critical badge renders FIRST in dashboard + customer-page count chains;
|
||||
`style.css` defines the previously-referenced-but-undefined `.severity-critical` (`--crit` tokens)
|
||||
and `.severity-ok` (neutral). Frozen legacy `/notify` path and `allowedEventTypes` untouched.
|
||||
|
||||
## 3. Commits pushed to `main` (one per repo)
|
||||
**Tests (new `hub/internal/api/event_test.go` — first tests on /event):**
|
||||
- A: critical preserved to store — **companion red-proof run:** with the one-line fix reverted the
|
||||
test FAILED with `stored severity = "info", want "critical"`; fix restored → PASS.
|
||||
- B: severity `"banana"` → stored `"info"`. C: unknown event_type → 400 + store empty. Auth: 401.
|
||||
- D: `TestTemplates_DashboardCriticalBadge` (render_test.go) asserts the rendered HTML contains the
|
||||
`severity-critical` count badge ordered before the error badge. **Method note:** UI verification
|
||||
used the Go render test — Chrome verification stopped at the operator login (password entry is
|
||||
out of CC's scope).
|
||||
- Green gate: `go build ./... && go vet ./... && go test ./...` — all green.
|
||||
|
||||
| Repo | Commit |
|
||||
|---|---|
|
||||
| felhom-controller | `eda33400da0fd6a3b80271c44df24a177f7a444a` |
|
||||
| felhom-agent | `2a024b603973e79a0c84d6847a7cde0898091c61` |
|
||||
| app-catalog-felhom.eu | `c5a3d1b15b71103a3d1da217f1ff5a834857375c` |
|
||||
| felhom.eu | the commit carrying this REPORT (HEAD of `main` after this push) |
|
||||
**Deploy (GitOps):** image `felhom-hub:0.31.0` built+pushed on 180; `manifests/hub.yaml` bumped;
|
||||
ArgoCD app `felhom` hard-refresh + sync → `Synced`/`Healthy`, rollout complete, pod image
|
||||
`...felhom-hub:0.31.0`, clean startup logs.
|
||||
|
||||
## 4. Per-repo sweep counts (evidence of depth)
|
||||
**Live probe (real pipeline):** POSTed one `event_type:"test"`, `severity:"critical"` event from
|
||||
guest 9201 using its own controller.yaml credentials (key never echoed — length-only). Result:
|
||||
HTTP 200; hub log `Event from demo-felhom: test (critical)` (stored severity critical, not info);
|
||||
dispatcher sent the operator email (`Test email sent to nagyfenyvesi.viktor@gmail.com`).
|
||||
|
||||
| Repo | funcs scanned (non-test) | §1 rows | §3 traps | §6 duplications |
|
||||
|---|---|---|---|---|
|
||||
| felhom-controller | 1174 | 62 | 8 | 12 |
|
||||
| felhom-agent | 702 | 48 | 10 | 7 |
|
||||
| felhom.eu (hub Go) | 297 | 34 | 8 | 5 |
|
||||
| app-catalog | 53 apps scanned | 0 (by design) | 3 | 6 |
|
||||
**REUSE.md** §1 gotcha + §3 trap row updated in the same commit as the fix;
|
||||
`reuse_refs_check.py` → `OK [felhom.eu]: 31 cited paths, all exist`.
|
||||
|
||||
Trap sweeps ran mechanically per §Method (`--delete`, `wipefs`, `mkfs`, `os.RemoveAll`, `rm -rf`,
|
||||
`sfdisk`, `dd `, `Format`, `Wipe`) in every repo; each hit classified as guarded-canonical (§1, guard
|
||||
named) or §3 trap. Known traps (rsyncMirror `--delete`, gokapi entrypoint, AGENT-001 TOCTOU shape,
|
||||
allowedEventTypes 400) were independently rediscovered from code.
|
||||
## 3. Part 2 — skills
|
||||
|
||||
## 5. reuse_refs_check.py output (final, all four repos)
|
||||
Three skills authored in `felhom.eu/skills/` (each SKILL.md ≤160 lines, pushy trigger descriptions):
|
||||
`felhom-build-deploy`, `felhom-ui-design`, `felhom-testing`. Content verified against live
|
||||
source/infra before writing (see §5). New `scripts/install_skills.py`: idempotent, junction-first
|
||||
(`mklink /J`) with copy fallback + re-run reminder.
|
||||
|
||||
**Install mode: JUNCTION** — created for all three; second run detected them and no-op'd.
|
||||
**Discovery (Scenario E):** fresh `claude -p` session listed all three:
|
||||
```
|
||||
OK [felhom-controller]: 65 cited paths, all exist
|
||||
OK [felhom-agent]: 57 cited paths, all exist
|
||||
OK [felhom.eu]: 30 cited paths, all exist
|
||||
OK [app-catalog-felhom.eu]: 10 cited paths, all exist
|
||||
felhom-build-deploy
|
||||
felhom-testing
|
||||
felhom-ui-design
|
||||
```
|
||||
Bonus trigger probe: "which skill to deploy a new hub version?" → answered `felhom-build-deploy`
|
||||
with the correct first command (`./build.sh <VER> --push` on 180).
|
||||
|
||||
(The gate earned its keep during authoring: it caught the controller map citing
|
||||
`scripts/{template_id_gate,emoji_gate}.py` — the real location is `controller/scripts/` — fixed
|
||||
before commit.)
|
||||
## 4. Part 3 — CLAUDE.md refresh
|
||||
|
||||
## 6. Observations — doc↔code drift & findings (recorded, NOT acted on)
|
||||
Standing rule adopted everywhere: **CLAUDE.md carries no version-pinned current state** (that lives
|
||||
in CONTEXT/CHANGELOG/REUSE); deep runbooks live in the skills, CLAUDE.md keeps a summary table + pointer.
|
||||
|
||||
**Worth operator attention:**
|
||||
- **hub severity-coercion gotcha (NEW finding):** the remembered "dispatcher drops critical" issue is
|
||||
FIXED in code (`severityNotifies` routes warning/error/critical since v0.24.0), **but**
|
||||
`handleEvent` (hub/internal/api/handler.go) coerces any severity outside {info,warning,error} —
|
||||
including `"critical"` — to `"info"` at ingest, so a controller-POSTed critical event never
|
||||
notifies. Hub-internal events bypass this. Recorded in felhom.eu REUSE.md §3.
|
||||
- **Committed secrets:** `manifests/felhom.secret.yaml` still carries inline `stringData`
|
||||
credentials (healthchecks superuser pw + Django secret — not deployed; umami APP_SECRET +
|
||||
POSTGRES_PASSWORD — deployed), unlike the de-gitted Resend key. Recorded as a §3 trap; rotation/
|
||||
de-git is an operator follow-up.
|
||||
| File | Diffstat | Headlines |
|
||||
|---|---|---|
|
||||
| felhom-controller/CLAUDE.md | +119/−294 (338→~160 lines) | full 30-package map (was 7); stale `/opt/docker` demo deploy replaced with verified 9201 bootstrap flow; hub build section deleted; patterns pruned to 6 invariants |
|
||||
| felhom-agent/CLAUDE.md | +86/−71 | "Current: v0.31.0" narrative + slice history deleted (30 versions stale); layout completed (+8 packages, opsign); deploy facts re-verified on felhom-pve |
|
||||
| felhom.eu/CLAUDE.md | +73/−58 | "Hub — current state (v0.7.x)" (23 versions stale) → version-free architecture section; GitOps rules kept incl. live-image-lag note; skills section added |
|
||||
| app-catalog CLAUDE.md | +26/−5 | stub → ~30-line proper file (purpose, deploy contract, pointers) |
|
||||
| E:\git\CLAUDE.md (local) | +8 | Skills section only |
|
||||
|
||||
**Stale docs (each a separate refresh task):**
|
||||
- `felhom-agent/CLAUDE.md` says "Current: v0.31.0" and omits ~8 live packages (localapi, signedjobs,
|
||||
escrow, capability, guesthook, lanresolver, desired, provision).
|
||||
- `felhom.eu/CLAUDE.md` "Hub — current state (v0.7.x)" is ~23 minor versions stale (live 0.30.1).
|
||||
- `felhom-controller/CLAUDE.md` layout lists ~7 of ~30 internal packages; bare-metal deploy steps
|
||||
superseded (already flagged in-file).
|
||||
- app-catalog `README.md`: phantom `filebrowser/` template, missing `recipe-importer/`, field-type
|
||||
table omits `subdomain`/`secret_input`, smtp_mapping list undercounts (2 listed, 6 real).
|
||||
## 5. Commands/facts re-verified against live infra (stale findings)
|
||||
|
||||
**Duplication highlights (full lists in each REUSE.md §6):** controller atomic-write ×4 (+1 inline,
|
||||
two with deliberate non-atomic bind-mount fallback), dir-size ×6, JSON writers ×5/3 envelopes;
|
||||
agent lsblk parsers ×2, atomic JSON store ×3, run() helper ×4, plus the acknowledged
|
||||
antiRetarget↔WipeExecutor mirror; hub Resend sender ×2 + Hungarian formatter ×2 (frozen legacy
|
||||
path) and semver-compare ×2 with different failure semantics; agent dead config knobs
|
||||
`Binaries.MkfsExt4/MkfsXfs` (never executed — wrapper paths are hardcoded).
|
||||
- Guest 9201 deploy mechanism confirmed live (`/etc/felhom-controller-image` = 0.98.3 tag,
|
||||
`felhom-controller-bootstrap.service` enabled, container healthy) — the OLD controller CLAUDE.md
|
||||
still documented the pre-re-platform `/opt/docker` compose deploy for node 1: **stale, replaced**.
|
||||
- Agent service confirmed on felhom-pve: `User=felhom-agent`,
|
||||
`ExecStart=/usr/local/bin/felhom-agent --config /etc/felhom-agent/agent.json`, live 0.61.0 — the
|
||||
OLD agent CLAUDE.md claimed v0.31.0 current: **stale, removed**.
|
||||
- Build dirs on 180 confirmed: `~/build/{felhom-agent,felhom-controller,felhom-hub}` + `~/git`
|
||||
clones; Go present.
|
||||
- Design tokens confirmed byte-identical across website site.css / hub style.css / controller
|
||||
style.css `:root` blocks (values inlined in the ui-design skill).
|
||||
- Hub GitOps flow re-verified by executing it (this deploy). Red-proof citation
|
||||
(`dispatcher_test.go` ~L27–49) verified in source.
|
||||
- Website displays no hub version anywhere (checked) — no website change needed.
|
||||
|
||||
## 7. Maintenance hooks now active
|
||||
## 6. Observations (noticed, NOT acted on)
|
||||
|
||||
Every repo's CLAUDE.md: "See `REUSE.md` before writing new code" + "update REUSE.md in the SAME
|
||||
commit that adds/changes/deprecates a shared helper or pattern". Staleness gate:
|
||||
`python scripts/reuse_refs_check.py <repo-root> [...]` (run from felhom.eu). Reviewer-side: symbols
|
||||
spot-verified at file:line (line numbers in the maps are landmarks only).
|
||||
- The hub severity badge classes predating this task (`.severity-error/-warning/-info`) use legacy
|
||||
raw hexes, not the v2 `--warn/--crit` tokens — left alone per the no-restyle rule; a token
|
||||
migration is a candidate D-series follow-up.
|
||||
- The hub login page appears unstyled relative to the v2 design system (default-blue button, white
|
||||
background) — pre-existing, out of scope.
|
||||
- `manifests/felhom.secret.yaml` inline credentials (from the REUSE report) remain an open operator
|
||||
follow-up.
|
||||
- The controller currently emits no `critical`-severity events; the fix future-proofs the channel
|
||||
(hub-internal `host_disk_critical`/`storage_fill_critical` already flow at critical severity —
|
||||
they bypass ingest).
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Felhom Hub — Changelog
|
||||
|
||||
## docs — Felhom skills introduced + CLAUDE.md refresh (2026-07-03)
|
||||
|
||||
Repo-level docs work alongside v0.31.0 (no hub code in this entry):
|
||||
- **`skills/`** (new, repo root): three versioned Claude Code skills — `felhom-build-deploy`
|
||||
(per-artifact runbooks, all commands verified live), `felhom-ui-design` (v2 tokens + gates),
|
||||
`felhom-testing` (non-hollow doctrine + red-proof procedure). Installed to `~/.claude/skills/`
|
||||
via `scripts/install_skills.py` (junction mode verified).
|
||||
- **CLAUDE.md refresh:** the "Hub — current state (v0.7.x)" narrative (stale by ~23 versions)
|
||||
replaced with a version-free architecture section; standing rule adopted — CLAUDE.md carries NO
|
||||
version-pinned state (that lives in CONTEXT/CHANGELOG/REUSE); skills pointers added. Same rule
|
||||
applied to the sibling repos' CLAUDE.md in their own commits.
|
||||
|
||||
## v0.31.0 — critical severity accepted at event ingest + visible in UI (2026-07-03)
|
||||
|
||||
Fixes the gotcha the REUSE sweep surfaced: `handleEvent` coerced any severity outside
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Felhom scripts — Changelog
|
||||
|
||||
## install_skills.py — new: Claude Code skills installer (2026-07-03)
|
||||
|
||||
Installs `skills/*/SKILL.md` (felhom-build-deploy, felhom-ui-design, felhom-testing) into
|
||||
`~/.claude/skills/` as Windows junctions (`mklink /J`) so repo edits are live immediately; falls
|
||||
back to a full copy if junction creation fails or isn't followed (copy mode prints a re-run
|
||||
reminder). Idempotent — re-runs detect a correct junction and leave it. Verified: junctions ARE
|
||||
followed by Claude Code skill discovery (fresh-session probe found all three).
|
||||
|
||||
## reuse_refs_check.py — new gate: REUSE.md citation checker (2026-07-03)
|
||||
|
||||
Staleness defense for the new per-repo `REUSE.md` reuse maps. Takes repo roots as argv, extracts
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Install the Felhom Claude Code skills from <repo>/skills/ into ~/.claude/skills/.
|
||||
|
||||
Usage: python scripts/install_skills.py (run from the felhom.eu repo root or anywhere)
|
||||
|
||||
Personal skills (~/.claude/skills/) apply across all projects — the right scope for the 4-repo
|
||||
E:\\git workspace. Preferred install is a Windows junction (mklink /J) so edits in the repo are live
|
||||
immediately; if junction creation fails or isn't followed, falls back to a full COPY — in copy mode
|
||||
you must RE-RUN this script after editing skills/. Idempotent: safe to re-run any time.
|
||||
"""
|
||||
import os, shutil, subprocess, sys
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SRC = os.path.join(REPO, "skills")
|
||||
DST = os.path.join(os.path.expanduser("~"), ".claude", "skills")
|
||||
|
||||
fails = 0
|
||||
copy_mode_used = False
|
||||
|
||||
|
||||
def is_working_junction(target, src):
|
||||
"""True if target resolves into src (junction/symlink already correct)."""
|
||||
try:
|
||||
return os.path.isfile(os.path.join(target, "SKILL.md")) and \
|
||||
os.path.samefile(os.path.join(target, "SKILL.md"), os.path.join(src, "SKILL.md"))
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def remove_existing(target):
|
||||
# a junction must be removed with rmdir semantics (never recurse INTO it), a copy with rmtree
|
||||
try:
|
||||
os.rmdir(target) # works for junctions and empty dirs
|
||||
except OSError:
|
||||
shutil.rmtree(target, ignore_errors=True)
|
||||
|
||||
|
||||
def install(name):
|
||||
global fails, copy_mode_used
|
||||
src = os.path.join(SRC, name)
|
||||
target = os.path.join(DST, name)
|
||||
if os.path.exists(target):
|
||||
if is_working_junction(target, src):
|
||||
print("OK %-22s junction (already installed, live-linked to repo)" % name)
|
||||
return
|
||||
remove_existing(target)
|
||||
# try junction first
|
||||
r = subprocess.run(["cmd", "/c", "mklink", "/J", target, src],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode == 0 and is_working_junction(target, src):
|
||||
print("OK %-22s junction -> %s" % (name, src))
|
||||
return
|
||||
# fall back to copy
|
||||
if os.path.exists(target):
|
||||
remove_existing(target)
|
||||
try:
|
||||
shutil.copytree(src, target)
|
||||
copy_mode_used = True
|
||||
print("OK %-22s COPY (junction failed: %s)" % (name, (r.stderr or r.stdout).strip() or "not followed"))
|
||||
except OSError as e:
|
||||
fails += 1
|
||||
print("FAIL %-22s %s" % (name, e))
|
||||
|
||||
|
||||
if not os.path.isdir(SRC):
|
||||
print("FAIL: no skills/ dir at %s" % SRC)
|
||||
sys.exit(2)
|
||||
os.makedirs(DST, exist_ok=True)
|
||||
names = sorted(d for d in os.listdir(SRC) if os.path.isfile(os.path.join(SRC, d, "SKILL.md")))
|
||||
if not names:
|
||||
print("FAIL: no skills found under %s" % SRC)
|
||||
sys.exit(2)
|
||||
for n in names:
|
||||
install(n)
|
||||
if copy_mode_used:
|
||||
print("\nNOTE: copy mode active for at least one skill — RE-RUN this script after editing skills/.")
|
||||
sys.exit(1 if fails else 0)
|
||||
@@ -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