diff --git a/CLAUDE.md b/CLAUDE.md index 0aaecf1..26f5bf3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,7 +3,9 @@ > 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. +> taxonomy, access): workspace-root `/mnt/5_hdd/felhom.eu/git/CLAUDE.md`; this file is +> `felhom.eu`-specific. A versioned copy of that workspace file lives at +> `documentation/runbooks/workspace-CLAUDE.md`. ## Project overview @@ -15,8 +17,9 @@ This repo contains: 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/`). + (`felhom-build-deploy`, `felhom-ui-design`, `felhom-testing`, `felhom-app-catalog`); + install/update with `python3 scripts/install_skills.py` (symlink into `~/.claude/skills/` on + POSIX, junction on Windows — either way repo edits are live immediately). 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. @@ -65,10 +68,11 @@ pushes; **you (Claude Code) implement**. A file being open in the editor is NOT 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`): 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. +- **Browser automation is NOT available** in the DooPlex environment (`claude-in-chrome` was a + Windows-workstation capability). Validate at the endpoint level — invoke the exact endpoint the UI + invokes — and via render tests; say which method was used. The hub UI is operator-password-gated + anyway, so render tests were already the method for UI changes. Strict end-to-end UI coverage is a + manual click-through by the operator. > **In every repository where you make a change, update both files in that repo:** > - **`CHANGELOG.md`** — cumulative log, newest on top (here: per-area `hub/`, `scripts/`, `website/`). @@ -93,14 +97,20 @@ pushes; **you (Claude Code) implement**. A file being open in the editor is NOT - **Config:** YAML via ConfigMap at `/etc/felhom-hub/hub.yaml`. Secrets via out-of-band `secretKeyRef` (never inline stringData — REUSE.md §3). -## SSH access +## Environment & access -Use the Windows OpenSSH binary (Git Bash's ssh fails silently): `SSH=/c/Windows/System32/OpenSSH/ssh.exe`. +Claude Code runs **on DooPlex (192.168.0.180, Debian 13, user `kisfenyo`)** — the k3s node itself. +Repos in `/mnt/5_hdd/felhom.eu/git/`, build dirs in `/mnt/5_hdd/felhom.eu/build/`. `kubectl` and the +image build/push are local commands; felhom-pve is one SSH hop. -| Host | IP | User | Role | -|------|----|------|------| -| 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 | +| Host | Access | Role | +|------|--------|------| +| **DooPlex (this host)** | local — `/mnt/5_hdd/felhom.eu/{git,build}/` | Build + push images, `sudo kubectl` | +| Demo Proxmox host | `ssh felhom-pve` (root@192.168.0.162) | pveum/pct + live Proxmox validation | + +> **Legacy: Windows workstation.** Until 2026-07-19 CC ran on Windows 11 with repos in `E:\git\`, +> and every remote command needed `SSH=/c/Windows/System32/OpenSSH/ssh.exe` (Git Bash's ssh fails +> silently). Retained in case that environment is revived. ## Build & deploy — Hub (GitOps via ArgoCD) @@ -118,14 +128,20 @@ truth is the **manifest**: happened — reconcile via the manifest, not the changelog. - Green gate before any hub commit: `go build ./... && go vet ./... && go test ./...` in `hub/`. -Steps: commit+push code → `./build.sh --push` on 180 (`/mnt/5_hdd/felhom.eu/build/felhom-hub` — all felhom build dirs/repos moved off the SSD to `/mnt/5_hdd/felhom.eu/` on 2026-07-18) → bump -`manifests/hub.yaml` tag + push → ArgoCD hard-refresh + sync (kubectl-patch method in the skill) → -verify Synced/Healthy + rollout + image + startup log. +> **Clean-tree gate before any build:** `git status --porcelain` must be empty and +> `git rev-parse HEAD` must equal `git rev-parse origin/main` in the repo being built. An unpushed +> change does not exist — never build a dirty or unpushed tree. The `git pull` in the build step +> stays (it is a no-op when you work in this tree, and load-bearing if anything was pushed from +> elsewhere). + +Steps: commit+push code → `cd /mnt/5_hdd/felhom.eu/build/felhom-hub && ./build.sh --push` +(local) → bump `manifests/hub.yaml` tag + push → ArgoCD hard-refresh + sync (kubectl-patch method in +the skill, now local `sudo kubectl`) → 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). **Run - `python scripts/site_gates.py` after ANY website change**; new pages go into its `PAGES` list. + `python3 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. diff --git a/documentation/runbooks/workspace-CLAUDE.md b/documentation/runbooks/workspace-CLAUDE.md new file mode 100644 index 0000000..37d7410 --- /dev/null +++ b/documentation/runbooks/workspace-CLAUDE.md @@ -0,0 +1,118 @@ + + +# CLAUDE.md — `/mnt/5_hdd/felhom.eu/git` workspace root (DooPlex) + +## What this workspace is + +`/mnt/5_hdd/felhom.eu/git` is a parent folder holding the felhom sibling repos. Most are one logical +product — **Felhom**, a managed home-server service for Hungarian households — spread across several +repos. (Any non-felhom repo is unrelated; ignore unless asked.) + +**Claude Code runs HERE, on DooPlex (192.168.0.180), as `kisfenyo`.** Builds are local commands; the +Proxmox host is one SSH hop (`ssh felhom-pve`). The Windows workstation is no longer the +orchestration point and its trees are stale — see "Legacy: Windows workstation" at the bottom. + +Run CC inside tmux so sessions survive SSH drops: **`tmux new -A -s cc`**. + +## This host is production infrastructure + +DooPlex runs Gitea, the container registry, k3s + Longhorn, PBS, and the hub. Treat it accordingly: + +- NEVER run `docker system prune`, `docker image prune -a`, or any global Docker cleanup here. +- NEVER touch k3s data dirs, Longhorn mounts, PBS datastores, or Gitea storage. Workspace is + `/mnt/5_hdd/felhom.eu/` — stay inside it plus `~/build` symlinks/dirs. +- Destructive disk/guest operations belong to felhom-pve via the agent — never on this host. +- Do not run Claude Code with permission prompts disabled on this host. +- Watch disk headroom before large builds: `df -h /mnt/5_hdd /` — abort if either is >90%. + +## The Felhom system (three-component model, Proxmox-based) + +- **Hub** — operator backend on k3s (`hub.felhom.eu`). Lives in `felhom.eu/hub/`. +- **Host agent** — one per Proxmox host, operator-tier, owns all Proxmox interaction. Repo `felhom-agent/`. +- **In-guest controller** — one per customer LXC, Docker-only. Repo `felhom-controller/`. + +Other felhom repos: `app-catalog-felhom.eu/` (app templates), `homelab-manifests/` (DooPlex k3s). + +**Authoritative design docs (read these before designing anything):** `felhom.eu/documentation/architecture/01..05-*.md`, `felhom.eu/documentation/proxmox-platform.md`, `felhom.eu/documentation/tests/phase{0,1-2,3,4}-findings.md`. + +## Per-repo guidance + +When you work in a repo, read its `CLAUDE.md` (it loads on-demand the moment you touch a file there): +- `felhom-agent/CLAUDE.md` — the Go host agent. +- `felhom.eu/CLAUDE.md` — hub + website + manifests + the architecture docs. +- `felhom-controller/CLAUDE.md` — the in-guest controller. + +## Skills + +Four Felhom skills exist (personal scope, `~/.claude/skills/`): **`felhom-build-deploy`** (all +build/deploy/publish runbooks), **`felhom-ui-design`** (design-system v2 tokens/rules/gates), +**`felhom-testing`** (non-hollow tests + red-proofs + seams), **`felhom-app-catalog`** (catalog +authoring workflow). Source of truth: `felhom.eu/skills/`; install/update with +`python3 felhom.eu/scripts/install_skills.py` (symlink — repo edits are live immediately). + +## Artifact taxonomy (READ THIS — it prevents the "what do I do?" stall) + +The planning/architecture assistant (in claude.ai, "project Claude") produces files with distinct roles. **A file being open in the editor is NOT an instruction. If no task is stated, ask.** + +- **`TASK.md` / `TASK-*.md`** — a spec for **you (Claude Code) to implement**. Implement it when it is placed as `TASK.md` at a repo root, or when explicitly told "implement ". Then push, update `CHANGELOG.md`, and write the repo's `REPORT.md`. +- **`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.) +- **Validation/review** — checking a push against a spec's criteria is **project Claude's** job, not yours, unless asked. + +## Shared conventions + +- **Push to `main` directly** — no feature branches. + +> **Clean-tree gate before any build:** `git status --porcelain` must be empty and +> `git rev-parse HEAD` must equal `git rev-parse origin/main` in the repo being built. An unpushed +> change does not exist — never build a dirty or unpushed tree. The `git pull` in the build step +> stays (it is a no-op when you work in this tree, and load-bearing if anything was pushed from +> elsewhere). + +> **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. +> +> **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. + +- **Versioning** is via build-time ldflags (`-X main.version`/`-X main.Version`); bump on meaningful changes + add a CHANGELOG entry. +- Code quality: double-check for bugs/edge cases; add debug logging; **ask rather than guess** when you'd otherwise need to invent input or output. + +## Live validation — no browser here + +**`claude-in-chrome` is NOT available on DooPlex.** The standard method is endpoint-level: invoke the +exact endpoint the UI invokes (no server logic is skipped, only rendering) and say which method was +used. Strict end-to-end UI coverage is a manual click-through by the operator. + +## Access + +Local (this host): repos `/mnt/5_hdd/felhom.eu/git/`, build dirs +`/mnt/5_hdd/felhom.eu/build/felhom-{controller,hub,agent}`, `sudo kubectl`, Go toolchain, Docker +build+push to `gitea.dooplex.hu/admin/`. + +| Host | Access | Use | +|---|---|---| +| **DooPlex (this host)** | local — Debian 13, `kisfenyo`, `/mnt/5_hdd/felhom.eu/` | build/push images, `sudo kubectl`, build+run the agent for tests | +| Demo Proxmox host `demo-felhom` | `ssh felhom-pve` (root@192.168.0.162, no sudo) | pveum/pct + live Proxmox validation | +| Demo guest 9201 | `ssh felhom-pve "pct exec 9201 -- ..."` | the live demo controller | +| felhotest (legacy) | `ssh -p 33022 kisfenyo@router.abonet.hu` | OLD /opt/docker compose mechanism | + +The demo Proxmox host key changes on reprovision (N100) → refresh with +`ssh-keygen -R 192.168.0.162` then connect with `-o StrictHostKeyChecking=accept-new` +(`ssh-keyscan` hangs — avoid it). + +## Legacy: Windows workstation + +Kept so the old environment can be revived; **not the current setup**. + +- Repos were in `E:\git\` (`/e/git/` in Git Bash); this file lived at `E:\git\CLAUDE.md`. +- **SSH binary had to be** `SSH=/c/Windows/System32/OpenSSH/ssh.exe` — Git Bash's `/usr/bin/ssh` + lacks access to the Windows SSH Agent and fails silently. Every remote command was + `$SSH kisfenyo@192.168.0.180 "..."`; details in `felhom-controller/docs/vscode-ssh-fix.md`. +- `pct exec` over SSH needed `export MSYS_NO_PATHCONV=1` (MSYS mangled `/`-paths). +- Agent deploy was a two-hop copy: build on 180 → `scp` to the Windows box (local path needed + `cygpath -w`) → `scp` on to felhom-pve. Beware CRLF when scp-ing config files through Windows. +- Skills were installed as Windows junctions (`mklink /J`) rather than POSIX symlinks. +- `claude-in-chrome` browser automation WAS available there (attaching only to sessions started + after the bridge connected). diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index 528ec69..8d24c0a 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,5 +1,21 @@ # Felhom scripts — Changelog +## install_skills.py — cross-platform (POSIX symlink / Windows junction) (2026-07-19) + +Claude Code now runs on DooPlex (Debian 13), where `mklink /J` does not exist — the script would +have fallen through to COPY mode on every run, silently breaking the "repo edits are live +immediately" property that makes `felhom.eu/skills/` the source of truth. + +- Link creation is now platform-dispatched behind `os.name == "nt"`: `os.symlink(..., + target_is_directory=True)` on POSIX, the existing `mklink /J` on Windows. Copy-mode fallback, + idempotency, and the re-run warning are unchanged. +- **`remove_existing()` had a real hazard on POSIX**: `os.rmdir()` fails on a symlink-to-directory, + which would have fallen through to `shutil.rmtree()` — and rmtree following a symlink into + `skills/` would have deleted the repo's own skill sources. It now unlinks symlinks explicitly + before any rmdir/rmtree path is reached. Existence checks use `os.path.lexists()` so a broken + link is seen and replaced rather than ignored. +- Human-facing output says "symlink" or "junction" per platform. + ## build-felhom-iso.sh v1.22.0 — the boot screen is ours, and it offers exactly one thing (R-38) (2026-07-19) **A boot menu is a product surface, and ours was Proxmox's.** Every ISO is now repacked after diff --git a/scripts/install_skills.py b/scripts/install_skills.py index 68c4ffb..015b74e 100644 --- a/scripts/install_skills.py +++ b/scripts/install_skills.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- """Install the Felhom Claude Code skills from /skills/ into ~/.claude/skills/. -Usage: python scripts/install_skills.py (run from the felhom.eu repo root or anywhere) +Usage: python3 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. +felhom workspace. Preferred install is a LINK so edits in the repo are live immediately: a POSIX +symlink on Linux/macOS (the DooPlex environment), a junction (mklink /J) on Windows. If the link +cannot be created 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 @@ -18,7 +19,11 @@ fails = 0 copy_mode_used = False -def is_working_junction(target, src): +IS_WINDOWS = os.name == "nt" +LINK_KIND = "junction" if IS_WINDOWS else "symlink" + + +def is_working_link(target, src): """True if target resolves into src (junction/symlink already correct).""" try: return os.path.isfile(os.path.join(target, "SKILL.md")) and \ @@ -28,35 +33,56 @@ def is_working_junction(target, src): def remove_existing(target): - # a junction must be removed with rmdir semantics (never recurse INTO it), a copy with rmtree + # a link must be removed WITHOUT recursing INTO it (that would delete the repo's skills); + # a real copied directory needs rmtree + if os.path.islink(target): + os.unlink(target) # POSIX symlink (also a Windows symlink-to-dir) + return try: - os.rmdir(target) # works for junctions and empty dirs + os.rmdir(target) # works for Windows junctions and empty dirs except OSError: shutil.rmtree(target, ignore_errors=True) +def make_link(target, src): + """Create the platform's live link. Returns (ok, detail-for-humans).""" + if IS_WINDOWS: + r = subprocess.run(["cmd", "/c", "mklink", "/J", target, src], + capture_output=True, text=True) + if r.returncode != 0: + return False, (r.stderr or r.stdout).strip() or "mklink failed" + return True, "" + try: + os.symlink(src, target, target_is_directory=True) + return True, "" + except OSError as e: + return False, str(e) + + 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) + if os.path.lexists(target): + if is_working_link(target, src): + print("OK %-22s %s (already installed, live-linked to repo)" % (name, LINK_KIND)) 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)) + ok, detail = make_link(target, src) + if ok and is_working_link(target, src): + print("OK %-22s %s -> %s" % (name, LINK_KIND, src)) return + if not ok: + reason = detail + else: + reason = "not followed" # fall back to copy - if os.path.exists(target): + if os.path.lexists(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")) + print("OK %-22s COPY (%s failed: %s)" % (name, LINK_KIND, reason)) except OSError as e: fails += 1 print("FAIL %-22s %s" % (name, e)) diff --git a/skills/felhom-app-catalog/SKILL.md b/skills/felhom-app-catalog/SKILL.md index 2f03b2f..7aad80a 100644 --- a/skills/felhom-app-catalog/SKILL.md +++ b/skills/felhom-app-catalog/SKILL.md @@ -36,9 +36,8 @@ The core rule. A guessed `wget` probe ENOENTs at runtime and the container flaps inspection). On the demo guest: ```bash -SSH=/c/Windows/System32/OpenSSH/ssh.exe; export MSYS_NO_PATHCONV=1 for t in wget curl node python3; do - echo "$t: $($SSH felhom-pve "pct exec 9201 -- docker run --rm --entrypoint sh : -c 'command -v $t'" 2>/dev/null | tr -d '\r')" + echo "$t: $(ssh felhom-pve "pct exec 9201 -- docker run --rm --entrypoint sh : -c 'command -v $t'" 2>/dev/null | tr -d '\r')" done ``` diff --git a/skills/felhom-build-deploy/SKILL.md b/skills/felhom-build-deploy/SKILL.md index d45eb95..0ee3093 100644 --- a/skills/felhom-build-deploy/SKILL.md +++ b/skills/felhom-build-deploy/SKILL.md @@ -5,24 +5,32 @@ description: Build, deploy, publish, or verify ANY Felhom artifact — felhom-co # Felhom build & deploy runbooks -All repos live in `E:\git\` (Git Bash: `/e/git/`). Trunk-based: commit+push to `main` first, always. +**Claude Code runs on DooPlex (192.168.0.180, Debian 13, `kisfenyo`) — builds are LOCAL commands.** +All repos live under `$FELHOM_ROOT/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.) -FELHOM_ROOT=/mnt/5_hdd/felhom.eu # build-server working root — ALL felhom repos/build/drill/iso live HERE - # (moved off the SSD 2026-07-18; expands locally into the "…" SSH strings below) +FELHOM_ROOT=/mnt/5_hdd/felhom.eu # working root — ALL felhom repos/build/drill/iso live HERE + # repos $FELHOM_ROOT/git/felhom-*, build dirs $FELHOM_ROOT/build/felhom-* ``` +> **Clean-tree gate before any build:** `git status --porcelain` must be empty and +> `git rev-parse HEAD` must equal `git rev-parse origin/main` in the repo being built. An unpushed +> change does not exist — never build a dirty or unpushed tree. The `git pull` in the build step +> stays (it is a no-op when you work in this tree, and load-bearing if anything was pushed from +> elsewhere). + | Host | Access | Role | |---|---|---| -| Build server (k3s) | `$SSH kisfenyo@192.168.0.180` | build+push images/binaries (`$FELHOM_ROOT/build/felhom-{controller,hub,agent}`, repos under `$FELHOM_ROOT/git/`), `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 | +| **DooPlex (this host, k3s)** | local | build+push images/binaries (`$FELHOM_ROOT/build/felhom-{controller,hub,agent}`, repos under `$FELHOM_ROOT/git/`), `sudo kubectl` | +| Demo Proxmox host | `ssh felhom-pve` (root@192.168.0.162) | agent deploy, `pct` into guests | +| Demo guest 9201 | via `ssh felhom-pve "pct exec 9201 -- bash -c '...'"` | the live controller | +| felhotest (legacy) | `ssh -p 33022 kisfenyo@router.abonet.hu` | OLD /opt/docker compose mechanism — not the 9201 flow | + +**No browser here:** `claude-in-chrome` is not available on DooPlex. Validate at the endpoint level +(invoke the exact endpoint the UI invokes) and say which method was used. Version bumps are ldflags-only (`-X main.version` / `-X main.Version`) — build scripts inject them; no source edit. @@ -32,13 +40,13 @@ Version bumps are ldflags-only (`-X main.version` / `-X main.Version`) — build 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 $FELHOM_ROOT/build/felhom-controller && git -C $FELHOM_ROOT/git/felhom-controller pull && ./build.sh --push" +# 1. commit+push the repo (clean-tree gate above) +# 2. build+push image, LOCAL (build.sh does NOT git-pull — the explicit pull is load-bearing) +cd $FELHOM_ROOT/build/felhom-controller && git -C $FELHOM_ROOT/git/felhom-controller pull && ./build.sh --push # 3. deploy in the guest -$SSH felhom-pve "pct exec 9201 -- bash -c 'docker pull gitea.dooplex.hu/admin/felhom-controller: && echo gitea.dooplex.hu/admin/felhom-controller: > /etc/felhom-controller-image && systemctl restart felhom-controller-bootstrap.service'" +ssh felhom-pve "pct exec 9201 -- bash -c 'docker pull gitea.dooplex.hu/admin/felhom-controller: && echo gitea.dooplex.hu/admin/felhom-controller: > /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}}'" +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`. @@ -49,19 +57,19 @@ Runs as the NON-ROOT `felhom-agent` user: `/usr/local/bin/felhom-agent --config (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 $FELHOM_ROOT/git/felhom-agent && git pull && go build -ldflags '-X main.version=' -o /tmp/felhom-agent- ./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- "$(cygpath -w /tmp/felhom-agent-)" -scp "$(cygpath -w /tmp/felhom-agent-)" felhom-pve:/tmp/ +# build LOCALLY (pull first!) +cd $FELHOM_ROOT/git/felhom-agent && git pull && go build -ldflags '-X main.version=' -o /tmp/felhom-agent- ./cmd/felhom-agent +# copy to the PVE host — ONE hop, no intermediate workstation +scp /tmp/felhom-agent- 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- /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" +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- /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. +patterns). After restart, check the journal for a clean `ReassertGuestBinds` and no capability-probe +degradations. (Legacy Windows workstation: the copy was two hops via the Windows box, needing +`cygpath -w` for the local scp path — and config files scp'd through Windows risked CRLF.) Publish to Gitea (so Day-0 self-install can fetch it): `scripts/publish-agent.sh ` with `REGISTRY_*` creds. The hub's Day-0 artifact manifest must then vouch the new version — that UI is @@ -74,21 +82,21 @@ operator-password-gated (CC cannot); flag it as an operator follow-up. (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 $FELHOM_ROOT/build/felhom-hub && ./build.sh --push" +# 1. commit+push code 2. build+push image (LOCAL) +cd $FELHOM_ROOT/build/felhom-hub && ./build.sh --push # 3. bump manifests/hub.yaml image tag → , 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\":{}}}}}'" +# 4. hard-refresh + sync (argocd CLI is not logged in — drive the Application CR) +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"}' +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" +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` +Push to `main` = deployed (git-sync sidecar, live in ~1–2 min). **Run `python3 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. diff --git a/skills/felhom-ui-design/SKILL.md b/skills/felhom-ui-design/SKILL.md index 494dcca..21cb273 100644 --- a/skills/felhom-ui-design/SKILL.md +++ b/skills/felhom-ui-design/SKILL.md @@ -34,8 +34,8 @@ Files: `website/assets/site.css`, `hub/internal/web/templates/style.css`, website sections are boxless (rules + spacing, not cards). - **Two-tone H1:** last word wrapped in `` (renders `--blue-bright`). H1 only — never H2+. - **Icons:** monochrome sprite (`icons.svg`, ``) / Lucide-style. **No emoji - anywhere** — enforced by gates; never hunt emoji with grep (Windows grep false-negatives multibyte; - Python gates only). + anywhere** — enforced by gates; never hunt emoji with grep (proven to false-negative multibyte on + the Windows workstation, and grep was never the sanctioned check regardless — 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` /