Skip to content

Commit 8b1717a

Browse files
committed
docs(adr): consolidate ADR-T-009 plan into a single self-contained ADR
The implementation work for ADR-T-009 is done; the separate phase-by-phase implementation plan that drove it has now served its purpose. This change folds every decision the plan recorded back into the ADR proper, deletes the plan file, and migrates every cross-reference in the codebase to the ADR's canonical `§D1`–`§D9` decision-section anchors. No runtime behaviour changes — this is a docs-and-references pass on top of the already-merged Phase 1–9 work. Single source of truth ---------------------- - `adr/009-container-infrastructure-refactor.md`: rewritten as the standalone reference for the refactor. Status flips from "Implemented (Phases 1–9 complete)" to plain "Implemented"; the prose drops the phase scaffolding and is reorganised around nine numbered decisions: - §D1 — Compose split (production-shaped baseline + auto-loaded dev override, wrapped by `make up-dev` / `make up-prod`). - §D2 — Strip credentials from shipped defaults; `tracker.token` and `database.connect_url` mandatory at the schema level. - §D3 — Entry script rewrite around the JSON config probe and the sourced POSIX shell library. - §D4 — Lean release runtime base + curated busybox applet set; `/busybox/` only on the debug image. - §D5 — Helper-binary dep-closure discipline (`index-cli-common`, JSON-on-stdout contract, root-only `0500 root:root` posture). - §D6 — `API_PORT` / `IMPORTER_API_PORT` demoted from build-time `ARG` to runtime `ENV`. - §D7 — `USER_ID` guard tightened to "numeric, non-zero" (was `>= 1000`). - §D8 — Vendored `su-exec.c` provenance + SHA-256-anchored audit log + CI guard. - §D9 — Build-context hygiene (`adr/`, `docs/` excluded via `.containerignore`). - `adr/009-implementation-plan.md`: deleted (~3000 lines). Every durable decision lives in the ADR; the historical phase ordering, file lists, and merge-conflict notes belong to the git history of the now-merged work. Cross-reference migration ------------------------- - `Containerfile`, `share/container/entry_script_sh`, `contrib/dev-tools/su-exec/AUDIT.md`, `.github/workflows/container.yaml`, `packages/index-config-probe/src/{lib.rs,bin/torrust-index-config-probe.rs}`, `docs/containers.md`: every `§N` / "implementation plan §N.M" reference rewritten as `ADR-T-009 §DN`. Comments that pointed at "Phase 9 §9" or "implementation plan §6.1" now point at the ADR's permanent anchors so the references survive any future reorganisation of the ADR's internal layout. CHANGELOG --------- - Expand the previously placeholder ADR-T-009 bullet into a full Added / Changed / Removed audit covering the seven new workspace artefacts (`index-cli-common`, `index-config-probe`, `index-entry-script`, the sourced shell library, `Makefile`, `compose.override.yaml`, `contrib/dev-tools/su-exec/AUDIT.md`), the `jq_donor` Containerfile stage, `Info::from_env`, the `DEFAULT_CONFIG_TOML_PATH` re-export, and the `ENTRY_ENV_VARS` manifest contract; the `release` / `debug` runtime base split, the production-shaped `compose.yaml`, the `API_PORT` / `IMPORTER_API_PORT` demotion, the rewritten health-check and auth-keypair helpers, and the `USER_ID` guard change (flagged **BREAKING**); and the credential / `[mail.smtp]` / `[auth]`-paths removal from the shipped TOMLs together with the `Default` impls (`Settings`, `Tracker`, `Database`) and the two retired `src/bin/*` helpers. README and crate docs --------------------- - `README.md`: note that the documented "for deployment, you __should__ override the tracker token" guidance is now schema-mandatory (per §D2), not advisory. - `src/lib.rs`: update the "Run with docker" example to supply the two now-mandatory `..._OVERRIDE_TRACKER__TOKEN` / `..._OVERRIDE_DATABASE__CONNECT_URL` env vars (and drop the `sqlite3 … VACUUM` pre-seed step, since the entry script's §D3 seed-on-empty path handles that). Cross-link the container guide and ADR-T-009; document the Compose split and the `make up-dev` / `make up-prod` wrappers. Refresh the Configuration section to mark `tracker.token` / `database.connect_url` as mandatory, point at the standalone `torrust-index-config` crate, and fix the env-var name typo (`TORRUST_INDEX_CONFIG_PATH` → `TORRUST_INDEX_CONFIG_TOML_PATH`).
1 parent 51cf24a commit 8b1717a

12 files changed

Lines changed: 1306 additions & 3727 deletions

File tree

.github/workflows/container.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ jobs:
4242
# output points the reader at the real line.
4343
if awk '{ sub(/#.*/, ""); print }' compose.yaml \
4444
| grep -nE 'mailcatcher|MAILER|SMTP|smtp_'; then
45-
echo "::error file=compose.yaml::dev mail sidecar / SMTP config present in production-shaped baseline (ADR-T-009 §8.1, §9.1.3)"
45+
echo "::error file=compose.yaml::dev mail sidecar / SMTP config present in production-shaped baseline (ADR-T-009 §D1 / §8.1)"
4646
exit 1
4747
fi
4848
echo "compose.yaml clean."
4949
50-
# Phase 9 §9.2 — vendored `su-exec.c` must not change
50+
# Phase 9 / ADR-T-009 §D8 — vendored `su-exec.c` must not change
5151
# without a fresh audit entry recording the new SHA-256
5252
# in contrib/dev-tools/su-exec/AUDIT.md.
5353
- id: su-exec-audit
@@ -59,17 +59,17 @@ jobs:
5959
recorded=$(sed -n '/^## Audit Log/,$ { s/^SHA-256: \([0-9a-f]\{64\}\)$/\1/p; }' "$audit" | tail -1)
6060
actual=$(sha256sum contrib/dev-tools/su-exec/su-exec.c | cut -d' ' -f1)
6161
if [ -z "$recorded" ]; then
62-
echo "::error file=$audit::no SHA-256 entry found in '## Audit Log' section (ADR-T-009 §9.2)"
62+
echo "::error file=$audit::no SHA-256 entry found in '## Audit Log' section (ADR-T-009 §D8)"
6363
exit 1
6464
fi
6565
if [ "$recorded" != "$actual" ]; then
66-
echo "::error file=$audit::recorded SHA-256 ($recorded) does not match contrib/dev-tools/su-exec/su-exec.c ($actual). Append a new dated audit entry per ADR-T-009 §9.2."
66+
echo "::error file=$audit::recorded SHA-256 ($recorded) does not match contrib/dev-tools/su-exec/su-exec.c ($actual). Append a new dated audit entry per ADR-T-009 §D8."
6767
exit 1
6868
fi
6969
echo "su-exec audit current ($actual)."
7070
71-
# Phase 9 §9 / Acceptance Criterion #7 — every env var
72-
# listed in the entry script's manifest block must be
71+
# Phase 9 / ADR-T-009 Acceptance Criterion #7 — every env
72+
# var listed in the entry script's manifest block must be
7373
# documented in docs/containers.md.
7474
- id: entry-env-docs
7575
name: entry-script env vars documented
@@ -80,7 +80,7 @@ jobs:
8080
| grep -oE '[A-Z][A-Z0-9_]+' \
8181
| sort -u)
8282
if [ -z "$vars" ]; then
83-
echo "::error file=$script::ENTRY_ENV_VARS manifest block not found or empty (ADR-T-009 §9 Crit. #7)"
83+
echo "::error file=$script::ENTRY_ENV_VARS manifest block not found or empty (ADR-T-009 Acceptance Criterion #7)"
8484
exit 1
8585
fi
8686
missing=0

CHANGELOG.md

Lines changed: 128 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- ADR-T-009: Container infrastructure refactor (Phases 1–9). Beyond
13-
the tactical hardening already noted under Phase 3, the refactor:
14-
- Splits the runtime image into a lean `release` (distroless
15-
`cc-debian13`) and `debug` (`cc-debian13:debug`) target. The
16-
`release` image keeps `/bin/busybox`, `/bin/su-exec`, and
17-
`/usr/bin/jq` root-only (mode `0700`/`0500 root:root`); the
18-
unprivileged `torrust` user gets `EACCES` on the entire toolset
19-
after privilege drop. The `debug` target retains the upstream
20-
`/busybox/` tree on `PATH` for interactive debugging.
21-
- Extracts three helper binaries into their own workspace crates
22-
with no transitive HTTP/TLS/async-runtime dependencies:
23-
`torrust-index-health-check` (renamed from `health_check`),
24-
`torrust-index-auth-keypair` (renamed from
25-
`torrust-generate-auth-keypair`), and the new
26-
`torrust-index-config-probe` (the same loader the application
27-
uses, exposing the resolved schema/database/auth state as JSON).
28-
- Splits Compose into a production-shaped
29-
[`compose.yaml`](./compose.yaml) baseline (no `mailcatcher`, no
30-
`tty`, dev ports bound to `127.0.0.1`, credentials referenced
31-
as bare `${VAR}`) and an auto-loaded
32-
[`compose.override.yaml`](./compose.override.yaml) supplying the
33-
dev sandbox. Two `Makefile` targets (`make up-dev`, `make up-prod`)
34-
wrap the documented invocation paths and validate required env
35-
vars before any container starts.
36-
- Adds `contrib/dev-tools/su-exec/AUDIT.md` recording provenance,
37-
rationale, and a SHA-256-anchored append-only audit log for the
38-
vendored `su-exec.c`. CI fails the build when the file changes
39-
without a matching audit entry.
40-
12+
- ADR-T-009: Container infrastructure refactor.
4113
- `torrust-index-config` workspace crate (`packages/index-config/`)
4214
containing the parsing surface of the configuration system: schema
4315
modules, validator, `load_settings`, `Info`, `Error`, the
4416
`CONFIG_OVERRIDE_*` / `ENV_VAR_CONFIG_TOML*` constants, and the
4517
permission value types (`Role`, `Action`, `Effect`,
46-
`PermissionOverride`). Leaf crate \u2014 no `tokio`, `reqwest`,
18+
`PermissionOverride`). Leaf crate no `tokio`, `reqwest`,
4719
`sqlx`, `hyper`, `rustls`, `native-tls`, or `openssl` in its dep
4820
closure (ADR-T-009 Phase 3).
21+
- `torrust-index-cli-common` workspace crate
22+
(`packages/index-cli-common/`) providing the shared P9 helper-binary
23+
scaffolding: `refuse_if_stdout_is_tty`, `init_json_tracing`,
24+
`emit<T: Serialize>`, and a common `BaseArgs` with `--debug`. Used
25+
by every helper binary so each `main` is only domain logic
26+
(ADR-T-009 §D5).
27+
- `torrust-index-config-probe` workspace crate
28+
(`packages/index-config-probe/`) loading the same `Settings` the
29+
application loads and emitting the resolved
30+
schema/database/auth state as one JSON object on stdout. The entry
31+
script consumes its output via `jq` and dispatches the auth-key /
32+
database-seeding decisions; this eliminates the script-vs-application
33+
parser drift that caused R3 (ADR-T-009 §D3).
34+
- `torrust-index-entry-script` test-only workspace crate
35+
(`packages/index-entry-script/`) driving the sourced shell library
36+
via `sh` subprocess and asserting exit codes / stderr contracts for
37+
every branch of the auth-key invariants and the SQLite-seeding
38+
outcomes (ADR-T-009 §D3).
39+
- Sourced POSIX `sh` library at
40+
`share/container/entry_script_lib_sh` containing the entry script's
41+
pure helpers (`inst`, `key_configured`, `validate_auth_keys`,
42+
`seed_sqlite`). Shipped to `/usr/local/lib/torrust/entry_script_lib_sh`
43+
(mode `0444 root:root`) and sourced — not exec'd — by the entry
44+
script and by the host-side test crate (ADR-T-009 §D3).
45+
- Top-level [`Makefile`](./Makefile) with `make up-dev` (plain
46+
`docker compose up`) and `make up-prod` (validates required
47+
credential env vars, then runs `docker compose --file compose.yaml
48+
up -d --wait` with the override excluded) (ADR-T-009 §D1).
49+
- [`compose.override.yaml`](./compose.override.yaml) auto-loaded by
50+
Compose v2, re-introducing the `mailcatcher` sidecar, `tty: true`,
51+
and permissive `${VAR:-default}` substitutions on top of the
52+
production-shaped baseline (ADR-T-009 §D1).
53+
- `contrib/dev-tools/su-exec/AUDIT.md` recording provenance, choice
54+
rationale (`su-exec` over `gosu`/`setpriv`), and a SHA-256-anchored
55+
append-only audit log for the vendored `su-exec.c`. CI fails the
56+
build when the file changes without a matching audit entry; there is
57+
deliberately no calendar-based re-audit trigger for an unchanged
58+
~105-line POSIX C file (ADR-T-009 §D8).
59+
- `jq_donor` Containerfile stage installing `jq` (and its `libjq.so.1`
60+
/ `libonig.so.5` shared libraries) from a pristine `rust:slim-trixie`
61+
base. Both runtime images copy `/usr/bin/jq` as `0500 root:root`,
62+
invoked only during the entry script's pre-`su-exec` phase to parse
63+
the config probe's and auth-keypair helper's JSON output. An
64+
`ldd`-based allow-list assertion catches future transitive-dep
65+
changes at build time (ADR-T-009 §D5).
66+
- `Info::from_env` constructor on `torrust-index-config`'s `Info`,
67+
the JSON-safe sibling of `Info::new` that skips the diagnostic
68+
`println!`s (so the config probe's stdout-only contract stays
69+
intact) and filters empty-string env vars so a bare `${VAR}`
70+
substituting to empty no longer reaches the deserialiser as
71+
configuration TOML.
72+
- `pub const DEFAULT_CONFIG_TOML_PATH` re-export from
73+
`torrust-index-config` so the entry script and the probe share a
74+
single source of truth for the default in-container TOML location.
75+
- `# ENTRY_ENV_VARS:` / `# END_ENTRY_ENV_VARS` canonical manifest
76+
block in `share/container/entry_script_sh`. CI extracts the
77+
variable names and verifies every one is documented in
78+
`docs/containers.md` (ADR-T-009 Acceptance Criterion #7).
4979
- `EXPOSE ${IMPORTER_API_PORT}/tcp` in Containerfile; port 3002 mapped in
5080
compose.
5181
- `restart: unless-stopped` on index and tracker compose services.
@@ -157,6 +187,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
157187
`crate::services::authorization` for backwards compatibility. The
158188
`Permissions` trait and `PermissionMatrix` runtime policy stay in
159189
the root crate (ADR-T-009 Phase 3).
190+
- **BREAKING:** Entry-script `USER_ID` guard tightened from
191+
`USER_ID >= 1000` to "is numeric and not `0`". The previous rule
192+
encoded the wrong property and rejected valid configurations
193+
(rootless Podman with subuid remapping, low-UID CI runners,
194+
BSD-derived hosts). Operators who relied on the old guard to
195+
reject low UIDs must enforce that policy at the orchestrator level
196+
(ADR-T-009 §D7).
197+
- Runtime images split onto two parallel bases. `release` now builds
198+
on the lean `gcr.io/distroless/cc-debian13` (was the `:debug`
199+
variant) with a single `/bin/busybox` (mode `0700 root:root`) and a
200+
curated set of applet symlinks; the unprivileged `torrust` user
201+
gets `EACCES` on every applet after privilege drop. `debug`
202+
retains the `:debug` base with `/busybox/` on `PATH` for
203+
interactive debugging. Eliminates the R6 absolute-path bypass of
204+
the curated subset (ADR-T-009 §D4).
205+
- `compose.yaml` restructured as a production-shaped baseline:
206+
`mailcatcher` removed, `tty: true` removed, dev-only ports bound to
207+
`127.0.0.1`, credentials referenced via bare `${VAR}` (no defaults,
208+
no `:?required` assertion). The dev sandbox lives in the
209+
auto-loaded `compose.override.yaml`. Plain `docker compose up`
210+
continues to work for dev (ADR-T-009 §D1).
211+
- `API_PORT` and `IMPORTER_API_PORT` are no longer build-time `ARG`s.
212+
Both keep their `ENV` defaults, which the listener and the
213+
healthcheck honour at runtime; consumers no longer need to rebuild
214+
the image to change a port (ADR-T-009 §D6).
215+
- `.containerignore` excludes `adr/` and `docs/` from the build
216+
context (ADR-T-009 §D9).
217+
- Health-check binary rewritten without `reqwest`/`tokio`/TLS using
218+
`std::net::TcpStream` + a minimal HTTP/1.1 GET (~30 lines), with
219+
`set_read_timeout`/`set_write_timeout` for a short connect/read
220+
window. Emits `{"target", "status", "elapsed_ms"}` JSON on stdout
221+
on success; empty stdout + non-zero exit on failure
222+
(ADR-T-009 §D5).
223+
- Auth-keypair helper emits `{"private_key_pem", "public_key_pem"}`
224+
JSON on stdout instead of two raw PEM blocks. The entry script's
225+
consumer migrated from `sed` PEM-block extraction to `jq`. The TTY
226+
guard now exits with code 2 (was 1) to align with the shared
227+
`refuse_if_stdout_is_tty` helper (ADR-T-009 §D5).
160228
- **BREAKING:** Raise MSRV from 1.85 to 1.88.
161229
- **BREAKING:** `administrator: bool` replaced by `role: String` in API
162230
responses (`TokenResponse`, `UserCompact`, etc.). The legacy `admin: bool`
@@ -245,6 +313,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
245313

246314
### Removed
247315

316+
- `connect_url`, `token`, `[mail.smtp]`, and `[auth]` path entries
317+
from every shipped TOML under `share/default/config/`. No
318+
credentials and no environment-coupled values ship inside the image
319+
(ADR-T-009 §D2). The `[auth]` paths are owned by the entry script
320+
(§D3); `connect_url` and `token` are now mandatory at the schema
321+
level.
322+
- `impl Default for Database` and the `default_database` /
323+
`default_connect_url` serde-default helpers; the enclosing
324+
`#[serde(default)]` on `TorrustIndex.database` was removed in the
325+
same change so an absent `[database]` block also fails serde
326+
validation (ADR-T-009 §D2).
327+
- `impl Default for Tracker`, `Tracker::default_token`, and the
328+
enclosing `#[serde(default = "default_tracker")]` on
329+
`Settings.tracker`. The `"tracker.token"` entry was also removed
330+
from `mandatory_options` since the schema-level enforcement
331+
supersedes it (ADR-T-009 §D2).
332+
- `impl Default for Settings` (the single ambient test fixture).
333+
Tests now consume the shared `PLACEHOLDER_TOML` /
334+
`placeholder_settings()` helpers from
335+
`torrust_index_config::test_helpers` (`#[doc(hidden)] pub`).
336+
- `src/bin/health_check.rs` — superseded by the
337+
`torrust-index-health-check` workspace crate
338+
(`packages/index-health-check/`) (ADR-T-009 §D5).
339+
- `src/bin/generate_auth_keypair.rs` and the
340+
`torrust-generate-auth-keypair` binary name — superseded by the
341+
`torrust-index-auth-keypair` workspace crate
342+
(`packages/index-auth-keypair/`) (ADR-T-009 §D5).
343+
- `/busybox/` directory from the `release` runtime image. The lean
344+
`cc-debian13` base does not ship it, so the R6 absolute-path
345+
bypass is eliminated by construction (ADR-T-009 §D4).
248346
- `admin: bool` field from `TokenResponse`, `LoggedInUserData`, and
249347
`TokenRenewalData` — superseded by `role: String` (ADR-T-008).
250348
- `UserCompact::is_admin()` convenience method — no longer needed after

Containerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ RUN mkdir -p /app/bin/; \
123123
# Phase 4: per-binary modes. Application binary stays
124124
# world-executable; root-phase-only helpers (auth-keypair,
125125
# config-probe) tighten to root-only (0500 root:root) per
126-
# ADR-T-009 §6 / §7.3 — same posture as busybox, su-exec, jq.
126+
# ADR-T-009 §D4 / §D5 — same posture as busybox, su-exec, jq.
127127
RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; \
128128
chmod 0755 /app/bin/torrust-index; \
129129
chown 0:0 /app/bin/torrust-index-auth-keypair \
@@ -149,7 +149,7 @@ RUN mkdir -p /app/bin/; \
149149
# Phase 4: per-binary modes (see test_debug above for rationale).
150150
# The healthcheck binary is invoked from HEALTHCHECK, which
151151
# runs as root (no --user in the directive), so 0500 is
152-
# sufficient. The config-probe (ADR-T-009 §6 / §7) is invoked
152+
# sufficient. The config-probe (ADR-T-009 §D3) is invoked
153153
# only from the entry script's pre-su-exec phase, so it is
154154
# also root-only.
155155
RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; \
@@ -247,8 +247,8 @@ COPY --from=preflight_gate /tmp/.adduser-ok /tmp/.preflight-sentinel
247247
ENV PATH=/usr/local/bin:/bin:/usr/bin:/sbin
248248
# Materialise the curated applet set as symlinks to the
249249
# single root-only /bin/busybox. The applet list must match
250-
# §4.4 of the implementation plan — update both in the same
251-
# change.
250+
# the curated applet reference in ADR-T-009 §D4 — update
251+
# both in the same change.
252252
RUN ["/bin/busybox", "sh", "-c", \
253253
"for a in sh adduser addgroup install mkdir dirname chown chmod tr mktemp cat printf rm echo grep; do \
254254
/bin/busybox ln -s busybox /bin/$a; \

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ _Optionally, you may choose to supply the entire configuration as an environment
125125
TORRUST_INDEX_CONFIG_TOML=$(cat "./storage/index/etc/index.toml") cargo run
126126
```
127127

128-
_For deployment, you __should__ override the `tracker_api_token`:_
128+
_For deployment, you __should__ override the `tracker.token` (per ADR-T-009 §D2 it is mandatory; the shipped TOMLs no longer carry a default):_
129129

130130
```sh
131131
# Override secrets in configuration using environmental variables

0 commit comments

Comments
 (0)