25 Commits
Author SHA1 Message Date
omarandClaude Opus 5 024e9308c9 fix(ci/apk): strip the SDK's generated per-package default m blocks
release / aarch64_cortex-a53 (push) Successful in 3m21s
release / x86_64 (push) Successful in 3m12s
release / apk aarch64_cortex-a53 (push) Successful in 5m32s
release / apk x86_64 (push) Successful in 2m32s
release / release (push) Successful in 8s
release / release apk (push) Successful in 5s
Run 60 settled what runs 58/59 left open. The second pass wrote an explicit
`# CONFIG_PACKAGE_kmod-x is not set` for all 1126 selected kmods and re-ran
defconfig; the count came back 1078, unchanged. The same explicit form DID hold
for CONFIG_ALL/ALL_KMODS/ALL_NONSHARED in the same run.

The difference is prompts. kconfig honours a user value only for symbols that
have one — sym_calc_value ignores S_DEF_USER for a promptless symbol and falls
back to its `default`. ALL* carry prompts in the SDK's Config.in; the blocks
convert-config.pl generates are bare:

    config PACKAGE_kmod-mlx5-core
            tristate
            default m

No value written into .config can turn those off, so remove the `default m`
itself: drop every generated `config PACKAGE_*` block from Config-build.in
before the first defconfig. Nothing is lost — those blocks only replay which
packages the buildbot built. The packages stay declared, with prompts, by the
package tree (tmp/.config-package.in), which is what makes our four selectable
and what `select` acts on; KERNEL_*/LIBC/TOOLCHAIN blocks are untouched, so the
SDK still reproduces its own toolchain settings.

The .config second pass is kept as a cheap backstop (it no-ops once the count
is 0), as are both tripwires.

Verified: bash -n on the file and on the extracted INNER body; the paragraph
delete tested on a synthetic Config-build.in (3 PACKAGE blocks -> 0, KERNEL_*,
LIBC and TOOLCHAINOPTS preserved); the missing-file path exercised under set -eu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 01:27:37 +03:00
omarandClaude Opus 5 eab1db2c3f fix(ci/apk): second defconfig pass — deselect the SDK's per-kmod default m
release / aarch64_cortex-a53 (push) Successful in 3m19s
release / x86_64 (push) Successful in 3m14s
release / apk aarch64_cortex-a53 (push) Failing after 1m4s
release / apk x86_64 (push) Failing after 1m3s
release / release (push) Successful in 8s
release / release apk (push) Successful in 5s
Turning ALL/ALL_KMODS/ALL_NONSHARED off (22d7161c0) provably worked — run 59
logs all three as `is not set` after defconfig — and changed the kmod count by
exactly zero, 1078 both times. The kmods never came from ALL_KMODS.

They come from the SDK itself. target/sdk/Makefile generates the SDK's
Config-build.in by running convert-config.pl over the BUILDBOT's .config, in
which ALL_KMODS=y had already expanded into one `CONFIG_PACKAGE_kmod-*=m` line
per module. convert-config.pl turns every `CONFIG_X=<val>` line into a symbol
with an unconditional `default <val>`; its `next if /^(# )?CONFIG_PACKAGE/`
filter sits in the `else` branch, which a line containing `=` never reaches.
The SDK therefore ships ~1078 verbatim blocks of `config PACKAGE_kmod-x /
tristate / default m`, none of which consult ALL_KMODS.

Fix: a second pass. The names only exist after kconfig has expanded the tree,
so after the first defconfig rewrite every selected kmod to `is not set` and
re-run defconfig. Two documented kconfig rules make this exact:
  - an explicit value in .config beats a `default` (same rule that kept our
    `# CONFIG_ALL* is not set` lines alive in run 59) -> the ~1078 stay off;
  - `select` is OR-ed in after the user value, so shater-core's
    `DEPENDS:=+kmod-nft-tproxy +kmod-nft-socket` brings those (and their
    transitive kmods) back on their own.

Also correct the tripwire message, which still blamed CONFIG_ALL_KMODS: it now
prints the ALL* state AND the first few surviving kmods, so the two failure
modes are distinguishable at a glance.

Verified: bash -n on the file and on the extracted INNER heredoc body; the
rewrite simulated against a run-59-shaped .config (1078 -> 0 selected, our 4
packages, LOCALMIRROR and the ALL* lines untouched).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 01:16:05 +03:00
omarandClaude Opus 5 22d7161c08 fix(ci/apk): disable the SDK's ALL/ALL_KMODS mass-select
release / aarch64_cortex-a53 (push) Successful in 3m25s
release / x86_64 (push) Successful in 3m14s
release / apk aarch64_cortex-a53 (push) Failing after 1m3s
release / apk x86_64 (push) Failing after 1m3s
release / release (push) Successful in 8s
release / release apk (push) Successful in 6s
Run 58 proved the previous commit aimed at the wrong thing, and the
diagnostics it added are what showed it: "0 lines carried over" plus a
`grep: .config: No such file or directory`, then 1078 kmods selected
anyway (1109 on x86_64). So an SDK tarball ships no top-level .config at
all — there was never a buildbot config for us to be appending to.

The real source is the SDK's OWN top-level Config.in, target/sdk/files/
Config.in, which it carries instead of the main tree's:

    config ALL_NONSHARED ... default ALL
    config ALL_KMODS     ... default ALL
    config ALL           ... default y

In the main tree all three default to n; the SDK flips ALL to y so that
`make world` in a bare SDK builds something. `make defconfig` therefore
selects the whole kernel from ANY .config, empty or not. This is stock
OpenWrt rather than an ImmortalWrt quirk — openwrt/openwrt's copy is
identical, which also means the awg-openwrt reference builds every kmod
too; it just never meets a disk quota on GitHub's runners.

Fix: write all three out as `# CONFIG_X is not set` before defconfig.
They have prompts in the SDK's Config.in, so they are user-settable and
an explicit value beats the default; `CONFIG_X=n` is not reliably
honoured for bools, hence the `is not set` form. Setting all three, not
just the root ALL, keeps this working whichever symbol roots the chain
in a future SDK.

Drops the hand-rolled CONFIG_TARGET_*/CONFIG_KERNEL_* carry-over as
redundant: target/sdk/convert-config.pl bakes the buildbot's non-package
settings into the SDK's generated Config-build.in as kconfig defaults,
so defconfig reproduces them by itself. A soft branch keeps target
identity and CONFIG_USE_APK if some future SDK does ship a .config.

Diagnostics gain a post-defconfig readout of the three mass-select
symbols and, while the list is short, the actual kmods selected — a
count of 0 is not fatal (the router's base feed carries them) but is
worth seeing. Guards and the 200 threshold are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 00:57:19 +03:00
omarandClaude Opus 5 24c5a1615d fix(ci/apk): build .config from scratch — stop packing all 3593 kmods
release / aarch64_cortex-a53 (push) Successful in 3m19s
release / x86_64 (push) Successful in 3m18s
release / apk aarch64_cortex-a53 (push) Failing after 1m4s
release / apk x86_64 (push) Failing after 1m2s
release / release (push) Successful in 7s
release / release apk (push) Successful in 5s
Both apk jobs of v0.2.2 died with `Disk quota exceeded`. The SDK was
running `apk mkpkg` on 3593 kmod-* packages (mlx5, amdgpu, ata, isdn —
none of which we ship) before it ever got near our four.

Root cause: ci/sdk-build-apk.sh APPENDED our package selections to the
.config that ships inside the ImmortalWrt SDK tarball. That file is the
buildbot's fully-expanded config and carries CONFIG_ALL_KMODS=y plus
CONFIG_ALL_NONSHARED=y (see config.buildinfo next to the SDK), so
`make defconfig` re-selected every kernel module of the target as =m and
package/kernel/linux/compile — pulled in via shater-core's nft kmod
deps — packed the lot.

Fix, modelled on Slava-Shchipunov/awg-openwrt's "Setup SDK and feeds":
start the .config EMPTY so kconfig can only pull in what our packages
actually select. Carried over from the SDK's .config, nothing more:
the target choice and its BOARD/SUBTARGET/ARCH_PACKAGES identities (a
wrong guess here means silently cross-compiling for another arch),
CONFIG_USE_APK (decides .apk vs .ipk — the point of this lane), and
CONFIG_KERNEL_* verbatim (they generate the kernel .config; dropping one
makes the buildsystem reconfigure and rebuild the SDK's prebuilt kernel).

Also adds the diagnostics this lane never had, since a failed run leaves
a 27 MB log: the carried-over identity lines, the post-defconfig kmod
count and target readout, a hard check that all four of our packages
survived defconfig, an abort if the kmod count is back in the hundreds,
and du/df after compile.

opkg lane (ci/sdk-build.sh, ci/make-index.sh) untouched. LOCALMIRROR,
CONFIG_DOWNLOAD_FOLDER and every cache path are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 00:31:28 +03:00
omarandClaude Fable 5 4492f0599c ci: harden feed/packaging shell scripts
release / aarch64_cortex-a53 (push) Successful in 7m3s
release / x86_64 (push) Successful in 3m13s
release / apk aarch64_cortex-a53 (push) Failing after 4m34s
release / apk x86_64 (push) Failing after 2m35s
release / release (push) Successful in 8s
release / release apk (push) Successful in 5s
- ci/make-index.sh: set -e → set -euo pipefail so a failing sha256sum|cut in
  the signed Packages index can't mask an empty SHA256. Script survives -u
  (all vars use :? or :- defaults).
- .github/deb2ipk.sh: quote $2/$DEB_NAME/output, derive the deb name from the
  copied file via basename instead of parsing `ls *.deb` (glob-fragile), add a
  trap-based tmpdir cleanup, and set -euo pipefail.

bash -n clean on both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 23:34:50 +03:00
omarandClaude Opus 4.8 8980a25a59 perf(ci): cache SDK feeds checkouts — the biggest recurring build cost
Audit of the run-51 logs showed actions/cache@v3.3.2 works on the act_runner
(cold: "Cache saved" x4; next job: "Cache restored" in ~2s, npm --fast skip,
usign/dl reused) and the sdk-cache mirror seeds correctly — but the single
biggest recurring cost was NOT cached: `scripts/feeds update -a` re-cloned
base+packages+luci+routing+telephony every run (~7.8 min warm x 4 SDK jobs on
the serial runner ≈ ~28 min/run wasted; github ~1 MB/s from this host).

Cache .cache/feeds/{opkg,apk} (workspace dir, actions/cache-persisted, visible
in the SDK container via --volumes-from) symlinked over the SDK's empty feeds/:
`feeds update` now git-fetches deltas (seconds) instead of full clones, always
checking out feeds.conf's pins. Fail-safe: any error on the cached checkouts
wipes the cache and clones fresh. Key by SDK release (feeds-opkg-24.10.4 /
feeds-apk-25.12.1) — stable across runs, invalidates on an SDK bump; both arch
jobs of a lane share one entry (identical pins, serial runner).

Steady-state warm run: ~60+ min -> ~20-22 min. Also documented in the workflow
header: never key a cache on github.sha — each cache SAVE stalls the act_runner
~3 min, so per-run-changing keys would add +3 min/entry every run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:41:53 +03:00
omarandClaude Opus 4.8 cb983afee1 perf(ci): stall-proof SDK fetch + caching (SDK/dl/go/npm/apt/usign) + concurrency
Builds were dominated by re-fetching the ImmortalWrt 25.12 SDK tarball
(~300 MB) every run, and a stalled downloads.immortalwrt.org transfer wedged
the apk job for 40+ min (plain `wget -q`, no timeout — same class as the
elfutils hang).

- New ci/fetch-sdk.sh (runner-side): cache -> our durable `sdk-cache` release
  mirror -> upstream with a stall-kill (curl --speed-limit 64K --speed-time 60
  --max-time 1800) + 3 retries + zstd-magic/size validation; seeds the mirror
  best-effort (github.token, non-fatal) so cold runs never touch upstream again.
  A 40-min hang is now impossible; the in-container fallback wget also gets
  --timeout=60 --tries=3.
- actions/cache@v3.3.2 (last release on the OLD cache API that Gitea act_runner
  implements; v4/v3.4.x use the new GitHub cache service) for: SDK tarball, SDK
  dl/ sources (hash of package Makefiles; PKG_HASH re-verified so a stale cache
  can't leak a wrong source), Go mod+build (go.sum), npm node_modules
  (package-lock.json) with build-shaterd.sh --fast, apt archives, built usign.
  Degrades safely if the cache server is off — the SDK mirror is independent.
- concurrency group release-${github.ref} cancel-in-progress so a re-dispatch
  cancels the stale run instead of piling up (tags stay isolated).

Signing (usign/apk), both keys, per-arch publish, manual triggers, LOCALMIRROR
and the scoped 4-package collection are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 21:24:49 +03:00
omarandClaude Opus 4.8 b7070ad7e8 fix(ci): install python3-distutils for the ImmortalWrt 25.12 apk-SDK
The apk lane runs the SDK on a bare debian:bookworm host, and the ImmortalWrt
25.12 SDK prerequisite check requires python3-distutils ("Checking
'python3-distutils'... failed. Prerequisite check failed." ->
.prereq-build Error 1), aborting before any package built. The opkg lane was
unaffected because the openwrt/sdk image ships the prereqs. Add
python3-distutils (and python3-setuptools defensively) to the host deps. apk
lane only; opkg untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 17:31:15 +03:00
omarandClaude Opus 4.8 ea24bad232 fix(ci): make $OUT writable for the SDK user and collect only our 4 packages
Two build-harness bugs surfaced once the SDK builds actually ran:

1. Permission denied writing the feed. ci/build-feed.sh creates $OUT as root on
   the runner, but the openwrt/sdk container runs as the unprivileged `buildbot`
   (uid 1000) — so `cp` of the .ipk into $OUT failed ("Permission denied"),
   yielding 0 packages and then "usign signing failed" (nothing to sign). Set
   `chmod 0777 "$OUT"` on the runner before docker run (a chmod from inside the
   container, as buildbot, cannot fix a root-owned dir). The apk lane already
   chmods $OUT from its root debian container, so it was unaffected.

2. Collecting the whole SDK. ci/sdk-build.sh did `find bin -name '*.ipk'`, which
   swept up the hundreds of prebuilt kmod/base .ipk shipped in the SDK image —
   bloating the feed and signing foreign kmods under our key. Collect strictly
   our four by name (`<pkg>_*.ipk`) and require >=4. Applied the same narrowing
   to ci/sdk-build-apk.sh (apk names carry no arch: `<pkg>-*.apk`), keeping the
   "wrong SDK produced only .ipk" guard.

No change to the feed format/signing (usign/KEY_BUILD/shater-feed.pub, apk EC
key), the package set, or triggers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 17:09:07 +03:00
omarandClaude Opus 4.8 1d285cf34f fix(ci): route SDK source downloads through OpenWrt CDN mirror
The apk (and opkg) SDK builds intermittently hung fetching build-time
sources like elfutils-0.192.tar.bz2 from sourceware.org: curl's
--connect-timeout covers only the TCP handshake, not a stalled mid-transfer,
so a slow upstream hangs the whole job (no --max-time in OpenWrt download.mk).

Set CONFIG_LOCALMIRROR=https://sources.cdn.openwrt.org in .config before
`make defconfig` in both ci/sdk-build-apk.sh and ci/sdk-build.sh so the SDK
tries the fast OpenWrt source CDN before each package's own PKG_SOURCE_URL —
fixes elfutils and any other flaky upstream. Mirror verified to hold the file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:50:42 +03:00
omarandClaude Opus 4.8 cd598b0fe2 feat(ci): add apk (ImmortalWrt/BananaWRT 25.12) release lane
Additive next to the opkg/24.10 lane — nothing existing changed. The same 4
packages (shaterd, shater-core, luci-app-shater, byedpi) are built through the
official ImmortalWrt 25.12 apk-SDK and published as per-arch rolling releases
apk-latest-<arch> / apk-<tag>-<arch> (x86_64, aarch64_cortex-a53).

- ci/sdk-build-apk.sh: drives the 25.12 SDK inside debian:bookworm, compiles
  .apk, then `apk mkndx --root T --keys-dir T/keys --allow-untrusted
  --sign KEY --output packages.adb *.apk` — the exact form the OpenWrt 25.12
  buildsystem uses (unsigned members, signed index).
- ci/build-feed-apk.sh: per-arch runner entrypoint (same --volumes-from and
  artifact-order contract as ci/build-feed.sh).
- ci/gen-apk-key.sh: one-shot EC (prime256v1) keypair generator; private half
  -> Gitea secret KEY_APK, public dist/shater-apk.pem committed.
- release.yml: additive build-apk / release-apk jobs; `on:` triggers untouched
  (v* tags + workflow_dispatch); apk release tags deliberately non-`v*`.
- docs-shater/INSTALL.md section 6, .gitignore (out-apk/), dist/shater-apk.pem.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:11:23 +03:00
omarandClaude Opus 4.8 be07e3ba57 ci(shater): Gitea feed build + usign-signed opkg release (Phase 8 ship — complete)
Ports the v0.1 Gitea release flow to the v0.2 single-binary + 4-package
layout, so a tag publishes a signed opkg feed the routers install from.

- .gitea/workflows/release.yml: on tag v* (+ dispatch), matrix over
  {x86_64, aarch64_cortex-a53}. Per arch: setup Go 1.24/Node 20/UPX ->
  scripts/build-shaterd.sh (SPA-embedded shaterd, stages the .upx) ->
  ci/build-feed.sh (OpenWrt SDK container builds all 4 packages ->
  usign-signed Packages index). A release job merges both arches into one
  signed feed + publishes the rolling 'latest'/tag release via the Gitea API.
- ci/sdk-build.sh: in-SDK build — add openwrt/ as the 'shater' feed, feeds
  update/install, make package/{shaterd,shater-core,byedpi,luci-app-shater}/
  compile (shaterd validates+installs the staged prebuilt; byedpi cross-
  compiles from source). ci/make-index.sh: opkg Packages(.gz) + usign sign
  with KEY_BUILD (keyfile umask 077, no secret hardcoded), verifiable by
  dist/shater-feed.pub. ci/install-usign.sh + ci/gitea-release.sh ported.
- INSTALL.md: add the signed feed src/gz line + import dist/shater-feed.pub
  to /etc/opkg/keys; apk (25.12) path noted.

Key kept: usign feed key 5ac4b177689cb8e0 (public dist/shater-feed.pub,
secret Gitea repo secret KEY_BUILD). Decision: opkg (24.10 uses opkg; apk
is 25.12) — matches the existing usign trust anchor.

Verified structurally (no live runner here): release.yml is valid YAML, all
ci/*.sh are bash -n clean, no hardcoded secrets, and every package name/
path/arch/artifact/secret reference cross-checks against openwrt/, scripts/
build-shaterd.sh, and dist/shater-feed.pub. Live-runner unknowns (full SDK
compile of the 4 packages, router-side signature verify) flagged in-agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
2026-07-16 00:29:35 +03:00
omarandClaude Opus 4.8 903f2345f3 chore: reset main for v0.2 (sing-box fork) — full context docs
Foundation pivot. The complete, working, VM-verified xray-based project is
preserved on the `v0.1` branch; `main` is reset to a docs-first scaffold for
v0.2, which will be built as a FORK of sing-box-lx with our control-plane,
DNS filter, stats and admin panel embedded in the one binary.

- Preserve everything on branch v0.1 (pushed).
- Remove the v0.1 implementation + old design docs from main (recoverable from
  v0.1); keep LICENSE, .gitignore, .gitattributes, dist/shater-feed.pub (feed
  signing key 5ac4b177689cb8e0 carries over).
- License -> GPL-3.0 (sing-box is GPL-3.0).
- Add full project context so it survives compaction:
  docs/CONTEXT.md (start here), DECISIONS.md, ARCHITECTURE.md, ROADMAP.md,
  FEATURES.md, and a new README.

Engine/UI decisions (see docs/DECISIONS.md): fork sing-box-lx (AmneziaWG 2.0 +
broad protocols, GPL-3.0, library-first) and embed the whole product for tight
integration; keep the fork maintainable via an additive overlay (shater/, panel/,
openwrt/) rebased on upstream tags. UI = thin LuCI launcher + a separate admin
panel served by the daemon, entered via a short-lived token minted in the
authenticated LuCI session. Do NOT write a proxy engine from scratch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
2026-07-14 13:53:15 +03:00
omarandClaude Opus 4.8 b4b7324db6 feat(ci): sign the opkg feed with usign (key 5ac4b177689cb8e0)
build / aarch64_cortex-a53 (push) Successful in 3m16s
build / x86_64 (push) Successful in 3m3s
build / release (push) Successful in 27s
The published feed is now usign-signed, so routers keep opkg's signature
verification ON instead of needing --no-check-signature.

- ci/install-usign.sh builds the standalone usign on the runner (the index steps
  run on the bare runner, not in the SDK container).
- ci/make-index.sh signs Packages -> Packages.sig with the secret key from the
  Gitea repo secret KEY_BUILD; it now FAILS the build if KEY_BUILD is set but
  usign is missing/broken, rather than silently shipping an unsigned feed.
- build.yml installs usign in both the per-arch build and the combined-index
  release step, passes KEY_BUILD to the release step, and publishes the public
  key (dist/shater-feed.pub) as the release asset shater-feed.pub.
- Setup is now: install the public key once into /etc/opkg/keys/<fingerprint>,
  then plain opkg update/install/upgrade with check_signature left on.

Verified locally on the VM: usign -S/-V round-trips, and with check_signature=1
and only the signed feed, `opkg update` + `opkg install luci-app-shater` succeed
with no --no-check-signature. FEED.md/README updated (key rotation documented).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
2026-07-13 14:37:03 +03:00
omarandClaude Opus 4.8 2202492e76 fix(ci): robust idempotent Gitea release publishing
build / aarch64_cortex-a53 (push) Successful in 2m57s
build / x86_64 (push) Successful in 2m57s
build / release (push) Successful in 6s
The release job failed on re-runs (rolling `latest` already present): a greedy
`.*"id":` match extracted the LAST id on the one-line JSON (the author id, 10)
instead of the release id, so the delete hit a nonexistent release, create then
409'd, and an unguarded `curl -f` aborted the job.

- extract the FIRST "<key>": <n> (release id precedes nested author/asset ids);
- drive the API through a code-returning helper (no `-f` aborts) and branch on
  HTTP status; reuse the existing release on 409;
- delete a same-named asset before re-upload so re-runs replace cleanly;
- per-object tag scan in the list fallback.
Verified end-to-end (two idempotent runs) against the live Gitea API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
2026-07-13 03:59:54 +03:00
omarandClaude Opus 4.8 0270963c59 fix(ci): data-.ipk versions from Makefile so bumps ship; document release flow
build / aarch64_cortex-a53 (push) Successful in 2m58s
build / x86_64 (push) Successful in 2m57s
build / release (push) Failing after 5s
pack-core.sh / pack-luci.sh hardcoded 0.1.0-r7, so shater-core and
luci-app-shater never changed version and `opkg upgrade` ignored new builds.
Derive PKG_VERSION-rPKG_RELEASE from each package Makefile (matching what the
SDK already does for xrayctl); add explicit PKG_VERSION/PKG_RELEASE to the
luci Makefile. Result: shater-core r19, luci-app-shater r8, xrayctl r18.
Document the Gitea auto-release flow and router install in BUILD.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
2026-07-13 03:47:55 +03:00
omarandClaude Opus 4.8 4778f3151b ci: build for both BPI routers + auto Gitea releases
- document/label the arch matrix: aarch64_cortex-a53 covers BOTH production
  routers (mini_router = BPI-R3 / MT7986, main_router = BPI-R4 / MT7988 — both
  mediatek/filogic); x86_64 is the testbed VM. Only xrayctl is arch-specific.
- add a `release` job (needs: build) that publishes automatically via the Gitea
  API (curl, ci/gitea-release.sh — no external action needed on the self-hosted
  runner): push to main refreshes a rolling `latest` pre-release; a `vX.Y.Z`
  tag publishes a versioned release. Assets: per-arch feed tarball
  (shater-feed-<arch>.tar.gz, ready to serve to opkg) + loose .ipk files.
  Auth: repo secret RELEASE_TOKEN if set, else the auto GITHUB_TOKEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
2026-07-13 03:38:18 +03:00
omarandClaude Opus 4.8 ef1c563293 docs(status): full 05-feature-catalog verification matrix; bump to r7
build / aarch64_cortex-a53 (push) Successful in 3m0s
build / x86_64 (push) Successful in 4m4s
Records live-VM proof for EVERY MVP/T1 catalog item (nodes/subs/HAPP/hwid, all six
balancer strategies, chains, full rule matcher set incl. MAC/iface/geosite/geoip,
egress iface/tunnel/proxy, lists, DNS DoH/FakeIP/nftset/hijack/DoT-block/IPv6,
reliability commit-confirm/rollback/watchdog/kill-switch/hotplug, per-client &
per-rule stats, LuCI CRUD via Playwright, CI both arches, feed install+upgrade).
Two bugs found & fixed during the sweep (balancer observatory; group probe_url
override). PKG_RELEASE -> 7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:27:07 +03:00
omarandClaude Opus 4.8 73f6c7a3c5 fix(probe): let globals.probe_url actually win; bump packages to r6
The group parser defaulted probe_url/probe_interval to gstatic/60s via optOr, so
EVERY group carried a non-empty ProbeURL that overrode the new global observatory
setting in newBuilder — the Settings "Health-check URL" never took effect. Groups
now start empty and only override when explicitly set; the built-in default lives in
newBuilder. Verified: setting globals.probe_url -> the generated observatory block
uses it (cloudflare/30s), xray -test OK.

Also bumps PKG_RELEASE 5->6 across the packages for the probe/observatory feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:58:09 +03:00
omarandClaude Opus 4.8 be323ad2b6 chore: bump packages to r5; record panel-polish proofs (Nodes/Overview/Egress)
build / aarch64_cortex-a53 (push) Successful in 3m1s
build / x86_64 (push) Successful in 3m1s
PKG_RELEASE 4->5 for the Nodes Test fix, Overview dashboard redesign, and egress
exit-interface data-plane. PROOFS.md updated with the live-VM evidence; verified via
opkg upgrade r4->r5 and Playwright over all 11 pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:26:01 +03:00
omarandClaude Opus 4.8 51f2c129ca chore: bump xrayctl+shater-core to r4; record DNS/chain/per-client/rollback/upgrade proofs
build / aarch64_cortex-a53 (push) Successful in 2m59s
build / x86_64 (push) Has been cancelled
PKG_RELEASE 3->4 for the DNS-guard + engine-reload fixes. PROOFS.md updated with the
live-VM evidence for: DNS :53 hijack + DoT block, 2-hop chain exit, per-client A/B
split, observatory alive (186/254), rollback/commit-confirm, and opkg feed upgrade
r3->r4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:20:53 +03:00
omarandClaude Opus 4.8 05920f83ad fix: eliminate reconcile fork-storm (OOM) + resolve UCI config conflict with xray-core
build / aarch64_cortex-a53 (push) Successful in 3m0s
build / x86_64 (push) Successful in 2m59s
Two release-blocking defects found via live 512M OpenWrt VM testing.

1) Fork-storm / OOM (critical). Reconcile() -> reloadXray() ->
   "/etc/init.d/shater reload" -> reload_service -> shater_reconcile ->
   `xrayctl reconcile` -> Reconcile() was an infinite mutual recursion; each
   level blocked on CombinedOutput and spawned a process (854x reconcile +
   853x reload observed), exhausting RAM and pinning both vCPUs -> OOM-killer.
   - Reconcile() no longer calls reloadXray(); procd's file-watch on run.json
     restarts xray. Reconcile runs inside the init lifecycle, so calling the
     init back is the recursion.
   - reloadXray() now runs `/etc/init.d/shater start` (not `reload`); it is only
     invoked from Apply()/Rollback() (LuCI/ubus/CLI), never the init lifecycle.
   - init reload_service() simplified to start/stop (dropped the double reconcile).
   Verified: `xrayctl apply` 0.39s (was >185s hang); 93 procs / load 0.00 stable
   with an active 254-node config on 512M; xray binds :10853 API + :12345 tproxy.

2) UCI config namespace clash. shater shipped /etc/config/xray, which collides
   with the xray-core dependency's own /etc/config/xray (opkg dropped ours to
   /etc/config/xray-opkg, so the schema never took effect). Renamed the UCI
   namespace xray -> shater everywhere (Go uci export/commit, all 11 LuCI views +
   acl, init scripts, hotplug, Makefile conffiles, examples). Runtime paths
   (/etc/xray/run.json, /usr/share/xray), the xray-core package, and the ubus
   object name are intentionally unchanged.

Also: ci/pack-xrayctl.sh (local ipk packer) with a postinst chmod so the binary
is executable regardless of host-tar mode handling; PKG_RELEASE bumped to r3.

Verified end-to-end on a fresh 512M OpenWrt 24.10.3 VM: feed install
(opkg install luci-app-shater pulls xrayctl+shater-core), real LAN client
(netns in br-lan) proxied through a live subscription node -> exit IP changed
(104.156.233.234 vs 45.131.214.140 direct); per-client nft counters + xray
stats API populated. See PROOFS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:36:48 +03:00
omar b51a138410 ci: lean build — xrayctl via SDK, shater-core+luci as data .ipk; fix pack path
build / aarch64_cortex-a53 (push) Failing after 3m0s
build / x86_64 (push) Failing after 3m0s
- build-sdk.sh: build ONLY xrayctl (its deps are light) — no kernel/kmod rebuild
  (shater-core's kmod-nft-tproxy dep was dragging in a full kernel build)
- pack-core.sh: shater-core data .ipk (Depends/conffiles/uci-defaults per Makefile)
- pack-luci.sh: fix $OUT to an ABSOLUTE path (relative path after 'cd $W' made tar
  write to the wrong dir -> 'Cannot open ... No such file')
- workflow: add Pack shater-core step
- all pack scripts dry-run green locally (valid .ipk structure + index)
2026-07-09 03:12:41 +03:00
omar 6108d5d5fb ci: share workspace into nested SDK container via --volumes-from
build / aarch64_cortex-a53 (push) Failing after 4m5s
build / x86_64 (push) Failing after 4m5s
bare -v $PWD failed (act_runner DinD: $PWD is a host path that doesn't exist)
-> 'No such file /repo/ci/build-sdk.sh'. Use --volumes-from $(hostname) so the
job's workspace volume is mounted in the SDK container; build-sdk.sh now reads
$REPO=$GITHUB_WORKSPACE and guards against an unmounted repo.
2026-07-09 02:59:34 +03:00
omar da1182dead ci: custom SDK build (clean feed) — gh-action-sdk indexed 0 packages from repo-root feed
build / aarch64_cortex-a53 (push) Failing after 31s
build / x86_64 (push) Failing after 49s
- ci/build-sdk.sh: run openwrt/sdk image with a CLEAN single-package feed; compile
  xrayctl (Go) + shater-core (in-tree, files only, no dep rebuild)
- ci/pack-luci.sh: luci-app-shater as data .ipk via tar (no luci-base compile)
- ci/make-index.sh: opkg Packages index + SHA256 (+ usign sign if KEY_BUILD set)
- workflow: docker-run the SDK per arch (x86_64, aarch64_cortex-a53), upload feed
  This mirrors the recipe verified locally that produced all 3 installable .ipk.
2026-07-09 02:55:18 +03:00