# GitTally Deployment This document describes how to run GitTally as a permanent service. The recommended setup is a systemd user service behind an existing reverse proxy. By default GitTally does not manage nginx or TLS certificates itself; it relies on the host's existing web server and certbot. For hosts without one, an opt-in managed nginx/TLS container is available, see [Hosts Without a Reverse Proxy](#hosts-without-a-reverse-proxy-managed-nginxtls). ## Prerequisites - Linux with systemd. - 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`. ## Jar Location Convention Build the executable jar once: ```bash ./gradlew build ls build/libs/gittally.jar ``` Copy the jar to a stable path outside any watched repository, by convention `~/bin/gittally.jar`: ```bash mkdir -p ~/bin cp build/libs/gittally.jar ~/bin/gittally.jar ``` The systemd unit generated below points at the jar that was used to run `init --systemd`. So always run it via the stable path, not via `build/libs/`. ## Install the Service Initialize GitTally in the repository to watch (see [bootstrapping.md](bootstrapping.md) for details): ```bash cd /path/to/repo java -jar ~/bin/gittally.jar init # fill in git.account and git.token in .git/gittally/.gittally.yml # review .gittally.yml ``` Generate the systemd user unit: ```bash java -jar ~/bin/gittally.jar init --systemd ``` This writes `.git/gittally/gittally-.service`, `.git/gittally/gittally.env`, and the nightly Docker cleanup units (`gittally-docker-prune.service`/`.timer`), and prints the install commands: ```bash ln -sf /path/to/repo/.git/gittally/gittally-.service ~/.config/systemd/user/gittally-.service ln -sf /path/to/repo/.git/gittally/gittally-docker-prune.service ~/.config/systemd/user/gittally-docker-prune.service ln -sf /path/to/repo/.git/gittally/gittally-docker-prune.timer ~/.config/systemd/user/gittally-docker-prune.timer systemctl --user daemon-reload systemctl --user enable --now gittally-.service systemctl --user enable --now gittally-docker-prune.timer ``` The unit runs `java -jar ~/bin/gittally.jar server` with the repository as working directory and `Restart=always`. The unit name contains the repository name, so several repositories can be served by one host, each with its own service and port. ### Nightly Docker Cleanup The `gittally-docker-prune.timer` runs `docker system prune -af` every night at 02:00 (host time), before the usual auto-build slots. It removes stopped containers, unused images, unused networks, and dangling build cache, so nightly builds start from freshly built images. Unlike the legacy cleanup it does **not** prune volumes — the per-repository Gradle cache volumes survive. The units are host-global (no repository name): with several GitTally instances on one host, every `init --systemd` generates the same files and the symlinks coincide. On hosts without a `docker` CLI the service is skipped, not failed (`ExecCondition`). `Persistent=true` catches up a missed run after downtime. Expect a burst of builds right after the very first start: in a fresh clone every origin branch counts as new, so each branch with commits younger than `watcher.newBranchMaxAge` (default 5d) is built once — one build per branch, executed serially up to `builds.maxConcurrent`. Lower `watcher.newBranchMaxAge` before the first start, or enable `requirePullRequest`, to limit the initial backlog. User services stop at logout unless lingering is enabled once per user: ```bash loginctl enable-linger "$USER" ``` ## Operating the Service ```bash systemctl --user status gittally-.service # state and last log lines journalctl --user -u gittally-.service -f # follow the log systemctl --user restart gittally-.service # restart (e.g. after config changes) systemctl --user stop gittally-.service # stop ``` To update GitTally, replace `~/bin/gittally.jar` and restart the service. ## Environment File `.git/gittally/gittally.env` is loaded by the unit as `EnvironmentFile`. It only tunes the JVM process, e.g. `JAVA_OPTS=-Xmx256m`. All GitTally configuration lives in the YAML files described in [configuration.md](configuration.md), not in environment variables. `init --systemd` never overwrites an existing environment file. ## Reverse Proxy (nginx) Bind GitTally to localhost and set the public URL in `.gittally.yml`: ```yaml server: bindAddress: 127.0.0.1 port: 18080 publicBaseUrl: "https://ci.example.org/" ``` `publicBaseUrl` is used for all links posted to Gitea, so it must be the externally reachable URL. Add a `server` block to the host's nginx: ```nginx server { listen 443 ssl; server_name ci.example.org; ssl_certificate /etc/letsencrypt/live/ci.example.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ci.example.org/privkey.pem; location / { proxy_pass http://127.0.0.1:18080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` Obtain and renew the certificate with the host's existing certbot, e.g.: ```bash 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. For these, GitTally can manage its own nginx+certbot Docker container (ADR 0005). This is opt-in; where a host web server exists, prefer the reverse-proxy setup above. Enable it in the server section of the configuration: ```yaml server: port: 18080 nginx: enabled: true serverName: ci.example.org httpPort: 8080 httpsPort: 8443 letsencryptEmail: admin@example.org ``` On server start, GitTally writes the nginx configuration, starts a labelled nginx container publishing `httpPort` and `httpsPort`, obtains a Let's Encrypt certificate via a certbot container (webroot mode), and restarts nginx with the full HTTPS configuration. A renewal check runs daily; certificates and nginx state persist in `server.nginx.stateDir` across restarts. On shutdown the container is removed. All nginx and certificate failures are non-fatal warnings — the plain HTTP server keeps running without the proxy. `serverName` must be a public DNS name pointing at the host, reachable from the internet on port 80/443 (directly or via a port forward to `httpPort`/`httpsPort`), otherwise the ACME challenge fails. The nginx container cannot reach `localhost` of the host, so the proxy upstream defaults to `serverName`; set `server.nginx.upstreamHost` if the host is reachable under a different name from inside containers. With the managed nginx, keep `server.bindAddress: 0.0.0.0` (or an address reachable from the Docker network) — binding GitTally to `127.0.0.1` would make it unreachable for the proxy. See [configuration.md](configuration.md) for all `server.nginx.*` keys.