Files
werkator/docs/adrs/0006-2026-08-10.runtime-bundle-distribution.md
T
mhoennigandClaude Fable 5 9e7982ce34 Add self-contained runtime bundle distribution (jlink) for hosts without Java
./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>
2026-08-10 15:35:35 +02:00

3.6 KiB

Self-Contained Runtime Bundle Distribution

Status:

  • proposed: 2026-08-10
  • accepted: 2026-08-10
  • rejected: -
  • superseded: -

Decision [accepted]: GitTally is distributed for hosts without a Java runtime as a self-contained runtime bundle — a jlink-trimmed JRE plus gittally.jar plus a launcher script in one tarball, built by ./gradlew runtimeBundle. The JAR stays the primary artifact; a GraalVM native image and a containerized GitTally runtime were rejected.

Context and Problem Statement

GitTally must run on Hostsharing container servers (the primary target, see ADR 0005). These hosts provide git, Docker, make, and systemd user sessions, but no Java runtime, and nothing may be installed system-wide. docs/bootstrapping.md sketched a containerized GitTally runtime as the future answer; that sketch was never validated against the operational details.

Considered Options

  • jlink runtime bundle (trimmed JRE + jar + launcher, one tarball)
  • GraalVM native image (single executable)
  • Containerized GitTally runtime (the original docs/bootstrapping.md sketch)

A jlink-generated JRE with the pinned module list, the boot jar, and a bin/gittally launcher script, packed as gittally-runtime-linux-x64.tar.gz (~66 MB) and unpacked to ~/opt/gittally/ on the target host.

Good:

  • No production-code changes and plain JVM semantics — no new failure modes.
  • git and docker CLIs are used from the host; build worktree paths stay host paths.
  • init --systemd works unchanged: java.home and the running-jar path resolve into the bundle, so the generated unit points at <bundle>/jre/bin/java and <bundle>/lib/gittally.jar (verified).
  • Every JDK 21 ships jlink — no new build-toolchain requirement.

Bad:

  • A directory tree, not a single file (still one tarball to copy).
  • JVM startup (~1-2 s) instead of native-image startup — irrelevant for a long-running server.
  • The jlink image links glibc dynamically: build on glibc ≤ target (Ubuntu 24.04 dev machine: 2.39; vm4006 Debian 13: 2.41 — compatible), same architecture.

GraalVM Native Image

Rejected because Spring AOT evaluates bean conditions at build time, and GitTally's dual-mode wiring cannot be represented in a single AOT arrangement: the CLI context runs without web and with @Profile("!server") CliRunner, while the server subcommand starts a second SpringApplication with WebApplicationType.SERVLET and the server profile gating the watcher/metrics/nginx lifecycles. Whichever profile and web type the AOT processing fixes, the other mode's beans are missing from the binary. Supporting both 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, on top of the usual native-image reflection work (Jackson-bound config and persistence classes, picocli).

Containerized GitTally Runtime

Rejected for operational complexity: the image must bundle git and docker CLIs; the container needs a same-path $HOME mount plus a docker-socket mount and uid/gid mapping so that DockerBuildRunner's --volume $workspace:$workspace sibling mounts keep working; and the systemd unit must be hand-edited to a docker run invocation. This remains the documented fallback if the runtime bundle ever becomes unworkable.

Consequences

  • docs/deployment.md documents the bundle path; the "Future: Docker-based Deployment" section in docs/bootstrapping.md is replaced.
  • The pinned JDK module list in build.gradle.kts must be re-checked (via the documented jdeps command) when dependencies change.
  • Deployment and migration for vm4006 follow plan step 15.