#!/usr/bin/env bash
#
# Activates an uploaded release on the server. Run by the GitHub Actions
# deploy workflow (.github/workflows/deploy.yml) after it unpacked the build
# into releases/<tag>:
#
#   bash /var/www/marcus-blog/releases/v1.2.0/scripts/deploy/deploy.sh v1.2.0
#
# Layout (see scripts/deploy/README.md):
#   $DEPLOY_PATH/releases/<tag>   one full build per tag (code, vendor, CSS)
#   $DEPLOY_PATH/current          symlink to the live release (web root: current/web)
#   $DEPLOY_PATH/shared/.env      environment (DB, hash salt, domain, ...)
#   $DEPLOY_PATH/shared/files     sites/default/files
#   $DEPLOY_PATH/shared/settings.production.php
#   $DEPLOY_PATH/backups/<time>-<tag>/  db.sql.gz, files.tar.gz, code.tar.gz
#
# Steps: back up database, files and the live code; link the shared files and
# settings into the release; maintenance mode on; switch `current`; drush
# deploy (updatedb, config:import, cache:rebuild, deploy hooks); maintenance
# off; check the home page. If drush deploy or the check fails, the previous
# release and the database dump are restored. Old releases and backups are
# pruned (KEEP_RELEASES / KEEP_BACKUPS, default 5 / 10).

set -euo pipefail

TAG="${1:?usage: deploy.sh <tag>}"
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/marcus-blog}"
RELEASE="$DEPLOY_PATH/releases/$TAG"
SHARED="$DEPLOY_PATH/shared"
CURRENT="$DEPLOY_PATH/current"
STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP="$DEPLOY_PATH/backups/$STAMP-$TAG"

log() { printf '\n==> %s\n' "$*"; }
fail() { printf '\n!!! %s\n' "$*" >&2; exit 1; }

[ -d "$RELEASE" ] || fail "Release $RELEASE does not exist."
[ -f "$SHARED/.env" ] || fail "$SHARED/.env is missing (see scripts/deploy/README.md)."

# The same environment PHP-FPM gets (DB credentials, hash salt, ...).
set -a
# shellcheck disable=SC1091
. "$SHARED/.env"
set +a
# Drush builds absolute URLs (llms.txt, the sitemap) from the public URL.
[ -n "${SITE_URL:-}" ] && export DRUSH_OPTIONS_URI="${DRUSH_OPTIONS_URI:-$SITE_URL}"
KEEP_RELEASES="${KEEP_RELEASES:-5}"
KEEP_BACKUPS="${KEEP_BACKUPS:-10}"
PHP_FPM_SERVICE="${PHP_FPM_SERVICE:-php8.4-fpm}"

drush_in() { local dir="$1"; shift; (cd "$dir" && vendor/bin/drush "$@"); }

reload_php() {
  # Clears OPcache so PHP sees the new code behind the switched symlink.
  # Needs a sudoers rule for exactly this command (see README).
  sudo -n systemctl reload "$PHP_FPM_SERVICE" 2>/dev/null \
    || echo "    (could not reload $PHP_FPM_SERVICE; add the sudoers rule from the README)"
}

PREVIOUS=""
if [ -L "$CURRENT" ]; then
  PREVIOUS="$(readlink -f "$CURRENT")"
fi
[ "$PREVIOUS" != "$(readlink -f "$RELEASE")" ] || fail "$TAG is already live."

log "Linking shared files and settings into $TAG"
mkdir -p "$SHARED/files"
# Drupal keeps sites/default read-only; open it just for the links.
chmod u+w "$RELEASE/web/sites/default"
rm -rf "$RELEASE/web/sites/default/files"
ln -sfn "$SHARED/files" "$RELEASE/web/sites/default/files"
if [ -f "$SHARED/settings.production.php" ]; then
  ln -sfn "$SHARED/settings.production.php" "$RELEASE/web/sites/default/settings.production.php"
else
  cp "$RELEASE/web/sites/default/settings.production.php.example" "$SHARED/settings.production.php"
  ln -sfn "$SHARED/settings.production.php" "$RELEASE/web/sites/default/settings.production.php"
  echo "    created shared/settings.production.php from the example"
fi
chmod a-w "$RELEASE/web/sites/default"

log "Backing up to $BACKUP"
mkdir -p "$BACKUP"
# Dump with the live code if there is one, else with the new release (first
# deploy into an imported database).
drush_in "${PREVIOUS:-$RELEASE}" sql:dump --gzip --extra-dump=--no-tablespaces --result-file="$BACKUP/db.sql" >/dev/null
tar -C "$SHARED" -czf "$BACKUP/files.tar.gz" \
  --exclude='files/css' --exclude='files/js' --exclude='files/php' --exclude='files/styles' files
if [ -n "$PREVIOUS" ]; then
  tar -C "$PREVIOUS" -czf "$BACKUP/code.tar.gz" --exclude='./web/sites/default/files' .
fi
printf 'tag=%s\nprevious=%s\n' "$TAG" "$PREVIOUS" > "$BACKUP/INFO"
ls -lh "$BACKUP" | tail -n +2

rollback() {
  printf '\n!!! %s - rolling back\n' "$1" >&2
  if [ -n "$PREVIOUS" ]; then
    ln -sfn "$PREVIOUS" "$DEPLOY_PATH/current.tmp" && mv -Tf "$DEPLOY_PATH/current.tmp" "$CURRENT"
    reload_php
    drush_in "$PREVIOUS" sql:drop -y >/dev/null
    gunzip -c "$BACKUP/db.sql.gz" | drush_in "$PREVIOUS" sql:cli
    drush_in "$PREVIOUS" cache:rebuild >/dev/null
    drush_in "$PREVIOUS" state:set system.maintenance_mode 0 --input-format=integer >/dev/null
    echo "    restored $(basename "$PREVIOUS") and the database from $BACKUP" >&2
  fi
  exit 1
}

if [ -n "$PREVIOUS" ]; then
  log "Maintenance mode on"
  drush_in "$PREVIOUS" state:set system.maintenance_mode 1 --input-format=integer
fi

log "Switching current -> $TAG"
ln -sfn "$RELEASE" "$DEPLOY_PATH/current.tmp"
mv -Tf "$DEPLOY_PATH/current.tmp" "$CURRENT"
reload_php

log "drush deploy (updatedb, config:import, cache:rebuild, deploy hooks)"
drush_in "$CURRENT" deploy -y || rollback "drush deploy failed"

drush_in "$CURRENT" state:set system.maintenance_mode 0 --input-format=integer
drush_in "$CURRENT" cache:rebuild >/dev/null

# config:import brings back the llms.txt text exported locally (local URLs):
# rebuild it, and the sitemap, with the public URL.
log "Rebuilding llms.txt and the sitemap"
drush_in "$CURRENT" php:eval '\Drupal::service(\Drupal\marcus_blog\LlmsTxtBuilder::class)->rebuild();' >/dev/null \
  || echo "    (could not rebuild llms.txt)"
drush_in "$CURRENT" simple-sitemap:generate >/dev/null || echo "    (could not generate the sitemap)"

if [ -n "${SITE_URL:-}" ]; then
  log "Checking $SITE_URL"
  code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 "$SITE_URL/" || true)"
  [ "$code" = "200" ] || rollback "home page answered $code"
  echo "    200 OK"
fi

log "Pruning (keeping $KEEP_RELEASES releases, $KEEP_BACKUPS backups)"
live="$(readlink -f "$CURRENT")"
ls -1dt "$DEPLOY_PATH"/releases/*/ 2>/dev/null | tail -n +$((KEEP_RELEASES + 1)) | while read -r dir; do
  [ "$(readlink -f "$dir")" = "$live" ] || { chmod -R u+w "$dir" 2>/dev/null || true; rm -rf "$dir"; }
done
ls -1dt "$DEPLOY_PATH"/backups/*/ 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | xargs -r rm -rf

log "Deployed $TAG"
