ci!: drop the opkg lane — apk only, and fix the stale rolling release
Both routers are past opkg: mini_router runs ImmortalWrt 25.12.1 and main_router OpenWrt 25.12.0, both with apk-tools 3.0.5, and main_router has no `opkg` binary at all. The 24.10 lane was building and signing a feed no device could consume. Removed jobs `build` and `release` with the scripts only they called (ci/build-feed.sh, ci/sdk-build.sh, ci/make-index.sh, ci/install-usign.sh) and the usign trust anchor dist/shater-feed.pub. A committed public key is an instruction: it invites the old install path for a feed that is no longer produced. The key is retired, not revoked -- git history keeps it, KEY_BUILD still holds the secret half, and a usign secret contains its own public half, so the identity is reconstructible if a 24.10 device ever needs serving. D7 is marked SUPERSEDED by the new D22 rather than deleted. Separately: the rolling `apk-latest-<arch>` release was frozen at 0.2.0 from 2026-07-24 while every tag run published its versioned release correctly. The publish loop was an either/or -- `TAG=apk-latest-<arch>` when VER=latest (workflow_dispatch only), ELSE `TAG=apk-<ver>-<arch>` -- so a `v*` tag run never touched the rolling pointer. Asset replacement was never the problem; ci/gitea-release.sh already deletes before recreating. A router pinned to the rolling URL sat on 0.2.0 while `apk update` reported success: silent staleness, the failure mode this repo keeps having to close. The rolling pointer is now published on EVERY run, tag runs included, and a new assert reads the release back over the API afterwards: our three tag-versioned packages at the built version plus the index and the key must be present (exit 13), and no package asset at any other version may survive (exit 14). Same class of check as sdk-build-apk.sh's package-version assert, added for the same reason -- the previous failure mode was silent. KEY_BUILD can now be deleted from the Gitea repo secrets; nothing references it. Docs state plainly that mini_router is deliberately pinned to a versioned URL and that the hand-edit per release is the price of pinning. Known consequence: the x86_64 QEMU testbed is still OpenWrt 24.10.3 and can no longer install our packages. Its 25.12 rebuild is in flight separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4PcWfrBRyg4eWN58axaGN
This commit is contained in:
+150
-315
@@ -1,36 +1,45 @@
|
||||
# Shater v0.2 — build the 4-package signed opkg feed and publish it as a rolling
|
||||
# Gitea release consumable as an `src/gz` feed.
|
||||
# Shater v0.2 — build the 4-package signed **apk** feed and publish it as
|
||||
# per-arch Gitea releases consumable as an apk repository.
|
||||
#
|
||||
# WHAT CHANGED FROM v0.1
|
||||
# v0.1 shipped 3 packages: xrayctl (SDK-compiled Go) + shater-core +
|
||||
# luci-app-shater (hand-packed data .ipk). v0.2 collapses the runtime into ONE
|
||||
# forked binary and ships 4 packages, all built the canonical SDK way:
|
||||
# WHAT WE SHIP
|
||||
# ONE forked binary plus its OpenWrt glue, 4 packages, all built the canonical
|
||||
# SDK way:
|
||||
# - shaterd PREBUILT static-musl + SPA-embedded + UPX binary. Built
|
||||
# OUT OF TREE by scripts/build-shaterd.sh (Go + Node + UPX)
|
||||
# and staged into openwrt/shaterd/files/ BEFORE the SDK
|
||||
# build; the openwrt/shaterd package just $(INSTALL_BIN)s
|
||||
# the arch-matched artifact. (arch-specific .ipk)
|
||||
# the arch-matched artifact. (arch-specific .apk)
|
||||
# - shater-core data glue, PKGARCH=all
|
||||
# - luci-app-shater LuCI thin launcher, PKGARCH=all (uses feeds/luci/luci.mk)
|
||||
# - byedpi ciadpi, C cross-compiled from source by the SDK (arch-specific)
|
||||
#
|
||||
# TARGET HARDWARE / ARCH MATRIX
|
||||
# x86_64 -> the QEMU testbed VM (generic x86-64).
|
||||
# aarch64_cortex-a53 -> BOTH production routers (BPI-R3 + BPI-R4, mediatek/filogic).
|
||||
# aarch64_cortex-a53 -> BOTH production routers (BPI-R3 mini + BPI-R4,
|
||||
# mediatek/filogic), both on 25.12 with apk-tools 3.
|
||||
# Only shaterd + byedpi are arch-specific; shater-core + luci-app-shater are
|
||||
# PKGARCH=all, so one build of each covers every device. opkg filters by
|
||||
# Architecture at install time, so a single combined feed URL serves all.
|
||||
# PKGARCH=all, so one build of each covers every device — but the RELEASES
|
||||
# are still per-arch (see the release-apk job for why).
|
||||
#
|
||||
# FEED SIGNING (opkg / usign — OpenWrt 24.10 is opkg, not apk; apk lands at 25.12)
|
||||
# The feed index (Packages) is usign-signed with the SECRET key in the Gitea
|
||||
# repo secret KEY_BUILD; routers verify it with the committed public key
|
||||
# dist/shater-feed.pub (fingerprint 5ac4b177689cb8e0). Do NOT regenerate the
|
||||
# key — that invalidates every deployed router's trust.
|
||||
# FORMAT: apk ONLY (25.12+)
|
||||
# The fleet runs OpenWrt/ImmortalWrt 25.12, where opkg is replaced by Alpine
|
||||
# apk (.apk files, binary packages.adb index, EC keys in /etc/apk/keys/). The
|
||||
# old .ipk lane was removed in 2026-07 (docs-shater/DECISIONS.md D22): no
|
||||
# device we serve has an opkg binary at all, so building and signing a second
|
||||
# feed served nobody.
|
||||
#
|
||||
# FEED SIGNING (EC / apk)
|
||||
# packages.adb is signed with the EC (prime256v1) SECRET key in the Gitea repo
|
||||
# secret KEY_APK; routers verify it with the committed public key
|
||||
# dist/shater-apk.pem (ci/gen-apk-key.sh). Do NOT regenerate the key — that
|
||||
# invalidates every deployed router's trust.
|
||||
#
|
||||
# AUTO-RELEASE
|
||||
# push a tag `vX.Y.Z` -> versioned release. workflow_dispatch / (optional) main
|
||||
# -> rolling `latest` pre-release (always-fresh feed). Publish uses the Gitea
|
||||
# API via curl (ci/gitea-release.sh) — no external action needed.
|
||||
# push a tag `vX.Y.Z` -> versioned per-arch releases `apk-vX.Y.Z-<arch>`.
|
||||
# workflow_dispatch -> rolling per-arch `apk-latest-<arch>` (always-fresh
|
||||
# feed). Publish uses the Gitea API via curl (ci/gitea-release.sh) — no
|
||||
# external action needed. NOTE: the apk release tags deliberately do NOT start
|
||||
# with `v` so publishing them cannot re-trigger this workflow's `v*` filter.
|
||||
#
|
||||
# PACKAGE VERSIONING (bug B4)
|
||||
# PKG_VERSION/PKG_RELEASE are NOT hand-written in the Makefiles any more. They
|
||||
@@ -41,28 +50,13 @@
|
||||
# exported via $GITHUB_ENV):
|
||||
# tag `vX.Y.Z` -> X.Y.Z-r1
|
||||
# anything else -> <nearest tag>-r<commits since it + 1>
|
||||
# and hands them to the SDK builds as SHATER_PKG_VERSION/SHATER_PKG_RELEASE;
|
||||
# and hands them to the SDK build as SHATER_PKG_VERSION/SHATER_PKG_RELEASE;
|
||||
# $SHATER_VERSION (the same numbers, plus the short sha off-tag) is stamped
|
||||
# into the binary's constant.Version. ci/sdk-build*.sh then ASSERT that the
|
||||
# built .ipk/.apk really carry that version, so the failure can never be
|
||||
# silent again. This is also why both build jobs check out with fetch-depth: 0
|
||||
# into the binary's constant.Version. ci/sdk-build-apk.sh then ASSERTS that the
|
||||
# built .apk really carry that version, so the failure can never be silent
|
||||
# again. This is also why the build job checks out with fetch-depth: 0
|
||||
# — `git describe` needs tags and ancestry. `byedpi` is excluded: it keeps
|
||||
# upstream ByeDPI's own PKG_VERSION (see openwrt/byedpi/Makefile).
|
||||
#
|
||||
# APK LANE (25.12+, ADDITIVE — T2)
|
||||
# The fleet is migrating to BananaWRT 25.12-mtk-vendor (= ImmortalWrt 25.12
|
||||
# base), where opkg is replaced by Alpine apk (.apk, binary packages.adb
|
||||
# index, EC keys in /etc/apk/keys/). The `build-apk` + `release-apk` jobs
|
||||
# below build the SAME 4 packages through the ImmortalWrt 25.12 apk-SDK and
|
||||
# publish PER-ARCH apk repos as releases `apk-latest-<arch>` (rolling) /
|
||||
# `apk-<tag>-<arch>` (versioned). Per-arch because apk filenames carry no
|
||||
# architecture (shaterd-0.2.0-r1.apk would collide across arches in one flat
|
||||
# release) and apk fetches packages relative to the packages.adb URL.
|
||||
# Signed with the EC key in the Gitea secret KEY_APK; trust anchor
|
||||
# dist/shater-apk.pem (ci/gen-apk-key.sh). The usign/opkg lane above is
|
||||
# UNCHANGED and keeps serving the 24.10 fleet. NOTE: the apk release tags
|
||||
# deliberately do NOT start with `v` so publishing them cannot re-trigger
|
||||
# this workflow's `v*` tag filter.
|
||||
|
||||
# CACHING (T3 — fast CI)
|
||||
# All caches use actions/cache pinned to v3.3.2: the LAST release speaking the
|
||||
@@ -80,34 +74,33 @@
|
||||
# (PKG_VERSION/PKG_HASH live there). Stale-safe: the buildroot verifies
|
||||
# PKG_HASH on every dl/ file and re-downloads on mismatch, so restore-keys
|
||||
# prefix fallback is allowed.
|
||||
# - Go module + build cache — key = hash of go.sum; shared by all 4 build
|
||||
# - Go module + build cache — key = hash of go.sum; shared by both build
|
||||
# jobs (each builds both GOARCHes).
|
||||
# - panel/node_modules — key = hash of panel/package-lock.json, exact-only
|
||||
# (a lockfile change MUST miss); on hit build-shaterd.sh gets --fast.
|
||||
# - apt .deb archives for the apk lane's debian:bookworm host-deps
|
||||
# (.cache/apt) — key = hash of ci/sdk-build-apk.sh (the apt list is in it).
|
||||
# - usign binary (.cache/tools) — static helper, fixed key.
|
||||
# - apt .deb archives for the debian:bookworm host-deps of the apk SDK
|
||||
# container (.cache/apt) — key = hash of ci/sdk-build-apk.sh (the apt list
|
||||
# is in it).
|
||||
# - SDK feeds/ git checkouts (.cache/feeds) — the single biggest recurring
|
||||
# cost: `scripts/feeds update -a` cloned base+packages+luci+routing+
|
||||
# telephony EVERY run (~7 min/job; github.com is ~1 MB/s from this
|
||||
# runner — run 51 evidence). The feeds dir is symlinked into the SDK
|
||||
# container from the workspace cache; `feeds update` on an existing clone
|
||||
# is a fast fetch+checkout of the pinned revs. Correctness-safe: update
|
||||
# always checks out feeds.conf's pins, and ci/sdk-build*.sh wipes the
|
||||
# always checks out feeds.conf's pins, and ci/sdk-build-apk.sh wipes the
|
||||
# cache + re-clones fresh if update ever fails on a cached checkout.
|
||||
# Key = lane + SDK release (shared across the two arch jobs of a lane —
|
||||
# same release pins identical feed revs; the sequential runner means the
|
||||
# second arch restores what the first saved). restore-keys lets an SDK
|
||||
# version bump start from the old clones (git fetch delta, not re-clone).
|
||||
# Key = lane + SDK release (shared across the two arch jobs — the same
|
||||
# release pins identical feed revs; the sequential runner means the second
|
||||
# arch restores what the first saved). restore-keys lets an SDK version
|
||||
# bump start from the old clones (git fetch delta, not re-clone).
|
||||
# Act_runner facts this design leans on (verified in run 51 logs):
|
||||
# - the cache backend works: restores/saves confirmed, hashFiles() works;
|
||||
# - docker images (openwrt/sdk, debian:bookworm, runner-images) live on the
|
||||
# PERSISTENT host daemon — "Image is up to date" each run, no re-download;
|
||||
# - docker images (debian:bookworm, runner-images) live on the PERSISTENT
|
||||
# host daemon — "Image is up to date" each run, no re-download;
|
||||
# - each actions/cache SAVE is followed by an exact 3-minute act_runner
|
||||
# stall (node process lingers; hit→no-save→no stall). Steady state saves
|
||||
# nothing, so adding cache entries is fine, but keys that change every
|
||||
# run (e.g. github.sha) would cost +3 min/entry/run — do NOT do that.
|
||||
|
||||
name: release
|
||||
|
||||
on:
|
||||
@@ -125,148 +118,11 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.arch }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x86_64, sdk: x86_64-24.10.4 } # testbed VM (generic x86-64)
|
||||
- { arch: aarch64_cortex-a53, sdk: mediatek-filogic-24.10.4 } # BPI-R3 + BPI-R4 (mediatek/filogic)
|
||||
steps:
|
||||
# fetch-depth: 0 — the package version is DERIVED from the git tag
|
||||
# (ci/version.sh: nearest `vX.Y.Z` + commits since it). The default
|
||||
# shallow checkout has neither tags nor ancestry, so `git describe` would
|
||||
# fail and every dispatch build would fall back to 0.0.0.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# scripts/build-shaterd.sh builds the engine via a go.mod
|
||||
# `replace => ./submodules/wireguard-go` (AmneziaWG fork), so that submodule
|
||||
# must be present or `go build` dies with "no such file or directory".
|
||||
# actions/checkout does not fetch submodules by default; init ONLY this one
|
||||
# (clients/apple+android are large and unused here).
|
||||
- name: Init wireguard-go submodule (awg)
|
||||
run: git submodule update --init --depth 1 submodules/wireguard-go
|
||||
|
||||
# THE version step (bug B4). One computation, used by both the binary
|
||||
# (constant.Version) and the three tag-versioned packages, exported to
|
||||
# every later step of this job:
|
||||
# tag vX.Y.Z -> X.Y.Z-r1 ; off-tag -> <last tag>-r<commits+1>
|
||||
- name: Compute version from git tag
|
||||
run: bash ci/version.sh --env >> "$GITHUB_ENV"
|
||||
|
||||
# Toolchain for scripts/build-shaterd.sh: Go (daemon), Node (Vite SPA), UPX.
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod # pins Go 1.24.7 (go.mod `go` line)
|
||||
cache: false # explicit actions/cache@v3.3.2 below (setup-go's
|
||||
# built-in cache uses the new API act_runner lacks)
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20' # Vite 5 needs Node 18+; 20 LTS
|
||||
|
||||
# ---- caches (see the header comment for keys + version pin rationale) ----
|
||||
- name: Cache Go modules + build cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: go-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
go-
|
||||
|
||||
- name: Cache panel node_modules
|
||||
id: npm-cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: panel/node_modules
|
||||
key: npm-${{ hashFiles('panel/package-lock.json') }}
|
||||
# NO restore-keys: node_modules must exactly match the lockfile;
|
||||
# on any lockfile change this misses and `npm ci` runs fresh.
|
||||
|
||||
- name: Cache SDK dl/ (package sources)
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: .cache/dl
|
||||
key: dl-${{ hashFiles('openwrt/*/Makefile') }}
|
||||
restore-keys: |
|
||||
dl-
|
||||
|
||||
# feeds git checkouts (see header): both 24.10.4 arch jobs share one entry
|
||||
# (same release = same feeds.conf.default pins), so derive the release
|
||||
# from the matrix sdk tag (x86_64-24.10.4 -> 24.10.4).
|
||||
- name: Compute feeds cache key
|
||||
id: feedskey
|
||||
run: echo "ver=$(echo '${{ matrix.sdk }}' | sed 's/.*-//')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache SDK feeds checkouts
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: .cache/feeds
|
||||
key: feeds-opkg-${{ steps.feedskey.outputs.ver }}
|
||||
restore-keys: |
|
||||
feeds-opkg-
|
||||
|
||||
- name: Cache CI tools (usign)
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: .cache/tools
|
||||
key: tools-usign-v1
|
||||
|
||||
- name: Install UPX
|
||||
run: sudo apt-get update -qq && sudo apt-get install -y -qq upx-ucl
|
||||
|
||||
# Build the SPA-embedded, static-musl, UPX'd shaterd for BOTH arches and
|
||||
# stage dist/shaterd-<a>.upx into openwrt/shaterd/files/. MUST run before
|
||||
# the SDK package build (the openwrt/shaterd package installs the staged
|
||||
# artifact). $SHATER_VERSION (from the version step above) is stamped into
|
||||
# constant.Version, so the binary and the package agree. On an exact
|
||||
# node_modules cache hit, --fast skips the redundant `npm ci`.
|
||||
- name: Build & stage shaterd artifact
|
||||
env:
|
||||
NPM_CACHE_HIT: ${{ steps.npm-cache.outputs.cache-hit }}
|
||||
run: |
|
||||
set -eu
|
||||
FAST=""
|
||||
if [ "${NPM_CACHE_HIT:-}" = "true" ]; then FAST="--fast"; fi
|
||||
echo "shaterd version: $SHATER_VERSION / package ${SHATER_PKG_VERSION}-r${SHATER_PKG_RELEASE} (npm cache hit: ${NPM_CACHE_HIT:-false})"
|
||||
bash scripts/build-shaterd.sh $FAST
|
||||
|
||||
# Compile the 4 packages through the arch-matched OpenWrt SDK and produce a
|
||||
# signed per-arch opkg feed (Packages + Packages.gz + Packages.sig + .ipk).
|
||||
# SHATER_PKG_VERSION/SHATER_PKG_RELEASE reach the package Makefiles through
|
||||
# the SDK container; ci/sdk-build.sh asserts the .ipk really carry them.
|
||||
- name: Build signed feed (SDK)
|
||||
env:
|
||||
KEY_BUILD: ${{ secrets.KEY_BUILD }}
|
||||
run: bash ci/build-feed.sh "${{ matrix.arch }}" "${{ matrix.sdk }}" "out/${{ matrix.arch }}"
|
||||
|
||||
- name: Show feed
|
||||
run: ls -l "out/${{ matrix.arch }}" && cat "out/${{ matrix.arch }}/Packages"
|
||||
|
||||
- name: Upload feed artifact
|
||||
# v4 uses an artifact backend Gitea Actions does not implement
|
||||
# (GHESNotSupportedError); v3 works on Gitea's act_runner.
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: shater-${{ matrix.arch }}
|
||||
path: out/${{ matrix.arch }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# APK lane (additive): the same 4 packages through the ImmortalWrt 25.12
|
||||
# apk-SDK for the 25.12/apk fleet (BananaWRT 25.12-mtk-vendor routers + the
|
||||
# future 25.12 VM). Produces a per-arch apk repo dir: *.apk + EC-signed
|
||||
# packages.adb + shater-apk.pem. Artifact prefix `apkfeed-` (NOT `shater-`)
|
||||
# so the opkg release job's `artifacts/shater-*` glob never picks these up.
|
||||
# Build the 4 packages through the ImmortalWrt 25.12 apk-SDK for the 25.12/apk
|
||||
# fleet (BPI-R3 mini on BananaWRT 25.12-mtk-vendor, BPI-R4 on OpenWrt 25.12,
|
||||
# and the testbed VM). Produces a per-arch apk repo dir: *.apk + EC-signed
|
||||
# packages.adb + shater-apk.pem, uploaded as the artifact `apkfeed-<arch>`.
|
||||
build-apk:
|
||||
name: apk ${{ matrix.arch }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -281,8 +137,10 @@ jobs:
|
||||
- arch: aarch64_cortex-a53 # BPI-R3 mini (BananaWRT 25.12-mtk-vendor) + BPI-R4
|
||||
sdk_url: https://downloads.immortalwrt.org/releases/25.12.1/targets/mediatek/filogic/immortalwrt-sdk-25.12.1-mediatek-filogic_gcc-14.3.0_musl.Linux-x86_64.tar.zst
|
||||
steps:
|
||||
# fetch-depth: 0 — see the opkg lane: the package version comes from
|
||||
# `git describe`, which needs tags + ancestry.
|
||||
# fetch-depth: 0 — the package version is DERIVED from the git tag
|
||||
# (ci/version.sh: nearest `vX.Y.Z` + commits since it). The default
|
||||
# shallow checkout has neither tags nor ancestry, so `git describe` would
|
||||
# fail and every dispatch build would fall back to 0.0.0.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -295,8 +153,10 @@ jobs:
|
||||
- name: Init wireguard-go submodule (awg)
|
||||
run: git submodule update --init --depth 1 submodules/wireguard-go
|
||||
|
||||
# Same single version computation as the opkg lane — both lanes MUST agree
|
||||
# on the version, they package the identical tree.
|
||||
# THE version step (bug B4). One computation, used by both the binary
|
||||
# (constant.Version) and the three tag-versioned packages, exported to
|
||||
# every later step of this job:
|
||||
# tag vX.Y.Z -> X.Y.Z-r1 ; off-tag -> <last tag>-r<commits+1>
|
||||
- name: Compute version from git tag
|
||||
run: bash ci/version.sh --env >> "$GITHUB_ENV"
|
||||
|
||||
@@ -376,8 +236,12 @@ jobs:
|
||||
- name: Install UPX
|
||||
run: sudo apt-get update -qq && sudo apt-get install -y -qq upx-ucl
|
||||
|
||||
# Same artifact-order contract as the opkg lane: the SPA-embedded shaterd
|
||||
# binary is built OUT of the SDK and staged before the package build.
|
||||
# Artifact-order contract: the SPA-embedded shaterd binary is built OUT of
|
||||
# the SDK and staged into openwrt/shaterd/files/ BEFORE the package build
|
||||
# (the openwrt/shaterd package only installs the staged artifact).
|
||||
# $SHATER_VERSION (from the version step above) is stamped into
|
||||
# constant.Version, so the binary and the package agree. On an exact
|
||||
# node_modules cache hit, --fast skips the redundant `npm ci`.
|
||||
- name: Build & stage shaterd artifact
|
||||
env:
|
||||
NPM_CACHE_HIT: ${{ steps.npm-cache.outputs.cache-hit }}
|
||||
@@ -409,117 +273,21 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Publish once both arches are built. Rolling `latest` on dispatch, a versioned
|
||||
# release on a `vX.Y.Z` tag. Self-contained (curl -> Gitea API).
|
||||
release:
|
||||
name: release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all arch feeds
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Assemble release assets
|
||||
id: assets
|
||||
run: |
|
||||
set -eu
|
||||
mkdir -p release
|
||||
# For each downloaded arch feed: one ready-to-serve tarball + loose ipks.
|
||||
for d in artifacts/shater-*; do
|
||||
[ -d "$d" ] || continue
|
||||
arch="${d#artifacts/shater-}"
|
||||
tar -C "$d" -czf "release/shater-feed-${arch}.tar.gz" .
|
||||
# loose .ipk for direct `opkg install <url>` (dedupe shared _all ipks by name)
|
||||
for ipk in "$d"/*.ipk; do
|
||||
[ -e "$ipk" ] || continue
|
||||
cp -n "$ipk" "release/$(basename "$ipk")"
|
||||
done
|
||||
done
|
||||
# ship the feed's public key so routers can verify (see docs-shater/INSTALL.md)
|
||||
cp -f dist/shater-feed.pub release/shater-feed.pub
|
||||
ls -l release
|
||||
echo "count=$(ls release | wc -l)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# restore the prebuilt usign binary (skips apt + cmake + clone + build)
|
||||
- name: Cache CI tools (usign)
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: .cache/tools
|
||||
key: tools-usign-v1
|
||||
|
||||
- name: Install usign (feed signer)
|
||||
run: bash ci/install-usign.sh
|
||||
|
||||
- name: Build & sign combined opkg feed index
|
||||
# One Packages/Packages.gz over ALL loose .ipk (every arch + arch=all),
|
||||
# with basename Filenames. opkg filters by Architecture, so a single
|
||||
# release URL serves every device: BPI routers pick aarch64_cortex-a53 +
|
||||
# all, the x86 testbed picks x86_64 + all. Signed with KEY_BUILD so
|
||||
# routers keep check_signature on. This is what makes the release directly
|
||||
# consumable as an `src/gz` feed (see docs-shater/INSTALL.md).
|
||||
env:
|
||||
KEY_BUILD: ${{ secrets.KEY_BUILD }}
|
||||
run: bash ci/make-index.sh release
|
||||
|
||||
- name: Determine release identity
|
||||
id: rel
|
||||
run: |
|
||||
set -eu
|
||||
if [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then
|
||||
echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
echo "name=shater ${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
echo "rolling=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tag=latest" >> "$GITHUB_OUTPUT"
|
||||
echo "name=shater latest (main)" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
||||
echo "rolling=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Publish Gitea release
|
||||
env:
|
||||
TOKEN: ${{ secrets.RELEASE_TOKEN != '' && secrets.RELEASE_TOKEN || github.token }}
|
||||
TAG: ${{ steps.rel.outputs.tag }}
|
||||
NAME: ${{ steps.rel.outputs.name }}
|
||||
PRERELEASE: ${{ steps.rel.outputs.prerelease }}
|
||||
ROLLING: ${{ steps.rel.outputs.rolling }}
|
||||
BODY: |
|
||||
Automated build. Packages: shaterd + byedpi (per-arch), shater-core +
|
||||
luci-app-shater (arch=all).
|
||||
Targets: x86_64 (testbed) and aarch64_cortex-a53 (BPI-R3 + BPI-R4, mediatek/filogic).
|
||||
|
||||
── Add as an opkg feed (recommended — then updating is one command) ──
|
||||
This release is itself a SIGNED package feed; opkg filters by
|
||||
architecture, so the same lines work on every device:
|
||||
wget -O /etc/opkg/keys/5ac4b177689cb8e0 https://git.qomar.pw/omar/shater/releases/download/latest/shater-feed.pub
|
||||
echo "src/gz shater https://git.qomar.pw/omar/shater/releases/download/latest" >> /etc/opkg/customfeeds.conf
|
||||
opkg update
|
||||
opkg install luci-app-shater # pulls shater-core + shaterd too
|
||||
The public-key install is one-time; after it, `opkg update/upgrade`
|
||||
verify the signature with check_signature left on. Full guide: docs-shater/INSTALL.md.
|
||||
|
||||
── Update (name our packages — never a bare `opkg upgrade`) ──
|
||||
opkg update
|
||||
opkg upgrade shaterd shater-core luci-app-shater byedpi
|
||||
|
||||
── Or install the loose .ipk directly / from the tarball feed ──
|
||||
wget -O /tmp/f.tgz <this release>/shater-feed-aarch64_cortex-a53.tar.gz
|
||||
mkdir -p /tmp/shater && tar -C /tmp/shater -xzf /tmp/f.tgz
|
||||
opkg install /tmp/shater/luci-app-shater_*_all.ipk
|
||||
run: bash ci/gitea-release.sh release/*
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Publish the apk lane: ONE release PER ARCH (apk package filenames carry no
|
||||
# arch, and apk fetches `<name>-<ver>.apk` relative to the packages.adb URL —
|
||||
# a flat multi-arch release would collide). Rolling `apk-latest-<arch>` on
|
||||
# dispatch, `apk-<tag>-<arch>` on a version tag. The tags do NOT match the
|
||||
# workflow's `v*` trigger, so publishing them cannot re-trigger the build.
|
||||
# Publish: ONE release PER ARCH (apk package filenames carry no arch, and apk
|
||||
# fetches `<name>-<ver>.apk` relative to the packages.adb URL — a flat
|
||||
# multi-arch release would collide). Every run refreshes the ROLLING pointer
|
||||
# `apk-latest-<arch>`; a `vX.Y.Z` tag run ALSO publishes the pinnable
|
||||
# `apk-vX.Y.Z-<arch>`. The tags do NOT match the workflow's `v*` trigger, so
|
||||
# publishing them cannot re-trigger the build.
|
||||
#
|
||||
# WHY THE ROLLING RELEASE IS PUBLISHED ON TAG RUNS TOO (fixed 2026-07-25):
|
||||
# it used to be an either/or — `TAG=apk-latest-<arch>` on dispatch, ELSE
|
||||
# `TAG=apk-<ver>-<arch>` — so once releases moved to tag pushes the rolling
|
||||
# pointer was never written again. It froze at 0.2.0 (published 2026-07-24)
|
||||
# while v0.2.9/v0.2.10 published fine, and every router whose
|
||||
# /etc/apk/repositories.d/shater.list points at the rolling URL kept getting a
|
||||
# successful, silent `apk update` with nothing new. Rolling is the whole point
|
||||
# of that URL, so it is now written unconditionally and asserted afterwards.
|
||||
release-apk:
|
||||
name: release apk
|
||||
needs: build-apk
|
||||
@@ -537,6 +305,9 @@ jobs:
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
# Identity of the VERSIONED release only. The rolling pointer is published
|
||||
# on every run with fixed prerelease=true/rolling=true, so it needs nothing
|
||||
# from here.
|
||||
- name: Determine release identity
|
||||
id: rel
|
||||
run: |
|
||||
@@ -558,21 +329,39 @@ jobs:
|
||||
PRERELEASE: ${{ steps.rel.outputs.prerelease }}
|
||||
ROLLING: ${{ steps.rel.outputs.rolling }}
|
||||
run: |
|
||||
set -eu
|
||||
set -euo pipefail
|
||||
for d in artifacts/apkfeed-*; do
|
||||
[ -d "$d" ] || continue
|
||||
arch="${d#artifacts/apkfeed-}"
|
||||
if [ "$VER" = latest ]; then TAG="apk-latest-$arch"; else TAG="apk-$VER-$arch"; fi
|
||||
ROLL="apk-latest-$arch"
|
||||
|
||||
# The version we just built, read straight off the artifact
|
||||
# (`shaterd-<ver>-r<rel>.apk`). NOT recomputed with ci/version.sh:
|
||||
# this job checks out shallow, so it has no tags to describe from.
|
||||
pkg=""
|
||||
for a in "$d"/shaterd-*.apk; do
|
||||
if [ -f "$a" ]; then pkg="$(basename "$a")"; fi
|
||||
done
|
||||
[ -n "$pkg" ] || { echo "[release-apk] ERROR: no shaterd-*.apk in $d"; exit 11; }
|
||||
want="${pkg#shaterd-}"; want="${want%.apk}"
|
||||
echo "[release-apk] arch=$arch built version=$want"
|
||||
|
||||
BODY="Automated apk (OpenWrt/ImmortalWrt 25.12+) package repo for \`$arch\`.
|
||||
Packages: shaterd + byedpi (per-arch), shater-core + luci-app-shater (arch=all).
|
||||
This build: \`$want\`.
|
||||
The index \`packages.adb\` is EC-signed; trust anchor \`shater-apk.pem\` (also in \`dist/\`).
|
||||
|
||||
── Add as an apk repository ──
|
||||
wget -O /etc/apk/keys/shater-apk.pem https://git.qomar.pw/omar/shater/releases/download/$TAG/shater-apk.pem
|
||||
── Add as an apk repository (rolling — install once, then just update) ──
|
||||
wget -O /etc/apk/keys/shater-apk.pem https://git.qomar.pw/omar/shater/releases/download/$ROLL/shater-apk.pem
|
||||
echo \"https://git.qomar.pw/omar/shater/releases/download/apk-latest-\$(cat /etc/apk/arch)/packages.adb\" > /etc/apk/repositories.d/shater.list
|
||||
apk update
|
||||
apk add luci-app-shater # pulls shater-core + shaterd too
|
||||
apk add byedpi # optional: ByeDPI desync egress
|
||||
\`apk-latest-<arch>\` is a MOVING pointer: every release run replaces its
|
||||
assets, so the same repo line keeps serving the newest build. To pin a
|
||||
version instead, point the repo line at
|
||||
\`.../download/apk-vX.Y.Z-\$(cat /etc/apk/arch)/packages.adb\` — then the
|
||||
file must be edited by hand for each upgrade.
|
||||
── Update — ALWAYS name the packages, NEVER a bare \`apk upgrade\` ──
|
||||
apk update
|
||||
apk upgrade shaterd shater-core luci-app-shater byedpi
|
||||
@@ -580,9 +369,55 @@ jobs:
|
||||
configured repo and can downgrade unrelated system packages; naming them
|
||||
upgrades only those (apk-tools 3: \"If list of packages is provided, only
|
||||
those packages are upgraded along with needed dependencies\").
|
||||
Full guide: docs-shater/INSTALL.md §6. The opkg/24.10 feed lives in the \`latest\` release."
|
||||
echo "[release-apk] publishing $TAG from $d"
|
||||
TAG="$TAG" NAME="shater apk $VER ($arch)" BODY="$BODY" \
|
||||
PRERELEASE="$PRERELEASE" ROLLING="$ROLLING" \
|
||||
Full guide: docs-shater/INSTALL.md §5."
|
||||
|
||||
# 1) the pinnable versioned release (tag runs only)
|
||||
if [ "$VER" != latest ]; then
|
||||
echo "[release-apk] publishing apk-$VER-$arch from $d"
|
||||
TAG="apk-$VER-$arch" NAME="shater apk $VER ($arch)" BODY="$BODY" \
|
||||
PRERELEASE="$PRERELEASE" ROLLING="$ROLLING" \
|
||||
bash ci/gitea-release.sh "$d"/*
|
||||
fi
|
||||
|
||||
# 2) the rolling pointer — ALWAYS, tag run included. ci/gitea-release.sh
|
||||
# deletes the existing release before recreating it, so the old
|
||||
# version's assets are REPLACED, never accumulated (two versions of
|
||||
# one package in one index would let apk choose, not us).
|
||||
echo "[release-apk] publishing $ROLL from $d"
|
||||
TAG="$ROLL" NAME="shater apk latest ($arch)" BODY="$BODY" \
|
||||
PRERELEASE=true ROLLING=true \
|
||||
bash ci/gitea-release.sh "$d"/*
|
||||
|
||||
# 3) ASSERT the rolling release really serves THIS build — same class
|
||||
# of check as ci/sdk-build-apk.sh's package-version assert, and for
|
||||
# the same reason: the previous failure mode was silent. Reads the
|
||||
# published release back over the API and requires our three
|
||||
# tag-versioned packages at $want, the index, the key — and NO
|
||||
# left-over package asset at any other version.
|
||||
api="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/tags/$ROLL"
|
||||
got="$(curl -fsS -H "Authorization: token $TOKEN" "$api" \
|
||||
| tr '{},' '\n\n\n' \
|
||||
| sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | sort -u)" || {
|
||||
echo "[release-apk] ERROR: cannot read back $ROLL from the API"; exit 12; }
|
||||
echo "[release-apk] $ROLL assets: $(printf '%s ' $got)"
|
||||
# here-string, NOT `printf | grep -q`: under `pipefail` the early
|
||||
# exit of grep -q can SIGPIPE the writer and fail a passing check.
|
||||
for f in "shaterd-$want.apk" "shater-core-$want.apk" \
|
||||
"luci-app-shater-$want.apk" packages.adb shater-apk.pem; do
|
||||
grep -qxF "$f" <<<"$got" || {
|
||||
echo "[release-apk] ERROR: $ROLL does not contain '$f' after publish."
|
||||
echo " A router pinned to the rolling URL would have silently"
|
||||
echo " stayed on its old version with a successful apk update."
|
||||
exit 13; }
|
||||
done
|
||||
stale="$(grep -E '^(shaterd|shater-core|luci-app-shater)-.*\.apk$' <<<"$got" \
|
||||
| grep -vxF -e "shaterd-$want.apk" -e "shater-core-$want.apk" \
|
||||
-e "luci-app-shater-$want.apk" || true)"
|
||||
[ -z "$stale" ] || {
|
||||
echo "[release-apk] ERROR: $ROLL still holds stale package assets:"
|
||||
printf ' %s\n' $stale
|
||||
echo " Two versions of one package in one feed = apk picks by its"
|
||||
echo " own rules, not by our intent."
|
||||
exit 14; }
|
||||
echo "[release-apk] OK — $ROLL serves $want"
|
||||
done
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ nul
|
||||
/venv/
|
||||
/test/cache.db
|
||||
|
||||
# feed artifacts (tracked public key dist/shater-feed.pub is force-added)
|
||||
# feed artifacts (the tracked apk trust anchor dist/shater-apk.pem is force-added)
|
||||
/dist/
|
||||
|
||||
# local agent config (CLAUDE.md is deliberately tracked; .claude local settings are not)
|
||||
|
||||
+16
-22
@@ -49,29 +49,22 @@ Full list with MVP/T1/T2 tags — [`docs-shater/FEATURES.md`](docs-shater/FEATUR
|
||||
|
||||
## Install
|
||||
|
||||
Two signed feeds. Pick by the router's OpenWrt version. Verbatim commands and the
|
||||
manual `.ipk`/`.apk` install are in [`docs-shater/INSTALL.md`](docs-shater/INSTALL.md).
|
||||
|
||||
**opkg (OpenWrt 24.10):**
|
||||
One signed **apk** feed (OpenWrt / ImmortalWrt / BananaWRT **25.12+**), one
|
||||
release per arch. Verbatim commands, the manual `.apk` install and the
|
||||
rolling-vs-pinned choice are in
|
||||
[`docs-shater/INSTALL.md`](docs-shater/INSTALL.md).
|
||||
|
||||
```sh
|
||||
wget -O /etc/opkg/keys/5ac4b177689cb8e0 \
|
||||
https://git.qomar.pw/omar/shater/releases/download/latest/shater-feed.pub
|
||||
echo "src/gz shater https://git.qomar.pw/omar/shater/releases/download/latest" \
|
||||
>> /etc/opkg/customfeeds.conf
|
||||
opkg update && opkg install luci-app-shater # -> shater-core -> shaterd
|
||||
```
|
||||
|
||||
**apk (OpenWrt / ImmortalWrt / BananaWRT 25.12+):**
|
||||
|
||||
```sh
|
||||
wget -O /etc/apk/keys/shater-apk.pem \
|
||||
"https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/shater-apk.pem"
|
||||
echo "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/packages.adb" \
|
||||
> /etc/apk/repositories.d/shater.list
|
||||
wget -O /etc/apk/keys/shater-apk.pem "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/shater-apk.pem"
|
||||
echo "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/packages.adb" > /etc/apk/repositories.d/shater.list
|
||||
apk update && apk add luci-app-shater # -> shater-core -> shaterd
|
||||
```
|
||||
|
||||
`apk-latest-<arch>` is a moving pointer refreshed by every release run — install
|
||||
once and `apk update && apk upgrade shaterd shater-core luci-app-shater byedpi`
|
||||
keeps the router current. Point the repo line at `apk-vX.Y.Z-<arch>` instead to
|
||||
pin a build; that file then has to be edited by hand for every upgrade.
|
||||
|
||||
shater ships **inert** (globals off) so install never breaks connectivity. After
|
||||
configuring nodes/rules: `uci set shater.globals.enabled=1 && uci commit shater`,
|
||||
then `shaterd apply` and `shaterd confirm`.
|
||||
@@ -91,16 +84,17 @@ into `openwrt/shaterd/files/`. Details in
|
||||
| `panel/` | Admin SPA (Vite + React + TS) and its Go server |
|
||||
| `openwrt/` | Packages: `shaterd`, `shater-core`, `luci-app-shater`, `byedpi` |
|
||||
| `docs-shater/` | Product documentation |
|
||||
| `scripts/`, `ci/`, `.gitea/workflows/` | Build script, feed/release scripts, CI |
|
||||
| `scripts/`, `ci/`, `.gitea/workflows/` | Build script, apk feed/release scripts, CI |
|
||||
| `SPECS/`, `docs-lx/` | Engine-fork constitution/specs and feature-config reference |
|
||||
| `docs/`, `mkdocs.yml` | **Upstream** sing-box docs (mkdocs) — kept as-is |
|
||||
| `adapter/ cmd/ dns/ route/ option/ protocol/ transport/ …` | sing-box-lx engine tree |
|
||||
|
||||
## CI, upstream & license
|
||||
|
||||
CI (`.gitea/workflows/release.yml`) builds all 4 packages and publishes signed
|
||||
feeds: opkg (usign, key `5ac4b177689cb8e0`) and apk (EC key `shater-apk.pem`). A
|
||||
`vX.Y.Z` tag → versioned release; `workflow_dispatch` → rolling `latest`.
|
||||
CI (`.gitea/workflows/release.yml`) builds all 4 packages and publishes a signed
|
||||
per-arch apk repo (EC key `shater-apk.pem`). A `vX.Y.Z` tag → the pinnable
|
||||
`apk-vX.Y.Z-<arch>`; every run also refreshes the rolling `apk-latest-<arch>` and
|
||||
asserts over the API that it really serves the version just built.
|
||||
|
||||
The engine is the **sing-box-lx** fork — a thin downstream of upstream sing-box that
|
||||
lives by **rebase, never merge**; its constitution is
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
[](LICENSE)
|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -128,42 +128,15 @@ data-plane, DNS-flow, apply-flow) — в [`docs-shater/ARCHITECTURE.md`](docs-sh
|
||||
|
||||
## Установка
|
||||
|
||||
shater поставляется двумя подписанными фидами. Выберите по версии OpenWrt на роутере:
|
||||
|
||||
- **OpenWrt 24.10** → фид **opkg** (`.ipk`, `Packages.gz`, ключ usign).
|
||||
- **OpenWrt / ImmortalWrt / BananaWRT 25.12+** → фид **apk** (`.apk`, `packages.adb`,
|
||||
EC-ключ).
|
||||
shater поставляется одним подписанным **apk-фидом** (OpenWrt / ImmortalWrt /
|
||||
BananaWRT **25.12+**: `.apk`, индекс `packages.adb`, EC-ключ в `/etc/apk/keys/`).
|
||||
Старый opkg-фид (`.ipk`, 24.10) снят — оба наших роутера на 25.12 с apk-tools 3,
|
||||
бинаря `opkg` там просто нет (`docs-shater/DECISIONS.md` D22).
|
||||
|
||||
Пакеты ставятся по зависимостям: `shaterd` → `shater-core` → `luci-app-shater`
|
||||
(+ опциональный `byedpi`). `shaterd` подтягивается автоматически как зависимость.
|
||||
|
||||
### Путь A — фид opkg (OpenWrt 24.10)
|
||||
|
||||
```sh
|
||||
# 1) доверяем ключу фида — ИМЯ файла обязано равняться отпечатку usign-ключа.
|
||||
wget -O /etc/opkg/keys/5ac4b177689cb8e0 \
|
||||
https://git.qomar.pw/omar/shater/releases/download/latest/shater-feed.pub
|
||||
|
||||
# 2) добавляем фид (один URL обслуживает все арки).
|
||||
echo "src/gz shater https://git.qomar.pw/omar/shater/releases/download/latest" \
|
||||
>> /etc/opkg/customfeeds.conf
|
||||
|
||||
# 3) обновляемся и ставим (shaterd подтянется как зависимость).
|
||||
opkg update
|
||||
opkg install luci-app-shater # -> shater-core -> shaterd
|
||||
opkg install byedpi # опционально: ByeDPI desync-egress
|
||||
```
|
||||
|
||||
Обновление — **только наши пакеты, никогда голый `opkg upgrade`** (без аргументов
|
||||
он тянет обновления и на системные пакеты, это классический способ окирпичить
|
||||
роутер):
|
||||
|
||||
```sh
|
||||
opkg update
|
||||
opkg upgrade shaterd shater-core luci-app-shater byedpi
|
||||
```
|
||||
|
||||
### Путь B — фид apk (OpenWrt / ImmortalWrt / BananaWRT 25.12+)
|
||||
### Фид apk
|
||||
|
||||
`/etc/apk/arch` сам выбирает нужный per-arch релиз (apk-релизы раздельны по арке):
|
||||
|
||||
@@ -198,13 +171,22 @@ apk upgrade shaterd shater-core luci-app-shater byedpi
|
||||
only those packages are upgraded along with needed dependencies»*. Проверить
|
||||
установленные версии: `apk list -I shaterd shater-core luci-app-shater byedpi`.
|
||||
|
||||
> **Роллинг или фиксация — это выбор URL в `shater.list`.** `apk-latest-<arch>`
|
||||
> — движущийся указатель: каждый релизный прогон заменяет его ассеты, поэтому
|
||||
> «поставил и забыл»: `apk update` сам видит новую сборку. `apk-vX.Y.Z-<arch>` —
|
||||
> фиксация на конкретной сборке: роутер не получит ничего нового, пока
|
||||
> `/etc/apk/repositories.d/shater.list` не отредактируют руками — на каждом
|
||||
> роутере и на каждый релиз. На `mini_router` сознательно прописан
|
||||
> версионированный URL, и ручная правка — его цена. Подробнее —
|
||||
> [`docs-shater/INSTALL.md`](docs-shater/INSTALL.md) §5.1.
|
||||
|
||||
> Версии пакетов CI берёт из git-тега (`vX.Y.Z` → `X.Y.Z-r1`, сборка вне тега →
|
||||
> `X.Y.Z-r<коммитов+1>`), поэтому каждая новая сборка действительно видна
|
||||
> менеджеру пакетов как новая. Подробности — `docs-shater/INSTALL.md` §2.1.
|
||||
|
||||
> Полные инструкции — раздельная установка из `.ipk`/`.apk` вручную, закрепление
|
||||
> версии (`vX.Y.Z` / `apk-vX.Y.Z-<arch>`), совместимость с BananaWRT
|
||||
> `25.12-mtk-vendor` — в [`docs-shater/INSTALL.md`](docs-shater/INSTALL.md).
|
||||
> Полные инструкции — ручная установка из `.apk`, фиксация версии
|
||||
> (`apk-vX.Y.Z-<arch>`), совместимость с BananaWRT `25.12-mtk-vendor` — в
|
||||
> [`docs-shater/INSTALL.md`](docs-shater/INSTALL.md).
|
||||
|
||||
### Включение
|
||||
|
||||
@@ -258,8 +240,8 @@ arm64}` с musl-static набором тегов (`CGO_ENABLED=0 GOOS=linux`), s
|
||||
| `openwrt/` | Пакеты: `shaterd`, `shater-core`, `luci-app-shater`, `byedpi` |
|
||||
| `docs-shater/` | Документация продукта (см. таблицу ниже) |
|
||||
| `scripts/` | `build-shaterd.sh` — сборка ship-артефакта |
|
||||
| `ci/` | Скрипты сборки фидов и релизов (SDK, usign/EC, Gitea API) |
|
||||
| `.gitea/workflows/` | `release.yml` — CI: сборка пакетов + подписанные фиды opkg/apk |
|
||||
| `ci/` | Скрипты сборки apk-фида и релизов (SDK, EC-подпись, Gitea API) |
|
||||
| `.gitea/workflows/` | `release.yml` — CI: сборка пакетов + подписанный apk-фид |
|
||||
| `SPECS/` | Конституция форка движка и спеки (Spec Kit) |
|
||||
| `docs-lx/` | Справочник конфигурации фич движка (`lx-config.md`, `.ru.md`) |
|
||||
| `lx-test/`, `submodules/` | Примеры конфигов движка и submodule AmneziaWG-рантайма |
|
||||
@@ -273,16 +255,17 @@ arm64}` с musl-static набором тегов (`CGO_ENABLED=0 GOOS=linux`), s
|
||||
CI на **Gitea Actions** (`.gitea/workflows/release.yml`) собирает все 4 пакета и
|
||||
публикует **подписанные фиды**:
|
||||
|
||||
- **opkg (24.10):** один комбинированный релиз, подписан usign-ключом (публичный
|
||||
`dist/shater-feed.pub`, отпечаток `5ac4b177689cb8e0`; секрет — в Gitea-secret
|
||||
`KEY_BUILD`).
|
||||
- **apk (25.12+):** параллельная линия, **по релизу на арку**, подписан EC-ключом
|
||||
(`dist/shater-apk.pem`; секрет — `KEY_APK`).
|
||||
- **apk (25.12+)** — единственный формат: **по релизу на арку**, индекс
|
||||
`packages.adb` подписан EC-ключом (публичный `dist/shater-apk.pem`; секрет — в
|
||||
Gitea-secret `KEY_APK`).
|
||||
|
||||
Триггеры: push тега **`vX.Y.Z`** → версионный релиз; `workflow_dispatch` →
|
||||
плавающий `latest`/`apk-latest-<arch>` (всегда свежий фид). Публикация — через
|
||||
Gitea API (`ci/gitea-release.sh`). Ключи **никогда не перегенерируются** — это
|
||||
инвалидировало бы доверие на всех развёрнутых роутерах.
|
||||
Триггеры: push тега **`vX.Y.Z`** → версионный релиз `apk-vX.Y.Z-<arch>`;
|
||||
`workflow_dispatch` → только роллинг. Роллинг `apk-latest-<arch>` обновляется
|
||||
**на каждом прогоне**, включая теговый, и после публикации проверяется через API:
|
||||
в нём обязаны лежать наши три пакета ровно собранной версии и ни одного ассета
|
||||
другой версии. Публикация — через Gitea API (`ci/gitea-release.sh`). Ключ
|
||||
**никогда не перегенерируется** — это инвалидировало бы доверие на всех
|
||||
развёрнутых роутерах.
|
||||
|
||||
---
|
||||
|
||||
@@ -307,7 +290,7 @@ build-тегами и живущий **ребейзом на каждый upstre
|
||||
| Документ | О чём |
|
||||
|----------|-------|
|
||||
| [`docs-shater/CONTEXT.md`](docs-shater/CONTEXT.md) | **Начните здесь** — контекст проекта, история v0.1→v0.2, testbed/инфра |
|
||||
| [`docs-shater/INSTALL.md`](docs-shater/INSTALL.md) | Сборка ship-артефакта и установка обоих фидов (opkg/apk) |
|
||||
| [`docs-shater/INSTALL.md`](docs-shater/INSTALL.md) | Сборка ship-артефакта и установка apk-фида (роллинг/фиксация) |
|
||||
| [`docs-shater/ARCHITECTURE.md`](docs-shater/ARCHITECTURE.md) | One-binary дизайн, auth-handoff, data/DNS/apply-потоки (диаграммы) |
|
||||
| [`docs-shater/FEATURES.md`](docs-shater/FEATURES.md) | Полный список фич с тегами MVP/T1/T2 |
|
||||
| [`docs-shater/ROADMAP.md`](docs-shater/ROADMAP.md) | Фазовый план |
|
||||
|
||||
+19
-20
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
# ci/build-feed-apk.sh — build the signed **apk** feed for ONE arch (the 25.12
|
||||
# lane — additive next to ci/build-feed.sh, which stays the opkg/24.10 lane).
|
||||
# ci/build-feed-apk.sh — build the signed **apk** feed for ONE arch (25.12+;
|
||||
# the only packaging lane shater has — see docs-shater/DECISIONS.md D22).
|
||||
#
|
||||
# Usage: ci/build-feed-apk.sh <ARCH> <SDK_URL> <OUTDIR>
|
||||
# e.g. ci/build-feed-apk.sh aarch64_cortex-a53 \
|
||||
@@ -10,14 +10,14 @@
|
||||
# This is the per-arch entrypoint the Gitea workflow's `build-apk` job calls.
|
||||
# It runs on the CI RUNNER and:
|
||||
# 1. asserts the prebuilt shaterd binary for this arch was already staged by
|
||||
# scripts/build-shaterd.sh (same artifact-order contract as the opkg lane);
|
||||
# 2. drives a plain `debian:bookworm` container (workspace shared via
|
||||
# `--volumes-from`, same trick as ci/build-feed.sh) that downloads the
|
||||
# ImmortalWrt 25.12 apk-SDK tarball and runs ci/sdk-build-apk.sh in it:
|
||||
# compile the 4 packages as .apk, then `apk mkndx --sign` the per-arch
|
||||
# `packages.adb` index. Unlike the usign lane (index signed on the runner),
|
||||
# apk indexing NEEDS the SDK's host `apk` tool, so index+sign happen inside
|
||||
# the container.
|
||||
# scripts/build-shaterd.sh (the artifact-order contract);
|
||||
# 2. drives a plain `debian:bookworm` container (the job's workspace volume is
|
||||
# shared into it with `--volumes-from $(hostname)`; a bare `-v $PWD:...`
|
||||
# points at a host path that does not exist under act_runner's DinD) that
|
||||
# downloads the ImmortalWrt 25.12 apk-SDK tarball and runs
|
||||
# ci/sdk-build-apk.sh in it: compile the 4 packages as .apk, then
|
||||
# `apk mkndx --sign` the per-arch `packages.adb` index. Indexing NEEDS the
|
||||
# SDK's host `apk` tool, so index+sign happen inside the container.
|
||||
#
|
||||
# Why the ImmortalWrt SDK (not openwrt/sdk images): the 25.12 fleet runs
|
||||
# BananaWRT 25.12-mtk-vendor = ImmortalWrt 25.12 base (target mediatek/filogic,
|
||||
@@ -25,9 +25,9 @@
|
||||
# mediatek-filogic 25.12 tag — hence the official SDK tarball.
|
||||
#
|
||||
# Env:
|
||||
# KEY_APK EC (prime256v1) PRIVATE key PEM (Gitea repo secret — the apk analog
|
||||
# of KEY_BUILD). If set, packages.adb carries an embedded signature
|
||||
# verifiable by dist/shater-apk.pem (routers: /etc/apk/keys/).
|
||||
# KEY_APK EC (prime256v1) PRIVATE key PEM (Gitea repo secret). If set,
|
||||
# packages.adb carries an embedded signature verifiable by
|
||||
# dist/shater-apk.pem (routers: /etc/apk/keys/).
|
||||
# If unset, an UNSIGNED index is produced (warning; not shippable —
|
||||
# apk signatures are effectively mandatory).
|
||||
set -eu
|
||||
@@ -56,10 +56,9 @@ fi
|
||||
chmod +x "$REPO"/ci/*.sh 2>/dev/null || true
|
||||
|
||||
# --- 0.4) package version from the git tag ------------------------------------
|
||||
# Same contract as the opkg lane (ci/build-feed.sh): the workflow puts these in
|
||||
# the job env via `ci/version.sh --env >> $GITHUB_ENV`; recompute here when run
|
||||
# standalone. Passed into the container below and re-exported to the
|
||||
# unprivileged build user in ci/sdk-build-apk.sh.
|
||||
# The workflow puts these in the job env via `ci/version.sh --env >>
|
||||
# $GITHUB_ENV`; recompute here when run standalone. Passed into the container
|
||||
# below and re-exported to the unprivileged build user in ci/sdk-build-apk.sh.
|
||||
if [ -z "${SHATER_PKG_VERSION:-}" ] || [ -z "${SHATER_PKG_RELEASE:-}" ]; then
|
||||
eval "$(sh "$REPO/ci/version.sh" --env)"
|
||||
fi
|
||||
@@ -73,7 +72,7 @@ echo "[apk-feed] package version: ${SHATER_PKG_VERSION}-r${SHATER_PKG_RELEASE}"
|
||||
# SDK; PKG_HASH still verifies every file, so stale = re-downloaded.
|
||||
# apt/ debian:bookworm .deb archives for the host-deps install.
|
||||
# The nested container runs the build as an unprivileged user -> must be writable
|
||||
# (same reason as the chmod 0777 "$OUT" in ci/build-feed.sh).
|
||||
# (same reason as the chmod 0777 "$OUT" above).
|
||||
CACHE="$REPO/.cache"
|
||||
mkdir -p "$CACHE/sdk" "$CACHE/dl" "$CACHE/apt"
|
||||
chmod -R a+rwX "$CACHE/dl" "$CACHE/apt" 2>/dev/null || true
|
||||
@@ -96,8 +95,8 @@ sh "$REPO/ci/fetch-sdk.sh" "$SDK_URL" "$SDK_TAR"
|
||||
|
||||
# --- 1) SDK build + index + sign inside a debian container -------------------
|
||||
# `--volumes-from $(hostname)` shares THIS job container's workspace volume into
|
||||
# the nested container (see ci/build-feed.sh for why a bare -v does not work on
|
||||
# the act_runner DinD setup).
|
||||
# the nested container: a bare `-v $PWD:...` points at a host path that does not
|
||||
# exist under the act_runner DinD setup.
|
||||
echo "[apk-feed] SDK build arch=$ARCH (ImmortalWrt 25.12 apk-SDK)"
|
||||
docker pull -q debian:bookworm
|
||||
docker run --rm --volumes-from "$(hostname)" \
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
#!/bin/sh
|
||||
# ci/build-feed.sh — build the signed opkg feed for ONE arch.
|
||||
#
|
||||
# Usage: ci/build-feed.sh <ARCH> <SDK_DOCKER_TAG> <OUTDIR>
|
||||
# e.g. ci/build-feed.sh x86_64 x86_64-24.10.4 out/x86_64
|
||||
# ci/build-feed.sh aarch64_cortex-a53 mediatek-filogic-24.10.4 out/aarch64_cortex-a53
|
||||
#
|
||||
# This is the reusable per-arch entrypoint the Gitea workflow calls. It runs on
|
||||
# the CI RUNNER and:
|
||||
# 1. asserts the prebuilt shaterd binary for this arch was already staged by
|
||||
# scripts/build-shaterd.sh (into openwrt/shaterd/files/) — proving artifact
|
||||
# order: SPA+shaterd build BEFORE the SDK package build;
|
||||
# 2. drives the arch-matched `openwrt/sdk` docker image to compile all 4
|
||||
# packages (ci/sdk-build.sh) and collect their .ipk into OUTDIR;
|
||||
# 3. builds + usign-signs the opkg `Packages` index over OUTDIR
|
||||
# (ci/install-usign.sh + ci/make-index.sh; signs iff $KEY_BUILD is set).
|
||||
#
|
||||
# Env:
|
||||
# KEY_BUILD usign SECRET key (Gitea repo secret). If set, the feed index is
|
||||
# signed and verifiable by dist/shater-feed.pub (fp 5ac4b177689cb8e0).
|
||||
# If unset, an UNSIGNED feed is produced (make-index warns).
|
||||
set -eu
|
||||
|
||||
ARCH="${1:?arch required (x86_64 | aarch64_cortex-a53)}"
|
||||
SDK_TAG="${2:?sdk docker tag required (e.g. x86_64-24.10.4)}"
|
||||
OUT="${3:?output dir required}"
|
||||
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
mkdir -p "$OUT"; OUT="$(cd "$OUT" && pwd)"
|
||||
# $OUT is created here as ROOT on the runner, but the nested `openwrt/sdk`
|
||||
# container runs as the unprivileged `buildbot` (uid 1000) — so it must be able
|
||||
# to write the collected .ipk into $OUT. World-writable is set HERE (a chmod
|
||||
# from inside the container, as buildbot, cannot fix a root-owned dir).
|
||||
chmod 0777 "$OUT"
|
||||
|
||||
# --- 0) the prebuilt shaterd binary must already be staged for this arch ------
|
||||
case "$ARCH" in
|
||||
x86_64) sfx=amd64 ;;
|
||||
aarch64_cortex-a53) sfx=arm64 ;;
|
||||
*) echo "[feed] ERROR: unsupported ARCH '$ARCH'"; exit 2 ;;
|
||||
esac
|
||||
if [ ! -f "$REPO/openwrt/shaterd/files/shaterd-$sfx.upx" ]; then
|
||||
echo "[feed] ERROR: openwrt/shaterd/files/shaterd-$sfx.upx not staged."
|
||||
echo " Run scripts/build-shaterd.sh BEFORE ci/build-feed.sh." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
chmod +x "$REPO"/ci/*.sh 2>/dev/null || true
|
||||
|
||||
# --- 0.4) package version from the git tag ------------------------------------
|
||||
# The workflow normally puts these in the job env (ci/version.sh --env >>
|
||||
# $GITHUB_ENV); recompute here when this script is run standalone so a manual
|
||||
# `ci/build-feed.sh ...` produces the same versions as CI. They are handed to the
|
||||
# SDK container below and read by openwrt/*/Makefile (bug B4 — versions used to
|
||||
# be hand-written literals that nobody bumped, so v0.2.2…v0.2.6 all shipped as
|
||||
# 0.2.0-r3 and no router could ever see an update).
|
||||
if [ -z "${SHATER_PKG_VERSION:-}" ] || [ -z "${SHATER_PKG_RELEASE:-}" ]; then
|
||||
eval "$(sh "$REPO/ci/version.sh" --env)"
|
||||
fi
|
||||
echo "[feed] package version: ${SHATER_PKG_VERSION}-r${SHATER_PKG_RELEASE}"
|
||||
|
||||
# --- 0.5) persistent dl/ (package source tarballs) ----------------------------
|
||||
# Workspace dir restored/saved by actions/cache in the workflow and shared into
|
||||
# the nested SDK container via --volumes-from; becomes CONFIG_DOWNLOAD_FOLDER
|
||||
# there (ci/sdk-build.sh). PKG_HASH still verifies every file, so a stale cache
|
||||
# can never produce a wrong build. Must be writable by the container's
|
||||
# unprivileged buildbot user (same reason as the $OUT chmod above).
|
||||
DL_DIR="$REPO/.cache/dl"
|
||||
mkdir -p "$DL_DIR"
|
||||
chmod -R a+rwX "$DL_DIR" 2>/dev/null || true
|
||||
|
||||
# --- 0.6) persistent feeds/ git checkouts -------------------------------------
|
||||
# Workspace dir restored/saved by actions/cache (key: feeds-opkg-<release>) and
|
||||
# symlinked over the SDK's feeds/ inside the container (ci/sdk-build.sh), so
|
||||
# `scripts/feeds update -a` fetches deltas instead of re-cloning base+packages+
|
||||
# luci from scratch (~7 min/run on this runner's slow github.com link).
|
||||
# Top-level chmod only: the contents are created by the container's uid-1000
|
||||
# build user and restored with the same ownership (tar-as-root preserves it).
|
||||
FEEDS_CACHE="$REPO/.cache/feeds/opkg"
|
||||
mkdir -p "$FEEDS_CACHE"
|
||||
chmod a+rwX "$REPO/.cache" "$REPO/.cache/feeds" "$FEEDS_CACHE" 2>/dev/null || true
|
||||
|
||||
# --- 1) SDK package build (4 packages) in the arch-matched SDK image ----------
|
||||
# We drive the `openwrt/sdk` docker image directly (not openwrt/gh-action-sdk):
|
||||
# on a self-hosted Gitea act_runner the marketplace action fetch can be
|
||||
# unavailable, and we need a CLEAN single-feed layout. `--volumes-from
|
||||
# $(hostname)` shares THIS job container's workspace volume into the nested SDK
|
||||
# container — a bare `-v $PWD:...` points at a host path that does not exist
|
||||
# under the act_runner DinD setup. (Requires the job to run inside a container,
|
||||
# which Gitea Actions does by default.)
|
||||
echo "[feed] SDK build arch=$ARCH image=openwrt/sdk:$SDK_TAG"
|
||||
docker pull "openwrt/sdk:$SDK_TAG"
|
||||
docker run --rm --volumes-from "$(hostname)" \
|
||||
-e ARCH="$ARCH" -e REPO="$REPO" -e OUT="$OUT" -e DL_DIR="$DL_DIR" \
|
||||
-e FEEDS_CACHE="$FEEDS_CACHE" \
|
||||
-e SHATER_PKG_VERSION="$SHATER_PKG_VERSION" \
|
||||
-e SHATER_PKG_RELEASE="$SHATER_PKG_RELEASE" \
|
||||
"openwrt/sdk:$SDK_TAG" \
|
||||
sh "$REPO/ci/sdk-build.sh"
|
||||
|
||||
# --- 2) index + sign the per-arch feed (usign, KEY_BUILD passed through) -------
|
||||
sh "$REPO/ci/install-usign.sh"
|
||||
KEY_BUILD="${KEY_BUILD:-}" bash "$REPO/ci/make-index.sh" "$OUT"
|
||||
|
||||
echo "[feed] done arch=$ARCH -> $OUT"
|
||||
ls -l "$OUT"
|
||||
+7
-10
@@ -2,24 +2,21 @@
|
||||
# ci/gen-apk-key.sh — generate the Shater **apk** feed signing keypair (25.12 lane).
|
||||
#
|
||||
# apk (OpenWrt/ImmortalWrt 25.12+) verifies package indexes with EC keys
|
||||
# (prime256v1 PEM), NOT usign — the existing usign identity
|
||||
# (dist/shater-feed.pub, fp 5ac4b177689cb8e0) keeps signing the opkg/24.10 feed
|
||||
# and is NOT touched by this script. This generates a SEPARATE, second identity:
|
||||
# (prime256v1 PEM). This is the ONLY feed identity shater has since the opkg
|
||||
# lane was removed (D22) — the old usign key is history, not a second lane.
|
||||
#
|
||||
# dist/shater-apk.key EC PRIVATE key. NEVER commit (dist/ is gitignored).
|
||||
# Paste its full PEM contents into the Gitea repo secret
|
||||
# KEY_APK (the apk analog of the usign secret KEY_BUILD).
|
||||
# Then delete the local file (or keep it in a password
|
||||
# manager as the offline backup — losing it means every
|
||||
# deployed router must re-trust a new key).
|
||||
# dist/shater-apk.pem PUBLIC key. Commit it next to shater-feed.pub:
|
||||
# KEY_APK. Then delete the local file (or keep it in a
|
||||
# password manager as the offline backup — losing it
|
||||
# means every deployed router must re-trust a new key).
|
||||
# dist/shater-apk.pem PUBLIC key. Commit it:
|
||||
# git add -f dist/shater-apk.pem
|
||||
# (-f because /dist/ is gitignored). Routers install it
|
||||
# as /etc/apk/keys/shater-apk.pem.
|
||||
#
|
||||
# Run ONCE. Refuses to overwrite: regenerating the key invalidates the trust of
|
||||
# every router that already installed shater-apk.pem (same rule as D7 for the
|
||||
# usign key).
|
||||
# every router that already installed shater-apk.pem (see D22).
|
||||
set -eu
|
||||
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Make `usign` available on the CI runner so ci/make-index.sh can sign the opkg
|
||||
# feed index. The OpenWrt SDK ships usign, but the index/signing step runs on the
|
||||
# bare runner (outside the SDK container), so we build the tiny standalone tool
|
||||
# from source (no libubox — it is intentionally dependency-free so it can
|
||||
# bootstrap a build system). No-op if usign is already on PATH.
|
||||
#
|
||||
# Ported unchanged from Shater v0.1 (ci/install-usign.sh): usign is
|
||||
# format-agnostic and the signing story is identical for the v0.2 4-package feed.
|
||||
#
|
||||
# CI cache: a previously-built binary is reused from $USIGN_CACHE (default:
|
||||
# <repo>/.cache/tools — a workspace dir the workflow persists via actions/cache),
|
||||
# skipping the apt + cmake + clone + build (~1 min). After a fresh build the
|
||||
# binary is copied there so the NEXT run hits the cache. usign is a tiny static
|
||||
# helper with no versioned protocol — a stale cached binary cannot mis-sign.
|
||||
set -eu
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TOOLS="${USIGN_CACHE:-$REPO_ROOT/.cache/tools}"
|
||||
|
||||
# place <binary> — install onto PATH (system-wide if we can, else ~/bin)
|
||||
place() {
|
||||
local SUDO=""; [ "$(id -u)" = 0 ] || SUDO="sudo"
|
||||
if $SUDO install -m0755 "$1" /usr/local/bin/usign 2>/dev/null; then
|
||||
:
|
||||
else
|
||||
mkdir -p "$HOME/bin"
|
||||
install -m0755 "$1" "$HOME/bin/usign"
|
||||
echo "$HOME/bin" >> "${GITHUB_PATH:-/dev/null}"
|
||||
export PATH="$HOME/bin:$PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
if command -v usign >/dev/null 2>&1; then
|
||||
echo "[usign] already present: $(command -v usign)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -x "$TOOLS/usign" ]; then
|
||||
place "$TOOLS/usign"
|
||||
echo "[usign] restored from cache: $(command -v usign || echo "$HOME/bin/usign")"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SUDO=""; [ "$(id -u)" = 0 ] || SUDO="sudo"
|
||||
if ! command -v cmake >/dev/null 2>&1 || ! command -v cc >/dev/null 2>&1; then
|
||||
$SUDO apt-get update -qq
|
||||
$SUDO apt-get install -y -qq cmake gcc git
|
||||
fi
|
||||
|
||||
tmp="$(mktemp -d)"
|
||||
# Canonical source; fall back to the GitHub mirror if git.openwrt.org is flaky.
|
||||
git clone --depth 1 https://git.openwrt.org/project/usign.git "$tmp/usign" \
|
||||
|| git clone --depth 1 https://github.com/openwrt/usign.git "$tmp/usign"
|
||||
( cd "$tmp/usign" && cmake -DCMAKE_BUILD_TYPE=Release . >/dev/null && make >/dev/null )
|
||||
|
||||
place "$tmp/usign/usign"
|
||||
# seed the cache for the next run (best-effort)
|
||||
mkdir -p "$TOOLS" 2>/dev/null && install -m0755 "$tmp/usign/usign" "$TOOLS/usign" 2>/dev/null || true
|
||||
echo "[usign] built: $(command -v usign || echo "$HOME/bin/usign")"
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Build the opkg feed index (Packages + Packages.gz) with SHA256 for a dir of
|
||||
# .ipk files, then optionally usign-sign it if $KEY_BUILD (the Gitea repo secret)
|
||||
# is set and usign is present. Arg $1 = feed dir.
|
||||
#
|
||||
# Ported from Shater v0.1 (ci/make-index.sh), unchanged. It is package-count and
|
||||
# package-name agnostic: it indexes whatever .ipk are in the dir, so it serves
|
||||
# BOTH the per-arch feed built by ci/build-feed.sh AND the combined release feed
|
||||
# assembled in the release job (shaterd + byedpi per-arch, shater-core +
|
||||
# luci-app-shater = _all). opkg filters by Architecture at install time, so one
|
||||
# combined URL serves every device.
|
||||
#
|
||||
# Feed format: opkg `src/gz` (.ipk + text Packages index, usign signature).
|
||||
# OpenWrt 24.10 (our SDK) still uses opkg; apk arrives at 25.12. The committed
|
||||
# trust anchor dist/shater-feed.pub is a usign (Ed25519) key, matching this.
|
||||
set -euo pipefail
|
||||
OUT="${1:?feed dir required}"; cd "$OUT"
|
||||
: > Packages
|
||||
for ipk in *.ipk; do
|
||||
[ -e "$ipk" ] || continue
|
||||
ctrl=$(tar -xzOf "$ipk" ./control.tar.gz | tar -xzO ./control)
|
||||
sz=$(wc -c < "$ipk"); sha=$(sha256sum "$ipk" | cut -d' ' -f1)
|
||||
printf '%s\n' "$ctrl" | sed '/^[[:space:]]*$/d' >> Packages
|
||||
printf 'Filename: %s\nSize: %s\nSHA256sum: %s\n\n' "$ipk" "$sz" "$sha" >> Packages
|
||||
done
|
||||
gzip -kf Packages
|
||||
|
||||
if [ -n "${KEY_BUILD:-}" ]; then
|
||||
# Signing was requested — a missing/broken signer must FAIL the build, not
|
||||
# silently ship an unsigned feed that routers with check_signature on reject.
|
||||
command -v usign >/dev/null 2>&1 || { echo "[index] ERROR: KEY_BUILD set but usign not found" >&2; exit 1; }
|
||||
umask 077; printf '%s\n' "$KEY_BUILD" > /tmp/usign.sec
|
||||
usign -S -m Packages -s /tmp/usign.sec || { rm -f /tmp/usign.sec; echo "[index] ERROR: usign signing failed" >&2; exit 1; }
|
||||
rm -f /tmp/usign.sec
|
||||
echo "[index] signed -> Packages.sig ($(head -1 Packages.sig))"
|
||||
else
|
||||
echo "[index] no KEY_BUILD -> UNSIGNED feed (opkg needs check_signature off, or set the secret)"
|
||||
fi
|
||||
echo "[index] contents:"; ls -l
|
||||
+3
-4
@@ -10,7 +10,6 @@
|
||||
# the target fleet (BananaWRT 25.12-mtk-vendor = ImmortalWrt 25.12 base, its
|
||||
# distfeeds even point at downloads.immortalwrt.org/releases/25.12-SNAPSHOT) is
|
||||
# ImmortalWrt — so we extract the official ImmortalWrt SDK tarball ourselves.
|
||||
# Same --volumes-from workspace-sharing pattern as ci/sdk-build.sh (opkg lane).
|
||||
#
|
||||
# The OpenWrt buildsystem refuses to run as root, so the SDK build itself runs
|
||||
# as an unprivileged `build` user created here.
|
||||
@@ -36,8 +35,8 @@ echo "[apk-sdk] package version: ${SHATER_PKG_VERSION:-<unset -> Makefile fallba
|
||||
test -f "$REPO/openwrt/shaterd/Makefile" || {
|
||||
echo "[apk-sdk] ERROR: feed not mounted ($REPO/openwrt/shaterd/Makefile missing)"; ls -la "$REPO" || true; exit 9; }
|
||||
|
||||
# The prebuilt shaterd artifact must already be staged for this arch (same
|
||||
# contract as the opkg lane — scripts/build-shaterd.sh runs first).
|
||||
# The prebuilt shaterd artifact must already be staged for this arch
|
||||
# (artifact-order contract — scripts/build-shaterd.sh runs first).
|
||||
case "$ARCH" in
|
||||
x86_64) sfx=amd64 ;;
|
||||
aarch64_cortex-a53) sfx=arm64 ;;
|
||||
@@ -113,7 +112,7 @@ export HOME=/home/build
|
||||
cd "$SDKDIR"
|
||||
|
||||
# Register this repo's openwrt/ as a src-link feed named `shater` (absolute
|
||||
# path required) — identical to the opkg lane (ci/sdk-build.sh).
|
||||
# path required).
|
||||
cp -f feeds.conf.default feeds.conf
|
||||
grep -q '^src-link shater ' feeds.conf || echo "src-link shater $REPO/openwrt" >> feeds.conf
|
||||
|
||||
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Runs INSIDE an `openwrt/sdk:<target>-<ver>` container (CWD = SDK root
|
||||
# /builder). The job's workspace is shared into this container via
|
||||
# `docker run --volumes-from`, so the repo is visible at $REPO and output goes
|
||||
# to $OUT (a dir under the repo, hence also visible to the runner afterwards).
|
||||
#
|
||||
# Unlike Shater v0.1 (which compiled ONLY xrayctl in the SDK and hand-packed the
|
||||
# pure-data packages with tar), v0.2 builds ALL FOUR packages the canonical way,
|
||||
# via the SDK feed + `make package/<p>/compile`:
|
||||
#
|
||||
# shaterd prebuilt binary — Build/Compile only VALIDATES that
|
||||
# openwrt/shaterd/files/shaterd-<amd64|arm64>.upx was staged
|
||||
# by scripts/build-shaterd.sh on the runner BEFORE this ran.
|
||||
# (arch-specific .ipk: RSTRIP/STRIP disabled — packed ELF.)
|
||||
# shater-core PKGARCH=all data glue (procd init, sysctl, uci-defaults).
|
||||
# luci-app-shater PKGARCH=all LuCI thin launcher — its Makefile does
|
||||
# `include $(TOPDIR)/feeds/luci/luci.mk`, so the `luci` feed
|
||||
# MUST be updated first (that is what creates feeds/luci/luci.mk).
|
||||
# byedpi arch-specific C — the SDK cross-compiles ciadpi from the
|
||||
# upstream tarball (needs network for PKG_SOURCE_URL).
|
||||
#
|
||||
# Env (required): ARCH, REPO, OUT.
|
||||
set -eu
|
||||
ARCH="${ARCH:?ARCH env required}"
|
||||
REPO="${REPO:?REPO env required}"
|
||||
OUT="${OUT:?OUT env required}"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
echo "[sdk] arch=$ARCH repo=$REPO out=$OUT"
|
||||
# Package version, derived from the git tag by ci/version.sh and handed in by
|
||||
# ci/build-feed.sh. openwrt/{shaterd,shater-core,luci-app-shater}/Makefile read
|
||||
# these straight out of the environment ($(if $(SHATER_PKG_VERSION),...)); make
|
||||
# imports every environment variable as a variable, and it propagates through
|
||||
# `make package/<p>/compile`, the metadata dump and the sub-makes alike.
|
||||
# byedpi deliberately keeps its own upstream version (see its Makefile).
|
||||
echo "[sdk] package version: ${SHATER_PKG_VERSION:-<unset -> Makefile fallback>}-r${SHATER_PKG_RELEASE:-?}"
|
||||
test -f "$REPO/openwrt/shaterd/Makefile" || {
|
||||
echo "[sdk] ERROR: feed not mounted ($REPO/openwrt/shaterd/Makefile missing)"; ls -la "$REPO" || true; exit 9; }
|
||||
|
||||
# The prebuilt shaterd artifact must already be staged for this arch.
|
||||
case "$ARCH" in
|
||||
x86_64) sfx=amd64 ;;
|
||||
aarch64_cortex-a53) sfx=arm64 ;;
|
||||
*) echo "[sdk] ERROR: unsupported ARCH '$ARCH'"; exit 2 ;;
|
||||
esac
|
||||
test -f "$REPO/openwrt/shaterd/files/shaterd-$sfx.upx" || {
|
||||
echo "[sdk] ERROR: openwrt/shaterd/files/shaterd-$sfx.upx not staged."
|
||||
echo " scripts/build-shaterd.sh must run on the runner before the SDK build."; exit 3; }
|
||||
|
||||
# --- register this repo's openwrt/ as a src-link feed named `shater` ---------
|
||||
# src-link REQUIRES an absolute path; $REPO/openwrt is exactly a feed root (it
|
||||
# contains the 4 package dirs and nothing else that looks like a package).
|
||||
cp -f feeds.conf.default feeds.conf
|
||||
grep -q '^src-link shater ' feeds.conf || echo "src-link shater $REPO/openwrt" >> feeds.conf
|
||||
|
||||
# Update metadata for ALL feeds: our `shater` feed + the SDK defaults (base,
|
||||
# luci, packages, routing, telephony). We need `luci` for feeds/luci/luci.mk and
|
||||
# `base`/`packages` for the runtime deps (kmod-nft-tproxy, kmod-nft-socket,
|
||||
# ip-full, rpcd, luci-base) to resolve.
|
||||
#
|
||||
# Persistent feeds checkouts: $FEEDS_CACHE (a workspace dir the runner restores
|
||||
# via actions/cache, shared into this container via --volumes-from) replaces
|
||||
# the SDK's ephemeral feeds/ dir, so `feeds update` git-fetches deltas instead
|
||||
# of re-cloning base+packages+luci every run (~7 min on the runner's slow
|
||||
# github.com link). Correctness-safe: update always checks out feeds.conf's
|
||||
# pinned revisions; if it ever fails on a cached checkout (e.g. a force-pushed
|
||||
# upstream), the cache is wiped and the update retried with fresh clones.
|
||||
if [ -n "${FEEDS_CACHE:-}" ] && mkdir -p "$FEEDS_CACHE" 2>/dev/null; then
|
||||
rm -rf feeds
|
||||
ln -s "$FEEDS_CACHE" feeds
|
||||
echo "[sdk] feeds/ -> $FEEDS_CACHE (persistent cache)"
|
||||
fi
|
||||
echo "[sdk] feeds update -a"
|
||||
if ! ./scripts/feeds update -a; then
|
||||
[ -L feeds ] || { echo "[sdk] ERROR: feeds update failed"; exit 8; }
|
||||
echo "[sdk] WARNING: feeds update failed on cached checkouts — wiping cache, cloning fresh"
|
||||
find "$FEEDS_CACHE" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
|
||||
./scripts/feeds update -a
|
||||
fi
|
||||
|
||||
echo "[sdk] feeds install (prefer shater feed)"
|
||||
./scripts/feeds install -p shater shaterd shater-core byedpi luci-app-shater
|
||||
|
||||
# Select our packages, then defconfig. `make package/<p>/compile` builds the
|
||||
# explicit target regardless, but selecting first makes deps visible to defconfig.
|
||||
for p in shaterd shater-core byedpi luci-app-shater; do
|
||||
echo "CONFIG_PACKAGE_$p=m" >> .config
|
||||
done
|
||||
# Route source downloads through OpenWrt's fast CDN mirror FIRST — sourceware.org
|
||||
# (elfutils) and other upstreams intermittently stall mid-transfer, and curl's
|
||||
# --connect-timeout doesn't cover a stalled stream, so the SDK download hangs the
|
||||
# build. LOCALMIRROR is tried before each package's own PKG_SOURCE_URL. (lx CI)
|
||||
echo 'CONFIG_LOCALMIRROR="https://sources.cdn.openwrt.org"' >> .config
|
||||
# Persistent dl/ across runs: $DL_DIR is a workspace dir the runner restores via
|
||||
# actions/cache (see ci/build-feed.sh). Correctness-safe: the buildroot verifies
|
||||
# PKG_HASH on every file already in dl/ and re-downloads on mismatch, so a stale
|
||||
# cache can never leak a wrong source into the build.
|
||||
if [ -n "${DL_DIR:-}" ]; then
|
||||
echo "CONFIG_DOWNLOAD_FOLDER=\"$DL_DIR\"" >> .config
|
||||
fi
|
||||
echo "[sdk] defconfig"
|
||||
make defconfig >/dev/null
|
||||
|
||||
# --- compile the 4 packages --------------------------------------------------
|
||||
for p in shaterd shater-core byedpi luci-app-shater; do
|
||||
echo "[sdk] === build $p ==="
|
||||
make "package/$p/compile" V=s -j"$(nproc)"
|
||||
done
|
||||
|
||||
# --- collect ONLY our 4 packages' .ipk (per-arch shaterd/byedpi + _all core/luci)
|
||||
# NOT `find bin -name '*.ipk'`: the openwrt/sdk image ships HUNDREDS of prebuilt
|
||||
# kmod/base .ipk under bin/, which a blanket copy would pull into the feed and
|
||||
# get signed under OUR key. Match each package's own `<name>_<ver>_<arch>.ipk`.
|
||||
found=0
|
||||
for p in shaterd shater-core byedpi luci-app-shater; do
|
||||
for ipk in $(find bin -type f -name "${p}_*.ipk"); do
|
||||
cp -f "$ipk" "$OUT/"; found=$((found+1))
|
||||
done
|
||||
done
|
||||
[ "$found" -ge 4 ] || { echo "[sdk] ERROR: expected >=4 of OUR .ipk, collected $found"; echo "[sdk] (all .ipk under bin/:)"; find bin -type f -name '*.ipk' | head -20; exit 4; }
|
||||
|
||||
# --- assert the tag-derived version actually reached the packages -------------
|
||||
# The whole point of B4 is that a WRONG-but-plausible version ships silently. The
|
||||
# env -> make hand-off has several layers (docker -e, make's env import, the
|
||||
# metadata dump), so verify the result instead of trusting it: every one of our
|
||||
# three tag-versioned packages must be named `<name>_<ver>-r<rel>_<arch>.ipk`.
|
||||
# byedpi is excluded on purpose — it keeps upstream ByeDPI's own version.
|
||||
if [ -n "${SHATER_PKG_VERSION:-}" ] && [ -n "${SHATER_PKG_RELEASE:-}" ]; then
|
||||
want="${SHATER_PKG_VERSION}-r${SHATER_PKG_RELEASE}"
|
||||
for p in shaterd shater-core luci-app-shater; do
|
||||
ls "$OUT/${p}_${want}_"*.ipk >/dev/null 2>&1 || {
|
||||
echo "[sdk] ERROR: $p was not built as version '$want'."
|
||||
echo " SHATER_PKG_VERSION/SHATER_PKG_RELEASE did not reach the package"
|
||||
echo " Makefile — the build would have shipped a stale version (bug B4)."
|
||||
echo "[sdk] collected:"; ls -1 "$OUT" | sed 's/^/ /'
|
||||
exit 12; }
|
||||
done
|
||||
echo "[sdk] version check OK — our 3 packages are $want"
|
||||
fi
|
||||
|
||||
chmod -R a+rwX "$OUT" 2>/dev/null || true
|
||||
echo "[sdk] OK arch=$ARCH — collected $found of our .ipk:"
|
||||
ls -l "$OUT"
|
||||
+7
-9
@@ -6,9 +6,9 @@
|
||||
# PKG_VERSION/PKG_RELEASE used to be hand-written literals in the four package
|
||||
# Makefiles, and nobody remembered to bump them: v0.2.2 … v0.2.6 all shipped as
|
||||
# `shaterd 0.2.0-r3` with DIFFERENT binaries inside (v0.2.6's ELF is 5 491 616 B
|
||||
# vs r2's 5 488 336 B). Since both opkg and apk offer an upgrade only when the
|
||||
# feed's version string differs from the installed one, `apk update` saw nothing
|
||||
# new and the routers could not be updated through the normal path at all.
|
||||
# vs r2's 5 488 336 B). Since apk offers an upgrade only when the feed's version
|
||||
# string differs from the installed one, `apk update` saw nothing new and the
|
||||
# routers could not be updated through the normal path at all.
|
||||
#
|
||||
# So the version is now DERIVED, in CI, from the git tag, and the package
|
||||
# Makefiles only carry a fallback for manual/offline builds.
|
||||
@@ -21,14 +21,12 @@
|
||||
# rolling `latest`)
|
||||
# no tag / no git at all -> PKG_VERSION=0.0.0 PKG_RELEASE=1 (+ warning)
|
||||
#
|
||||
# Both managers compare `<upstream>-r<rel>` the same way: the dotted upstream
|
||||
# part first (numerically, component by component), the `r<rel>` only as a
|
||||
# tie-break. Verified against the real tools, not from memory:
|
||||
# apk-tools 3.0.3 (`apk version -t`) and apk-tools 2.14.6:
|
||||
# apk compares `<upstream>-r<rel>` as: the dotted upstream part first
|
||||
# (numerically, component by component), the `r<rel>` only as a tie-break.
|
||||
# Verified against the real tool, not from memory —
|
||||
# apk-tools 3.0.3 (`apk version -t`) and apk-tools 2.14.6:
|
||||
# 0.2.6-r1 > 0.2.0-r3 0.2.6-r12 > 0.2.6-r1
|
||||
# 0.2.7-r1 > 0.2.6-r12 0.0.0-r1 < 0.2.0-r3
|
||||
# opkg 38eccbb1 from openwrt/rootfs:x86-64-24.10.4 (`opkg compare-versions`):
|
||||
# identical results (opkg implements the Debian algorithm).
|
||||
# That is exactly the ordering this scheme needs:
|
||||
# * a release always outranks every rolling build that preceded it
|
||||
# (0.2.7-r1 > 0.2.6-rN for any N — the dotted part decides), and
|
||||
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
untrusted comment: shater feed signing key
|
||||
RWRaxLF3aJy44JbcxSFujtrFFEQ8lIsnTkd1K5TdjIhdlC2c0wa0fv4V
|
||||
+10
-11
@@ -31,12 +31,11 @@ Do not delete it — we port proven pieces from it. What v0.1 has:
|
||||
- **`luci-app-shater`** — a custom "instrument panel" LuCI app (client-side JS +
|
||||
ucode/rpcd ubus backend): Overview with a live Signal Path, Simple/Advanced
|
||||
toggle, quick-start wizard, Nodes/Subs/Rules/DNS/Live/Profiles/Settings pages.
|
||||
- **CI + signed opkg feed** on Gitea: builds per-arch, signs the feed index with
|
||||
usign, publishes a rolling `latest` Gitea release consumable as `src/gz`. **Feed
|
||||
signing key fingerprint `5ac4b177689cb8e0`**; public key `dist/shater-feed.pub`,
|
||||
secret in the Gitea repo secret `KEY_BUILD`.
|
||||
- **CI + a signed package feed** on Gitea: builds per-arch, signs the feed index,
|
||||
publishes a rolling `latest` Gitea release the router consumes as a feed.
|
||||
(v0.1 shipped `.ipk` signed with a usign key — that lane is retired, D22.)
|
||||
- Verified end-to-end on the VM: real LAN client proxied, DNS anti-leak, honest
|
||||
fail-closed, opkg install/upgrade from the signed feed.
|
||||
fail-closed, install/upgrade from the signed feed.
|
||||
|
||||
v0.1 is engine-locked to **xray-core**; its generator, share-link parser and
|
||||
`run.json` are xray-shaped.
|
||||
@@ -91,7 +90,7 @@ We are rebasing onto a new engine and a new UI architecture. Full rationale in
|
||||
- **`shater` branch `v0.1`** = the standalone xray-based version (frozen, ported
|
||||
from).
|
||||
- Until Phase 1 merges the engine in, `main` is the docs-first overlay seed you
|
||||
are reading now (LICENSE, README, `docs-shater/`, `dist/shater-feed.pub`).
|
||||
are reading now (LICENSE, README, `docs-shater/`, the feed signing key).
|
||||
|
||||
## What to port from v0.1 (don't rewrite these ideas)
|
||||
|
||||
@@ -105,8 +104,8 @@ overlay, don't redo:
|
||||
- **Subscription fetch** (HAPP emulation, fingerprint reconcile, per-sub cache)
|
||||
and the flexible **ruleset/list** model — though sing-box has its own share-link
|
||||
parser and config schema we now target.
|
||||
- **CI feed build + usign signing + Gitea release** (adapt to the single forked
|
||||
binary; keep key `5ac4b177689cb8e0`).
|
||||
- **CI feed build + index signing + Gitea release** (adapted to the single forked
|
||||
binary; the format is apk, signed with the EC key — D22).
|
||||
- The LuCI **design system** (the "instrument panel" identity) — reused for the
|
||||
mini-dashboard and as the panel's visual language.
|
||||
|
||||
@@ -122,9 +121,9 @@ filter/stats engine wired into sing-box's DNS.
|
||||
`https://github.com/SagerNet/sing-box`).
|
||||
- **CI:** Gitea Actions (act_runner + Docker). v0.1's workflow was removed from
|
||||
`main`; new CI is added when the v0.2 build exists.
|
||||
- **Feed signing:** usign key `5ac4b177689cb8e0`; secret in repo secret
|
||||
`KEY_BUILD`; public key `dist/shater-feed.pub` (kept so existing installs keep
|
||||
verifying).
|
||||
- **Feed signing:** EC (prime256v1) key for the apk index; secret in the repo
|
||||
secret `KEY_APK`; public key `dist/shater-apk.pem`, installed on routers as
|
||||
`/etc/apk/keys/shater-apk.pem`. Never regenerate it (D22).
|
||||
- **Test VM:** OpenWrt 24.10.3 x86_64 in Docker (`docker ps --filter
|
||||
name=openwrt-vm`). SSH via the ssh-manager MCP server `local_openwrt`
|
||||
(localhost:2222, root/openwrt). LuCI at `http://127.0.0.1:8080` (root/openwrt),
|
||||
|
||||
@@ -64,11 +64,17 @@ sing-box is GPL-3.0; linking it makes the combined work GPL-3.0. Our own files m
|
||||
stay GPL-2.0-or-later (which permits the upgrade), but the project LICENSE is
|
||||
GPL-3.0 for clarity.
|
||||
|
||||
## D7 — Keep the v0.1 feed signing identity
|
||||
## D7 — Keep the v0.1 feed signing identity *(SUPERSEDED by D22)*
|
||||
The usign feed key `5ac4b177689cb8e0` (public key in `dist/shater-feed.pub`,
|
||||
secret in Gitea secret `KEY_BUILD`) carries over, so routers that already trust it
|
||||
keep verifying v0.2 packages. Do not regenerate it without a documented rotation.
|
||||
|
||||
> **Superseded 2026-07-25 (D22).** The opkg feed this identity signed no longer
|
||||
> exists, so there is nothing left for the key to verify. It was never rotated or
|
||||
> compromised — it is simply unused. `dist/shater-feed.pub` was deleted from the
|
||||
> tree; the reasoning, and how to resurrect the identity if it is ever needed
|
||||
> again, is in D22.
|
||||
|
||||
## D8 — Preserve, don't destroy: v0.1 lives on its branch
|
||||
The reset moved the full working xray-based project to the `v0.1` branch and
|
||||
cleaned `main`. Nothing is lost; reusable logic (reliability layer, nft/routing,
|
||||
@@ -529,3 +535,49 @@ the rule editor**, because a second place to author a list is a second place for
|
||||
its semantics and its duplicate-name rules to drift, and the whole point of this
|
||||
decision was to stop having two.
|
||||
|
||||
## D22 — One packaging lane: apk. The opkg/`.ipk` lane is deleted, not disabled
|
||||
Decided 2026-07-25 (product owner). CI built and published TWO signed feeds from
|
||||
every run: opkg/usign (`.ipk` + `Packages.gz`, OpenWrt 24.10) and apk/EC (`.apk` +
|
||||
`packages.adb`, OpenWrt/ImmortalWrt 25.12). The opkg half served nobody. Checked
|
||||
on the actual hardware, not inferred:
|
||||
|
||||
| Device | Firmware | pkg arch | package manager |
|
||||
|---|---|---|---|
|
||||
| `mini_router` (BPi-R3 Mini) | ImmortalWrt 25.12.1 | `aarch64_cortex-a53` | apk-tools 3.0.5 |
|
||||
| `main_router` (BPi-R4) | OpenWrt 25.12.0 | `aarch64_cortex-a53` | apk-tools 3.0.5 — **no `opkg` binary on the system at all** |
|
||||
|
||||
**Decision: delete the opkg lane outright.** Removed: the `build` + `release`
|
||||
jobs from `.gitea/workflows/release.yml`; `ci/build-feed.sh`, `ci/sdk-build.sh`,
|
||||
`ci/make-index.sh`, `ci/install-usign.sh`; and the trust anchor
|
||||
`dist/shater-feed.pub`. The Gitea secret `KEY_BUILD` is now referenced by
|
||||
nothing and can be deleted from the repo settings. `ci/version.sh`,
|
||||
`ci/gitea-release.sh` and `ci/fetch-sdk.sh` are shared or apk-only and stay.
|
||||
|
||||
- **Rejected: keep the lane but stop triggering it** (comment it out / gate it on
|
||||
a dispatch input). Dead code in CI is worse than no code: it keeps a second SDK
|
||||
matrix, a second signing key and a second feed layout alive in everyone's head
|
||||
and in every future edit, and it silently rots because nothing runs it. The
|
||||
24.10 SDK images it pins are themselves a frozen dependency.
|
||||
- **Rejected: keep `dist/shater-feed.pub` as a historical artifact.** A committed
|
||||
trust anchor is an instruction — it invites someone to follow the old install
|
||||
path for a feed that is no longer produced. Nothing is lost by removing it:
|
||||
git history still holds the file, the SECRET half is untouched in `KEY_BUILD`,
|
||||
and a usign secret key blob contains its own public half, so the identity can
|
||||
be reconstructed if a 24.10 device ever has to be served again. Deleting the
|
||||
file is reversible; a stale trust anchor pointing at an unmaintained feed is
|
||||
the thing that quietly misleads.
|
||||
- **Not done: revoking or rotating the usign key.** There is no incident. It is
|
||||
retired, not burned (D7).
|
||||
|
||||
Consequence: one SDK, one key, one feed layout, one set of install instructions.
|
||||
It also makes the rolling release `apk-latest-<arch>` the *only* install path
|
||||
that does not require hand-editing a file per release — which is why the same
|
||||
change fixed it: publishing was an either/or (`apk-latest-<arch>` on dispatch,
|
||||
ELSE `apk-vX.Y.Z-<arch>` on a tag), so once releases moved to tag pushes the
|
||||
rolling pointer stopped being written and froze at `0.2.0` while v0.2.9/v0.2.10
|
||||
shipped — routers on the rolling URL got a successful, silent `apk update` with
|
||||
nothing new. `release-apk` now writes the rolling pointer on every run and
|
||||
asserts, by reading the published release back over the Gitea API, that it holds
|
||||
our three tag-versioned packages at exactly the version just built and no asset
|
||||
at any other version.
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@ usable release, **[T1]** next, **[T2]** later. Phases refer to `ROADMAP.md`.
|
||||
SIM uplink → different egress); backup/restore; i18n (EN + RU).
|
||||
|
||||
## Ops & distribution
|
||||
- **[MVP]** Single signed binary; signed opkg feed on Gitea (reuse key
|
||||
`5ac4b177689cb8e0`); one-line install; `opkg upgrade`.
|
||||
- **[MVP]** Single signed binary; signed apk feed on Gitea (EC key
|
||||
`dist/shater-apk.pem`); one-line install; named-package `apk upgrade`.
|
||||
- **[T1]** Upstream-rebase cadence (track sing-box-lx tags) with a smoke suite.
|
||||
- **[T2]** apk (OpenWrt 25.x) packaging; multi-router fleet management; REST/gRPC
|
||||
external API; Telegram bot.
|
||||
- **[T2]** Multi-router fleet management; REST/gRPC external API; Telegram bot.
|
||||
(apk packaging landed and is now the only lane — D22.)
|
||||
|
||||
+72
-103
@@ -1,7 +1,7 @@
|
||||
# Shater v0.2 — Build & Install
|
||||
|
||||
How to build the ship artifact (the SPA-embedded `shaterd` binary) and install
|
||||
the OpenWrt feed onto a router.
|
||||
the signed apk repo onto a router.
|
||||
|
||||
## 1. Build the `shaterd` binary
|
||||
|
||||
@@ -28,7 +28,7 @@ Arg / env:
|
||||
- `VERSION` — stamped into `constant.Version`. Resolution: positional arg →
|
||||
`$SHATER_VERSION` → `ci/version.sh --binary` → `v0.2.0-dev`. `ci/version.sh` is
|
||||
the **same** computation the package version comes from (§2.1), so the string
|
||||
the panel shows always matches what `apk info shaterd` / `opkg status` report.
|
||||
the panel shows always matches what `apk list -I shaterd` reports.
|
||||
- `--fast` — skip `npm ci` when `panel/node_modules` already exists.
|
||||
- `UPX=/path/to/upx` — override the UPX binary (default `upx` on `PATH`). UPX is
|
||||
cross-arch, so one host packs both the amd64 and aarch64 ELFs. (Note: UPX also
|
||||
@@ -91,9 +91,9 @@ See `openwrt-package-build-ci` for SDK/feed mechanics.
|
||||
`PKG_VERSION`/`PKG_RELEASE` are **not** maintained by hand. They used to be, and
|
||||
nobody bumped them: **v0.2.2 … v0.2.6 all shipped as `shaterd 0.2.0-r3`** with
|
||||
different binaries inside (v0.2.6's ELF is 5 491 616 B against r2's 5 488 336 B).
|
||||
Both package managers offer an upgrade only when the feed's version string
|
||||
differs from the installed one, so `apk update` saw nothing new and the routers
|
||||
could not be updated through the normal path at all.
|
||||
apk offers an upgrade only when the feed's version string differs from the
|
||||
installed one, so `apk update` saw nothing new and the routers could not be
|
||||
updated through the normal path at all.
|
||||
|
||||
`ci/version.sh` now derives them from `git describe`, once per CI job:
|
||||
|
||||
@@ -103,9 +103,8 @@ could not be updated through the normal path at all.
|
||||
| dispatch, 3 commits past `v0.2.7` | `0.2.7` | `4` | `v0.2.7-r4-g<sha>` |
|
||||
| no reachable tag / no git | `0.0.0` | `1` | `v0.0.0-r1` |
|
||||
|
||||
Ordering is what makes this safe, and both managers agree on it (checked with
|
||||
`apk version -t` on apk-tools 3.0.3 and `opkg compare-versions` on opkg
|
||||
38eccbb1): the dotted part decides first, `-rN` only breaks ties — so
|
||||
Ordering is what makes this safe (checked with `apk version -t` on apk-tools
|
||||
3.0.3): the dotted part decides first, `-rN` only breaks ties — so
|
||||
`0.2.7-r1 > 0.2.6-r12 > 0.2.6-r1 > 0.2.0-r3`. A release therefore always
|
||||
outranks every rolling build before it, rolling builds between two releases grow
|
||||
monotonically, and an untagged build (`0.0.0`) can never masquerade as an
|
||||
@@ -113,8 +112,9 @@ upgrade.
|
||||
|
||||
The value travels as `SHATER_PKG_VERSION`/`SHATER_PKG_RELEASE` in the SDK build
|
||||
environment; the Makefiles read it with a literal fallback for manual/offline
|
||||
builds. Both lanes then **assert** the produced `.ipk`/`.apk` really carries it,
|
||||
so a lost variable fails the build instead of shipping a stale version.
|
||||
builds. `ci/sdk-build-apk.sh` then **asserts** the produced `.apk` really carries
|
||||
it, so a lost variable fails the build instead of shipping a stale version. The
|
||||
release job asserts the same version again on the published rolling repo (§5.1).
|
||||
|
||||
`byedpi` is deliberately excluded — `PKG_VERSION:=0.17.3` is *upstream ByeDPI's*
|
||||
version, which is what `PKG_HASH` pins and what tells you which ByeDPI is
|
||||
@@ -124,21 +124,27 @@ when our packaging of it changes.
|
||||
|
||||
## 3. Install on a router
|
||||
|
||||
Install order follows the deps (`shaterd` → `shater-core` → `luci-app-shater`):
|
||||
**The normal path is the signed apk repo — §5.** This section is the manual
|
||||
fallback (a router with no route to the Gitea host, or a hand-carried build).
|
||||
|
||||
Install order follows the deps (`shaterd` → `shater-core` → `luci-app-shater`).
|
||||
apk filenames carry no architecture, so make sure you copied the `.apk` built for
|
||||
*this* router's arch (`cat /etc/apk/arch`):
|
||||
|
||||
```sh
|
||||
# <ver> = the release version, e.g. 0.2.7-r1 (§2.1 — it comes from the git tag)
|
||||
opkg install shaterd_<ver>_<arch>.ipk # or: apk add shaterd (25.12+)
|
||||
opkg install shater-core_<ver>_all.ipk
|
||||
opkg install luci-app-shater_<ver>_all.ipk
|
||||
opkg install byedpi_0.17.3-r1_<arch>.ipk # optional: ByeDPI egress
|
||||
# --allow-untrusted: our member .apk are unsigned by design — trust lives in the
|
||||
# signed packages.adb index (§5), which a loose file install does not consult.
|
||||
apk add --allow-untrusted ./shaterd-<ver>.apk
|
||||
apk add --allow-untrusted ./shater-core-<ver>.apk
|
||||
apk add --allow-untrusted ./luci-app-shater-<ver>.apk
|
||||
apk add --allow-untrusted ./byedpi-0.17.3-r1.apk # optional: ByeDPI egress
|
||||
```
|
||||
|
||||
Installing from a signed feed instead:
|
||||
From the repo instead (§5 sets it up once), deps pull the rest in:
|
||||
|
||||
```sh
|
||||
# add the feed (customfeeds.conf / apk repositories), then:
|
||||
opkg update && opkg install shater-core luci-app-shater # shaterd pulled in as a dep
|
||||
apk update && apk add luci-app-shater # -> shater-core -> shaterd
|
||||
```
|
||||
|
||||
## 4. Enable
|
||||
@@ -158,92 +164,55 @@ daemon (`shaterd run`), which owns the engine, the `inet shater` data plane, pol
|
||||
routing, in-process DNS, and the admin panel (default `:8088`). The LuCI app's
|
||||
"Open panel" button mints a single-use token and hands the browser off to the panel.
|
||||
|
||||
## 5. Add the signed feed (recommended — then `opkg upgrade` just works)
|
||||
## 5. The signed apk repo (the normal install path)
|
||||
|
||||
CI (`.gitea/workflows/release.yml`) publishes every build as a **rolling `latest`
|
||||
Gitea release** that is itself a signed opkg `src/gz` feed: the release holds the
|
||||
`.ipk` for all arches, a `Packages`/`Packages.gz` index, a usign `Packages.sig`,
|
||||
and the public key `shater-feed.pub`. opkg filters by `Architecture`, so the **same
|
||||
two lines work on every device** (x86 testbed picks `x86_64 + all`; the BPI routers
|
||||
pick `aarch64_cortex-a53 + all`).
|
||||
OpenWrt/ImmortalWrt **25.12** packages with Alpine's **apk**: `.apk` files, a
|
||||
binary `packages.adb` index, EC (prime256v1) keys in `/etc/apk/keys/`, and
|
||||
effectively mandatory signatures (unsigned needs `--allow-untrusted`). This is
|
||||
the only format shater publishes — the `.ipk`/opkg lane was removed in 2026-07
|
||||
(`DECISIONS.md` D22); every device we serve is on 25.12 with apk-tools 3.
|
||||
|
||||
> **Format:** OpenWrt 24.10 (our SDK) uses **opkg** (`.ipk`, `Packages.gz`, usign),
|
||||
> so the feed is `src/gz` and the trust anchor is the usign key
|
||||
> `dist/shater-feed.pub` (fingerprint **`5ac4b177689cb8e0`**). apk only replaces
|
||||
> opkg at OpenWrt **25.12** — see §6.
|
||||
|
||||
One-time setup on the router:
|
||||
|
||||
```sh
|
||||
# 1) trust the feed key — the FILENAME must equal the usign key fingerprint.
|
||||
wget -O /etc/opkg/keys/5ac4b177689cb8e0 \
|
||||
https://git.qomar.pw/omar/shater/releases/download/latest/shater-feed.pub
|
||||
|
||||
# 2) add the feed (one URL serves every arch).
|
||||
echo "src/gz shater https://git.qomar.pw/omar/shater/releases/download/latest" \
|
||||
>> /etc/opkg/customfeeds.conf
|
||||
|
||||
# 3) refresh + install (shaterd is pulled in as a dependency).
|
||||
opkg update
|
||||
opkg install luci-app-shater # -> shater-core -> shaterd
|
||||
opkg install byedpi # optional: ByeDPI desync egress
|
||||
```
|
||||
|
||||
With the key installed, opkg's default `check_signature 1` verifies the feed on
|
||||
every `opkg update`; no `--nocheck-signature` needed. A **tagged** release
|
||||
(`vX.Y.Z`) publishes the identical layout at
|
||||
`.../releases/download/vX.Y.Z` if you prefer to pin a version instead of tracking
|
||||
`latest`.
|
||||
|
||||
### Updating
|
||||
|
||||
Name the packages. **Never run a bare `opkg upgrade`** — with no arguments it
|
||||
tries to upgrade *every* installed package from *every* configured feed, which on
|
||||
OpenWrt means base/system packages on the overlay and is a well-known way to
|
||||
brick a router.
|
||||
|
||||
```sh
|
||||
opkg update
|
||||
opkg upgrade shaterd shater-core luci-app-shater byedpi # only our own packages
|
||||
```
|
||||
|
||||
Drop `byedpi` from the list if you never installed it. An upgrade is offered only
|
||||
when the feed's `Version` differs from the installed one — that is exactly what
|
||||
bug B4 broke (v0.2.2…v0.2.6 all published as `0.2.0-r3`). Since then CI derives
|
||||
the version from the git tag on every build (§2.1), so there is nothing to bump
|
||||
by hand any more; check with:
|
||||
|
||||
```sh
|
||||
opkg list-installed | grep -E 'shaterd|shater-core|luci-app-shater|byedpi'
|
||||
```
|
||||
|
||||
## 6. apk feed (OpenWrt/ImmortalWrt 25.12+ — incl. BananaWRT 25.12-mtk-vendor)
|
||||
|
||||
OpenWrt/ImmortalWrt **25.12** replaces opkg with Alpine's **apk**: `.apk` files,
|
||||
a binary `packages.adb` index, EC (prime256v1) keys in `/etc/apk/keys/`, and
|
||||
effectively mandatory signatures (unsigned needs `--allow-untrusted`). The
|
||||
package **Makefiles are unchanged** — the SDK release decides the format.
|
||||
|
||||
CI builds this lane **in parallel** with the opkg feed (same manual triggers:
|
||||
`v*` tag push or `workflow_dispatch`): the `build-apk` jobs in
|
||||
`.gitea/workflows/release.yml` compile the same 4 packages through the official
|
||||
**ImmortalWrt 25.12 SDK** (tarballs from
|
||||
CI (`v*` tag push or `workflow_dispatch`) compiles the 4 packages through the
|
||||
official **ImmortalWrt 25.12 SDK** (tarballs from
|
||||
`downloads.immortalwrt.org/releases/25.12.1/targets/{x86/64,mediatek/filogic}/`)
|
||||
and publish **one release per arch** — rolling `apk-latest-x86_64` /
|
||||
`apk-latest-aarch64_cortex-a53`, or `apk-vX.Y.Z-<arch>` for a tagged version.
|
||||
Per-arch (unlike the combined opkg release) because apk filenames carry no
|
||||
architecture and packages are fetched relative to the `packages.adb` URL.
|
||||
and publishes **one release per arch**: the rolling `apk-latest-x86_64` /
|
||||
`apk-latest-aarch64_cortex-a53`, plus `apk-vX.Y.Z-<arch>` on a tag. Per-arch
|
||||
because apk filenames carry no architecture and packages are fetched *relative to
|
||||
the `packages.adb` URL*, so one flat multi-arch release would collide.
|
||||
|
||||
> **Key:** apk cannot use the usign key. The apk trust anchor is the separate EC
|
||||
> public key **`dist/shater-apk.pem`** (generated once by `ci/gen-apk-key.sh`;
|
||||
> private half lives ONLY in the Gitea secret **`KEY_APK`**, the apk analog of
|
||||
> `KEY_BUILD`). Never regenerate either key — that invalidates every deployed
|
||||
> router's trust. The usign identity `shater-feed.pub` keeps signing the
|
||||
> opkg/24.10 feed, untouched.
|
||||
> **Key:** the trust anchor is the EC public key **`dist/shater-apk.pem`**
|
||||
> (generated once by `ci/gen-apk-key.sh`; the private half lives ONLY in the
|
||||
> Gitea secret **`KEY_APK`**). Never regenerate it — that invalidates every
|
||||
> deployed router's trust.
|
||||
|
||||
One-time setup on a 25.12 router (BananaWRT `25.12-mtk-vendor` on the BPI-R3
|
||||
mini, BPI-R4 on 25.12, or the future 25.12 VM — `/etc/apk/arch` picks the right
|
||||
per-arch release automatically):
|
||||
### 5.1 Rolling or pinned — pick the repo URL deliberately
|
||||
|
||||
The repo line names an **index file**, and which one you name is the whole
|
||||
update policy:
|
||||
|
||||
| Repo line points at | Behaviour | Cost |
|
||||
|---|---|---|
|
||||
| `apk-latest-<arch>/packages.adb` (**rolling**) | Every release run REPLACES this release's assets, so `apk update && apk upgrade <our packages>` always sees the newest build. Install once, never touch the file again. | You get whatever CI published last; there is no per-router pin. |
|
||||
| `apk-vX.Y.Z-<arch>/packages.adb` (**pinned**) | The router stays on exactly that build. `apk update` will never offer a newer shater. | `/etc/apk/repositories.d/shater.list` must be edited **by hand on every upgrade**, on every router. |
|
||||
|
||||
`mini_router` is deliberately on a **pinned** URL — a considered choice, and the
|
||||
hand-edit per release is its price. Use rolling unless you specifically want to
|
||||
freeze a device.
|
||||
|
||||
> The rolling release used to go stale silently: publishing was an either/or, so
|
||||
> tag runs wrote only `apk-vX.Y.Z-<arch>` and `apk-latest-<arch>` was last
|
||||
> refreshed on 2026-07-24 at `0.2.0` while v0.2.9/v0.2.10 shipped. A router on
|
||||
> the rolling URL kept getting a successful `apk update` with nothing new. Fixed
|
||||
> 2026-07-25: `release-apk` writes the rolling pointer on **every** run and then
|
||||
> reads the release back over the Gitea API, asserting it holds our three
|
||||
> tag-versioned packages at exactly the version just built and **no** leftover
|
||||
> asset at another version (two versions of one package in one index would let
|
||||
> apk choose instead of us).
|
||||
|
||||
### 5.2 One-time setup on the router
|
||||
|
||||
BananaWRT `25.12-mtk-vendor` on the BPI-R3 mini, OpenWrt 25.12 on the BPI-R4, or
|
||||
the testbed VM — `/etc/apk/arch` picks the right per-arch release automatically:
|
||||
|
||||
```sh
|
||||
# 1) trust the apk feed key (any *.pem filename under /etc/apk/keys works).
|
||||
@@ -251,6 +220,7 @@ wget -O /etc/apk/keys/shater-apk.pem \
|
||||
"https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/shater-apk.pem"
|
||||
|
||||
# 2) add the repo — the line points at the packages.adb INDEX FILE itself.
|
||||
# (rolling; for a pinned router put apk-vX.Y.Z-$(cat /etc/apk/arch) here — §5.1)
|
||||
echo "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/packages.adb" \
|
||||
> /etc/apk/repositories.d/shater.list
|
||||
|
||||
@@ -260,7 +230,7 @@ apk add luci-app-shater # -> shater-core -> shaterd
|
||||
apk add byedpi # optional: ByeDPI desync egress
|
||||
```
|
||||
|
||||
### Updating
|
||||
### 5.3 Updating
|
||||
|
||||
**Never run a bare `apk upgrade`.** With no arguments apk reconciles *every*
|
||||
installed package against *every* configured repository at once; on a router
|
||||
@@ -286,9 +256,8 @@ Drop `byedpi` from either list if you never installed it. Check what you are on
|
||||
with `apk list -I shaterd shater-core luci-app-shater byedpi` — the version reads
|
||||
`0.2.7-r1` (§2.1: `PKG_VERSION-rPKG_RELEASE`, derived from the git tag by CI, so
|
||||
every build really is a new version; before that fix v0.2.2…v0.2.6 all published
|
||||
as `0.2.0-r3` and `apk update` offered nothing). Pin a version instead of tracking
|
||||
rolling by pointing the repo line at
|
||||
`.../download/apk-vX.Y.Z-$(cat /etc/apk/arch)/packages.adb`.
|
||||
as `0.2.0-r3` and `apk update` offered nothing). Rolling vs pinned repo URL —
|
||||
§5.1.
|
||||
|
||||
### BananaWRT `25.12-mtk-vendor` compatibility
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ OpenWrt). Лицо репозитория и быстрый старт — в к
|
||||
| Документ | О чём |
|
||||
|----------|-------|
|
||||
| [CONTEXT.md](CONTEXT.md) | **Начните здесь** — контекст проекта, история v0.1→v0.2, решения в кратце, testbed/инфра |
|
||||
| [INSTALL.md](INSTALL.md) | Сборка ship-артефакта (`shaterd`) и установка обоих фидов — opkg (24.10) и apk (25.12+) |
|
||||
| [INSTALL.md](INSTALL.md) | Сборка ship-артефакта (`shaterd`) и установка apk-фида (25.12+): роллинг или фиксация версии |
|
||||
| [ARCHITECTURE.md](ARCHITECTURE.md) | One-binary дизайн, auth-handoff LuCI→панель, data/DNS/apply-потоки (диаграммы) |
|
||||
| [FEATURES.md](FEATURES.md) | Полный список фич с тегами MVP/T1/T2 |
|
||||
| [ROADMAP.md](ROADMAP.md) | Фазовый план |
|
||||
|
||||
@@ -104,7 +104,7 @@ build new logic in the `shater/`, `panel/`, `openwrt/` overlay.
|
||||
|
||||
## Phase 8 — Ship it ✅ DONE
|
||||
- Adapt CI to build/sign the single forked binary for both arches; publish the
|
||||
signed opkg feed (reuse key `5ac4b177689cb8e0`); install/upgrade docs.
|
||||
signed feed (apk since D22, EC key `dist/shater-apk.pem`); install/upgrade docs.
|
||||
- Set an upstream-rebase cadence (merge new sing-box-lx tags, run the smoke suite).
|
||||
|
||||
## Cross-cutting (every phase)
|
||||
|
||||
@@ -21,8 +21,8 @@ PKG_NAME:=byedpi
|
||||
# ci/version.sh). PKG_VERSION here is THIRD-PARTY UPSTREAM's version — it is what
|
||||
# PKG_SOURCE_URL/PKG_HASH pin, and what tells an operator which ByeDPI is
|
||||
# actually installed. Stamping our tag on it would be both a lie and a
|
||||
# regression: our tags are 0.2.x, and every version comparator (apk-tools 3 and
|
||||
# opkg alike, verified) reads 0.2.7 < 0.17.3 — component-wise numerically, 2 < 17
|
||||
# regression: our tags are 0.2.x, and the version comparator (apk-tools 3,
|
||||
# verified) reads 0.2.7 < 0.17.3 — component-wise numerically, 2 < 17
|
||||
# — so the "new" package would be a DOWNGRADE and routers would refuse it.
|
||||
# Bump PKG_RELEASE BY HAND when *our packaging* of it changes (init script, uci
|
||||
# defaults, build flags); bump PKG_VERSION+PKG_HASH when upstream releases.
|
||||
|
||||
@@ -38,9 +38,9 @@ PKG_NAME:=shaterd
|
||||
# VERSIONING — derived from the git tag, NOT hand-maintained here (bug B4).
|
||||
# ci/version.sh turns `git describe` into SHATER_PKG_VERSION/SHATER_PKG_RELEASE
|
||||
# (tag vX.Y.Z -> X.Y.Z + r1; off-tag -> last tag + r<commits+1>), and
|
||||
# ci/build-feed.sh / ci/build-feed-apk.sh export them into the SDK build env of
|
||||
# both lanes. Both lanes then ASSERT that the produced .ipk/.apk really carries
|
||||
# that version, so a lost env can never silently ship a stale one again.
|
||||
# ci/build-feed-apk.sh exports them into the SDK build env. ci/sdk-build-apk.sh
|
||||
# then ASSERTS that the produced .apk really carries that version, so a lost env
|
||||
# can never silently ship a stale one again.
|
||||
# The literals below are ONLY the manual/offline fallback (no CI, no git) — they
|
||||
# are not "the release version"; releases are named by the tag.
|
||||
PKG_VERSION:=$(if $(SHATER_PKG_VERSION),$(SHATER_PKG_VERSION),0.2.0)
|
||||
@@ -104,8 +104,8 @@ define Package/shaterd/install
|
||||
$(INSTALL_BIN) $(CURDIR)/files/$(SHATERD_BIN) $(1)/usr/bin/shaterd
|
||||
endef
|
||||
|
||||
# This package ships ONLY the binary — no init script — so opkg's default
|
||||
# postinst never touches the running service. On `opkg upgrade shaterd` the new
|
||||
# This package ships ONLY the binary — no init script — so the package manager's
|
||||
# postinst never touches the running service. On `apk upgrade shaterd` the new
|
||||
# ELF lands at /usr/bin/shaterd while the OLD image keeps running from its
|
||||
# unlinked inode: the upgrade silently has no effect until the next reboot, and
|
||||
# meanwhile the new CLI (`shaterd reconcile`, `status`, `mint-token` — invoked by
|
||||
|
||||
Reference in New Issue
Block a user