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
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
SELF_HOST.md5.9 KB · 174 lines
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.