vpnhide/docs/releasing.md
okhsunrog 0a9fcef3c0 ci: rename vpnhide APK artifact + publish as draft release
Two tweaks driven by the same goal — make the artifact list on the CI
run page less ambiguous and give the release step a review gate.

- The APK artifact was named `vpnhide`, which blends in with the other
  module-zip artifacts (`vpnhide-kmod-*`, `vpnhide-zygisk`,
  `vpnhide-ports`). Rename to `vpnhide-apk` so every entry in the
  Artifacts list names the thing you actually get when you download it.
- Release-on-tag job now creates a DRAFT GitHub release instead of
  publishing directly. Gives a chance to eyeball the release notes and
  attached binaries before they go public, and avoids racing
  update-json.sh against the assets becoming reachable.

docs/releasing.md and the release.py post-run hints updated to reflect
the manual Publish step and the fact that update-json still has to
wait for the release to be *published*, not just drafted (draft
release assets sit behind auth).
2026-04-17 15:54:32 +03:00

3.4 KiB

Releasing

Model

  • VERSION file = the last released version on main. It is only modified by release.py.
  • changelog.json.unreleased = work in progress. Entries accumulate here via ./scripts/changelog.py during normal development. See changelog.md.
  • Intermediate builds (main, feature branches, local) get a version string derived from git describe — propagated into module.prop / APK versionName at build time. See build versions below.

Cutting a release

  1. Make sure everything you want in the release is merged to main and the unreleased section of changelog.json lists exactly what should appear in the release notes.
  2. Run the release script with the new version:
    ./scripts/release.py 0.6.2
    
    Atomically:
    • promotes unreleasedhistory[0] with version: "0.6.2",
    • resets unreleased to empty,
    • writes 0.6.2 to VERSION,
    • patches versionName/versionCode in every module.prop, Cargo.toml, and build.gradle.kts,
    • regenerates CHANGELOG.md and update-json/changelog.md.
  3. Commit, tag, push:
    git commit -am "chore: release v0.6.2"
    git tag v0.6.2
    git push
    git push origin v0.6.2
    
  4. Wait for CI to finish the build. CI creates a draft GitHub release with all artifacts attached and release notes extracted from CHANGELOG.md — review it on the Releases page and click Publish release when you're happy.
  5. Generate update-json files pointing at the new release assets:
    ./scripts/update-json.sh
    
  6. Commit and push:
    git commit -am "chore: update-json for v0.6.2"
    git push
    

Why update-json is a separate commit

Update-json must be committed after the GitHub release is published (i.e. after you promote the draft to public). Magisk and KSU fetch these files to decide whether an update is available, then download the zip from the URL inside. Draft releases are private — their asset URLs require auth — so update-json must not point at them.

Notes

  • versionCode is derived automatically by release.py as major*10000 + minor*100 + patch (e.g. 0.6.2602).
  • If unreleased has no entries when you run release.py, it warns but proceeds — useful for version-only bumps.
  • release.py refuses to release a version that already exists in history[].

Build versions

Every packaging step runs ./scripts/build-version.sh to compute the version string stamped into the artifact:

  • On a release tag vX.Y.Z: X.Y.Z
  • N commits after the nearest tag: X.Y.Z-N-gSHA (the git describe format)
  • Working tree dirty: additional -dirty suffix
  • No git / no tags: falls back to the VERSION file

This string goes into:

  • module.prop version=... (visible in the Magisk/KSU manager app)
  • APK versionName (visible in Android Settings → Apps, diagnostic debug zip, BuildConfig.VERSION_NAME)
  • Inside the zip filenames (only for release tags; dev artifacts in CI keep a stable name)

The committed module.prop files are not modified — build-zip.sh stages a copy, patches the version there, and zips. lsposed/app/build.gradle.kts evaluates build-version.sh at configure time and sets versionName dynamically.

versionCode stays at the value baked in by the last release.py run (monotonically increasing integer required by Android/Magisk).