Skip to content

Allow multi-member session filters (#56) #26

Allow multi-member session filters (#56)

Allow multi-member session filters (#56) #26

Workflow file for this run

name: Deploy
# 정책 (Week 3~):
# - N100 에는 prod 단일 환경만 운영.
# - dev push → staging-* GHCR image build/push 까지만. "컨테이너 빌드가 깨지지 않는가" 안전망용.
# N100 에는 절대 SSH 안 함.
# - main push → prod-* GHCR image build/push + N100 SSH 배포.
# - workflow_dispatch 로 임의 ref 에 대해 prod target 수동 배포 가능.
#
# Staging 컨테이너를 N100 에서 안 돌리기로 결정한 배경은 회고-Week2 + 회고-Week3 참조.
on:
push:
branches: [dev, main]
workflow_dispatch:
inputs:
target:
description: "수동 배포 대상 (prod 만 지원)"
required: true
default: "prod"
type: choice
options: [prod]
# 동시 배포 금지. 같은 ref 에 새 push 가 오면 in-progress 취소.
concurrency:
group: deploy-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: read
packages: write # ghcr.io push 권한
env:
REGISTRY: ghcr.io
IMAGE: ${{ github.repository }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
# ---------------------------------------------------------------------------
# 1) build image → ghcr.io 푸시 (dev=staging tag, main=prod tag, dispatch=prod tag)
# ---------------------------------------------------------------------------
build:
name: Build & push image
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
image_tag: ${{ steps.meta.outputs.image_tag }}
target: ${{ steps.meta.outputs.target }}
deploy_to_n100: ${{ steps.meta.outputs.deploy_to_n100 }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve target + tag
id: meta
run: |
# 우선순위: workflow_dispatch.inputs.target → branch
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
target="${{ inputs.target }}"
deploy_to_n100="true"
elif [ "${{ github.ref_name }}" = "main" ]; then
target="prod"
deploy_to_n100="true"
else
# dev push (또는 그 외 브랜치) — image 만 빌드하고 N100 SSH 는 안 함.
target="staging"
deploy_to_n100="false"
fi
sha_short="$(echo "${{ github.sha }}" | cut -c1-7)"
image_tag="${target}-${sha_short}"
echo "target=$target" >> "$GITHUB_OUTPUT"
echo "image_tag=$image_tag" >> "$GITHUB_OUTPUT"
echo "deploy_to_n100=$deploy_to_n100" >> "$GITHUB_OUTPUT"
echo "image_ref=${REGISTRY}/${IMAGE}:${image_tag}" >> "$GITHUB_OUTPUT"
echo "image_ref_target=${REGISTRY}/${IMAGE}:${target}" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & push
uses: docker/build-push-action@v6
with:
context: .
file: deploy/Dockerfile
push: true
tags: |
${{ steps.meta.outputs.image_ref }}
${{ steps.meta.outputs.image_ref_target }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ---------------------------------------------------------------------------
# 2) N100 SSH secrets 준비 여부 확인. 미등록이면 build 까지만 하고 deploy 는 skip.
# ---------------------------------------------------------------------------
deploy-preflight:
name: Check deploy secrets
runs-on: ubuntu-latest
timeout-minutes: 3
needs: build
if: needs.build.outputs.deploy_to_n100 == 'true'
outputs:
deploy_ready: ${{ steps.secrets.outputs.deploy_ready }}
steps:
- name: Check N100 secrets
id: secrets
run: |
if [ -n "$N100_HOST" ] && [ -n "$N100_USER" ] && [ -n "$N100_SSH_KEY" ]; then
echo "deploy_ready=true" >> "$GITHUB_OUTPUT"
else
echo "deploy_ready=false" >> "$GITHUB_OUTPUT"
echo "::notice::N100 deploy secrets are not configured yet. Image build/push completed; SSH deploy is skipped."
fi
env:
N100_HOST: ${{ secrets.N100_HOST }}
N100_USER: ${{ secrets.N100_USER }}
N100_SSH_KEY: ${{ secrets.N100_SSH_KEY }}
# ---------------------------------------------------------------------------
# 3) main / dispatch 한정. SSH 로 N100 접속 → docker compose pull && up -d
# ---------------------------------------------------------------------------
deploy:
name: Deploy to ${{ needs.build.outputs.target }}
needs: [build, deploy-preflight]
if: needs.build.outputs.deploy_to_n100 == 'true' && needs.deploy-preflight.outputs.deploy_ready == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
# prod environment 에 Required reviewers 를 걸면 수동 승인 게이트가 된다.
environment:
name: ${{ needs.build.outputs.target }}
steps:
- name: Pull & restart on N100
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.N100_HOST }}
username: ${{ secrets.N100_USER }}
key: ${{ secrets.N100_SSH_KEY }}
# 호스트의 deploy/ 디렉터리에서 git sync → image pull → compose up.
# IMAGE_TAG 는 GHCR 이미지 태그, MURUN_ENV_FILE 은 .env.prod 고정 (N100=prod only).
script: |
set -e
cd ${MURUN_DEPLOY_DIR:-~/murun-peterabcd/deploy}
git -C .. fetch --depth=1 origin "${{ github.ref_name }}"
git -C .. checkout -B "${{ github.ref_name }}" FETCH_HEAD
export IMAGE_TAG="${{ needs.build.outputs.image_tag }}"
export MURUN_ENV_FILE=".env.${{ needs.build.outputs.target }}"
on_error() {
echo "::group::docker compose ps"
docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" ps || true
echo "::endgroup::"
echo "::group::app logs"
docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" logs --tail=160 app || true
echo "::endgroup::"
echo "::group::caddy logs"
docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" logs --tail=80 caddy || true
echo "::endgroup::"
}
trap on_error ERR
docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" pull
docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" up -d --remove-orphans
echo "::group::post-deploy smoke"
for i in $(seq 1 30); do
if docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" exec -T app node -e "fetch('http://127.0.0.1:3000/api/health').then(async (r) => { const j = await r.json(); if (!r.ok || !j.ok) process.exit(1); }).catch((e) => { console.error(e); process.exit(1); })"; then
break
fi
if [ "$i" -eq 30 ]; then
echo "health smoke failed"
exit 1
fi
sleep 2
done
docker compose -p murun-${{ needs.build.outputs.target }} --env-file "$MURUN_ENV_FILE" exec -T app node -e "fetch('http://127.0.0.1:3000/login?error=Configuration').then(async (r) => { const text = await r.text(); if (!r.ok || !text.includes('서버의 Google 로그인 설정')) process.exit(1); }).catch((e) => { console.error(e); process.exit(1); })"
echo "::endgroup::"
docker image prune -f