diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba8e48a..4016f37 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,21 +3,30 @@ # # build.yml — CI/CD for camex: build, test, release # +# Release (#60): one release only, "latest", replaced by every push to master +# (or a manual run on master) once all build and test jobs pass. Its title is +# "Camex ", its file names carry no version, +# so download links never change, and SHA256SUMS lists them. No versioned +# releases or v* tags are kept; the change history lives in CHANGELOG.md. +# name: camex CI on: push: branches: [master, main, develop] - tags: ['v*'] pull_request: branches: [master, main] + # a manual run on master rebuilds and republishes "latest" workflow_dispatch: - inputs: - version: - description: 'Release version (e.g., v3.0.2)' - required: true - default: 'v3.0.2' + +# One run per ref at a time. Branches and pull requests: a newer push cancels +# the older run. master (pushes and manual runs share the group): runs are +# queued, never cancelled, so a release is not stopped between deleting and +# recreating "latest"; a newer queued run replaces an older waiting one. +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: BUILD_DIR: build @@ -210,16 +219,14 @@ jobs: if-no-files-found: error # Debuggable build for testing on devices (adb install, run-as, logcat). - # Not uploaded for tags, so it never ends up in a release. + # The release job takes camex-android.apk by name, so it never ends up there. - name: Build debug APK - if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: | cd android gradle assembleDebug cp app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/debug/camex-android-debug.apk - name: Upload artifact (debug APK) - if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: actions/upload-artifact@v7 with: name: camex-android-debug-apk @@ -455,41 +462,16 @@ jobs: # ----------------------------------------------------------------- release release: - name: GitHub Release + name: Release "latest" needs: [linux, smoke, android, windows, wine-smoke, macos] runs-on: ubuntu-latest - # Runs on tag push (auto) OR manual workflow_dispatch - if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' permissions: contents: write steps: - - name: Determine tag name - id: tag - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "tag=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT" - else - echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" - fi - - - name: Checkout (for release notes) + - name: Checkout uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Verify version.h matches git tag - run: | - FILE_VERSION=$(grep -Po '(?<=CAMEX_VERSION ")[^"]*' src/version.h) - TAG_VERSION="${{ steps.tag.outputs.tag }}" - TAG_VERSION="${TAG_VERSION#v}" - echo "version.h → ${FILE_VERSION}" - echo "git tag → ${TAG_VERSION}" - if [ "${FILE_VERSION}" != "${TAG_VERSION}" ]; then - echo "::error::VERSION MISMATCH: version.h says '${FILE_VERSION}' but git tag is 'v${TAG_VERSION}'" - exit 1 - fi - echo "✅ Versions match: ${FILE_VERSION}" - name: Download all artifacts uses: actions/download-artifact@v7 @@ -497,129 +479,106 @@ jobs: path: artifacts merge-multiple: true - - name: Collect release binaries + - name: Collect release files + id: vars + env: + GH_TOKEN: ${{ github.token }} run: | - mkdir release-binaries - VERSION="${{ steps.tag.outputs.tag }}" - VERSION="${VERSION#v}" - [ -z "$VERSION" ] && VERSION="0.0.0" - - # Collect all camex-* (Linux), camex-*.exe (Windows), and .apk (Android) - find artifacts -type f \( -name 'camex-*' -o -name 'camex.exe' -o -name '*.apk' \) \ - -exec cp -v {} release-binaries/ \; - - # Rename Linux binaries: camex- → camex-- - for f in release-binaries/camex-aarch64 release-binaries/camex-arm \ - release-binaries/camex-mipsel release-binaries/camex-x86_64; do - [ -f "$f" ] || continue - arch="${f#release-binaries/camex-}" - mv -v "$f" "release-binaries/camex-${VERSION}-${arch}" - done - - # Rename APK: camex-android.apk → camex--android.apk - if [ -f release-binaries/camex-android.apk ]; then - mv -v release-binaries/camex-android.apk \ - "release-binaries/camex-${VERSION}-android.apk" + VER=$(sed -n 's/^#define CAMEX_VERSION "\(.*\)"/\1/p' src/version.h) + [ -n "$VER" ] || { echo "::error::no CAMEX_VERSION in src/version.h"; exit 1; } + echo "ver=$VER" >> "$GITHUB_OUTPUT" + # Only the newest commit of master is published: a run of an older one + # (a manual run started before a push) stops here + HEAD=$(git ls-remote origin refs/heads/master | cut -f1) + if [ "$HEAD" != "$GITHUB_SHA" ]; then + echo "::notice::master is at ${HEAD::7} now, not ${GITHUB_SHA::7}: not published" + echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0 fi - - echo "=== Release binaries ===" - ls -lh release-binaries/ - - - name: Package .zip archives for macOS and Windows - run: | - VERSION="${{ steps.tag.outputs.tag }}" - VERSION="${VERSION#v}" - [ -z "$VERSION" ] && VERSION="0.0.0" - # macOS .zip - if [ -f release-binaries/camex-darwin ]; then - mkdir -p pkg-macos - cp release-binaries/camex-darwin pkg-macos/camex - cp LICENSE README.md pkg-macos/ 2>/dev/null || true - cd pkg-macos && zip -9 "../release-binaries/camex-${VERSION}-macos.zip" * && cd .. - rm -rf pkg-macos - # Remove raw darwin binary — only .zip remains - rm -f release-binaries/camex-darwin + # The APK goes to Telegram only when the version changes. A missing + # release means a first one; any other lookup error stops the job, so + # an unchanged version is never posted by mistake. + if PREV=$(gh release view latest --repo "$GITHUB_REPOSITORY" --json name -q .name 2>err.txt); then + : + elif grep -q 'release not found' err.txt; then + PREV= + else + echo "::error::cannot read the current latest release: $(cat err.txt)"; exit 1 fi - # Windows .zip — download wintun.dll once, package per arch - if ls release-binaries/camex-*.exe >/dev/null 2>&1; then - # Download wintun.dll once - curl -fsSL "https://www.wintun.net/builds/wintun-0.14.1.zip" -o /tmp/wintun.zip - unzip -q -o /tmp/wintun.zip -d /tmp/wintun-extract - - # x86_64 package - if [ -f release-binaries/camex-x86_64.exe ]; then - mkdir -p pkg-win64 - cp release-binaries/camex-x86_64.exe pkg-win64/camex.exe - cp LICENSE README.md pkg-win64/ 2>/dev/null || true - find /tmp/wintun-extract -name 'wintun.dll' -path '*/amd64/*' -exec cp {} pkg-win64/wintun.dll \; - cd pkg-win64 && zip -9 "../release-binaries/camex-${VERSION}-windows-x86_64.zip" * && cd .. - rm -rf pkg-win64 - fi + echo "Previous release: ${PREV:-none}" + [ "$PREV" = "Camex $VER" ] || echo "new=true" >> "$GITHUB_OUTPUT" - # i686 package - if [ -f release-binaries/camex-i686.exe ]; then - mkdir -p pkg-win32 - cp release-binaries/camex-i686.exe pkg-win32/camex.exe - cp LICENSE README.md pkg-win32/ 2>/dev/null || true - find /tmp/wintun-extract -name 'wintun.dll' -path '*/x86/*' -exec cp {} pkg-win32/wintun.dll \; - cd pkg-win32 && zip -9 "../release-binaries/camex-${VERSION}-windows-i686.zip" * && cd .. - rm -rf pkg-win32 - fi + mkdir release + # Linux: static musl binaries, one per architecture + for arch in aarch64 arm mipsel x86_64; do + cp -v "artifacts/camex-$arch" release/ + done + # Android: the signed release APK only (the debug APK has another name) + cp -v artifacts/camex-android.apk release/ + APKSIGNER=$(ls "$ANDROID_HOME"/build-tools/*/apksigner 2>/dev/null | sort -V | tail -1 || true) + [ -n "$APKSIGNER" ] || { echo "::error::apksigner not found"; exit 1; } + "$APKSIGNER" verify release/camex-android.apk || + { echo "::error::camex-android.apk is not signed: an unsigned APK cannot be installed"; exit 1; } + + # macOS: the binary with LICENSE and README + mkdir pkg-macos + cp artifacts/camex-darwin pkg-macos/camex + cp LICENSE README.md pkg-macos/ + (cd pkg-macos && zip -9 ../release/camex-macos.zip *) + # Windows: camex.exe with wintun.dll of the same architecture + curl -fsSL "https://www.wintun.net/builds/wintun-0.14.1.zip" -o wintun.zip + unzip -q wintun.zip -d wintun + for pair in x86_64:amd64 i686:x86; do + arch=${pair%%:*}; wt=${pair#*:} + mkdir "pkg-$arch" + cp "artifacts/camex-$arch.exe" "pkg-$arch/camex.exe" + cp LICENSE README.md "pkg-$arch/" + cp "wintun/wintun/bin/$wt/wintun.dll" "pkg-$arch/" + (cd "pkg-$arch" && zip -9 "../release/camex-windows-$arch.zip" *) + done - rm -rf /tmp/wintun.zip /tmp/wintun-extract - # Remove raw .exe files — only .zip remains - rm -f release-binaries/camex-x86_64.exe release-binaries/camex-i686.exe - fi - echo "=== Release binaries (final) ===" - ls -lh release-binaries/ + (cd release && sha256sum * > SHA256SUMS && sha256sum -c SHA256SUMS) + ls -l release - - name: Create or update release (gh CLI) - id: release + - name: Publish "latest" + if: steps.vars.outputs.skip != 'true' env: GH_TOKEN: ${{ github.token }} + VER: ${{ steps.vars.outputs.ver }} run: | - if gh release view "${{ steps.tag.outputs.tag }}" --repo "${{ github.repository }}" &>/dev/null; then - # Release exists — upload/clobber new assets - gh release upload "${{ steps.tag.outputs.tag }}" \ - --repo "${{ github.repository }}" \ - --clobber \ - release-binaries/* - else - # Create new release - gh release create "${{ steps.tag.outputs.tag }}" \ - --repo "${{ github.repository }}" \ - --title "camex ${{ steps.tag.outputs.tag }}" \ - --generate-notes \ - release-binaries/* - echo "new=true" >> "$GITHUB_OUTPUT" - fi + gh release delete latest --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes 2>/dev/null || true + # Downloads are gone until the release is back: retry a failed create + for try in 1 2 3; do + if gh release create latest release/* \ + --repo "$GITHUB_REPOSITORY" \ + --latest \ + --title "Camex ${VER}" \ + --target "${GITHUB_SHA}" \ + --notes "Camex ${VER} (${GITHUB_SHA::7}): static musl builds for Linux (aarch64, arm, mipsel, x86_64), the Android app (camex-android.apk), macOS and Windows (x86_64, i686) packages, and SHA256SUMS. Rebuilt from every push to master. Changes: CHANGELOG.md; installation: README.md."; then + exit 0 + fi + echo "::warning::gh release create failed (attempt $try)" + # a half-created release blocks the next attempt + gh release delete latest --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes 2>/dev/null || true + sleep 15 + done + echo "::error::latest could not be published"; exit 1 - # The Telegram channel gets the APK of every new release, as in - # OpenIPC/vtesse (#43). Organization secrets; only a signed APK is sent, - # and a failed upload is reported but does not fail the release. + # The Telegram channel gets the APK of every new version, as in + # OpenIPC/vtesse. Organization secrets; a failed upload is reported but + # does not fail the release. - name: Telegram - if: steps.release.outputs.new == 'true' + if: steps.vars.outputs.skip != 'true' && steps.vars.outputs.new == 'true' env: TG_TOKEN: ${{ secrets.TELEGRAM_TOKEN_BOT_OPENIPC }} TG_CHANNEL: ${{ secrets.TELEGRAM_CHANNEL_OPENIPC_DEV }} - TAG: ${{ steps.tag.outputs.tag }} + VER: ${{ steps.vars.outputs.ver }} run: | if [ -z "$TG_TOKEN" ] || [ -z "$TG_CHANNEL" ]; then echo "::warning::Telegram secrets are not available, APK not posted"; exit 0 fi - VER="${TAG#v}" - APK="release-binaries/camex-${VER}-android.apk" - [ -f "$APK" ] || { echo "::warning::no APK in the release, nothing posted"; exit 0; } - # An unsigned APK (no release keystore) cannot be installed: do not post it - APKSIGNER=$(ls "$ANDROID_HOME"/build-tools/*/apksigner 2>/dev/null | sort -V | tail -1 || true) - if [ -z "$APKSIGNER" ] || ! "$APKSIGNER" verify "$APK" >/dev/null 2>&1; then - echo "::warning::$APK is not signed (or apksigner is missing), not posted"; exit 0 - fi - # The commit the tag points to (a manual run may start from another one) - COMMIT=$(git rev-list -n 1 "$TAG" 2>/dev/null || echo "$GITHUB_SHA") - CAPTION=$(printf 'camex %s for Android\nCommit: %s\nTime: %s\n\n%s' "$VER" "${COMMIT::7}" \ - "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}") + CAPTION=$(printf 'camex %s for Android\nCommit: %s\nTime: %s\n\n%s' "$VER" "${GITHUB_SHA::7}" \ + "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/latest") HTTP=$(curl -sS -o tg.json -w '%{http_code}' "https://api.telegram.org/bot${TG_TOKEN}/sendDocument" \ - -F chat_id="$TG_CHANNEL" -F caption="$CAPTION" -F document=@"$APK") || HTTP=000 + -F chat_id="$TG_CHANNEL" -F caption="$CAPTION" -F document=@release/camex-android.apk) || HTTP=000 echo "Telegram response: $HTTP" [ "$HTTP" = 200 ] || echo "::warning::Telegram upload failed (HTTP $HTTP): $(cat tg.json 2>/dev/null)" diff --git a/README.md b/README.md index 796eeda..7a36f72 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **English** | [Русский](README.ru.md) | [简体中文](README.zh-CN.md) [![camex CI](https://github.com/OpenIPC/camex/actions/workflows/build.yml/badge.svg)](https://github.com/OpenIPC/camex/actions/workflows/build.yml) -[![Release](https://img.shields.io/github/v/release/OpenIPC/camex)](https://github.com/OpenIPC/camex/releases) +[![Release](https://img.shields.io/badge/download-latest-blue)](https://github.com/OpenIPC/camex/releases/latest) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Code size](https://img.shields.io/github/languages/code-size/OpenIPC/camex)](https://github.com/OpenIPC/camex) @@ -79,6 +79,35 @@ The app connects in auto mode: the server sends the address, routes and MTU. - **Advanced** — MTU, keepalive and server timeout ([screenshot](docs/images/android-settings-advanced.png)). +## Permanent links + +The binaries are published as one release, [`latest`](https://github.com/OpenIPC/camex/releases/latest), +rebuilt by CI from every push to `master`. Its title shows the camex version +(`Camex X.Y.Z`); the file names carry no version, so these links never change: + +- release page: `https://github.com/OpenIPC/camex/releases/latest` +- a file: `https://github.com/OpenIPC/camex/releases/latest/download/` + +| File | Platform | +|----------------------------|-------------------------------------------------------------------------------| +| `camex-aarch64` | Linux AArch64, static (musl) | +| `camex-arm` | Linux 32-bit ARM, static (musl): OpenIPC cameras | +| `camex-mipsel` | Linux MIPS little-endian, static (musl, soft-float): Ingenic cameras, routers | +| `camex-x86_64` | Linux x86_64, static (musl) | +| `camex-android.apk` | Android 8.0 and later | +| `camex-macos.zip` | macOS (utun): `camex`, LICENSE, README | +| `camex-windows-x86_64.zip` | Windows 64-bit: `camex.exe` and `wintun.dll` | +| `camex-windows-i686.zip` | Windows 32-bit: `camex.exe` and `wintun.dll` | +| `SHA256SUMS` | SHA256 of all the files above | + +```sh +URL=https://github.com/OpenIPC/camex/releases/latest/download +curl -fL -O $URL/camex-arm -O $URL/SHA256SUMS +sha256sum -c --ignore-missing SHA256SUMS +``` + +The change history is in [CHANGELOG.md](CHANGELOG.md). + ## Binary Sizes | Platform | Toolchain | Size | @@ -716,35 +745,34 @@ The **only** place to change the software version is `src/version.h`: #define CAMEX_VERSION "X.Y.Z" ``` -All other version references (Android, CI, git tags) **must** match this value. -The CI pipeline enforces this automatically: a release build will fail if the -git tag does not match `CAMEX_VERSION`. +The Android `versionName` and `versionCode` follow it: `scripts/sync-android-version.sh` +(or `make apk`, which calls it) updates `android/app/build.gradle.kts` and +`android/app/src/main/AndroidManifest.xml`. + +### Release process -### Release checklist +There are no version tags and no versioned releases: one release, `latest`, is +replaced by CI after every push to `master`, once all build and test jobs pass. -1. **Bump `src/version.h`** — update `CAMEX_VERSION` to the new version. -2. **Sync Android versions** — run `scripts/sync-android-version.sh` (or `make apk` - which calls it automatically). This updates: - - `android/app/build.gradle.kts` — `versionName` + `versionCode` - - `android/app/src/main/AndroidManifest.xml` — `android:versionName` + `android:versionCode` -3. **Commit**: `git commit -m "chore: bump version to X.Y.Z"` -4. **Tag**: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` -5. **Push**: `git push origin master && git push origin vX.Y.Z` -6. **CI creates the GitHub Release** automatically with artifacts named - `camex-X.Y.Z-`. +1. **In a PR**: bump `CAMEX_VERSION`, run `scripts/sync-android-version.sh`, add the + version's section to `CHANGELOG.md`. +2. **Merge into `master`**. CI deletes the old `latest` release and its tag and creates + it again on the built commit: title `Camex X.Y.Z`, the files from + [Permanent links](#permanent-links), `SHA256SUMS`. +3. **Telegram**: when the version differs from the title of the previous `latest`, CI + posts the APK to the OpenIPC Telegram channel. A push without a version bump only + replaces the files. ### Version numbering rules -- `versionName` (Android) = `CAMEX_VERSION` (e.g., `"2.3.6"`) -- `versionCode` (Android) = `MAJOR * 10000 + MINOR * 100 + PATCH` (e.g., `20306`) -- Git tag = `v` + `CAMEX_VERSION` (e.g., `v2.3.6`) -- GitHub Release title = `camex vX.Y.Z` +- `versionName` (Android) = `CAMEX_VERSION` (e.g., `"3.0.2"`) +- `versionCode` (Android) = `MAJOR * 10000 + MINOR * 100 + PATCH` (e.g., `30002`) +- GitHub Release title = `Camex X.Y.Z`, tag `latest` ### Manual CI trigger -If a release needs to be triggered manually (e.g., re-uploading artifacts), -use the `workflow_dispatch` event in the Actions tab with the `version` input -set to the tag name (e.g., `v2.3.6`). +To rebuild and republish `latest` without a new commit, run the workflow with +`workflow_dispatch` on `master` in the Actions tab. ## Notes diff --git a/README.ru.md b/README.ru.md index 1307f6f..fe33ce7 100644 --- a/README.ru.md +++ b/README.ru.md @@ -5,7 +5,7 @@ [English](README.md) | **Русский** | [简体中文](README.zh-CN.md) [![camex CI](https://github.com/OpenIPC/camex/actions/workflows/build.yml/badge.svg)](https://github.com/OpenIPC/camex/actions/workflows/build.yml) -[![Release](https://img.shields.io/github/v/release/OpenIPC/camex)](https://github.com/OpenIPC/camex/releases) +[![Release](https://img.shields.io/badge/download-latest-blue)](https://github.com/OpenIPC/camex/releases/latest) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Code size](https://img.shields.io/github/languages/code-size/OpenIPC/camex)](https://github.com/OpenIPC/camex) @@ -80,6 +80,35 @@ sudo ./camex --mode client \ - **Advanced** — MTU, keepalive и таймаут сервера ([скриншот](docs/images/android-settings-advanced.png)). +## Постоянные ссылки + +Бинарные файлы публикуются одним релизом, [`latest`](https://github.com/OpenIPC/camex/releases/latest), +который CI пересобирает при каждом push в `master`. В его заголовке — версия camex +(`Camex X.Y.Z`); в именах файлов версии нет, поэтому ссылки не меняются: + +- страница релиза: `https://github.com/OpenIPC/camex/releases/latest` +- файл: `https://github.com/OpenIPC/camex/releases/latest/download/<файл>` + +| Файл | Платформа | +|----------------------------|----------------------------------------------------------------------------------| +| `camex-aarch64` | Linux AArch64, статически (musl) | +| `camex-arm` | Linux 32-bit ARM, статически (musl): камеры OpenIPC | +| `camex-mipsel` | Linux MIPS little-endian, статически (musl, soft-float): камеры Ingenic, роутеры | +| `camex-x86_64` | Linux x86_64, статически (musl) | +| `camex-android.apk` | Android 8.0 и новее | +| `camex-macos.zip` | macOS (utun): `camex`, LICENSE, README | +| `camex-windows-x86_64.zip` | Windows 64-bit: `camex.exe` и `wintun.dll` | +| `camex-windows-i686.zip` | Windows 32-bit: `camex.exe` и `wintun.dll` | +| `SHA256SUMS` | SHA256 всех файлов выше | + +```sh +URL=https://github.com/OpenIPC/camex/releases/latest/download +curl -fL -O $URL/camex-arm -O $URL/SHA256SUMS +sha256sum -c --ignore-missing SHA256SUMS +``` + +История изменений — в [CHANGELOG.md](CHANGELOG.md). + ## Размер бинарных файлов | Платформа | Тулчейн | Размер | @@ -722,35 +751,33 @@ sudo rmmod camex #define CAMEX_VERSION "X.Y.Z" ``` -Все остальные упоминания версии (Android, CI, git-теги) **должны** совпадать с этим значением. -CI-конвейер проверяет это автоматически: релизная сборка завершится ошибкой, если -git-тег не совпадает с `CAMEX_VERSION`. +`versionName` и `versionCode` Android следуют за ней: `scripts/sync-android-version.sh` +(или `make apk`, который его вызывает) обновляет `android/app/build.gradle.kts` и +`android/app/src/main/AndroidManifest.xml`. + +### Процесс релиза -### Чек-лист релиза +Тегов версий и релизов по версиям нет: один релиз, `latest`, CI заменяет после +каждого push в `master`, когда все сборки и тесты прошли. -1. **Поднимите версию в `src/version.h`** — обновите `CAMEX_VERSION`. -2. **Синхронизируйте версии Android** — запустите `scripts/sync-android-version.sh` (или `make apk`, - который вызывает его автоматически). Обновляются: - - `android/app/build.gradle.kts` — `versionName` + `versionCode` - - `android/app/src/main/AndroidManifest.xml` — `android:versionName` + `android:versionCode` -3. **Коммит**: `git commit -m "chore: bump version to X.Y.Z"` -4. **Тег**: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` -5. **Push**: `git push origin master && git push origin vX.Y.Z` -6. **CI автоматически создаёт GitHub Release** с артефактами вида - `camex-X.Y.Z-`. +1. **В PR**: поднять `CAMEX_VERSION`, запустить `scripts/sync-android-version.sh`, добавить + раздел версии в `CHANGELOG.md`. +2. **Смержить в `master`**. CI удаляет старый релиз `latest` вместе с тегом и создаёт его + заново на собранном коммите: заголовок `Camex X.Y.Z`, файлы из раздела + [«Постоянные ссылки»](#постоянные-ссылки), `SHA256SUMS`. +3. **Telegram**: если версия отличается от заголовка прежнего `latest`, CI отправляет APK + в Telegram-канал OpenIPC. Push без смены версии только заменяет файлы. ### Правила нумерации версий -- `versionName` (Android) = `CAMEX_VERSION` (например, `"2.3.6"`) -- `versionCode` (Android) = `MAJOR * 10000 + MINOR * 100 + PATCH` (например, `20306`) -- Git-тег = `v` + `CAMEX_VERSION` (например, `v2.3.6`) -- Заголовок GitHub Release = `camex vX.Y.Z` +- `versionName` (Android) = `CAMEX_VERSION` (например, `"3.0.2"`) +- `versionCode` (Android) = `MAJOR * 10000 + MINOR * 100 + PATCH` (например, `30002`) +- Заголовок GitHub Release = `Camex X.Y.Z`, тег `latest` ### Ручной запуск CI -Если релиз нужно запустить вручную (например, чтобы заново загрузить артефакты), -используйте событие `workflow_dispatch` на вкладке Actions, указав во входном параметре -`version` имя тега (например, `v2.3.6`). +Чтобы пересобрать и заново опубликовать `latest` без нового коммита, запустите workflow +через `workflow_dispatch` на ветке `master` на вкладке Actions. ## Примечания diff --git a/README.zh-CN.md b/README.zh-CN.md index 056ad16..34aa265 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -5,7 +5,7 @@ [English](README.md) | [Русский](README.ru.md) | **简体中文** [![camex CI](https://github.com/OpenIPC/camex/actions/workflows/build.yml/badge.svg)](https://github.com/OpenIPC/camex/actions/workflows/build.yml) -[![Release](https://img.shields.io/github/v/release/OpenIPC/camex)](https://github.com/OpenIPC/camex/releases) +[![Release](https://img.shields.io/badge/download-latest-blue)](https://github.com/OpenIPC/camex/releases/latest) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Code size](https://img.shields.io/github/languages/code-size/OpenIPC/camex)](https://github.com/OpenIPC/camex) @@ -79,6 +79,35 @@ sudo ./camex --mode client \ - **Advanced** — MTU、keepalive 和服务器超时 ([截图](docs/images/android-settings-advanced.png))。 +## 永久链接 + +二进制文件只发布在一个版本中:[`latest`](https://github.com/OpenIPC/camex/releases/latest), +CI 会在每次推送到 `master` 时重新构建。其标题显示 camex 版本(`Camex X.Y.Z`); +文件名不含版本号,因此以下链接始终不变: + +- 发布页面:`https://github.com/OpenIPC/camex/releases/latest` +- 单个文件:`https://github.com/OpenIPC/camex/releases/latest/download/<文件>` + +| 文件 | 平台 | +|----------------------------|---------------------------------------------------------------| +| `camex-aarch64` | Linux AArch64,静态链接(musl) | +| `camex-arm` | Linux 32 位 ARM,静态链接(musl):OpenIPC 摄像头 | +| `camex-mipsel` | Linux MIPS 小端,静态链接(musl,软浮点):君正摄像头、路由器 | +| `camex-x86_64` | Linux x86_64,静态链接(musl) | +| `camex-android.apk` | Android 8.0 及更高版本 | +| `camex-macos.zip` | macOS(utun):`camex`、LICENSE、README | +| `camex-windows-x86_64.zip` | Windows 64 位:`camex.exe` 和 `wintun.dll` | +| `camex-windows-i686.zip` | Windows 32 位:`camex.exe` 和 `wintun.dll` | +| `SHA256SUMS` | 以上所有文件的 SHA256 | + +```sh +URL=https://github.com/OpenIPC/camex/releases/latest/download +curl -fL -O $URL/camex-arm -O $URL/SHA256SUMS +sha256sum -c --ignore-missing SHA256SUMS +``` + +变更历史见 [CHANGELOG.md](CHANGELOG.md)。 + ## 二进制大小 | 平台 | 工具链 | 大小 | @@ -693,35 +722,32 @@ sudo rmmod camex #define CAMEX_VERSION "X.Y.Z" ``` -其他所有版本引用(Android、CI、git 标签)都**必须**与该值一致。 -CI 流水线会自动强制检查:如果 git 标签与 `CAMEX_VERSION` -不一致,发布构建将会失败。 +Android 的 `versionName` 和 `versionCode` 随之变化:`scripts/sync-android-version.sh` +(或调用它的 `make apk`)会更新 `android/app/build.gradle.kts` 和 +`android/app/src/main/AndroidManifest.xml`。 + +### 发布流程 -### 发布检查清单 +没有版本标签,也没有按版本的发布:只有一个发布 `latest`,每次推送到 `master` +且所有构建和测试通过后由 CI 替换。 -1. **更新 `src/version.h`** —— 将 `CAMEX_VERSION` 改为新版本号。 -2. **同步 Android 版本** —— 运行 `scripts/sync-android-version.sh`(或 `make apk`, - 它会自动调用该脚本)。该脚本会更新: - - `android/app/build.gradle.kts` —— `versionName` + `versionCode` - - `android/app/src/main/AndroidManifest.xml` —— `android:versionName` + `android:versionCode` -3. **提交**:`git commit -m "chore: bump version to X.Y.Z"` -4. **打标签**:`git tag -a vX.Y.Z -m "Release vX.Y.Z"` -5. **推送**:`git push origin master && git push origin vX.Y.Z` -6. **CI 自动创建 GitHub Release**,产物命名为 - `camex-X.Y.Z-`。 +1. **在 PR 中**:更新 `CAMEX_VERSION`,运行 `scripts/sync-android-version.sh`,并在 + `CHANGELOG.md` 中添加该版本的章节。 +2. **合并到 `master`**。CI 删除旧的 `latest` 发布及其标签,并在构建所用的提交上重新创建: + 标题 `Camex X.Y.Z`,[永久链接](#永久链接)中列出的文件,以及 `SHA256SUMS`。 +3. **Telegram**:当版本与之前 `latest` 的标题不同时,CI 会将 APK 发送到 OpenIPC + Telegram 频道。未更改版本的推送只替换文件。 ### 版本号规则 -- `versionName`(Android)= `CAMEX_VERSION`(例如 `"2.3.6"`) -- `versionCode`(Android)= `MAJOR * 10000 + MINOR * 100 + PATCH`(例如 `20306`) -- Git 标签 = `v` + `CAMEX_VERSION`(例如 `v2.3.6`) -- GitHub Release 标题 = `camex vX.Y.Z` +- `versionName`(Android)= `CAMEX_VERSION`(例如 `"3.0.2"`) +- `versionCode`(Android)= `MAJOR * 10000 + MINOR * 100 + PATCH`(例如 `30002`) +- GitHub Release 标题 = `Camex X.Y.Z`,标签 `latest` ### 手动触发 CI -如果需要手动触发发布(例如重新上传产物), -请在 Actions 页面使用 `workflow_dispatch` 事件,并将 `version` 输入 -设置为标签名(例如 `v2.3.6`)。 +如需在没有新提交的情况下重新构建并发布 `latest`,请在 Actions 页面对 `master` +分支以 `workflow_dispatch` 运行该工作流。 ## 说明