#!/usr/bin/env bash # # One-time setup for the gitea_ci deploy-user on every host that # .gitea/workflows/{deploy,refresh}.yml targets: # - create the gitea_ci system user (if missing) # - install the runner's pubkey into ~gitea_ci/.ssh/authorized_keys # - add gitea_ci to the systemd-journal group (so the workflow can capture # `journalctl -u moments-*.service` without a sudoers entry) # - install the host-appropriate /etc/sudoers.d/moments_*_gitea_ci drop-in, # verified with `visudo -cf` so a typo can't lock the host out # - check the postgres mTLS host cert the api/worker need already exists # # Run this from a workstation with ssh + sudo access to the hosts, once per # host, before the deploy workflow can succeed. Idempotent — safe to re-run. # Application config is NOT shipped here: the workflow renders /etc/moments/*.env # from Gitea secrets on every deploy. # # Never suppresses errors; hosts that fail to provision are reported and skipped # so one offline host doesn't block the rest. set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_path="$(cd "${script_dir}/.." && pwd)" # host -> sudoers drop-in (the source of infra truth, mirroring the workflow env) api_host="nikola.kosherinata.internal" worker_host="frootmig.kosherinata.internal" web_host="oolon.kosherinata.internal" api_sudoers="${repo_path}/asset/sudoers.d/api-host.conf" worker_sudoers="${repo_path}/asset/sudoers.d/worker-host.conf" web_sudoers="${repo_path}/asset/sudoers.d/web-host.conf" pubkey="${HOME}/.ssh/id_gitea_ci.pub" if [[ ! -f "${pubkey}" ]]; then echo "fatal: ${pubkey} not found" >&2 echo " generate with: ssh-keygen -t ed25519 -f ${pubkey%.pub} -C gitea_ci" >&2 echo " then add the matching private key as the RSYNC_SSH_KEY Gitea secret" >&2 exit 1 fi # Create gitea_ci, install its authorized_keys, and grant journal read access. provision_user() { local host="$1" echo "==> ${host}: provisioning gitea_ci" if ! ssh "${host}" ' set -eu if id -u gitea_ci >/dev/null 2>&1; then echo " gitea_ci user already present" else sudo useradd --system --create-home \ --home-dir /var/lib/gitea_ci --shell /bin/bash gitea_ci echo " gitea_ci user created" fi # `install -o` does its own fresh user lookup, avoiding the brief NSS # cache lag that makes `sudo -u gitea_ci` fail right after useradd. sudo install -d -o gitea_ci -g gitea_ci -m 0700 /var/lib/gitea_ci/.ssh sudo usermod -aG systemd-journal gitea_ci '; then echo " failed to provision gitea_ci — skipping ${host}" return 1 fi if rsync --archive --compress \ --chown gitea_ci:gitea_ci --chmod 0600 \ --rsync-path 'sudo rsync' \ "${pubkey}" \ "${host}:/var/lib/gitea_ci/.ssh/authorized_keys"; then echo " authorized_keys synced" else echo " failed to sync authorized_keys to ${host}" return 1 fi } # Install the sudoers drop-in and verify it parses, so a typo can't lock out. install_sudoers() { local host="$1" template="$2" name="$3" local dest="/etc/sudoers.d/${name}" echo "==> ${host}: installing ${dest}" if ! rsync --archive --compress \ --chown root:root --chmod 0440 \ --rsync-path 'sudo rsync' \ "${template}" \ "${host}:${dest}"; then echo " failed to sync ${template##*/}" return 1 fi if ssh "${host}" "sudo visudo -cf ${dest}" >/dev/null; then echo " installed and verified" else echo " WARNING: visudo rejected the installed file — review on ${host}" return 1 fi } # The api/worker connect to postgres with mTLS using the host's own cert. The # workflow only ACLs it to the moments user; it must already exist (provisioned # by the host's PKI/step convention — see architecture/internal-tls.md). check_pg_cert() { local host="$1" echo "==> ${host}: checking postgres mTLS host cert" if ssh "${host}" ' fqdn="$(hostname -f)" test -f "/etc/pki/tls/private/${fqdn}.pem" && test -f "/etc/pki/tls/misc/${fqdn}.pem" '; then echo " host cert present" else echo " WARNING: /etc/pki/tls/{private,misc}/.pem missing on ${host}" echo " the moments-{api,worker} service will fail to reach postgres until it exists" fi } setup_host() { local host="$1" sudoers="$2" name="$3" provision_user "${host}" && install_sudoers "${host}" "${sudoers}" "${name}" \ || { echo " ${host}: setup incomplete"; return 1; } } setup_host "${api_host}" "${api_sudoers}" moments_api_gitea_ci check_pg_cert "${api_host}" setup_host "${worker_host}" "${worker_sudoers}" moments_worker_gitea_ci check_pg_cert "${worker_host}" setup_host "${web_host}" "${web_sudoers}" moments_web_gitea_ci echo "==> done." echo " Gitea repo secrets to set (Settings -> Actions -> Secrets):" echo " RSYNC_SSH_KEY private key matching ${pubkey}" echo " QUERY_GITHUB_TOKEN github api token for the worker poller" echo " QUERY_GITEA_TOKEN git.lair.cafe api token for the worker poller" echo " (GITHUB_TOKEN / GITEA_TOKEN are reserved Actions names — hence QUERY_.)"