Commit Graph
170 Commits
Author SHA1 Message Date
mhoennigandClaude Opus 5 0428cf6737 Config files declare their GitTally version (v0.9.18)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 10:18:21 +02:00
mhoennigandClaude Opus 5 8608170f5b Configuration files declare the GitTally they are written for
Until now a version that renames or drops a key did not fail — it silently
ignored what it no longer understood, and the effect surfaced as a build
doing the wrong thing. Both directions of that happened within two days:
a branch config using `??:00` on a GitTally that did not know it yet, and a
`builds.maxConcurrent` that had moved to another section.

    gitTally:
      version:
        since: "0.9.18"   # enforced
        below: "2.0"      # release marker; GitTally decides how strictly

There is deliberately no version of the file format (no `apiVersion`): no API
is involved — GitTally reads its own configuration — and only one generation
is ever supported. The declaration exists to make an incompatibility
nameable, never to run two parsers.

`since` is hard in both directions. Too new a requirement is refused, and so
is a file written before the version in which the configuration format last
broke (`ConfigVersions.FORMAT_BROKE_IN`, empty for now) — that check needs no
declared ceiling, because GitTally knows its own breaking changes.

`below` is the team's release marker and only warns: an unmaintained caution
value must never stop a CI. The routine it serves is the one known from IDE
plugins — new version, warning, try it, then raise the marker and commit.

The reach of a violation follows the layer: the machine and project configs
abort the start naming the file and the rollback, while an incompatible
branch config fails only that branch's builds. A branch cut before a
migration must not stop the server or hold up the branches that are fine.
A file that declares nothing keeps working, and the CLI prints one line
instead of a stack trace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 10:15:35 +02:00
mhoennigandClaude Opus 5 fa183ef1db Record the v0.9.17 deployment to vm4006
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:37:43 +02:00
mhoennigandClaude Opus 5 763af51501 Refresh on return from the background (v0.9.17)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:36:58 +02:00
mhoennigandClaude Opus 5 1d28ec2a3f Refresh the page when it comes back from the background
Durations of running builds are ticked client-side, so a page left in the
background kept counting up a build that had long finished on the server —
on a phone the tab is frozen for an hour and comes back showing a build
"running" for an hour.

Resuming now re-arms the poll unconditionally instead of only when the page
was stopped: a frozen page may never deliver the hidden event, and its
throttled interval then fires whenever the browser feels like it. `pageshow`
and `focus` are listened to as well, because not every browser reports a
returning page as a visibility change; resume events arriving together are
collapsed into one fetch.

Verified in a browser: hidden for 11s over a 10s interval makes no request,
a lone focus event makes exactly one, and a second resume event right after
makes none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:30:39 +02:00
mhoennigandClaude Opus 5 cd0596b17a Record the v0.9.16 deployment to vm4006
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:12:01 +02:00
mhoennigandClaude Opus 5 dfbd67fa37 Hourly build slots and the true build command on the artifact page (v0.9.16)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:57:51 +02:00
mhoennigandClaude Opus 5 1a84b1fc1b Show the command a build actually runs on its artifact page
The artifact page read `branches.<branch>.buildCommand` from the primary
config only, so for a named build on a branch with its own config it showed
a command that build never ran — on vm4006 it showed the host config's
command for a build that ran the branch definition's `pitestFull`.

Resolving "what does this build run" now has one implementation,
`GitTallyConfig.buildSettings(branch, build)`: the branch entry with the
build definition's overrides applied last. The executor uses it, and the
page resolves it against the branch layer committed at the build's own
commit — the same inputs the executor had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:52:22 +02:00
mhoennigandClaude Opus 5 939d8eeb9c Hourly scheduled builds via a ??:MM slot
`atTimes: ["??:05"]` runs a build five past every hour. The pattern expands
to its 24 concrete slots before the due-slot match, so each hour is its own
slot in the trigger state and fires once — the existing per-slot semantics
carry over unchanged, including that only the latest due slot of a day
triggers and that a slot whose pool is still building is retried until it
starts.

Only the hour may be a wildcard; anything else is skipped with a warning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:52:22 +02:00
mhoennigandClaude Opus 5 08a8a0bbb4 Record the v0.9.15 deployment to vm4006
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:38:57 +02:00
mhoennigandClaude Opus 5 ca3e758cdc Ignore a leftover builds.maxConcurrent instead of refusing to start
The concurrency limit moved to executor.maxConcurrent without an alias, so
the old key binds a scalar where a build definition belongs and failed the
whole configuration. But that configuration is committed in the watched
repository, and a repository's master is not always changeable right away —
an installation must not be stuck on a key it is meant to forget.

A `builds` entry that is not a mapping is now dropped with a warning (once
per key, the config is loaded every poll cycle), naming executor.maxConcurrent
for the key that moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:37:10 +02:00
mhoennigandClaude Opus 5 f5871a0442 The branch config takes precedence, including its build definitions
A branch's committed .gittally.yml describes that branch's CI, so it wins
over the project and repo-install config — the `builds` section included.
Pinning it was wrong: a new build definition can only be tried out by
committing it on a branch, and pinned it neither took effect at build time
nor existed for the watcher, so the job silently never ran.

The watcher now decides per branch from that branch's own definitions,
reading its committed config via `git show` and caching it by head commit,
so the read happens only when the branch moved; an unreadable config falls
back to the primary definitions instead of failing the poll cycle. A
branch's definitions are evaluated for that branch alone, so a definition
committed on one branch can never trigger builds of another.

The pinned set is reduced to what does not describe this branch's build:
secrets (`git`), the host and repository sections (`server`, `gitea`,
`executor`, `watcher`), the sandbox policy (`docker.enabled`/`network`),
and the trust gate (`requirePullRequest`). Letting a branch set its own
build command through a definition grants no new power — `branches.*.
buildCommand` always allowed exactly that — while the sandbox and the gate
decide whether untrusted branch code runs on the host at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:29:26 +02:00
mhoennigandClaude Fable 5 a3faa172f8 Concurrency limit under executor.maxConcurrent (v0.9.15)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 21:24:51 +02:00
mhoennigandClaude Fable 5 495b7f3282 Move the concurrency limit to executor.maxConcurrent
builds.maxConcurrent mixed an execution setting into the build
definitions as a reserved key. The limit now lives in the new executor
section (pinned like the builds section, enforced for all builds
regardless of trigger), default 1, without a compatibility alias — a
leftover builds.maxConcurrent key is rejected as an invalid build
definition. Recorded as a follow-up in ADR 0007.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 21:04:25 +02:00
mhoennigandClaude Fable 5 e118c239f8 Record the v0.9.14 deployment to vm4006
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 20:50:13 +02:00
mhoennigandClaude Fable 5 3ff27dac0b Named build definitions (v0.9.14)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 20:47:28 +02:00
mhoennigandClaude Fable 5 0e8db18e69 Build definitions with onPush/atTimes replace branch-owned schedules
ADR 0007: the YAML builds section (next to the reserved maxConcurrent
key) defines named builds (jobs) with onPush/atTimes triggers, branch
selectors (name globs, activeWithin age filter), and build-setting
overrides applied last over the merged branch config. The implicit
default build (onPush over all branches) preserves the previous
behavior; the section is pinned against the worktree layer.

Results record the job name; restart, retry, and startup recovery
re-run by it, resolving settings from the current config. A non-default
build records under the <branch>@<build> pool with its own row,
retention count, and permanent latest-green link.

branches.*.autoBuild stays as a deprecated alias (plain times only);
the unreleased-in-practice v0.9.13 per-slot buildCommand/name syntax is
removed again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 20:06:02 +02:00
mhoennigandClaude Fable 5 5051c7bb99 Propose ADR 0007: build definitions replace branch-owned schedules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 19:52:39 +02:00
mhoennigandClaude Fable 5 6f80190581 Record the v0.9.13 deployment to vm4006
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 19:16:22 +02:00
mhoennigandClaude Fable 5 a7ac0a72c3 Per-slot auto-build commands and named nightly pools (v0.9.13)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 19:13:12 +02:00
mhoennigandClaude Fable 5 8aee3190ea Let auto-build slots run their own build command under their own name
A branches.<name>.autoBuild.times entry is now either a plain HH:MM
string or an object with time, its own buildCommand, and a name, so a
nightly slot can run a fuller check than the on-commit builds. The
watcher passes the slot's command and name to the executor, persisted in
the build result — UI restarts, gittally retry, and the startup recovery
repeat a build with the command and name it originally ran under.

A named slot (e.g. master@nightly) gets its own pool: repository
grouping, retention count, branches-view row (sorted after its branch),
latest status, and permanent latest-green artifact link are keyed by the
build name, while origin lookups, gone-branch pruning, worktrees, and
Gitea links/statuses stay keyed by the real branch. Without a name,
slot builds share the branch's pool as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 19:11:35 +02:00
mhoennigandClaude f292badac1 Record the v0.9.12 deployment to vm4006
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-26 13:36:47 +02:00
mhoennigandClaude 5bbe422734 Visible mid-merge builds and dedup'd triggers (v0.9.12)
Deployment bundle: the prune protection for queued/running builds and the
manual-trigger dedup.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-26 13:35:17 +02:00
mhoennigandClaude 09ed193ac7 Never prune queued/running builds; dedup manual triggers
Two defects seen live on vm4006 when a merged branch was deleted from origin
while its last build still ran:

- The result prune removed the PENDING/RUNNING entries of branches gone from
  origin, so the executing build vanished from UI and history and the queue
  looked stuck. Prune now never touches a PENDING or RUNNING entry (worktrees
  were already protected). The startup recovery closes out an orphaned PENDING
  of a gone branch as INTERRUPTED, so the new immunity cannot leak entries.

- The apparent hang invited restart clicks, and each click stacked another
  build of the same commit. startBuild now returns the already queued or
  executing build of the same branch and commit instead of a duplicate;
  cancel-requested builds do not block re-queueing, and re-running a finished
  build stays possible.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-26 13:30:11 +02:00
mhoennigandClaude d2128afb6d Record the v0.9.11 deployment to vm4006
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-14 12:22:04 +02:00
mhoennigandClaude 349a01183d Fast-forwarded branch refs and failure-marked logs (v0.9.11)
Deployment bundle: the watcher's local-ref fast-forward and the failed badge on
the logs that carry the build tool's failure line.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-14 12:19:26 +02:00
mhoennigandClaude 9b992c71ed Fast-forward local branch refs at the end of each poll cycle
Build worktrees share the primary checkout's .git, so build tools can read
refs/heads there. Since the rewrite never moved those refs, they stayed frozen at
the last checkout: hs.hsadmin.ng's prQuickCheck compares master with
origin/master and therefore failed every build once origin moved on.

The sync runs after the enqueue decision on purpose — a local ref lagging behind
origin is exactly how the watcher recognizes new commits, so keeping the refs in
sync earlier (cron job, mirroring refspec, or this step moved up) would silence
the branch instead of building it.

Fast-forward only, as a compare-and-swap against the commit just read: diverged
or ahead branches stay untouched, and the checked-out branch is advanced with
merge --ff-only, which refuses to overwrite conflicting uncommitted changes.
Switched off with watcher.fastForwardLocalRefs: false.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-14 11:29:42 +02:00
mhoennigandClaude 129f38143d Warn that a statusContext rename mid-build strands a pending Gitea status
Renaming gitea.statusContext while a build runs splits that build over two
contexts: the abandoned one keeps its 'build running' entry, and Gitea reports
the commit as pending forever. Gitea cannot delete a commit status, so document
the manual closing POST as the only way out.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 11:17:24 +02:00
mhoennigandClaude 903a87e547 Correct the runtime bundle's glibc rule: the JDK vendor sets the floor
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>
2026-08-11 10:23:15 +02:00
mhoennigandClaude c77de1c725 Record the webspace's bwrap and kernel versions, and the glibc consequence
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>
2026-08-11 10:21:00 +02:00
mhoennigandClaude 3f93882dda Record the passing bwrap precondition check on a Managed Webspace
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>
2026-08-11 10:19:21 +02:00
mhoennigandClaude 021d97eca8 Mark the logs that carry a failure line on the artifact index
The artifact index listed the stored logs as bare file names, so a red build
gave no hint which of build.log, build.stdout.log and build.stderr.log actually
explains it — with stdout and stderr stored separately, the failure is usually
in only some of them.

Each log of a non-green build is now scanned for an upper-case FAILED/FAILURE,
which covers BUILD FAILED, Maven's BUILD FAILURE and Gradle's per-test
"SomeTest > works() FAILED"; lower-case prose does not count. The scan streams
line by line with an early exit and reads ISO-8859-1, so no byte sequence of a
build log can fail to decode. Logs of a successful build are not scanned at all
— that saves reading megabytes per page view and avoids an alarming badge on a
green build whose log mentions a deliberately failing sub-build.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 10:11:45 +02:00
mhoennigandClaude cff365767e Plan step 17: run on a dedicated unix user, and cap the unit's resources
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>
2026-08-11 08:52:24 +02:00
mhoennigandClaude d5f2784b60 Plan step 17: web access on a Managed Webspace, alongside the sandbox
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>
2026-08-11 08:37:58 +02:00
mhoennigandClaude 2351ddcecd Document the update procedure for an existing installation
deployment.md only said "replace the jar and restart", which left out the
runtime-bundle case entirely — including the trap that the tarball
unpacks to a `gittally/` directory and must not be extracted over ~/opt.
Both variants now list the actual commands, with a rollback copy and the
note that a restart is safe because in-flight builds are re-enqueued.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 08:09:01 +02:00
mhoennigandClaude 960dc97e75 Record the v0.9.10 deployment to vm4006
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 08:08:15 +02:00
mhoennigandClaude dbb26be38b Settle TODO 5: public build logs are intended, not a leak
The watched projects (GitTally and hs.hsadmin.ng) are open source, keep
no secrets in the repository and build against test data, so credentials
appearing in a log are fixtures. The builds neither deploy nor sign; the
only planned artifact is a jar. Public logs are also the point: a red
build has to be diagnosable from the link in the Gitea status without a
login.

Recorded as a property of the watched project rather than of GitTally —
deployment.md now says that an installation whose builds touch real
credentials has to stay off the public internet, since GitTally offers no
per-endpoint gating.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 08:06:04 +02:00
mhoennigandClaude a734d91918 Stop embedding the control token in every page (v0.9.10)
Closes TODO 2 of the security audit in docs/prs/2026-07-08-PR#000.

Every rendered page carried the live control token in a meta tag so that
gittally.js could send it, but no GET is authenticated — so `curl … |
grep gittally-control-token` handed the token to anyone, and read access
was effectively write access.

Reading stays fully public, which is a requirement rather than an
oversight: build states, logs and artifacts must be linkable from Gitea,
chats or tickets without a login. Only the distribution of the token
changed. The meta tag is gone; gittally.js keeps the token in
localStorage and asks for it once per browser, so knowing it requires
shell access to `.git/gittally/control-token` on the host. A token the
server rejects is dropped and asked for once more, so a rotated secret is
not a dead end. As a request header it stays inherently CSRF-safe.

The five branches of that flow (first use, reuse, stale token, cancelled
prompt, wrong token twice) were exercised against the real source with a
throwaway node harness; the UI test now asserts the token does not appear
in the rendered page. `docs/deployment.md` gained a "Control Token"
section on the public-read/token-protected-write split.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 08:00:09 +02:00
mhoennigandClaude ea0b67d331 Record the v0.9.9 deployment to vm4006
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 07:45:45 +02:00
mhoennigandClaude a4c995592f Header-only control token, masked secrets, loopback default (v0.9.9)
Finishes the small items of the security audit in
docs/prs/2026-07-08-PR#000: TODO 3, 4 and 7.

The three mutating endpoints of BuildsApiController no longer accept the
control token as a `token` query parameter — only the X-GitTally-Token
header, which the bundled UI has always used. URLs end up in access logs,
proxy logs, browser history and Referer headers, and the token never
expires, so a historical log capture would yield a valid credential.

`config:print` masks git.token as `***` on both the raw and the --full
path and names the new --show-secrets flag in a leading YAML comment, so
the output stays parseable when piped. The setup script points at
--show-secrets where it used to steer the operator to the plain token.

`server.bindAddress` now defaults to 127.0.0.1: neither the UI nor the
API authenticates read access, so reaching GitTally should require the
host's reverse proxy. Existing .gittally.yml files keep their explicit
value; the managed nginx container needs `0.0.0.0` set deliberately,
which is noted in the release notes, docs/configuration.md and
docs/deployment.md.

Released as v0.9.9, which also carries the previous two commits.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 07:38:33 +02:00
mhoennigandClaude dea6770998 Harden secret-file creation, token comparison and git refname args
Works off the security audit in docs/prs/2026-07-08-PR#000: TODO 1, 8, 9
and 10, the four items that need no design decision.

New `SecretFiles` creates files holding secrets with mode 0600 and their
directories with 0700 *at creation*, as a file attribute, instead of
writing at the umask default and chmod-ing afterwards — that left a
window in which the Gitea token was world-readable, which matters on a
multi-tenant host. It is used by `init` for .git/gittally/.gittally.yml
and by `ControlTokenService` for the control token; the shell setup
script now writes its YAML in a `umask 077` subshell for the same reason.

`ControlTokenService.matches` hashes both sides with SHA-256 before
`MessageDigest.isEqual`, so the comparison always runs over two 32-byte
buffers and cannot return early on a length mismatch.

`GitService.checkout` and `fetchBranch` pass `--` before the refname, so
a branch named like an option cannot be read as one. `resetHardToOrigin`
keeps its plain form: `git reset --hard -- <commit>` is rejected outright
and its argument is already `origin/`-prefixed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 07:09:34 +02:00
mhoennigandClaude 3eb41d4c66 Stack page title and repository name in the mobile header
On narrow screens the page title and the repository name shared one flex
line and wrapped unreadably. Below 680px the h1 now becomes a grid: the
logo spans both rows on the left, the title takes the first line and the
repository name the second (slightly smaller, wrapping anywhere so long
owner/repo names cannot overflow). The desktop layout is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 06:59:52 +02:00
mhoennigandClaude Fable 5 daba7f6acc Record the v0.9.8 deployment to vm4006
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 21:37:30 +02:00
mhoennigandClaude Fable 5 962836beaf Stable per-branch report URLs and a reachable live view (v0.9.8)
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>
2026-08-10 21:27:53 +02:00
mhoennigandClaude Fable 5 c1869ea427 Link index-less report pages from the artifact index (v0.9.8)
Gradle's --profile report is archived but was unreachable: report discovery
only looked for index.html, while the profile page carries a timestamped
file name. Scan reports/ and its direct sub-directories for HTML pages that
no index covers, so a report tree cannot flood the index with inner pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 21:11:15 +02:00
mhoennigandClaude Fable 5 632e4396e4 Plan step 17: bubblewrap build sandbox for Hostsharing Managed Webspaces
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>
2026-08-10 20:42:48 +02:00
mhoennigandClaude Fable 5 e1f3f5f384 Install a nightly Docker cleanup timer with init --systemd (v0.9.7)
Port of the legacy host's docker-prune.timer: 02:00 host time,
Persistent=true, docker system prune -af — but without --volumes, so
the per-repository Gradle cache volumes survive. The units are
host-global; several GitTally instances share one timer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 19:22:44 +02:00
mhoennigandClaude Fable 5 74bc4c2d73 Mention the utilization highlighting in the 0.9.6 release notes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 19:09:21 +02:00
mhoennigandClaude Fable 5 f2d4dbfda0 Highlight critical utilization on the system page (v0.9.6)
The Current cell of CPU/RAM/disk used turns orange from 80% of the
total and red from 90%. UiFormats.utilizationClass and the mirrored
utilizationClass in gittally.js apply the same thresholds; unavailable
metrics (n/a) are never highlighted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 19:08:23 +02:00
mhoennigandClaude Fable 5 428d1a06cb Add a release-notes page linked from the footer version (v0.9.6)
Reconstructed from the commit history since the first production
deployment; the initial entry is the 0.9.0 port of the legacy bash
script to Kotlin/Spring Boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 18:02:30 +02:00