Generic host/repo/branch-aware CD on adnanh/webhook
Replaces the per-project deploy scripts and self-contained webhook receivers with one manifest-driven implementation that lives outside the application repositories and can be updated independently of them. First targets: domaindingo test and prod on s5.
This commit is contained in:
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Install the CD webhook receiver on this host.
|
||||
#
|
||||
# Idempotent: re-run it after editing targets.json, after rotating a secret, or
|
||||
# after pulling a new version of this repository. It re-renders the hooks file
|
||||
# and restarts the daemon; it never touches a project repository or uas-ng.
|
||||
#
|
||||
# Prerequisites:
|
||||
# * adnanh/webhook on PATH (or WEBHOOK_BIN pointing at it)
|
||||
# * a host-local secrets file, see secrets.env.example
|
||||
# * this host present in targets.json under "hosts"
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
readonly CONFIG_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/cd-webhook"
|
||||
readonly HOOKS_FILE="${CONFIG_DIR}/hooks.json"
|
||||
readonly SECRETS_FILE="${CONFIG_DIR}/secrets.env"
|
||||
readonly SYSTEMD_DIR="${HOME}/.config/systemd/user"
|
||||
readonly UNIT_NAME="cd-webhook.service"
|
||||
|
||||
info() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
|
||||
ok() { printf ' ok %s\n' "$*"; }
|
||||
die() { printf '\n\033[31mERROR\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# ------------------------------------------------------------ prerequisites --
|
||||
|
||||
info "Checking prerequisites"
|
||||
|
||||
for tool in python3 docker curl flock stdbuf; do
|
||||
command -v "$tool" >/dev/null 2>&1 || die "required tool not found: ${tool}"
|
||||
done
|
||||
ok "python3, docker, curl, flock, stdbuf"
|
||||
|
||||
WEBHOOK_BIN="${WEBHOOK_BIN:-$(command -v webhook || true)}"
|
||||
[[ -n "$WEBHOOK_BIN" && -x "$WEBHOOK_BIN" ]] || die \
|
||||
"adnanh/webhook not found. Install it, or set WEBHOOK_BIN=/path/to/webhook.
|
||||
Debian/Ubuntu: sudo apt install webhook
|
||||
Or a release binary from https://github.com/adnanh/webhook/releases"
|
||||
ok "webhook binary: ${WEBHOOK_BIN}"
|
||||
|
||||
docker info >/dev/null 2>&1 || die \
|
||||
"cannot talk to Docker as $(whoami). The deploy runs as this user, so this
|
||||
account needs working access to the Docker socket it deploys through."
|
||||
ok "docker reachable as $(whoami)"
|
||||
|
||||
# ------------------------------------------------------------- host config ---
|
||||
|
||||
info "Resolving this host in targets.json"
|
||||
|
||||
eval "$("${REPO_ROOT}/bin/cd-target" host-config)"
|
||||
ok "host ${CD_HOST}, binding ${CD_BIND}:${CD_PORT}"
|
||||
|
||||
mapfile -t TARGETS < <("${REPO_ROOT}/bin/cd-target" list)
|
||||
[[ ${#TARGETS[@]} -gt 0 ]] || die "no enabled targets for ${CD_HOST} in targets.json"
|
||||
for target in "${TARGETS[@]}"; do
|
||||
ok "target ${target}"
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------- secrets ---
|
||||
|
||||
info "Checking the secrets file"
|
||||
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
chmod 700 "$CONFIG_DIR"
|
||||
|
||||
if [[ ! -f "$SECRETS_FILE" ]]; then
|
||||
install -m 0600 "${REPO_ROOT}/secrets.env.example" "$SECRETS_FILE"
|
||||
die "created a template at ${SECRETS_FILE}
|
||||
Fill in one secret per repository, then re-run this installer.
|
||||
The same value must be set as the Secret on the Gitea webhook."
|
||||
fi
|
||||
chmod 600 "$SECRETS_FILE"
|
||||
ok "${SECRETS_FILE}"
|
||||
|
||||
# ------------------------------------------------------------------- hooks ---
|
||||
|
||||
info "Rendering hooks for ${CD_HOST}"
|
||||
"${REPO_ROOT}/bin/cd-render-hooks" --output "$HOOKS_FILE" --secrets "$SECRETS_FILE"
|
||||
|
||||
# ------------------------------------------------------------------ systemd --
|
||||
|
||||
info "Installing the user service"
|
||||
|
||||
mkdir -p "$SYSTEMD_DIR"
|
||||
sed -e "s|@WEBHOOK_BIN@|${WEBHOOK_BIN}|g" \
|
||||
-e "s|@HOOKS_FILE@|${HOOKS_FILE}|g" \
|
||||
-e "s|@BIND@|${CD_BIND}|g" \
|
||||
-e "s|@PORT@|${CD_PORT}|g" \
|
||||
"${REPO_ROOT}/etc/cd-webhook.service" > "${SYSTEMD_DIR}/${UNIT_NAME}"
|
||||
ok "${SYSTEMD_DIR}/${UNIT_NAME}"
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable "$UNIT_NAME" >/dev/null
|
||||
systemctl --user restart "$UNIT_NAME"
|
||||
ok "enabled and restarted"
|
||||
|
||||
# Survive logout, same as the other user services in this fleet.
|
||||
loginctl enable-linger "$(whoami)" >/dev/null 2>&1 || true
|
||||
|
||||
sleep 1
|
||||
systemctl --user is-active --quiet "$UNIT_NAME" \
|
||||
|| die "the service did not stay running:
|
||||
systemctl --user status ${UNIT_NAME}
|
||||
journalctl --user -u ${UNIT_NAME} -n 50 --no-pager"
|
||||
|
||||
# ------------------------------------------------------------------ summary --
|
||||
|
||||
info "Done. Configure these webhooks in Gitea"
|
||||
|
||||
echo
|
||||
printf ' %-28s %s\n' "Target URL" "http://${CD_BIND}:${CD_PORT}/hooks/<id> (below)"
|
||||
printf ' %-28s %s\n' "HTTP Method" "POST"
|
||||
printf ' %-28s %s\n' "Content Type" "application/json"
|
||||
printf ' %-28s %s\n' "Trigger On" "Push Events"
|
||||
printf ' %-28s %s\n' "Secret" "the matching value from ${SECRETS_FILE}"
|
||||
echo
|
||||
|
||||
"${REPO_ROOT}/bin/cd-render-hooks" --redact >/dev/null 2>&1 || true
|
||||
python3 - "$HOOKS_FILE" "$CD_BIND" "$CD_PORT" <<'PY'
|
||||
import json, sys
|
||||
hooks_file, bind, port = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
with open(hooks_file) as fh:
|
||||
hooks = json.load(fh)
|
||||
for hook in hooks:
|
||||
print(f" http://{bind}:{port}/hooks/{hook['id']}")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo " Logs: journalctl --user -u ${UNIT_NAME} -f"
|
||||
echo " Status: ${REPO_ROOT}/bin/cd-status"
|
||||
echo
|
||||
Reference in New Issue
Block a user