@@ -2,6 +2,13 @@ name: Deploy Pull-it backend and worker to Yeon Pi
22
33on :
44 workflow_dispatch :
5+ inputs :
6+ operation :
7+ description : ' preflight은 Pi에 쓰지 않고 전송 경로만 검증합니다.'
8+ required : true
9+ default : preflight
10+ type : choice
11+ options : [preflight, deploy]
512 push :
613 branches : [develop]
714 paths :
@@ -23,7 +30,63 @@ concurrency:
2330 cancel-in-progress : false
2431
2532jobs :
33+ transport-preflight :
34+ runs-on : ubuntu-latest
35+ environment : pullit-backend-production
36+ steps :
37+ - name : Install pinned Cloudflare Access client
38+ run : |
39+ set -euo pipefail
40+ curl --fail --location --retry 3 --output /tmp/cloudflared \
41+ https://github.com/cloudflare/cloudflared/releases/download/2026.5.0/cloudflared-linux-amd64
42+ echo '0095e46fdc88855d801c4d304cb1f5dd4bd656116c47ab94c2ad0ae7cda1c7ec /tmp/cloudflared' | sha256sum --check -
43+ install -D -m 0755 /tmp/cloudflared "$HOME/.local/bin/cloudflared"
44+ printf '%s\n' "$HOME/.local/bin" >> "$GITHUB_PATH"
45+ - name : Verify Cloudflare Access and the restricted Pi command path
46+ env :
47+ DEPLOY_KEY : ${{ secrets.PULLIT_DEPLOY_SSH_PRIVATE_KEY }}
48+ KNOWN_HOSTS : ${{ vars.PULLIT_DEPLOY_SSH_KNOWN_HOSTS }}
49+ DEPLOY_HOST : ${{ vars.PULLIT_DEPLOY_HOST }}
50+ PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID : ${{ secrets.PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID }}
51+ PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET : ${{ secrets.PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET }}
52+ DB_USERNAME : ${{ vars.PULLIT_DB_USERNAME }}
53+ DB_PASSWORD : ${{ secrets.DB_PASSWORD }}
54+ DB_ROOT_PASSWORD : ${{ secrets.DB_ROOT_PASSWORD }}
55+ PULLIT_DATABASE_NAME : ${{ vars.PULLIT_DATABASE_NAME }}
56+ RABBITMQ_DEFAULT_USER : ${{ vars.RABBITMQ_DEFAULT_USER }}
57+ RABBITMQ_DEFAULT_PASS : ${{ secrets.RABBITMQ_DEFAULT_PASS }}
58+ JWT_SECRET_KEY : ${{ secrets.JWT_SECRET_KEY }}
59+ KAKAO_REST_API_KEY : ${{ secrets.KAKAO_REST_API_KEY }}
60+ KAKAO_CLIENT_SECRET : ${{ secrets.KAKAO_CLIENT_SECRET }}
61+ GEMINI_API_KEY : ${{ secrets.GEMINI_API_KEY }}
62+ S3_BUCKET_NAME : ${{ vars.S3_BUCKET_NAME }}
63+ S3_REGION : ${{ vars.S3_REGION }}
64+ S3_ACCESS_KEY : ${{ secrets.S3_ACCESS_KEY }}
65+ S3_SECRET_KEY : ${{ secrets.S3_SECRET_KEY }}
66+ run : |
67+ set -euo pipefail
68+ required=(DEPLOY_KEY KNOWN_HOSTS DEPLOY_HOST PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET DB_USERNAME DB_PASSWORD DB_ROOT_PASSWORD PULLIT_DATABASE_NAME RABBITMQ_DEFAULT_USER RABBITMQ_DEFAULT_PASS JWT_SECRET_KEY KAKAO_REST_API_KEY KAKAO_CLIENT_SECRET GEMINI_API_KEY S3_BUCKET_NAME S3_REGION S3_ACCESS_KEY S3_SECRET_KEY)
69+ for name in "${required[@]}"; do test -n "${!name}"; done
70+ case "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID" in 'CF-Access-Client-Id:'*) exit 1 ;; esac
71+ case "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET" in 'CF-Access-Client-Secret:'*) exit 1 ;; esac
72+ status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
73+ --header "CF-Access-Client-Id: $PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID" \
74+ --header "CF-Access-Client-Secret: $PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET" "https://$DEPLOY_HOST")"
75+ test "$status" = 200 || { echo "Cloudflare Access service-token probe returned HTTP $status" >&2; exit 1; }
76+ install -d -m 700 ~/.ssh
77+ printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519
78+ printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
79+ chmod 600 ~/.ssh/id_ed25519
80+ printf 'Host %s\n' "$DEPLOY_HOST" > ~/.ssh/config
81+ cat >> ~/.ssh/config <<'EOF'
82+ ProxyCommand cloudflared access ssh --hostname %h --service-token-id "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID" --service-token-secret "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET"
83+ EOF
84+ chmod 600 ~/.ssh/config
85+ ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes -o StrictHostKeyChecking=yes "pullit-deploy@$DEPLOY_HOST" \
86+ 'test -d /opt/pullit/incoming && test -w /opt/pullit/incoming && sudo -n -l /usr/local/sbin/pullit-deploy >/dev/null'
2687 build :
88+ needs : transport-preflight
89+ if : github.event_name == 'push' || inputs.operation == 'deploy'
2790 runs-on : ubuntu-latest
2891 steps :
2992 - uses : actions/checkout@v5
43106 labels : org.opencontainers.image.source=https://github.com/${{ github.repository }}
44107
45108 deploy :
46- needs : build
109+ needs : [transport-preflight, build]
110+ if : github.event_name == 'push' || inputs.operation == 'deploy'
47111 runs-on : ubuntu-latest
48112 environment : pullit-backend-production
49113 steps :
@@ -56,27 +120,6 @@ jobs:
56120 echo '0095e46fdc88855d801c4d304cb1f5dd4bd656116c47ab94c2ad0ae7cda1c7ec /tmp/cloudflared' | sha256sum --check -
57121 install -D -m 0755 /tmp/cloudflared "$HOME/.local/bin/cloudflared"
58122 printf '%s\n' "$HOME/.local/bin" >> "$GITHUB_PATH"
59- - name : Verify Cloudflare Access service token
60- env :
61- DEPLOY_HOST : ${{ vars.PULLIT_DEPLOY_HOST }}
62- PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID : ${{ secrets.PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID }}
63- PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET : ${{ secrets.PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET }}
64- run : |
65- set -euo pipefail
66- test -n "$DEPLOY_HOST"
67- test -n "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID"
68- test -n "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET"
69- case "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID" in
70- 'CF-Access-Client-Id:'*) echo 'Cloudflare client ID must be the value only.' >&2; exit 1 ;;
71- esac
72- case "$PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET" in
73- 'CF-Access-Client-Secret:'*) echo 'Cloudflare client secret must be the value only.' >&2; exit 1 ;;
74- esac
75- status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
76- --header "CF-Access-Client-Id: $PULLIT_CLOUDFLARE_ACCESS_CLIENT_ID" \
77- --header "CF-Access-Client-Secret: $PULLIT_CLOUDFLARE_ACCESS_CLIENT_SECRET" \
78- "https://$DEPLOY_HOST")"
79- test "$status" = 200 || { echo "Cloudflare Access service-token probe returned HTTP $status" >&2; exit 1; }
80123 - name : Create isolated runtime environment and deploy it
81124 env :
82125 PULLIT_BACKEND_IMAGE : ghcr.io/hyeonjun0527/pullit-backend:${{ github.sha }}
0 commit comments