Since the redesign of the firmware CI pipeline in May 2026, every nightly build is content-addressable: it has its own dated GitHub Release, a 90-day history is retained, and a public manifest indexes every artifact. Cameras can pin to a specific historic build, downgrade after a bad update, and bisect to find which nightly introduced a regression.
This article is for users who need to:
- Get the current nightly without thinking about it (the default, see sysupgrade).
- List what builds are available for their camera.
- Pin to a specific dated build (downgrade after a bad update, or pin a known-good version on a fleet).
- Bisect to find which nightly broke a feature (no image at all, broken device driver, missing UI element, anything).
For the more traditional flag set (offline --archive, --kernel/--rootfs from TFTP/SD/scp), see the Upgrade firmware article. This document only covers the manifest-aware additions in sysupgrade ≥ 1.0.50.
ATTENTION! Upgrading firmware can lead to "bricking" your camera. Have a UART adapter and a rescue SD card ready before you start a bisect. The bisect tool itself stores state on your workstation, so a brick during bisect does not lose progress — but you still need to get the camera back online.
A JSON-and-text index of every dated nightly is served from GitHub Pages:
| URL | Format | Audience |
|---|---|---|
| https://openipc.github.io/firmware/manifest.json | Rich JSON | Hosts, agents, CI tooling |
| https://openipc.github.io/firmware/manifest.flat | Whitespace-delimited (busybox awk friendly) | On-device sysupgrade |
The flat form has one line per <build, platform, flash> triple plus channel pointers:
# columns: build_id platform flash size url
nightly-20260520-887328c hi3520dv200_lite nor 4823040 https://github.com/OpenIPC/firmware/releases/download/nightly-20260520-887328c/openipc.hi3520dv200-nor-lite.tgz
nightly-20260520-887328c gk7205v200_lite nor 7049210 https://github.com/OpenIPC/firmware/releases/download/nightly-20260520-887328c/openipc.gk7205v200-nor-lite.tgz
...
# channels
@channel nightly nightly-20260520-887328c
@channel latest nightly-20260520-887328c
The rich JSON form adds sha, branch, built_at, and release_url per build. Both files are regenerated every time a nightly build completes (the workflow that publishes them is manifest.yml).
A build id has the shape nightly-YYYYMMDD-<short> (8-digit UTC date + 7-char git commit hash). Example: nightly-20260520-887328c. The last 90 builds are retained; older releases and tags are pruned every Monday.
The currently-running build id lives in the camera's /etc/os-release:
$ grep ^BUILD_ /etc/os-release
BUILD_ID=nightly-20260520-887328c
BUILD_SHA=887328caf77479cf45d40fef76d3a5b0f4546993If BUILD_ID is absent your firmware predates the redesign — you can still install a manifest-aware nightly with sysupgrade -k -r, which always grabs the rolling current build.
The manifest indexes artifacts by ${soc}_${variant}. Your camera derives these from the U-Boot environment and the rootfs:
$ soc=$(fw_printenv -n soc); variant=$(grep ^BUILD_OPTION= /etc/os-release | cut -d= -f2)
$ echo "${soc}_${variant}"
hi3520dv200_liteThat string is what sysupgrade --list-builds and --build= will look up in the manifest.
sysupgrade (in firmware ≥ 1.0.50) gains three manifest-aware flags. All of them coexist with the existing --url, --archive, --kernel, --rootfs, and -k/-r flags — anything documented in the older Upgrade firmware article still works exactly as before.
Print the last N (default 20) builds available for your camera's platform. The currently-running build is marked with *.
$ sysupgrade --list-builds=10
OpenIPC System Updater v1.0.50
Vendor hisilicon
SoC hi3520dv200
Kernel 08:37:58 2026-05-18
RootFS master+887328c, 2026-05-20
Available builds for hi3520dv200_lite (newest first):
nightly-20260524-1a2b3c4
nightly-20260523-9876543
* nightly-20260520-887328c
nightly-20260519-abcd123
nightly-20260518-fedcba9
...This is the easiest way to confirm the manifest is reachable and to see what build ids are valid for your camera.
Update to whatever the named channel currently points at. Two channels are defined:
nightly— newest dated build (refreshed every successful nightly).latest— alias ofnightlyfor now; kept distinct so the project can later carve out a slower "stable" channel without breaking scripts that pinned tolatest.
sysupgrade --channel=nightly # the default destination going forwardPin to one specific dated build. This is the downgrade primitive.
sysupgrade --build=nightly-20260519-abcd123 # roll back to that exact buildThe build id must be one that --list-builds shows for your platform — --build validates against the manifest and refuses to flash if the build id has no artifact for ${soc}_${variant}.
sysupgradewill self-update before flashing if the script on the camera is older than the one inmaster. After self-update it re-invokes itself with your original arguments.- Older OpenIPC variants — for example fpv — are built and published from the separate OpenIPC/builder repository and do not appear in this manifest.
--channel/--buildwill report "no builds available" on those cameras; their classicsysupgrade -k -rpath (which readsreleases/download/latestdirectly) still works as it always did. - The on-device parser uses only busybox
awk/grep— nojq, nojsonfilter. The manifest schema is deliberately busybox-friendly. You can replicate the same lookup from any shell:curl -s https://openipc.github.io/firmware/manifest.flat \ | awk '$1=="@channel" && $2=="nightly" {print $3}'
When a recent update breaks something — the camera doesn't boot, an ISP driver vanishes, a web-UI element disappears, video glitches — and you don't know which nightly introduced the problem, run a bisect to narrow it down to a single dated build (and the small batch of commits it contains).
The bisect loop reboots the camera many times. It is unsafe to keep bisect state on the camera itself: a "no boot" verdict means the camera is now unreachable until you recover it via UART/TFTP, and any state on the camera goes with it. The bisect tool therefore runs on your workstation and stores its state on disk locally — a brick can never lose progress.
# From a checkout of OpenIPC/firmware:
sudo install -m 0755 contrib/openipc-bisect /usr/local/bin/openipc-bisect
# Or with curl (no clone needed):
sudo curl -L -o /usr/local/bin/openipc-bisect \
https://raw.githubusercontent.com/OpenIPC/firmware/master/contrib/openipc-bisect
sudo chmod +x /usr/local/bin/openipc-bisectDependencies on the workstation: sh, jq, curl, ssh. Nothing extra is needed on the camera beyond sysupgrade ≥ 1.0.50.
The example below bisects between a build from two weeks ago (known good) and the current nightly (known bad) on a Hisilicon camera.
openipc-bisect start root@192.168.1.10 \
--good=nightly-20260506-1111111 \
--bad=channels.nightly--goodis the last build where the feature you care about worked. Pass an exactbuild_id, a short or full git SHA, orchannels.nightly/channels.latest.--baddefaults tochannels.nightlyif omitted. Anything that fails today.- Platform is detected automatically (
fw_printenv soc+BUILD_OPTION) — override with--platform=<id>if you must.
The tool fetches the manifest, slices the window, picks the median build, flashes it, and waits for the camera to come back. Once the camera is up, it prints the build id and waits for your verdict.
After each round you decide whether this build exhibits the regression:
openipc-bisect bad # this build is broken
openipc-bisect good # this build is OK
openipc-bisect skip # build itself is broken (build error, partial release), not your bugThe tool narrows the window, picks the next median, flashes it, and repeats. A window of N builds converges in about log2(N) rounds.
$ openipc-bisect status
{
"host": "root@192.168.1.10",
"platform": "hi3520dv200_lite",
"good": "nightly-20260513-7c37df7",
"bad": "nightly-20260520-887328c",
"current": "nightly-20260517-aabbccd",
"window_size": 4,
"est_rounds_left": 2
}When the window narrows to a single build the tool prints the first bad build:
Bisect complete. First bad build: nightly-20260517-aabbccd
Use the release URL (https://github.com/OpenIPC/firmware/releases/tag/nightly-20260517-aabbccd) or its sha= field to pull up the diff against the previous good nightly. Open an issue with the culprit build id and a description of the regression.
openipc-bisect resetFlashes the camera back to channels.nightly and clears the host state.
A bisect by definition flashes builds that you don't know are good. If the chosen build doesn't boot at all:
- The tool will time out waiting for the camera to come back (default 5 minutes — adjust with
OPENIPC_BISECT_WAIT=600). - It prints recovery instructions and exits with code 75. Your bisect state stays on disk.
- Recover the camera by any means — UART + TFTP is the canonical path. See the Sigmastar unbrick or Ingenic T31 unbrick articles for vendor-specific recipes.
- Once the camera responds to SSH again, mark the iteration:
openipc-bisect bad # almost always the right verdict if the camera didn't boot - The loop continues with a narrower window.
The bisect tool cannot lose progress to a brick because nothing relevant lives on the camera.
State lives at $XDG_STATE_HOME/openipc/bisect/<host>.json (typically ~/.local/state/openipc/bisect/). To re-attach to an in-flight bisect after closing your terminal or rebooting your workstation:
openipc-bisect resumeIf you are bisecting more than one camera at the same time, set OPENIPC_BISECT_HOST=<host> so the tool knows which one you mean.
If the regression window narrows to a single nightly but you need to know which commit inside that day broke things, use the build-one.yml GitHub Actions workflow. It accepts an arbitrary commit SHA and publishes a one-off build to a separate tag namespace (nightly-bisect-<short>) that does not pollute the dated nightly index and is not swept by retention.
# Build one board at one commit:
gh workflow run build-one.yml \
-f platform=hi3520dv200_lite \
-f commit=<sha>
# Then on the camera (one-off, manifest-bypass):
sysupgrade --url=https://github.com/OpenIPC/firmware/releases/download/nightly-bisect-<short>/openipc.hi3520dv200-nor-lite.tgzCombined with git bisect run this gives true commit-level resolution, at the cost of one ~5-minute CI job per iteration.
| Goal | Command |
|---|---|
| Update to the current nightly | sysupgrade --channel=nightly |
| Downgrade to a specific historic nightly | sysupgrade --build=nightly-YYYYMMDD-<short> |
| List the last 20 builds for this camera | sysupgrade --list-builds |
| List the last N builds for this camera | sysupgrade --list-builds=N |
| Show the current channel pointer (host shell) | curl -s https://openipc.github.io/firmware/manifest.flat | awk '$1=="@channel" && $2=="nightly" {print $3}' |
| Bisect a regression | openipc-bisect start root@<camera> --good=<id> [--bad=<id>] |
| Mark a bisect verdict | openipc-bisect good | bad | skip |
| Re-attach after host restart | openipc-bisect resume |
| Abandon bisect and return to nightly | openipc-bisect reset |
| Variable | Default | Purpose |
|---|---|---|
OPENIPC_MANIFEST_URL |
https://openipc.github.io/firmware/manifest.json |
Override manifest URL (mirror, fork, local proxy). |
OPENIPC_BISECT_STATE |
$XDG_STATE_HOME/openipc/bisect |
Where the host stores bisect state JSON. |
OPENIPC_BISECT_HOST |
(auto) | Pick a host when multiple bisects are in flight. |
OPENIPC_BISECT_WAIT |
300 |
Seconds to wait for the camera to come back online. |
OPENIPC_SSH_OPTS |
-o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new |
Extra SSH options. |
| Variable | Default | Purpose |
|---|---|---|
MANIFEST_URL |
https://openipc.github.io/firmware/manifest.flat |
Override the flat manifest URL for sysupgrade --channel/--build. |