feat(api): Codex: add /health endpoint with podman + systemd checks

This commit is contained in:
kodi
2026-02-25 13:16:53 +01:00
parent ebb6d755a0
commit b89a31a068
7 changed files with 536 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
# AGENTS.md — podman-mvp (WebUI + API)
## Goal
Primary goal: extend functionality and evolve the platform.
Feature development is the default workflow.
Refactoring is allowed when:
- it improves maintainability, OR
- it is required to implement a feature.
Refactoring must always:
- be proposed first,
- remain backward compatible,
- not change existing behaviour or API contracts without agreement.
---
## Repository structure
Backend (FastAPI)
- control/app.py (main API)
- control/app_images.py
WebUI (static Apache)
- webui/html/index.html
- webui/html/assets/js/tabs/
- webui/conf/httpd.conf
---
## Runtime architecture (IMPORTANT)
Application runs inside a Podman pod.
Pod created with:
podman pod create \
--name mvp-pod \
-p 8080:8000 \
-p 8081:8081 \
--userns=keep-id
### Backend container
Runs FastAPI control API with Podman access.
Created with:
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:/run/user/1000:rw \
-v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:rw \
-v /home/kodi/.config/containers:/app/workloads:rw \
mvp-control:latest
Important notes:
- Backend communicates with Podman through unix socket.
- User-session Podman is used (not root).
- DBus access is required.
- Host PID/IPC namespaces are intentional.
Do NOT change these assumptions without proposal.
---
### WebUI container
Static Apache 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
Frontend is static JS calling API through proxy.
---
## Access
WebUI:
http://127.0.0.1:8081/
API (via proxy):
http://127.0.0.1:8081/api/
---
## Testing workflow (REQUIRED)
Always validate changes using curl.
Example:
curl -s http://127.0.0.1:8081/api/...
Before proposing implementation:
1. Analyse existing endpoints.
2. Confirm available data using curl tests.
3. Propose minimal change.
4. Provide verification curl commands.
---
## Contract rules (HARD)
- Never break existing API responses.
- Never rename or remove JSON keys.
- Maintain backward compatibility.
New functionality must be added via:
- new endpoints, OR
- optional response fields.
Security rules:
- No shell=True
- subprocess must be explicit and safe
- Respect allowed_units.txt
- Never assume systemd states.
---
## Change policy
Preferred workflow:
1. Analyse existing behaviour.
2. Propose small implementation plan.
3. Identify affected files.
4. Provide curl validation tests.
5. Implement after agreement.
Avoid:
- large rewrites
- structural changes without need
- hidden refactors.
All significant changes must follow PR_RULES.md workflow.
---
## UI direction
Target style:
Portainer-like dashboard UI.
Guidelines:
- tables and overview panels
- container status badges
- row-level actions
- minimalistic professional layout
Do NOT introduce large frontend frameworks without agreement.
---
## Coding style
Follow existing structure and conventions of each file.
Do not reformat unrelated code.
Minimize diff size whenever possible.
## Safety boundaries
Follow SAFE_FILES.md before modifying infrastructure or core files.
+10
View File
@@ -0,0 +1,10 @@
# Core runtime / infra: altijd extra voorzichtig
^control/Dockerfile$ @kodi
^webui/conf/httpd\.conf$ @kodi
# Core API files
^control/app\.py$ @kodi
^control/app_images\.py$ @kodi
# Frontend entry
^webui/html/index\.html$ @kodi
+51
View File
@@ -0,0 +1,51 @@
# Change / PR Rules — podman-mvp
All non-trivial changes must follow this workflow.
## Step 1 — Scope
Describe:
- What feature is added or improved
- Which files are touched
## Step 2 — Contract safety check
Must remain TRUE:
- Existing API responses unchanged
- No JSON keys removed or renamed
- Backward compatibility maintained
- allowed_units.txt respected
If not certain → STOP and propose first.
## Step 3 — Runtime safety
Do NOT change without agreement:
- Pod structure
- Podman socket mounts
- DBus configuration
- host PID/IPC usage
## Step 4 — Verification (required)
Provide curl validation commands.
Example:
curl -s http://127.0.0.1:8081/api/...
Explain what should change in output.
## Step 5 — Refactoring
Allowed only when:
- required for feature OR
- clearly improves maintainability
Refactor must:
- keep behaviour identical
- minimize diff size
- be proposed first.
+87
View File
@@ -0,0 +1,87 @@
# SAFE FILES — podman-mvp
These files and runtime assumptions are considered infrastructure-critical.
Changes are NOT forbidden, but must ALWAYS be proposed first
and explicitly approved before implementation.
---
## Runtime architecture (critical)
Do not change without agreement:
- Pod name: mvp-pod
- Port mappings:
- 8080 → backend
- 8081 → webui proxy
- userns=keep-id
Backend runtime assumptions:
- DBUS_SESSION_BUS_ADDRESS usage
- XDG_RUNTIME_DIR mounts
- Podman unix socket access
- /run/user/1000 mounts
- host PID namespace
- host IPC namespace
Reason:
Backend communicates with user-session Podman and systemd.
---
## Infrastructure sensitive files
High risk files:
control/Dockerfile
webui/conf/httpd.conf
Changes must be proposed first.
---
## Core API stability
Files requiring caution:
control/app.py
control/app_images.py
Rules:
- Never rewrite structure without agreement.
- Extend endpoints instead of replacing logic.
---
## Frontend stability
Files:
webui/html/index.html
Avoid:
- framework migrations
- large UI rewrites
Prefer incremental improvements.
---
## Allowed improvements
Safe changes include:
- new API endpoints
- optional JSON response fields
- new UI tabs
- bug fixes
- performance improvements
---
## Goal
System stability has priority over architectural perfection.
Prefer minimal and predictable changes.
+181
View File
@@ -0,0 +1,181 @@
# API_GOLDEN.md — podman-mvp
Purpose:
Freeze existing API response contracts used by the WebUI.
Existing response structures MUST remain backward compatible.
Rules:
- Existing JSON keys MUST NOT be removed.
- Existing JSON keys MUST NOT be renamed.
- Data types of listed keys MUST NOT change.
- New optional fields are allowed.
- New endpoints are allowed.
- Podman passthrough responses must remain raw Podman responses.
API accessed via proxy:
http://127.0.0.1:8081/api
==================================================
GET /api/containers-dashboard
==================================================
Curl:
curl -s http://127.0.0.1:8081/api/containers-dashboard
Response type:
Array of container objects.
Golden keys per item:
- Names
- Image
- State
- Status
- Ports
- PodName
- _dashboard_source
- _dashboard_published_ports
- _dashboard_unit
- _dashboard_def_path
Golden example:
[
{
"Names": ["mvp-webui"],
"Image": "docker.io/library/httpd:2.4",
"State": "running",
"Status": "",
"Ports": [],
"PodName": "mvp-pod",
"_dashboard_source": "podman",
"_dashboard_published_ports": [
"8080:8000/tcp",
"8081:8081/tcp"
],
"_dashboard_unit": null,
"_dashboard_def_path": null
}
]
==================================================
GET /api/pods-dashboard
==================================================
Curl:
curl -s http://127.0.0.1:8081/api/pods-dashboard
Response type:
Array of pod dashboard objects.
Golden keys per item:
- Name
- Status
- Containers
- Unit
- Source
Golden example:
[
{
"Name": "mvp-pod",
"Status": "Running",
"Containers": [
"mvp-backend",
"mvp-webui"
],
"Unit": "pod-mvp-pod.service",
"Source": "podman"
}
]
==================================================
GET /api/images
==================================================
Curl:
curl -s http://127.0.0.1:8081/api/images
Response type:
Array of Podman image objects.
Golden keys per item:
- RepoTags
- RepoDigests
- Created
- Size
- Containers
- Digest
- Arch
- Os
Golden example:
[
{
"RepoTags": [
"docker.io/library/httpd:2.4"
],
"RepoDigests": [
"docker.io/library/httpd@sha256:..."
],
"Created": 1770085385,
"Size": 120210217,
"Containers": 1,
"Digest": "sha256:...",
"Arch": "amd64",
"Os": "linux"
}
]
==================================================
GET /api/networks/meta
==================================================
Curl:
curl -s http://127.0.0.1:8081/api/networks/meta
Golden keys:
- networkBackend
- rootless
- infoEndpoint
Golden example:
{
"networkBackend": "netavark",
"rootless": true,
"infoEndpoint": "http+unix://%2Frun%2Fuser%2F1000%2Fpodman%2Fpodman.sock/v5.4.2/libpod/info"
}
==================================================
GET /api/openapi.json
==================================================
Curl:
curl -s http://127.0.0.1:8081/api/openapi.json
Contract:
OpenAPI schema must remain available for tooling and inspection.
Required top-level keys:
- openapi
- info
- paths
==================================================
GENERAL BACKWARD COMPATIBILITY RULE
==================================================
The following dashboard endpoints are considered UI-critical:
- /containers-dashboard
- /pods-dashboard
- /images
- /networks/meta
Changes affecting these endpoints must be proposed before implementation.
System stability has priority over structural refactoring.
+30
View File
@@ -52,6 +52,36 @@ def _run_systemctl_action(action: str, unit: str):
cmd = ["systemctl", "--user", action, unit] cmd = ["systemctl", "--user", action, unit]
return _systemctl(cmd) return _systemctl(cmd)
@app.get("/health")
def health():
podman_ok = False
try:
r = SESSION.get(f"{PODMAN_API_BASE}/libpod/info", timeout=2)
if r.status_code == 200:
try:
r.json()
podman_ok = True
except Exception:
podman_ok = False
except Exception:
podman_ok = False
systemd_reachable = False
try:
res = subprocess.run(
["systemctl", "--user", "list-units", "--no-pager", "--no-legend"],
capture_output=True,
text=True,
check=False,
timeout=2,
)
systemd_reachable = (res.returncode == 0)
except Exception:
systemd_reachable = False
ok = podman_ok and systemd_reachable
return {"ok": ok, "podman": {"ok": podman_ok}, "systemd_user": {"reachable": systemd_reachable}}
# --- MODELS --- # --- MODELS ---
class FileContent(BaseModel): class FileContent(BaseModel):
+4
View File
File diff suppressed because one or more lines are too long