# Driving the guest-9202 controller API from DooPlex — the working recipe (2026-09-21) Controller v0.260.0 in LXC guest **9202 `demo-hp-scratch`** on host `demo-hp`. ## The one surprise that saves the most time **Guest 9202 is directly reachable from DooPlex on the home LAN at `192.168.0.114`.** You do NOT have to `ssh demo-hp` + `pct exec` to drive the API — that was only needed for `docker`/`pct`. Curling straight from DooPlex is what makes a 200 ms poll loop possible at all. Still mandatory: the **`Host: felhom.enkisfelhom.hu`** header (without it every route 404s with the public page) and **`-k`** (self-signed cert on traefik). Port 443. `127.0.0.1:8080` inside the guest is NOT open — traefik is the only door. ## 0. The password, file→file, never through stdout ```bash python3 /mnt/5_hdd/felhom.eu/git/felhom.eu/scripts/read_credential.py PASSWORD /tmp/.ctlpw chmod 600 /tmp/.ctlpw # expect 13 bytes; 15 means it is wearing its quotes ``` ## 1. Log in and scrape the session CSRF (both are needed for any POST) curl's cookie jar drops `felhom_session`, so dump the headers and grep it out by hand. The CSRF meta tag's closing quote must be stripped or you get a 65-char token that silently mismatches — check the length is exactly 64. ```bash S=/tmp/ctl # any scratch dir mkdir -p $S B="https://192.168.0.114" H='Host: felhom.enkisfelhom.hu' PW=$(cat /tmp/.ctlpw) curl -sk -D $S/hdr.txt -o /dev/null -H "$H" -X POST --data-urlencode "password=$PW" "$B/login" head -1 $S/hdr.txt # expect: HTTP/2 302 grep -oiE 'felhom_session=[A-Za-z0-9._-]+' $S/hdr.txt | head -1 > $S/sess.txt # ~79 chars curl -sk -L -H "$H" -H "Cookie: $(cat $S/sess.txt)" "$B/" -o $S/home.html grep -oE ' $S/csrf.txt echo "csrf len: $(wc -c < $S/csrf.txt)" # expect 65 = 64 + newline ``` ## 2. The call helper — `c.sh GET /api/stacks` / `c.sh POST /api/sync '{...}'` ```bash cat > $S/c.sh <<'EOF' #!/bin/bash S=/tmp/ctl B="https://192.168.0.114" H='Host: felhom.enkisfelhom.hu' M=$1; P=$2; D=$3 SESS=$(cat $S/sess.txt); CT=$(cat $S/csrf.txt) if [ "$M" = GET ]; then curl -sk -H "$H" -H "Cookie: $SESS" "$B$P" else curl -sk -H "$H" -H "Cookie: $SESS" -H "X-CSRF-Token: $CT" \ -H "Content-Type: application/json" -X "$M" ${D:+--data "$D"} "$B$P" fi EOF chmod +x $S/c.sh ``` Worked endpoints (all verified today): | call | meaning | |---|---| | `c.sh GET /api/stacks` | every stack; `app_config.pinned_images`, `app_config.installed_images`, `template_images`, `catalog_images`, `updating`, `update_phase` | | `c.sh GET /api/stacks/vikunja` | one stack, same shape — this is the 200 ms poll target | | `c.sh POST /api/stacks//deploy '{"values":{"DOMAIN":"enkisfelhom.hu","SUBDOMAIN":"tasks"}}'` | real deploy path, answers 202 | | `c.sh POST /api/stacks//update` | the Update button | | `c.sh POST /api/sync` | pull the catalog git clone | | `c.sh POST /api/stacks/rescan` | **always run this after a sync before reading any badge** (R-607) | | `c.sh GET /api/stacks//logs?lines=200` | app container log | `pinned_images` and `installed_images` live under **`app_config`**, not at the top level — that cost a few minutes. ## 3. Reading the customer's app page, both languages **The route is `/apps/`, not `/app/`.** This section said `/app/` until 2026-09-21 and every call it described 404s. `/` redirects to `/launcher`, and the app links live on `/stacks`. ```bash curl -sk -H "$H" -H "Cookie: $(cat $S/sess.txt)" "$B/apps/vikunja" # Hungarian curl -sk -H "$H" -H "Cookie: $(cat $S/sess.txt)" "$B/apps/vikunja?lang=en" # English ``` **The session dies whenever the controller restarts** — the store is in memory — so a long run must be able to log in again mid-flight rather than assuming one login lasts the night. ## 4. Shell into the guest (for docker / pct only) ```bash ssh -o StrictHostKeyChecking=accept-new demo-hp "pct exec 9202 -- bash -c ''" 2>/dev/null ``` `2>/dev/null` drops the perl locale warnings. For anything with awkward quoting, pipe a script: ```bash cat <<'EOF' | ssh -o StrictHostKeyChecking=accept-new demo-hp \ 'cat > /tmp/c.sh; pct push 9202 /tmp/c.sh /tmp/c.sh >/dev/null 2>&1; pct exec 9202 -- bash /tmp/c.sh' 2>/dev/null docker ps --format '{{.Names}}\t{{.Image}}' EOF ``` ## 5. Paths inside the running guest - controller data dir (journal, catalog cache): `/var/lib/docker/volumes/felhom-controller-data/_data/data/` - `update-journal.json` — present only while an update is in flight - `catalog-cache/` — a git clone; `git -C … log --oneline -1` tells you what the box actually has - stacks: `/opt/docker/stacks//docker-compose.yml` (the LIVE rendered file) and `app.yaml` ## 6. App front doors on 9202 (Host header per app, same IP) `tasks.` vikunja · `status.` uptime-kuma · `wishes.` wishlist · `dashboard.` glance — all `…enkisfelhom.hu` against `https://192.168.0.114`.