docs: migrate workflow to DooPlex-local execution
This commit is contained in:
@@ -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 <VER> --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 <VER> --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.
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<!-- VERSIONED COPY. The LIVE file is /mnt/5_hdd/felhom.eu/git/CLAUDE.md on DooPlex (it is not
|
||||
inside any repo, so it is mirrored here to be versioned and restorable). Keep the two in
|
||||
sync when either changes. -->
|
||||
|
||||
# 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 <file>". 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/<repo>`, 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).
|
||||
@@ -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
|
||||
|
||||
+43
-17
@@ -1,12 +1,13 @@
|
||||
# -*- 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)
|
||||
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))
|
||||
|
||||
@@ -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 <image>:<tag> -c 'command -v $t'" 2>/dev/null | tr -d '\r')"
|
||||
echo "$t: $(ssh felhom-pve "pct exec 9201 -- docker run --rm --entrypoint sh <image>:<tag> -c 'command -v $t'" 2>/dev/null | tr -d '\r')"
|
||||
done
|
||||
```
|
||||
|
||||
|
||||
@@ -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 <VER> --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 <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'"
|
||||
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}}'"
|
||||
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=<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/
|
||||
# build LOCALLY (pull first!)
|
||||
cd $FELHOM_ROOT/git/felhom-agent && git pull && go build -ldflags '-X main.version=<VER>' -o /tmp/felhom-agent-<VER> ./cmd/felhom-agent
|
||||
# copy to the PVE host — ONE hop, no intermediate workstation
|
||||
scp /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"
|
||||
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.
|
||||
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 <ver> <binary>` 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 <VER> --push"
|
||||
# 1. commit+push code 2. build+push image (LOCAL)
|
||||
cd $FELHOM_ROOT/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\":{}}}}}'"
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -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 `<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).
|
||||
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` /
|
||||
|
||||
Reference in New Issue
Block a user