gabriel / musehub public
Closed #65 deploy
filed by gabriel human · 8 days ago

v0.2.0rc11 staging deploy — muse CLI, musehub, R2 nuke and re-push

0 Anchors
Blast radius
Churn 30d
0 Proposals

Overview

Full rc11 deploy to staging. Two parallel tracks: muse CLI tarball publish and musehub Docker image. After the app is healthy on rc11, we nuke the Cloudflare R2 bucket (tens of thousands of stale objects from bench seeds and old test repos), re-create only the canonical repos, re-push from local, and run the mpack index backfill.

Ordering rationale: musehub rc11 must land before the R2 nuke. The old container code may have object-read paths that depend on storage layout assumptions that changed in rc11. Getting the new code live first means the re-push and backfill happen against the correct serving logic.


Phase 1 — Deploy muse CLI v0.2.0rc11

Goal: publish the rc11 tarball to staging so muse on staging resolves to 0.2.0rc11.

Pre-flight: confirm local muse is at rc11.

python3 -c "import re, pathlib; t = pathlib.Path('~/ecosystem/muse/pyproject.toml').read_text(); m = re.search(r'version.*=.*\"([^\"]+)\"', t); print(m.group(1))"
# expected: 0.2.0rc11

Publish:

bash ~/ecosystem/musehub/deploy/publish_muse_release.sh

What it does: builds sdist from ~/ecosystem/muse, uploads to s3://musehub-releases/muse-0.2.0rc11.tar.gz, SSMs to the staging instance to copy it into /data/releases/, verifies HTTP 200 at https://staging.musehub.ai/releases/muse-0.2.0rc11.tar.gz, prunes older tarballs (keeps 3).

Smoke test (18 checks against the published tarball):

bash ~/ecosystem/musehub/deploy/smoke_muse.sh

Installs the tarball in a throwaway venv, runs: version, init, add, commit, status, log, read, ls-files, branch (create/list/delete), diff, checkout, tag, verify. All 18 must pass. Exit 0 = good, exit 1 = failure in checks, exit 2 = setup error.

Done when: smoke_muse.sh exits 0.


Phase 2 — Deploy musehub rc11 (Docker blue-green)

Goal: get the rc11 musehub image running on staging, Alembic migrations applied.

bash ~/ecosystem/musehub/deploy/push.sh staging

What it does: builds a linux/amd64 Docker image, tags it with <commit>-<timestamp>, pushes to ECR (musehub/musehub), SSMs to the staging instance (i-07547cd20bee2dea5) to pull the image and blue-green swap. Alembic upgrade head runs automatically on container start — migrations 0058 through 0070 must apply cleanly.

Verify:

curl -sk https://staging.musehub.ai/healthz
# expected: {"status": "ok", ...}

Key changes landing in rc11 that affect staging data:

  • Migration 0068: adds storage_uri column to musehub_commits and musehub_snapshots
  • Migration 0069: proposal comment provenance
  • Migration 0070: rename merge_strategy aliases to canonical names (state_overlayoverlay, etc.)
  • Architecture: commits and snapshots are now DB-canonical only — blobs exclusively in R2 via mpack byte-range indexing (musehub_mpack_index)
  • compute_snapshot_id uses typed-object formula (canonical sha256:-prefixed IDs)

Done when: healthz returns 200 and muse -C ~/ecosystem/muse ls-remote staging --json succeeds without 401.


Phase 3 — Nuke R2 and delete stale repos

Goal: wipe tens of thousands of stale R2 objects (bench seeds, test pushes, old wire protocol objects) by deleting all repos, then clearing R2.

3a — Delete all staging repos

These repos are safe to delete. The canonical repos (muse, musehub, agentception, muse-zsh, identity) will be re-created and re-pushed in Phase 4.

# Repos to delete (adjust list after reviewing hub repo list):
# bench-seed-xs, bench-seed-s, bench-seed-m, bench-seed-l, bench-seed-xl
# wire-hello, diag-pull*, 5i18xxyrgunh, 9clq6y68x4pr, ckxsge2f8yco
# muse, musehub, muse-zsh, identity (will be re-created)
muse -C ~/ecosystem/musehub hub repo list --hub https://staging.musehub.ai --json   | python3 -c "import sys,json; [print(r['slug']) for r in json.load(sys.stdin)['repos']]"

# Delete each repo:
muse -C ~/ecosystem/musehub hub repo delete --hub https://staging.musehub.ai --json
# (run from within each repo's context, or use the slug-based API)

Deleting repos cascades in the DB: removes musehub_objects, musehub_object_refs, musehub_commits, musehub_snapshots, musehub_branches, and all intel/index rows for those repos.

3b — Nuke Cloudflare R2 bucket

With all repos deleted, every R2 object is now orphaned. Wipe the bucket:

# Via AWS CLI (R2 exposes an S3-compatible endpoint):
aws s3 rm s3://muse-objects/ --recursive --profile musehub-infra
# or via the Cloudflare dashboard: R2 → muse-objects → Empty bucket

Verify the bucket is empty:

aws s3 ls s3://muse-objects/ --recursive --profile musehub-infra | wc -l
# expected: 0

Done when: bucket is empty and DB has no repo rows.


Phase 4 — Re-create canonical repos and push

Goal: create fresh repo records on staging and push local dev + main from each canonical repo.

# Create repos (run from each repo directory):
muse -C ~/ecosystem/muse     hub repo create --hub https://staging.musehub.ai --name muse      --visibility public --json
muse -C ~/ecosystem/musehub  hub repo create --hub https://staging.musehub.ai --name musehub   --visibility public --json
muse -C ~/ecosystem/agentception hub repo create --hub https://staging.musehub.ai --name agentception --visibility public --json
muse -C ~/ecosystem/muse-zsh hub repo create --hub https://staging.musehub.ai --name muse-zsh  --visibility public --json

# Verify remotes are configured:
muse -C ~/ecosystem/muse     remote --json
muse -C ~/ecosystem/musehub  remote --json

# Push main + dev for each repo:
muse -C ~/ecosystem/muse     push staging main
muse -C ~/ecosystem/muse     push staging dev
muse -C ~/ecosystem/musehub  push staging main
muse -C ~/ecosystem/musehub  push staging dev
muse -C ~/ecosystem/agentception push staging main
muse -C ~/ecosystem/agentception push staging dev
muse -C ~/ecosystem/muse-zsh push staging main
muse -C ~/ecosystem/muse-zsh push staging dev

Each push will create fresh mpacks in R2, populate musehub_mpack_index rows, and create musehub_commits/musehub_snapshots DB rows for all history.

Done when: all pushes exit 0 and muse ls-remote staging --json shows main and dev for each repo.


Phase 5 — Mpack index backfill

Goal: ensure all mpack objects have their byte-range entries in musehub_mpack_index so wire fetch can serve individual objects efficiently.

docker exec musehub-blue python3 /app/deploy/enqueue_mpack_index_backfill.py --dry-run
# Review: how many unindexed mpacks?

docker exec musehub-blue python3 /app/deploy/enqueue_mpack_index_backfill.py
# Processes one mpack at a time, polls until each job completes before moving to next

If the dry-run shows 0 unindexed mpacks (likely after a clean re-push with rc11), this phase is a no-op.

Object store backfill (commits/snapshots — run only if storage_uri IS NULL rows exist):

docker exec musehub-blue python3 /app/deploy/backfill_object_store.py --dry-run
# If > 0 rows need backfilling, run via the loop in batches of 50:
bash ~/ecosystem/musehub/deploy/backfill_loop.sh --batches 10 --limit 50

After a clean re-push, storage_uri is vestigial in rc11 (commits/snapshots are DB-canonical, not S3-served). This backfill may be a no-op — confirm with dry-run first.

Done when: enqueue_mpack_index_backfill.py --dry-run reports 0 unindexed mpacks.


Phase 6 — Final smoke and sign-off

# Muse CLI smoke (runs against staging tarball):
bash ~/ecosystem/musehub/deploy/smoke_muse.sh --url https://staging.musehub.ai

# Wire push/pull round-trip (confirm objects flow end-to-end):
muse -C /tmp/smoke-clone clone https://staging.musehub.ai/gabriel/muse --json
muse -C /tmp/smoke-clone log --json -n 3

# Check staging healthz one final time:
curl -sk https://staging.musehub.ai/healthz

Done when: smoke exits 0, clone succeeds, healthz is green.


Rollback

Failure point Rollback
Phase 1 (muse CLI) IMAGE_TAG=<prev> bash deploy/publish_muse_release.sh
Phase 2 (musehub image) IMAGE_TAG=<prev-tag> bash deploy/push.sh staging
Phase 3a (repo delete) Repos are gone — no rollback; continue to Phase 4
Phase 3b (R2 nuke) Objects are gone — no rollback; continue to Phase 4
Phase 4 (push fails) Fix remote config or auth, retry push
Phase 5 (backfill) Idempotent — retry enqueue_mpack_index_backfill.py

Notes

  • Staging instance: i-07547cd20bee2dea5 (us-east-1)
  • Active slot check: cat /opt/musehub/.active-slot on instance
  • SSM must be reachable before Phase 2; if stuck as Pending, reboot the instance and wait for SSM ping
  • R2 bucket name: muse-objects (S3-compatible endpoint via Cloudflare)
  • identity repo: private — recreate after the public repos are verified, confirm key is still registered on staging (muse auth whoami --hub https://staging.musehub.ai)
Activity3
gabriel opened this issue 8 days ago
gabriel 8 days ago

Phase 1 complete

Pre-flight

Muse
➜  muse muse:(code:main) muse log --oneline --max-count 1      
sha256:c5131d76c6eada02939111fda4aa8e51b0c1456b9983727cfd6be101916de14e (HEAD -> main) merge: pull local/dev — resolve trivial _EXT_MAP symbol conflict (identical on both sides)

➜  muse muse:(code:main) muse checkout dev
Switched to branch 'dev'

➜  muse muse:(code:dev) muse log --oneline --max-count 1
sha256:c5131d76c6eada02939111fda4aa8e51b0c1456b9983727cfd6be101916de14e (HEAD -> dev) merge: pull local/dev — resolve trivial _EXT_MAP symbol conflict (identical on both sides)
MuseHub
➜  musehub muse:(code:dev) muse -C ~/ecosystem/musehub log --oneline --max-count 1
sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa (HEAD -> dev) Merge 'fix/assignee-sigil-inline' into 'dev' — proposal: Assignee sigil and name stacking vertically in issue sidebar

➜  musehub muse:(code:dev) muse -C ~/ecosystem/musehub switch main
Switched to branch 'main'

➜  musehub muse:(code:main) muse -C ~/ecosystem/musehub log --oneline --max-count 1
sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa (HEAD -> main) Merge 'fix/assignee-sigil-inline' into 'dev' — proposal: Assignee sigil and name stacking vertically in issue sidebar

Publish

bash ~/ecosystem/musehub/deploy/publish_muse_release.sh
[1/5] Building muse 0.2.0rc11 from /Users/gabriel/ecosystem/muse
* Getting build dependencies for sdist...
* Building sdist...
Successfully built muse-0.2.0rc11.tar.gz
  Built: 1.5M  dist/muse-0.2.0rc11.tar.gz
[2/5] Uploading to s3://musehub-releases/
upload: dist/muse-0.2.0rc11.tar.gz to s3://musehub-releases/muse-0.2.0rc11.tar.gz
[3/5] Pushing to staging (i-07547cd20bee2dea5)
  SSM command: f6c476f3-d6d2-4ba5-9759-8469807b05d7 — polling...
.  ✅ Staging copy succeeded.
[4/5] Cleaning up old releases (keeping 3 newest)
  Deleting s3://musehub-releases/muse-0.2.0rc8.tar.gz
delete: s3://musehub-releases/muse-0.2.0rc8.tar.gz
[5/5] Verifying https://staging.musehub.ai/releases/muse-0.2.0rc11.tar.gz
  ✅ Live at https://staging.musehub.ai/releases/muse-0.2.0rc11.tar.gz

muse 0.2.0rc11 is live. Install with:
  curl -fsSL https://staging.musehub.ai/install.sh | sh

➜  muse muse:(code:dev *) muse -v
muse 0.2.0rc12

➜  muse muse:(code:dev *) /Users/gabriel/.local/bin/muse --version
muse 0.2.0rc11

Smoke test

➜  muse muse:(code:dev *) bash ~/ecosystem/musehub/deploy/smoke_muse.sh --version 0.2.0rc11

[smoke] muse 0.2.0rc11  url=https://staging.musehub.ai

  Downloading https://staging.musehub.ai/releases/muse-0.2.0rc11.tar.gz …
  Downloaded 1.5M
  Installing into throwaway venv …

local CLI checks

  ✅  version                1158ms
  ✅  init                   263ms
  ✅  add_empty              296ms
  ✅  add_file               263ms
  ✅  commit                 1479ms
  ✅  status_clean           269ms
  ✅  log_count              259ms
  ✅  read_message           260ms
  ✅  ls_files               244ms
  ✅  branch_count           281ms
  ✅  branch_current         267ms
  ✅  diff_clean             237ms
  ✅  checkout_b             240ms
  ✅  checkout_main          244ms
  ✅  branch_delete          270ms
  ✅  tag_add                256ms
  ✅  tag_list               243ms
  ✅  verify                 242ms

[smoke] PASSED  18/18  total=9957ms
gabriel 8 days ago

Phase 2 complete

Deploy

➜  musehub muse:(code:main *) bash ~/ecosystem/musehub/deploy/publish_muse_release.sh

[1/5] Building muse 0.2.0rc11 from /Users/gabriel/ecosystem/muse
* Getting build dependencies for sdist...
* Building sdist...
Successfully built muse-0.2.0rc11.tar.gz
  Built: 1.5M  dist/muse-0.2.0rc11.tar.gz
[2/5] Uploading to s3://musehub-releases/
upload: dist/muse-0.2.0rc11.tar.gz to s3://musehub-releases/muse-0.2.0rc11.tar.gz
[3/5] Pushing to staging (i-07547cd20bee2dea5)
  SSM command: 1a5b3522-0654-4f02-81e9-07f8dce8424d — polling...
  ✅ Staging copy succeeded.
[4/5] Cleaning up old releases (keeping 3 newest)
  S3: nothing to remove.
[5/5] Verifying https://staging.musehub.ai/releases/muse-0.2.0rc11.tar.gz
  ✅ Live at https://staging.musehub.ai/releases/muse-0.2.0rc11.tar.gz

muse 0.2.0rc11 is live. Install with:
  curl -fsSL https://staging.musehub.ai/install.sh | sh

Verify

# healthz
➜  musehub muse:(code:main *) curl -sk https://staging.musehub.ai/healthz
{"status":"ok","db":true,"storage":true}%   

# ls-remote
➜  musehub muse:(code:main *) muse -C ~/ecosystem/muse ls-remote staging --json | jq
{
  "muse_version": "0.2.0rc11",
  "schema": 1,
  "exit_code": 0,
  "duration_ms": 541.006,
  "timestamp": "2026-06-07T04:43:23.060Z",
  "warnings": [],
  "status": "ok",
  "error": "",
  "repo_id": "sha256:66e71656ea348c9b9517a16c6dd276b917bb7f6c9e39d110aa980f656a56dfe5",
  "domain": "code",
  "default_branch": "main",
  "branches": {
    "dev": "sha256:f6cd81bc71702f5c1c6890bd39aaba994fe58c75f019d7c03934724fa2739bb4",
    "main": "sha256:f6cd81bc71702f5c1c6890bd39aaba994fe58c75f019d7c03934724fa2739bb4"
  },
  "remote": "staging",
  "url": "https://staging.musehub.ai/gabriel/muse"
}
gabriel 7 days ago

Phase 3 complete

Delete all staging repos

➜  musehub muse:(code:main *) muse -C ~/ecosystem/musehub hub repo list --hub https://staging.musehub.ai --json   | python3 -c "import sys,json; [print(r['slug']) for r in json.load(sys.stdin)['repos']]"
# returns no repos. I have deleted all the repos.

Nuke Cloudflare R2 bucket

aws s3 ls s3://musehub-staging/ \
  --recursive \
  --endpoint-url https://foobar.r2.cloudflarestorage.com \
  --region auto \
  | wc -l

       0

Phase 4 Complete

Re-create canonical repos and push

ls-remote statging on muse repo.

➜  musehub muse:(code:main *) muse -C ~/ecosystem/muse ls-remote staging --json | jq 
{
  "muse_version": "0.2.0rc11",
  "schema": 1,
  "exit_code": 0,
  "duration_ms": 387.811,
  "timestamp": "2026-06-07T07:08:26.884Z",
  "warnings": [],
  "status": "ok",
  "error": "",
  "repo_id": "sha256:200e8689fe34a831289bc1eca17633b2069d595379b7c2f57a158e35d8291bec",
  "domain": "code",
  "default_branch": "main",
  "branches": {
    "main": "sha256:c5131d76c6eada02939111fda4aa8e51b0c1456b9983727cfd6be101916de14e",
    "dev": "sha256:c5131d76c6eada02939111fda4aa8e51b0c1456b9983727cfd6be101916de14e"
  },
  "remote": "staging",
  "url": "https://staging.musehub.ai/gabriel/muse"
}

ls-remote staging on muse-zsh repo.

➜  musehub muse:(code:main *) muse -C ~/ecosystem/muse-zsh ls-remote staging --json | jq
{
  "muse_version": "0.2.0rc11",
  "schema": 1,
  "exit_code": 0,
  "duration_ms": 295.215,
  "timestamp": "2026-06-07T07:08:35.602Z",
  "warnings": [],
  "status": "ok",
  "error": "",
  "repo_id": "sha256:a77120810e5422e6feabfbafdb44da33de6a2433f8e8344c61fe9aa4637be3b6",
  "domain": "code",
  "default_branch": "main",
  "branches": {
    "main": "sha256:438c3579f822425cd827926255214f7e202395ba1f08d6556828d7cda528bb53",
    "dev": "sha256:3c558f3f0539757b51874047f477b633f3966aa9f239acbdf95b93480a167cc9"
  },
  "remote": "staging",
  "url": "https://staging.musehub.ai/gabriel/muse-zsh"
}

ls-remote statging on musehub repo.

➜  musehub muse:(code:main *) muse -C ~/ecosystem/musehub ls-remote staging --json | jq 
{
  "muse_version": "0.2.0rc11",
  "schema": 1,
  "exit_code": 0,
  "duration_ms": 288.785,
  "timestamp": "2026-06-07T07:08:45.390Z",
  "warnings": [],
  "status": "ok",
  "error": "",
  "repo_id": "sha256:feb66707e068612cdb963f05b6b0294c5a3549fb054382efa829fefcb8565cc6",
  "domain": "code",
  "default_branch": "main",
  "branches": {
    "main": "sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa",
    "dev": "sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa"
  },
  "remote": "staging",
  "url": "https://staging.musehub.ai/gabriel/musehub"
}
closed this issue 3 days ago