* Add the bubblewrap build runtime (step 17, ADR 0007)
BwrapBuildRunner: third runtime behind BuildRunner for hosts without root
and without Docker (e.g. Hostsharing managed webspaces). Shells out to the
bwrap CLI, unpacks a prepared rootfs on demand into
.git/werkator/buildenv/<envKey>/rootfs, reuses the Docker runner's git
metadata mounts, and returns the attached bwrap process for streaming and
cancellation.
Config: bwrap.enabled/rootfs/env on BranchConfig and BwrapOverrides on
BuildDefinition; enabled/rootfs are pinned like the docker sandbox policy.
Docker and bwrap are mutually exclusive per build, rejected in
buildSettings instead of picked silently. DispatchingBuildRunner routes
bwrap; InitCommand template, docs/configuration.md and AGENTS.md in sync.
* bwrap rollout tooling: remote script, prerequisites disk/quota check, absolute workspace binds
- tools/remote: central remote control script with check-prerequisites, install and build commands
- tools/werkator-build-prerequisites.sh: compact PASS/FAIL output, target-dir parameter, free-space and
group-quota headroom checks against the ~5 GiB build footprint, home-filesystem reference
- BwrapBuildRunner: bind workspace and home at absolute paths resolved against repoDir — a relative
path made bwrap create mountpoints inside the read-only rootfs (seen on the webspace); regression test
- TestcontainersSmokeTest: gated with enabledIf docker available (skip, never fail, without a daemon)
- docs: configuration reference, step-17 plan notes, PR-doc
* bwrap: bind the repo read-write before the workspace so mountpoints are creatable
bwrap creates mountpoints for bind destinations inside the sandbox; with only a
read-only rootfs bound at /, creating them for the workspace under .git/werkator/
worktrees failed with 'Read-only file system' (seen on the webspace). Binding the
repo dir read-write first provides the base; the git metadata mounts then layer
the usual isolation on top (read-only .git, tmpfs mask over .git/werkator,
read-write worktree admin dir).
* bwrap: pre-create bind mountpoints inside the unpacked rootfs
bwrap mkdirs mountpoints for bind destinations against the sandbox view; with the
rootfs ro-bound at / every destination missing from the rootfs (the repo dir under
/home/storage/... on the webspace) fails with 'Read-only file system'. The rootfs
directory is a plain host dir, so create the mountpoints there before launching
bwrap; it then finds them and has nothing left to create.
* bwrap: skip existing rootfs files when pre-creating bind mountpoints
/etc/resolv.conf is a file the rootfs already ships; createDirectories threw on it.
Only missing directories are created now.
* bwrap: pre-create proc/dev/tmpfs mountpoints in the rootfs too
The rootfs archive ships no /proc or /dev (excluded when packed), so bwrap failed
mkdir'ing their mountpoints against the read-only root.
* bwrap: bind the workspace after the git metadata mounts
The tmpfs mask over .git/werkator shadowed the earlier workspace bind, because the
worktree lives under .git/werkator/worktrees — chdir then failed with ENOENT. The
workspace bind now comes last and shadows the mask at exactly its own path.
* systemd resource limits and webspace start command (step 17, web access)
- server.systemd.memoryMax/tasksMax (empty = directive omitted): on platforms where
the service runs in a shared memory slice (Hostsharing Managed Webspaces) a
runaway Gradle build must not starve the whole package; init --systemd reads the
effective config and bakes the values into the generated unit
- tools/remote werkator start: writes server settings (assigned port, loopback
bind, publicBaseUrl, nginx off) plus the Apache reverse-proxy .htaccess into
~/doms/<domain>/subs/www, runs init --systemd and enables the user unit
- docs/configuration.md documents the new keys
* tools/remote: env-based configuration and background port-forward
All connection and deployment values come from .env in the repository root
(WERKATOR_REMOTE, WERKATOR_PATH, WERKATOR_PORT, WERKATOR_DOMAIN,
WERKATOR_LOCAL_PORT, optional WERKATOR_BRANCH/MEMORY_MAX/TASKS_MAX/ROOTFS);
missing values fail with a pointing error instead of positional parameters.
- port-forward is now 'tools/remote port-forward start|stop' with a detached
ssh tunnel, pid file under /tmp, and idempotent start
- start restarts the systemd unit after updating the machine config
- control-token generates the token in place when the server has not yet
- the rootfs archive default moves to build/ (already gitignored)
* renaming from gitTally to Werkator
* 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>
* Move the pre-rename state directory at the first start
The configuration is found under either name, the state is not: build
history, control token, auto-build slots and worktrees live at one fixed
path. An installation that updates without moving `.git/gittally` would
not fail — it would come up with an empty history and a fresh control
token, quietly. So the first start moves it instead of the release notes
asking for it.
Only when the old directory exists and the new one does not. Where both
exist nothing is touched and a warning names the leftover: which of the
two is the live state is not something to guess. A failed move is an
error in the log, never an abort — a CI must not hang on it.
The worktrees are dropped rather than moved, since they point at their
old path in both directions; `GitWorktreeWorkspaces` prunes the stale
admin entry and recreates each on its branch's next build. A generated
systemd unit moves with the directory and leaves its symlink dangling,
which is warned about — the running service is unaffected, the next
start is not.
Runs from `CliRunner`, before any command resolves a path under the
directory, and so before the second context of `server` exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Document PR#1: the rename to Werkator
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Drop the legacy env-to-YAML conversion from the setup tool
The old bash script configured itself through `GITTALLY_*` environment
variables. The blanket rename rewrote those literals, so the converter
was looking for `WERKATOR_*` — a spelling no host has ever written. Fed
a real legacy file it would have found nothing and written an almost
empty configuration, without an error, which is the same silent failure
this rename is otherwise careful to avoid.
The conversion has served its purpose with the vm2176 to vm4006
migration, so it goes instead of being repaired. What remains is the
setup of a new instance: the preconditions, the credential prompt, and
the machine configuration written mode 600 — now carrying the host's
public URL as well, since that is host-specific too. Everything the
repository builds comes from `init` and its templates.
It also stops emitting a legacy `branches:` section, which step 18 is
about to reject outright.
`docs/plan/00-legacy-analysis.md` and `13-nginx-tls.md` get the real
`GITTALLY_*` spelling back: they record what the old script read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Describe this repository's build with a build definition
Its own `.werkator.yml` still used the deprecated `branches` section
with an `autoBuild` schedule that was switched off. That section is read
only while nothing defines a build at all, and step 18 rejects it by
name — so this repository would have blocked the precondition of that
step, which asks that no configuration still in play carries it.
Nothing about the build changes: `builds.default` with `trigger.onPush`
is a build of every new commit on every branch, which is what the branch
section said. `config:print --full` resolves the definition completely
and logs no deprecation warning any more.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Stop documenting the pre-rename fallback for users
Exactly one repository is configured with the old names, and it is
migrated by hand in the same move as this release. The fallback is
therefore a transition of days, not a feature anyone reading the release
notes or the configuration reference has to plan around.
Removed from `releases.html` and `docs/configuration.md`. The mechanism
itself is unchanged and stays described where it is worked on: in
`ConfigFiles`, in `StateDirMigration`, and in the migration plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Bring the PR-doc to its final state
The `statusContext` question is answered and marked as decided rather
than left standing: it is the one value a human reads as a label, and it
stays lowercase because Gitea matches it and the client reads it back,
which makes it a value.
Also records that the pre-rename fallback is deliberately absent from
the release notes and the configuration reference.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Release v1.0.0: Werkator
The release after 0.9.21 is 1.0.0, because a product that changes its
name is better off counting from one under it. The release note says as
much, so the jump is not read as a claim about maturity — plan steps 14,
17 and 18 are still open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Correct the PR-doc about the version
It claimed the PR carries no version bump, which the release commit made
untrue, and records why the number is 1.0.0 instead of 0.9.22.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Point the legacy references at the history
`legacy/gitTally` was removed from the tree with the rename, but the
README still described it as a reference kept in the repository, and the
plan told an executing session to read parts of it — including step 14,
which is open.
The README section is gone; `docs/plan/README.md`, step 14 and the
legacy analysis now say where the script actually is
(`git show 7f55068^:legacy/gitTally`). Executed steps and ADR 0004 keep
their wording: they record what was true when they ran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Retarget the links in the historic PR-docs
The package rename moved every file the older PR-docs link to, leaving
60 dead links. Only the link targets are rewritten, never the visible
text and never a statement: those documents record what was true when
they were written, GitTally in the prose included. A snapshot may be
outdated; it should still be navigable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Add the v1.0.0 deployment procedure for vm4006
Measured, not estimated: the state directory is 878 MB, of which 878 MB
are the nine build worktrees. What cannot be recreated is 84 KB, so the
snapshot before an in-place switch is instant and the rollback is one
sequence of moves.
Records the three expected non-failures — a cold Gradle volume, one
image rebuild, containers left under the old label — and that
`gitea.statusContext` needs no attention because it comes from the
watched repository's committed configuration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Rename the machine configuration along with its directory
Found by the deployment to vm4006: the move renames the directory and
leaves the file inside it alone, so the machine configuration ended up at
`.git/werkator/.gittally.yml` — a pair of names the lookup did not
expect, because it pairs directory and file name. The instance resolved
empty credentials, no public URL and none of the host's build
definitions, and said nothing about it. That is the exact failure this
change exists to prevent, produced by the change itself.
`StateDirMigration` now renames the configuration with the directory,
unless one under the current name is already there. `ConfigFiles` carries
`.git/werkator/.gittally.yml` as a third candidate as well, for a
directory somebody moved by hand, where the migration never runs and so
can rename nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Let init see a configuration under its previous name
`init --systemd` runs `init`, and its "already exists" check knew only
the current name. On the one repository still carrying `.gittally.yml`
it therefore wrote a fresh template `.werkator.yml` beside it — and
since the current name wins, that repository would have built the
template's `./gradlew test` instead of what its own configuration says.
Found on vm4006, where the file was created in the watched working tree
and removed again by hand.
Both checks now ask `ConfigFiles`, so init decides existence by the same
rule the loader uses to read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Record the v1.0.0 deployment to vm4006
Deployed from the branch as the final test of PR#1, and it did what a
final test is for: it found two silent-failure defects before the
service was started, both fixed and redeployed in the same window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Drop the control token left under the old localStorage key
The key is named after the product, so the rename left every browser
with a token under `gittally.controlToken`, which nothing reads any more
and which "forget token" can no longer reach. It is a write-scope token
in a browser store, not a password, but a secret nobody owns is worth
one line to remove.
Removed on load. The token on the server is unchanged, so re-entering it
once per browser is all the rename costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Plan tracking the build duration over time (step 20)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* renaming from gitTally to Werkator
* 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>
* Move the pre-rename state directory at the first start
The configuration is found under either name, the state is not: build
history, control token, auto-build slots and worktrees live at one fixed
path. An installation that updates without moving `.git/gittally` would
not fail — it would come up with an empty history and a fresh control
token, quietly. So the first start moves it instead of the release notes
asking for it.
Only when the old directory exists and the new one does not. Where both
exist nothing is touched and a warning names the leftover: which of the
two is the live state is not something to guess. A failed move is an
error in the log, never an abort — a CI must not hang on it.
The worktrees are dropped rather than moved, since they point at their
old path in both directions; `GitWorktreeWorkspaces` prunes the stale
admin entry and recreates each on its branch's next build. A generated
systemd unit moves with the directory and leaves its symlink dangling,
which is warned about — the running service is unaffected, the next
start is not.
Runs from `CliRunner`, before any command resolves a path under the
directory, and so before the second context of `server` exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Document PR#1: the rename to Werkator
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Drop the legacy env-to-YAML conversion from the setup tool
The old bash script configured itself through `GITTALLY_*` environment
variables. The blanket rename rewrote those literals, so the converter
was looking for `WERKATOR_*` — a spelling no host has ever written. Fed
a real legacy file it would have found nothing and written an almost
empty configuration, without an error, which is the same silent failure
this rename is otherwise careful to avoid.
The conversion has served its purpose with the vm2176 to vm4006
migration, so it goes instead of being repaired. What remains is the
setup of a new instance: the preconditions, the credential prompt, and
the machine configuration written mode 600 — now carrying the host's
public URL as well, since that is host-specific too. Everything the
repository builds comes from `init` and its templates.
It also stops emitting a legacy `branches:` section, which step 18 is
about to reject outright.
`docs/plan/00-legacy-analysis.md` and `13-nginx-tls.md` get the real
`GITTALLY_*` spelling back: they record what the old script read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Describe this repository's build with a build definition
Its own `.werkator.yml` still used the deprecated `branches` section
with an `autoBuild` schedule that was switched off. That section is read
only while nothing defines a build at all, and step 18 rejects it by
name — so this repository would have blocked the precondition of that
step, which asks that no configuration still in play carries it.
Nothing about the build changes: `builds.default` with `trigger.onPush`
is a build of every new commit on every branch, which is what the branch
section said. `config:print --full` resolves the definition completely
and logs no deprecation warning any more.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Stop documenting the pre-rename fallback for users
Exactly one repository is configured with the old names, and it is
migrated by hand in the same move as this release. The fallback is
therefore a transition of days, not a feature anyone reading the release
notes or the configuration reference has to plan around.
Removed from `releases.html` and `docs/configuration.md`. The mechanism
itself is unchanged and stays described where it is worked on: in
`ConfigFiles`, in `StateDirMigration`, and in the migration plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Bring the PR-doc to its final state
The `statusContext` question is answered and marked as decided rather
than left standing: it is the one value a human reads as a label, and it
stays lowercase because Gitea matches it and the client reads it back,
which makes it a value.
Also records that the pre-rename fallback is deliberately absent from
the release notes and the configuration reference.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Release v1.0.0: Werkator
The release after 0.9.21 is 1.0.0, because a product that changes its
name is better off counting from one under it. The release note says as
much, so the jump is not read as a claim about maturity — plan steps 14,
17 and 18 are still open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Correct the PR-doc about the version
It claimed the PR carries no version bump, which the release commit made
untrue, and records why the number is 1.0.0 instead of 0.9.22.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Point the legacy references at the history
`legacy/gitTally` was removed from the tree with the rename, but the
README still described it as a reference kept in the repository, and the
plan told an executing session to read parts of it — including step 14,
which is open.
The README section is gone; `docs/plan/README.md`, step 14 and the
legacy analysis now say where the script actually is
(`git show 7f55068^:legacy/gitTally`). Executed steps and ADR 0004 keep
their wording: they record what was true when they ran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Retarget the links in the historic PR-docs
The package rename moved every file the older PR-docs link to, leaving
60 dead links. Only the link targets are rewritten, never the visible
text and never a statement: those documents record what was true when
they were written, GitTally in the prose included. A snapshot may be
outdated; it should still be navigable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Add the v1.0.0 deployment procedure for vm4006
Measured, not estimated: the state directory is 878 MB, of which 878 MB
are the nine build worktrees. What cannot be recreated is 84 KB, so the
snapshot before an in-place switch is instant and the rollback is one
sequence of moves.
Records the three expected non-failures — a cold Gradle volume, one
image rebuild, containers left under the old label — and that
`gitea.statusContext` needs no attention because it comes from the
watched repository's committed configuration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Rename the machine configuration along with its directory
Found by the deployment to vm4006: the move renames the directory and
leaves the file inside it alone, so the machine configuration ended up at
`.git/werkator/.gittally.yml` — a pair of names the lookup did not
expect, because it pairs directory and file name. The instance resolved
empty credentials, no public URL and none of the host's build
definitions, and said nothing about it. That is the exact failure this
change exists to prevent, produced by the change itself.
`StateDirMigration` now renames the configuration with the directory,
unless one under the current name is already there. `ConfigFiles` carries
`.git/werkator/.gittally.yml` as a third candidate as well, for a
directory somebody moved by hand, where the migration never runs and so
can rename nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Let init see a configuration under its previous name
`init --systemd` runs `init`, and its "already exists" check knew only
the current name. On the one repository still carrying `.gittally.yml`
it therefore wrote a fresh template `.werkator.yml` beside it — and
since the current name wins, that repository would have built the
template's `./gradlew test` instead of what its own configuration says.
Found on vm4006, where the file was created in the watched working tree
and removed again by hand.
Both checks now ask `ConfigFiles`, so init decides existence by the same
rule the loader uses to read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Record the v1.0.0 deployment to vm4006
Deployed from the branch as the final test of PR#1, and it did what a
final test is for: it found two silent-failure defects before the
service was started, both fixed and redeployed in the same window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Drop the control token left under the old localStorage key
The key is named after the product, so the rename left every browser
with a token under `gittally.controlToken`, which nothing reads any more
and which "forget token" can no longer reach. It is a write-scope token
in a browser store, not a password, but a secret nobody owns is worth
one line to remove.
Removed on load. The token on the server is unchanged, so re-entering it
once per browser is all the rename costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A wrong git token made the watcher fail every fetch for 57 minutes while
the branches view kept showing its last known list. WatcherState already
records lastFetchError and /api/watcher already serves it; only the UI
never renders it.
The step also folds in the logging volume: one outage wrote 297 identical
warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Not a shrink to the pinned docker keys but a full removal: the pinning
strips the branch layer only, so master's committed config is merged
unstripped and its sandbox policy reaches every branch. The nightly job
moves there with it. A rollback below v0.9.19 is no longer provided for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The machine config on vm4006 lost its legacy branches block, renamed
builds.master to builds.nightly and moved build/libs into the default
definition's artifactDirs. Step 18 still described that work as pending
and named a rollback path to 0.9.18 that the file no longer supports.
What is left there is the shrinking to genuinely host-specific keys,
plus the open question where the nightly rebuild belongs once master
carries its own config.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`onPush`, `atTimes`, `branches`, and `activeWithin` move into a nested
`trigger`. The split is structural on purpose: the inheritance from
`builds.default` now subtracts one key instead of a list of four, so a
selector added to `TriggerConfig` later is non-inheritable by
construction rather than because someone remembered to extend the list.
A definition still writing those keys flat is refused by name, per file
and scoped like the version check — the machine and project config abort
the start, a branch's committed config fails only that branch. Ignoring
them would leave the build with no trigger at all, which is a job that
quietly stops running: the failure this refusal exists to prevent.
Two more things a definition can now say:
- A `!` prefix in `trigger.branches` excludes, and an exclusion wins
whatever the order. `["*", "!master"]` gives one branch a build of its
own without the default build running over it as well — until now the
only way out of that double build was to drop the second definition's
push trigger.
- `statusContext` overrides the Gitea check this build reports as, empty
keeping the repository-wide one. Two builds of a commit shared a
context and overwrote each other's result, so a quick check beside a
long build was not readable in Gitea. Pinned like `requirePullRequest`:
a branch that could pick its context could take over the check a branch
protection rule depends on.
Fixed on the way: a branch whose builds all belong to named definitions
rendered an empty row in the branches view, reading as "never built"
directly beside its real builds. That row was unreachable before the
exclusion patterns made such a branch possible.
ADR 0006 assumed the bundle inherits the build machine's glibc, which would
have blocked the Hostsharing Managed Webspace (glibc 2.36, dev machine 2.39).
Measuring all 33 ELF files of the produced bundle shows GLIBC_2.15 as the
highest required symbol version: jlink copies Temurin's prebuilt binaries
instead of compiling, so the floor is the JDK vendor's build environment and
the build machine's glibc is irrelevant unless the toolchain resolves to a
distribution-packaged JDK.
Co-Authored-By: Claude <noreply@anthropic.com>
bubblewrap 0.8.0 covers every option the sandbox design uses; only overlayfs is
missing, which the design does not need. The kernel version, however, points at
Debian 12 and thus a glibc older than the dev machine's, which would break the
jlink runtime bundle on that host -- noted as a check to run before deploying.
Co-Authored-By: Claude <noreply@anthropic.com>
Plan step 17 hinges on unprivileged user namespaces being usable on a
Hostsharing Managed Webspace. The check ran on h68 and passed with all three
expected signals, so the step is viable there and the sandbox design stands.
Co-Authored-By: Claude <noreply@anthropic.com>
Hostsharing recommends assigning domains to separate domain admins rather
than to the package admin, and all their service guides (Mattermost,
Tomcat, Nextcloud) run the daemon as its own user. For GitTally the
argument is stronger: it checks out foreign commits and executes their
build scripts, so running as the package admin would undo the sandbox
rationale of this step. The service user has to be named when ordering
the daemon port anyway.
Also records a trap found on the way: the RAM contingent is a package
slice, not a per-user quota, so a dedicated user buys isolation but no
extra memory — a runaway Gradle build could starve the whole webspace.
The unit from `init --systemd` sets neither MemoryMax nor TasksMax today,
so adding them (configurable, empty = unset) becomes part of step 17.
Co-Authored-By: Claude <noreply@anthropic.com>
Keeping both halves in one step: a bubblewrap runtime alone would only
prove that sandboxed builds work somewhere, and web access alone would
mean builds running unsandboxed on the webspace. Neither ships value on
its own, so step 17 now covers the whole deployment.
The web half needs no code: Hostsharing provides Apache plus Let's
Encrypt and documents the reverse proxy to a self-hosted service, so the
managed nginx container of ADR 0005 is not used there. What it needs is
the booked "eigener Serverdienst" option with an assigned localhost port,
a systemd user unit (which `init --systemd` already generates), and a
`.htaccess` with a `[proxy]` RewriteRule — with sources from Hostsharing's
own wiki and feature pages. GitTally fits as is, because it builds
external links from `server.publicBaseUrl` rather than from the request,
so no forward-headers handling is required.
Two points are explicitly marked unverified in the step file: the
effective AllowOverride value and whether an unassigned port would bind.
Also ticks steps 15 and 16 in the plan index — both carry a Result
section and are long done.
Co-Authored-By: Claude <noreply@anthropic.com>
A report directory holding a single page is now linked and served as a
directory, so Gradle's --profile report has a stable permanent URL although
its file name carries the build timestamp.
The permanent link moves to the build it resolves to — the branch's latest
green build — and appears on every build table instead of only the branches
view. The Current tab gave way to a link in the artifacts column, shown
while a build runs; /current itself stays routable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third build runtime behind BuildRunner: unprivileged user namespace via
bwrap with a prepared Debian rootfs, for hosts without Docker or root.
The step starts with a one-line precondition check to run on the target
webspace; the step-16 git metadata mounts port 1:1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
./gradlew runtimeBundle packs a jlink-trimmed JRE, gittally.jar, and a
launcher script into one tarball, unpacked to ~/opt/gittally on the target
host; init --systemd works from the bundle unchanged because java.home and
the running-jar path resolve into it. Chosen over a GraalVM native image
(Spring AOT evaluates bean conditions at build time, which cannot represent
the dual-context CLI/server wiring) and over a containerized runtime — see
ADR 0006 and docs/plan/15-runtime-bundle-distribution.md, which also records
the full vm2176-to-vm4006 migration walkthrough.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes from the vm4006 rollout (docs/plan/16-git-in-docker-builds.md):
Rootless daemons map the host user to container root, so running the build
container as --user <host-uid> put it into the subuid range and it could not
even create .gradle in a fresh worktree (legacy only worked because its
ownership-repair chown had accidentally moved build/ and .gradle/ into subuid
ownership in its reused primary checkout). The container now always runs as
--user 0: the unprivileged host user under rootless, real root under rootful
where the ownership repair still applies; under rootless it degenerates to 0:0.
Git now works inside build containers: the primary .git is mounted read-only
with .git/gittally/ masked by an empty tmpfs (git.token and the control token
stay unreachable, the workspace bind resurfaces only the build's own worktree)
and the worktree admin dir mounted read-write for index-refreshing commands.
Verified on vm4006: git log/status succeed, the machine config is invisible,
ref writes fail on the read-only mount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>