-
Notifications
You must be signed in to change notification settings - Fork 1
160 lines (144 loc) · 5.42 KB
/
deploy.yml
File metadata and controls
160 lines (144 loc) · 5.42 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
name: Deploy
on:
workflow_run:
workflows: [Release]
types: [completed]
env:
IMAGE_NAME: networkcat/rustyip
permissions:
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
environment: production
concurrency:
group: deploy-production
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Resolve image tag from release
id: image
run: |
TAG="${{ github.event.workflow_run.head_branch }}"
TAG="${TAG#v}"
echo "full=${{ env.IMAGE_NAME }}:${TAG}" >> "$GITHUB_OUTPUT"
# ---- Terraform: provision infrastructure ----
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
- name: Terraform init
working-directory: deploy/terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TF_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TF_STATE_SECRET_KEY }}
run: |
terraform init \
-backend-config="bucket=${{ secrets.TF_STATE_BUCKET }}" \
-backend-config="endpoints={s3=\"${{ secrets.TF_STATE_ENDPOINT }}\"}"
- name: Terraform apply
working-directory: deploy/terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TF_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TF_STATE_SECRET_KEY }}
run: |
terraform apply -auto-approve \
-var="vultr_api_key=${{ secrets.VULTR_API_KEY }}" \
-var="ssh_public_key=${{ secrets.SSH_PUBLIC_KEY }}"
- name: Extract VPS IP (masked)
id: vps
working-directory: deploy/terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TF_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TF_STATE_SECRET_KEY }}
run: |
VPS_IP=$(terraform output -raw instance_ip)
echo "::add-mask::${VPS_IP}"
echo "ip=${VPS_IP}" >> "$GITHUB_OUTPUT"
# ---- SSH setup ----
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
for i in 1 2 3; do
keys=$(ssh-keyscan -H "${{ steps.vps.outputs.ip }}" 2>/dev/null) || true
if [ -n "$keys" ]; then
echo "$keys" >> ~/.ssh/known_hosts
echo "ssh-keyscan succeeded on attempt $i"
exit 0
fi
echo "ssh-keyscan attempt $i failed, retrying in 10s..."
sleep 10
done
echo "ssh-keyscan failed after 3 attempts"
exit 1
# ---- Detect first deployment ----
- name: Detect first deployment
id: detect
run: |
set +e
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \
-i ~/.ssh/deploy_key root@"${{ steps.vps.outputs.ip }}" \
"test -f /opt/rustyip/.active_color"
if [ $? -ne 0 ]; then
echo "first_deploy=true" >> "$GITHUB_OUTPUT"
else
echo "first_deploy=false" >> "$GITHUB_OUTPUT"
fi
# ---- Ansible: configure VPS and deploy ----
- name: Install Ansible
run: pip install ansible
- name: Generate Ansible inventory
run: |
cat > deploy/ansible/inventory.yml <<EOF
all:
hosts:
vps:
ansible_host: "${{ steps.vps.outputs.ip }}"
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/deploy_key
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new"
EOF
- name: Run Ansible playbook
working-directory: deploy/ansible
env:
APP_IMAGE: ${{ steps.image.outputs.full }}
SITE_DOMAIN: ${{ secrets.SITE_DOMAIN }}
DB_UPDATE_URL: ${{ secrets.DB_UPDATE_URL }}
ORIGIN_CERT: ${{ secrets.ORIGIN_CERT }}
ORIGIN_KEY: ${{ secrets.ORIGIN_KEY }}
IPV4_DOMAIN: ${{ secrets.IPV4_DOMAIN }}
IPV4_ORIGIN_CERT: ${{ secrets.IPV4_ORIGIN_CERT }}
IPV4_ORIGIN_KEY: ${{ secrets.IPV4_ORIGIN_KEY }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
FIRST_DEPLOY: ${{ steps.detect.outputs.first_deploy }}
run: |
ansible-playbook -i inventory.yml playbook.yml
# ---- Verify deployment ----
- name: Verify deployment health
env:
SSH_OPTS: -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -i ~/.ssh/deploy_key
VPS_HOST: root@${{ steps.vps.outputs.ip }}
run: |
DEPLOY_COLOR=$(ssh $SSH_OPTS "$VPS_HOST" "cat /opt/rustyip/.active_color")
echo "Active deployment color: ${DEPLOY_COLOR}"
for i in $(seq 1 30); do
if ssh $SSH_OPTS "$VPS_HOST" \
"docker exec rustyip-app-${DEPLOY_COLOR} wget -qO /dev/null http://127.0.0.1:3000/health" 2>/dev/null; then
echo "Deployment verified: app-${DEPLOY_COLOR} is healthy"
exit 0
fi
echo "Waiting for health check... (attempt $i/30)"
sleep 4
done
echo "Health check failed after 120 seconds"
exit 1
# ---- Cleanup ----
- name: Cleanup SSH key
if: always()
run: rm -f ~/.ssh/deploy_key