-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor deployment and migration handling for droplets #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3e31454
ae917a1
34d1bef
74bfd1e
bac3d8f
dc8f9fb
a34d8ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| * text=auto eol=lf | ||
| *.sh text eol=lf |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,10 +36,43 @@ jobs: | |
| - name: Await reviewer approval | ||
| run: echo "Approved for ${{ github.ref_name }}." | ||
|
|
||
| discover: | ||
| build-and-push: | ||
| needs: gate | ||
| if: github.ref_name == 'master' || github.ref_name == 'prod' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| steps: | ||
| - uses: actions/checkout@v6.0.2 | ||
|
|
||
| - uses: docker/setup-buildx-action@v4 | ||
|
|
||
| - uses: docker/login-action@v4 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - id: meta | ||
| uses: docker/metadata-action@v6 | ||
| with: | ||
| images: ghcr.io/${{ github.repository }} | ||
| tags: type=ref,event=branch | ||
|
|
||
| - uses: docker/build-push-action@v7 | ||
| with: | ||
| context: . | ||
| push: true | ||
| platforms: linux/amd64 | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
|
|
||
| discover: | ||
| needs: build-and-push | ||
| runs-on: ubuntu-latest | ||
| environment: ${{ github.ref_name == 'prod' && 'prod' || 'dev' }} | ||
| permissions: | ||
| contents: read | ||
|
|
||
|
|
@@ -99,13 +132,11 @@ jobs: | |
| needs: discover | ||
| if: needs.discover.outputs.droplet_exists != 'true' | ||
| runs-on: ubuntu-latest | ||
| environment: ${{ github.ref_name == 'prod' && 'prod' || 'dev' }} | ||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| DEPLOY_BRANCH: ${{ github.ref_name }} | ||
| APP_PATH: ${{ vars.APP_PATH || '/opt/event-queue-bot' }} | ||
| DO_REGION: ${{ vars.DO_REGION || 'nyc3' }} | ||
| DO_SIZE: ${{ vars.DO_SIZE || 's-1vcpu-1gb' }} | ||
| DO_IMAGE: ${{ vars.DO_IMAGE || 'ubuntu-24-04-x64' }} | ||
|
|
@@ -164,12 +195,17 @@ jobs: | |
| && (needs.provision.result == 'success' || needs.provision.result == 'skipped') | ||
| runs-on: ubuntu-latest | ||
| environment: ${{ github.ref_name == 'prod' && 'prod' || 'dev' }} | ||
| concurrency: | ||
| group: provision-and-deploy-bot-droplet | ||
| cancel-in-progress: false | ||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| DEPLOY_BRANCH: ${{ github.ref_name }} | ||
| APP_PATH: ${{ vars.APP_PATH || '/opt/event-queue-bot' }} | ||
| APP_PATH: ${{ vars.APP_PATH || (github.ref_name == 'prod' && '/opt/event-queue-bot' || '/opt/event-queue-bot-nightly') }} | ||
| CONTAINER_NAME: ${{ github.ref_name == 'prod' && 'queue-bot' || 'queue-bot-nightly' }} | ||
| IMAGE_TAG: ${{ github.ref_name }} | ||
| # Coalesce: fresh-provision output wins; otherwise discover's lookup. | ||
| BOT_HOST: ${{ needs.provision.outputs.bot_host || needs.discover.outputs.bot_host }} | ||
|
|
||
|
|
@@ -191,6 +227,17 @@ jobs: | |
| with: | ||
| ref: ${{ env.DEPLOY_BRANCH }} | ||
|
|
||
| - name: Install doctl | ||
| uses: digitalocean/action-doctl@v2.5.2 | ||
| with: | ||
| token: ${{ secrets.DIGITALOCEAN_TOKEN }} | ||
| version: 1.159.0 | ||
|
|
||
| - name: Ensure firewall allows bot ports | ||
| env: | ||
| DO_DROPLET_NAME: ${{ vars.DO_DROPLET_NAME || 'event-queue-bot' }} | ||
| run: bash scripts/ensure-firewall.sh | ||
|
|
||
| - name: Configure SSH | ||
| env: | ||
| SSH_DEPLOY_PRIVATE_KEY: ${{ secrets.SSH_DEPLOY_PRIVATE_KEY }} | ||
|
|
@@ -218,7 +265,8 @@ jobs: | |
| - name: Wait for cloud-init | ||
| run: | | ||
| for attempt in {1..30}; do | ||
| if ssh -i ~/.ssh/bot_deploy_key -o ConnectTimeout=10 deploy@"${BOT_HOST}" "cloud-init status --wait && test -x /usr/local/bin/deploy-event-queue-bot"; then | ||
| if ssh -i ~/.ssh/bot_deploy_key -o ConnectTimeout=10 deploy@"${BOT_HOST}" \ | ||
| "cloud-init status --wait && test -x /usr/local/bin/deploy-event-queue-bot"; then | ||
| exit 0 | ||
| fi | ||
|
|
||
|
|
@@ -252,26 +300,35 @@ jobs: | |
| printf 'ENABLE_LEGACY_MIGRATION=%s\n' "${BOT_ENABLE_LEGACY_MIGRATION:-false}" | ||
| printf 'FORCE_SEND_PATCH_NOTES=%s\n' "${BOT_FORCE_SEND_PATCH_NOTES:-false}" | ||
| printf 'SILENT=%s\n' "${BOT_SILENT:-false}" | ||
| printf 'CONTAINER_NAME=%s\n' "${CONTAINER_NAME}" | ||
| printf 'IMAGE_TAG=%s\n' "${IMAGE_TAG}" | ||
| } > "${RUNNER_TEMP}/bot.env" | ||
|
|
||
| - name: Sync repository to VPS | ||
| - name: Sync deploy artifacts to VPS | ||
| run: | | ||
| rsync -az --delete \ | ||
| --exclude '.git' \ | ||
| --exclude '.github' \ | ||
| --exclude 'node_modules' \ | ||
| --exclude 'data/main.sqlite' \ | ||
| --exclude 'data/backups' \ | ||
| --exclude 'data/migrations/legacy-export' \ | ||
| --exclude 'logs' \ | ||
| --exclude '.env' \ | ||
| # Pre-create the bind-mount targets so Docker mounts a file/dir, not a | ||
| # new directory in place of the sqlite file. The compose file mounts | ||
| # data/main.sqlite and data/backups individually (not the whole data | ||
| # dir) so the image's data/migrations is not shadowed. | ||
| ssh -i ~/.ssh/bot_deploy_key deploy@"${BOT_HOST}" \ | ||
| "mkdir -p '${APP_PATH}/data/backups' && touch '${APP_PATH}/data/main.sqlite'" | ||
| rsync -az \ | ||
| -e "ssh -i ~/.ssh/bot_deploy_key" \ | ||
| ./ deploy@"${BOT_HOST}":"${APP_PATH}/" | ||
| docker-compose.app.yml deploy@"${BOT_HOST}":"${APP_PATH}/" | ||
|
|
||
| - name: Write bot environment | ||
| run: | | ||
| ssh -i ~/.ssh/bot_deploy_key deploy@"${BOT_HOST}" "umask 077 && cat > '${APP_PATH}/.env.tmp' && mv '${APP_PATH}/.env.tmp' '${APP_PATH}/.env'" < "${RUNNER_TEMP}/bot.env" | ||
|
|
||
| - name: Log in to GHCR on VPS | ||
| env: | ||
| GHCR_PULL_TOKEN: ${{ secrets.GHCR_PULL_TOKEN }} | ||
| run: | | ||
| if [ -n "${GHCR_PULL_TOKEN}" ]; then | ||
| ssh -i ~/.ssh/bot_deploy_key deploy@"${BOT_HOST}" \ | ||
| "echo '${GHCR_PULL_TOKEN}' | docker login ghcr.io -u '${{ github.actor }}' --password-stdin" | ||
| fi | ||
|
|
||
| - name: Deploy bot | ||
| run: | | ||
| ssh -i ~/.ssh/bot_deploy_key deploy@"${BOT_HOST}" "sudo /usr/local/bin/deploy-event-queue-bot" | ||
| ssh -i ~/.ssh/bot_deploy_key deploy@"${BOT_HOST}" "sudo /usr/local/bin/deploy-event-queue-bot '${APP_PATH}'" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GHCR login user mismatchHigh Severity The deploy workflow runs Additional Locations (1)Reviewed by Cursor Bugbot for commit a34d8ae. Configure here. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,8 @@ jobs: | |
|
|
||
| env: | ||
| DO_DROPLET_NAME: ${{ vars.DO_DROPLET_NAME || 'event-queue-bot' }} | ||
| APP_PATH: ${{ vars.APP_PATH || '/opt/event-queue-bot' }} | ||
| APP_PATH: ${{ vars.APP_PATH || (inputs.environment == 'prod' && '/opt/event-queue-bot' || '/opt/event-queue-bot-nightly') }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restart bypasses deploy lockMedium Severity Prod and dev now share one droplet, and the Additional Locations (1)Reviewed by Cursor Bugbot for commit a34d8ae. Configure here. |
||
| CONTAINER_NAME: ${{ inputs.environment == 'prod' && 'queue-bot' || 'queue-bot-nightly' }} | ||
|
|
||
| steps: | ||
| - name: Validate required secrets | ||
|
|
@@ -87,4 +88,4 @@ jobs: | |
| BOT_HOST: ${{ steps.lookup.outputs.bot_host }} | ||
| run: | | ||
| ssh -i ~/.ssh/bot_deploy_key deploy@"${BOT_HOST}" \ | ||
| "cd '${APP_PATH}' && docker compose restart && docker logs --tail 100 queue-bot" | ||
| "cd '${APP_PATH}' && docker compose -f docker-compose.app.yml restart && docker logs --tail 100 '${CONTAINER_NAME}'" | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong GHCR login username
Medium Severity
When
GHCR_PULL_TOKENis used,docker loginon the VPS uses${{ github.actor }}as the username. GHCR expects the GitHub username that owns the PAT, which often differs from whoever triggered the workflow, so authentication can fail for private packages.Reviewed by Cursor Bugbot for commit a34d8ae. Configure here.