diff --git a/CLAUDE.md b/CLAUDE.md index d9ed38f..65f4d21 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,3 +162,4 @@ All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc` - **Gradle**: 8.14.5 (ADR 0002) - **Spring Boot**: 4.0.6 (ADR 0003) - **Rewrite architecture**: JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx — systemd unit behind the host's reverse proxy (ADR 0004) +- **Managed nginx/TLS**: revises ADR 0004 — an opt-in nginx+certbot container for hosts without a reverse proxy (e.g. Hostsharing), planned as `docs/plan/13-nginx-tls.md` (ADR 0005) diff --git a/docs/adrs/0004-2026-07-07.rewrite-architecture.md b/docs/adrs/0004-2026-07-07.rewrite-architecture.md index c357803..e806b91 100644 --- a/docs/adrs/0004-2026-07-07.rewrite-architecture.md +++ b/docs/adrs/0004-2026-07-07.rewrite-architecture.md @@ -4,9 +4,9 @@ - proposed: 2026-07-07 - accepted: 2026-07-07 - rejected: - -- superseded: - +- superseded: partially on 2026-07-07 — the "no managed nginx/TLS" part is revised by ADR 0005 (opt-in managed nginx/TLS container for hosts without a reverse proxy) -**Decision [accepted]:** JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx/TLS — deployment via systemd user unit behind the host's reverse proxy. +**Decision [accepted, nginx part revised by ADR 0005]:** JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx/TLS by default — deployment via systemd user unit behind the host's reverse proxy. ## Context and Problem Statement diff --git a/docs/adrs/0005-2026-07-07.managed-nginx-tls.md b/docs/adrs/0005-2026-07-07.managed-nginx-tls.md new file mode 100644 index 0000000..d351aef --- /dev/null +++ b/docs/adrs/0005-2026-07-07.managed-nginx-tls.md @@ -0,0 +1,67 @@ +# Optional Managed nginx/TLS Container + +**Status:** +- proposed: 2026-07-07 +- accepted: 2026-07-07 +- rejected: - +- superseded: - + +**Decision [accepted]:** GitTally optionally manages an nginx+certbot Docker container for hosts without a usable reverse proxy — revises the "no managed nginx" part of ADR 0004; deployment behind an existing reverse proxy stays the default. + +## Context and Problem Statement + +ADR 0004 dropped the legacy nginx/Let's Encrypt container management and documented deployment behind an existing reverse proxy instead. +That decision was carried over from the rewrite plan without validating it against the primary target environment. + +### Technical Background + +GitTally must run on Hostsharing managed container environments. +These hosts provide Docker but no root access and no host web server that GitTally could sit behind. +Without the managed nginx container, GitTally cannot be served over HTTPS there at all. +The legacy script already solved this: it wrote an nginx config, ran an nginx Docker container, and obtained/renewed Let's Encrypt certificates via a certbot container in webroot mode. + +## Considered Options + +* Keep ADR 0004 as is (host reverse proxy only) +* Re-add the legacy managed nginx+certbot container as an opt-in feature +* External tooling (user-maintained compose stack next to GitTally) + +### Host reverse proxy only + +#### Advantages + +- No container lifecycle or certificate code in GitTally. + +#### Disadvantages + +- Unusable on Hostsharing container hosts — the primary deployment target. + +### Opt-in managed nginx+certbot container + +GitTally starts and supervises a labelled nginx container and handles certificate issuance/renewal via certbot, only when explicitly enabled in the config. + +#### Advantages + +- Works on hosts that provide only Docker; HTTPS without root or a host web server. +- The behavior is proven — it is a port of the working legacy subsystem. +- Opt-in: hosts with a reverse proxy keep the simple ADR 0004 setup. + +#### Disadvantages + +- Re-adds container lifecycle and certificate renewal complexity to GitTally. + +### External compose stack + +#### Advantages + +- Keeps GitTally itself simple. + +#### Disadvantages + +- Pushes nginx config templating, cert bootstrap ordering, and renewal onto every operator; exactly the manual work the legacy script automated. + +## Decision Outcome + +Re-add the managed nginx+certbot container as an opt-in feature (`docs/plan/13-nginx-tls.md`). +This partially supersedes ADR 0004: its persistence and UI decisions stay in force; "no managed nginx/TLS" becomes "no managed nginx/TLS by default". +The reverse-proxy deployment from `docs/deployment.md` remains the recommended setup where a host web server exists. diff --git a/docs/deployment.md b/docs/deployment.md index 93c663c..2490db9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -121,4 +121,5 @@ Obtain and renew the certificate with the host's existing certbot, e.g.: sudo certbot --nginx -d ci.example.org ``` -This replaces the legacy script's managed nginx/Let's Encrypt Docker container, which was intentionally not ported (see [migration-from-legacy.md](migration-from-legacy.md)). +This replaces the legacy script's managed nginx/Let's Encrypt Docker container for hosts that have their own web server. +For hosts without a usable reverse proxy (e.g. Hostsharing managed containers), an opt-in managed nginx/TLS container is planned (see `docs/plan/13-nginx-tls.md`, ADR 0005). diff --git a/docs/migration-from-legacy.md b/docs/migration-from-legacy.md index f59bbad..ac51bc1 100644 --- a/docs/migration-from-legacy.md +++ b/docs/migration-from-legacy.md @@ -42,7 +42,8 @@ New keys without a legacy counterpart: `builds.maxConcurrent`, `artifacts.rootDi ## Intentionally Not Ported -- Managed nginx/Let's Encrypt container (`GITTALLY_ARTIFACT_NGINX_*`, `GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL`) — use the host's reverse proxy, see [deployment.md](deployment.md). +- Managed nginx/Let's Encrypt container (`GITTALLY_ARTIFACT_NGINX_*`, `GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL`) — not ported yet, but planned as an opt-in feature for hosts without a reverse proxy (see `docs/plan/13-nginx-tls.md`). + Until then, use the host's reverse proxy, see [deployment.md](deployment.md). - Self-install and self-update (`--install`, `--pull`, `GITTALLY_INSTALL_DIR`) — replaced by jar deployment plus `init --systemd`. - `GITTALLY_BUILD_DOCKER_PREFLIGHT_COMMAND` and `GITTALLY_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — hsadmin-ng-specific; use `branches..docker.env` if needed. - `HSADMIN_NG_*` environment-variable fallbacks. diff --git a/docs/plan/00-legacy-analysis.md b/docs/plan/00-legacy-analysis.md index 6969603..698cfd1 100644 --- a/docs/plan/00-legacy-analysis.md +++ b/docs/plan/00-legacy-analysis.md @@ -79,6 +79,7 @@ No status changes observable during a build (control loop): ## Not Ported (decided) - nginx + certbot/Let's Encrypt container management — replaced by deployment documentation (step 12). + Revised by ADR 0005: it IS needed for Hostsharing container hosts and returns as an opt-in feature (step 13). - Self-install (`--install`), self-update script generation — replaced by jar deployment plus systemd docs (step 12). - Legacy `HSADMIN_NG_*` environment fallbacks and env-file config — replaced by YAML config (done). - Regex-based in-place HTML patching — replaced by server-rendered pages/JSON endpoints. @@ -90,6 +91,6 @@ No status changes observable during a build (control loop): Verify need before porting any of these: - `GITTALLY_BUILD_DOCKER_PREFLIGHT_COMMAND`, `GITTALLY_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — highly hsadmin-ng-specific defaults. -- `GITTALLY_ARTIFACT_NGINX_*`, `GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL` — dropped with nginx management. +- `GITTALLY_ARTIFACT_NGINX_*`, `GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL` — dropped with nginx management; revived as `server.nginx.*` by step 13 (ADR 0005). - `GITTALLY_IMPRESSUM_URL` — keep as optional simple footer link if wanted. - `GITTALLY_INSTALL_DIR` — dropped with self-install. diff --git a/docs/plan/13-nginx-tls.md b/docs/plan/13-nginx-tls.md new file mode 100644 index 0000000..cb58917 --- /dev/null +++ b/docs/plan/13-nginx-tls.md @@ -0,0 +1,41 @@ +# Step 13: Managed nginx/TLS Container + +Prerequisites: steps 07, 11, 12. +Read `README.md`, `00-legacy-analysis.md`, and ADR 0005 first. +Consult `legacy/gitTally` for the functions referenced below. + +## Goal + +Serve GitTally over HTTPS on hosts that provide Docker but no host reverse proxy (e.g. Hostsharing managed container environments). +GitTally optionally manages an nginx Docker container with Let's Encrypt certificates, ported from the legacy subsystem. +This is opt-in; the reverse-proxy deployment from step 12 stays the default (ADR 0005). + +## Design + +Port the legacy nginx subsystem (functions `configure_artifact_nginx_defaults` ~1545, `artifact_nginx_write_ssl_options` ~4051, `artifact_nginx_write_config` ~4077, `artifact_nginx_ports_free` ~4242, `cleanup_stale_artifact_nginx_containers` ~4274, `artifact_nginx_run_container` ~4281, `artifact_nginx_obtain_or_renew_certificate` ~4310, `start_artifact_nginx` ~4342, shutdown cleanup ~723): + +- Config under `server.nginx.*`: `enabled` (default false), `serverName`, `httpPort` (8080), `httpsPort` (8443), `upstreamHost` (default: `serverName`), `containerName` (default: `gittally-nginx-`), `stateDir` (default: `${XDG_STATE_HOME:-~/.local/state}/gittally/nginx/`), `letsencryptEmail`. + Update all three config places (`GitTallyConfig`, `init` templates, `docs/configuration.md`). +- When `server.publicBaseUrl` is empty and `serverName` is set, default it to `https:///` (legacy line ~541). +- Shell out to the `docker` CLI like `DockerBuildRunner` (no SDK); label the container `org.hoennig.gittally` for stale-container cleanup. +- Lifecycle as a server-profile component (like `ServerWatcherLifecycle`/`ServerMetricsLifecycle`): start after the web server is up, stop and remove the container on shutdown. + Nothing runs in CLI mode or tests. +- Two-phase startup, ported from legacy: write an HTTP-only nginx config for the ACME webroot challenge, run the container, obtain the certificate via a certbot container (webroot mode), then rewrite the full HTTPS config and restart nginx. + If a certificate already exists in the state dir, start with the full config directly. +- All failures are non-fatal warnings; the plain HTTP server keeps running (legacy behavior). +- Improvement over legacy (fix by design, do not port): legacy renewed certificates only at process start, relying on frequent self-update restarts. + Schedule a periodic renewal check (e.g. daily) in the lifecycle component instead. + +## Tests + +- Unit tests for nginx config generation (init vs. full mode, upstream/port substitution, server name escaping). +- Unit tests for docker argv assembly (run, certbot, cleanup), mocking the command runner — no real Docker or ACME interaction. +- Renewal scheduling logic with a fake clock/scheduler. + +## Acceptance Criteria + +- `./gradlew ktlintFormat` then `./gradlew build` is green. +- With `server.nginx.enabled: false` (default) nothing changes; no container is touched. +- Manual walkthrough on a Docker host: nginx container starts with the init config and proxies HTTP to GitTally. + Full ACME issuance needs a public DNS name; if none is available, verify the certbot argv and the full-config path against the legacy script and document that in this file. +- `docs/deployment.md` gains a section for hosts without a reverse proxy; `docs/migration-from-legacy.md` maps the `GITTALLY_ARTIFACT_NGINX_*`/`GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL` variables. diff --git a/docs/plan/README.md b/docs/plan/README.md index 4306f43..04bf399 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -37,6 +37,7 @@ Revisit them in an ADR if a step uncovers problems. - The web UI is server-rendered HTML plus small JavaScript polling JSON endpoints (no SPA framework). - The watcher is a Spring-managed scheduled component, decoupled from the build executor via the result repository and events. - nginx/Let's Encrypt container management is NOT ported; deployment behind an existing reverse proxy is documented instead. + Revised after step 12 (ADR 0005): an opt-in managed nginx/TLS container is required for Hostsharing container hosts — see step 13. ## Steps @@ -64,7 +65,12 @@ Completion: - [x] `11-docker-build-runtime.md` — optional Docker build execution - [x] `12-deployment.md` — systemd service, migration from legacy, docs +Added after the initial plan (ADR 0005): + +- [ ] `13-nginx-tls.md` — opt-in managed nginx/TLS container for hosts without a reverse proxy + Steps 01–03 are independent of each other. Steps 04–06 depend on 01–03. Steps 07–09 depend on 04–06. Steps 11 and 12 are optional/deferrable; 10 only needs 04–06. +Step 13 depends on 07, 11, and 12.