7d2c205930
start/stop/restart van systemd units gaan nu via de host-helper
(/run/podman-helper.sock) in plaats van directe systemctl subprocess
vanuit de container. Hiermee wordt de user namespace isolatie omzeild
die D-Bus calls vanuit de container onbetrouwbaar maakt.
- common.py: _helper_call(action, unit) toegevoegd
- app_system.py: /{action}/{unit} route gebruikt helper voor start/stop/restart
- app_containers.py: container_action() gebruikt helper
- daemon-reload en is-active blijven subprocess (read-only, werkt al)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
107 lines
3.1 KiB
Python
107 lines
3.1 KiB
Python
import json
|
|
import socket
|
|
import subprocess
|
|
|
|
from fastapi import HTTPException
|
|
|
|
HELPER_SOCKET = "/run/podman-helper.sock"
|
|
|
|
|
|
def _helper_call(action: str, unit: str) -> tuple[int, str]:
|
|
"""Stuur start/stop/restart naar de host-helper via Unix socket.
|
|
Returntype identiek aan run(): (returncode, output)."""
|
|
payload = json.dumps({"action": action, "unit": unit}).encode()
|
|
try:
|
|
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
|
|
s.settimeout(35)
|
|
s.connect(HELPER_SOCKET)
|
|
s.sendall(payload)
|
|
s.shutdown(socket.SHUT_WR)
|
|
data = b""
|
|
while True:
|
|
chunk = s.recv(4096)
|
|
if not chunk:
|
|
break
|
|
data += chunk
|
|
resp = json.loads(data.decode())
|
|
if resp.get("ok"):
|
|
return 0, resp.get("output", "")
|
|
return 1, resp.get("error", "mislukt")
|
|
except Exception as e:
|
|
return 1, f"helper niet bereikbaar: {e}"
|
|
|
|
|
|
def run(cmd):
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
output = (result.stdout or "") + (result.stderr or "")
|
|
return result.returncode, output.strip()
|
|
except Exception as e:
|
|
return 1, str(e)
|
|
|
|
|
|
def _podman_get_json_checked(session, url: str):
|
|
r = session.get(url)
|
|
if r.status_code >= 400:
|
|
raise HTTPException(status_code=502, detail=r.text)
|
|
try:
|
|
return r.json()
|
|
except Exception:
|
|
raise HTTPException(status_code=502, detail=r.text)
|
|
|
|
|
|
def _podman_get_json(session, url: str):
|
|
return session.get(url).json()
|
|
|
|
|
|
def _podman_get_text(session, url: str) -> str:
|
|
return session.get(url).text
|
|
|
|
|
|
def _podman_post(session, url: str, **kwargs):
|
|
return session.post(url, **kwargs)
|
|
|
|
|
|
def _podman_action_post(session, podman_api_base: str, kind: str, name: str, action: str):
|
|
if kind == "pods":
|
|
url = f"{podman_api_base}/libpod/pods/{name}/{action}"
|
|
else:
|
|
url = f"{podman_api_base}/libpod/containers/{name}/{action}"
|
|
return _podman_post(session, url)
|
|
|
|
|
|
def _podman_delete(session, url: str):
|
|
return session.delete(url)
|
|
|
|
|
|
def _systemctl(cmd, run_func):
|
|
# Proxy to existing run() to avoid behavioral changes.
|
|
return run_func(cmd)
|
|
|
|
|
|
def _build_pod_to_containers_map(containers: list):
|
|
# preserves original order of containers processing; no sorting added
|
|
pod_to_containers = {}
|
|
for c in containers:
|
|
pod_name = c.get("PodName") or ""
|
|
if pod_name:
|
|
pod_to_containers.setdefault(pod_name, []).append((c.get("Names") or ["?"])[0])
|
|
return pod_to_containers
|
|
|
|
|
|
def _map_pod_to_unit(podname: str) -> str | None:
|
|
if not podname:
|
|
return None
|
|
if podname.startswith("pod"):
|
|
return f"{podname[3:]}.service"
|
|
return f"{podname}.service"
|
|
|
|
|
|
def _systemd_then_podman(systemd_callable, podman_callable):
|
|
systemd_res = systemd_callable()
|
|
if systemd_res is not None:
|
|
if isinstance(systemd_res, dict) and systemd_res.get("exit", 1) == 0:
|
|
return systemd_res
|
|
return podman_callable(systemd_res)
|
|
return podman_callable(None)
|