feat(api): Codex: add /health endpoint with podman + systemd checks
This commit is contained in:
@@ -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
@@ -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
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user