Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

SELF_HOST.md

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

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