#!/usr/bin/env bash set -Eeuo pipefail if [[ ${EUID} -ne 0 ]]; then echo "Run this installer with sudo." >&2 exit 1 fi readonly SOURCE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly PUBLIC_KEY_FILE="${1:-}" readonly NGINX_LINK="/etc/nginx/sites-enabled/eusrr" readonly UPSTREAM_FILE="/etc/nginx/conf.d/eusrr-active-upstreams.conf" if [[ -z ${PUBLIC_KEY_FILE} || ! -f ${PUBLIC_KEY_FILE} ]]; then echo "Usage: $0 /path/to/gitea-runner.pub" >&2 exit 2 fi if ! id deploy >/dev/null 2>&1; then echo "The deploy system account does not exist." >&2 exit 3 fi if [[ ! -d ${SOURCE_DIR}/systemd ]]; then echo "Systemd templates are missing from ${SOURCE_DIR}." >&2 exit 4 fi exec 9>/run/lock/eusrr-deploy.lock flock -n 9 || { echo "A deployment is running; retry bootstrap after it finishes." >&2; exit 3; } read -r key_type key_body _ <"${PUBLIC_KEY_FILE}" if [[ ${key_type} != "ssh-ed25519" || -z ${key_body:-} ]]; then echo "Expected an Ed25519 public key." >&2 exit 5 fi nginx_config="$(readlink -f "${NGINX_LINK}")" nginx_backup="${nginx_config}.bak.blue-green.$(date -u +%Y%m%dT%H%M%SZ)" cp -a "${nginx_config}" "${nginx_backup}" upstream_existed=0 upstream_backup="" if [[ -f ${UPSTREAM_FILE} ]]; then upstream_existed=1 upstream_backup="${UPSTREAM_FILE}.bak.$(date -u +%Y%m%dT%H%M%SZ)" cp -a "${UPSTREAM_FILE}" "${upstream_backup}" fi install_complete=0 temporary_files=() cleanup() { local status=$? for file in "${temporary_files[@]:-}"; do [[ -n ${file} ]] && rm -f "${file}" done if [[ ${install_complete} -eq 0 ]]; then cp -a "${nginx_backup}" "${nginx_config}" if [[ ${upstream_existed} -eq 1 ]]; then cp -a "${upstream_backup}" "${UPSTREAM_FILE}" else rm -f "${UPSTREAM_FILE}" fi nginx -t >/dev/null 2>&1 && systemctl reload nginx || true fi exit "${status}" } trap cleanup EXIT install -o root -g root -m 0755 "${SOURCE_DIR}/eusrr-deploy" /usr/local/sbin/eusrr-deploy install -o root -g root -m 0755 "${SOURCE_DIR}/eusrr-deploy-ssh" /usr/local/sbin/eusrr-deploy-ssh install -d -o root -g root -m 0755 /usr/local/libexec /etc/systemd/journald.conf.d install -o root -g root -m 0755 "${SOURCE_DIR}/eusrr-deploy-healthcheck" /usr/local/libexec/eusrr-deploy-healthcheck install -o root -g root -m 0644 "${SOURCE_DIR}/logrotate.conf" /etc/logrotate.d/eusrr-deploy install -o root -g root -m 0644 "${SOURCE_DIR}/journald.conf" /etc/systemd/journald.conf.d/60-eusrr-retention.conf for unit in "${SOURCE_DIR}"/systemd/*.service; do install -o root -g root -m 0644 "${unit}" "/etc/systemd/system/$(basename "${unit}")" done install -d -o deploy -g eusrr -m 2775 /srv/eusrr-releases install -d -o root -g root -m 0755 /srv/eusrr-slots chmod 00755 /srv/eusrr-slots install -d -o deploy -g www-data -m 0755 /srv/eusrr-shared /srv/eusrr-shared/next-static if [[ -d /srv/eusrr/frontend/.next/static ]]; then rsync -a /srv/eusrr/frontend/.next/static/ /srv/eusrr-shared/next-static/ fi chown -R deploy:www-data /srv/eusrr-shared find /srv/eusrr-shared -type d -exec chmod 0755 {} + find /srv/eusrr-shared -type f -exec chmod 0644 {} + # Re-running bootstrap on an existing native slot must not point static files # back at the legacy checkout while Nginx still serves the native slot. if [[ ! -L /srv/eusrr-current ]]; then ln -sfn /srv/eusrr /srv/eusrr-current.new mv -Tf /srv/eusrr-current.new /srv/eusrr-current fi if [[ ! -f ${UPSTREAM_FILE} ]]; then cat >"${UPSTREAM_FILE}" <<'EOF' upstream eusrr_backend { server 127.0.0.1:9000; keepalive 32; } upstream eusrr_frontend { server 127.0.0.1:3000; keepalive 32; } EOF chmod 0644 "${UPSTREAM_FILE}" fi NGINX_CONFIG="${nginx_config}" python3 <<'PY' import os import re from pathlib import Path path = Path(os.environ["NGINX_CONFIG"]) text = path.read_text(encoding="utf-8") next_static = re.compile( r"^ location /_next/static/ \{\n.*?^ \}", flags=re.MULTILINE | re.DOTALL, ) replacement = """ location /_next/static/ { alias /srv/eusrr-shared/next-static/; autoindex off; expires 365d; add_header Cache-Control \"public, immutable\"; }""" text, static_blocks = next_static.subn(replacement, text) if static_blocks not in (0, 2): raise SystemExit(f"Expected zero or two Next.js static blocks, found {static_blocks}") text = text.replace("proxy_pass http://127.0.0.1:9000;", "proxy_pass http://eusrr_backend;") text = text.replace("proxy_pass http://127.0.0.1:3000;", "proxy_pass http://eusrr_frontend;") text = text.replace( "alias /srv/eusrr/backend/staticfiles/;", "alias /srv/eusrr-current/backend/staticfiles/;", ) required = ( "proxy_pass http://eusrr_backend;", "proxy_pass http://eusrr_frontend;", "alias /srv/eusrr-shared/next-static/;", "alias /srv/eusrr-current/backend/staticfiles/;", ) for marker in required: if marker not in text: raise SystemExit(f"Nginx transformation marker is missing: {marker}") if "proxy_pass http://127.0.0.1:9000;" in text or "proxy_pass http://127.0.0.1:3000;" in text: raise SystemExit("Legacy direct upstreams remain in Nginx configuration") path.write_text(text, encoding="utf-8") PY install -d -o root -g root -m 0755 /etc/eusrr/slots /var/lib/eusrr-deploy [[ $(ssh-keygen -lf "${SOURCE_DIR}/gitea_known_hosts" | awk '{print $2}') == \ 'SHA256:341ON+FsVZMVMsduFUZjevfxjagbFG9yPeZ0edTVSOk' ]] || { echo 'Pinned Gitea host key fingerprint does not match.' >&2; exit 1; } install -o root -g root -m 0644 "${SOURCE_DIR}/gitea_known_hosts" /etc/eusrr/gitea_known_hosts cat >/etc/eusrr/slots/blue.env <<'EOF' BACKEND_PORT=9001 PORT=3001 HOSTNAME=127.0.0.1 EOF cat >/etc/eusrr/slots/green.env <<'EOF' BACKEND_PORT=9002 PORT=3002 HOSTNAME=127.0.0.1 EOF chmod 0644 /etc/eusrr/slots/blue.env /etc/eusrr/slots/green.env if [[ ! -f /var/lib/eusrr-deploy/active-slot ]]; then printf '%s\n' legacy >/var/lib/eusrr-deploy/active-slot fi install -d -o deploy -g deploy -m 0755 /var/log/celery install -d -o deploy -g deploy -m 0700 /var/backups/eusrr/deploy touch /var/log/eusrr-deploy.log chown root:root /var/log/eusrr-deploy.log chmod 0600 /var/log/eusrr-deploy.log sudoers_tmp="$(mktemp)" temporary_files+=("${sudoers_tmp}") printf '%s\n' 'deploy ALL=(root) NOPASSWD: /usr/local/sbin/eusrr-deploy ^[0-9a-f]{40}$' >"${sudoers_tmp}" chmod 0440 "${sudoers_tmp}" visudo -cf "${sudoers_tmp}" >/dev/null install -o root -g root -m 0440 "${sudoers_tmp}" /etc/sudoers.d/eusrr-deploy deploy_home="$(getent passwd deploy | cut -d: -f6)" install -d -o deploy -g deploy -m 0700 "${deploy_home}/.ssh" authorized_keys="${deploy_home}/.ssh/authorized_keys" touch "${authorized_keys}" chown deploy:deploy "${authorized_keys}" chmod 0600 "${authorized_keys}" filtered_keys="$(mktemp)" temporary_files+=("${filtered_keys}") grep -v ' eusrr-gitea-runner$' "${authorized_keys}" >"${filtered_keys}" || true printf '%s %s %s %s\n' \ 'command="/usr/local/sbin/eusrr-deploy-ssh",restrict' \ "${key_type}" "${key_body}" 'eusrr-gitea-runner' >>"${filtered_keys}" install -o deploy -g deploy -m 0600 "${filtered_keys}" "${authorized_keys}" systemctl daemon-reload nginx -t systemctl reload nginx /usr/local/sbin/eusrr-deploy --self-test install_complete=1 echo "Restricted blue-green Gitea deployment installed successfully."