# felhom.eu — task reports > **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md). ## CC workflow migrated from the Windows workstation to DooPlex — 2026-07-19 Claude Code now runs natively on **DooPlex (192.168.0.180, Debian 13, `kisfenyo`)**, working directly in `/mnt/5_hdd/felhom.eu/git/`. Builds are local commands; felhom-pve is one `ssh` hop; `kubectl` is local. The Windows workstation can stay powered off. Docs/skills adapted in all four repos, non-versioned assets migrated, environment wired and verified end-to-end. **Not a code change** — no Go source, no `build.sh`, no ArgoCD/GitOps rule was touched. Only *where the commands are typed from*. --- ### 1. Part 0 inventory | Asset | Class | Disposition | |---|---|---| | Repo docs (`CLAUDE.md`, `CONTEXT.md`, `REUSE.md`, runbooks, `README.md`) ×4 repos | versioned | adapted in place, pushed (§2) | | `felhom.eu/skills/*` (4 skills — build-deploy, ui-design, testing, app-catalog) | versioned | adapted; installed on DooPlex as symlinks | | `felhom.eu/scripts/install_skills.py` | versioned | made cross-platform (POSIX symlink / Windows junction) | | `E:\git\CLAUDE.md` (workspace root, in no repo) | portable | ported → `/mnt/5_hdd/felhom.eu/git/CLAUDE.md` **and** versioned copy at `documentation/runbooks/workspace-CLAUDE.md` (verified byte-identical) | | `~/.claude/settings.json` | portable | adapted → DooPlex (§5) | | `E:\git\.claude\settings.local.json` (workspace-scoped) | portable | **folded into** the user settings; every entry was a Windows `$SSH`/path one-off with no Linux meaning | | **Project memory — 119 files, 672 KB** (`~/.claude/projects/E--git/memory/`) | portable | **migrated** → `/mnt/5_hdd/felhom.eu/git/.claude-memory/`, surfaced via `~/.claude/projects/-mnt-5-hdd-felhom-eu-git/memory` symlink. *Not anticipated by the spec* — flagged per its "migrate by the same rules" clause | | `~/.claude/skills/*` (Windows junctions) | versioned source | recreated on DooPlex as POSIX symlinks by the fixed installer | | Per-repo `.claude/settings.local.json` | — | **none exist** (checked all repos) | | `~/.claude/CLAUDE.md` | — | **does not exist** on the Windows side; nothing to merge | | Session history, `~/.claude.json`, `history.jsonl`, `shell-snapshots/`, `file-history/` | not portable | path-keyed / machine-local — **not copied**, accepted loss | | Stray workspace files (`felhom-agent-0.2x.0.bin` ×4, `felhom-opsign.exe`, `longhorn-faulted-diagnosis-*.md`, `prompt_template.txt`) | not portable | build leftovers/scratch — **not copied**; left on Windows | | Non-felhom repos (`jarr`, `recipe-importer`, `revfulop-calendar`, `sparkyfitness-export`, `misc-scripts`) | out of scope | untouched | ### 2. Commits pushed Windows batch (the last Windows pushes): | Repo | Commit | Content | |---|---|---| | felhom-controller | `ac7323dc9a` | docs: migrate workflow to DooPlex-local execution | | felhom-agent | `d81fddc7b1` | docs: migrate workflow to DooPlex-local execution | | felhom.eu | `ee48a48288` | docs: migrate workflow to DooPlex-local execution | | felhom-controller | `29eda5d86e` | docs(runbook): e2e live-drive commands are DooPlex-local | | felhom.eu | `29b2b8ad7c` | docs: DooPlex-local commands in PROMPT-TEMPLATE + manual-build runbook | | app-catalog-felhom.eu | `34d50a33ac` | docs: migrate workflow to DooPlex-local execution | Pushed **from DooPlex** (the new-home proof): | Repo | Commit | Content | |---|---|---| | felhom-controller | `2fcae041ae` | chore: mark `build-samba-image.sh` executable (mode-only) | | felhom.eu | `a868e3585a` | docs: DooPlex is the canonical CC environment (CONTEXT.md) + iso scripts executable | | felhom.eu | `fa8c0d5972` | docs(workspace): record the migrated project-memory location | **Final HEADs, all four trees clean on DooPlex:** felhom-controller `29eda5d86e`, felhom-agent `d81fddc7b1`, felhom.eu `29b2b8ad7c`, app-catalog-felhom.eu `34d50a33ac`. ### 3. Grep-hit counts (env-specific references in live docs) Scope: `*.md` + `*.py`, excluding `CHANGELOG.md`, `REPORT*.md`, and the historical `documentation/{audits,tests,pilot}/` (history is history). | Repo | Before | After | Remaining — all intentional | |---|---|---|---| | felhom-controller | 47 | 36 | `CLAUDE.md` 4 + `RUNBOOK-e2e-live-drive.md` 2 = the new "Legacy: Windows workstation" notes; `docs/vscode-ssh-fix.md` 27 = the legacy doc, now carrying a LEGACY banner; **`TASK.md` 3 = see §6** | | felhom-agent | 4 | 4 | `CLAUDE.md` 3 = new legacy note; `PLAN.md` 1 = historical plan note | | felhom.eu | 27 | 20 | `CLAUDE.md` 3 + `skills/felhom-build-deploy` 2 + `workspace-CLAUDE.md` 8 = legacy sections; `CONTEXT.md` 2 = the migration note itself; `install_skills.py` 3 = the deliberate Windows branch; `architecture/04` 1 (`sk-ssh-ed25519@openssh.com`) + `break-glass.md` 1 (`/run/sshd`) = **protocol/package names, never environment** | | app-catalog-felhom.eu | 1 | 0 | — | **No unadapted live instruction remains.** The one deliberate exception is `felhom-controller/TASK.md` (§6). Genuinely-platform mentions were left alone by design: `_linux.go`/`_other.go` dev stubs (`REUSE.md`), `\\FELHOM` shares in Windows Explorer (`controller/README.md`), the `openssh-client` package in the restic image, and the Windows-grep-multibyte rationale in the gate scripts. ### 4. Verification (all from DooPlex, verbatim) ``` === felhom-controller (controller/) === BUILD+VET OK === felhom-agent (root) === BUILD+VET OK === felhom.eu (hub/) === BUILD+VET OK === kubectl (local) === NAME READY STATUS RESTARTS AGE contact-mailer-5bb869b85b-pqtjt 1/1 Running 0 20d felhom-webpage-69cdbfc47-jm7lv 2/2 Running 2 (9d ago) 18d filebrowser-59ff87cd88-662kz 1/1 Running 0 38d hub-6d859c4dff-r5vrx 1/1 Running 0 14h umami-7cd7f95cd8-89wrn 1/1 Running 1 (38d ago) 42d umami-db-5fd98f59c5-xhwfr 1/1 Running 0 38d === pve hop === pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve) === guest 9201 (through the pve hop) === gitea.dooplex.hu/admin/felhom-controller:0.147.3 Up 2 hours (healthy) === claude + skills === 2.1.205 (Claude Code) felhom-app-catalog felhom-build-deploy felhom-testing felhom-ui-design === tmux === tmux 3.5a === disk headroom === /dev/sda1 9.1T 2.1T 6.6T 24% /mnt/5_hdd /dev/sdb1 445G 284G 139G 68% / === repo HEADs / cleanliness === felhom-controller 29eda5d86e dirty=[] felhom-agent d81fddc7b1 dirty=[] felhom.eu 29b2b8ad7c dirty=[] app-catalog-felhom.eu 34d50a33ac dirty=[] === live workspace CLAUDE.md vs versioned copy === IDENTICAL ``` `install_skills.py` was exercised on DooPlex twice: first run created 4 symlinks, second run reported "already installed" for all 4 with the repo's `skills/*/SKILL.md` intact and the tree clean — confirming idempotency **and** that the rmtree hazard (§5) does not fire. **Explicitly NOT validated:** a full image **build → push → deploy** cycle from DooPlex. Nothing here required a build, and a docs-only change must not push an image. The first real task run on DooPlex should **treat its build step as a validation step — watch it, do not assume.** The pieces it depends on are individually proven (local Go toolchain, registry creds already on the host, `ssh felhom-pve` → `pct exec 9201`), but the composed path has not run end-to-end. ### 5. Notable fixes made along the way - **`install_skills.py` had a latent data-loss bug on POSIX.** `remove_existing()` used `os.rmdir()`, which fails on a symlink-to-directory and fell through to `shutil.rmtree()` — and rmtree following the symlink would have **deleted the repo's own `skills/` sources**. Now symlinks are unlinked explicitly before any rmdir/rmtree path is reached, and existence checks use `os.path.lexists()` so a broken link is replaced rather than ignored. (Windows never hit this: junctions satisfy `os.rmdir`.) - **`skipDangerousModePermissionPrompt: true` was deliberately DROPPED** from the DooPlex settings — it directly contradicts the new production-host guardrail ("do not run CC with permission prompts disabled on this host"). Everything else portable was kept: `model: opus[1m]`, `effortLevel: high`, `tui: fullscreen`, `agentPushNotifEnabled`, the three plugins. - **Permission allowlist rebuilt** (30 rules): Windows one-offs dropped (`$SSH …`, `cygpath`, `SCP=/c/Windows/…`, `/e/git/…` paths, `dir:*`, `Read(//c/Users/…)`), Linux equivalents added (`sudo kubectl:*`, `ssh felhom-pve:*`, `python3:*`, `go test:*`, `df:*`). **Also dropped:** the recipe-site WebFetch domains (`streetkitchen.hu`, `sobors.hu`, `kiskegyed.hu`, `gastrohobbi.hu`), the jarr/pnpm entries, and assorted malformed rules (`Bash(1:*)`, `Bash(done)`, `Bash(settings")`) — all belonged to non-felhom projects that are not migrating. - **`PROMPT-TEMPLATE.md` §13 contradicted the GitOps rule**: it instructed `kubectl set image` for hub deploys, which the hub docs explicitly forbid (auto-sync is OFF; imperative changes are reverted on the next sync). Corrected to the manifest-bump + deliberate-sync method while the block was being de-SSH'd. - **Demo PVE host key had changed** on DooPlex (the known N100-reprovision gotcha). The new key was **verified identical to the one the Windows workstation already trusted** (`SHA256:bcSDbVzxoLQhOURUDzRGf0zJheU3wyXR4EmQ4NEPuTo`) before being accepted — not blindly trust-on-first-use. `ssh-keyscan` was avoided (it hangs, per prior findings). ### 6. Pending — Viktor 1. **`claude` login (OAuth) — the only blocking step.** Everything else is ready. On DooPlex: ``` ssh kisfenyo@192.168.0.180 tmux new -A -s cc cd /mnt/5_hdd/felhom.eu/git && claude ``` (`~/.local/bin` was appended to `~/.bashrc`; a fresh login shell picks it up.) 2. **Push token: NOT needed.** DooPlex already had a working `credential.helper=store` + `~/.git-credentials`; four pushes from DooPlex succeeded during this task. Nothing to issue, nothing written anywhere — the credential stays out-of-band in `~/.git-credentials`. 3. **`felhom-controller/TASK.md` is a stale spec** — it targets **v0.28.0** and the retired `/opt/docker` compose deploy mechanism, and per the artifact taxonomy a `TASK.md` at a repo root reads as "implement this". It was left untouched deliberately (not mine to delete). Recommend deleting or moving it to `documentation/`; it is the last live-looking Windows-command block in the four repos. 4. **Remote-access preflight**, before relying on this from outside the LAN: verify the SSH path to DooPlex from that location. Gitea's **web UI is geo-fenced (HU/DE/US)** — CC on DooPlex is unaffected (it pushes over the LAN), but browser access to Gitea from elsewhere is not. 5. **Windows side is untouched** and remains a stale fallback. Nothing was deleted there. ### 7. Observations (out of scope) - **R-43/R-44 landed mid-task.** `felhom-controller` had ~721 lines of uncommitted, build-clean offsite-reconstitute work when this task started. Rather than commit unreleased data-restore code under a docs commit, it was surfaced and left alone; you shipped it as **v0.148.0 (`062357f`)** during the run. The Part 3 gate then compiled that code on DooPlex — `go build ./... && go vet ./...` **clean** — so v0.148.0 has a green toolchain gate on the new host as a side effect. - **Windows `grep -i` combined with `-F -f ` silently returns zero matches** in this Git Bash build — it produced a false "0 remaining references" during verification and was only caught by cross-checking on Linux. This is the same false-negative class the repos already warn about for multibyte emoji; the counts in §3 are all from Linux grep. One more reason the Python gates exist. - **Four DooPlex script files were mode-only dirty** (`100644 → 100755`) — the exec bit set when they were run locally, which Windows commits cannot carry. Committed as mode-only changes so the new clean-tree gate starts from a genuinely clean state; expect the same for any script Windows added. - **The skill listing says three skills; there are four.** `felhom-app-catalog` was missing from the workspace CLAUDE.md's Skills section — corrected in the ported file. - `~/.claude/projects/` on DooPlex already contained a `-mnt-5-hdd-felhom-eu` entry, which is what confirmed the path-key mangling scheme (`_` and `.` both → `-`) and let the memory symlink be placed on the correct key rather than guessed.