#!/usr/bin/env bash
# release <owner/repo> <commit-or-tag>
#
# Builds and deploys one exact commit of a repo listed in sites.json, then
# tags it deploy/<target>/<UTC time> in Gogs and appends a line to the
# deploy log. Runs as the deploy user, from deployd or by hand for a
# rollback. Unknown repos are refused.
#
# The RELEASE_* variables exist only so tests can point the script at a
# sandbox; on the server they are never set.
set -euo pipefail

conf=${RELEASE_CONF:-/etc/deployd/sites.json}
git_base=${RELEASE_GIT_BASE:-git@git.monoexp.com:}
work_root=${RELEASE_WORK:-/var/lib/deploy/src}
apps=${RELEASE_APPS:-/opt/apps}
vhosts=${RELEASE_VHOSTS:-/srv/www/vhosts}
logfile=${RELEASE_LOG:-/var/log/deploys.log}
by=${DEPLOY_BY:-manual}

[[ $# -eq 2 ]] || { echo "usage: release <owner/repo> <commit-or-tag>" >&2; exit 2; }
repo=$1; ref=$2
[[ $repo =~ ^[A-Za-z0-9_-][A-Za-z0-9_.-]*/[A-Za-z0-9_-][A-Za-z0-9_.-]*$ ]] || { echo "bad repo name: $repo" >&2; exit 2; }
[[ $ref =~ ^[A-Za-z0-9_./-]+$ && $ref != -* ]] || { echo "bad ref: $ref" >&2; exit 2; }

site=$(jq -ce --arg r "$repo" '.[$r]' "$conf") || { echo "unknown repo $repo" >&2; exit 1; }
kind=$(jq -r .kind <<<"$site")

# One release at a time, including manual ones.
mkdir -p "$work_root"
exec 9>"$work_root/.release.lock"
flock 9

work=$work_root/${repo//\//_}
[[ -d $work ]] || git clone --quiet "$git_base$repo.git" "$work"
cd "$work"
git fetch --quiet --tags origin
git -c advice.detachedHead=false checkout --quiet --detach "$ref"
sha=$(git rev-parse HEAD)
[[ -z $(git status --porcelain) ]] || { echo "working tree not clean" >&2; exit 1; }

case $kind in
  go)
    name=$(jq -r .name <<<"$site")
    [[ $name =~ ^[a-z0-9][a-z0-9-]*$ ]] || { echo "bad app name: $name" >&2; exit 1; }
    CGO_ENABLED=0 go build -trimpath -o "$apps/$name/$name.new" .
    content=$(jq -r '.content // empty' <<<"$site")
    if [[ -n $content ]]; then
      # -rlt, not -a: let the folder's default ACLs set group and mode, so the
      # app (group apps) can always read what lands there.
      rsync -rlt --delete "$content/" "$apps/$name/content/"
    fi
    mv "$apps/$name/$name.new" "$apps/$name/$name"
    sudo -n systemctl restart "goapp@$name"
    sleep 2
    systemctl is-active --quiet "goapp@$name" || { echo "goapp@$name is not active" >&2; exit 1; }
    target=$name ;;
  static)
    host=$(jq -r .host <<<"$site")
    dir=$(jq -r '.dir // "public"' <<<"$site")
    rsync -rlt --delete "$dir/" "$vhosts/$host/"
    target=$host ;;
  *) echo "unknown kind $kind" >&2; exit 1 ;;
esac

tag="deploy/$target/$(date -u +%Y%m%dT%H%M%SZ)"
git tag -a "$tag" -m "Deployed $sha to $target ($by)"
git push --quiet origin "$tag"
echo "$(date -Is) $target $sha $tag $by" >> "$logfile"
echo "released $repo $sha as $tag"
