Rename GitTally to Werkator
`gitTally` is the name of another product in the git space, so the rename is a precaution; nothing about what the build system does changes. The name follows one rule: `Werkator` where it is prose, capitalized where it is a Kotlin type and its file, lowercase everywhere a machine reads it — the command, packages, paths, configuration keys and values, the Gitea check context. Environment variables keep their convention and are uppercase throughout. Every configuration file is still found under its pre-rename name (`ConfigFiles`): `.gittally.yml` at the repository root, in a build worktree and as committed on a branch, `.git/gittally/.gittally.yml` for the machine layer. The current name wins where both exist, and the old file is then ignored rather than merged — two files side by side are a half-done rename, not a layering. Without the fallback an installation that updated without renaming would not fail: a configuration that is not found leaves every setting at its default, so it would come up looking healthy while having forgotten its credentials and its builds. `docs/werkator-migrationsplan.md` lists what the fallback does not cover and has to be moved by hand — above all the state directory `.git/werkator/`, which holds the build history, the control token and the worktrees, and has no fallback of its own. `docs/migration-from-legacy.md` is deleted with this: it mapped the legacy script's environment variables, and every host it addressed has long since moved to the YAML configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7f550689dd
commit
35f06ec1ec
@@ -1,4 +1,4 @@
|
||||
# Legacy werkator Analysis
|
||||
# Legacy Werkator Analysis
|
||||
|
||||
Condensed analysis of `legacy/werkator` (bash, ~6000 lines) as input for the rewrite.
|
||||
Line numbers refer to the legacy script at the time of analysis (version 0.7.8).
|
||||
@@ -90,7 +90,7 @@ No status changes observable during a build (control loop):
|
||||
|
||||
Verify need before porting any of these:
|
||||
|
||||
- `werkator_BUILD_DOCKER_PREFLIGHT_COMMAND`, `werkator_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — highly hsadmin-ng-specific defaults.
|
||||
- `werkator_ARTIFACT_NGINX_*`, `werkator_ARTIFACT_LETSENCRYPT_EMAIL` — dropped with nginx management; revived as `server.nginx.*` by step 13 (ADR 0005).
|
||||
- `werkator_IMPRESSUM_URL` — keep as optional simple footer link if wanted.
|
||||
- `werkator_INSTALL_DIR` — dropped with self-install.
|
||||
- `WERKATOR_BUILD_DOCKER_PREFLIGHT_COMMAND`, `WERKATOR_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — highly hsadmin-ng-specific defaults.
|
||||
- `WERKATOR_ARTIFACT_NGINX_*`, `WERKATOR_ARTIFACT_LETSENCRYPT_EMAIL` — dropped with nginx management; revived as `server.nginx.*` by step 13 (ADR 0005).
|
||||
- `WERKATOR_IMPRESSUM_URL` — keep as optional simple footer link if wanted.
|
||||
- `WERKATOR_INSTALL_DIR` — dropped with self-install.
|
||||
|
||||
@@ -24,7 +24,7 @@ Configuration comes from `WerkatorConfig` (`gitea.*`, `git.token`).
|
||||
## Out of Scope
|
||||
|
||||
- No callers yet; the build executor (step 04) wires status publishing.
|
||||
- No webhook receiving; werkator remains poll-based.
|
||||
- No webhook receiving; Werkator remains poll-based.
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
@@ -81,9 +81,9 @@ Deviation: the listing enumerates origin branches instead of legacy's local bran
|
||||
Addendum (2026-07-07): the legacy per-page reload button (`⟳`, top right) was also re-added on request, next to the live indicator.
|
||||
On polling pages it triggers an immediate data refresh via the page's poller; pages without a poller (artifact index) reload fully.
|
||||
|
||||
Addendum (2026-07-07): all links that leave the werkator UI open in a new tab (`target="_blank" rel="noopener noreferrer"`).
|
||||
Addendum (2026-07-07): all links that leave the Werkator UI open in a new tab (`target="_blank" rel="noopener noreferrer"`).
|
||||
This already held for Gitea branch/commit links and the footer; it was added for the artifact page's log and report links, whose targets have no navigation.
|
||||
Links between werkator pages (nav, artifact index) stay in the same tab.
|
||||
Links between Werkator pages (nav, artifact index) stay in the same tab.
|
||||
|
||||
Addendum (2026-08-10): the artifacts column carries the whole build-reachability logic, and the nav lost its `Current` entry.
|
||||
The permanent `🔗` link is rendered on the build it resolves to — the branch's latest green build — on every build table, instead of on each row of a branch with any green build.
|
||||
|
||||
@@ -39,7 +39,7 @@ Exit codes: 0 on success, 1 on build failure, 2 on usage/config errors (align wi
|
||||
|
||||
## Implementation Notes (2026-07-07)
|
||||
|
||||
Implemented as designed: `status`, `build`, and `retry` are picocli `@Component` subcommands in `commands/`, wired into `werkatorCommand` like the existing ones.
|
||||
Implemented as designed: `status`, `build`, and `retry` are picocli `@Component` subcommands in `commands/`, wired into `WerkatorCommand` like the existing ones.
|
||||
They implement `Callable<Int>`, so the exit codes align with `CliRunner`'s `ExitCodeGenerator` contract: 0 on success, 1 on build failure, 2 on usage/config errors (picocli's own `USAGE` code for invalid options matches).
|
||||
|
||||
- `status [--history]` reads `BuildResultRepository` directly and prints an aligned table (branch, status, commit, time, duration); it reuses `UiFormats`, so the console shows the same timestamp/duration formats as the web UI.
|
||||
@@ -56,7 +56,7 @@ Deviations and decisions:
|
||||
- A failed fetch only warns and the commands continue from the last-known origin state, so they work offline.
|
||||
- `retry` only retries FAILED builds (legacy `branch_has_failed_build` checked exactly `failed`); interrupted/pending builds are the watcher's startup-recovery job.
|
||||
- Exit code 130 for cancelled builds was not ported; a cancelled/interrupted build exits 1 like any non-success.
|
||||
- Found while smoke testing: `.gitignore`'s `*.jar` rule excluded `gradle/wrapper/gradle-wrapper.jar`, so builds in fresh checkouts — including every werkator worktree — failed with `ClassNotFoundException: GradleWrapperMain`.
|
||||
- Found while smoke testing: `.gitignore`'s `*.jar` rule excluded `gradle/wrapper/gradle-wrapper.jar`, so builds in fresh checkouts — including every Werkator worktree — failed with `ClassNotFoundException: GradleWrapperMain`.
|
||||
Fixed with a `!gradle/wrapper/gradle-wrapper.jar` exception and by adding the jar (same class of defect as the `build/` rule fixed in step 04).
|
||||
|
||||
Manual smoke test (2026-07-07, in this repository):
|
||||
|
||||
@@ -72,4 +72,4 @@ Manual smoke test (2026-07-07, scratch repo, Rancher Desktop 27.3.1):
|
||||
- A second run reused the image (inputs label matched, no rebuild; `success after 0:04`).
|
||||
- The command ran as uid 0 inside the container while `build/who.txt` ended up owned by the host user — the in-container ownership repair works.
|
||||
- No labelled containers were left behind after the builds.
|
||||
- Caveat found while testing (environmental, not werkator): with a VM-based Docker (Rancher Desktop/Lima), workspace bind mounts only work for paths shared into the VM (e.g. `$HOME`); a repo under an unshared `/tmp` builds against an empty VM-side directory.
|
||||
- Caveat found while testing (environmental, not Werkator): with a VM-based Docker (Rancher Desktop/Lima), workspace bind mounts only work for paths shared into the VM (e.g. `$HOME`); a repo under an unshared `/tmp` builds against an empty VM-side directory.
|
||||
|
||||
@@ -5,7 +5,7 @@ Read `README.md` and `00-legacy-analysis.md` first.
|
||||
|
||||
## Goal
|
||||
|
||||
Make the new werkator deployable as a service and retire the legacy script.
|
||||
Make the new Werkator deployable as a service and retire the legacy script.
|
||||
|
||||
## Design
|
||||
|
||||
@@ -40,6 +40,7 @@ Housekeeping:
|
||||
Implemented as designed: `init --systemd` (an option on `init`, not a separate subcommand) generates the unit and its `EnvironmentFile` under `.git/werkator/`, prints the install commands, and never touches `~/.config/systemd` itself (no self-install).
|
||||
`SystemdServiceFiles` builds the file contents and is unit-tested by content assertions, including the legacy `%` escaping and `ExecStart` quoting.
|
||||
`docs/deployment.md` and `docs/migration-from-legacy.md` were written; `README.md`, `docs/bootstrapping.md`, `../Werkator-Konzept.md`, and `CLAUDE.md` were updated to reference them.
|
||||
`docs/migration-from-legacy.md` was deleted again on 2026-08-30 with the rename to Werkator: every host it addressed had long since moved to the YAML configuration.
|
||||
|
||||
Deviations and decisions:
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Consult `legacy/werkator` for the functions referenced below.
|
||||
|
||||
## Goal
|
||||
|
||||
Serve werkator over HTTPS on hosts that provide Docker but no host reverse proxy (e.g. Hostsharing managed container environments).
|
||||
werkator optionally manages an nginx Docker container with Let's Encrypt certificates, ported from the legacy subsystem.
|
||||
Serve Werkator over HTTPS on hosts that provide Docker but no host reverse proxy (e.g. Hostsharing managed container environments).
|
||||
Werkator 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
|
||||
@@ -38,7 +38,7 @@ Port the legacy nginx subsystem (functions `configure_artifact_nginx_defaults` ~
|
||||
- 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 werkator.
|
||||
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 `werkator_ARTIFACT_NGINX_*`/`werkator_ARTIFACT_LETSENCRYPT_EMAIL` variables.
|
||||
- `docs/deployment.md` gains a section for hosts without a reverse proxy; `docs/migration-from-legacy.md` maps the `WERKATOR_ARTIFACT_NGINX_*`/`WERKATOR_ARTIFACT_LETSENCRYPT_EMAIL` variables.
|
||||
|
||||
## Result (2026-07-08)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ This step revises the "Future: Docker-based Deployment" section of `docs/bootstr
|
||||
|
||||
## Goal
|
||||
|
||||
Deploy werkator on hosts that provide Docker and git but no Java runtime (Hostsharing container servers, e.g. `tallyman@vm4006`).
|
||||
werkator is distributed as a self-contained runtime bundle: a jlink-trimmed JRE plus `werkator.jar` plus a launcher script, packed as one tarball.
|
||||
Deploy Werkator on hosts that provide Docker and git but no Java runtime (Hostsharing container servers, e.g. `tallyman@vm4006`).
|
||||
Werkator is distributed as a self-contained runtime bundle: a jlink-trimmed JRE plus `werkator.jar` plus a launcher script, packed as one tarball.
|
||||
The JAR stays the primary artifact for development and for hosts that already have a JRE.
|
||||
|
||||
## Distribution Format Decision (ADR 0006)
|
||||
@@ -17,9 +17,9 @@ Three formats were considered; write ADR 0006 recording the decision and this ra
|
||||
- **jlink runtime bundle (chosen)** — no production-code changes, plain JVM semantics, one tarball to `scp`.
|
||||
git and docker CLIs are used from the host, worktree paths stay host paths, and the `init --systemd` unit works unchanged because `java.home` and the running-jar path resolve into the bundle.
|
||||
- **GraalVM native image (rejected)** — Spring AOT evaluates bean conditions at build time.
|
||||
werkator's dual-context design (CLI context without web, second `SpringApplication` with the `server` profile and `WebApplicationType.SERVLET`, `@Profile("!server")` `CliRunner`, `@Profile("server")` lifecycles) cannot be represented in a single AOT arrangement.
|
||||
Werkator's dual-context design (CLI context without web, second `SpringApplication` with the `server` profile and `WebApplicationType.SERVLET`, `@Profile("!server")` `CliRunner`, `@Profile("server")` lifecycles) cannot be represented in a single AOT arrangement.
|
||||
Supporting it would require replacing the profile wiring with runtime guards and collapsing the two context shapes — an invasive rewrite with regression risk for the JVM path.
|
||||
- **Containerized werkator runtime (rejected, was the `docs/bootstrapping.md` sketch)** — needs git and docker CLIs inside the image, a same-path `$HOME` mount plus docker-socket mount and uid/gid mapping so that `DockerBuildRunner`'s `--volume $workspace:$workspace` sibling mounts keep working, and a hand-edited systemd unit.
|
||||
- **Containerized Werkator runtime (rejected, was the `docs/bootstrapping.md` sketch)** — needs git and docker CLIs inside the image, a same-path `$HOME` mount plus docker-socket mount and uid/gid mapping so that `DockerBuildRunner`'s `--volume $workspace:$workspace` sibling mounts keep working, and a hand-edited systemd unit.
|
||||
Kept as the documented fallback if the bundle approach ever becomes unworkable.
|
||||
|
||||
## Target Host Facts (verified 2026-08-10)
|
||||
@@ -43,7 +43,7 @@ Deployment (no code changes expected):
|
||||
- Unpack to `~/opt/werkator/` on the target host; run everything via `~/opt/werkator/bin/werkator`.
|
||||
- `init --systemd` already generates `ExecStart=<java> $JAVA_OPTS -jar <jar> server` from `java.home` and the running jar path — from the bundle both resolve into `~/opt/werkator/`, so the unit points at the bundle without changes.
|
||||
Verify this instead of adapting code; adapt only if the resolution fails.
|
||||
- Updating werkator = unpack a new bundle over `~/opt/werkator/` (or switch a symlink) and restart the service.
|
||||
- Updating Werkator = unpack a new bundle over `~/opt/werkator/` (or switch a symlink) and restart the service.
|
||||
|
||||
Documentation:
|
||||
|
||||
@@ -99,15 +99,15 @@ Fix: `HSADMINNG_POSTGRES_ADMIN_USERNAME=admin` and `HSADMINNG_POSTGRES_RESTRICTE
|
||||
Verified by running both test classes in the build container with the variables set: green.
|
||||
Open oddity: the same commit passed on vm2176 although neither its daemon environment, build image, Gradle volume, nor any build-script mechanism supplies these variables there (an unused git-ignored `.environment` file exists in its primary checkout, but nothing in the build reads it); the loading path on vm2176 remains unidentified.
|
||||
|
||||
Fourth finding (werkator limitation, worked around in config): with all tests green, the build then failed in hsadmin-ng's `:prQuickCheck` — "fatal: not a git repository".
|
||||
werkator builds in a git worktree whose `.git` is a pointer file into the primary repository's `.git/worktrees/…`, and the Docker build container (deliberately, credentials live under `.git/werkator/`) only mounts the worktree — so build steps that call git fail; the legacy script avoided this by building in the primary checkout.
|
||||
Fourth finding (Werkator limitation, worked around in config): with all tests green, the build then failed in hsadmin-ng's `:prQuickCheck` — "fatal: not a git repository".
|
||||
Werkator builds in a git worktree whose `.git` is a pointer file into the primary repository's `.git/worktrees/…`, and the Docker build container (deliberately, credentials live under `.git/werkator/`) only mounts the worktree — so build steps that call git fail; the legacy script avoided this by building in the primary checkout.
|
||||
Workaround: `prQuickCheck` removed from the vm4006 build command — it is a PR quality gate against a base branch and has no meaning in a post-merge master build (on vm2176 it only passed as an accidental no-op).
|
||||
The underlying question (safe git availability inside Docker build containers without exposing `.git/werkator/` secrets) is left as a follow-up design task.
|
||||
|
||||
Cutover completed (2026-08-10, same day): after three green master builds and verified Gitea statuses from vm4006, the legacy service on vm2176 was disabled and removed from systemd.
|
||||
vm4006's `statusContext` was switched to the canonical `werkator` (effective without a restart — the Gitea client loads the config per call), and the branches still carrying red statuses from the buggy first hours were re-queued.
|
||||
vm2176 now runs only a redirect nginx container (`werkator-redirect`, ports 8080/8443 like before): HTTP and HTTPS answer 301 to `https://vm4006.hostsharing.net$request_uri`, the ACME webroot keeps serving so the `nginx-letsencrypt-renew.timer` continues to renew the old host's certificate (the renew unit gained an `ExecStartPost` nginx reload).
|
||||
werkator answers the legacy static page names (`/index.html`, `/branches.html`, `/history.html`, `/system.html`, `/about.html`, `/license.html`) with permanent redirects to the new routes, so pre-rewrite links survive the host redirect.
|
||||
Werkator answers the legacy static page names (`/index.html`, `/branches.html`, `/history.html`, `/system.html`, `/about.html`, `/license.html`) with permanent redirects to the new routes, so pre-rewrite links survive the host redirect.
|
||||
|
||||
Update to v0.9.8 (2026-08-10): the running build was awaited first (a restart would have killed it), then service stopped, `~/opt/werkator` backed up to `~/opt/werkator.v0.9.7.bak` and the new bundle unpacked over it, service started.
|
||||
Verified live: `/` reports v0.9.8, the nav has no `Current` entry, the permanent `🔗` link appears only on branches whose latest build is their latest green one, and the newly linked reports answer 200 — including the stable `/branches/<branch>/reports/profile/`.
|
||||
@@ -157,7 +157,7 @@ Shipped fix: a page returning from the background fetches the current state imme
|
||||
|
||||
Update to v0.9.18 (2026-08-29): same procedure, `~/opt/werkator.0.9.17.bak` as the rollback copy, no build was running.
|
||||
Verified live: `bin/werkator --version` reports v0.9.18 before the start, the service is `active`, `/releases` lists v0.9.18, the watcher polls without fetch or poll errors, and the only warnings are the two known `builds.maxConcurrent` lines from the repository's committed config.
|
||||
Shipped feature: a configuration file can declare the werkator it is written for (`werkator.version.since`/`below`), so an incompatibility is named instead of silently ignored.
|
||||
Shipped feature: a configuration file can declare the Werkator it is written for (`werkator.version.since`/`below`), so an incompatibility is named instead of silently ignored.
|
||||
The configs of the watched repository declare nothing yet and are unaffected — a missing declaration is never an error.
|
||||
|
||||
Update to v0.9.19 (2026-08-29): same procedure, `~/opt/werkator.0.9.18.bak` as the rollback copy, no build was running.
|
||||
@@ -177,5 +177,5 @@ The branch-scoped refusal showed itself in production immediately: `mihoe/reacti
|
||||
|
||||
Update to v0.9.21 (2026-08-30): same procedure, `~/opt/werkator.0.9.20.bak` as the rollback copy, no build was running; the machine config needed no change this time.
|
||||
Shipped feature: an unreachable origin is shown in the web UI (step 19), and a lasting fetch failure is logged once per message instead of once per poll.
|
||||
The occasion was an outage the same morning: the `git.token` in the machine config had been overwritten with a placeholder string, werkator failed every fetch for 57 minutes, and the branches view kept showing its last known list as if nothing were wrong.
|
||||
The occasion was an outage the same morning: the `git.token` in the machine config had been overwritten with a placeholder string, Werkator failed every fetch for 57 minutes, and the branches view kept showing its last known list as if nothing were wrong.
|
||||
Verified live: `--version` reports v0.9.21, the service is `active`, `/` answers 200 with v0.9.21 in the footer, `/api/watcher` reports `lastFetchError: null`, the served `werkator.js` carries `refreshWatcherBanner`, `/branches` carries the banner element, and the only warnings are the two expected ones from the repository's committed config (`builds.maxConcurrent`, the ignored `branches` section).
|
||||
|
||||
@@ -22,11 +22,11 @@ Hard invariant to preserve: a branch build must never be able to reach credentia
|
||||
`DockerBuildRunner.gitMetadataMounts(workspace, repoDir)` adds three mounts when (and only when) the workspace is a worktree of `repoDir` (detected via the `gitdir:` pointer file, which must resolve into `repoDir/.git`):
|
||||
|
||||
1. `repoDir/.git` → same path, **read-only**: objects, refs, and the worktree admin metadata become resolvable; object and ref writes stay impossible.
|
||||
2. An empty **tmpfs over `repoDir/.git/werkator`**: masks the machine config (`git.token`), the control token, and all werkator state; the workspace bind (deeper path, Docker nests mounts by target depth) resurfaces only this build's own worktree inside the masked directory.
|
||||
2. An empty **tmpfs over `repoDir/.git/werkator`**: masks the machine config (`git.token`), the control token, and all Werkator state; the workspace bind (deeper path, Docker nests mounts by target depth) resurfaces only this build's own worktree inside the masked directory.
|
||||
3. `repoDir/.git/worktrees/<key>` → same path, **read-write**: the worktree's admin dir (HEAD, index), so index-refreshing commands like `git status` work.
|
||||
|
||||
No configuration key: the exposure is strictly smaller than the legacy baseline, and a knob would join the pinned sandbox-policy set without a known use case.
|
||||
Remaining, documented exposure: the rest of `.git` — including `.git/config` — is readable by builds; werkator never stores credentials there (fetch auth uses a secret-free `GIT_ASKPASS` with env-passed credentials).
|
||||
Remaining, documented exposure: the rest of `.git` — including `.git/config` — is readable by builds; Werkator never stores credentials there (fetch auth uses a secret-free `GIT_ASKPASS` with env-passed credentials).
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Step 17: Running werkator on a Managed Webspace (bubblewrap builds + web access)
|
||||
# Step 17: Running Werkator on a Managed Webspace (bubblewrap builds + web access)
|
||||
|
||||
Prerequisites: steps 11, 15, 16.
|
||||
Read `README.md` first.
|
||||
Motivated by running werkator on Hostsharing **Managed Webspaces**: no root, no Docker daemon, but `bwrap` (bubblewrap) is available and unprivileged user namespaces are allowed.
|
||||
Target use case: werkator builds werkator itself on a Managed Webspace; builds needing special dependencies get them from a prepared root filesystem instead of the host.
|
||||
Motivated by running Werkator on Hostsharing **Managed Webspaces**: no root, no Docker daemon, but `bwrap` (bubblewrap) is available and unprivileged user namespaces are allowed.
|
||||
Target use case: Werkator builds Werkator itself on a Managed Webspace; builds needing special dependencies get them from a prepared root filesystem instead of the host.
|
||||
Projects that need Docker for their own tests (hs.hsadmin.ng with Testcontainers) stay on a container host like vm4006 — the webspace is for Docker-free builds only.
|
||||
|
||||
The step covers two halves of the same deployment and is deliberately not split:
|
||||
the build sandbox (most of this document) and the web access under a domain (last section).
|
||||
Without the second half the first one only proves that sandboxed builds work somewhere; without the first one werkator on a webspace would run builds unsandboxed on the host.
|
||||
Without the second half the first one only proves that sandboxed builds work somewhere; without the first one Werkator on a webspace would run builds unsandboxed on the host.
|
||||
|
||||
## Precondition Check (run on the target webspace first)
|
||||
|
||||
@@ -46,7 +46,7 @@ What 0.8.0 lacks is overlayfs (`--overlay`, added in 0.9.0): a future "throwaway
|
||||
**The runtime bundle runs there — checked, not assumed.** The webspace has glibc 2.36 (Debian 12), below the dev machine's 2.39, which by ADR 0006's original wording would have ruled the bundle out.
|
||||
That wording was wrong and has been corrected: the bundle's highest required symbol version is `GLIBC_2.15`, because `jlink` copies Temurin's prebuilt binaries rather than compiling anything.
|
||||
So no container build and no second build machine are needed for this platform.
|
||||
The bundle's `java.desktop` module does carry X11, ALSA and freetype dependencies, but only in the AWT libraries, which a headless werkator never loads — as on vm4006.
|
||||
The bundle's `java.desktop` module does carry X11, ALSA and freetype dependencies, but only in the AWT libraries, which a headless Werkator never loads — as on vm4006.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -59,7 +59,7 @@ No root on the host, no Docker daemon, no changes to the native and Docker runti
|
||||
|
||||
`debootstrap`/`mmdebstrap` are not available on the webspace, so the rootfs is **not created on the target system**.
|
||||
It is built once elsewhere (any machine with Docker or root, e.g. a container VM) and distributed as an archive, e.g. `werkator-buildenv-trixie-java21.tar.zst`, containing Debian plus all build dependencies (JDK 21, git, locales, project-specific tools).
|
||||
werkator unpacks it on demand (`tar --no-same-owner`) into `.git/werkator/buildenv/<envKey>/rootfs` — **not** into the working tree.
|
||||
Werkator unpacks it on demand (`tar --no-same-owner`) into `.git/werkator/buildenv/<envKey>/rootfs` — **not** into the working tree.
|
||||
Like the Docker image and the Gradle cache volume, the environment is shared across all branch worktrees and survives worktree pruning; `<envKey>` derives from a hash of the configured archive source, so an environment-version change unpacks a fresh rootfs and stale ones can be pruned.
|
||||
|
||||
### Configuration
|
||||
@@ -93,7 +93,7 @@ bwrap --unshare-user --unshare-pid --die-with-parent --uid 0 --gid 0 \
|
||||
|
||||
- Network stays shared with the host (Gradle needs it); isolation is weaker than Docker's per-container network.
|
||||
- No Docker inside the sandbox, so no Testcontainers-based tests; build commands must select a Docker-free test subset.
|
||||
For werkator's own build this means `TestcontainersSmokeTest` must become conditional (`enabledIf` docker present) — that change is part of this step.
|
||||
For Werkator's own build this means `TestcontainersSmokeTest` must become conditional (`enabledIf` docker present) — that change is part of this step.
|
||||
|
||||
## Web Access under a Domain (no Docker, no managed nginx)
|
||||
|
||||
@@ -103,7 +103,7 @@ Three platform-side prerequisites, none of them code:
|
||||
|
||||
1. **Book the "eigener Serverdienst" option** — a service user plus one reserved localhost port, requested from `service@hostsharing.net` stating the service user and the number of ports.
|
||||
Surcharged on Managed Webspaces (RAM contingent in 128 MB steps), included on Managed Servers.
|
||||
The port number is **assigned by Hostsharing** (wiki examples use 34567, 38005/38006), so it goes into `server.port` — werkator's 18080 is not available by choice.
|
||||
The port number is **assigned by Hostsharing** (wiki examples use 34567, 38005/38006), so it goes into `server.port` — Werkator's 18080 is not available by choice.
|
||||
Sources: [Individuelle Serverdienste](https://www.hostsharing.net/features/individuelle-serverdienste/), [Apache](https://www.hostsharing.net/features/apache/).
|
||||
2. **Run the service as a systemd user unit** — mandatory on Managed Webspaces (no `nohup`, no supervisord); lingering needs a valid login shell configured in HSAdmin, and the account's RAM is capped by a slice (`systemctl status pacs-<account>.slice`).
|
||||
`werkator init --systemd` already generates the unit and the `werkator.env`, whose `JAVA_OPTS=-Xmx…` is what keeps the JVM inside the slice.
|
||||
@@ -113,9 +113,9 @@ Three platform-side prerequisites, none of them code:
|
||||
|
||||
### User model: a dedicated unix user, not the package admin
|
||||
|
||||
werkator runs as its own unix user, e.g. `xyz00-werkator`, with the domain assigned to that same user (`domain.add({set:{name:'…',user:'xyz00-werkator'}})`), so the service, its repository checkout and `~/doms/<domain>/htdocs-ssl/` share one home directory.
|
||||
Werkator runs as its own unix user, e.g. `xyz00-werkator`, with the domain assigned to that same user (`domain.add({set:{name:'…',user:'xyz00-werkator'}})`), so the service, its repository checkout and `~/doms/<domain>/htdocs-ssl/` share one home directory.
|
||||
That is what every Hostsharing service guide does (`xyz00-chat` for Mattermost, `xyz00-tomcat`, `xyz00-cloud` for Nextcloud) and what their user documentation recommends: a domain *can* run under the package admin, but "aus Sicherheitsgründen empfiehlt es sich aber Domains auf separate Domain-Admins aufzuschalten", so a compromise stays inside one home instead of reaching the whole package.
|
||||
Here the argument is stronger than usual, because werkator checks out foreign commits and executes their build scripts — running that as the package admin would undo the sandbox rationale of this very step.
|
||||
Here the argument is stronger than usual, because Werkator checks out foreign commits and executes their build scripts — running that as the package admin would undo the sandbox rationale of this very step.
|
||||
The service user is named when ordering the daemon port anyway.
|
||||
Sources: [Benutzer](https://www.hostsharing.net/doc/managed-operations-platform/benutzer/), [HSAdmin domain](https://www.hostsharing.net/doc/managed-operations-platform/hsadmin/domain/).
|
||||
|
||||
@@ -137,7 +137,7 @@ RewriteRule .* http://127.0.0.1:<assigned-port>%{REQUEST_URI} [proxy]
|
||||
|
||||
Sources: [Mattermost Installieren](https://wiki.hostsharing.net/index.php/Mattermost_Installieren), [Tomcat Installieren](https://wiki.hostsharing.net/index.php?title=Tomcat_Installieren).
|
||||
|
||||
The matching werkator configuration:
|
||||
The matching Werkator configuration:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
@@ -148,7 +148,7 @@ server:
|
||||
enabled: false # the managed nginx container is not used on a webspace
|
||||
```
|
||||
|
||||
**This half needs no code change.** werkator never reconstructs absolute URLs from the request — everything external comes from `server.publicBaseUrl` and the UI links relatively — so the usual reverse-proxy fix `server.forward-headers-strategy` is not needed.
|
||||
**This half needs no code change.** Werkator never reconstructs absolute URLs from the request — everything external comes from `server.publicBaseUrl` and the UI links relatively — so the usual reverse-proxy fix `server.forward-headers-strategy` is not needed.
|
||||
|
||||
Two claims could **not** be verified from a Hostsharing primary source; check them on the target webspace rather than relying on them:
|
||||
|
||||
@@ -169,6 +169,6 @@ Write ADR 0007: bubblewrap user-namespace sandbox as the third build runtime (op
|
||||
|
||||
- The precondition command line above passes on the target webspace; its output is recorded in this file.
|
||||
- `./gradlew ktlintFormat` then `./gradlew build` is green — also on a machine without Docker (Testcontainers smoke test skipped, not failed).
|
||||
- On a Managed Webspace: werkator (from the runtime bundle) builds a real branch of a repo inside the bwrap sandbox; git commands work in the worktree; `.git/werkator/` is not readable from the build; a write to `/usr` fails.
|
||||
- On a Managed Webspace: Werkator (from the runtime bundle) builds a real branch of a repo inside the bwrap sandbox; git commands work in the worktree; `.git/werkator/` is not readable from the build; a write to `/usr` fails.
|
||||
- On the same webspace: the UI answers over HTTPS under the domain through the Apache `.htaccess` proxy, the service survives a logout and a reboot (systemd lingering), and Gitea statuses carry `publicBaseUrl` links that resolve.
|
||||
- Docs updated: `docs/configuration.md` (bwrap section), architecture skill (third runtime), ADR 0007, and `docs/deployment.md` gains "Hostsharing Managed Webspace" as a third deployment variant — written only once the setup above is verified on a real webspace, not from this plan.
|
||||
|
||||
@@ -30,7 +30,7 @@ Note that `branches:` also exists as the *selector* key **inside** a build defin
|
||||
|
||||
## Code
|
||||
|
||||
- `config/werkatorConfig.kt`: drop the `branches` property and `AutoBuildConfig`, and `BranchConfig.autoBuild` with it.
|
||||
- `config/WerkatorConfig.kt`: drop the `branches` property and `AutoBuildConfig`, and `BranchConfig.autoBuild` with it.
|
||||
Rename `BranchConfig` to `BuildSettings` — with the section gone it is no longer a schema type but the resolved answer to "what does this build run", which is all it is used for.
|
||||
`buildSettings(branch, build)` then no longer needs the branch lookup: `effectiveBuildDefinitions()[build]?.applyTo(BuildSettings()) ?: BuildSettings()`.
|
||||
Keep the `branch` parameter — the callers pass it and a later per-branch concern would need it back.
|
||||
@@ -66,7 +66,6 @@ Add a test that a build whose definition was removed from the config still resol
|
||||
- `docs/configuration.md`: delete the section "The legacy `branches` section"; drop the "only while nothing defines a build" qualifier from the branch-layer section.
|
||||
- `AGENTS.md`: the invariant bullet starting "`builds` or the legacy `branches`, never both" becomes the rejection rule.
|
||||
- `.claude/skills/architecture/SKILL.md`: `resolveBuildSections` no longer chooses between two sections.
|
||||
- `docs/migration-from-legacy.md`: already maps to `builds.<name>`; re-check it reads correctly without the legacy section existing.
|
||||
|
||||
## Production
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Read `README.md` first.
|
||||
## Why
|
||||
|
||||
On 2026-08-30 the Gitea token in the machine config on vm4006 was replaced by a placeholder string.
|
||||
For 57 minutes werkator failed `git fetch --prune origin` every ten seconds and wrote 297 warnings to the journal.
|
||||
For 57 minutes Werkator failed `git fetch --prune origin` every ten seconds and wrote 297 warnings to the journal.
|
||||
The branches view showed a calm, ordinary list the whole time: every branch with its last build, nothing amiss.
|
||||
The failure was noticed only because an expected build did not start, and it took reading the journal to see why.
|
||||
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
# werkator Rewrite Plan
|
||||
# Werkator Rewrite Plan
|
||||
|
||||
This directory contains the step-by-step plan for rewriting `legacy/werkator` (bash) as the Kotlin/Spring application in this repository.
|
||||
Each step file is self-contained and sized for one focused Claude Code session.
|
||||
@@ -86,9 +86,9 @@ Added after a silent 57-minute fetch outage on vm4006 (2026-08-30):
|
||||
|
||||
- [x] `19-watcher-health-in-ui.md` — show an unreachable origin in the web UI instead of only in the journal
|
||||
|
||||
Added for running werkator on Hostsharing Managed Webspaces (2026-08-10):
|
||||
Added for running Werkator on Hostsharing Managed Webspaces (2026-08-10):
|
||||
|
||||
- [ ] `17-bwrap-build-runtime.md` — werkator on a Managed Webspace: bubblewrap user-namespace build sandbox with a prepared rootfs (precondition check first — see the step file), plus web access under a domain via the platform's Apache proxy and Let's Encrypt
|
||||
- [ ] `17-bwrap-build-runtime.md` — Werkator on a Managed Webspace: bubblewrap user-namespace build sandbox with a prepared rootfs (precondition check first — see the step file), plus web access under a domain via the platform's Apache proxy and Let's Encrypt
|
||||
|
||||
Steps 01–03 are independent of each other.
|
||||
Steps 04–06 depend on 01–03.
|
||||
|
||||
Reference in New Issue
Block a user