**Phase 28 final pass — complete, 100 % green.** All five tasks were already `complete/`; this pass verified the criteria, re-ran the live half, and did the docs pass. - Gate `bash .agents/validate.sh` **rc=0**: `1650 passed, 15 skipped`, coverage **98.97 %** (floor 94), `ruff` clean, `pyright` **0 errors**, `node --check` clean, hygiene clean (`/tmp/validate-done.log`). - Live: `pytest -m live --live` **11 passed**; `./scripts/dev-server.sh status` nine sources `ok`; mTLS re-read of `gateway` → agent **1.2.0**, all three sections `available: true`, 6/6 frontends routed, 11 nginx servers + `$targetBackend`, **19** listeners, no core errors (`data/logs/28-finalpass-live.out`); installed agent file byte-identical to this tree (`b0e9ebf1b718…`). - Criteria: 1 ✅ pytest/coverage · 2 ✅ three sections over a captured tree, `false`/`null` + one `errors` line each elsewhere with `cpu`/`mem`/`disk_io`/`system` intact · 3 ✅ `AGENT_VERSION` 1.2.0 + SCHEMA names all three · 4 ✅ six fixtures verbatim, capture refuses a bad read, 116 socket address columns hold no globally routable endpoint · 5 ✅ gateway runs 1.2.0 and answers over mTLS (transcript `data/logs/28-task05-rollout.md`, `28-finalpass-live.out`) · 6 ✅ validate · 7 ✅ additive: HEAD's 1.1.0 and this 1.2.0 `collect()`ed over one `--root` are key-for-key equal plus the three new sections, `--no-proxy` equal exactly (`data/logs/verify-28-final.py` → 41 PASS, `28-final-verify.out`). - Docs fixed (README/AGENTS/Containerfile, all re-green): README's opening said "Four dashboards" above a five-row table and said the new sections are "stored in the snapshot" (they are in the **agent's** document — no `/api` payload, no `sample` row, nothing charts them until phase 29); `Containerfile`'s header still said "three dashboards"; AGENTS's coverage figure (98.94 → **98.97 %**), invariant 6 gained the captured-socket-table identifier rule, the agent bullet gained "phase 28 stops at the wire", and the fleet record names the stash-and-compare control plus the volatile listener count. Full record: `data/logs/NOTES-28-final.md`. - Deviations: none functional; no git index/history touched, phase files untouched (`PLAN.md` and `complete/` unchanged). `uv pip list --outdated` still lists 4 packages (fastapi 0.153→0.161 etc.) — advisory only, dependencies untouched by this phase, left for a deliberate pass. - Next pending phase: `.agents/phases/todo/29_gateway_dashboard_page/` (the page that consumes these three sections).
53 lines
2.5 KiB
Docker
53 lines
2.5 KiB
Docker
# Homelab dashboards — one image, four dashboards and their overview, one port.
|
|
#
|
|
# podman build -t dashboards .
|
|
# podman run --rm -p 8090:8090 --env-file dashboards.env \
|
|
# -v dashboards-data:/var/lib/dashboards dashboards
|
|
#
|
|
# Two stages: the builder resolves `uv.lock` (exactly the versions the tests ran against),
|
|
# the runtime stage is a plain slim Python with only the installed venv in it. No build
|
|
# toolchain, no source, no dev dependencies in the final image.
|
|
|
|
FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim AS builder
|
|
WORKDIR /app
|
|
# The venv is built at the path it will run from: a venv moved out of place has shebangs and
|
|
# a `bin/python` symlink pointing at wherever it was built, which fails at exec time with a
|
|
# confusing "missing dynamic library?".
|
|
ENV UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never UV_PROJECT_ENVIRONMENT=/opt/venv
|
|
# manifest first: a source-only change must not re-resolve the dependency graph
|
|
COPY pyproject.toml uv.lock README.md ./
|
|
RUN uv sync --frozen --no-dev --no-install-project
|
|
COPY src ./src
|
|
# --no-editable matters: uv installs the project editable by default, which leaves a .pth
|
|
# pointing at /app/src — a path that only exists in the builder, and the app then dies at
|
|
# import time with "No module named dashboards".
|
|
RUN uv sync --frozen --no-editable
|
|
|
|
FROM python:3.14-slim
|
|
# no image.source label: the origin is set once this repo has one
|
|
LABEL org.opencontainers.image.title="homelab dashboards" \
|
|
org.opencontainers.image.description="System, LLM engine and UPS battery dashboards on one domain"
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
PATH=/opt/venv/bin:$PATH \
|
|
DASH_HOST=0.0.0.0 \
|
|
DASH_PORT=8090 \
|
|
DASH_DB=/var/lib/dashboards/dashboards.sqlite3
|
|
|
|
RUN useradd --system --uid 10001 --home-dir /var/lib/dashboards --create-home dashboards
|
|
COPY --from=builder /opt/venv /opt/venv
|
|
# the host agent ships in the same image so one container can monitor the box it runs on
|
|
COPY agent/host_agent.py /opt/dashboards/agent/host_agent.py
|
|
|
|
VOLUME /var/lib/dashboards
|
|
EXPOSE 8090
|
|
USER dashboards
|
|
|
|
# /api/health answers "empty" (200) with no sources configured, so a container that starts
|
|
# before its env file is right still passes the probe instead of crash-looping.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
CMD ["python", "-c", "import sys,urllib.request; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8090/api/health', timeout=4).status == 200 else 1)"]
|
|
|
|
CMD ["dashboards"]
|