diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28e54b6..f2fb494 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,13 +3,19 @@ name: ci on: push: branches: [master] - tags: ['v*'] pull_request: workflow_dispatch: permissions: contents: read +# One run per branch: on pull requests a newer push cancels the older run; on master it waits +# instead (only the newest waits), so a release is never cut off halfway. A manual run shares the +# push group. +concurrency: + group: ci-${{ github.event_name == 'pull_request' && 'pull_request' || 'push' }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + jobs: # Pull requests: a change to src/, the installers, the Makefile or ci/ comes with a new # CERBERUS_VERSION (MAJOR.MINOR.PATCH) and its CHANGELOG.md section (#33). The checkout is the @@ -76,10 +82,8 @@ jobs: matrix: arch: [arm, mipsel, aarch64, x86_64] steps: + # The version compiled into the binary is CERBERUS_VERSION in src/cerberus.c. - uses: actions/checkout@v4 - with: - # git describe needs the tags: the version compiled into the binary comes from them. - fetch-depth: 0 - name: Toolchain and mbedTLS cache uses: actions/cache@v4 @@ -98,10 +102,14 @@ jobs: release: name: release - # A v* tag publishes the binaries that were just built and tested — the same files, not a rebuild — - # together with the installer, the uninstaller (now a wrapper around it) and SHA256SUMS over all of them. - # Asset names carry no version: cameras fetch releases/latest/download/. - if: startsWith(github.ref, 'refs/tags/v') + # The one release, "latest" (#32): published from master when CERBERUS_VERSION differs from the + # version in the title of the current "latest", with the binaries that were just built and tested + # (the same files, not a rebuild), the installer, the uninstaller (a wrapper around it), the license + # and SHA256SUMS over all of them. Asset names carry no version: cameras fetch + # releases/latest/download/, so "latest" is updated in place, never deleted: the files are + # replaced, the tag is moved to the built commit, then the title and notes follow. A push without + # a new version publishes nothing, and only the head of master is published. + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' needs: [lint, test, build] runs-on: ubuntu-latest permissions: @@ -109,25 +117,84 @@ jobs: steps: - uses: actions/checkout@v4 + - name: A new version? + id: ver + env: + GH_TOKEN: ${{ github.token }} + run: | + # Only the head of master: a rerun of an older run must not roll the cameras back. + head=$(git ls-remote origin refs/heads/master | cut -f 1) + if [ "$head" != "$GITHUB_SHA" ]; then + echo "::notice::master is at ${head:0:7} now, not ${GITHUB_SHA::7}: not published"; exit 0 + fi + ver=$(sed -n 's/^#define CERBERUS_VERSION "\([^"]*\)"[[:space:]]*$/\1/p' src/cerberus.c) + [ "$(printf '%s\n' "$ver" | grep -c .)" = 1 ] || { echo "::error::expected one CERBERUS_VERSION"; exit 1; } + # "Not found" means there is no "latest" yet; any other failure stops here. + if prev=$(gh release view latest --repo "$GITHUB_REPOSITORY" --json name -q .name 2>err.txt); then + echo "exists=1" >> "$GITHUB_OUTPUT" + elif grep -qi 'not found' err.txt; then + prev="" + echo "exists=0" >> "$GITHUB_OUTPUT" + else + cat err.txt; echo "::error::cannot read the current \"latest\" release"; exit 1 + fi + echo "latest is \"${prev:-none}\", src/cerberus.c says $ver" + echo "ver=$ver" >> "$GITHUB_OUTPUT" + if [ "$prev" != "Cerberus $ver" ]; then + echo "new=true" >> "$GITHUB_OUTPUT" + else + # same version: publish again only if a publication was cut off and files are missing + have=$(gh release view latest --repo "$GITHUB_REPOSITORY" --json assets -q '.assets[].name') + for f in cerberus-arm cerberus-mipsel cerberus-aarch64 cerberus-x86_64 install.sh uninstall.sh LICENSE.md SHA256SUMS; do + printf '%s\n' "$have" | grep -qx "$f" || { echo "latest lacks $f: publishing again"; echo "new=true" >> "$GITHUB_OUTPUT"; break; } + done + fi + - uses: actions/download-artifact@v4 + if: steps.ver.outputs.new == 'true' with: pattern: cerberus-* path: dist merge-multiple: true - - name: The binaries were built as this tag + - name: The binaries were built as this version + if: steps.ver.outputs.new == 'true' + env: + VER: ${{ steps.ver.outputs.ver }} run: | chmod +x dist/cerberus-* got=$(dist/cerberus-x86_64 --help 2>&1 | head -1 | cut -d' ' -f2) - echo "binary says $got, tag says ${GITHUB_REF_NAME#v}" - [ "$got" = "${GITHUB_REF_NAME#v}" ] + echo "binary says $got, src/cerberus.c says $VER" + [ "$got" = "$VER" ] - name: Lay out the release + if: steps.ver.outputs.new == 'true' run: ci/assemble.sh - name: Publish + if: steps.ver.outputs.new == 'true' env: GH_TOKEN: ${{ github.token }} + VER: ${{ steps.ver.outputs.ver }} + EXISTS: ${{ steps.ver.outputs.exists }} run: | - gh release create "$GITHUB_REF_NAME" dist/* \ - --repo "$GITHUB_REPOSITORY" --title "cerberus ${GITHUB_REF_NAME#v}" --generate-notes --verify-tag + url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" + cat > notes.md <\`. Changes: [CHANGELOG.md](${url}/blob/master/CHANGELOG.md); details: [README](${url}#readme). + NOTES + if [ "$EXISTS" = 1 ]; then + # in place: files first, then the tag, then the title and notes + gh release upload latest dist/* --repo "$GITHUB_REPOSITORY" --clobber + gh api -X PATCH "repos/${GITHUB_REPOSITORY}/git/refs/tags/latest" -f sha="$GITHUB_SHA" -F force=true >/dev/null + gh release edit latest --repo "$GITHUB_REPOSITORY" --latest --title "Cerberus ${VER}" --notes-file notes.md + else + # a "latest" tag left without its release would keep its old commit under --target: + # move it (a failure stops the job), or create the release when there is no such tag + if gh api "repos/${GITHUB_REPOSITORY}/git/refs/tags/latest" >/dev/null 2>&1; then + gh api -X PATCH "repos/${GITHUB_REPOSITORY}/git/refs/tags/latest" -f sha="$GITHUB_SHA" -F force=true >/dev/null + fi + gh release create latest dist/* --repo "$GITHUB_REPOSITORY" --latest \ + --target "$GITHUB_SHA" --title "Cerberus ${VER}" --notes-file notes.md + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e0438..6eb8c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ The version lives in `CERBERUS_VERSION` in `src/cerberus.c` (`MAJOR.MINOR.PATCH` published only as the [`latest`](https://github.com/OpenIPC/cerberus/releases/latest) release (see the README); this file is the change history, newest first. +## 0.3.1 — 2026-10-17 + +One release, `latest` (#32), and a version check (#33). + +- Releases: one release, `latest`, titled `Cerberus `, published from `master` when + `CERBERUS_VERSION` changes; the versioned releases `v0.1.0`…`v0.3.0` and their tags are gone, + their notes are in this file. The download links (`releases/latest/download/`) and the + files stay the same. +- The version compiled into the binary is `CERBERUS_VERSION` in `src/cerberus.c`; it no longer + comes from a `v*` tag. +- CI: a pull request that changes the code or the build raises the version and adds its section + here (#33). + ## 0.3.0 — 2026-10-15 - Review fixes: the snapshot URL, the probe subnet, the enrolment JSON, `--help` (PR #12). diff --git a/README.md b/README.md index c6c3fdf..1b4abbe 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ the camera. ## Releases -Every release carries one **static** binary per CPU architecture — static with musl and mbedTLS +There is one release, [`latest`](https://github.com/OpenIPC/cerberus/releases/latest), titled +`Cerberus `; it is published again whenever the version changes. It carries one **static** binary per CPU architecture — static with musl and mbedTLS inside, so it does not matter which libc or which mbedTLS a given firmware has, or whether it has one at all: @@ -306,12 +307,11 @@ makes its absence a failure, which is how CI runs it. ## CI and releases Every push and pull request runs shellcheck, the suite on both mbedTLS generations, and the four -release builds. A `v*` tag publishes those same binaries, with the installers, the init script and -`SHA256SUMS`, as a GitHub release; the version compiled into the binaries comes from the tag. - -```sh -git tag v0.2.0 && git push origin v0.2.0 -``` +release builds. When a push to `master` brings a new `CERBERUS_VERSION` (`src/cerberus.c`), CI +replaces the one release, `latest`, with those same binaries, the installers, the license and +`SHA256SUMS`, titled `Cerberus `; a push without a new version publishes nothing. The +version compiled into the binaries is `CERBERUS_VERSION` itself; there are no `v*` tags. To release: +bump the version and add its `CHANGELOG.md` section in the pull request, then merge it. Versions are `MAJOR.MINOR.PATCH` (`CERBERUS_VERSION` in `src/cerberus.c`): PATCH for fixes and small changes, MINOR for a new feature, MAJOR for an incompatible change or a large update, only diff --git a/README.ru.md b/README.ru.md index 22d2bee..d2a8642 100644 --- a/README.ru.md +++ b/README.ru.md @@ -69,7 +69,8 @@ fw_setenv cerberus_enrollment_token tok_... ## Релизы -В каждом релизе — по одному **статическому** бинарю на архитектуру процессора: musl и mbedTLS внутри, +Релиз один — [`latest`](https://github.com/OpenIPC/cerberus/releases/latest), с заголовком +`Cerberus <версия>`; он публикуется заново при каждой смене версии. В нём — по одному **статическому** бинарю на архитектуру процессора: musl и mbedTLS внутри, поэтому неважно, какая libc и какой mbedTLS в конкретной прошивке и есть ли он там вообще: | файл | на чём работает | @@ -304,12 +305,11 @@ kernel-WireGuard туннель до хаба в отдельном network name ## CI и релизы На каждый push и pull request — shellcheck, тесты на обоих поколениях mbedTLS и четыре сборки релиза. -Тег `v*` публикует эти же бинари вместе с установщиками, init-скриптом и `SHA256SUMS` как GitHub -release; версия, вкомпилированная в бинари, берётся из тега. - -```sh -git tag v0.2.0 && git push origin v0.2.0 -``` +Когда push в `master` приносит новую `CERBERUS_VERSION` (`src/cerberus.c`), CI заменяет единственный +релиз `latest` этими же бинарями вместе с установщиками, лицензией и `SHA256SUMS`, с заголовком +`Cerberus <версия>`; push без новой версии ничего не публикует. Версия, вкомпилированная в бинари, — +сама `CERBERUS_VERSION`, тегов `v*` нет. Чтобы выпустить релиз, поднимите версию и добавьте её раздел +в `CHANGELOG.md` в pull request, затем смержите его. Версия — `MAJOR.MINOR.PATCH` (`CERBERUS_VERSION` в `src/cerberus.c`): PATCH — исправления и мелкие правки, MINOR — новая возможность, MAJOR — несовместимое изменение или крупное обновление, только по diff --git a/README.zh-CN.md b/README.zh-CN.md index 933a22f..873f65c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -63,7 +63,8 @@ fw_setenv cerberus_enrollment_token tok_... ## 发布 -每个发布都为每种 CPU 架构提供一个**静态**二进制文件:musl 和 mbedTLS 已编译在内,因此无论固件使用哪种 libc、 +只有一个发布:[`latest`](https://github.com/OpenIPC/cerberus/releases/latest),标题为 `Cerberus <版本>`;每当版本号变化时都会重新发布。 +它为每种 CPU 架构提供一个**静态**二进制文件:musl 和 mbedTLS 已编译在内,因此无论固件使用哪种 libc、 带有哪个版本的 mbedTLS,甚至没有 mbedTLS,都没有关系: | 文件 | 适用于 | @@ -255,12 +256,10 @@ CERBERUS_TEST_IMAGE=debian:bookworm-slim tests/run.sh # glibc + mbedTLS 2.2 ## CI 与发布 -每次 push 和 pull request 都会运行 shellcheck、两代 mbedTLS 上的测试套件以及四个发布构建。推送 `v*` 标签后, -这些二进制文件会连同安装脚本、启动脚本和 `SHA256SUMS` 一起作为 GitHub release 发布;编译进二进制文件的版本号取自标签。 - -```sh -git tag v0.2.0 && git push origin v0.2.0 -``` +每次 push 和 pull request 都会运行 shellcheck、两代 mbedTLS 上的测试套件以及四个发布构建。当推送到 `master` 带来新的 +`CERBERUS_VERSION`(`src/cerberus.c`)时,CI 会用这些二进制文件连同安装脚本、许可证和 `SHA256SUMS` 替换唯一的发布 +`latest`,标题为 `Cerberus <版本>`;不带新版本号的推送不会发布任何内容。编译进二进制文件的版本号就是 +`CERBERUS_VERSION` 本身,不再使用 `v*` 标签。发布方法:在 pull request 中提升版本号并添加其 `CHANGELOG.md` 章节,然后合并。 版本号格式为 `MAJOR.MINOR.PATCH`(`src/cerberus.c` 中的 `CERBERUS_VERSION`):PATCH 表示修复和小改动,MINOR 表示新功能,MAJOR 表示不兼容的变更或大规模更新,仅由维护者决定。修改 `src/`、安装脚本、`Makefile` 或 `ci/` diff --git a/ci/build.sh b/ci/build.sh index a1c2e29..e601da6 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -22,8 +22,9 @@ # ci/build.sh arm mipsel # a subset # VERSION=1.2.0 OUT=dist ci/build.sh # -# VERSION defaults to the nearest v* tag, without the v. It is compiled into the binary — it is what -# the agent reports to the coordinator. With no tag in reach the binary keeps the version in src/cerberus.c. +# The version compiled into the binary — what the agent reports to the coordinator — is +# CERBERUS_VERSION in src/cerberus.c. VERSION overrides it for a test build only; releases never set it. +# (It used to come from the nearest v* tag; there are no such tags any more, #32.) # # Output: $OUT/cerberus-. Names carry no version on purpose: the installer fetches # releases/latest/download/cerberus-, and that address must not change between releases. @@ -41,8 +42,7 @@ TOOLCHAIN_RELEASE=stable-2025.08-1 MBEDTLS_VERSION=3.6.7 MBEDTLS_SHA256=a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6 -VERSION=${VERSION:-$(git describe --tags --match 'v[0-9]*' --dirty 2>/dev/null || true)} -VERSION=${VERSION#v} +VERSION=${VERSION:-} mkdir -p "$OUT" "$CACHE" diff --git a/src/cerberus.c b/src/cerberus.c index cf7e477..7a584d9 100644 --- a/src/cerberus.c +++ b/src/cerberus.c @@ -70,7 +70,7 @@ /* Release builds pass the tag in (`make VERSION=...`); a plain `make` reports the source version. */ #ifndef CERBERUS_VERSION -#define CERBERUS_VERSION "0.3.0" +#define CERBERUS_VERSION "0.3.1" #endif /* Where the errors are decoded. Printed after every fatal one: the reader is on a camera