Skip to content

Merge pull request #297 from kw-coms/codex/p0-website-security-hardening #263

Merge pull request #297 from kw-coms/codex/p0-website-security-hardening

Merge pull request #297 from kw-coms/codex/p0-website-security-hardening #263

Workflow file for this run

name: Deploy COMS Website
on:
push:
branches:
- main
workflow_dispatch:
inputs:
deploy_ref:
description: "Branch, tag, or commit SHA to deploy"
required: false
default: main
concurrency:
group: deploy-${{ github.workflow }}
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to COMS server
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || '22' }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH || '~/apps/coms-website' }}
DEPLOY_REPO: https://github.com/kw-coms/coms-website.git
DEPLOY_REF: ${{ github.event.inputs.deploy_ref || github.ref_name }}
PUBLIC_BASE_URL: ${{ secrets.PUBLIC_BASE_URL || 'https://coms.kw.ac.kr' }}
run: |
set -euo pipefail
if [ -z "$DEPLOY_HOST" ] || [ -z "$DEPLOY_USER" ] || [ -z "$DEPLOY_SSH_KEY" ]; then
echo "Missing deploy secrets. Set DEPLOY_HOST, DEPLOY_USER, and DEPLOY_SSH_KEY." >&2
exit 1
fi
mkdir -p ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p "$DEPLOY_PORT" "$DEPLOY_HOST" >> ~/.ssh/known_hosts
ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
"DEPLOY_PATH=${DEPLOY_PATH@Q} DEPLOY_REPO=${DEPLOY_REPO@Q} DEPLOY_REF=${DEPLOY_REF@Q} PUBLIC_BASE_URL=${PUBLIC_BASE_URL@Q} bash -c 'tmp=\$(mktemp /tmp/coms-deploy.XXXXXX); cat > \"\$tmp\"; bash \"\$tmp\"; status=\$?; rm -f \"\$tmp\"; exit \"\$status\"'" << 'REMOTE'
set -euo pipefail
DEPLOY_PATH="${DEPLOY_PATH/#\~/$HOME}"
PUBLIC_BASE_URL="${PUBLIC_BASE_URL%/}"
if ! command -v git >/dev/null 2>&1; then
echo "git is required on the server." >&2
exit 1
fi
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
else
echo "Docker Compose is required on the server." >&2
exit 1
fi
mkdir -p "$(dirname "$DEPLOY_PATH")"
if [ ! -d "$DEPLOY_PATH/.git" ]; then
git clone "$DEPLOY_REPO" "$DEPLOY_PATH"
fi
cd "$DEPLOY_PATH"
git remote set-url origin "$DEPLOY_REPO"
git fetch --prune origin "+refs/heads/*:refs/remotes/origin/*" "+refs/tags/*:refs/tags/*"
if git rev-parse --verify --quiet "origin/$DEPLOY_REF^{commit}" >/dev/null; then
git reset --hard "origin/$DEPLOY_REF"
elif git rev-parse --verify --quiet "$DEPLOY_REF^{commit}" >/dev/null; then
git reset --hard "$DEPLOY_REF"
else
git fetch origin "$DEPLOY_REF"
git reset --hard FETCH_HEAD
fi
# Keep only persistent secrets/data; remove every other untracked or ignored build artifact.
git clean -fdx -e .env -e backend-data/ -e backend/uploads/ -e uploads/
if [ ! -f .env ]; then
echo "Missing $DEPLOY_PATH/.env on the server. Create it from .env.example before deploying." >&2
exit 1
fi
if $COMPOSE --env-file .env -f docker-compose.yml exec -T db true >/dev/null 2>&1; then
V38_SCRIPT="$($COMPOSE --env-file .env -f docker-compose.yml exec -T db sh -c \
'if [ "$(psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT to_regclass('\''public.flyway_schema_history'\'')")" = "flyway_schema_history" ]; then
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT script FROM flyway_schema_history WHERE version = '\''38'\'' AND success = true ORDER BY installed_rank DESC LIMIT 1"
fi')"
if [ -n "$V38_SCRIPT" ] && [ "$V38_SCRIPT" != "V38__mobile_push_tokens.sql" ]; then
echo "Refusing deploy: Flyway V38 is recorded as $V38_SCRIPT, expected V38__mobile_push_tokens.sql." >&2
echo "Back up the database and repair its Flyway history before deploying this release." >&2
exit 1
fi
fi
(cd backend && ./gradlew clean test --no-daemon)
$COMPOSE --env-file .env -f docker-compose.yml up -d --build --remove-orphans
docker image prune -f
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if curl -fsS http://127.0.0.1:8080/actuator/health; then
break
fi
if [ "$attempt" -eq 10 ]; then
$COMPOSE --env-file .env -f docker-compose.yml ps
docker logs --tail=200 coms-backend
exit 1
fi
sleep 3
done
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if curl -fI "$PUBLIC_BASE_URL/" && curl -f "$PUBLIC_BASE_URL/api/server/time"; then
break
fi
if [ "$attempt" -eq 10 ]; then
$COMPOSE --env-file .env -f docker-compose.yml ps
docker logs --tail=200 coms-backend
docker logs --tail=100 coms-frontend
exit 1
fi
sleep 3
done
REMOTE