diff --git a/.github/scripts/server_ssh.sh b/.github/scripts/server_ssh.sh index 8c208dfa..8a3b76b6 100755 --- a/.github/scripts/server_ssh.sh +++ b/.github/scripts/server_ssh.sh @@ -1,8 +1,8 @@ #!/bin/bash # Run one command on the TF server as SSH_USER with SSH_KEY. The server pins -# each deploy key to one forced command: psdeploy runs ps-preview -# (up | down | list) and ryan's deploy key runs -# site-deploy (deploy ). +# each psdeploy key to one forced command: the preview key runs ps-preview +# (up | down | list) and the deploy key runs site-deploy +# (deploy ). set -euo pipefail : "${SSH_USER:?}" "${SSH_KEY:?}" "${SSH_HOST:?}" "${SSH_KNOWN_HOSTS:?}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a1437f3f..15219894 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -58,7 +58,7 @@ jobs: echo "Deploying ${sha::7} ($(git log -1 --format=%s "$sha")) to $SITE" - name: Deploy env: - SSH_USER: ryan + SSH_USER: psdeploy SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} SSH_HOST: ${{ secrets.PREVIEW_SSH_HOST }} SSH_KNOWN_HOSTS: ${{ secrets.PREVIEW_SSH_KNOWN_HOSTS }} diff --git a/scripts/site-deploy-ssh.sh b/scripts/site-deploy-ssh.sh new file mode 100755 index 00000000..e23bafc2 --- /dev/null +++ b/scripts/site-deploy-ssh.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Forced command for the Deploy workflow's key on psdeploy. Installed as root's +# /usr/local/sbin/site-deploy-ssh; sudoers lets psdeploy run site-deploy as tfmc: +# psdeploy ALL=(tfmc) NOPASSWD: /usr/local/sbin/site-deploy +set -euo pipefail +read -r -a args <<< "${SSH_ORIGINAL_COMMAND:-}" +if [[ ${#args[@]} -ne 3 || ${args[0]} != deploy || ! ${args[1]} =~ ^(www|dev)$ || ! ${args[2]} =~ ^[0-9a-f]{40}$ ]]; then + echo "usage: deploy " >&2 + exit 2 +fi +exec sudo -H -n -u tfmc /usr/local/sbin/site-deploy "${args[@]}" diff --git a/scripts/site-deploy.sh b/scripts/site-deploy.sh index 6eaf8517..085e6ae3 100755 --- a/scripts/site-deploy.sh +++ b/scripts/site-deploy.sh @@ -1,11 +1,11 @@ #!/bin/bash # Deploy a commit on main to www.tfminecraft.net or dev.tfminecraft.net. # -# Installed on the TF server as ryan's ~/bin/site-deploy (copy it there after -# changing it; never run it from a checkout it updates). The Deploy workflow's -# key may run only this script: -# restrict,command="/home/ryan/bin/site-deploy" ssh-ed25519 ... github-site-deploy -# By hand: site-deploy deploy +# Installed on the TF server as root's /usr/local/sbin/site-deploy and run as +# tfmc, which owns both checkouts. The Deploy workflow's key logs in as +# psdeploy, pinned to site-deploy-ssh, which runs this through sudo: +# restrict,command="/usr/local/sbin/site-deploy-ssh" ssh-ed25519 ... github-site-deploy +# By hand: sudo -u tfmc site-deploy deploy # # Exit codes: 0 deployed or already deployed, 2 bad request, 4 commit not on # main, 5 site did not come up and the previous code was put back, 1 other. @@ -16,24 +16,27 @@ usage() { exit 2 } -read -r -a args <<< "${SSH_ORIGINAL_COMMAND:-$*}" -[[ ${#args[@]} -eq 3 && ${args[0]} == deploy ]] || usage -site=${args[1]} -sha=${args[2]} +[[ $# -eq 3 && $1 == deploy ]] || usage +site=$2 +sha=$3 [[ $sha =~ ^[0-9a-f]{40}$ ]] || usage case "$site" in - www) target=main repo=$HOME/work/provincesystem data=$HOME/work/provincesystem-data front=3000 back=8000 ;; - dev) target=dev repo=$HOME/work/provincesystem-dev data=$HOME/work/provincesystem-dev-data front=13001 back=18001 ;; + www) target=main repo=/home/tfmc/ProvinceSystem data=/home/tfmc/provincesystem-data front=3000 back=8000 ;; + dev) target=dev repo=/home/tfmc/tfmc-staging data=/home/tfmc/provincesystem-dev-data front=13001 back=18001 ;; *) usage ;; esac +[[ $(id -un) == tfmc ]] || { echo "run as tfmc: sudo -u tfmc $0 deploy $site $sha" >&2; exit 2; } +umask 022 +export HOME=/home/tfmc +state=$HOME -exec 9> "$HOME/.site-deploy-$site.lock" +exec 9> "$state/.site-deploy-$site.lock" flock 9 log() { echo "$*" >&2; } -compose() { sudo -n -u tfmc /usr/local/sbin/ps-compose "$target" "$@"; } -record() { printf '%s %s %s -> %s %s\n' "$(date -u +%FT%TZ)" "$site" "${old:0:7}" "${sha:0:7}" "$*" >> "$HOME/site-deploy.log"; } +compose() { /usr/local/sbin/ps-compose "$target" "$@"; } +record() { printf '%s %s %s -> %s %s\n' "$(date -u +%FT%TZ)" "$site" "${old:0:7}" "${sha:0:7}" "$*" >> "$state/site-deploy.log"; } cd "$repo" git fetch -q origin @@ -58,8 +61,8 @@ services=() grep -qE '^(frontend|shared)/|^docker-compose' <<< "$changed" && services+=(frontend) grep -qE '^(backend|shared|frontend/lib/skins)/|^docker-compose' <<< "$changed" && services+=(backend) -# A consistent copy needs SQLite's backup API inside the container; the ~/work -# mounts don't pass file locks through. +# SQLite's backup API inside the container keeps the copy consistent while the +# site writes. log "backing up province.db" compose exec -T backend python3 - <<'PYTHON' import sqlite3 @@ -81,16 +84,6 @@ checkout() { # Keeps dev's local edits; refuses if they would be overwritten. git checkout -q --detach "$1" || return 1 fi - # Git writes new files here as 660, which the containers cannot read. - git diff --name-only -z "$old" "$sha" | while IFS= read -r -d '' file; do - [[ -e $file ]] || continue - chmod a+r "$file" - dir=$(dirname "$file") - while [[ $dir != . ]]; do - chmod a+rx "$dir" - dir=$(dirname "$dir") - done - done } healthy() {