mirror of
https://github.com/NetworkCats/rustyip.git
synced 2026-11-13 16:17:46 +00:00
177 lines
6.1 KiB
YAML
177 lines
6.1 KiB
YAML
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: Wait for SSH to become available
|
|
run: |
|
|
VPS_IP="${{ steps.vps.outputs.ip }}"
|
|
echo "Waiting for SSH port 22 on ${VPS_IP}..."
|
|
for i in $(seq 1 30); do
|
|
if nc -z -w5 "$VPS_IP" 22 2>/dev/null; then
|
|
echo "SSH port open on attempt $i"
|
|
break
|
|
fi
|
|
echo "SSH port not ready yet (attempt $i/30), retrying in 10s..."
|
|
sleep 10
|
|
done
|
|
|
|
- name: Setup SSH key
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
for i in $(seq 1 10); do
|
|
keys=$(ssh-keyscan -H "${{ steps.vps.outputs.ip }}" 2>&1) || true
|
|
# Filter to only key lines (ignore stderr/warnings)
|
|
key_lines=$(echo "$keys" | grep -v '^#' | grep -v '^$' | grep -v 'getaddrinfo' || true)
|
|
if [ -n "$key_lines" ]; then
|
|
echo "$key_lines" >> ~/.ssh/known_hosts
|
|
echo "ssh-keyscan succeeded on attempt $i"
|
|
exit 0
|
|
fi
|
|
echo "ssh-keyscan attempt $i failed, retrying in 15s..."
|
|
echo "Output was: $keys"
|
|
sleep 15
|
|
done
|
|
echo "ssh-keyscan failed after 10 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
|