implemented 12-deployment.md: added systemd service generation (init --systemd) and migration guide from legacy script; introduced JSON-file persistence, server-rendered UI with polling, and reverse-proxy-based deployment; updated documentation
This commit is contained in:
@@ -36,7 +36,7 @@ GitTallyApplication ← @SpringBootApplication
|
|||||||
CliRunner ← CommandLineRunner + ExitCodeGenerator
|
CliRunner ← CommandLineRunner + ExitCodeGenerator
|
||||||
GitTallyCommand ← root @Command, delegates to subcommands
|
GitTallyCommand ← root @Command, delegates to subcommands
|
||||||
commands/
|
commands/
|
||||||
InitCommand ← "init"
|
InitCommand ← "init [--systemd]"
|
||||||
ServerCommand ← "server"
|
ServerCommand ← "server"
|
||||||
StatusCommand ← "status [--history]"
|
StatusCommand ← "status [--history]"
|
||||||
BuildCommand ← "build [<branch>]"
|
BuildCommand ← "build [<branch>]"
|
||||||
@@ -150,6 +150,8 @@ Keep sentences short.
|
|||||||
- `docs/GitTally-Konzept.md` — product concept and target architecture (in German): git-centric CI, builds in Docker, one instance per repository, status reported back to Gitea.
|
- `docs/GitTally-Konzept.md` — product concept and target architecture (in German): git-centric CI, builds in Docker, one instance per repository, status reported back to Gitea.
|
||||||
- `docs/configuration.md` — configuration reference; keep in sync with `GitTallyConfig` and the `init` templates.
|
- `docs/configuration.md` — configuration reference; keep in sync with `GitTallyConfig` and the `init` templates.
|
||||||
- `docs/bootstrapping.md` — how `init` prepares a repository.
|
- `docs/bootstrapping.md` — how `init` prepares a repository.
|
||||||
|
- `docs/deployment.md` — running GitTally as a systemd user service behind an existing reverse proxy (`init --systemd` generates the unit).
|
||||||
|
- `docs/migration-from-legacy.md` — legacy env vars → YAML keys mapping and the manual migration steps; `legacy/gitTally` is deprecated.
|
||||||
- `docs/plan/` — the step-by-step rewrite plan; `docs/plan/README.md` explains how to execute a step, `docs/plan/00-legacy-analysis.md` summarizes the legacy bash script.
|
- `docs/plan/` — the step-by-step rewrite plan; `docs/plan/README.md` explains how to execute a step, `docs/plan/00-legacy-analysis.md` summarizes the legacy bash script.
|
||||||
|
|
||||||
## Key Architectural Decisions
|
## Key Architectural Decisions
|
||||||
@@ -159,3 +161,4 @@ All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc`
|
|||||||
- **Test framework**: Kotest + MockK + WireMock + Testcontainers (ADR 0001)
|
- **Test framework**: Kotest + MockK + WireMock + Testcontainers (ADR 0001)
|
||||||
- **Gradle**: 8.14.5 (ADR 0002)
|
- **Gradle**: 8.14.5 (ADR 0002)
|
||||||
- **Spring Boot**: 4.0.6 (ADR 0003)
|
- **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)
|
||||||
|
|||||||
@@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
Lightweight, declarative and highly opinionated software build system (CI/CD).
|
Lightweight, declarative and highly opinionated software build system (CI/CD).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [docs/configuration.md](docs/configuration.md) — configuration reference
|
||||||
|
- [docs/bootstrapping.md](docs/bootstrapping.md) — initializing a repository with `init`
|
||||||
|
- [docs/deployment.md](docs/deployment.md) — running GitTally as a systemd service behind a reverse proxy
|
||||||
|
- [docs/migration-from-legacy.md](docs/migration-from-legacy.md) — migrating from the legacy bash script
|
||||||
|
|
||||||
|
## Legacy Script
|
||||||
|
|
||||||
|
`legacy/gitTally` (bash) is **deprecated** and kept only as a behavioral reference for the rewrite.
|
||||||
|
Do not use it for new installations; see [docs/migration-from-legacy.md](docs/migration-from-legacy.md).
|
||||||
|
|
||||||
## Developer Setup
|
## Developer Setup
|
||||||
|
|
||||||
Source `.envrc` to add `tools/` to your `PATH`, or install [direnv](#direnv) to have this done automatically on `cd`:
|
Source `.envrc` to add `tools/` to your `PATH`, or install [direnv](#direnv) to have this done automatically on `cd`:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Ziel ist es, die Komplexität klassischer CI-Systeme wie Jenkins erheblich zu re
|
|||||||
## Grundprinzipien
|
## Grundprinzipien
|
||||||
|
|
||||||
- Git wird immer verwendet.
|
- Git wird immer verwendet.
|
||||||
- Builds laufen in Docker.
|
- Builds laufen nativ oder optional in Docker (pro Branch konfigurierbar).
|
||||||
- Die Konfiguration erfolgt primär über YAML-Dateien.
|
- Die Konfiguration erfolgt primär über YAML-Dateien.
|
||||||
- Eine Instanz verwaltet zunächst genau ein Repository.
|
- Eine Instanz verwaltet zunächst genau ein Repository.
|
||||||
- Build-Status werden an das Git-System zurückgemeldet.
|
- Build-Status werden an das Git-System zurückgemeldet.
|
||||||
@@ -20,6 +20,7 @@ Ziel ist es, die Komplexität klassischer CI-Systeme wie Jenkins erheblich zu re
|
|||||||
|
|
||||||
- Interaktive Nutzung
|
- Interaktive Nutzung
|
||||||
- Status anzeigen
|
- Status anzeigen
|
||||||
|
- Builds starten und wiederholen
|
||||||
- Konfiguration anzeigen
|
- Konfiguration anzeigen
|
||||||
- Initialisierung durchführen
|
- Initialisierung durchführen
|
||||||
|
|
||||||
@@ -110,6 +111,7 @@ flowchart LR
|
|||||||
### CLIFrontend
|
### CLIFrontend
|
||||||
|
|
||||||
- Status anzeigen
|
- Status anzeigen
|
||||||
|
- Builds starten und wiederholen
|
||||||
- Konfiguration anzeigen
|
- Konfiguration anzeigen
|
||||||
- Initialisierung durchführen
|
- Initialisierung durchführen
|
||||||
|
|
||||||
@@ -168,6 +170,8 @@ java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar init
|
|||||||
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar server
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Für den Dauerbetrieb als systemd-User-Service siehe [deployment.md](deployment.md) (`init --systemd`).
|
||||||
|
|
||||||
### Konfigurationsanzeige
|
### Konfigurationsanzeige
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -177,7 +181,7 @@ java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar config:print --full
|
|||||||
|
|
||||||
## Erweiterungen
|
## Erweiterungen
|
||||||
|
|
||||||
- Deployment / CD
|
- Continuous Delivery (CD; das Deployment von GitTally selbst ist in [deployment.md](deployment.md) beschrieben)
|
||||||
- SQLite statt Dateisystem
|
- SQLite statt Dateisystem
|
||||||
- Mehrere BuildWorker
|
- Mehrere BuildWorker
|
||||||
- Multi-Repository-Verwaltung
|
- Multi-Repository-Verwaltung
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Rewrite Architecture Decisions
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- proposed: 2026-07-07
|
||||||
|
- accepted: 2026-07-07
|
||||||
|
- rejected: -
|
||||||
|
- superseded: -
|
||||||
|
|
||||||
|
**Decision [accepted]:** JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx/TLS — deployment via systemd user unit behind the host's reverse proxy.
|
||||||
|
|
||||||
|
## Context and Problem Statement
|
||||||
|
|
||||||
|
The rewrite of `legacy/gitTally` (bash) as a Kotlin/Spring application (see `docs/plan/`) required several cross-cutting architecture decisions.
|
||||||
|
They were proposed in `docs/plan/README.md`, validated step by step during implementation, and are summarized here as one record.
|
||||||
|
|
||||||
|
### Technical Background
|
||||||
|
|
||||||
|
The legacy script kept all state in TSV/HTML files, patched its web UI with regex rewrites, and managed its own nginx+certbot Docker container.
|
||||||
|
Its two structural defects — build status not observable during a build, and a web UI that could get stuck loading forever — had to be fixed by design, not by patching.
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
* Database persistence (SQLite) vs. JSON files behind a repository interface
|
||||||
|
* SPA frontend or server push (WebSocket/SSE) vs. server-rendered HTML with JSON polling
|
||||||
|
* Managed nginx/Let's Encrypt container vs. documented deployment behind an existing reverse proxy
|
||||||
|
|
||||||
|
### Persistence: JSON files behind `BuildResultRepository`
|
||||||
|
|
||||||
|
Build results are persisted as a JSON file under `.git/gittally/`, accessed only through the `BuildResultRepository` interface.
|
||||||
|
|
||||||
|
#### Advantages
|
||||||
|
|
||||||
|
- No database dependency, no schema migrations; state stays inspectable with a text editor, like legacy.
|
||||||
|
- The volume is tiny (retention prunes per branch), so file rewrites are cheap.
|
||||||
|
- The interface keeps a later switch to SQLite possible without touching callers.
|
||||||
|
|
||||||
|
#### Disadvantages
|
||||||
|
|
||||||
|
- No queries or transactions; concurrent writers must be serialized in-process.
|
||||||
|
|
||||||
|
### Web UI: server-rendered Thymeleaf plus JSON polling
|
||||||
|
|
||||||
|
Pages render the full state server-side; one hand-written JavaScript file polls JSON endpoints and re-renders table bodies.
|
||||||
|
|
||||||
|
#### Advantages
|
||||||
|
|
||||||
|
- No SPA framework and no frontend build pipeline.
|
||||||
|
- Fixes the legacy stuck-spinner defect by design: every fetch has a timeout and an explicit error badge, and status transitions are event-driven and observable while a build runs.
|
||||||
|
- Pages stay useful without JavaScript (initial render is complete).
|
||||||
|
|
||||||
|
#### Disadvantages
|
||||||
|
|
||||||
|
- Updates are only as fresh as the polling interval; no server push.
|
||||||
|
|
||||||
|
### Deployment: no managed nginx, systemd user unit instead
|
||||||
|
|
||||||
|
nginx/Let's Encrypt container management was not ported; `init --systemd` generates a user unit running `java -jar gittally.jar server`, and `docs/deployment.md` documents the reverse-proxy setup with the host's certbot.
|
||||||
|
|
||||||
|
#### Advantages
|
||||||
|
|
||||||
|
- Removes the largest and most brittle legacy subsystem (container lifecycle, certificate renewal, config templating).
|
||||||
|
- Hosts usually already run a web server with TLS; one `server` block suffices.
|
||||||
|
- The generated unit replaces the legacy self-copy/self-update machinery with plain jar deployment.
|
||||||
|
|
||||||
|
#### Disadvantages
|
||||||
|
|
||||||
|
- HTTPS setup is a manual, host-specific step outside GitTally's control.
|
||||||
|
|
||||||
|
## Decision Outcome
|
||||||
|
|
||||||
|
All three proposals from the plan were confirmed during implementation and are in force:
|
||||||
|
|
||||||
|
- JSON-file persistence behind `BuildResultRepository` (steps 01, 05, 06).
|
||||||
|
- Server-rendered HTML with polling JSON endpoints and explicit error states (steps 07–09).
|
||||||
|
- No nginx management; systemd user unit plus reverse-proxy documentation (step 12).
|
||||||
|
|
||||||
|
Related, previously decided in the same spirit: external systems are accessed by shelling out to the `git` and `docker` CLIs instead of SDK dependencies (steps 02, 11).
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# GitTally Bootstrapping
|
# GitTally Bootstrapping
|
||||||
|
|
||||||
Bootstrapping prepares a git repository for use with GitTally.
|
Bootstrapping prepares a git repository for use with GitTally.
|
||||||
It creates the config files described in [configuration.md](configuration.md) and optionally installs GitTally as a system service.
|
It creates the config files described in [configuration.md](configuration.md).
|
||||||
|
With `init --systemd` it also generates a systemd user unit for running the server permanently, see [deployment.md](deployment.md).
|
||||||
|
|
||||||
Run `init` once per repository, from within a checked-out working tree.
|
Run `init` once per repository, from within a checked-out working tree.
|
||||||
|
|
||||||
@@ -125,6 +126,7 @@ Until then, a Java runtime must be available on the host.
|
|||||||
```bash
|
```bash
|
||||||
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar server
|
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar server
|
||||||
```
|
```
|
||||||
|
5. For permanent operation, install the systemd user service described in [deployment.md](deployment.md).
|
||||||
|
|
||||||
## Example: Self-Hosting GitTally
|
## Example: Self-Hosting GitTally
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# 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.
|
||||||
|
GitTally does not manage nginx or TLS certificates itself (unlike the legacy script); it relies on the host's existing web server and certbot.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Linux with systemd.
|
||||||
|
- Java runtime (JRE 21).
|
||||||
|
- `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-*-SNAPSHOT.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-0.1.0-SNAPSHOT.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-<repo-name>.service` and `.git/gittally/gittally.env` and prints the install commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ln -sf /path/to/repo/.git/gittally/gittally-<repo-name>.service ~/.config/systemd/user/gittally-<repo-name>.service
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now gittally-<repo-name>.service
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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-<repo-name>.service # state and last log lines
|
||||||
|
journalctl --user -u gittally-<repo-name>.service -f # follow the log
|
||||||
|
systemctl --user restart gittally-<repo-name>.service # restart (e.g. after config changes)
|
||||||
|
systemctl --user stop gittally-<repo-name>.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, which was intentionally not ported (see [migration-from-legacy.md](migration-from-legacy.md)).
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Migration from the Legacy Script
|
||||||
|
|
||||||
|
The bash script `legacy/gitTally` is deprecated and replaced by this application.
|
||||||
|
This document maps the legacy environment-variable configuration to the YAML configuration and lists the manual migration steps.
|
||||||
|
See [configuration.md](configuration.md) for the full configuration reference and [deployment.md](deployment.md) for the new service setup.
|
||||||
|
|
||||||
|
## Configuration Mapping
|
||||||
|
|
||||||
|
Legacy configuration came from environment variables (`gitTally --env` template, sourced env files).
|
||||||
|
The new configuration lives in two YAML files: `.gittally.yml` (committed) and `.git/gittally/.gittally.yml` (machine-specific, secrets).
|
||||||
|
|
||||||
|
Branch-level keys below live under `branches.<name>`; use `branches.default` for what used to be the global value.
|
||||||
|
|
||||||
|
| Legacy environment variable | New YAML key |
|
||||||
|
|---|---|
|
||||||
|
| `GITTALLY_BUILD_COMMAND` | `branches.<name>.buildCommand` |
|
||||||
|
| `GITTALLY_BUILD_CLEAN_COMMAND` | `branches.<name>.cleanCommand` |
|
||||||
|
| `GITTALLY_BUILD_ARTEFACT_DIRS` | `branches.<name>.artifactDirs` — YAML list instead of `;`-separated |
|
||||||
|
| `GITTALLY_BUILD_STDOUT_LOG` | `branches.<name>.stdoutLog` |
|
||||||
|
| `GITTALLY_BUILD_STDERR_LOG` | `branches.<name>.stderrLog` |
|
||||||
|
| `GITTALLY_NEW_BRANCH_COMMIT_MAX_AGE` | `watcher.newBranchMaxAge` |
|
||||||
|
| `GITTALLY_BUILD_DOCKER_IMAGE` | `branches.<name>.docker.image` — also set `docker.enabled: true` (replaces the `--docker` flag) |
|
||||||
|
| `GITTALLY_BUILD_DOCKERFILE` | `branches.<name>.docker.dockerfile` |
|
||||||
|
| `GITTALLY_BUILD_DOCKER_CONTEXT` | `branches.<name>.docker.context` |
|
||||||
|
| `GITTALLY_BUILD_DOCKER_NETWORK` | `branches.<name>.docker.network` — default is now Docker's default network, not `host` |
|
||||||
|
| `GITTALLY_BUILD_DOCKER_ENV` | `branches.<name>.docker.env` — YAML map instead of space-separated assignments |
|
||||||
|
| `GITTALLY_ARTIFACT_SERVER_PORT` | `server.port` |
|
||||||
|
| `GITTALLY_ARTIFACT_SERVER_BIND_ADDRESS` | `server.bindAddress` |
|
||||||
|
| `GITTALLY_ARTIFACT_PUBLIC_BASE_URL` | `server.publicBaseUrl` |
|
||||||
|
| `GITTALLY_ARTIFACT_BUILD_RETENTION_PER_BRANCH` | `artifacts.retentionPerBranch` — build count only; the legacy age suffix (`h`/`d`) is not supported |
|
||||||
|
| `GITTALLY_IMPRESSUM_URL` | `server.impressumUrl` |
|
||||||
|
| `GITTALLY_AUTO_BUILD_BRANCHES` | `branches.<name>.autoBuild.enabled: true` per branch instead of a branch list |
|
||||||
|
| `GITTALLY_AUTO_BUILD_TIMES` | `branches.<name>.autoBuild.times` — YAML list, per branch |
|
||||||
|
| `GITTALLY_GITEA_BASE_URL` | `gitea.baseUrl` |
|
||||||
|
| `GITTALLY_GITEA_OWNER` | `gitea.owner` |
|
||||||
|
| `GITTALLY_GITEA_REPO` | `gitea.repo` |
|
||||||
|
| `GITTALLY_GITEA_STATUS_CONTEXT` | `gitea.statusContext` |
|
||||||
|
| `GITTALLY_GITEA_GIT_USERNAME` | `git.account` — in `.git/gittally/.gittally.yml` |
|
||||||
|
| `GITTALLY_GITEA_TOKEN` | `git.token` — in `.git/gittally/.gittally.yml`, never committed |
|
||||||
|
|
||||||
|
New keys without a legacy counterpart: `builds.maxConcurrent`, `artifacts.rootDir`, and `watcher.pollInterval`.
|
||||||
|
|
||||||
|
## Intentionally Not Ported
|
||||||
|
|
||||||
|
- Managed nginx/Let's Encrypt container (`GITTALLY_ARTIFACT_NGINX_*`, `GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL`) — use the host's reverse proxy, see [deployment.md](deployment.md).
|
||||||
|
- Self-install and self-update (`--install`, `--pull`, `GITTALLY_INSTALL_DIR`) — replaced by jar deployment plus `init --systemd`.
|
||||||
|
- `GITTALLY_BUILD_DOCKER_PREFLIGHT_COMMAND` and `GITTALLY_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — hsadmin-ng-specific; use `branches.<name>.docker.env` if needed.
|
||||||
|
- `HSADMIN_NG_*` environment-variable fallbacks.
|
||||||
|
- Env-file configuration itself — the systemd `EnvironmentFile` now only tunes the JVM (`JAVA_OPTS`).
|
||||||
|
- `GITTALLY_GITEA_DELETED_STATUS_DESCRIPTION`, `GITTALLY_BIN_FORWARD`, `GITTALLY_CONFIG_*` — internal legacy mechanics without a counterpart.
|
||||||
|
|
||||||
|
## Build History
|
||||||
|
|
||||||
|
Legacy build history (`.git/git-watch-origin-and-test/build-results.tsv`) is **not** imported; history starts fresh.
|
||||||
|
The formats differ substantially (TSV vs. JSON with commit metadata and artifact keys), and retention would prune imported rows quickly anyway.
|
||||||
|
Old artifacts under the legacy artifact root remain readable on disk until you delete them.
|
||||||
|
|
||||||
|
## Manual Migration Steps
|
||||||
|
|
||||||
|
1. Stop and remove the legacy service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user disable --now gitTally.service
|
||||||
|
rm -f ~/.config/systemd/user/gitTally.service
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build and place the jar as described in [deployment.md](deployment.md).
|
||||||
|
3. In the repository, run `java -jar ~/bin/gittally.jar init`.
|
||||||
|
4. Transfer your settings from the legacy env file into `.gittally.yml` using the table above.
|
||||||
|
5. Put `git.account` and `git.token` into `.git/gittally/.gittally.yml`.
|
||||||
|
6. Verify the effective configuration: `java -jar ~/bin/gittally.jar config:print --full`.
|
||||||
|
7. Install and start the new service: `init --systemd` plus the printed commands, see [deployment.md](deployment.md).
|
||||||
|
8. Optionally clean up legacy state: `.git/git-watch-origin-and-test/` and the legacy artifact root.
|
||||||
@@ -34,3 +34,28 @@ Housekeeping:
|
|||||||
|
|
||||||
- `./gradlew ktlintFormat` then `./gradlew build` is green.
|
- `./gradlew ktlintFormat` then `./gradlew build` is green.
|
||||||
- A fresh clone can follow `docs/deployment.md` to a running service (manual walkthrough; document the result in this file).
|
- A fresh clone can follow `docs/deployment.md` to a running service (manual walkthrough; document the result in this file).
|
||||||
|
|
||||||
|
## Implementation Notes (2026-07-07)
|
||||||
|
|
||||||
|
Implemented as designed: `init --systemd` (an option on `init`, not a separate subcommand) generates the unit and its `EnvironmentFile` under `.git/gittally/`, prints the install commands, and never touches `~/.config/systemd` itself (no self-install).
|
||||||
|
`SystemdServiceFiles` builds the file contents and is unit-tested by content assertions, including the legacy `%` escaping and `ExecStart` quoting.
|
||||||
|
`docs/deployment.md` and `docs/migration-from-legacy.md` were written; `README.md`, `docs/bootstrapping.md`, `docs/GitTally-Konzept.md`, and `CLAUDE.md` were updated to reference them.
|
||||||
|
|
||||||
|
Deviations and decisions:
|
||||||
|
|
||||||
|
- The unit is named per repository (`gittally-<repo-name>.service`) instead of the global legacy `gitTally.service`, because one instance serves one repository and several repositories can share a host.
|
||||||
|
- `ExecStart` uses the `java` binary and the jar path of the JVM that ran `init --systemd`, so the unit points at the jar in place (legacy copied the script to an install dir); systemd expands `$JAVA_OPTS` from the `EnvironmentFile` into the command line.
|
||||||
|
When not started via `java -jar` (e.g. from Gradle), `init --systemd` prints an error instead of generating a broken unit.
|
||||||
|
- The `EnvironmentFile` only tunes the JVM (`JAVA_OPTS`); the legacy env file carried username/token, which now live in `.git/gittally/.gittally.yml`.
|
||||||
|
An existing `gittally.env` is kept; the unit file is regenerated on every run (same as legacy).
|
||||||
|
- The legacy `--nginx --docker` `ExecStart` flags were dropped (runtime selection is per-branch config now); the `After=… docker.service` ordering was kept.
|
||||||
|
- Legacy build history (`build-results.tsv`) is not imported — decided and documented in `docs/migration-from-legacy.md` (formats differ substantially; retention would prune imported rows quickly).
|
||||||
|
- ADR 0004 records the rewrite architecture decisions (JSON-file persistence behind a repository interface, polling UI, no managed nginx).
|
||||||
|
- `docs/GitTally-Konzept.md` review: only one real deviation found — "Builds laufen in Docker" became "nativ oder optional in Docker (pro Branch konfigurierbar)"; CLI capability lists gained build/retry; deployment links added.
|
||||||
|
|
||||||
|
Manual walkthrough (2026-07-07, fresh clone under `~/.cache`):
|
||||||
|
|
||||||
|
- Followed `docs/deployment.md` end to end: built the jar, copied it to a stable path, cloned the repository freshly, ran `init` and `init --systemd`, linked the generated unit, `daemon-reload`, started the service.
|
||||||
|
- The clone already contained the committed `.gittally.yml`, so only the machine config was created; `server.port` was overridden to a free port via `.git/gittally/.gittally.yml` to avoid clashing with a locally running instance.
|
||||||
|
- Result: unit `active (running)`, `GET /` returned 200, `/api/watcher` showed a successful poll, `journalctl --user -u …` showed the startup log, `restart` and `stop` worked; the unit link and the scratch clone were removed afterwards.
|
||||||
|
- Not machine-verified: `systemctl --user enable` and `loginctl enable-linger` (the walkthrough used a transient `start` to leave no persistent service behind) and the nginx/certbot section (no public host available); those commands were reviewed against the systemd/certbot documentation instead.
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ Completion:
|
|||||||
|
|
||||||
- [x] `10-cli-commands.md` — CLI build/status commands
|
- [x] `10-cli-commands.md` — CLI build/status commands
|
||||||
- [x] `11-docker-build-runtime.md` — optional Docker build execution
|
- [x] `11-docker-build-runtime.md` — optional Docker build execution
|
||||||
- [ ] `12-deployment.md` — systemd service, migration from legacy, docs
|
- [x] `12-deployment.md` — systemd service, migration from legacy, docs
|
||||||
|
|
||||||
Steps 01–03 are independent of each other.
|
Steps 01–03 are independent of each other.
|
||||||
Steps 04–06 depend on 01–03.
|
Steps 04–06 depend on 01–03.
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# DEPRECATED: This script has been replaced by the Kotlin/Spring application in this repository.
|
||||||
|
# See docs/migration-from-legacy.md for the migration guide; the script is kept only as a behavioral reference.
|
||||||
|
#
|
||||||
# A small and opinionated continuous integration tool for projects that do not have the hardware budget or operational staff for large CI/CD systems.
|
# A small and opinionated continuous integration tool for projects that do not have the hardware budget or operational staff for large CI/CD systems.
|
||||||
# Made to run locally or in Designed for Hostsharing Container Server environments with Docker (Podman not tested yet).
|
# Made to run locally or in Designed for Hostsharing Container Server environments with Docker (Podman not tested yet).
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.hoennig.gittally.commands
|
|||||||
import de.hoennig.gittally.git.GitService
|
import de.hoennig.gittally.git.GitService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.Option
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
@@ -17,6 +18,18 @@ class InitCommand(
|
|||||||
) : Runnable {
|
) : Runnable {
|
||||||
var workingDir: Path = Paths.get(".")
|
var workingDir: Path = Paths.get(".")
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
names = ["--systemd"],
|
||||||
|
description = ["also generate a systemd user unit that runs `gittally server` for this repository"],
|
||||||
|
)
|
||||||
|
var systemd: Boolean = false
|
||||||
|
|
||||||
|
/** Replaceable for tests: the jar this JVM was started from, or null when not run via `java -jar`. */
|
||||||
|
internal var jarPathResolver: () -> Path? = { runningJarPath() }
|
||||||
|
|
||||||
|
/** Replaceable for tests: the `java` binary of the current JVM. */
|
||||||
|
internal var javaExecutableResolver: () -> Path = { Paths.get(System.getProperty("java.home"), "bin", "java") }
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val normalizedWorkingDir = workingDir.toAbsolutePath().normalize()
|
val normalizedWorkingDir = workingDir.toAbsolutePath().normalize()
|
||||||
val root =
|
val root =
|
||||||
@@ -32,6 +45,9 @@ class InitCommand(
|
|||||||
|
|
||||||
createRepoInstallConfig(root, detected, normalizedWorkingDir)
|
createRepoInstallConfig(root, detected, normalizedWorkingDir)
|
||||||
createProjectConfig(root, detected, normalizedWorkingDir)
|
createProjectConfig(root, detected, normalizedWorkingDir)
|
||||||
|
if (systemd) {
|
||||||
|
createSystemdFiles(root, normalizedWorkingDir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun detectFromUrl(url: String?): DetectedValues {
|
private fun detectFromUrl(url: String?): DetectedValues {
|
||||||
@@ -165,10 +181,70 @@ class InitCommand(
|
|||||||
println("created ${file.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
println("created ${file.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createSystemdFiles(
|
||||||
|
root: Path,
|
||||||
|
normalizedWorkingDir: Path,
|
||||||
|
) {
|
||||||
|
val jarPath = jarPathResolver()
|
||||||
|
if (jarPath == null) {
|
||||||
|
println("Error: cannot determine the GitTally jar path — run `init --systemd` via `java -jar <path-to>/gittally.jar`")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val gittallyDir = root.resolve(".git/gittally")
|
||||||
|
gittallyDir.toFile().mkdirs()
|
||||||
|
val unitName = SystemdServiceFiles.unitName(root)
|
||||||
|
val unitFile = gittallyDir.resolve(unitName)
|
||||||
|
val envFile = gittallyDir.resolve(SystemdServiceFiles.ENV_FILE_NAME)
|
||||||
|
|
||||||
|
unitFile.toFile().writeText(
|
||||||
|
SystemdServiceFiles.unitFileContent(
|
||||||
|
repoRoot = root,
|
||||||
|
javaExecutable = javaExecutableResolver(),
|
||||||
|
jarPath = jarPath,
|
||||||
|
envFile = envFile,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
println("created ${unitFile.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||||
|
|
||||||
|
if (envFile.toFile().exists()) {
|
||||||
|
println("${envFile.toFile().relativeTo(normalizedWorkingDir.toFile())} already exists — not overwritten")
|
||||||
|
} else {
|
||||||
|
envFile.toFile().writeText(SystemdServiceFiles.envFileContent())
|
||||||
|
println("created ${envFile.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||||
|
}
|
||||||
|
|
||||||
|
println("install and start the service with:")
|
||||||
|
println(" ln -sf $unitFile ~/.config/systemd/user/$unitName")
|
||||||
|
println(" systemctl --user daemon-reload")
|
||||||
|
println(" systemctl --user enable --now $unitName")
|
||||||
|
}
|
||||||
|
|
||||||
private data class DetectedValues(
|
private data class DetectedValues(
|
||||||
val baseUrl: String = "",
|
val baseUrl: String = "",
|
||||||
val owner: String = "",
|
val owner: String = "",
|
||||||
val repo: String = "",
|
val repo: String = "",
|
||||||
val account: String = "",
|
val account: String = "",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* The jar this JVM was started from. With `java -jar` the launch command starts with the
|
||||||
|
* jar path; as a fallback (e.g. custom launchers) the Spring Boot loader's nested code
|
||||||
|
* source URL contains it. Null when running from classes (IDE, Gradle, tests).
|
||||||
|
*/
|
||||||
|
private fun runningJarPath(): Path? {
|
||||||
|
val launchCommand = System.getProperty("sun.java.command").orEmpty().substringBefore(' ')
|
||||||
|
if (launchCommand.endsWith(".jar")) {
|
||||||
|
return Paths.get(launchCommand).toAbsolutePath().normalize()
|
||||||
|
}
|
||||||
|
val codeSource =
|
||||||
|
InitCommand::class.java.protectionDomain.codeSource
|
||||||
|
?.location
|
||||||
|
?.toString()
|
||||||
|
.orEmpty()
|
||||||
|
return Regex("""(/[^!]*?\.jar)""")
|
||||||
|
.find(codeSource)
|
||||||
|
?.let { Paths.get(it.groupValues[1]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package de.hoennig.gittally.commands
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the content of the systemd user unit and its `EnvironmentFile` for running
|
||||||
|
* `gittally server` as a service — the shape of the legacy `generate_systemd_config`,
|
||||||
|
* without the self-copy/self-update machinery (the unit points at the jar in place).
|
||||||
|
*/
|
||||||
|
object SystemdServiceFiles {
|
||||||
|
const val ENV_FILE_NAME = "gittally.env"
|
||||||
|
|
||||||
|
/** Per-repository unit name, because one GitTally instance serves exactly one repository. */
|
||||||
|
fun unitName(repoRoot: Path): String = "gittally-${sanitize(repoRoot.fileName.toString())}.service"
|
||||||
|
|
||||||
|
fun unitFileContent(
|
||||||
|
repoRoot: Path,
|
||||||
|
javaExecutable: Path,
|
||||||
|
jarPath: Path,
|
||||||
|
envFile: Path,
|
||||||
|
): String =
|
||||||
|
"""
|
||||||
|
[Unit]
|
||||||
|
Description=GitTally CI for ${repoRoot.fileName}
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=${systemdPath("$repoRoot")}
|
||||||
|
EnvironmentFile=-${systemdPath("$envFile")}
|
||||||
|
ExecStart=${systemdQuote("$javaExecutable")} ${'$'}JAVA_OPTS -jar ${systemdQuote("$jarPath")} server
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
""".trimIndent() + "\n"
|
||||||
|
|
||||||
|
fun envFileContent(): String =
|
||||||
|
"""
|
||||||
|
# EnvironmentFile for the GitTally systemd service.
|
||||||
|
# GitTally itself is configured via .gittally.yml and .git/gittally/.gittally.yml,
|
||||||
|
# not via environment variables; this file only tunes the JVM process.
|
||||||
|
#JAVA_OPTS=-Xmx256m
|
||||||
|
""".trimIndent() + "\n"
|
||||||
|
|
||||||
|
private fun sanitize(name: String): String = name.replace(Regex("[^A-Za-z0-9_.-]"), "-")
|
||||||
|
|
||||||
|
/** Escape `%` specifiers in systemd unit values (legacy `systemd_path`). */
|
||||||
|
private fun systemdPath(value: String): String = value.replace("%", "%%")
|
||||||
|
|
||||||
|
/** Quote one `ExecStart` word (legacy `systemd_quote`). */
|
||||||
|
private fun systemdQuote(value: String): String =
|
||||||
|
"\"" +
|
||||||
|
value
|
||||||
|
.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("%", "%%") +
|
||||||
|
"\""
|
||||||
|
}
|
||||||
@@ -82,6 +82,62 @@ class InitCommandTest : FunSpec() {
|
|||||||
projectConfig.toFile().readText() shouldBe "existing: content"
|
projectConfig.toFile().readText() shouldBe "existing: content"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("--systemd generates unit and environment file with install instructions") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
initCommand.systemd = true
|
||||||
|
initCommand.jarPathResolver = { Paths.get("/home/ci/bin/gittally.jar") }
|
||||||
|
initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") }
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
val unitName = SystemdServiceFiles.unitName(tempDir)
|
||||||
|
val unitFile = tempDir.resolve(".git/gittally/$unitName")
|
||||||
|
unitFile.toFile().shouldExist()
|
||||||
|
val unitContent = unitFile.toFile().readText()
|
||||||
|
unitContent shouldContain "WorkingDirectory=$tempDir"
|
||||||
|
unitContent shouldContain """ExecStart="/usr/bin/java" ${'$'}JAVA_OPTS -jar "/home/ci/bin/gittally.jar" server"""
|
||||||
|
|
||||||
|
tempDir.resolve(".git/gittally/gittally.env").toFile().shouldExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
test("--systemd keeps an existing environment file") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
initCommand.systemd = true
|
||||||
|
initCommand.jarPathResolver = { Paths.get("/home/ci/bin/gittally.jar") }
|
||||||
|
initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") }
|
||||||
|
|
||||||
|
val envFile = tempDir.resolve(".git/gittally/gittally.env")
|
||||||
|
Files.createDirectories(envFile.parent)
|
||||||
|
envFile.toFile().writeText("JAVA_OPTS=-Xmx1g\n")
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
envFile.toFile().readText() shouldBe "JAVA_OPTS=-Xmx1g\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
test("--systemd without a resolvable jar path generates no unit file") {
|
||||||
|
val tempDir = Files.createTempDirectory("gittally-init-test")
|
||||||
|
initCommand.workingDir = tempDir
|
||||||
|
initCommand.systemd = true
|
||||||
|
initCommand.jarPathResolver = { null }
|
||||||
|
|
||||||
|
every { gitService.getTopLevel(tempDir) } returns tempDir
|
||||||
|
every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"
|
||||||
|
|
||||||
|
initCommand.run()
|
||||||
|
|
||||||
|
val unitFile = tempDir.resolve(".git/gittally/${SystemdServiceFiles.unitName(tempDir)}")
|
||||||
|
unitFile.toFile().exists() shouldBe false
|
||||||
|
}
|
||||||
|
|
||||||
test("reproduces path root mismatch issue") {
|
test("reproduces path root mismatch issue") {
|
||||||
val tempDir = Files.createTempDirectory("gittally-init-test").toAbsolutePath().normalize()
|
val tempDir = Files.createTempDirectory("gittally-init-test").toAbsolutePath().normalize()
|
||||||
initCommand.workingDir = Paths.get(".") // Set to relative path as in real app
|
initCommand.workingDir = Paths.get(".") // Set to relative path as in real app
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package de.hoennig.gittally.commands
|
||||||
|
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.kotest.matchers.string.shouldContain
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
class SystemdServiceFilesTest : FunSpec() {
|
||||||
|
init {
|
||||||
|
test("unit name is derived from the sanitized repository directory name") {
|
||||||
|
SystemdServiceFiles.unitName(Paths.get("/srv/repos/my-repo")) shouldBe "gittally-my-repo.service"
|
||||||
|
SystemdServiceFiles.unitName(Paths.get("/srv/repos/my repo!")) shouldBe "gittally-my-repo-.service"
|
||||||
|
}
|
||||||
|
|
||||||
|
test("unit file runs the server jar in the repository with restart and environment file") {
|
||||||
|
val content =
|
||||||
|
SystemdServiceFiles.unitFileContent(
|
||||||
|
repoRoot = Paths.get("/srv/repos/my-repo"),
|
||||||
|
javaExecutable = Paths.get("/usr/lib/jvm/java-21/bin/java"),
|
||||||
|
jarPath = Paths.get("/home/ci/bin/gittally.jar"),
|
||||||
|
envFile = Paths.get("/srv/repos/my-repo/.git/gittally/gittally.env"),
|
||||||
|
)
|
||||||
|
|
||||||
|
content shouldContain "Description=GitTally CI for my-repo"
|
||||||
|
content shouldContain "After=network-online.target docker.service"
|
||||||
|
content shouldContain "WorkingDirectory=/srv/repos/my-repo"
|
||||||
|
content shouldContain "EnvironmentFile=-/srv/repos/my-repo/.git/gittally/gittally.env"
|
||||||
|
content shouldContain
|
||||||
|
"""ExecStart="/usr/lib/jvm/java-21/bin/java" ${'$'}JAVA_OPTS -jar "/home/ci/bin/gittally.jar" server"""
|
||||||
|
content shouldContain "Restart=always"
|
||||||
|
content shouldContain "RestartSec=30"
|
||||||
|
content shouldContain "WantedBy=default.target"
|
||||||
|
}
|
||||||
|
|
||||||
|
test("percent signs in paths are escaped for systemd") {
|
||||||
|
val content =
|
||||||
|
SystemdServiceFiles.unitFileContent(
|
||||||
|
repoRoot = Paths.get("/srv/100%-repo"),
|
||||||
|
javaExecutable = Paths.get("/usr/bin/java"),
|
||||||
|
jarPath = Paths.get("/srv/100%-repo/gittally.jar"),
|
||||||
|
envFile = Paths.get("/srv/100%-repo/gittally.env"),
|
||||||
|
)
|
||||||
|
|
||||||
|
content shouldContain "WorkingDirectory=/srv/100%%-repo"
|
||||||
|
content shouldContain "EnvironmentFile=-/srv/100%%-repo/gittally.env"
|
||||||
|
content shouldContain """-jar "/srv/100%%-repo/gittally.jar" server"""
|
||||||
|
}
|
||||||
|
|
||||||
|
test("environment file template only tunes the JVM") {
|
||||||
|
val content = SystemdServiceFiles.envFileContent()
|
||||||
|
|
||||||
|
content shouldContain "#JAVA_OPTS="
|
||||||
|
content shouldContain ".gittally.yml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user