merge: T014 ledger Dockerfile + deploy notes
This commit is contained in:
commit
bc650ea4ee
2 changed files with 87 additions and 0 deletions
22
ledger/Dockerfile
Normal file
22
ledger/Dockerfile
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install the ledger package itself (deps come from pyproject.toml [project.dependencies]).
|
||||||
|
COPY pyproject.toml ./
|
||||||
|
COPY src ./src
|
||||||
|
RUN pip install --no-cache-dir .
|
||||||
|
|
||||||
|
RUN groupadd --system ledger && useradd --system --gid ledger --home-dir /app ledger \
|
||||||
|
&& mkdir -p /data && chown -R ledger:ledger /data /app
|
||||||
|
USER ledger
|
||||||
|
|
||||||
|
ENV LEDGER_DB_PATH=/data/ledger.db
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)" || exit 1
|
||||||
|
|
||||||
|
CMD ["uvicorn", "m2_ledger.api:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
65
ledger/README.md
Normal file
65
ledger/README.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# m2-ledger
|
||||||
|
|
||||||
|
Append-only credits ledger — transactions `{ts, from, to, amount, reason, ref}`,
|
||||||
|
balances always derived by folding the tx log (never stored authoritatively). See
|
||||||
|
`specs/001-market-first-wedge/contracts/ledger-api.md` (frozen v1 contract) and
|
||||||
|
`docs/config.md` (env var / key reference) for the full spec.
|
||||||
|
|
||||||
|
## Run locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ledger
|
||||||
|
uv sync
|
||||||
|
LEDGER_DB_PATH=./ledger.db \
|
||||||
|
LEDGER_SERVICE_KEYS=svc_devkey \
|
||||||
|
LEDGER_ADMIN_KEYS=admin_devkey \
|
||||||
|
uv run uvicorn m2_ledger.api:app --reload --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
`GET http://localhost:8000/health` should return `{"status":"healthy"}`. All other
|
||||||
|
endpoints require an `X-API-Key` header (service or admin class — see
|
||||||
|
`docs/config.md` §4).
|
||||||
|
|
||||||
|
## Deploy as a Coolify app
|
||||||
|
|
||||||
|
`m2-ledger` is the one sanctioned new service in the first wedge (constitution I);
|
||||||
|
it deploys as a plain Coolify app on the `coolify` Docker network, not baked into
|
||||||
|
any image.
|
||||||
|
|
||||||
|
1. **Build**: point the Coolify app at this repo/subpath (`ledger/`) with
|
||||||
|
`ledger/Dockerfile` — no build args, no secrets at build time (constitution VI:
|
||||||
|
no secrets in repos or images).
|
||||||
|
2. **Network + alias**: attach the app to the `coolify` network with alias
|
||||||
|
`m2-ledger`, so callers use `http://m2-ledger:8000` per the frozen contract's
|
||||||
|
base URL.
|
||||||
|
3. **Env vars** (inject at runtime via Coolify's environment panel — never commit
|
||||||
|
real values, see `docs/config.md` §5):
|
||||||
|
- `LEDGER_DB_PATH=/data/ledger.db` (already set as the image default; override
|
||||||
|
only if the persistent volume is mounted elsewhere)
|
||||||
|
- `LEDGER_SERVICE_KEYS` — comma-separated `service`-class keys (CLI/Store/indexer)
|
||||||
|
- `LEDGER_ADMIN_KEYS` — comma-separated `admin`-class keys (grants, snapshots);
|
||||||
|
keep this set small
|
||||||
|
- `LEDGER_PLATFORM_PCT` (default `0.10`, UNCONFIRMED) and `LEDGER_STARTER_GRANT`
|
||||||
|
(default `100`, UNCONFIRMED) — optional, override the code defaults
|
||||||
|
- `REGISTRY_REPO_URL` + `FORGEJO_TOKEN` — required once `/snapshot` actually
|
||||||
|
commits to `m2/market-registry` (see TODO below)
|
||||||
|
4. **Persistent volume**: mount a Coolify persistent volume at `/data` (the image
|
||||||
|
declares `VOLUME /data`) so `ledger.db` survives redeploys. This is the single
|
||||||
|
source of truth for the tx log — never point it at ephemeral container storage.
|
||||||
|
5. **Health check**: the image's `HEALTHCHECK` hits `GET /health` (no auth). Coolify
|
||||||
|
should also be configured to poll the same endpoint for its own readiness gate.
|
||||||
|
6. **Canary-first** (constitution VI): roll out to one environment before any
|
||||||
|
fleet-wide dependency on `m2-ledger` — there is no fleet-wide blast for this
|
||||||
|
service since it's a single shared instance, but verify `/health` and one
|
||||||
|
`/balance/{operator_id}` call with a real service key before pointing the CLI's
|
||||||
|
`ledger_url` at it in `~/.m2-market/config.toml`.
|
||||||
|
|
||||||
|
## Snapshot cron (TODO T034)
|
||||||
|
|
||||||
|
`POST /snapshot` (admin) currently computes `audit/YYYY-MM-DD.json` balances and
|
||||||
|
returns the JSON body but does **not** commit it anywhere — see the
|
||||||
|
`TODO(T034)` in `src/m2_ledger/api.py`. Once T034 lands (Forgejo commit via
|
||||||
|
`REGISTRY_REPO_URL` + `FORGEJO_TOKEN`), add a daily Coolify scheduled task (or
|
||||||
|
cron container) that calls `POST /snapshot` with an admin key once every 24h,
|
||||||
|
per constitution V ("Daily balance snapshots MUST be committed to Forgejo for
|
||||||
|
audit") and the contract's "admin, also daily cron" note. Not wired up yet.
|
||||||
Loading…
Reference in a new issue