Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/release-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Release Deployment Bundle

on:
push:
tags: ["bundle/v*"]

permissions:
contents: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-24.04
environment: bundle-release
timeout-minutes: 180
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Validate tag and release metadata
run: |
git fetch --no-tags origin main:refs/remotes/origin/main
scripts/validate_bundle_release_ci.sh "$GITHUB_REF_NAME" "$GITHUB_SHA" origin/main notes.md
echo "VERSION=${GITHUB_REF_NAME#bundle/v}" >> "$GITHUB_ENV"

- name: Install bundle build tools
run: |
sudo apt-get update
sudo apt-get install -y jq zstd python3-jsonschema
docker compose version
df -h /

- uses: arduino/setup-arduino-cli@v2

- name: Run release metadata and deployment script tests
run: |
python3 -m unittest discover -s scripts/tests -p 'test_*.py' -v
python3 -m unittest discover -s tileserver/tests -p 'test_*.py' -v
deploy/scripts/tests/run-tests.sh
bash -n scripts/build-bundle.sh scripts/release.sh scripts/validate_bundle_release_ci.sh scripts/publish_bundle_release.sh tileserver/download-script.sh
bash -n scripts/lib/*.sh deploy/scripts/*.sh deploy/scripts/lib/*.sh

- name: Download and validate Batangas map data
env:
GH_TOKEN: ${{ github.token }}
run: ./tileserver/download-script.sh

- name: Build deployment bundle
run: ./scripts/build-bundle.sh --low-disk

- name: Validate release assets
run: |
ARCHIVE="dist/sapot-bundle-v$VERSION.tar.zst"
python3 scripts/validate_archive_size.py "$ARCHIVE"
ROOT="sapot-bundle-v$VERSION"
VERIFY_DIR=$(mktemp -d)
tar --use-compress-program=unzstd -xf "$ARCHIVE" -C "$VERIFY_DIR"
scripts/validate_extracted_bundle.py "$VERIFY_DIR/$ROOT" --version "$VERSION" --commit "$GITHUB_SHA"
(cd "$VERIFY_DIR/$ROOT" && sha256sum --check CHECKSUMS.sha256)
rm -rf "$VERIFY_DIR"
(cd dist && sha256sum "sapot-bundle-v$VERSION.tar.zst" > "sapot-bundle-v$VERSION.tar.zst.sha256")

- name: Create immutable-policy reader token
id: policy-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BUNDLE_RELEASE_POLICY_APP_ID }}
private-key: ${{ secrets.BUNDLE_RELEASE_POLICY_PRIVATE_KEY }}
owner: SAPOT-System
repositories: YLP-software
permission-administration: read

- name: Stage and publish verified release assets
env:
GH_TOKEN: ${{ github.token }}
SAPOT_RELEASE_POLICY_TOKEN: ${{ steps.policy-token.outputs.token }}
run: scripts/publish_bundle_release.sh "$GITHUB_REPOSITORY" "$GITHUB_REF_NAME" "$VERSION" "$GITHUB_SHA" notes.md "dist/sapot-bundle-v$VERSION.tar.zst" "dist/sapot-bundle-v$VERSION.tar.zst.sha256"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
logs/*
.worktrees/
node_modules/
__pycache__/
*.py[cod]
docs/superpowers/
reports/
.pnpm-store/
Expand Down
35 changes: 26 additions & 9 deletions VERSIONING.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Versioning Guide

This repo uses git-tag-driven versioning for five independent components: **mobile**, **server**, **admin**, **portal** (captive portal), and **gsm** (GSM-module: `GSM-fastapi` + the production Arduino firmware, versioned together since they implement one wire-protocol contract).
This repo uses git-tag-driven versioning for five application components and one deployment artifact: **mobile**, **server**, **admin**, **portal** (captive portal), **gsm** (GSM-module: `GSM-fastapi` + the production Arduino firmware), and the independent offline deployment **bundle**.

`GSM-module/GSM-API/` and `GSM-trial-code/` are not covered — neither is deployed (see `GSM-module/CLAUDE.md`). `tileserver/` has no source of its own (deploy scripts + `.mbtiles` data only) and isn't versioned.
`GSM-module/GSM-API/` and `GSM-trial-code/` are not covered — neither is deployed (see `GSM-module/CLAUDE.md`). TileServer GL deployment scripts are unversioned, but the reviewed map data is independently identified by immutable `map/v*` release tags.

---

Expand All @@ -15,13 +15,15 @@ This repo uses git-tag-driven versioning for five independent components: **mobi
| Admin | `admin/vX.Y.Z` | `admin/v0.2.0` |
| Captive Portal | `portal/vX.Y.Z` | `portal/v0.2.0` |
| GSM Module | `gsm/vX.Y.Z` | `gsm/v0.2.0` |
| Deployment Bundle | `bundle/vX.Y.Z` | `bundle/v0.0.1` |

**Pre-release suffix:** append `-(alpha|beta|rc).N` — e.g. `mobile/v1.0.0-beta.2`.
A tag with any `-` suffix is published as a GitHub **pre-release**. A tag without one is a full release.

Tags are immutable release identifiers. Never move, delete, or reuse a published
component tag to point at a newer commit. Each component is versioned independently:
a new server release does not require a new mobile, admin, portal, or GSM release.
component tag to point at a newer commit. Every release unit is versioned independently.
A new server release does not require a new bundle release, and a bundle release does
not change any bundled component's version.

---

Expand Down Expand Up @@ -69,19 +71,21 @@ preflight above:

```bash
# From the repo root, on a clean release branch:
./scripts/release.sh <mobile|server|admin|portal|gsm> <X.Y.Z[-(alpha|beta|rc).N]>
./scripts/release.sh <mobile|server|admin|portal|gsm|bundle> <X.Y.Z[-(alpha|beta|rc).N]>

# Examples:
./scripts/release.sh mobile 1.0.0-beta.1
./scripts/release.sh server 0.2.0
./scripts/release.sh admin 0.2.0
./scripts/release.sh portal 0.2.0
./scripts/release.sh gsm 0.2.0-beta.1
./scripts/release.sh bundle 0.0.1
```

The script will:
1. Bump the component's version file(s) (`package.json` for mobile and admin, `server/app/version.py` for server, `captive-portal/VERSION` for portal, `GSM-module/GSM-fastapi/app_version.py` **and** the Arduino firmware's `FIRMWARE_VERSION` define for gsm).
2. Commit the bump: `chore(version): <component> <version>`.
1. Bump the release unit's version file(s) (`package.json` for mobile and admin, `server/app/version.py` for server, `captive-portal/VERSION` for portal, `GSM-module/GSM-fastapi/app_version.py` **and** the Arduino firmware's `FIRMWARE_VERSION` define for gsm, or `deploy/VERSION` for the bundle).
2. Commit the bump: `chore(version): <component> <version>`, or
`chore(deploy-version): bump bundle to <version>` for the bundle.
3. Create a local **annotated git tag** (`<component>/vX.Y.Z`).
4. Print the push command — **it does NOT push automatically**.

Expand Down Expand Up @@ -148,27 +152,40 @@ Generated Docs Check fails even when no endpoint shape changed.
| Admin | `admin-frontend/sapot-admin/package.json` → `version` |
| Captive Portal | `captive-portal/VERSION` (plain text) |
| GSM Module | `GSM-module/GSM-fastapi/app_version.py` → `__version__` **and** `GSM-module/GSM-arduino-actual-code/GSM-arduino-actual-code.ino` → `FIRMWARE_VERSION` (kept in lockstep by `GSM-module/scripts/set_version.py`) |
| Deployment Bundle | `deploy/VERSION` (independent of all component versions) |

`app.config.ts` (`version` and `extra.displayVersion`) is kept in sync by `set-version.js` — do not edit it manually.

---

## CI Behaviour

When a tag is pushed, the corresponding GitHub Actions workflow:
When an application component tag is pushed, the corresponding GitHub Actions workflow:
1. Derives the version from the tag name.
2. **Asserts** the version in the source file matches the tag (fails loudly if not).
3. Extracts the annotated tag message (`<component> <version>`) as the GitHub Release body.
4. Creates a GitHub Release (pre-release if the tag has a `-` suffix).

CI does not draft or edit notes — it only reads the tag message and publishes it, using the default `GITHUB_TOKEN`.

For `bundle/v*`, CI validates the committed compatibility policy, downloads the pinned
immutable `map/v1.0.0` asset, builds the offline bundle, and publishes the archive plus
its SHA-256 file only after both remote asset digests are verified. Bundle versions use
canonical SAPOT SemVer. The `0.0.1` prerelease and stable family is fresh-install-only,
so both compatibility floors must equal the exact candidate. Later releases require
`minimumRollbackVersion <= minimumUpgradeVersion < bundle version`.

Repository immutable releases are a publication prerequisite. A repository administrator
must enable them and approve the protected `bundle-release` environment before a map or
bundle publication. This locks every future repository release tag and asset, not only
bundle releases. Build hosts need a GitHub CLI with `gh release verify` and
`gh release verify-asset`, plus `python3-jsonschema` for bundle-content validation.

---

## Out of Scope

This versioning system covers **only** version strings and GitHub Releases. The following are handled separately:

- EAS builds and OTA updates
- Offline Docker bundles: a bundle uses the server `__version__` at build time and records its exact artifact metadata in `manifest.json`; see [the Docker bundle deployment guide](docs/deployment/docker-bundle.md)
- In-app update banners and client-version gating
1 change: 1 addition & 0 deletions admin-frontend/sapot-admin/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
!/pnpm-lock.yaml

# env files (can opt-in for committing if needed)
.env
Expand Down
Loading
Loading