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

deploy: self-healing + fast auto-deploy for standalone box

deploy: self-healing + fast auto-deploy for standalone box

- autoheal + log rotation in the standalone compose stack
- 1G swap + unattended-upgrades provisioned by the deploy script
- daily Postgres backup (scripts/backup.sh) via systemd timer, 14-day
  retention, optional offsite (rclone) + dead-man's-switch heartbeat
- ~60s auto-deploy (scripts/auto-update.sh) via systemd timer: push to
  the deploy branch goes live automatically after rebuild + migrate

https://claude.ai/code/session_01QKaJBYpD6DGErWHrRVChkf
Claude committed on May 31, 2026Parent: 6b603a3
5 files changed+1663f635e2f95fa2ba6ee66cdb5590d6e65d5695ae05
5 changed files+166−3
Modified.gitignore+3−0View fileUnifiedSplit
1919# kept (it's the protected-platform pointer); the .gatetest/ directory
2020# is scanner state (memory + reports) that should not be committed.
2121.gatetest/
22
23# standalone box local DB backups
24backups/
Modifieddocker-compose.standalone.yml+29−3View fileUnifiedSplit
1# Standalone single-box deploy: Gluecron + Postgres(pgvector) + Caddy(auto-HTTPS).
1# Standalone single-box deploy: Gluecron + Postgres(pgvector) + Caddy(auto-HTTPS)
2# plus self-healing (autoheal), log rotation, daily backups, and fast auto-deploy.
23#
34# For a DEDICATED VPS where Gluecron owns ports 80/443 (no co-tenant). One
45# command brings up the whole stack with automatic Let's Encrypt certs:
67# docker compose -f docker-compose.standalone.yml up -d --build
78#
89# Variables come from a local .env (POSTGRES_PASSWORD, optional ANTHROPIC_API_KEY)
9# — see scripts/standalone-deploy.sh which generates it. The Caddyfile in the
10# repo already serves gluecron.com + www.gluecron.com -> gluecron:3000.
10# — see scripts/standalone-deploy.sh which generates it and installs the backup
11# + auto-deploy systemd timers. The Caddyfile already serves gluecron.com + www.
12
13x-logging: &default-logging
14 driver: json-file
15 options:
16 max-size: "10m"
17 max-file: "3"
18
1119services:
1220 postgres:
1321 # pgvector image is required: migration 0057 does CREATE EXTENSION vector.
1927 volumes:
2028 - pgdata:/var/lib/postgresql/data
2129 restart: unless-stopped
30 logging: *default-logging
2231 healthcheck:
2332 test: ["CMD-SHELL", "pg_isready -U gluecron -d gluecron"]
2433 interval: 10s
4352 postgres:
4453 condition: service_healthy
4554 restart: unless-stopped
55 logging: *default-logging
4656 healthcheck:
57 # Liveness — autoheal restarts the container if this fails repeatedly.
4758 test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
4859 interval: 30s
4960 timeout: 10s
5061 retries: 3
62 start_period: 60s
5163
5264 caddy:
5365 image: caddy:2-alpine
5466 restart: unless-stopped
67 logging: *default-logging
5568 ports:
5669 - "80:80"
5770 - "443:443"
6275 depends_on:
6376 - gluecron
6477
78 # Self-healing: watches every container that declares a healthcheck and
79 # restarts any that goes unhealthy (a hung process that didn't crash).
80 autoheal:
81 image: willfarrell/autoheal:latest
82 restart: unless-stopped
83 logging: *default-logging
84 environment:
85 - AUTOHEAL_CONTAINER_LABEL=all
86 - AUTOHEAL_INTERVAL=15
87 - AUTOHEAL_START_PERIOD=60
88 volumes:
89 - /var/run/docker.sock:/var/run/docker.sock
90
6591volumes:
6692 pgdata:
6793 git-repos:
Addedscripts/auto-update.sh+29−0View fileUnifiedSplit
1#!/usr/bin/env bash
2#
3# Fast auto-deploy for the standalone box. Installed as a ~60s systemd timer by
4# scripts/standalone-deploy.sh. Polls the deploy branch; when new commits land,
5# it pulls, rebuilds, and runs migrations. Push -> live in ~1-2 min, hands-off.
6#
7# Exits immediately (cheap) when there is nothing new, so a tight interval is fine.
8set -euo pipefail
9
10REPO_DIR="/opt/gluecron"
11BRANCH="claude/site-migration-vercel-XstpK"
12COMPOSE="docker compose -f docker-compose.standalone.yml"
13
14cd "$REPO_DIR"
15git fetch origin "$BRANCH" --quiet
16
17local_sha=$(git rev-parse HEAD)
18remote_sha=$(git rev-parse "origin/$BRANCH")
19[ "$local_sha" = "$remote_sha" ] && exit 0
20
21echo "$(date -Is) deploying $local_sha -> $remote_sha"
22git reset --hard "origin/$BRANCH" # untracked .env / backups are preserved
23
24$COMPOSE up -d --build
25sleep 5
26$COMPOSE exec -T gluecron bun run db:migrate || true
27docker image prune -f >/dev/null 2>&1 || true
28
29echo "$(date -Is) deploy complete: $remote_sha"
Addedscripts/backup.sh+37−0View fileUnifiedSplit
1#!/usr/bin/env bash
2#
3# Daily Postgres backup for the standalone box. Installed as a systemd timer by
4# scripts/standalone-deploy.sh. Keeps 14 days locally; optionally copies offsite
5# and pings a dead-man's-switch monitor.
6#
7# Optional env (set in /opt/gluecron/.env):
8# BACKUP_RCLONE_REMOTE e.g. r2:gluecron-backups (needs rclone configured)
9# HEALTHCHECK_PING_URL e.g. https://hc-ping.com/<uuid> (healthchecks.io)
10set -euo pipefail
11
12REPO_DIR="/opt/gluecron"
13BACKUP_DIR="$REPO_DIR/backups"
14RETAIN_DAYS=14
15COMPOSE="docker compose -f docker-compose.standalone.yml"
16
17cd "$REPO_DIR"
18mkdir -p "$BACKUP_DIR"
19ts=$(date +%Y%m%d-%H%M%S)
20out="$BACKUP_DIR/gluecron-$ts.sql.gz"
21
22$COMPOSE exec -T postgres pg_dump -U gluecron gluecron | gzip > "$out"
23
24# Retention
25find "$BACKUP_DIR" -name 'gluecron-*.sql.gz' -mtime +$RETAIN_DAYS -delete
26
27# Optional offsite copy
28if [ -n "${BACKUP_RCLONE_REMOTE:-}" ] && command -v rclone >/dev/null 2>&1; then
29 rclone copy "$out" "$BACKUP_RCLONE_REMOTE" || echo "WARN: offsite copy failed"
30fi
31
32# Optional dead-man's-switch heartbeat (alerts you if a backup is ever missed)
33if [ -n "${HEALTHCHECK_PING_URL:-}" ]; then
34 curl -fsS -m 10 "$HEALTHCHECK_PING_URL" >/dev/null 2>&1 || true
35fi
36
37echo "$(date -Is) backup written: $out ($(du -h "$out" | cut -f1))"
Modifiedscripts/standalone-deploy.sh+68−0View fileUnifiedSplit
7575sleep 5
7676$COMPOSE exec -T gluecron bun run db:migrate || true
7777
78# 9. Swap (protects a small box from OOM kills)
79if [ "$(swapon --show --noheadings | wc -l)" -eq 0 ]; then
80 echo "-- creating 1G swap file"
81 fallocate -l 1G /swapfile && chmod 600 /swapfile && mkswap /swapfile >/dev/null && swapon /swapfile
82 grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
83fi
84
85# 10. Unattended security updates
86echo "-- enabling unattended-upgrades"
87DEBIAN_FRONTEND=noninteractive apt-get install -y unattended-upgrades >/dev/null 2>&1 || true
88dpkg-reconfigure -f noninteractive unattended-upgrades >/dev/null 2>&1 || true
89
90# 11. Self-managing systemd timers: fast auto-deploy (~60s) + daily backup
91chmod +x scripts/auto-update.sh scripts/backup.sh
92
93cat > /etc/systemd/system/gluecron-update.service <<EOF
94[Unit]
95Description=Gluecron auto-deploy (pull + rebuild on new commits)
96After=docker.service
97Requires=docker.service
98[Service]
99Type=oneshot
100WorkingDirectory=$REPO_DIR
101EnvironmentFile=-$REPO_DIR/.env
102ExecStart=$REPO_DIR/scripts/auto-update.sh
103EOF
104
105cat > /etc/systemd/system/gluecron-update.timer <<EOF
106[Unit]
107Description=Run Gluecron auto-deploy every minute
108[Timer]
109OnBootSec=2min
110OnUnitActiveSec=60s
111[Install]
112WantedBy=timers.target
113EOF
114
115cat > /etc/systemd/system/gluecron-backup.service <<EOF
116[Unit]
117Description=Gluecron daily Postgres backup
118After=docker.service
119Requires=docker.service
120[Service]
121Type=oneshot
122WorkingDirectory=$REPO_DIR
123EnvironmentFile=-$REPO_DIR/.env
124ExecStart=$REPO_DIR/scripts/backup.sh
125EOF
126
127cat > /etc/systemd/system/gluecron-backup.timer <<EOF
128[Unit]
129Description=Run Gluecron backup daily
130[Timer]
131OnCalendar=daily
132Persistent=true
133[Install]
134WantedBy=timers.target
135EOF
136
137systemctl daemon-reload
138systemctl enable --now gluecron-update.timer gluecron-backup.timer >/dev/null 2>&1 || true
139
78140echo
79141echo "== status =="
80142$COMPOSE ps
143echo "-- timers --"
144systemctl list-timers 'gluecron-*' --no-pager 2>/dev/null || true
145echo
146echo "Self-healing active: container auto-restart, autoheal, log rotation,"
147echo "1G swap, unattended security updates, daily DB backups (backups/), and"
148echo "auto-deploy (~60s) from the deploy branch."
81149echo
82150echo "Done. Now point gluecron.com + www.gluecron.com DNS at THIS box's IP"
83151echo "(Cloudflare, DNS-only / grey cloud). Caddy issues the cert automatically"
84152