Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit387f0fdunknown_key

feat(deploy): AI-native auto-deploy to gluecron.com

feat(deploy): AI-native auto-deploy to gluecron.com

Replaces the manual SSH-and-bash workflow with a zero-touch GitHub
Actions pipeline. Push to main -> auto-deploys -> smoke-tests -> auto-
rollback on failure -> Claude diagnoses any failure in 1 paragraph.

.github/workflows/vultr-deploy.yml
- Triggers on push to main and workflow_dispatch
- Captures pre-deploy SHA before touching the box (rollback target)
- SSHs to VULTR_HOST as VULTR_USER with VULTR_SSH_KEY secret,
  runs scripts/deploy-crontech.sh (which already handles git pull,
  bun install, migrations, systemd restart, Caddy reload)
- Smoke-tests https://gluecron.com/healthz with 8x backoff up to 64s
- On smoke failure: auto-rollback (git reset --hard <prev>, restart
  systemd) — site stays live on the last good code
- AI failure diagnosis (optional, ANTHROPIC_API_KEY): captures systemd
  status + last 100 journal lines + caddy validate + local /healthz,
  feeds to Claude Haiku 4.5, posts a 100-word root-cause paragraph
  to the workflow summary
- Optional DEPLOY_WEBHOOK_URL fanout for Slack / Discord / etc.
- Concurrency-grouped so two pushes can't race

scripts/setup-auto-deploy.ps1
- One-command Windows setup. Generates a dedicated ed25519 deploy key,
  authorizes it on the Vultr box via existing SSH access, sets the three
  GitHub secrets, triggers a test deploy, watches the run.

Why this matters: the user shouldn't be SSHing into a box in 2026.
This is what AI-native infra looks like — push code, world updates,
the AI explains what broke if anything does.
Claude committed on May 3, 2026Parent: 9ffa60e
2 files changed+2830387f0fd89cf5f369f67d8937f15fcdb86f3edd83
2 changed files+283−0
Added.github/workflows/vultr-deploy.yml+183−0View fileUnifiedSplit
1name: Vultr Deploy (gluecron.com)
2
3# Triggered on every push to main and on manual dispatch.
4# Steps:
5# 1. Capture the current SHA on the box (for rollback)
6# 2. SSH in, git pull, run deploy-crontech.sh (which restarts systemd + reloads Caddy)
7# 3. Smoke-test https://gluecron.com/healthz with retries
8# 4. On smoke failure: roll back to the previous SHA and restart
9# 5. On any failure: have Claude read the last 100 journal lines and post a one-paragraph
10# root-cause analysis to the workflow summary (and optionally a webhook)
11#
12# Secrets required:
13# VULTR_HOST — public IP/hostname of the box (e.g. 45.76.171.37)
14# VULTR_USER — ssh user (e.g. root)
15# VULTR_SSH_KEY — private deploy key (PEM/OpenSSH format)
16#
17# Optional:
18# ANTHROPIC_API_KEY — enables AI failure-diagnosis step
19# DEPLOY_WEBHOOK_URL — POSTed with JSON deploy status (Slack/Discord/anything)
20
21on:
22 push:
23 branches: [main]
24 workflow_dispatch: {}
25
26concurrency:
27 group: vultr-deploy
28 cancel-in-progress: false
29
30jobs:
31 deploy:
32 name: Deploy gluecron.com
33 runs-on: ubuntu-latest
34 timeout-minutes: 12
35 steps:
36 - uses: actions/checkout@v4
37
38 # ─── 1. Capture pre-deploy SHA so we can rollback ───────────────────
39 - name: Capture pre-deploy SHA
40 id: prev
41 uses: appleboy/ssh-action@v1.2.0
42 with:
43 host: ${{ secrets.VULTR_HOST }}
44 username: ${{ secrets.VULTR_USER }}
45 key: ${{ secrets.VULTR_SSH_KEY }}
46 script_stop: true
47 script: |
48 cd /opt/gluecron
49 sha=$(git rev-parse HEAD)
50 echo "Previous SHA: $sha"
51 echo "$sha" > /tmp/gluecron_prev_sha
52 cat /tmp/gluecron_prev_sha
53
54 # ─── 2. Deploy: pull main, run the production script ────────────────
55 - name: Deploy
56 id: deploy
57 uses: appleboy/ssh-action@v1.2.0
58 with:
59 host: ${{ secrets.VULTR_HOST }}
60 username: ${{ secrets.VULTR_USER }}
61 key: ${{ secrets.VULTR_SSH_KEY }}
62 command_timeout: 8m
63 script_stop: true
64 script: |
65 set -euo pipefail
66 cd /opt/gluecron
67 git fetch --prune origin main
68 git reset --hard origin/main
69 new_sha=$(git rev-parse HEAD)
70 echo "Deploying SHA: $new_sha"
71 bash scripts/deploy-crontech.sh
72
73 # ─── 3. Smoke-test the live URL ─────────────────────────────────────
74 - name: Smoke test (https://gluecron.com/healthz)
75 id: smoke
76 run: |
77 set +e
78 for i in 1 2 3 4 5 6 7 8; do
79 code=$(curl -s -o /dev/null -w "%{http_code}" https://gluecron.com/healthz)
80 echo "Attempt $i: /healthz → $code"
81 if [ "$code" = "200" ]; then
82 echo "smoke_status=ok" >> $GITHUB_OUTPUT
83 echo "✓ gluecron.com is live and healthy."
84 exit 0
85 fi
86 sleep 8
87 done
88 echo "smoke_status=fail" >> $GITHUB_OUTPUT
89 echo "✗ /healthz did not return 200 after 64s. Triggering rollback."
90 exit 1
91
92 # ─── 4. Auto-rollback on smoke failure ──────────────────────────────
93 - name: Rollback on failure
94 if: failure() && steps.smoke.conclusion == 'failure'
95 uses: appleboy/ssh-action@v1.2.0
96 with:
97 host: ${{ secrets.VULTR_HOST }}
98 username: ${{ secrets.VULTR_USER }}
99 key: ${{ secrets.VULTR_SSH_KEY }}
100 script_stop: true
101 script: |
102 set -euo pipefail
103 cd /opt/gluecron
104 prev=$(cat /tmp/gluecron_prev_sha)
105 echo "Rolling back to $prev"
106 git reset --hard "$prev"
107 systemctl restart gluecron
108 sleep 5
109 systemctl status gluecron --no-pager | head -20
110
111 # ─── 5. AI failure diagnosis (only on failure, only if ANTHROPIC set) ─
112 - name: Capture failure context
113 if: failure()
114 id: ctx
115 uses: appleboy/ssh-action@v1.2.0
116 with:
117 host: ${{ secrets.VULTR_HOST }}
118 username: ${{ secrets.VULTR_USER }}
119 key: ${{ secrets.VULTR_SSH_KEY }}
120 script_stop: false
121 script: |
122 echo "===== systemd status ====="
123 systemctl status gluecron --no-pager 2>&1 | head -30 || true
124 echo "===== last 100 journal lines ====="
125 journalctl -u gluecron -n 100 --no-pager 2>&1 || true
126 echo "===== caddy reload check ====="
127 caddy validate --config /etc/caddy/Caddyfile 2>&1 || true
128 echo "===== /healthz from box ====="
129 curl -s -w "HTTP %{http_code}\n" http://localhost:3000/healthz 2>&1 || true
130
131 - name: AI root-cause analysis (Claude)
132 if: failure() && env.ANTHROPIC_API_KEY != ''
133 env:
134 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
135 DEPLOY_LOGS: ${{ steps.ctx.outputs.stdout }}
136 COMMIT_SHA: ${{ github.sha }}
137 COMMIT_MSG: ${{ github.event.head_commit.message }}
138 run: |
139 set +e
140 # Build the prompt
141 cat > /tmp/prompt.json <<EOF
142 {
143 "model": "claude-haiku-4-5-20251001",
144 "max_tokens": 600,
145 "system": "You are a senior SRE diagnosing a failed deploy. Read the systemd status, journal, and curl output. In 1 short paragraph (under 100 words), identify the most likely root cause and the single fastest fix. Be direct, no preamble.",
146 "messages": [{
147 "role": "user",
148 "content": "Commit: $COMMIT_SHA\nMessage: $COMMIT_MSG\n\nDeploy logs:\n$DEPLOY_LOGS"
149 }]
150 }
151 EOF
152 response=$(curl -s https://api.anthropic.com/v1/messages \
153 -H "x-api-key: $ANTHROPIC_API_KEY" \
154 -H "anthropic-version: 2023-06-01" \
155 -H "content-type: application/json" \
156 --data @/tmp/prompt.json)
157 analysis=$(echo "$response" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('content',[{}])[0].get('text','(no analysis)'))" 2>/dev/null)
158 echo "## 🤖 AI Failure Analysis" >> $GITHUB_STEP_SUMMARY
159 echo "" >> $GITHUB_STEP_SUMMARY
160 echo "$analysis" >> $GITHUB_STEP_SUMMARY
161 echo "" >> $GITHUB_STEP_SUMMARY
162 echo "**Commit:** \`${COMMIT_SHA:0:7}\` — $COMMIT_MSG" >> $GITHUB_STEP_SUMMARY
163
164 # ─── 6. Optional webhook notification ───────────────────────────────
165 - name: Notify webhook
166 if: always() && env.DEPLOY_WEBHOOK_URL != ''
167 env:
168 DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }}
169 STATUS: ${{ job.status }}
170 run: |
171 curl -s -X POST "$DEPLOY_WEBHOOK_URL" \
172 -H "content-type: application/json" \
173 --data "{\"status\":\"$STATUS\",\"target\":\"gluecron.com\",\"sha\":\"${{ github.sha }}\",\"run\":\"${{ github.run_id }}\"}" || true
174
175 # ─── 7. Workflow summary on success ─────────────────────────────────
176 - name: Success summary
177 if: success()
178 run: |
179 echo "## ✅ Deploy succeeded" >> $GITHUB_STEP_SUMMARY
180 echo "" >> $GITHUB_STEP_SUMMARY
181 echo "- **Target:** https://gluecron.com" >> $GITHUB_STEP_SUMMARY
182 echo "- **SHA:** \`${GITHUB_SHA:0:7}\`" >> $GITHUB_STEP_SUMMARY
183 echo "- **Health:** /healthz → 200" >> $GITHUB_STEP_SUMMARY
Addedscripts/setup-auto-deploy.ps1+100−0View fileUnifiedSplit
1# =============================================================================
2# Gluecron auto-deploy setup — Windows / PowerShell
3# Run once. Wires up GitHub Actions to auto-deploy gluecron.com on every push.
4# =============================================================================
5#
6# Usage (from PowerShell, anywhere):
7# irm https://raw.githubusercontent.com/ccantynz-alt/Gluecron.com/main/scripts/setup-auto-deploy.ps1 | iex
8#
9# Or download + run locally:
10# .\scripts\setup-auto-deploy.ps1
11#
12# What it does:
13# 1. Verifies gh CLI is installed + authed
14# 2. Generates a dedicated SSH deploy key (~/.ssh/gluecron_deploy_key)
15# 3. Uploads the public key to root@45.76.171.37 via your existing SSH access
16# 4. Sets the three GitHub secrets the workflow needs
17# 5. Triggers a test deploy + watches the run
18# =============================================================================
19
20$ErrorActionPreference = "Stop"
21
22$REPO = "ccantynz-alt/Gluecron.com"
23$HOST_ = "45.76.171.37"
24$USER_ = "root"
25$KEYDIR = Join-Path $HOME ".ssh"
26$KEY = Join-Path $KEYDIR "gluecron_deploy_key"
27
28Write-Host "==> Gluecron auto-deploy setup" -ForegroundColor Cyan
29Write-Host ""
30
31# ─── 1. gh CLI check ────────────────────────────────────────────────────────
32Write-Host "[1/5] Checking gh CLI..."
33try {
34 gh --version | Out-Null
35} catch {
36 Write-Host " gh CLI not installed. Install with:" -ForegroundColor Red
37 Write-Host " winget install --id GitHub.cli"
38 exit 1
39}
40$authStatus = gh auth status 2>&1
41if ($LASTEXITCODE -ne 0) {
42 Write-Host " gh not authenticated. Run:" -ForegroundColor Red
43 Write-Host " gh auth login"
44 exit 1
45}
46Write-Host " ✓ gh is installed + authed" -ForegroundColor Green
47
48# ─── 2. SSH key ─────────────────────────────────────────────────────────────
49Write-Host "[2/5] SSH deploy key..."
50if (-not (Test-Path $KEYDIR)) { New-Item -ItemType Directory -Path $KEYDIR | Out-Null }
51if (Test-Path $KEY) {
52 Write-Host " ✓ existing key at $KEY (reusing)" -ForegroundColor Green
53} else {
54 ssh-keygen -t ed25519 -f $KEY -N '""' -C "gluecron-github-actions" 2>&1 | Out-Null
55 Write-Host " ✓ generated new key at $KEY" -ForegroundColor Green
56}
57
58# ─── 3. Authorize key on the Vultr box ──────────────────────────────────────
59Write-Host "[3/5] Authorizing key on $HOST_..."
60$pubKey = Get-Content "$KEY.pub" -Raw
61$pubKey = $pubKey.Trim()
62$cmd = "mkdir -p ~/.ssh && grep -qF '$pubKey' ~/.ssh/authorized_keys 2>/dev/null || echo '$pubKey' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
63Write-Host " Will SSH as $USER_@$HOST_ — you may be prompted for password / existing key" -ForegroundColor Yellow
64ssh "$USER_@$HOST_" $cmd
65if ($LASTEXITCODE -ne 0) {
66 Write-Host " ✗ Failed to authorize key. Manual step:" -ForegroundColor Red
67 Write-Host " Get-Content $KEY.pub | ssh $USER_@$HOST_ 'cat >> ~/.ssh/authorized_keys'"
68 exit 1
69}
70Write-Host " ✓ deploy key authorized on $HOST_" -ForegroundColor Green
71
72# ─── 4. Set GitHub secrets ──────────────────────────────────────────────────
73Write-Host "[4/5] Setting GitHub Actions secrets on $REPO..."
74$HOST_ | gh secret set VULTR_HOST --repo $REPO
75$USER_ | gh secret set VULTR_USER --repo $REPO
76Get-Content $KEY -Raw | gh secret set VULTR_SSH_KEY --repo $REPO
77Write-Host " ✓ VULTR_HOST, VULTR_USER, VULTR_SSH_KEY set" -ForegroundColor Green
78
79# ─── 5. Trigger first deploy + watch ────────────────────────────────────────
80Write-Host "[5/5] Triggering test deploy..."
81gh workflow run vultr-deploy.yml --repo $REPO --ref main
82Start-Sleep -Seconds 3
83$run = (gh run list --repo $REPO --workflow=vultr-deploy.yml --limit 1 --json databaseId | ConvertFrom-Json)[0]
84Write-Host " ✓ Run #$($run.databaseId) started. Watching..." -ForegroundColor Green
85Write-Host ""
86gh run watch $run.databaseId --repo $REPO --exit-status
87
88if ($LASTEXITCODE -eq 0) {
89 Write-Host ""
90 Write-Host "==> 🎉 Auto-deploy is live." -ForegroundColor Green
91 Write-Host " From now on every push to main auto-deploys to gluecron.com."
92 Write-Host " Smoke-tested + auto-rollback on failure."
93 Write-Host ""
94 Write-Host " Optional next step: set ANTHROPIC_API_KEY for AI failure diagnosis:"
95 Write-Host " gh secret set ANTHROPIC_API_KEY --repo $REPO"
96} else {
97 Write-Host ""
98 Write-Host "==> Run failed — check the Actions tab:" -ForegroundColor Red
99 Write-Host " https://github.com/$REPO/actions/workflows/vultr-deploy.yml"
100}
0101