Files
shater/docs-shater/CONTEXT.md
T
omarandClaude Opus 5 f86501bf77 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
2026-07-25 18:46:21 +03:00

7.7 KiB
Raw Blame History

Project context (read this first)

This is the durable, single-source-of-truth context for shater v0.2 — kept in the repo so it survives conversation compaction and new sessions. If you are an agent or a new contributor picking this up: read this file, then ROADMAP.md, FEATURES.md, ARCHITECTURE.md, DECISIONS.md.

What shater is

An internet-control appliance for OpenWrt routers: a whole-network transparent proxy + DNS filter + traffic-analytics box, configured from a rich web admin panel. One device turns a home/office network into: VPN-through-the-router (split by domain/geo/client, no DNS leaks), an ad/tracker/malware blocker, per-device parental control, and a detailed live dashboard — all local, all self-hosted.

Target hardware: Banana Pi BPI-R3 (MT7986/Filogic 830) and BPI-R4 (MT7988/Filogic 880), both the OpenWrt mediatek/filogic target (package arch aarch64_cortex-a53). x86_64 is the QEMU test VM.

Where we came from — v0.1 (branch v0.1)

The v0.1 git branch holds a complete, working, VM-verified first version. Do not delete it — we port proven pieces from it. What v0.1 has:

  • xrayctl — a Go control-plane: parses subscription share-links, renders xray-core JSON + an nftables inet shater table + policy routing, applies atomically with xray -test/nft -c validation and commit-confirm rollback.
  • shater-core — procd init, hotplug reconcile, sysctl, fw4/routing glue, default UCI config. Fail-closed kill-switch, live-flag /var/run/shater.active, watchdog cron.
  • 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 + 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, 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.

The v0.2 pivot (decided in the session that created this file)

We are rebasing onto a new engine and a new UI architecture. Full rationale in DECISIONS.md. Short version:

  1. Engine → a FORK of sing-box-lx, with our whole product embedded inside it. github.com/Leadaxe/sing-box-lx is a thin, rebaseable downstream fork of SagerNet/sing-box adding AmneziaWG 2.0 (I1–I5 CPS decoy packets), XHTTP, MASQUE/WARP, and gRPC observability (DNS queries / rules / outbounds). Upstream sing-box brings VLESS/VMess/Trojan/Shadowsocks/WireGuard/Reality + Hysteria2/ TUIC. It is library-first (libbox) and GPL-3.0 (compatible with us).

    • We fork it (not just depend on it) so we can embed literally everything — control-plane, admin panel, DNS filter — and integrate tightly with the engine internals (DNS, routing, stats). This is a deliberate, decided trade-off: maximum integration over minimum maintenance.
    • Maintainability discipline (mandatory): our overlay lives in NEW top-level dirs (shater/, panel/, openwrt/) so it never conflicts with upstream files on rebase. Any unavoidable edit to an upstream file is minimal and marked // shater. We rebase/merge onto sing-box-lx (and thus sing-box) tags on a schedule — the same model sing-box-lx uses on sing-box. Fork ≠ divergence; fork = additive overlay tracking upstream tags.
    • We do not write a proxy engine from scratch (byte-precise anti-DPI arms race — reuse, never reinvent).
  2. UI → thin LuCI launcher + separate full admin panel (embedded in the binary). LuCI stays minimal: a small, pretty mini-dashboard plus an "Open panel" button. That button mints a short-lived token inside the already- authenticated LuCI session (via ubus) and redirects to our standalone admin panel served on its own port by the daemon. The panel validates the token with the daemon and opens a session. Panel auth is bootstrapped from LuCI's existing auth (no second login to secure); the real UX — detailed config, rich live stats, per-device control — is a modern SPA we fully own, served by and embedded in the forked binary.

  3. We own the value layers: control-plane, DNS filter + blocklists (flexible sources: inline / file / url / geosite), per-domain + per-device statistics, per-device policy, schedules, alerts. Built inside the fork, hooking the engine's DNS/routing/stats directly.

  4. License → GPL-3.0 (sing-box is GPL-3.0; our former GPL-2.0-or-later files upgrade cleanly).

Repository model

  • shater main = our fork of sing-box-lx. After Phase 1 it contains the full sing-box-lx tree PLUS our additive overlay (shater/, panel/, openwrt/, docs-shater/). Upstream is tracked via a git remote and merged by tag.
  • 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/, the feed signing key).

What to port from v0.1 (don't rewrite these ideas)

Engine-agnostic and proven — port and adapt into the shater/ + openwrt/ overlay, don't redo:

  • The reliability layer: atomic apply, validate → stage → swap, commit-confirm rollback, idempotent hash-compared reconcile under flock, live-flag semantics, hotplug/boot persistence, management-bypass, fail-closed kill-switch.
  • nftables inet shater tproxy/mark/counters + policy routing (own marks/ tables, never touch fw4).
  • 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 + 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.

New in v0.2: sing-box config generation (replaces xray JSON) via the engine's own types, the embedded admin-panel web server + token-handoff auth, the DNS filter/stats engine wired into sing-box's DNS.

Infra & testbed (for whoever continues)

  • Repo: https://git.qomar.pw/omar/shater (Gitea). Default branch main = v0.2 fork; branch v0.1 = the working xray-based version.
  • Upstream to track: https://github.com/Leadaxe/sing-box-lx (which tracks 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: 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), drivable with the Playwright MCP.

Current status

Repo reset done: v0.1 preserved on its branch; main cleaned to this docs-first scaffold. Next is Phase 1 in ROADMAP.md — fork sing-box-lx into main (add upstream remote, merge a pinned tag), stand up the embedding prototype (prove AmneziaWG 2.0, measure binary size with feature-trim + -s -w + UPX) before building the control plane and panel.