Drop the legacy env-to-YAML conversion from the setup tool

The old bash script configured itself through `GITTALLY_*` environment
variables. The blanket rename rewrote those literals, so the converter
was looking for `WERKATOR_*` — a spelling no host has ever written. Fed
a real legacy file it would have found nothing and written an almost
empty configuration, without an error, which is the same silent failure
this rename is otherwise careful to avoid.

The conversion has served its purpose with the vm2176 to vm4006
migration, so it goes instead of being repaired. What remains is the
setup of a new instance: the preconditions, the credential prompt, and
the machine configuration written mode 600 — now carrying the host's
public URL as well, since that is host-specific too. Everything the
repository builds comes from `init` and its templates.

It also stops emitting a legacy `branches:` section, which step 18 is
about to reject outright.

`docs/plan/00-legacy-analysis.md` and `13-nginx-tls.md` get the real
`GITTALLY_*` spelling back: they record what the old script read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-31 06:58:43 +02:00
co-authored by Claude Opus 5
parent 6a18ec27fd
commit 25c0eb94b6
4 changed files with 44 additions and 194 deletions
+4 -4
View File
@@ -90,7 +90,7 @@ No status changes observable during a build (control loop):
Verify need before porting any of these: Verify need before porting any of these:
- `WERKATOR_BUILD_DOCKER_PREFLIGHT_COMMAND`, `WERKATOR_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — highly hsadmin-ng-specific defaults. - `GITTALLY_BUILD_DOCKER_PREFLIGHT_COMMAND`, `GITTALLY_BUILD_DOCKER_JAVA_TOOL_OPTIONS` — highly hsadmin-ng-specific defaults.
- `WERKATOR_ARTIFACT_NGINX_*`, `WERKATOR_ARTIFACT_LETSENCRYPT_EMAIL` — dropped with nginx management; revived as `server.nginx.*` by step 13 (ADR 0005). - `GITTALLY_ARTIFACT_NGINX_*`, `GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL` — dropped with nginx management; revived as `server.nginx.*` by step 13 (ADR 0005).
- `WERKATOR_IMPRESSUM_URL` — keep as optional simple footer link if wanted. - `GITTALLY_IMPRESSUM_URL` — keep as optional simple footer link if wanted.
- `WERKATOR_INSTALL_DIR` — dropped with self-install. - `GITTALLY_INSTALL_DIR` — dropped with self-install.
+1 -1
View File
@@ -38,7 +38,7 @@ Port the legacy nginx subsystem (functions `configure_artifact_nginx_defaults` ~
- With `server.nginx.enabled: false` (default) nothing changes; no container is touched. - With `server.nginx.enabled: false` (default) nothing changes; no container is touched.
- Manual walkthrough on a Docker host: nginx container starts with the init config and proxies HTTP to werkator. - Manual walkthrough on a Docker host: nginx container starts with the init config and proxies HTTP to werkator.
Full ACME issuance needs a public DNS name; if none is available, verify the certbot argv and the full-config path against the legacy script and document that in this file. Full ACME issuance needs a public DNS name; if none is available, verify the certbot argv and the full-config path against the legacy script and document that in this file.
- `docs/deployment.md` gains a section for hosts without a reverse proxy; `docs/migration-from-legacy.md` maps the `WERKATOR_ARTIFACT_NGINX_*`/`WERKATOR_ARTIFACT_LETSENCRYPT_EMAIL` variables. - `docs/deployment.md` gains a section for hosts without a reverse proxy; `docs/migration-from-legacy.md` maps the `GITTALLY_ARTIFACT_NGINX_*`/`GITTALLY_ARTIFACT_LETSENCRYPT_EMAIL` variables.
## Result (2026-07-08) ## Result (2026-07-08)
@@ -134,8 +134,6 @@ The rollback section names the asymmetry: the new version reads both names, the
- The default `gitea.statusContext` is now `werkator` (lowercase, as a value read by a machine). - The default `gitea.statusContext` is now `werkator` (lowercase, as a value read by a machine).
It is also the label a human sees in Gitea; `Werkator` there would be a deliberate exception to the spelling rule. It is also the label a human sees in Gitea; `Werkator` there would be a deliberate exception to the spelling rule.
- The legacy environment-variable names documented for the old bash script are now spelled `WERKATOR_*`, which is a name that never existed — the real ones were `GITTALLY_*`.
Kept, because every host they addressed has long since moved to the YAML configuration, and the converter in `tools/setup-werkator-instance` is a one-shot tool for exactly those hosts.
## Additional Changes ## Additional Changes
@@ -145,6 +143,11 @@ The rollback section names the asymmetry: the new version reads both names, the
- Fixed a broken web UI: five templates already referenced `/werkator.js` and `/werkator.css` while the static files were still named `gittally.*`, so every page but the release notes served neither stylesheet nor live updates. - Fixed a broken web UI: five templates already referenced `/werkator.js` and `/werkator.css` while the static files were still named `gittally.*`, so every page but the release notes served neither stylesheet nor live updates.
- Renamed the Kotlin types `werkatorCommand` and `werkatorMeta`, which a case-insensitive replace had left lowercase, and the constant `werkator_LABEL` to `WERKATOR_LABEL`. - Renamed the Kotlin types `werkatorCommand` and `werkatorMeta`, which a case-insensitive replace had left lowercase, and the constant `werkator_LABEL` to `WERKATOR_LABEL`.
- Added the release-notes entry for the rename, as `unreleased`. - Added the release-notes entry for the rename, as `unreleased`.
- Removed the legacy environment-variable conversion from `tools/setup-werkator-instance`.
The blanket rename had rewritten the old script's `GITTALLY_*` variables to a spelling that never existed on any host, so the conversion would have read nothing from a real legacy file and written an almost empty configuration — silently.
The conversion has served its purpose with the vm2176 → vm4006 migration; what remains is the setup of a new instance: the preconditions, the credential prompt, and the machine configuration written mode 600.
It also stops emitting a legacy `branches:` section, which [plan step 18](../plan/18-remove-branches-section.md) is about to reject outright.
- Restored the real `GITTALLY_*` spelling in `docs/plan/00-legacy-analysis.md` and `docs/plan/13-nginx-tls.md`, which record what the old script read.
## Follow-up PRs ## Follow-up PRs
+34 -187
View File
@@ -1,26 +1,25 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Set up a fresh Werkator instance on a new Docker host by converting a legacy # Set up a fresh Werkator instance on a Docker host.
# .werkator env configuration to the new YAML format.
# #
# The legacy config comes either from a file passed as the optional third # Checks the preconditions, prompts for the Gitea credentials and writes them —
# argument, or — when omitted — from the embedded snapshot of the hs.hsadmin.ng # together with the host's public URL — into the machine configuration, which is
# .werkator on vm2176. Either way the script sources it, converts it to YAML # the layer that is never committed. Then runs `init`, which writes the commented
# (mapping the legacy env vars to YAML keys), substitutes the public hostname # templates for everything else.
# from the command line, and prompts for the Gitea secrets.
# #
# Run this ON the new Docker host. The repository must already be cloned at the # The secrets are written with umask 077, so the file is mode 600 at creation and
# given repo-dir. Builds run in Docker; Werkator itself runs from the jar via # the token is never world-readable, not even between the redirect and a chmod.
# java, so a JRE and the jar are required (see checks below).
# #
# Usage: setup-werkator-instance [--force] <public-hostname> <repo-dir> [legacy-config] # Run this ON the host. The repository must already be cloned at the given
# <public-hostname> FQDN this instance is reachable at, e.g. vm2180.hostsharing.net # repo-dir. Builds run in Docker; Werkator itself runs from the jar via java, so
# a JRE and the jar are required (see checks below).
#
# Usage: setup-werkator-instance [--force] <public-hostname> <repo-dir>
# <public-hostname> FQDN this instance is reachable at, e.g. vm4006.hostsharing.net
# (used for server.publicBaseUrl) # (used for server.publicBaseUrl)
# <repo-dir> path to the cloned git repository to configure, # <repo-dir> path to the cloned git repository to configure,
# e.g. ~/hs.hsadmin.ng # e.g. ~/hs.hsadmin.ng
# [legacy-config] optional path to a legacy .werkator file to convert; # --force overwrite a machine configuration that already exists
# if omitted, the embedded vm2176 snapshot is used
# --force overwrite .werkator.yml files that already exist
set -euo pipefail set -euo pipefail
@@ -32,8 +31,8 @@ die() { echo "ERROR: $*" >&2; exit 1; }
warn() { echo "WARNING: $*" >&2; } warn() { echo "WARNING: $*" >&2; }
usage() { usage() {
echo "usage: setup-werkator-instance [--force] <public-hostname> <repo-dir> [legacy-config]" >&2 echo "usage: setup-werkator-instance [--force] <public-hostname> <repo-dir>" >&2
echo "example: setup-werkator-instance vm2180.hostsharing.net ~/hs.hsadmin.ng" >&2 echo "example: setup-werkator-instance vm4006.hostsharing.net ~/hs.hsadmin.ng" >&2
exit 1 exit 1
} }
@@ -48,55 +47,13 @@ for arg in "$@"; do
*) positional+=("$arg") ;; *) positional+=("$arg") ;;
esac esac
done done
case ${#positional[@]} in [ ${#positional[@]} -eq 2 ] || usage
2) legacy_file="" ;;
3) legacy_file="${positional[2]}" ;;
*) usage ;;
esac
hostname="${positional[0]}" hostname="${positional[0]}"
REPO_DIR="${positional[1]}" REPO_DIR="${positional[1]}"
[ -z "$legacy_file" ] || [ -f "$legacy_file" ] || die "legacy config not found: $legacy_file"
# ---------------------------------------------------- legacy config source -- # ------------------------------------------------------------- yaml helper --
# load_legacy_config populates the werkator_* variables, either by sourcing the
# file passed on the command line, or from the embedded vm2176 snapshot below.
# Secrets (git username/token) are intentionally never read here — they are
# prompted for, so a token in a legacy file is not echoed or reused implicitly.
load_legacy_config() {
if [ -n "$legacy_file" ]; then
# shellcheck source=/dev/null
. "$legacy_file"
return
fi
# embedded, non-secret snapshot of tallyman@vm2176:~/hs.hsadmin.ng/.werkator
export WERKATOR_BUILD_COMMAND='./gradlew --console=plain --no-daemon --no-build-cache jacocoTestReport check generateDocumentation -x pitest -x dependencyCheckAnalyze'
export WERKATOR_BUILD_CLEAN_COMMAND='rm -rf build'
export WERKATOR_BUILD_ARTEFACT_DIRS='build/reports:build/doc'
export WERKATOR_BUILD_STDOUT_LOG='build.stdout.log'
export WERKATOR_BUILD_STDERR_LOG='build.stderr.log'
export WERKATOR_BUILD_DOCKER_IMAGE='hsadmin-ng-build-env:latest'
export WERKATOR_BUILD_DOCKERFILE='Jenkins/jenkins-agent/Dockerfile'
export WERKATOR_BUILD_DOCKER_CONTEXT='Jenkins/jenkins-agent'
export WERKATOR_BUILD_DOCKER_NETWORK='host'
export WERKATOR_BUILD_DOCKER_ENV='TESTCONTAINERS_RYUK_DISABLED=true'
# legacy hsadmin-ng-specific JAVA_TOOL_OPTIONS — carried into docker.env so the
# Testcontainers builds keep finding the docker socket (see migration doc).
export WERKATOR_BUILD_DOCKER_JAVA_TOOL_OPTIONS='-Ddocker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy -Dtestcontainers.docker.socket.override=/var/run/docker.sock'
export WERKATOR_ARTIFACT_SERVER_PORT='18080'
export WERKATOR_ARTIFACT_SERVER_BIND_ADDRESS='0.0.0.0'
export WERKATOR_ARTIFACT_BUILD_RETENTION_PER_BRANCH='3'
export WERKATOR_IMPRESSUM_URL='https://michael.hoennig.de/imprint.html'
export WERKATOR_GITEA_BASE_URL='https://dev.hostsharing.net'
export WERKATOR_GITEA_OWNER='hostsharing'
export WERKATOR_GITEA_REPO='hs.hsadmin.ng'
export WERKATOR_GITEA_STATUS_CONTEXT='werkator'
}
# ----------------------------------------------------------------- helpers --
# quote a scalar for double-quoted YAML
yaml_quote() { yaml_quote() {
local s=${1//\\/\\\\} local s=${1//\\/\\\\}
s=${s//\"/\\\"} s=${s//\"/\\\"}
@@ -117,154 +74,41 @@ if ! command -v java >/dev/null 2>&1; then
warn "no java in PATH — install a JRE (e.g. Temurin 21 into ~/opt) before running init/server" warn "no java in PATH — install a JRE (e.g. Temurin 21 into ~/opt) before running init/server"
fi fi
machine_yml="$REPO_DIR/.git/werkator/.werkator.yml"
if ! $force; then
[ -e "$machine_yml" ] && die "$machine_yml already exists — re-run with --force to overwrite"
fi
# ----------------------------------------------------------- prompt secrets -- # ----------------------------------------------------------- prompt secrets --
# suggest the account from the legacy config's git username, if it sets one
suggested_account=$( set +u; load_legacy_config >/dev/null 2>&1; printf '%s' "${WERKATOR_GITEA_GIT_USERNAME:-jenkins-ci}" )
echo "== Gitea credentials for this instance (used for git HTTPS auth + status API)" echo "== Gitea credentials for this instance (used for git HTTPS auth + status API)"
read -r -p " Gitea git account [$suggested_account]: " git_account read -r -p " Gitea git account [jenkins-ci]: " git_account
git_account=${git_account:-$suggested_account} git_account=${git_account:-jenkins-ci}
read -r -s -p " Gitea API token: " git_token; echo read -r -s -p " Gitea API token: " git_token; echo
[ -n "$git_token" ] || die "the Gitea token must not be empty" [ -n "$git_token" ] || die "the Gitea token must not be empty"
# -------------------------------------------------------------- convert cfg --
project_yml="$REPO_DIR/.werkator.yml"
machine_yml="$REPO_DIR/.git/werkator/.werkator.yml"
public_base_url="https://$hostname/"
if ! $force; then
for f in "$project_yml" "$machine_yml"; do
[ -e "$f" ] && die "$f already exists — re-run with --force to overwrite"
done
fi
config_source=${legacy_file:-"embedded vm2176 snapshot"}
echo "== writing $project_yml (public host: $hostname, source: $config_source)"
(
set +u
load_legacy_config
# warn about legacy keys that have no counterpart in the new config
for var in \
WERKATOR_ARTIFACT_NGINX_SERVER_NAME WERKATOR_ARTIFACT_NGINX_HTTP_PORT \
WERKATOR_ARTIFACT_NGINX_HTTPS_PORT WERKATOR_ARTIFACT_LETSENCRYPT_EMAIL \
WERKATOR_ARTIFACT_AUTH_MODE WERKATOR_BUILD_DOCKER_PREFLIGHT_COMMAND \
WERKATOR_INSTALL_DIR
do
[ -n "${!var}" ] && warn "$var is set but has no counterpart in the new config — skipped"
done
# retention: strip a legacy age suffix (h/d), only the build count is supported
retention="$WERKATOR_ARTIFACT_BUILD_RETENTION_PER_BRANCH"
if [ -n "$retention" ] && [[ ! "$retention" =~ ^[0-9]+$ ]]; then
warn "retention '$retention' has an age suffix — only the build count is supported, using '${retention%%[!0-9]*}'"
retention=${retention%%[!0-9]*}
fi
emit() { printf '%s\n' "$1" >>"$project_yml"; }
: >"$project_yml"
emit "# Werkator configuration, converted from $config_source"
emit "# by tools/setup-werkator-instance; public host set to $hostname."
emit "# Omitted keys fall back to the application defaults — see docs/configuration.md."
emit ""
emit "server:"
emit " publicBaseUrl: $(yaml_quote "$public_base_url")"
[ -n "$WERKATOR_ARTIFACT_SERVER_PORT" ] && emit " port: $WERKATOR_ARTIFACT_SERVER_PORT"
[ -n "$WERKATOR_ARTIFACT_SERVER_BIND_ADDRESS" ] && emit " bindAddress: $WERKATOR_ARTIFACT_SERVER_BIND_ADDRESS"
[ -n "$WERKATOR_IMPRESSUM_URL" ] && emit " impressumUrl: $(yaml_quote "$WERKATOR_IMPRESSUM_URL")"
emit ""
emit "gitea:"
[ -n "$WERKATOR_GITEA_BASE_URL" ] && emit " baseUrl: $(yaml_quote "$WERKATOR_GITEA_BASE_URL")"
[ -n "$WERKATOR_GITEA_OWNER" ] && emit " owner: $(yaml_quote "$WERKATOR_GITEA_OWNER")"
[ -n "$WERKATOR_GITEA_REPO" ] && emit " repo: $(yaml_quote "$WERKATOR_GITEA_REPO")"
[ -n "$WERKATOR_GITEA_STATUS_CONTEXT" ] && emit " statusContext: $(yaml_quote "$WERKATOR_GITEA_STATUS_CONTEXT")"
emit ""
[ -n "$retention" ] && { emit "artifacts:"; emit " retentionPerBranch: $retention"; emit ""; }
[ -n "$WERKATOR_NEW_BRANCH_COMMIT_MAX_AGE" ] && { emit "watcher:"; emit " newBranchMaxAge: $WERKATOR_NEW_BRANCH_COMMIT_MAX_AGE"; emit ""; }
emit "branches:"
emit " default:"
[ -n "$WERKATOR_BUILD_CLEAN_COMMAND" ] && emit " cleanCommand: $(yaml_quote "$WERKATOR_BUILD_CLEAN_COMMAND")"
[ -n "$WERKATOR_BUILD_COMMAND" ] && emit " buildCommand: $(yaml_quote "$WERKATOR_BUILD_COMMAND")"
if [ -n "$WERKATOR_BUILD_ARTEFACT_DIRS" ]; then
emit " artifactDirs:"
# legacy separators: ';' (documented) and ':' (as used on vm2176)
IFS=';:' read -r -a dirs <<<"$WERKATOR_BUILD_ARTEFACT_DIRS"
for dir in "${dirs[@]}"; do
[ -n "$dir" ] && emit " - $(yaml_quote "$dir")"
done
fi
[ -n "$WERKATOR_BUILD_STDOUT_LOG" ] && emit " stdoutLog: $(yaml_quote "$WERKATOR_BUILD_STDOUT_LOG")"
[ -n "$WERKATOR_BUILD_STDERR_LOG" ] && emit " stderrLog: $(yaml_quote "$WERKATOR_BUILD_STDERR_LOG")"
if [ -n "$WERKATOR_BUILD_DOCKER_IMAGE" ] || [ -n "$WERKATOR_BUILD_DOCKERFILE" ]; then
emit " docker:"
emit " enabled: true"
[ -n "$WERKATOR_BUILD_DOCKER_IMAGE" ] && emit " image: $(yaml_quote "$WERKATOR_BUILD_DOCKER_IMAGE")"
[ -n "$WERKATOR_BUILD_DOCKERFILE" ] && emit " dockerfile: $(yaml_quote "$WERKATOR_BUILD_DOCKERFILE")"
[ -n "$WERKATOR_BUILD_DOCKER_CONTEXT" ] && emit " context: $(yaml_quote "$WERKATOR_BUILD_DOCKER_CONTEXT")"
# legacy default network was host; new default is Docker's default
emit " network: $(yaml_quote "${WERKATOR_BUILD_DOCKER_NETWORK:-host}")"
if [ -n "$WERKATOR_BUILD_DOCKER_ENV" ] || [ -n "$WERKATOR_BUILD_DOCKER_JAVA_TOOL_OPTIONS" ]; then
emit " env:"
for assignment in $WERKATOR_BUILD_DOCKER_ENV; do
emit " ${assignment%%=*}: $(yaml_quote "${assignment#*=}")"
done
# hsadmin-ng-specific JAVA_TOOL_OPTIONS carried into docker.env so the
# Testcontainers builds keep finding the docker socket (see migration doc)
[ -n "$WERKATOR_BUILD_DOCKER_JAVA_TOOL_OPTIONS" ] && \
emit " JAVA_TOOL_OPTIONS: $(yaml_quote "$WERKATOR_BUILD_DOCKER_JAVA_TOOL_OPTIONS")"
fi
fi
# legacy auto-build: a branch list plus global times -> per-branch autoBuild
if [ -n "$WERKATOR_AUTO_BUILD_BRANCHES" ]; then
times_yaml=""
for t in ${WERKATOR_AUTO_BUILD_TIMES//,/ }; do
times_yaml+="${times_yaml:+, }$(yaml_quote "$t")"
done
for branch in ${WERKATOR_AUTO_BUILD_BRANCHES//,/ }; do
emit " $(yaml_quote "$branch"):"
emit " autoBuild:"
emit " enabled: true"
[ -n "$times_yaml" ] && emit " times: [$times_yaml]"
done
fi
)
# -------------------------------------------------------- machine + secrets -- # -------------------------------------------------------- machine + secrets --
echo "== writing $machine_yml (secrets, mode 600)" echo "== writing $machine_yml (secrets, mode 600)"
mkdir -p "$(dirname "$machine_yml")" mkdir -p "$(dirname "$machine_yml")"
# the umask in the subshell makes the file mode 600 at creation, so the token is
# never world-readable — not even between the redirect and a follow-up chmod;
# an existing file is removed first, because the redirect would keep its mode # an existing file is removed first, because the redirect would keep its mode
rm -f "$machine_yml" rm -f "$machine_yml"
( (
umask 077 umask 077
{ {
echo "# Machine-specific overrides and secrets. Keys here win over .werkator.yml." echo "# Machine-specific overrides and secrets. Keys here win over .werkator.yml."
echo "server:"
echo " publicBaseUrl: $(yaml_quote "https://$hostname/")"
echo "git:" echo "git:"
echo " account: $(yaml_quote "$git_account")" echo " account: $(yaml_quote "$git_account")"
echo " token: $(yaml_quote "$git_token")" echo " token: $(yaml_quote "$git_token")"
} >"$machine_yml" } >"$machine_yml"
) )
echo "== converted project config:"
sed 's/^/ /' "$project_yml"
# ------------------------------------------------------------------- finish -- # ------------------------------------------------------------------- finish --
if $have_jar && $have_java; then if $have_jar && $have_java; then
echo "== running init (keeps the config files just written)" echo "== running init (keeps the machine config just written)"
( cd "$REPO_DIR" && java -jar "$JAR_PATH" init ) ( cd "$REPO_DIR" && java -jar "$JAR_PATH" init )
fi fi
@@ -272,6 +116,9 @@ cat <<EOF
Done. Werkator config for $hostname is in place. Done. Werkator config for $hostname is in place.
What this wrote is the host's part: the public URL and the credentials. What the
repository builds belongs in its committed .werkator.yml — see docs/configuration.md.
Verify and start on this host: Verify and start on this host:
cd $REPO_DIR cd $REPO_DIR
@@ -279,7 +126,7 @@ Verify and start on this host:
java -jar $JAR_PATH config:print --full --show-secrets # ... including git.token in clear text java -jar $JAR_PATH config:print --full --show-secrets # ... including git.token in clear text
java -jar $JAR_PATH server # or: java -jar $JAR_PATH init --systemd java -jar $JAR_PATH server # or: java -jar $JAR_PATH init --systemd
Note: Werkator binds to $hostname:${WERKATOR_ARTIFACT_SERVER_PORT:-18080} over plain HTTP. Note: Werkator binds to localhost by default and publicBaseUrl is https://$hostname/ —
publicBaseUrl is https://$hostname/ — terminate TLS in front of it with the host's terminate TLS in front of it with the host's reverse proxy, or enable the managed
reverse proxy (managed nginx/TLS is ADR 0005 / docs/plan/13-nginx-tls.md, not yet built). nginx container (server.nginx.enabled, see docs/deployment.md).
EOF EOF