added ADR 0005 and step 13: reintroduce managed nginx+certbot as an opt-in feature for hosts without a reverse proxy (e.g. Hostsharing); partially supersedes ADR 0004; updated plans, documentation, and migration guides

This commit is contained in:
Michael Hoennig
2026-07-07 14:40:11 +02:00
parent f6a876b29b
commit 142bc87604
8 changed files with 123 additions and 5 deletions
+1
View File
@@ -162,3 +162,4 @@ All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc`
- **Gradle**: 8.14.5 (ADR 0002) - **Gradle**: 8.14.5 (ADR 0002)
- **Spring Boot**: 4.0.6 (ADR 0003) - **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) - **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)
@@ -4,9 +4,9 @@
- proposed: 2026-07-07 - proposed: 2026-07-07
- accepted: 2026-07-07 - accepted: 2026-07-07
- rejected: - - 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 ## Context and Problem Statement
@@ -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.
+2 -1
View File
@@ -121,4 +121,5 @@ Obtain and renew the certificate with the host's existing certbot, e.g.:
sudo certbot --nginx -d ci.example.org 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).
+2 -1
View File
@@ -42,7 +42,8 @@ New keys without a legacy counterpart: `builds.maxConcurrent`, `artifacts.rootDi
## Intentionally Not Ported ## 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`. - 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.<name>.docker.env` if needed. - `GITTALLY_BUILD_DOCKER_PREFLIGHT_COMMAND` and `GITTALLY_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — hsadmin-ng-specific; use `branches.<name>.docker.env` if needed.
- `HSADMIN_NG_*` environment-variable fallbacks. - `HSADMIN_NG_*` environment-variable fallbacks.
+2 -1
View File
@@ -79,6 +79,7 @@ No status changes observable during a build (control loop):
## Not Ported (decided) ## Not Ported (decided)
- nginx + certbot/Let's Encrypt container management — replaced by deployment documentation (step 12). - 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). - 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). - 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. - 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: Verify need before porting any of these:
- `GITTALLY_BUILD_DOCKER_PREFLIGHT_COMMAND`, `GITTALLY_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — highly hsadmin-ng-specific defaults. - `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_IMPRESSUM_URL` — keep as optional simple footer link if wanted.
- `GITTALLY_INSTALL_DIR` — dropped with self-install. - `GITTALLY_INSTALL_DIR` — dropped with self-install.
+41
View File
@@ -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-<repo-name>`), `stateDir` (default: `${XDG_STATE_HOME:-~/.local/state}/gittally/nginx/<repoKey>`), `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://<serverName>/` (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.
+6
View File
@@ -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 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. - 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. - 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 ## Steps
@@ -64,7 +65,12 @@ Completion:
- [x] `11-docker-build-runtime.md` — optional Docker build execution - [x] `11-docker-build-runtime.md` — optional Docker build execution
- [x] `12-deployment.md` — systemd service, migration from legacy, docs - [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 0103 are independent of each other. Steps 0103 are independent of each other.
Steps 0406 depend on 0103. Steps 0406 depend on 0103.
Steps 0709 depend on 0406. Steps 0709 depend on 0406.
Steps 11 and 12 are optional/deferrable; 10 only needs 0406. Steps 11 and 12 are optional/deferrable; 10 only needs 0406.
Step 13 depends on 07, 11, and 12.