Repository navigation
101 lines (90 loc) · 3.67 KB
/
Copy pathping-api-deploy.yml
File metadata and controls
101 lines (90 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
name: Deploy ping_api (CS_1)
# Deploys the ping_api custom service (web/backend CS_1) to Hetzner prod.
# Gate: deploy runs ONLY on manual workflow_dispatch. Pushes to main run a
# no-op job that always succeeds, so main CI stays green and the prod SSH
# deploy is never triggered automatically. The PING_API_DEPLOY_AUTHORIZED
# org secret (UA-02) is consumed inside the deploy steps at dispatch time.
# secrets context is NOT valid in a job-level `if:` (github/needs/vars/inputs
# only) — gating is by github.event_name, which is valid.
#
# prod IP: 5.75.235.42 port: 8001
on:
push:
branches: [main]
workflow_dispatch:
inputs:
force:
description: 'Force deploy even if no ping_api changes detected'
required: false
default: 'false'
type: boolean
permissions:
contents: read
# cancel-in-progress: false — this workflow deploys/publishes/releases; a superseding
# run must queue behind an in-flight one, never cancel it mid-flight.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
# No-op guard: exits success on branch pushes so GitHub never records a
# failure run for push events. Always skipped on workflow_dispatch, where
# the real deploy runs instead.
branch-push-noop:
name: No-op (branch push — deploy requires workflow_dispatch)
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Skip — deploy is dispatch-only
run: echo "ping_api deploy is workflow_dispatch-only. No action on push."
deploy:
name: Deploy ping_api to prod
runs-on: ubuntu-latest
timeout-minutes: 15
# Dispatch-only. Never runs on push (branch-push-noop handles push).
# Valid job-level `if:` (github context only — no secrets reference).
if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v7.0.1
- name: Verify ping_api service directory exists
run: |
if [ ! -d "web/backend/services/ping_api" ]; then
echo "::error::web/backend/services/ping_api not found — aborting deploy"
exit 1
fi
- name: Verify deploy authorization (UA-02)
env:
PING_API_DEPLOY_AUTHORIZED: ${{ secrets.PING_API_DEPLOY_AUTHORIZED }}
run: |
if [ -z "${PING_API_DEPLOY_AUTHORIZED}" ]; then
echo "::error::PING_API_DEPLOY_AUTHORIZED secret absent — deploy not authorized (UA-02, P104-held)."
exit 1
fi
echo "Deploy authorized."
- name: Deploy to Hetzner prod (SSH)
env:
HETZNER_SSH_PRIVATE_KEY: ${{ secrets.HETZNER_NSELF_SSH_PRIVATE_KEY }}
PROD_HOST: 5.75.235.42
PING_API_DEPLOY_AUTHORIZED: ${{ secrets.PING_API_DEPLOY_AUTHORIZED }}
run: |
# Write SSH key
mkdir -p ~/.ssh
echo "$HETZNER_SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 0600 ~/.ssh/deploy_key
ssh-keyscan -H "$PROD_HOST" >> ~/.ssh/known_hosts 2>/dev/null
# Deploy via nself CLI on the remote host
ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes \
"deploy@${PROD_HOST}" \
"cd /srv/nself && nself deploy --service ping_api --env prod"
- name: Health check ping_api
run: |
# Wait for service to become healthy (max 60s)
for i in $(seq 1 12); do
if curl -sf "https://ping.nself.org/health" | grep -q '"status":"ok"'; then
echo "ping_api healthy after $((i * 5))s"
exit 0
fi
echo "Waiting... attempt $i/12"
sleep 5
done
echo "::error::ping_api health check failed after 60s"
exit 1