chore: containerize for Coolify + docs (stage 5)
- Dockerfile (python:3.13-slim, uvicorn, 비루트, PORT, /healthz HEALTHCHECK) - .dockerignore - README: 로컬/컨테이너/Coolify 배포 + 폐쇄망 wheelhouse 반입 절차 - CLAUDE.md 갱신(현재 구현 상태/라우트/명령/배포), MANUAL.md 추가 - 정적자산은 이미 로컬 동봉(런타임 CDN 0) → 최종 폐쇄망 반입은 의존성 wheelhouse만 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
99
CLAUDE.md
99
CLAUDE.md
@@ -12,17 +12,23 @@ Context: it runs in an **air-gapped (closed) network** at the Bank of Korea IT
|
||||
Strategy Dept. cloud team. The console lets operators mirror, snapshot, verify,
|
||||
and deploy RPM/DEB patch repositories by clicking instead of running CLI commands.
|
||||
|
||||
The full implementation spec is **`pulp-console-spec.md`** (written in Korean,
|
||||
addressed to the implementing AI). It is the source of truth — read it before
|
||||
making design decisions. The codebase itself does not yet exist; the spec
|
||||
describes what to build and in what order.
|
||||
The full implementation spec is **`pulp-console-spec.md`** (Korean). `PLAN.md`
|
||||
tracks staged progress (checkboxes); `ref/design-ref.md` is the visual design
|
||||
standard; `MANUAL.md` is the Coder/Gitea/Coolify dev+deploy environment guide.
|
||||
|
||||
Status: stages 0–4 are implemented and tested (dashboard, sync+polling, version
|
||||
list, gated deploy+audit). Stage 5 = containerization/packaging. Code lives under
|
||||
`app/` (`config.py`, `pulp_client.py`, `deps.py`, `views.py`, `audit.py`,
|
||||
`task_store.py`, `routes/`, `templates/`, `static/`); tests under `tests/`.
|
||||
|
||||
## Stack (decided — do not substitute)
|
||||
|
||||
- **Backend / BFF:** FastAPI (Python), using `httpx` to call Pulp.
|
||||
- **Frontend:** HTMX + Jinja2 templates. **No build step.** Buttons trigger
|
||||
partial-HTML swaps; progress polling is done with `hx-trigger="every 3s"`.
|
||||
- **Styling:** Pico.css (preferred for air-gap simplicity) or Tailwind standalone.
|
||||
- **Styling:** Pico.css + a custom layer (`app/static/app.css`) applying the
|
||||
`ref/design-ref.md` tokens (Toss-style). Pretendard font is vendored locally.
|
||||
No decorative emoji in chrome — status uses colored chips/dots.
|
||||
|
||||
## Hard constraints (these are the point of the project)
|
||||
|
||||
@@ -66,36 +72,50 @@ name, in code.** Sync and deploy return async **tasks** — capture `task_href`
|
||||
poll `GET {task_href}` (`state`: waiting/running/completed/failed) for completion.
|
||||
RPM paths use `rpm/rpm`; the DEB equivalent swaps that segment for `deb/apt`.
|
||||
|
||||
## Safety model (deploy is gated)
|
||||
## Safety model (deploy is gated) — implemented in `routes/deploy.py`
|
||||
|
||||
- Deploy must go through a **confirmation modal** (`hx-confirm` or an explicit step).
|
||||
- The deploy button is **disabled for versions that aren't verified** (GPG /
|
||||
checksum passed). Verification comes from the Remote's `gpgkey`/`tls_validation`
|
||||
and the Repository `repo_config` (`gpgcheck: 1`, `repo-gpgcheck: 1`) — this is
|
||||
the basis for the "verified ✅" badge.
|
||||
- Every deploy is written to an **audit log** (who / when / which repo / which version).
|
||||
- **Verification gate** (re-checked server-side): only `repo_config` GPG-passing
|
||||
repos are deployable; the button is disabled otherwise. Note verification is
|
||||
**repo-level config**, so it indicates "verification is configured", not a
|
||||
per-snapshot cryptographic proof (real package checks happen at sync time).
|
||||
- **Confirmation modal** with a **preview** (current → target version, net package
|
||||
delta) and **type-to-confirm** (operator must type the repo name).
|
||||
- **Audit log → Postgres** (`audit.py`, table `deploy_audit`): who / when / repo /
|
||||
from-version → to-version (rollback trail). DB write failure does not 500 a
|
||||
completed deploy — it surfaces an `audit_ok=False` warning (TODO: hard-fail if
|
||||
audit becomes mandatory policy).
|
||||
- **Two-step clarity**: deploy = create publication → PATCH distribution; on
|
||||
failure the UI states clearly whether production was changed.
|
||||
- **Auth is a TODO** (`operator` is a placeholder; deploy-time password re-entry planned).
|
||||
|
||||
## Planned route surface (BFF)
|
||||
## Route surface (BFF)
|
||||
|
||||
```
|
||||
GET / dashboard (full page)
|
||||
GET /repos repo list fragment
|
||||
POST /repos/{uuid}/sync start sync, return progress-bar fragment
|
||||
GET /repos/{uuid}/progress progress-bar fragment (polled every 3s)
|
||||
GET /repos/{uuid}/versions version list fragment/page
|
||||
POST /repos/{uuid}/deploy confirm deploy (publication + distribution), result fragment
|
||||
GET /healthz proxy Pulp status
|
||||
GET / dashboard (full page)
|
||||
GET /repos repo list fragment
|
||||
POST /repos/{uuid}/sync start sync, return progress-bar fragment
|
||||
GET /repos/{uuid}/progress progress-bar fragment (polled every 3s)
|
||||
GET /repos/{uuid}/versions version list page
|
||||
GET /repos/{uuid}/deploy/confirm deploy confirmation modal (preview + type-to-confirm)
|
||||
POST /repos/{uuid}/deploy gated deploy (publication + distribution) + audit
|
||||
GET /healthz app liveness — always {"ok": true} (container health)
|
||||
GET /pulp-status Pulp connectivity badge (dashboard polls this)
|
||||
```
|
||||
|
||||
Pulp access is centralized in a `pulp_client.py` helper (`get`/`post`/`patch`
|
||||
with Basic Auth, `list_repos`, `sync_repo`, `list_versions`,
|
||||
`create_publication`, `update_distribution`, single task fetch — the *page* does
|
||||
the polling, not the client).
|
||||
Pulp access is centralized in `pulp_client.py` (`get`/`post`/`patch` with Basic
|
||||
Auth, plus `status`, `list_repos`, `get_repo`, `get_version`, `list_versions`,
|
||||
`sync_repo`, `get_task`, `list_distributions`, `get_publication`,
|
||||
`create_publication`, `update_distribution`, `wait_for_task`). HTML polling is the
|
||||
*page's* job; the client only single-fetches — except `wait_for_task`, used only
|
||||
for the compound deploy (publication→distribution) where server-side waiting is
|
||||
needed. In-flight syncs are tracked in `task_store.py` (in-memory; single
|
||||
instance only). View-model shaping (pure, httpx-free) lives in `views.py`.
|
||||
|
||||
## Configuration (env vars)
|
||||
|
||||
`PULP_BASE_URL`, `PULP_USERNAME`, `PULP_PASSWORD` (secret), `PULP_VERIFY_TLS`,
|
||||
`PULP_CA_FILE` (path to internal CA pem).
|
||||
`PULP_CA_FILE` (internal CA pem), `DATABASE_URL` (Postgres for audit log),
|
||||
`PORT` (default 8000), `PULP_DEMO` (fake data for screen preview, default off).
|
||||
|
||||
## Build order (from the spec — build in working-screen increments)
|
||||
|
||||
@@ -108,8 +128,31 @@ the polling, not the client).
|
||||
|
||||
Each step should produce a working screen. Do not build everything at once.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
.venv/Scripts/python.exe -m uvicorn app.main:app --reload # run (http://127.0.0.1:8000)
|
||||
.venv/Scripts/python.exe -m pytest -q # tests
|
||||
.venv/Scripts/python.exe -m ruff check app tests # lint
|
||||
.venv/Scripts/python.exe -m ruff format app tests # format
|
||||
```
|
||||
|
||||
Tests mock the Pulp client via FastAPI `dependency_overrides` (route tests) or
|
||||
`respx` (client tests); `views.py` is unit-tested as pure functions. Audit DB
|
||||
writes are intercepted by monkeypatching `app.audit._write`.
|
||||
|
||||
## Deployment
|
||||
|
||||
Now (demo): **Coolify** builds the repo `Dockerfile` (Build Pack: Dockerfile,
|
||||
Port 8000) and injects env vars — see `README.md` / `MANUAL.md`. `/healthz` is
|
||||
liveness only so Pulp being down doesn't fail the container.
|
||||
|
||||
Final target is the **air-gapped** network: static assets are already vendored
|
||||
(no runtime CDN); only Python deps need offline reintroduction via a wheelhouse
|
||||
(`pip download` → swap the Dockerfile install step). Procedure in `README.md`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Python 3.14 is installed locally. Once code exists, prefer a virtualenv and
|
||||
pin dependencies (`requirements.txt` / lockfile) so they can be carried into
|
||||
the air-gapped network via pip.
|
||||
- Python 3.14 local venv; deps pinned in `requirements.txt` (container uses
|
||||
`python:3.13-slim`). Reference Pulp objects by `pulp_href`, not name.
|
||||
- Each stage should produce a working screen and stay green (tests + ruff) before commit.
|
||||
|
||||
Reference in New Issue
Block a user