diff --git a/AGENTS.md b/AGENTS.md index 5042e03..86adf2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,6 +74,7 @@ All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc` - **Spring Boot**: 4.0.6 (ADR 0003) - **Rewrite architecture**: JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx — systemd unit behind the host's reverse proxy (ADR 0004) - **Managed nginx/TLS**: revises ADR 0004 — an opt-in nginx+certbot container for hosts without a reverse proxy (e.g. Hostsharing), planned as `docs/plan/13-nginx-tls.md` (ADR 0005) +- **Runtime bundle distribution**: `./gradlew runtimeBundle` builds a jlink-trimmed JRE + jar tarball for hosts without a Java runtime; GraalVM native image and a containerized runtime were rejected (ADR 0006) ## Skills diff --git a/build.gradle.kts b/build.gradle.kts index 4973998..fb7842a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -76,6 +76,96 @@ kotlin { } } +// Self-contained runtime bundle for hosts without a Java runtime (plan step 15, ADR 0006): +// a jlink-trimmed JRE plus gittally.jar plus the packaging/gittally launcher, packed as a tarball. +// The JDK module list below was computed from the exploded boot jar via +// jdeps -q --ignore-missing-deps --multi-release 21 --print-module-deps \ +// --class-path 'BOOT-INF/lib/*' BOOT-INF/classes BOOT-INF/lib/*.jar +// plus runtime-only modules jdeps cannot detect: java.logging (Tomcat JULI), +// jdk.crypto.ec (TLS ECDHE), jdk.management (extended OS MXBeans), jdk.zipfs (nested jar access). +// Re-check the jdeps output when dependencies change. +val runtimeBundleModules = + listOf( + "java.base", + "java.compiler", + "java.desktop", + "java.instrument", + "java.logging", + "java.management", + "java.naming", + "java.net.http", + "java.prefs", + "java.scripting", + "java.security.jgss", + "java.sql", + "jdk.crypto.ec", + "jdk.jfr", + "jdk.management", + "jdk.unsupported", + "jdk.zipfs", + ).joinToString(",") + +val runtimeBundle by tasks.registering { + group = "distribution" + description = "Builds the self-contained runtime bundle (jlink JRE + jar + launcher) as a tar.gz" + dependsOn(tasks.bootJar) + + val jdkHome = javaToolchains.launcherFor(java.toolchain).map { it.metadata.installationPath.asFile } + val jarFile = tasks.bootJar.flatMap { it.archiveFile } + val launcherFile = layout.projectDirectory.file("packaging/gittally").asFile + val stagingDir = + layout.buildDirectory + .dir("runtime-bundle") + .get() + .asFile + val tarballFile = + layout.buildDirectory + .file("distributions/gittally-runtime-linux-x64.tar.gz") + .get() + .asFile + + inputs.files(tasks.bootJar.map { it.outputs.files }) + inputs.file(launcherFile) + inputs.property("modules", runtimeBundleModules) + outputs.file(tarballFile) + + doLast { + val bundleRoot = stagingDir.resolve("gittally") + bundleRoot.deleteRecursively() + bundleRoot.parentFile.mkdirs() + + val jlink = jdkHome.get().resolve("bin/jlink") + val jlinkProcess = + ProcessBuilder( + jlink.absolutePath, + "--add-modules", + runtimeBundleModules, + "--strip-debug", + "--no-header-files", + "--no-man-pages", + "--compress", + "zip-6", + "--output", + bundleRoot.resolve("jre").absolutePath, + ).redirectErrorStream(true).start() + val jlinkOutput = jlinkProcess.inputStream.bufferedReader().readText() + check(jlinkProcess.waitFor() == 0) { "jlink failed:\n$jlinkOutput" } + + jarFile.get().asFile.copyTo(bundleRoot.resolve("lib/gittally.jar").also { it.parentFile.mkdirs() }) + val launcher = launcherFile.copyTo(bundleRoot.resolve("bin/gittally").also { it.parentFile.mkdirs() }) + check(launcher.setExecutable(true, false)) { "cannot make $launcher executable" } + + tarballFile.parentFile.mkdirs() + // system tar preserves the execute bits of jre/bin/* and jre/lib/jspawnhelper + val tarProcess = + ProcessBuilder("tar", "-czf", tarballFile.absolutePath, "-C", stagingDir.absolutePath, "gittally") + .redirectErrorStream(true) + .start() + val tarOutput = tarProcess.inputStream.bufferedReader().readText() + check(tarProcess.waitFor() == 0) { "tar failed:\n$tarOutput" } + } +} + tasks.withType { useJUnitPlatform() } diff --git a/docs/adrs/0006-2026-08-10.runtime-bundle-distribution.md b/docs/adrs/0006-2026-08-10.runtime-bundle-distribution.md new file mode 100644 index 0000000..3d1debc --- /dev/null +++ b/docs/adrs/0006-2026-08-10.runtime-bundle-distribution.md @@ -0,0 +1,57 @@ +# 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) + +### jlink Runtime Bundle + +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 `/jre/bin/java` and `/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. diff --git a/docs/bootstrapping.md b/docs/bootstrapping.md index 8deb8e8..e0b924c 100644 --- a/docs/bootstrapping.md +++ b/docs/bootstrapping.md @@ -102,17 +102,11 @@ Or, when files already exist: .gittally.yml already exists — not overwritten ``` -## Future: Docker-based Deployment +## Hosts Without a Java Runtime -GitTally is intended to run on Hostsharing Container Server environments, which provide Docker -but no Java runtime. A later development step will add a Docker image distribution where: - -- GitTally itself runs as a Docker container (image bundles the JRE + JAR) -- Builds are spawned by mounting the host Docker socket (`/var/run/docker.sock`) -- `init` then optionally generates a `docker-compose.yml`, a secrets env file, and a systemd unit - that starts the Compose stack at boot - -Until then, a Java runtime must be available on the host. +GitTally is intended to run on Hostsharing Container Server environments, which provide Docker and git but no Java runtime. +For these hosts, `./gradlew runtimeBundle` builds a self-contained runtime bundle (jlink-trimmed JRE + JAR + launcher) — see [deployment.md](deployment.md) and ADR 0006. +A containerized GitTally runtime was considered and rejected there. ## Next Steps After `init` diff --git a/docs/deployment.md b/docs/deployment.md index d5ad3b1..0f8eb3e 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -8,7 +8,7 @@ For hosts without one, an opt-in managed nginx/TLS container is available, see [ ## Prerequisites - Linux with systemd. -- Java runtime (JRE 21). +- Java runtime (JRE 21) — or none, when using the self-contained runtime bundle, see [Hosts Without a Java Runtime](#hosts-without-a-java-runtime-runtime-bundle). - `git` CLI on the `PATH`. - `docker` CLI on the `PATH`, only if any branch uses `docker.enabled` (see [configuration.md](configuration.md)). - A checked-out working tree of the repository to watch, with a remote named `origin`. @@ -124,6 +124,38 @@ sudo certbot --nginx -d ci.example.org This replaces the legacy script's managed nginx/Let's Encrypt Docker container for hosts that have their own web server. +## Hosts Without a Java Runtime (Runtime Bundle) + +Some hosts provide git and Docker but no Java runtime and no way to install one, e.g. Hostsharing container servers. +For these, GitTally ships as a self-contained runtime bundle: a jlink-trimmed JRE, `gittally.jar`, and a launcher script in one tarball (ADR 0006). + +Build the bundle on a Linux x86_64 machine whose glibc is not newer than the target's: + +```bash +./gradlew runtimeBundle +ls build/distributions/gittally-runtime-linux-x64.tar.gz +``` + +Copy and unpack it on the target host, by convention to `~/opt/gittally`: + +```bash +scp build/distributions/gittally-runtime-linux-x64.tar.gz user@host:/tmp/ +ssh user@host 'mkdir -p ~/opt && tar -xzf /tmp/gittally-runtime-linux-x64.tar.gz -C ~/opt' +``` + +Then use `~/opt/gittally/bin/gittally` wherever this document says `java -jar ~/bin/gittally.jar`: + +```bash +cd /path/to/repo +~/opt/gittally/bin/gittally init +~/opt/gittally/bin/gittally init --systemd +``` + +`init --systemd` detects the bundle automatically: the generated unit's `ExecStart` points at the bundle's `jre/bin/java` and `lib/gittally.jar`, so the install commands printed by `init --systemd` work unchanged. +`JAVA_OPTS` from the environment file applies as usual. + +To update GitTally, stop the service, unpack the new bundle over `~/opt/gittally`, and restart the service. + ## Hosts Without a Reverse Proxy (Managed nginx/TLS) Some hosts provide Docker but no root access and no host web server, e.g. Hostsharing managed container environments. diff --git a/docs/migration-from-legacy.md b/docs/migration-from-legacy.md index 0caed4f..70aa105 100644 --- a/docs/migration-from-legacy.md +++ b/docs/migration-from-legacy.md @@ -63,6 +63,9 @@ Old artifacts under the legacy artifact root remain readable on disk until you d ## Manual Migration Steps +When migrating to a **different host**, the legacy instance can keep running in parallel until the new one is verified — then skip step 1 here and stop the legacy service on the old host last. +During parallel operation, give the new instance a distinct `gitea.statusContext`, so the two instances do not overwrite each other's commit statuses in Gitea. + 1. Stop and remove the legacy service: ```bash diff --git a/docs/plan/15-runtime-bundle-distribution.md b/docs/plan/15-runtime-bundle-distribution.md new file mode 100644 index 0000000..2b5a0f1 --- /dev/null +++ b/docs/plan/15-runtime-bundle-distribution.md @@ -0,0 +1,107 @@ +# Step 15: Self-Contained Runtime Bundle Distribution + +Prerequisites: steps 12, 13. +Read `README.md` first. +This step revises the "Future: Docker-based Deployment" section of `docs/bootstrapping.md` — see the ADR requirement below. + +## Goal + +Deploy GitTally on hosts that provide Docker and git but no Java runtime (Hostsharing container servers, e.g. `tallyman@vm4006`). +GitTally is distributed as a self-contained runtime bundle: a jlink-trimmed JRE plus `gittally.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) + +Three formats were considered; write ADR 0006 recording the decision and this rationale: + +- **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. + GitTally'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 GitTally 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) + +- `vm4006.hostsharing.net`: Debian 13, x86_64, glibc 2.41, git 2.47.3, docker 26.1.5, GNU make, systemd user session running with `Linger=yes`, no Java runtime. +- The jlink image contains natively linked JVM libs, so it must be built on glibc ≤ 2.41 for the same architecture; the Ubuntu 24.04 dev machine (glibc 2.39, x86_64) qualifies. + For reproducible builds elsewhere, the bundle can be built inside an `eclipse-temurin:21-jdk` container (glibc 2.39 base). + +## Design + +Gradle: + +- Add a `runtimeBundle` task (depends on `bootJar`); the normal `./gradlew build` stays unchanged. +- The task runs `jlink` from the configured Java toolchain (every JDK 21 ships jlink; no new toolchain requirement). +- The JDK module list is pinned in the build script, computed once via `jdeps` on the exploded boot jar and its `BOOT-INF/lib`; document the jdeps command next to the list and re-check it when dependencies change. +- Bundle layout: `gittally/jre/` (jlink image), `gittally/lib/gittally.jar`, `gittally/bin/gittally` (sh launcher: `exec "$DIR/../jre/bin/java" $JAVA_OPTS -jar "$DIR/../lib/gittally.jar" "$@"`). +- Output: `build/distributions/gittally-runtime-linux-x64.tar.gz` with preserved execute permissions. + +Deployment (no code changes expected): + +- Unpack to `~/opt/gittally/` on the target host; run everything via `~/opt/gittally/bin/gittally`. +- `init --systemd` already generates `ExecStart= $JAVA_OPTS -jar server` from `java.home` and the running jar path — from the bundle both resolve into `~/opt/gittally/`, so the unit points at the bundle without changes. + Verify this instead of adapting code; adapt only if the resolution fails. +- Updating GitTally = unpack a new bundle over `~/opt/gittally/` (or switch a symlink) and restart the service. + +Documentation: + +- `docs/deployment.md`: prerequisites become "JRE 21 **or** the runtime bundle"; add a section "Hosts Without a Java Runtime (Runtime Bundle)" with build, `scp`, unpack, and systemd setup. +- `docs/bootstrapping.md`: replace the "Future: Docker-based Deployment" section with the runtime bundle and a pointer to ADR 0006. +- `docs/adrs/0006-…`: the distribution-format decision (see above). +- `docs/migration-from-legacy.md`: add a note that migrating to a different host allows parallel operation, with a distinct `gitea.statusContext` per instance so the two CIs do not overwrite each other's commit statuses. + +## Tests + +- No production code changes are expected, so no new unit tests; existing tests must stay green. +- Smoke-verify the bundle manually: `bin/gittally --help`, `init` in a scratch repo, `config:print --full`, a short `server` run, and `init --systemd` unit content pointing into the bundle; document the results in this file. +- Verify on vm4006 (which has no Java): copy the bundle, run `bin/gittally --help` and `config:print`; document the results in this file. + +## Acceptance Criteria + +- `./gradlew ktlintFormat` then `./gradlew build` is green, with unchanged toolchain requirements. +- `./gradlew runtimeBundle` produces a tarball whose `bin/gittally` runs `--help`, `init`, and `server` on a machine without any Java runtime. +- A fresh deployment to vm4006 following `docs/deployment.md` and `docs/migration-from-legacy.md` reaches a running service: web UI reachable, a Docker build succeeds, commit status arrives in Gitea, managed nginx/TLS works (`server.nginx.enabled: true`, DNS for `serverName` pointing at vm4006). +- vm2176 (legacy) keeps running in parallel during the migration; the legacy service is only retired after vm4006 is verified. +- Docs and ADR 0006 written as described; document deviations in this file. + +## Result (2026-08-10) + +Implemented as designed; no production-code change was needed. +The step was originally drafted for a GraalVM native image; it was re-planned to the jlink bundle after the Spring-AOT build-time condition evaluation turned out to be incompatible with the dual-context CLI/server wiring (see ADR 0006). + +- `runtimeBundle` task in `build.gradle.kts` with the pinned module list (jdeps output plus java.logging, jdk.crypto.ec, jdk.management, jdk.zipfs); launcher script in `packaging/gittally`; tarball ~66 MB. +- Smoke tests on the dev machine (with `JAVA_HOME` unset and a stripped `PATH`): `--help`, `init --systemd` in a scratch repo, `config:print --full`, and a `server` run all passed; `/` served HTTP 200 and `/api/branches` returned JSON. +- The `init --systemd` unit generated from the bundle points at `/jre/bin/java` and `/lib/gittally.jar` as predicted — no detection code needed. +- Verified on vm4006 (no Java installed): bundle unpacked to `~/opt/gittally`, `--version`, `--help`, and `status` in a scratch repo (host git via `GitCommandRunner`) all worked. + +Production deployment to vm4006 (2026-08-10, same session): + +- `hs.hsadmin.ng` cloned to `~/hs.hsadmin.ng` on vm4006; legacy configuration from vm2176 (repo `.gitTally` + `gitTally.env`) migrated to `.gittally.yml` per `docs/migration-from-legacy.md`; Gitea token moved (the token in vm2176's `gitTally.env` file was stale — the valid one came from the running legacy process environment). +- `statusContext: GitTally@vm4006` for the parallel phase; rename to `GitTally` after vm2176 is retired. +- systemd user service installed via `init --systemd` from the bundle and running; watcher fetches origin branches with the migrated credentials. +- Managed nginx/TLS live: Let's Encrypt certificate for `vm4006.hostsharing.net` obtained, `https://vm4006.hostsharing.net/` serves the UI with a valid chain, HTTP 301s to HTTPS (Hostsharing routes public 80/443 to `httpPort`/`httpsPort`, same as on vm2176). +- Fix discovered during rollout: certbot removed `ssl-dhparams.pem` from its repository, so the first nginx start failed with HTTP 404. + The DH parameters (RFC 7919 ffdhe2048) are now bundled as the classpath resource `nginx/ssl-dhparams.pem` instead of downloaded; the download seam and `NginxConfigFiles.DH_PARAMS_URL` were removed (revises the step-13 note about the download). + Legacy on vm2176 only kept working because its state dir cached the file. + +Second fix discovered by the first real build: under a **rootless** Docker daemon, `DockerBuildRunner` ran the build container as `--user ` (ported from legacy). +With rootless identity mapping the host user is container root, and the host uid inside the container falls into the subuid range — the container could not create `.gradle` in the freshly created worktree ("Failed to create parent directory"). +Legacy on vm2176 only worked because its ownership-repair chown had (unintentionally) moved `build/` and `.gradle/` into subuid ownership on the host (verified: owned by uid 166536 there) — a stable but wrong equilibrium tied to its reused primary checkout. +The rewrite now always runs the build container as `--user 0`: under rootless that IS the unprivileged host user (files stay host-owned, the repair chown degenerates to `0:0`); under rootful daemons the behavior is unchanged (root + chown to the host ids). + +Third finding (config, not code): the hsadmin-ng Liquibase migration tests (`LiquibaseCompatibilityIntegrationTest`, `ImportHostingAssets.liquibaseMigrationForBookingAndHosting`) failed on vm4006 with "environment variable HSADMINNG_POSTGRES_ADMIN_USERNAME not set". +These tests run Liquibase programmatically without Spring's `spring.liquibase.parameters`, so the changelog parameters resolve only via Liquibase's env-var substitution; the legacy script had a host-env passthrough for exactly these variables. +The values `admin`/`restricted` are hsadmin-ng's committed defaults, but only for the other execution paths: `.tc-environment` for the documented dev workflow (`. .tc-environment; ./gradlew …`) and the `${…:admin}` fallbacks in `src/test/resources/application.yml` for the Spring-managed Liquibase path. +The programmatic path deliberately has no fallback — `009-check-environment.sql` exists to verify the environment is configured — so a CI environment must export the variables itself. +Fix: `HSADMINNG_POSTGRES_ADMIN_USERNAME=admin` and `HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted` in `branches.default.docker.env` on vm4006 — the mechanism `docs/migration-from-legacy.md` prescribes for the legacy passthrough list; alternatively the build command could source `.tc-environment` like the dev workflow. +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 (GitTally limitation, worked around in config): with all tests green, the build then failed in hsadmin-ng's `:prQuickCheck` — "fatal: not a git repository". +GitTally 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/gittally/`) 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/gittally/` secrets) is left as a follow-up design task. + +Still open: green completion of the first real build with its Gitea commit status, and — after a stable parallel phase — retiring the legacy service on vm2176. diff --git a/docs/plan/README.md b/docs/plan/README.md index 5bc8aaa..bc2178c 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -71,10 +71,16 @@ Added after the initial plan (ADR 0005): Added after the 2026-08-10 overhead measurements on vm2176: -- [ ] `14-build-phase-timing-and-overhead.md` — per-build phase timing, overhead budget warning, ownership/metrics fixes +- [ ] `14-build-phase-timing-and-overhead.md` — per-build phase timing, overhead budget warning, ownership/metrics fixes — deferred until after step 15; revisit relevance on vm4006 + +Added for the vm2176 → vm4006 migration (2026-08-10): + +- [ ] `15-runtime-bundle-distribution.md` — self-contained runtime bundle (jlink JRE + jar) for hosts without a Java runtime +- [ ] `16-git-in-docker-builds.md` — read-only git metadata inside Docker build containers, with `.git/gittally/` masked Steps 01–03 are independent of each other. Steps 04–06 depend on 01–03. Steps 07–09 depend on 04–06. Steps 11 and 12 are optional/deferrable; 10 only needs 04–06. Step 13 depends on 07, 11, and 12. +Step 15 depends on 12 and 13 and revises the containerized-runtime sketch in `docs/bootstrapping.md` (ADR 0006 is written as part of the step; GraalVM native image was evaluated and rejected there). diff --git a/packaging/gittally b/packaging/gittally new file mode 100755 index 0000000..3473f8b --- /dev/null +++ b/packaging/gittally @@ -0,0 +1,6 @@ +#!/bin/sh +# Launcher for the self-contained GitTally runtime bundle (jlink JRE + jar). +# Built by `./gradlew runtimeBundle`; see docs/deployment.md. +DIR=$(CDPATH='' cd -- "$(dirname -- "$(readlink -f -- "$0")")" && pwd) +# shellcheck disable=SC2086 # JAVA_OPTS is intentionally word-split +exec "$DIR/../jre/bin/java" $JAVA_OPTS -jar "$DIR/../lib/gittally.jar" "$@"