|
| 1 | +# Releasing SafeCare |
| 2 | + |
| 3 | +This is the end-to-end runbook for cutting a SafeCare release. There are three |
| 4 | +independent artifacts, and you can ship any subset: |
| 5 | + |
| 6 | +1. **Docker images** (`ghcr.io/jasontitus/safecare-*`) + a **GitHub Release** — |
| 7 | + used by PC/VPS deployments and the in-dashboard "System Updates" updater. |
| 8 | + Fully automated by `.github/workflows/release.yml` on a `v*` tag push. |
| 9 | +2. **Raspberry Pi SD-card image** (`safecare-vX.Y.Z.img.xz`) — the flashable |
| 10 | + appliance image linked from `safecare.app/download`. Built and uploaded |
| 11 | + manually. |
| 12 | +3. **Website** (`safecare.app`) — Firebase Hosting; the `/download` redirect |
| 13 | + points at the current Pi image in GCS. |
| 14 | + |
| 15 | +Versions `0.4.1`–`0.4.6` were Pi-image-only releases (artifact 2) and did not |
| 16 | +bump the in-app version constant. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## 0. Prerequisites |
| 21 | + |
| 22 | +- Clean `main`, all checks green: `pnpm typecheck && pnpm lint && pnpm test && pnpm build`. |
| 23 | +- For the Pi image: Docker running, ~10 GB free disk, 30–60 min. |
| 24 | +- For the upload/website: `gcloud` authenticated to the `safecare-maps` |
| 25 | + project and `firebase` CLI authenticated. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## 1. Bump the version + changelog (every release that ships code) |
| 30 | + |
| 31 | +The source of truth for in-app update checks is `SAFECARE_VERSION`. |
| 32 | + |
| 33 | +1. Edit `packages/shared/src/constants.ts`: |
| 34 | + ```ts |
| 35 | + export const SAFECARE_VERSION = 'X.Y.Z'; |
| 36 | + ``` |
| 37 | +2. Add a section to `CHANGELOG.md` (newest first) describing the changes, and |
| 38 | + add the matching link reference at the bottom of the file. |
| 39 | +3. Commit to `main`: |
| 40 | + ```bash |
| 41 | + pnpm --filter @safecare/shared build # recompile the constant |
| 42 | + git add packages/shared/src/constants.ts CHANGELOG.md |
| 43 | + git commit -m "Release vX.Y.Z: bump version + changelog" |
| 44 | + git push origin main |
| 45 | + ``` |
| 46 | + |
| 47 | +### Semver guidance |
| 48 | + |
| 49 | +- **Patch** (`0.4.x`): bug fixes, reliability, Pi-image-only fixes. |
| 50 | +- **Minor** (`0.x.0`): new features, backwards-compatible. |
| 51 | +- **Major**: breaking changes to the data model, API, or deployment. |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +## 2. Docker images + GitHub Release (tag push) |
| 56 | + |
| 57 | +Pushing a `v*` tag triggers `.github/workflows/release.yml`, which builds the |
| 58 | +backend/dashboard/pwa images for `linux/arm64,linux/amd64`, pushes them to GHCR |
| 59 | +tagged with the version and `latest`, and creates a GitHub Release with |
| 60 | +auto-generated notes. |
| 61 | + |
| 62 | +```bash |
| 63 | +git tag -a vX.Y.Z <commit> -m "SafeCare vX.Y.Z" # <commit> = the main commit to release |
| 64 | +git push origin vX.Y.Z |
| 65 | +``` |
| 66 | + |
| 67 | +Verify: **Actions** tab shows the release workflow green, and |
| 68 | +`ghcr.io/jasontitus/safecare-backend:X.Y.Z` exists. Deployed dashboards will now |
| 69 | +report the update under **Settings > System Updates** (the checker compares |
| 70 | +`SAFECARE_VERSION` against the latest GitHub Release). |
| 71 | + |
| 72 | +> Note: some managed/CI git proxies block tag pushes (HTTP 403) even when branch |
| 73 | +> pushes succeed. If so, push the tag from a normal clone/workstation. |
| 74 | +
|
| 75 | +--- |
| 76 | + |
| 77 | +## 3. Raspberry Pi image (optional per release) |
| 78 | + |
| 79 | +Build the flashable appliance image with pi-gen in Docker: |
| 80 | + |
| 81 | +```bash |
| 82 | +./scripts/rpi/build/build-image.sh safecare # or: rideshare | full |
| 83 | +# output: scripts/rpi/build/output/safecare-<date>.img.xz |
| 84 | +``` |
| 85 | + |
| 86 | +Rename to the release convention (`safecare-vX.Y.Z.img.xz`) so the public |
| 87 | +download URL is versioned: |
| 88 | + |
| 89 | +```bash |
| 90 | +mv scripts/rpi/build/output/safecare-<date>.img.xz \ |
| 91 | + scripts/rpi/build/output/safecare-vX.Y.Z.img.xz |
| 92 | +``` |
| 93 | + |
| 94 | +(Optional) flash + smoke-test before publishing: `./scripts/rpi/build/test-image.sh`. |
| 95 | + |
| 96 | +--- |
| 97 | + |
| 98 | +## 4. Upload the image + publish the website redirect |
| 99 | + |
| 100 | +`upload-image.sh` uploads to the `safecare-maps-osrm` GCS bucket, makes the |
| 101 | +object public, and rewrites the `/download` redirect in |
| 102 | +`infra/prebuilt/hosting/firebase.json` to point at the new image: |
| 103 | + |
| 104 | +```bash |
| 105 | +./scripts/rpi/build/upload-image.sh scripts/rpi/build/output/safecare-vX.Y.Z.img.xz |
| 106 | +``` |
| 107 | + |
| 108 | +Then deploy the hosting change so `safecare.app/download` serves the new image: |
| 109 | + |
| 110 | +```bash |
| 111 | +cd infra/prebuilt/hosting && firebase deploy --only hosting |
| 112 | +``` |
| 113 | + |
| 114 | +Commit the `firebase.json` change so the repo reflects the live redirect: |
| 115 | + |
| 116 | +```bash |
| 117 | +git add infra/prebuilt/hosting/firebase.json |
| 118 | +git commit -m "Point /download redirect at safecare-vX.Y.Z.img.xz" |
| 119 | +git push origin main |
| 120 | +``` |
| 121 | + |
| 122 | +> Do not update the `/download` redirect to a `vX.Y.Z` image before the image is |
| 123 | +> actually uploaded — it would 404 the public download link. `upload-image.sh` |
| 124 | +> updates the redirect only after a successful upload. |
| 125 | +
|
| 126 | +--- |
| 127 | + |
| 128 | +## Release checklist |
| 129 | + |
| 130 | +- [ ] `main` green (`typecheck`, `lint`, `test`, `build`) |
| 131 | +- [ ] `SAFECARE_VERSION` bumped |
| 132 | +- [ ] `CHANGELOG.md` entry + link reference added |
| 133 | +- [ ] version/changelog committed and pushed to `main` |
| 134 | +- [ ] `vX.Y.Z` tag pushed → release workflow green, GHCR images published |
| 135 | +- [ ] (if shipping Pi image) image built, renamed `safecare-vX.Y.Z.img.xz` |
| 136 | +- [ ] (if shipping Pi image) `upload-image.sh` run, `firebase deploy` done |
| 137 | +- [ ] (if shipping Pi image) `firebase.json` redirect committed to `main` |
| 138 | +- [ ] `safecare.app/download` serves the new image |
0 commit comments