ci(ota): verify each published binary's native channel by tag

Every observer build now carries both channel URLs, so checking for a URL's
presence or absence can no longer tell prod from beta (and the old checks would
reject every build). Read the ota-base-* tags from every .bin in out/ instead
of a single ELF, with URLs shared by build.sh and both workflows.
This commit is contained in:
agessaman committed 2026-10-03 15:24:11 -07:00
1 parent fc182b6f8c
commit 33bc19722f
3 files changed
+147 -14

No files matched your search

@@ -67,6 +67,10 @@ env:
# The channel itself. Firmware fetches <OTA_MANIFEST_BASE>/<OTA_VARIANT>.json,
# so this URL is what keeps beta nodes on beta.
OTA_MANIFEST_BASE_URL: https://observer.gessaman.com/beta/v
# Both channel bases are baked into every build so `ota branch prod|beta` can
# move a node between channels. Identical in both observer workflows.
OTA_MANIFEST_BASE_STABLE_URL: https://observer.gessaman.com/v
OTA_MANIFEST_BASE_DEV_URL: https://observer.gessaman.com/beta/v
# Marks the embedded version, e.g. v1.16.0.3-observer-beta-dev-abc1234, so `ver`
# (and the MQTT firmware_version / SNMP) identify BOTH the channel and its
# provenance: this channel is built from the upstream-dev-merged line, so "dev"
@@ -168,20 +172,9 @@ jobs:
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
- name: Verify beta channel is baked in
shell: bash
run: |
# Fail fast rather than publish firmware that would OTA itself onto the
# production channel. Checks one built binary actually carries the beta
# manifest URL and does NOT carry the production one.
BIN=$(find .pio/build -name firmware.elf | head -1)
if [ -z "$BIN" ]; then echo "no ELF found to verify" >&2; exit 1; fi
if ! strings "$BIN" | grep -qF "$OTA_MANIFEST_BASE_URL"; then
echo "ERROR: beta manifest base missing from $BIN" >&2; exit 1
fi
if strings "$BIN" | grep -qE 'https://observer\.gessaman\.com/v"?$'; then
echo "ERROR: production manifest base present in a beta build" >&2; exit 1
fi
echo "OK: $BIN carries $OTA_MANIFEST_BASE_URL"
# Fail fast rather than publish firmware that would OTA itself onto the
# production channel: checks every .bin the shard publishes.
run: python3 scripts/verify_ota_channel.py --expect beta
- name: Upload Shard Artifact
uses: actions/upload-artifact@v4
@@ -48,6 +48,16 @@ env:
# repo). Baked into the slim OTA manifests' file URLs; must stay consistent
# with config.json's staticPath.
STATIC_PATH: https://observer-fw.gessaman.com
# Where the firmware fetches <OTA_VARIANT>.json from — this URL *is* the release
# channel. Stated explicitly (it equals build.sh's default) rather than left
# unset, so "Verify production channel is baked in" below can assert against the
# same value the build was handed instead of a second hardcoded copy. Must match
# build.sh's OTA_MANIFEST_BASE_URL default and the Pages path serving flasher/v.
OTA_MANIFEST_BASE_URL: https://observer.gessaman.com/v
# Both channel bases are baked into every build so `ota branch prod|beta` can
# move a node between channels. Identical in both observer workflows.
OTA_MANIFEST_BASE_STABLE_URL: https://observer.gessaman.com/v
OTA_MANIFEST_BASE_DEV_URL: https://observer.gessaman.com/beta/v
jobs:
@@ -137,6 +147,11 @@ jobs:
FIRMWARE_BUILD_NUMBER: ${{ needs.enumerate.outputs.build_number }}
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.shard.envs }}
- name: Verify production channel is baked in
# Fail fast rather than publish firmware that would OTA itself onto the
# wrong channel: checks every .bin the shard publishes.
run: python3 scripts/verify_ota_channel.py --expect prod
- name: Upload Shard Artifact
uses: actions/upload-artifact@v4
with: