From 6ff5fe53c5fdc26faef276d990f76da5b8252688 Mon Sep 17 00:00:00 2001 From: Stan Runge Date: Sun, 18 Oct 2026 22:37:50 +0000 Subject: [PATCH] fix: deploy with only sudo tee and docker The deploy user has passwordless sudo for tee and docker only, so the script stopped at its first sudo install. release.env is rewritten with tee, and the backup container creates the backup directory and sets the file modes itself. HOST_PORT comes from the target, not release.env. Co-Authored-By: Claude Opus 5.5 --- scripts/deploy-remote.sh | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index 009c0a4..21db159 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -25,13 +25,15 @@ else environment_name=production fi -sudo install -d -m 0755 "$directory" -if [ ! -f "$directory/release.env" ]; then - printf 'IMAGE_TAG=%s\nHOST_PORT=%s\n' "$sha" "$host_port" | sudo tee "$directory/release.env" >/dev/null -fi -if ! grep -q '^HOST_PORT=' "$directory/release.env"; then - echo "HOST_PORT=$host_port" | sudo tee -a "$directory/release.env" >/dev/null +# The deploy user only has passwordless sudo for tee and docker. +if [ ! -d "$directory" ]; then + echo "$directory does not exist; create it once on the host" >&2 + exit 1 fi +previous_tag=$(sed -n 's/^IMAGE_TAG=//p' "$directory/release.env" 2>/dev/null | head -n 1 || true) +write_release() { + printf 'IMAGE_TAG=%s\nHOST_PORT=%s\n' "$1" "$host_port" | sudo tee "$directory/release.env" >/dev/null +} sudo tee "$compose" >/dev/null <&2 - sudo sed -i "s/^IMAGE_TAG=.*/IMAGE_TAG=$previous_tag/" "$directory/release.env" + write_release "$previous_tag" sudo docker compose --env-file "$directory/release.env" -f "$compose" up -d fi } trap rollback ERR -port=$(sed -n 's/^HOST_PORT=//p' "$directory/release.env" | head -n 1) +port=$host_port if [ "${FORCE_DEPLOY:-}" != 1 ] && curl --fail --silent --show-error "http://127.0.0.1:${port}/api/health" > /tmp/esports-health.json; then python3 - <<'PY' import json @@ -107,20 +108,20 @@ backup_dir=/var/backups/esports backup_stamp=$(date -u +%Y%m%dT%H%M%SZ) backup_name="${environment_name}-${backup_stamp}-${sha}.dump" sudo docker pull "$backup_image" -sudo install -d -m 0700 "$backup_dir" +# The container runs as root, so it creates the directory and sets the modes. sudo docker run --rm --network backend --env-file "$environment" \ - --mount "type=bind,source=$backup_dir,target=/backups" \ - --env "BACKUP_FILE=$backup_name" "$backup_image" \ - sh -eu -c 'pg_dump --format=custom --no-owner --no-acl --file="/backups/$BACKUP_FILE" "${DB_URL:-$DATABASE_URL}"' -sudo docker run --rm --mount "type=bind,source=$backup_dir,target=/backups" \ - "$backup_image" pg_restore --list "/backups/$backup_name" >/dev/null -sudo chmod 0600 "$backup_dir/$backup_name" + --mount "type=bind,source=$(dirname "$backup_dir"),target=/backups" \ + --env "BACKUP_FILE=$(basename "$backup_dir")/$backup_name" "$backup_image" \ + sh -eu -c 'mkdir -p -m 0700 "/backups/${BACKUP_FILE%/*}" + pg_dump --format=custom --no-owner --no-acl --file="/backups/$BACKUP_FILE" "${DB_URL:-$DATABASE_URL}" + chmod 0600 "/backups/$BACKUP_FILE" + pg_restore --list "/backups/$BACKUP_FILE" >/dev/null' echo "Verified $environment_name database backup at $backup_dir/$backup_name" sudo docker run --rm --network backend --env-file "$environment" "$image" \ bun scripts/migrate.ts -sudo sed -i "s/^IMAGE_TAG=.*/IMAGE_TAG=$sha/" "$directory/release.env" +write_release "$sha" switched=1 sudo docker compose --env-file "$directory/release.env" -f "$compose" up -d curl --retry 12 --retry-delay 2 --retry-all-errors --fail --silent --show-error \