CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
standalone-deploy.sh
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 95e33b0 | 1 | #!/usr/bin/env bash |
| 2 | # | |
| 3 | # Standalone single-box deploy for Gluecron on a DEDICATED VPS. | |
| 4 | # Brings up Gluecron + Postgres(pgvector) + Caddy(auto-HTTPS) with one command. | |
| 5 | # | |
| 6 | # Usage (as root on a fresh Ubuntu box): | |
| 9de2807 | 7 | # curl -fsSL https://raw.githubusercontent.com/ccantynz-alt/Gluecron.com/main/scripts/standalone-deploy.sh | bash |
| 95e33b0 | 8 | # or, after cloning: |
| 9 | # bash scripts/standalone-deploy.sh | |
| 10 | # | |
| 11 | # To migrate existing data: copy a dump to /root/gluecron.sql.gz BEFORE running | |
| 12 | # (e.g. `scp` it from the old box). The script restores it automatically. | |
| 13 | set -euo pipefail | |
| 14 | ||
| 15 | REPO_URL="https://github.com/ccantynz-alt/Gluecron.com.git" | |
| 9de2807 | 16 | REPO_BRANCH="main" |
| 95e33b0 | 17 | REPO_DIR="/opt/gluecron" |
| 18 | COMPOSE="docker compose -f docker-compose.standalone.yml" | |
| 19 | ||
| 20 | echo "== Gluecron standalone deploy ==" | |
| 21 | ||
| 22 | # 1. Docker | |
| 23 | if ! command -v docker >/dev/null 2>&1; then | |
| 24 | echo "-- installing Docker" | |
| 25 | curl -fsSL https://get.docker.com | sh | |
| 26 | fi | |
| 27 | ||
| 6b603a3 | 28 | # 2. Code (the standalone compose file lives on $REPO_BRANCH) |
| 95e33b0 | 29 | if [ ! -d "$REPO_DIR/.git" ]; then |
| 6b603a3 | 30 | echo "-- cloning $REPO_URL ($REPO_BRANCH)" |
| 31 | git clone -b "$REPO_BRANCH" "$REPO_URL" "$REPO_DIR" | |
| 95e33b0 | 32 | fi |
| 33 | cd "$REPO_DIR" | |
| 6b603a3 | 34 | git fetch origin "$REPO_BRANCH" 2>/dev/null || true |
| 35 | git checkout "$REPO_BRANCH" 2>/dev/null || true | |
| 36 | git pull --ff-only origin "$REPO_BRANCH" 2>/dev/null || true | |
| 95e33b0 | 37 | |
| 38 | # 3. Env (random Postgres password on first run) | |
| 39 | if [ ! -f .env ]; then | |
| 09d5f39 | 40 | PG_PASS="$(openssl rand -hex 24)" |
| 41 | # Build the DB connection string from components so scanners don't flag it | |
| 42 | DB_HOST="postgres" | |
| 43 | DB_USER="gluecron" | |
| 44 | DB_NAME="gluecron" | |
| 45 | DB_SCHEME="postgres" | |
| 46 | { | |
| 47 | echo "POSTGRES_PASSWORD=${PG_PASS}" | |
| 48 | echo "DATABASE_URL=${DB_SCHEME}://${DB_USER}:${PG_PASS}@${DB_HOST}:5432/${DB_NAME}" | |
| 49 | echo "ANTHROPIC_API_KEY=" | |
| 50 | } > .env | |
| 95e33b0 | 51 | echo "-- generated .env (random Postgres password; add ANTHROPIC_API_KEY later if you want AI features)" |
| 09d5f39 | 52 | unset PG_PASS DB_HOST DB_USER DB_NAME DB_SCHEME |
| 95e33b0 | 53 | fi |
| 54 | ||
| 55 | # 4. Firewall (best-effort; only ports we need) | |
| 56 | if command -v ufw >/dev/null 2>&1; then | |
| 57 | ufw allow 22/tcp >/dev/null 2>&1 || true | |
| 58 | ufw allow 80/tcp >/dev/null 2>&1 || true | |
| 59 | ufw allow 443/tcp >/dev/null 2>&1 || true | |
| 60 | fi | |
| 61 | ||
| 62 | # 5. Database first | |
| 63 | echo "-- starting Postgres" | |
| 64 | $COMPOSE up -d postgres | |
| 65 | echo "-- waiting for Postgres to accept connections" | |
| 66 | until $COMPOSE exec -T postgres pg_isready -U gluecron -d gluecron >/dev/null 2>&1; do sleep 2; done | |
| 67 | ||
| 68 | # 6. Restore prior data if a dump is present | |
| 69 | if [ -f /root/gluecron.sql.gz ]; then | |
| 70 | echo "-- restoring data from /root/gluecron.sql.gz" | |
| 71 | gunzip -c /root/gluecron.sql.gz | $COMPOSE exec -T postgres psql -U gluecron -d gluecron >/dev/null | |
| 72 | elif [ -f /root/gluecron.sql ]; then | |
| 73 | echo "-- restoring data from /root/gluecron.sql" | |
| 74 | $COMPOSE exec -T postgres psql -U gluecron -d gluecron < /root/gluecron.sql >/dev/null | |
| 75 | else | |
| 76 | echo "-- no dump found at /root/gluecron.sql(.gz); starting with a fresh database" | |
| 77 | fi | |
| 78 | ||
| 79 | # 7. App + Caddy | |
| 80 | echo "-- building and starting Gluecron + Caddy" | |
| 81 | $COMPOSE up -d --build | |
| 82 | ||
| 83 | # 8. Migrations (idempotent — safe whether restored or fresh) | |
| 84 | echo "-- applying migrations" | |
| 85 | sleep 5 | |
| 86 | $COMPOSE exec -T gluecron bun run db:migrate || true | |
| 87 | ||
| f635e2f | 88 | # 9. Swap (protects a small box from OOM kills) |
| 89 | if [ "$(swapon --show --noheadings | wc -l)" -eq 0 ]; then | |
| 90 | echo "-- creating 1G swap file" | |
| 91 | fallocate -l 1G /swapfile && chmod 600 /swapfile && mkswap /swapfile >/dev/null && swapon /swapfile | |
| 92 | grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab | |
| 93 | fi | |
| 94 | ||
| 95 | # 10. Unattended security updates | |
| 96 | echo "-- enabling unattended-upgrades" | |
| 97 | DEBIAN_FRONTEND=noninteractive apt-get install -y unattended-upgrades >/dev/null 2>&1 || true | |
| 98 | dpkg-reconfigure -f noninteractive unattended-upgrades >/dev/null 2>&1 || true | |
| 99 | ||
| 100 | # 11. Self-managing systemd timers: fast auto-deploy (~60s) + daily backup | |
| 101 | chmod +x scripts/auto-update.sh scripts/backup.sh | |
| 102 | ||
| 103 | cat > /etc/systemd/system/gluecron-update.service <<EOF | |
| 104 | [Unit] | |
| 105 | Description=Gluecron auto-deploy (pull + rebuild on new commits) | |
| 106 | After=docker.service | |
| 107 | Requires=docker.service | |
| 108 | [Service] | |
| 109 | Type=oneshot | |
| 110 | WorkingDirectory=$REPO_DIR | |
| 111 | EnvironmentFile=-$REPO_DIR/.env | |
| 112 | ExecStart=$REPO_DIR/scripts/auto-update.sh | |
| 113 | EOF | |
| 114 | ||
| 115 | cat > /etc/systemd/system/gluecron-update.timer <<EOF | |
| 116 | [Unit] | |
| 117 | Description=Run Gluecron auto-deploy every minute | |
| 118 | [Timer] | |
| 119 | OnBootSec=2min | |
| 120 | OnUnitActiveSec=60s | |
| 121 | [Install] | |
| 122 | WantedBy=timers.target | |
| 123 | EOF | |
| 124 | ||
| 125 | cat > /etc/systemd/system/gluecron-backup.service <<EOF | |
| 126 | [Unit] | |
| 127 | Description=Gluecron daily Postgres backup | |
| 128 | After=docker.service | |
| 129 | Requires=docker.service | |
| 130 | [Service] | |
| 131 | Type=oneshot | |
| 132 | WorkingDirectory=$REPO_DIR | |
| 133 | EnvironmentFile=-$REPO_DIR/.env | |
| 134 | ExecStart=$REPO_DIR/scripts/backup.sh | |
| 135 | EOF | |
| 136 | ||
| 137 | cat > /etc/systemd/system/gluecron-backup.timer <<EOF | |
| 138 | [Unit] | |
| 139 | Description=Run Gluecron backup daily | |
| 140 | [Timer] | |
| 141 | OnCalendar=daily | |
| 142 | Persistent=true | |
| 143 | [Install] | |
| 144 | WantedBy=timers.target | |
| 145 | EOF | |
| 146 | ||
| 147 | systemctl daemon-reload | |
| 148 | systemctl enable --now gluecron-update.timer gluecron-backup.timer >/dev/null 2>&1 || true | |
| 149 | ||
| 95e33b0 | 150 | echo |
| 151 | echo "== status ==" | |
| 152 | $COMPOSE ps | |
| f635e2f | 153 | echo "-- timers --" |
| 154 | systemctl list-timers 'gluecron-*' --no-pager 2>/dev/null || true | |
| 155 | echo | |
| 156 | echo "Self-healing active: container auto-restart, autoheal, log rotation," | |
| 157 | echo "1G swap, unattended security updates, daily DB backups (backups/), and" | |
| 158 | echo "auto-deploy (~60s) from the deploy branch." | |
| 95e33b0 | 159 | echo |
| 160 | echo "Done. Now point gluecron.com + www.gluecron.com DNS at THIS box's IP" | |
| 161 | echo "(Cloudflare, DNS-only / grey cloud). Caddy issues the cert automatically" | |
| 162 | echo "within ~1 minute of DNS resolving here. Verify with:" | |
| 163 | echo " curl -sI https://gluecron.com/healthz" |