fix (helper): verplaats socket naar dedicated submap /run/podman-mvp/

Vervangt file bind-mount door directory mount om stale inode probleem
op te lossen: bij file bind-mounts bindt Podman de inode op run-tijd;
als podman-helper stopt en de socket verwijdert, wijst de container
nog steeds naar de verwijderde inode. Een directory mount lost altijd
op naar de huidige mapinhoud inclusief nieuwe inodes.

Wijzigingen:
- podman-helper.py: SOCKET_PATH → XDG_RUNTIME_DIR/podman-mvp/podman-helper.sock
- common.py: HELPER_SOCKET → /run/podman-mvp/podman-helper.sock
- CLAUDE.md: run-commando gebruikt -v /run/user/1000/podman-mvp:/run/podman-mvp

Deploy: kopieer podman-helper.py naar host, daemon-reload, restart helper,
rebuild backend image, herstart container met nieuwe mount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 09:50:31 +01:00
parent a05d79ae2c
commit 5196e7840f
3 changed files with 112 additions and 2 deletions
+110
View File
@@ -0,0 +1,110 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
podman-mvp is a Portainer-like web dashboard for managing rootless user-session Podman containers. It runs as a two-container Podman pod: a FastAPI backend (`mvp-backend`) that talks to Podman over a Unix socket, and a static Apache frontend (`mvp-webui`) that reverse-proxies `/api/` to the backend.
## Architecture
### Backend — FastAPI modular monolith (`control/`)
| File | Role |
|---|---|
| `app.py` | Bootstrap only — creates FastAPI app, wires routers, no feature logic |
| `common.py` | Shared helpers: Podman HTTP, systemctl, utilities |
| `app_system.py` | System/platform router: `/health`, `/daemon-reload`, systemctl unit actions |
| `app_containers.py` | Containers router: dashboard, inspect, logs, stats stream, exec sessions |
| `app_pods.py` | Pods router: dashboard, pod actions |
| `app_networks.py` | Networks router |
| `app_images.py` | Images router |
| `app_files.py` | Files/workloads router: tree, read, save |
Backend communicates with Podman through the Unix socket at `/run/user/1000/podman/podman.sock` using `requests_unixsocket`. Podman API base: `http+unix://%2Frun%2Frun%2Fuser%2F1000%2Fpodman%2Fpodman.sock/v5.4.2`.
### Frontend — Static Apache (`webui/`)
- `webui/html/index.html` — single-page app shell
- `webui/html/assets/js/tabs/` — per-tab JavaScript modules (containers, networks, images, files)
- `webui/conf/httpd.conf` — Apache config, proxies `/api/``http://127.0.0.1:8000/api/`
## Build & Deploy
```bash
# Build backend image
podman build -t mvp-control:latest control/
# Create pod
podman pod create --name mvp-pod -p 8080:8000 -p 8081:8081 --userns=keep-id
# Run backend
podman run -d --pod mvp-pod --name mvp-backend \
--ipc=host --pid=host \
-e XDG_RUNTIME_DIR=/run/user/1000 \
-e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
-v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:rw \
-v /run/user/1000/podman-mvp:/run/podman-mvp \
-v /home/kodi/.config/containers:/app/workloads:rw \
mvp-control:latest
# Run frontend
podman run -d --pod mvp-pod --name mvp-webui \
-v $HOME/.config/podman-mvp/webui/html:/usr/local/apache2/htdocs:ro \
-v $HOME/.config/podman-mvp/webui/conf/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro \
docker.io/library/httpd:2.4
```
## Verification Commands
```bash
# Syntax check all backend modules
python3 -m py_compile control/app.py control/common.py control/app_system.py \
control/app_containers.py control/app_pods.py control/app_networks.py \
control/app_files.py control/app_images.py
# Smoke test key endpoints (all via proxy on :8081)
curl -fsS http://127.0.0.1:8081/api/health | jq
curl -fsS http://127.0.0.1:8081/api/containers-dashboard >/dev/null && echo OK
curl -fsS http://127.0.0.1:8081/api/pods-dashboard >/dev/null && echo OK
curl -fsS http://127.0.0.1:8081/api/files/tree >/dev/null && echo OK
curl -fsS http://127.0.0.1:8081/api/networks/meta | jq
```
All test/verification URLs must target `127.0.0.1:8081` (the proxy), not port 8000 directly.
## Hard Rules
### Module placement
- `app.py` is bootstrap-only — no endpoints, no feature logic, no Podman/systemctl calls.
- New system/platform endpoints → `app_system.py`.
- New domain feature endpoints → the corresponding `app_<domain>.py`.
- Shared helpers → `common.py`, never duplicated into routers.
- `allow_list` / `allowed_units.txt` has been removed and must NOT be reintroduced.
- `app_system.py` broad wildcard routes (`/{action}/{unit}`) must be defined **last**.
### API contract (`contracts/API_GOLDEN.md`)
- Never remove or rename existing JSON response keys.
- Never change existing key data types.
- Extend via new optional fields or new endpoints only.
- UI-critical endpoints requiring pre-approval before any change: `/containers-dashboard`, `/pods-dashboard`, `/images`, `/networks/meta`.
### Security
- No `shell=True` in subprocess calls.
- All subprocess commands must be explicit lists.
### Infrastructure (propose before changing)
- Pod name, port mappings, `userns=keep-id`.
- DBus/XDG_RUNTIME_DIR mounts, Podman socket path, host PID/IPC namespaces.
- `control/Containerfile`, `webui/conf/httpd.conf`.
## Change Workflow
For non-trivial changes, follow PR_RULES.md:
1. Analyse existing behaviour with curl.
2. Propose minimal plan identifying affected files.
3. Confirm API contract safety.
4. Provide curl validation commands showing expected output change.
5. Implement after agreement.
Minimize diff size. Do not reformat unrelated code. No large rewrites or hidden refactors.
+1 -1
View File
@@ -4,7 +4,7 @@ import subprocess
from fastapi import HTTPException from fastapi import HTTPException
HELPER_SOCKET = "/run/podman-helper.sock" HELPER_SOCKET = "/run/podman-mvp/podman-helper.sock"
def _helper_call(action: str, unit: str) -> tuple[int, str]: def _helper_call(action: str, unit: str) -> tuple[int, str]:
+1 -1
View File
@@ -27,7 +27,7 @@ import sys
# ── Configuratie ───────────────────────────────────────────────────────────── # ── Configuratie ─────────────────────────────────────────────────────────────
SOCKET_PATH = os.getenv( SOCKET_PATH = os.getenv(
"HELPER_SOCKET", "HELPER_SOCKET",
os.path.join(os.getenv("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}"), "podman-helper.sock") os.path.join(os.getenv("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}"), "podman-mvp", "podman-helper.sock")
) )
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
TIMEOUT = 30 # seconden maximaal per systemctl aanroep TIMEOUT = 30 # seconden maximaal per systemctl aanroep