#!/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/ (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