▍ humdrum codex / custard v0.3.0
license AGPL-3.0
7.7 KB raw

Migrating GitHub → Soft Serve + custard

Move a repo off GitHub onto a self-hosted Soft Serve + custard forge, including deployment. Replace your-host / you/tap / <server> with your values. See also MANUAL.md.

Step 1 — move the git repo (every migration)

# 1. create it on Soft (or let it autocreate on first push)
ssh soft repo create <name> -d "what it is"

# 2. point the local clone at Soft
git remote set-url origin soft:<name>          # full move, or:
git remote add soft soft:<name>                # keep GitHub as a backup mirror

# 3. push everything (history, branches, tags)
git push origin --all && git push origin --tags

# 4. set visibility (public repos show on the forge)
ssh soft repo private <name> true|false

Remote naming — origin is always the primary. Decide which forge is primary, then name accordingly (see DevFlow decisions/0003; custard's release verb pushes with git push origin, so the release target must be origin):

  • Soft Serve primary (full move) → Soft is origin, GitHub (if kept) becomes github: git remote set-url origin soft:<name> then git remote rename origin github first if you're demoting an existing GitHub origin. The clone URL is soft:<name> — the soft: is the SSH host alias, not the remote name.
  • GitHub stays primary (Soft as a backup mirror) → leave GitHub as origin, add Soft as soft: git remote add soft soft:<name>. Push to the mirror explicitly (git push soft …); custard release stays pointed at the GitHub origin.

Step 2 — drop GitHub-only cruft (full self-host)

git rm -r .github .goreleaser.yaml && git commit -m "drop GitHub CI/release" && git push

Step 3 — issues → Backlog.md

GitHub Issues don't transfer. Use Backlog.md: tasks live in backlog/tasks/ and render on /r/<name>/issues. Recreate open issues by hand.


Deployment, by type

A. Static site / GitHub Pages → your server

Pages goes away with GitHub. Serve the built site from your server with Caddy:

  1. Build locally (Hugo/Jekyll/Astro/HTML) → an output dir (public/, dist/, …).
  2. Add a Caddy site block for the domain → file_server over the output dir.
  3. Point the domain's DNS at the server; Caddy issues TLS.
  4. Optional — gate through custard with rsync in .custard.yaml:
    ci: ["npm run build"]
    deploy:
      preview: "rsync -a ./public/ <server>:/var/www/<site>-preview/"
      promote: "rsync -a ./public/ <server>:/var/www/<site>/"
    
    Then custard preview (build + stage) → custard promote (go live).

custard is a git forge, not a static host — Caddy serves the files; custard just runs the build/deploy if you want the check+gate flow.

B. Vercel app — stay on Vercel, point at the Soft repo

Vercel's git integration supports only GitHub/GitLab/Bitbucket, so a Soft-hosted repo can't auto-deploy via Vercel's hook. Keep Vercel as the host, deploy from the CLI:

  1. vercel link the repo to the existing Vercel project (keeps the prod URL), then note the orgId + projectId it writes to .vercel/project.json.
  2. In Vercel's dashboard, disconnect the GitHub integration (stop it deploying the old repo).
  3. .custard.yamlpin the project with env IDs. custard deploys a clean git archive HEAD export, so .vercel/project.json (gitignored) is not present; without the IDs the CLI creates a stray new project. The IDs are identifiers, not secrets — safe to commit. Auth still comes from your local vercel login.
    ci: ["pnpm test", "pnpm build"]
    deploy:
      preview: VERCEL_ORG_ID=team_xxx VERCEL_PROJECT_ID=prj_xxx vercel deploy --yes
      promote: VERCEL_ORG_ID=team_xxx VERCEL_PROJECT_ID=prj_xxx vercel promote {{url}} --yes
    
  4. Push to Soft → custard preview (build HEAD, run ci, preview URL) → custard promote (same build → prod).

You keep Vercel hosting + URLs; you trade Vercel's auto-PR previews + GitHub checks for on-demand custard preview + .custard.yaml ci.

C. CLI / TUI → Homebrew

# .custard.yaml
brew: { enabled: true, package: "." }   # or ./cmd/<name>
ssh soft repo webhook create <name> https://your-host/hooks/release \
  -e branch_tag_create -c json -s "$WEBHOOK_SECRET"
custard release v0.1.0
# install: brew tap you/tap https://your-host/git/homebrew-tap.git && brew install <name>

Replaces GitHub Releases + a GitHub-hosted tap.

D. Library / plain code

Step 1 is the whole migration — browsable on the forge, tag versions as usual.


What you give up off GitHub → the replacement

GitHub feature On Soft + custard
Pull requests / review Push-based + custard check/preview gate; branches if needed
GitHub Actions CI .custard.yaml ci via custard check/preview
Issues Backlog.md (backlog/tasks/, shown on the forge)
Releases + assets custard /dl tarballs + the tap
Pages Caddy static hosting on your server
Vercel/Netlify git auto-deploy custard preview/promote (CLI)
Dependabot / scanning none (manual)

Keep GitHub as a mirror (optional)

git remote add github git@github.com:you/<name>.git
git push github --mirror      # occasionally

(Then you're primary-on-Soft, not fully off GitHub.)


Switching a repo from git tags to jj bookmarks

If you move a repo's workflow to jujutsu, you lose the ability to create annotated git tags — which is how custard's Homebrew release pipeline normally triggers. Bookmark mode lets jj repos publish releases from release/vX.Y.Z bookmarks (git branches) instead.

This is an intentional, one-way flip. Do not switch back and forth; the two modes never mix.

The config change

In .custard.yaml, set brew.source: bookmark:

brew:
  enabled: true
  package: "."
  source: bookmark
  # bookmark_prefix: release/  # default; omit unless you need a different prefix

Commit and push. After the flip, the tag-based trigger stops firing — future tag pushes are ignored for release purposes. Releases come only from release/* bookmark pushes going forward.

Version continuity and the downgrade guard

Past tag-based releases already published to the tap are unaffected; users with those versions installed can still brew upgrade once the first bookmark release lands.

The downgrade guard is bookmark-scoped: it compares the incoming bookmark version only against the highest already-published release bookmark (i.e. other release/vX.Y.Z branches). It does not consult the old git tags at all. At the moment you flip, there are zero release bookmarks, so the guard imposes no floor — it would accept any semver version as the first bookmark release.

The real reason your first bookmark release must be >= your last published tag is Homebrew upgrade continuity: existing tap users have that tag's version installed, and brew upgrade only moves them forward if the new formula version is strictly higher. That is a convention you must honour manually, not something the guard enforces. If your last tag was v1.3.0, the first release/vX.Y.Z bookmark must be v1.3.0 or higher — otherwise existing users would never pick up the new release via brew upgrade.

Plan the cutover version accordingly — bumping the patch is fine: v1.3.1 works.

Day-to-day workflow after the flip

See JUJUTSU.md for the full jj usage guide, including how each operation appears in custard and the per-release push workflow.