Rename GitTally to Werkator
`gitTally` is the name of another product in the git space, so the rename is a precaution; nothing about what the build system does changes. The name follows one rule: `Werkator` where it is prose, capitalized where it is a Kotlin type and its file, lowercase everywhere a machine reads it — the command, packages, paths, configuration keys and values, the Gitea check context. Environment variables keep their convention and are uppercase throughout. Every configuration file is still found under its pre-rename name (`ConfigFiles`): `.gittally.yml` at the repository root, in a build worktree and as committed on a branch, `.git/gittally/.gittally.yml` for the machine layer. The current name wins where both exist, and the old file is then ignored rather than merged — two files side by side are a half-done rename, not a layering. Without the fallback an installation that updated without renaming would not fail: a configuration that is not found leaves every setting at its default, so it would come up looking healthy while having forgotten its credentials and its builds. `docs/werkator-migrationsplan.md` lists what the fallback does not cover and has to be moved by hand — above all the state directory `.git/werkator/`, which holds the build history, the control token and the worktrees, and has no fallback of its own. `docs/migration-from-legacy.md` is deleted with this: it mapped the legacy script's environment variables, and every host it addressed has long since moved to the YAML configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7f550689dd
commit
35f06ec1ec
Executable
+285
@@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Set up a fresh Werkator instance on a new Docker host by converting a legacy
|
||||
# .werkator env configuration to the new YAML format.
|
||||
#
|
||||
# The legacy config comes either from a file passed as the optional third
|
||||
# argument, or — when omitted — from the embedded snapshot of the hs.hsadmin.ng
|
||||
# .werkator on vm2176. Either way the script sources it, converts it to YAML
|
||||
# (mapping the legacy env vars to YAML keys), substitutes the public hostname
|
||||
# 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
|
||||
# given 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> [legacy-config]
|
||||
# <public-hostname> FQDN this instance is reachable at, e.g. vm2180.hostsharing.net
|
||||
# (used for server.publicBaseUrl)
|
||||
# <repo-dir> path to the cloned git repository to configure,
|
||||
# e.g. ~/hs.hsadmin.ng
|
||||
# [legacy-config] optional path to a legacy .werkator file to convert;
|
||||
# if omitted, the embedded vm2176 snapshot is used
|
||||
# --force overwrite .werkator.yml files that already exist
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------- settings --
|
||||
|
||||
JAR_PATH="$HOME/bin/werkator.jar" # where the Werkator jar is expected
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
warn() { echo "WARNING: $*" >&2; }
|
||||
|
||||
usage() {
|
||||
echo "usage: setup-werkator-instance [--force] <public-hostname> <repo-dir> [legacy-config]" >&2
|
||||
echo "example: setup-werkator-instance vm2180.hostsharing.net ~/hs.hsadmin.ng" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- arguments --
|
||||
|
||||
force=false
|
||||
positional=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force) force=true ;;
|
||||
-*) die "unknown option: $arg" ;;
|
||||
*) positional+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
case ${#positional[@]} in
|
||||
2) legacy_file="" ;;
|
||||
3) legacy_file="${positional[2]}" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
hostname="${positional[0]}"
|
||||
REPO_DIR="${positional[1]}"
|
||||
[ -z "$legacy_file" ] || [ -f "$legacy_file" ] || die "legacy config not found: $legacy_file"
|
||||
|
||||
# ---------------------------------------------------- legacy config source --
|
||||
# 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() {
|
||||
local s=${1//\\/\\\\}
|
||||
s=${s//\"/\\\"}
|
||||
printf '"%s"' "$s"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------ preconditions --
|
||||
|
||||
[ -d "$REPO_DIR/.git" ] || die "$REPO_DIR is not a git clone — clone the repository there first"
|
||||
command -v docker >/dev/null 2>&1 || die "docker not found — this setup targets a Docker host"
|
||||
|
||||
have_jar=true
|
||||
[ -f "$JAR_PATH" ] || { have_jar=false; warn "jar not found at $JAR_PATH — config will be written, but copy the jar there before running init"; }
|
||||
|
||||
have_java=true
|
||||
if ! command -v java >/dev/null 2>&1; then
|
||||
have_java=false
|
||||
warn "no java in PATH — install a JRE (e.g. Temurin 21 into ~/opt) before running init/server"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------- 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)"
|
||||
read -r -p " Gitea git account [$suggested_account]: " git_account
|
||||
git_account=${git_account:-$suggested_account}
|
||||
read -r -s -p " Gitea API token: " git_token; echo
|
||||
[ -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 --
|
||||
|
||||
echo "== writing $machine_yml (secrets, mode 600)"
|
||||
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
|
||||
rm -f "$machine_yml"
|
||||
(
|
||||
umask 077
|
||||
{
|
||||
echo "# Machine-specific overrides and secrets. Keys here win over .werkator.yml."
|
||||
echo "git:"
|
||||
echo " account: $(yaml_quote "$git_account")"
|
||||
echo " token: $(yaml_quote "$git_token")"
|
||||
} >"$machine_yml"
|
||||
)
|
||||
|
||||
echo "== converted project config:"
|
||||
sed 's/^/ /' "$project_yml"
|
||||
|
||||
# ------------------------------------------------------------------- finish --
|
||||
|
||||
if $have_jar && $have_java; then
|
||||
echo "== running init (keeps the config files just written)"
|
||||
( cd "$REPO_DIR" && java -jar "$JAR_PATH" init )
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Done. Werkator config for $hostname is in place.
|
||||
|
||||
Verify and start on this host:
|
||||
|
||||
cd $REPO_DIR
|
||||
java -jar $JAR_PATH config:print --full # check the effective config (git.token masked)
|
||||
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
|
||||
|
||||
Note: Werkator binds to $hostname:${WERKATOR_ARTIFACT_SERVER_PORT:-18080} over plain HTTP.
|
||||
publicBaseUrl is https://$hostname/ — terminate TLS in front of it with the host's
|
||||
reverse proxy (managed nginx/TLS is ADR 0005 / docs/plan/13-nginx-tls.md, not yet built).
|
||||
EOF
|
||||
Reference in New Issue
Block a user