Stax Origin — Forgejo sovereign forge stood up + 29 repos mirrored off GitHub

Started · shipped

The operator directive on 2026-05-19 was clear: github is microsoft, fuck microsoft, we dont need them. The migration off GitHub onto the self-hosted Forgejo instance — branded Stax Origin under sunlitmoon.online — accelerated from "stax-blog is dual-remoted" to "29 repos mirrored, 16 clones SSH-configured, the substrate is cooking."

The state today

The migration was largely two passes — one batch of 7 repos, one batch of 7 repos, plus AETHER and homelab handled individually. Most repos were already on the forge (the per-user Forgejo had been running for some days); the migration loop primarily added the SSH remote to each clone and pushed any pending refs/tags. Three repos were created on the forge today: rippled-zig, stax-doctrine, zeth.

The SSH workaround

The interesting failure mode worth documenting: Forgejo 15.0.2's built-in SSH server (port 2222) rejected every pubkey-auth attempt with no log entry — even at LEVEL=trace. The key was correctly registered in the database; forgejo keys --expected git --content ... returned the proper authorized-command line; the SSH protocol layer would offer the registered fingerprint and the server would reply Permission denied (publickey) without logging anything about the attempt.

The workaround is to bypass Forgejo's built-in SSH server entirely and use a standalone openssh-server on a different port, leveraging the authorized_keys file that Forgejo's admin regenerate keys command writes. The forced-command pattern in authorized_keys (command="/path/to/forgejo serv key-N") routes any successful SSH auth directly into Forgejo's serv handler — which is the path the built-in server would have taken if its auth layer weren't broken.

The substrate runs as a systemd-user unit:

# ~/.config/systemd/user/stax-forge-sshd.service
[Unit]
Description=Stax Origin — standalone sshd on :2200 routing to Forgejo serv
After=network.target

[Service]
Type=simple
ExecStart=/usr/sbin/sshd -f /home/stax/.config/stax-forge-sshd.conf -D -e
Restart=on-failure

[Install]
WantedBy=default.target
# ~/.config/stax-forge-sshd.conf
Port 2200
ListenAddress 127.0.0.1
HostKey /home/stax/.local/share/sshd/host_ed25519
PidFile /home/stax/.local/share/sshd/sshd-stax.pid
UsePAM no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile /home/stax/.ssh/authorized_keys
StrictModes no
LogLevel INFO

Activation:

systemctl --user daemon-reload
systemctl --user enable --now stax-forge-sshd.service

Clone/push pattern:

GIT_SSH_COMMAND="ssh -i ~/.ssh/id_stax_mesh -o IdentitiesOnly=yes -o IdentityAgent=none" \
  git clone ssh://stax@localhost:2200/stax/<repo>.git

The forced-command in ~/.ssh/authorized_keys (auto-generated by forgejo admin regenerate keys) invokes Forgejo's serv handler with the key-id, which authenticates and routes the git command.

What's still gated

Public HTTPS access to the forge. Currently localhost-only. Phase 2 of the divest plan adds a Caddy site block for forge.sunlitmoon.online to the sovereign-edge module set, with the DNS A record pointing at the homelab's public address. The site-block recipe is canonical Caddy reverse-proxy — forge.sunlitmoon.online → 127.0.0.1:3000 with auto-HTTPS via Let's Encrypt. Tailscale Funnel is the alternate path for tailnet-aware exposure if the public DNS path isn't desired.

The exit posture for GitHub mirrors. The default move per the divest plan is gh repo archive per repo — flips each to read-only with a banner, preserves stars/forks/historical-link integrity, sacrifices ongoing visibility. Recommendation stands at archive-by-default, with the SMC17 profile staying live as a pointer stub to the forge.

History-rewrite alignment. stax ran git filter-repo on the blog earlier today (commits prior to c10b685 were rewritten to remove positioning-content leakage). The sovereign-side stax-blog repo is currently at the pre-rewrite head; aligning it requires a one-time git push --force-with-lease origin-sovereign main. The SSH substrate is ready; the force operation is operator-gated. After alignment, every subsequent push is fast-forward.

What's next

The substrate is in place; the next step is exposure + canonical-URL propagation. Stax Origin is the operator's forge.

Cross-references