CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | # Gluecron self-host — eating our own dog food
> BLOCK W. The moment Gluecron's own source code stops living on GitHub
> and starts living on Gluecron itself.
## Why
Today every push to `ccantynz-alt/Gluecron.com` on GitHub fires the
`hetzner-deploy.yml` workflow, which SSHes into the box and runs a deploy
script. Every red Action means a manual SSH deploy. Every AI safeguard
(auto-merge, AI review, gate enforcement) only applies to **Gluecron-
hosted repos** — so the platform's own source has been the one repo where
none of the safety net runs.
After this block lands and the operator runs the bootstrap, `git push`
fires the deploy directly via Gluecron's own post-receive hook in ~25
seconds. No GitHub Actions in the middle, no SSH retries, no missing AI
safeguards.
The pitch to customers writes itself: *"Gluecron deploys Gluecron with
Gluecron."*
## Pre-flight (do this BEFORE the bootstrap)
1. **You have site-admin access.** Confirm `/admin/ops` renders for your
account.
2. **You have root SSH to the production box.** The bootstrap must run on
the box where `/opt/gluecron` lives, because it writes to
`$GIT_REPOS_PATH` and to `/opt/gluecron/repos/<owner>/<repo>.git/hooks/`.
3. **`DATABASE_URL` points at the live Neon DB.** Source
`/etc/gluecron.env` before running the script: `set -a && source
/etc/gluecron.env && set +a`.
4. **`git` is on PATH and the box can reach `github.com`.** The bootstrap
clones the GitHub source over HTTPS before push-mirroring it into the
local bare repo.
5. **Disk: ~200 MB free** for the temp mirror clone + the bare repo
itself.
6. **No SSH key required from your laptop yet.** Gluecron's git is HTTPS-
only for now; cutover is `https://gluecron.com/<owner>/<repo>.git`.
## 1. Run the bootstrap
On the box, as root:
```bash
cd /opt/gluecron
set -a; source /etc/gluecron.env; set +a
bun run scripts/self-host-bootstrap.ts
```
Optional flags:
```bash
bun run scripts/self-host-bootstrap.ts \
--owner=ccantynz \
--name=Gluecron.com \
--source=https://github.com/ccantynz-alt/Gluecron.com.git \
--dry-run # print what would happen, change nothing
```
What happens (every step prints v/x/!):
1. Looks up the operator — first row in `site_admins`, falling back to
the oldest user (the bootstrap admin).
2. INSERTs `repositories(name='Gluecron.com', ownerId=<operator>,
isPrivate=false, defaultBranch='main', diskPath=<computed>)`. Skips
if a row already exists.
3. `git init --bare /opt/gluecron/repos/ccantynz/Gluecron.com.git` —
skips if the bare repo already exists.
4. `git clone --mirror` the GitHub source into a temp dir, then `git
push --mirror` into the bare repo. Every branch, tag, and commit is
transferred.
5. Writes `hooks/post-receive` on the bare repo: a one-line bash script
that invokes `scripts/self-deploy.sh` when ref is `refs/heads/main`.
6. Prints cutover instructions.
The script is idempotent — re-run anytime. It only fails if a step
truly cannot proceed (no users in DB, mirror push rejected, etc.).
## 2. Cutover
### On your laptop
```bash
cd ~/code/Gluecron.com
git remote set-url origin https://gluecron.com/ccantynz/Gluecron.com.git
```
### On the production box
```bash
cd /opt/gluecron
git remote set-url origin https://gluecron.com/ccantynz/Gluecron.com.git
```
### Flip the self-host env var
Add to `/etc/gluecron.env`:
```
SELF_HOST_REPO=ccantynz/Gluecron.com
```
Then reload systemd so the live process picks it up:
```bash
systemctl restart gluecron
```
## 3. Verify
1. Open `/admin/self-host` in the browser. All three status pills should
be green: **Mirrored**, **Installed**, **Set**.
2. From your laptop, push an empty commit:
```bash
git commit --allow-empty -m "self-host smoke"
git push
```
3. Watch `/admin/deploys` — a new row appears with `source='self-deploy'`
and streams through `git-pull → bun-install → db-migrate → build →
restart-service → healthz → full-smoke`.
4. Tail the log on the box: `tail -f /var/log/gluecron-self-deploy.log`.
Total wall-clock: push to live ≈ 20–30 seconds.
## 4. Rollback plan
If a self-deploy breaks the site:
- **Automatic:** `scripts/self-deploy.sh` runs `post-deploy-smoke.ts`
after every restart. On failure it `git reset --hard <PREV_SHA>` and
restarts. The previous-good SHA is captured *before* `git pull`, so
rollback is reflog-safe.
- **Manual fallback to GitHub:** change the remote back and re-fire the
old workflow.
```bash
# on laptop AND on the box
git remote set-url origin https://github.com/ccantynz-alt/Gluecron.com.git
```
The GitHub mirror remains the canonical history for at least 30 days
after cutover — we don't deprecate `hetzner-deploy.yml` until the
self-host path has run a full week without intervention.
## 5. Deprecating GitHub Actions
After 7 days of green self-deploys:
1. Set the workflow `on:` trigger to `workflow_dispatch:` only (no push
trigger) so it stays available for emergencies but doesn't fire on
every push.
2. After 30 days, remove the `.github/workflows/hetzner-deploy.yml` file
entirely.
Until then it's a free hot-spare deploy path.
## Operator footnotes
- **Log rotation:** `/var/log/gluecron-self-deploy.log` is appended to
forever. Add a `/etc/logrotate.d/gluecron-self-deploy` entry — TODO.
- **Permissions:** `scripts/self-deploy.sh` runs as root (it has to,
because of `systemctl restart`). The bare-repo post-receive hook
inherits whatever user the HTTP receive-pack runs under (usually root
in our setup); SSH receive-pack will eventually need a `git` user with
passwordless `sudo systemctl restart gluecron`.
- **Self-deploy is gated by `SELF_HOST_REPO`.** Customer repos named
`Gluecron.com` are safe — only the exact `<owner>/<repo>` slug in the
env var fires the local deploy.
- **The optional `.gluecron/workflows/deploy.yml`** runs `self-deploy.sh
--inline` on the in-process workflow runner if you want belt-and-
braces. The post-receive hook is the primary path.
|