Commit Graph

71 Commits

Author SHA1 Message Date
CyberMind
90a323e6e8
secubox-p2p: Kademlia DHT + federation health-checks + hierarchical master-link (#774) (#775)
Some checks are pending
License Headers / check (push) Waiting to run
* feat(p2p): DHT, Federation, MasterLink evolutions for P2P-EVO-2026-07-001

Implements three major features for secubox-p2p:

1. DHT (Distributed Hash Table) based on Kademlia algorithm
   - Peer discovery across different subnets
   - Resilience to temporary node failures
   - Scalability for large number of nodes
   - REST API endpoints for DHT management

2. Service Federation
   - Service registration and discovery
   - Multi-version service management
   - Automatic health checks
   - DHT integration for service propagation

3. Master-Link Hierarchical Topology
   - Master/satellite/leaf hierarchical structure
   - Automatic master election and failover
   - Heartbeat monitoring
   - Routing policy management
   - OPAD security integration

New files:
- api/dht.py: Kademlia DHT implementation
- api/federation.py: Service federation system
- api/masterlink.py: Hierarchical topology manager
- api/main_evolutions.py: Integrated FastAPI server
- tests/test_dht.py: Unit tests for DHT

Issue: P2P-EVO-2026-07-001
Worktree: secubox-p2p-evolutions

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

* revert(p2p): remove non-integrating DHT/federation/masterlink code, keep requirements

The DHT/federation/masterlink modules + main_evolutions.py + test_dht.py did not
integrate with the module: tests imported 'secubox.p2p.api.*' (the package uses
'from api import ...' via conftest sys.path) so nothing even collected, and the
modules reinvented standalone aiohttp servers instead of building on the existing
WireGuard mesh (mesh.py) + annuaire-backed registry (registry.py/annuaire_client.py).
Removing them to restart on the real base. The requirements doc
(.github/ISSUES/2026-07-P2P-EVOLUTIONS.md) is kept as the spec source.

* docs(p2p): point the local evolutions note to real issue #774

The '.github/ISSUES/2026-07-P2P-EVOLUTIONS.md' was a local file with a fabricated
id, not a tracked issue. Replace its contents with a pointer to the real GitHub
issue #774 (Kademlia DHT + hierarchical master-link + federation health-checks),
framed against the already-done federation (#766) and directory (#768) work and
the master-link/auto-enrollment issue (#762).

* docs(spec): p2p Kademlia DHT + master-link + federation health-checks (#774)

Clean redesign on the real base (mesh.py + registry/annuaire_client) after
reverting the non-integrating attempt. Custom minimal Kademlia (pure asyncio
UDP, JSON wire, signed reachability records), federation health-checks layered
over the annuaire federation (#766) publishing status via the DHT, and a
deterministic-election master-link (term-based failover, aligns #762). All
feature-flagged off by default (OPAD opt-in), from api import convention, TDD
per module. Refs #774.

* docs(plan): p2p DHT/federation/master-link TDD implementation plan (#774)

17 bite-sized TDD tasks across 3 phases (DHT 1-9, federation health-checks
10-13, master-link 14-16, finalization 17). Each task = failing test -> minimal
impl -> pass -> commit, on the real base (from api import ...). Crypto (Ed25519
sign/verify) and UDP transport are module-level seams injected in unit tests so
the Kademlia primitives, health debounce, and election/term logic are tested
without real sockets. Refs #774.

* feat(p2p): DHT scaffold — node id + xor distance (#774)

* feat(p2p): DHT k-bucket with LRU (#774)

Add DHTNode contact + DHTBucket (k-bucket with LRU ordering).

- DHTNode: dataclass with node_id, did, endpoint, last_seen
- DHTBucket: Kademlia k-bucket with OrderedDict LRU management
  - add(node) → bool (True if stored/refreshed, False if full)
  - refresh moves node to tail (most-recent)
  - oldest() returns head node
  - remove(node_id) and nodes property for list access

All Task 1 tests + new Task 2 tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(p2p): DHT routing table + closest-N (#774)

* feat(p2p): DHT signed reachability records + verify (#774)

Implement canonical_record, sign_record, verify_record functions with
crypto SEAMS (_did_from_pubkey, _verify_sig, _sign_sig) for testing.
Tests use monkeypatching; real crypto wired in Task 8.

Canonical records are deterministic sorted JSON. verify_record checks
for sig field, DID validity via wg_pubkey, and cryptographic signature.

* feat(p2p): DHT JSON UDP codec + hardening (#774)

* feat(p2p): DHTNetwork store+dispatch (transport-injected) (#774)

* feat(p2p): DHT iterative find_node/find_value + announce (#774)

Add asyncio-based iterative Kademlia lookup on top of the injected
DHTNetwork transport from Task 6:

- pending: dict[rpc_id, Future] on DHTNetwork, resolved by handle_message
  when a reply (pong/nodes/value/ok) arrives with a matching rpc_id.
- _rpc(node, msg): send + await the matching future with RPC_TIMEOUT,
  returns None on timeout.
- iterative_find(target_id, mode): alpha-parallel iterative lookup,
  dedups queried nodes, merges discovered contacts into the shortlist
  (dedup by node_id, skip self), terminates on no-improvement-in-a-round
  or exhausted candidates; mode="value" short-circuits on first verified
  record.
- find_peer(did) / announce(): DID resolution and self-record
  publication (sign, cache locally, push to the k closest nodes found).

Tests (tests/test_dht.py, +2 async, pytest-asyncio strict mode):
- in-process UDP router delivering via call_soon (never inline) so
  awaited RPC futures resolve on a later loop turn;
- find_peer resolves a record announced by a node reachable only
  through a bootstrap intermediary;
- dedup: a node introduced twice into the shortlist (via two different
  repliers) is queried exactly once.

All 16 tests green (14 prior + 2 new).

* fix(p2p): harden DHT iterative lookup — skip malformed contacts, cap shortlist, tolerate rpc exceptions (#774)

* feat(p2p): DHT record schema (id+wg keys) + real Ed25519 sign/verify (#774)

* feat(p2p): DHT UDP transport + routing persistence + bootstrap (#774)

* feat(p2p): mount DHT — endpoints + startup guard + [dht] config (#774)

Task 9: wire the finished Kademlia DHT (api/dht.py) into the FastAPI
app as a feature-flagged background service, fully backward compatible.

- api/mesh.py: load_p2p_config() now also returns a `dht` sub-dict
  (enabled/port/bootstrap/announce/announce_interval/rps) built from an
  optional [dht] toml section, with defaults when absent.
- api/main.py: @app.on_event("startup") _dht_startup creates+binds a
  DHTNetwork only when [dht].enabled is true; any failure (missing
  identity, bind error, ...) is caught and leaves app.state.dht = None
  so the app always starts. _dht_shutdown persists the routing table
  and closes the UDP transport.
- New endpoints (bare paths, nginx adds /api/v1/p2p prefix): GET
  /dht/peers (routing snapshot), POST /dht/announce (JWT-protected,
  audit-logged to /var/log/secubox/p2p-audit.log), GET /dht/find/{did}.

Tests: tests/test_dht_wiring.py (config defaults/overrides + disabled
TestClient smoke). All 22 existing DHT tests + 3 new tests green (74
total in the package).

* feat(p2p): federation HealthStore + debounce (#774)

* feat(p2p): federation HealthChecker sweep (#774)

* feat(p2p): federation real probe + registry services (#774)

* fix(p2p): bound TCP-probe close, declare aiohttp dep, guard non-dict service (#774)

* feat(p2p): federation health publish-via-DHT + endpoints + [federation] config (#774)

* fix(p2p): wire federation probe_timeout + harden sweep/health-TTL tests (#774)

* feat(p2p): master-link elect() + term store (#774)

* feat(p2p): master-link state machine + failover (#774)

* fix(p2p): master-link equal-term tie-break (no zero-master window) + defensive peers (#774)

* feat(p2p): master-link signed heartbeats + UDP transport + tick loop (#774)

* feat(p2p): mount master-link — endpoints + startup guard + [masterlink] config (#774)

- masterlink.MasterLink.request_promotion(): on-demand promotion attempt
  (bump term, run election, become MASTER + heartbeat on win, else CANDIDATE)
- mesh.load_p2p_config(): add [masterlink] section defaults/overrides
  (enabled, role_preference, priority, heartbeat_interval, election_timeout,
  port, peer_addrs) alongside existing [dht]/[federation]
- main.py: _masterlink_startup/_masterlink_shutdown mirror the DHT/federation
  defensive startup pattern (feature-flagged, never breaks app startup);
  GET /masterlink/topology (public) + POST /masterlink/promote (JWT, audited
  to p2p-audit.log)

* fix(p2p): master-link request_promotion records winner on loss + strengthen test (#774)

* docs(p2p): document DHT/federation/master-link evolutions (#774)

Add an "Evolutions (#774)" section to packages/secubox-p2p/README.md
covering the DHT (api/dht.py), federation health-checks (api/federation.py)
and hierarchical master-link (api/masterlink.py) subsystems: what each does,
their API endpoints, the real p2p.toml config defaults from api/mesh.py,
and the audit log. All three remain OFF by default / OPAD opt-in. Docs only,
no behavior change. Append a matching dated entry to .claude/HISTORY.md.

* fix(p2p): final-review fixes — audit-log path, master-link peers_fn, wg_pubkey, handle_message hardening (#774)

* fix(p2p): implement mesh visualization tab — load on activation + master-link roles (#774)

The Mesh tab canvas was never rendered (the tab-switch handler didn't call
initMesh, and drawing into a hidden tab gave clientWidth 0 -> blank canvas).
Now: initMesh fires when the Mesh tab is activated, retries if the canvas has
no layout yet, and enriches the star graph with the live master-link state
(role/term/master + DHT peer count from /masterlink/topology + /dht/peers) —
center node coloured gold=MASTER / cyan=SATELLITE, peers by online status.

* docs(p2p): poster GPT + roadmap des évolutions DHT/federation/master-link; maj WIP/HISTORY/TODO (ref #774)

---------

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 06:59:35 +02:00
b945c831a0 fix(image): policy-rc.d so the kiosk (X11/chromium) installs in chroot
Some checks are pending
License Headers / check (push) Waiting to run
The live-usb kiosk stack (dbus, X11, chromium) aborted its postinst in the
init-less chroot ('Failed to connect to system message bus', invoke-rc.d
errors), failing the build. Add /usr/sbin/policy-rc.d (exit 101) before the
installs and remove it before squashfs, so packages don't try to start
services at build time but the booted system still does. Keep kiosk ON for
amd64 USB (extra_args=--kiosk). Do NOT disable kiosk.
2026-06-28 11:05:08 +02:00
1a60f82de9 fix(ci): build pure-Go pkgs with -d (golang-go metapackage dep-check) (ref #760)
dpi/toolbox-ng/waf-ng are CGO_ENABLED=0 cross-compiles; the go toolchain is
present (golang-1.22-go) but dpkg-checkbuilddeps trips on the golang-go
metapackage. Skip the dep check for just these three (-mod=vendor, self-contained).
2026-06-28 09:10:45 +02:00
8d5627567d fix(ci): build-packages arch=both must expand to both arches (ref #760)
The discover matrix filter only compared requested_arch against amd64/arm64/
empty, so the 'both' workflow_dispatch option produced an EMPTY matrix (no
builds, collect failed). Normalize 'both' -> empty (= all arches).
2026-06-28 06:57:25 +02:00
8bbc573149 fix(ci): install golang-go so arm64 pure-Go packages build (ref #760)
secubox-dpi and secubox-toolbox-ng are CGO_ENABLED=0, GOARCH=arm64,
-mod=vendor offline cross-compiles. They failed the arm64 build only
because golang-go was never installed, so dpkg-checkbuilddeps aborted on
their `Build-Depends: golang-go (>= 1.22)`. Not a CGO cross-toolchain
gap. ubuntu-24.04 ships golang-go >= 1.22; installing it lets the pure-Go
arm64 cross-compile run on the amd64 runner.

Unblocks a complete amd64+arm64 package set for the apt repo + arm64 images.
2026-06-28 06:52:40 +02:00
dac89d9e1c revert(ndpid): drop nDPId daemon path — keep ndpiReader (perf on saturated board)
Decision (operator): nDPId is a permanent capture daemon + nDPIsrvd process →
continuous CPU/RAM on an already-saturated board (load ~4.6/4 cores), whereas the
ndpiReader producer runs in bounded niced 60s windows (~1% CPU) and frees the
core between passes. The richer-JSON / no-respawn gain doesn't justify the perf
risk, and the QEMU cross-build failed first try (fragile path). The exfil
pipeline was never cut over, so live is unaffected.

Reverts #722/#723: removes .github/workflows/build-ndpid.yml and restores
secubox-ndpid to its prior arch:all stub. DPI Phase 3 = ASN (#719) + history/
timeline (#721); nDPId dropped. ndpiReader remains the producer.
2026-06-22 17:16:48 +02:00
26b7d7a080 build(ndpid): Phase 3 nDPId daemon CI cross-build + package wiring (ref #722)
nDPId is a C daemon needing libnDPI >= 5.0 (bookworm ships 4.2) and the sandbox
cross-toolchain can't link C, so:
- .github/workflows/build-ndpid.yml — QEMU arm64 NATIVE build inside
  debian:bookworm (cmake -DBUILD_NDPI=ON bundles the right libnDPI), commits
  nDPId + nDPIsrvd to packages/secubox-ndpid/bin/ + uploads an artifact.
  Manual (workflow_dispatch), ndpid_ref input to bump.
- secubox-ndpid: Architecture all → arm64, Depends libpcap0.8 (dropped the
  external ndpid|ndpi-reader dep), debian/rules ships bin/nDPId + bin/nDPIsrvd to
  /usr/sbin (guarded so it builds before CI populates bin/). 1.1.0.

Follow-on (after CI yields a validated binary): daemon service capturing
wg-toolbox → JSON socket, and secubox-dpi-flowcap consuming nDPId JSON instead of
the ndpiReader CSV (PoC stays as fallback).
2026-06-22 13:35:57 +02:00
68e2723747 fix(webext): tag-pinned .xpi release URL + make_latest:false (ref #532)
Publishing webext-v* with the default make_latest would steal the
"latest" release pointer from the Android APK release, breaking the
APK endpoint's /releases/latest/download/secubox-toolbox-android.apk
fallback. Publish the webext release with make_latest:false and point
the /wg/toolbox.xpi endpoint + fetch helper at the tag-pinned download
URL instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:45:40 +02:00
b2ee2a97ef fix(webext): clean web-ext lint — drop unused optional_host_permissions, ignore build.sh/README (ref #532)
web-ext lint: 0 errors, 2 benign warnings (AMO data-collection
declaration is submission-time, tied to the signing follow-up;
service_worker-ignored is the intentional cross-browser pattern).
optional_host_permissions needed FF128 and was unused at MVP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:21:45 +02:00
c77e6250a9 feat(webext): browser extension — emancipate R3 cartographie live (ref #532)
New client clients/webext-toolbox/ (WebExtension MV3, Firefox .xpi +
Chromium): surfaces the toolbox live tracker analysis in the browser.

- manifest.json MV3, cross-browser background (service_worker + scripts
  for Firefox 115+ event page); host_permissions *.secubox.in only
- api.js: shared client over /wg/r3-check, /social/me (pair → HMAC token),
  /social/graph/{token}, /social/wipe/{token}
- background.js: toolbar badge = live tracker count, silent re-pair on
  token expiry, colour escalates gold → anti-bot → operator-grade
- popup: 4 stat tiles + dependency-free mini Round-Eye SVG graph + top
  trackers tagged CDN/anti-bot/operator-grade + cartographie/PDF/RGPD-wipe
- options: host / analysis window / manual token
- build.sh + build-webext.yml (web-ext lint + build, release on webext-v*)

Serve from the toolbox (2.6.14):
- GET /wg/toolbox.xpi (local file, else 302 → latest release asset)
- '🧩 Extension navigateur' button on both onboard panels
- sbin/secubox-toolbox-fetch-xpi + postinst serve dir + rules install

No server-side CORS needed (MV3 host_permissions). MVP polls /social/graph
and computes the delta client-side; SSE /social/live is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:08:06 +02:00
cf3aef48c8 feat(toolbox): serve Android APK from /wg/toolbox.apk + onboard button (#536)
Follow-up to #531 — one-tap APK install from the cabine.
  - api.py GET /wg/toolbox.apk: serve local APK (android content-type),
    302 → latest GitHub release asset if absent (never dead-ends).
  - /wg/onboard Android panels (inline + _install_panels): 📱 Installer
    l'app ToolBoX (1-tap) button.
  - sbin/secubox-toolbox-fetch-apk: pull latest release asset into the
    serve path (best-effort + APK magic check); postinst creates the dir
    + first fetch.
  - build-android-apk.yml: publish APK as release asset
    secubox-toolbox-android.apk on android-v* tags (contents:write).
  - changelog 2.6.13.

Live on gk2: /wg/toolbox.apk 302 → release (fallback, no release yet),
serve dir created, onboard button rendered.
2026-06-12 23:46:29 +02:00
ab67c981dd feat(android): scaffold one-tap toolbox R3 installer + CI (ref #531)
Kotlin/Jetpack-Compose app under clients/android-toolbox/ replacing the
manual Android onboarding tutorial. 5-step flow:
  discover -> install CA (KeyChain intent) -> import WG profile (handed
  to the WireGuard app via FileProvider) -> verify (/wg/r3-check) ->
  live cartographie sociale (/social/me).

  - ToolboxApi: plain HttpURLConnection client for /wg/ca.crt,
    /wg/profile/new, /wg/r3-check, /social/me (no Retrofit/OkHttp —
    minimal deps + CI).
  - MainActivity: Compose stepper UI, palette-matched (cosmos/gold/cyan),
    FR copy. Intents for CA install + WireGuard handoff + Play fallback.
  - Gradle (AGP 8.5.2 / Kotlin 1.9.24 / Compose BOM 2024.06), minSdk 26,
    targetSdk 34, package in.secubox.toolbox. No wrapper jar committed.
  - res: adaptive launcher icon (vector eye glyph), theme, file_paths,
    strings — all text/XML, no binaries.
  - .github/workflows/build-android-apk.yml: setup-android + setup-gradle
    8.9 build assembleDebug -> APK artifact (sideloadable).
  - README documents the flow, build, and the Android user-CA-trust
    constraint (MVP guides manual confirm; release signing is follow-up).

Closes the scaffold half of #531; CI produces the debug APK.
2026-06-12 18:15:46 +02:00
3d52b63d26 ci: drop espressobin-v7 + espressobin-ultra from scheduled image matrix (ref #503)
The two boards fail in the cross-arm64 chroot stage of build-image.yml
and (even with fail-fast: false) block the downstream release.yml job
from publishing the OTHER boards' images.  Releases v2.13.9 through
v2.13.12 all hit this trap.

This change keeps both entries available via workflow_dispatch (operators
can still build them on-demand), but removes them from the push-tag
scheduled matrix so mochabin / vm-x64 / rpi400 actually ship on tag.

Board support files (board/espressobin-*/, image/build-image.sh
--board espressobin-*) stay in tree.

  - build-image.yml matrix excludes espressobin-v7 + espressobin-ultra
    on push and workflow_call.
  - build-image.yml workflow_dispatch choice list keeps both entries
    flagged as on-demand only.
  - Release notes template drops the two image rows, adds a note
    explaining the on-demand path.
  - Install instruction adjusted (MOCHAbin + Raspberry Pi 400).
2026-06-09 09:42:08 +02:00
4341da96a0 fix(ci): collect+upload kernel .deb on failure (linux-image often built before headers fail)
The bindeb-pkg target builds linux-image-*.deb FIRST then attempts the
linux-headers-*.deb. The headers build can fail (silent in CI log) while
linux-image is already on disk and ready to deploy. The deployment-
critical artifact for the MochaBin mesh integration is linux-image —
headers/libc-dev are needed only for out-of-tree module builds against
this kernel (which we don't do today).

Add `if: always()` to both Collect and Upload steps so partial
artifacts survive a late bindeb-pkg failure. Downgrade if-no-files-found
from `error` to `warn` so a fully empty /tmp/artifacts/ doesn't mask
the real upstream failure with a misleading artifact error.
2026-06-02 14:17:22 +02:00
6eb399eade fix(ci): heredoc → loop for arm64 ports.list (YAML parser couldn't handle col-0 EOF)
Previous attempt used a heredoc with EOF marker at column 0, which
YAML parses as a new top-level key, breaking the workflow file.
Replace with the same loop pattern used elsewhere in the step
(echo | tee -a with iteration over codename suites).
2026-06-02 13:29:37 +02:00
c37368de7a fix(ci): point arm64 apt sources to ports.ubuntu.com (security/archive serve amd64 only)
Ubuntu's security.ubuntu.com and archive.ubuntu.com host amd64/i386
only. arm64 packages live on ports.ubuntu.com.

After `dpkg --add-architecture arm64` + `apt-get update`, the runner
404s on:
  https://security.ubuntu.com/ubuntu/dists/noble/main/binary-arm64/Packages

Fix: pin existing entries to [arch=amd64] and add [arch=arm64] entries
pointing to http://ports.ubuntu.com/ubuntu-ports/. Detected codename
via /etc/os-release so the workflow stays compatible across runner
updates (jammy/noble/numbat).

Unblocks libssl-dev:arm64 install for cross-arm64 kernel .deb build.
2026-06-02 13:26:23 +02:00
e17f757931 fix(ci): enable arm64 multi-arch + install libssl-dev:arm64 in build-kernel.yml
dpkg-buildpackage -aarm64 (cross-build) resolves Build-Depends to the
target arch. The kernel package's debian/control declares
`Build-Depends: libssl-dev` without arch suffix, so dpkg-checkbuilddeps
looks for libssl-dev:arm64 and fails when only libssl-dev (amd64) is
installed.

Add `dpkg --add-architecture arm64` before `apt-get update` so the
arm64 package index is available, then install libssl-dev:arm64
alongside the host libssl-dev.

This is a pre-existing CI gap, exposed by run 26816056566. Unrelated
to the secubox-mesh fragment (signed-regdb was a red herring).
2026-06-02 13:24:20 +02:00
67c54ee421 feat(kernel): add 802.11s mesh stack (mt76x2u/ath10k_pci/ath9k_htc) — CM-MESH-MPCIE-2026-06 v0.2.1
Append WIRELESS/WIFI block to OpenWrt-merged kernel fragment so the next
build-kernel.yml run ships:
  - mac80211 + 802.11s mesh + HWMP (=m)
  - ath10k_pci for QCA9880 / WLE900VX backhaul mesh
  - mt76x2u for MT7632U USB access client
  - ath9k_htc for AR9271 firmware-free audit dongle
  - signed regulatory DB (FR domain lock via secubox-mesh postinst)

Add four sanity-check greps in build-kernel.yml so a future fragment
regression fails the CI before producing a kernel .deb without WiFi.

Manual workflow_dispatch of build-kernel.yml required to materialise
the new linux-image-*.deb (artifact path not yet wired into
build-image.yml — see tracking issue).
2026-06-02 12:59:55 +02:00
8dc43ca6f9 fix(ci): install binutils-aarch64-linux-gnu for arm64 build-packages (ref #431)
After #427 added '-a matrix.arch' to dpkg-buildpackage, debhelper now
correctly invokes the cross-toolchain binaries:

  - aarch64-linux-gnu-strip  (used by dh_strip)
  - aarch64-linux-gnu-objdump (used by dh_makeshlibs)

…which were not installed on the amd64 CI runner. Result: arm64 builds
of arch-specific packages shipping prebuilt arm64 ELF binaries failed:

  - secubox-sentinelle-gsm — dh_strip on bin/secubox-redsea
  - secubox-daemon         — dh_makeshlibs on secuboxctl

Install binutils-aarch64-linux-gnu (~5 MB) only when matrix.arch == arm64.
Folded into the existing apt step to keep one network round-trip.

Single workflow change > per-package debian/rules no-op overrides:
- no need to revisit every future arch-specific package
- preserves real strip benefit (smaller .deb)
- fixes the CI gap at the source

This is the final piece behind #427 + #425 to unblock APT publish on
arm64 since v2.13.0.
2026-05-30 14:38:04 +02:00
cd177020e5 fix(ci): pass -a \${{ matrix.arch }} to dpkg-buildpackage (closes #427)
build-packages.yml ran `dpkg-buildpackage -us -uc -b` without -a, so on the
amd64 ubuntu-latest runner the arm64 matrix slot defaulted to amd64. For
Architecture-specific packages (notably secubox-sentinelle-gsm =
Architecture: arm64) that means no binary is produced and dpkg-genbuildinfo
fails with "binary build with no binary artifacts found" → that job has
been red since v2.13.0 and gated the APT publish step.

For amd64 matrix jobs the change is a no-op. For arm64 matrix jobs on the
amd64 runner, packages that don't compile native code (Python only, or
prebuilt arm64 binaries as in sentinelle-gsm) cross-stamp the .deb fine
with -a arm64. Compiling-from-source arm64 packages aren't a thing in this
repo today.

Verified locally: dpkg-buildpackage -a arm64 -us -uc -b on
secubox-sentinelle-gsm produces a clean
secubox-sentinelle-gsm_0.4.0-1~bookworm1_arm64.deb (with #425's shlibdeps
-Xsecubox-redsea, already on master).
2026-05-30 13:06:22 +02:00
94da816b51 fix(rpi-build): implement --kiosk flag + wire it on rpi400 in CI (closes #423)
The --kiosk flag in image/build-rpi-usb.sh was parsed but never acted on
(INCLUDE_KIOSK toggled but no install block existed), so the rpi400 image
shipped without chromium / X / kiosk service. The boot menu's apply_mode
"kiosk" branch even references a secubox-kiosk.service that was never
created. Adding all the missing pieces:

- New Step 5.4 block (gated by INCLUDE_KIOSK=1):
  - apt install xserver-xorg xinit openbox chromium x11-xserver-utils
  - install image/kiosk/secubox-kiosk.sh → /usr/share/secubox/kiosk/
  - install image/kiosk/xinitrc → /root/.xinitrc
  - create /etc/systemd/system/secubox-kiosk.service (startx on vt7,
    Conflicts with getty@tty7; matches apply_mode's expected unit name)
  - seed /var/lib/secubox/boot-mode = "kiosk" so first boot enters kiosk
  - set default systemd target to graphical.target + enable the unit

- CI build-all-live-usb.yml: add `extra_args: "--kiosk"` to the rpi400
  matrix entry and append `${{ matrix.extra_args }}` to the build invocation.
  Pi 400 = keyboard + HDMI, kiosk-by-default is the obvious user-facing
  shape; other platforms keep their headless default.
2026-05-30 10:04:44 +02:00
CyberMind
97015bbb7f
fix(kernel-build): install build-essential so dpkg-checkbuilddeps passes (#336)
Build #5 cleared the ZRAM choice sed fix (#326) and reached the
bindeb-pkg step, then failed at:

  dpkg-checkbuilddeps: error: Unmet build dependencies:
    build-essential:native libssl-dev

bindeb-pkg auto-generates a debian/control that Build-Depends on
`build-essential:native`. The previous install list pulled in gcc /
make / libc-dev individually but never the meta-package, so
dpkg-checkbuilddeps failed even though all the underlying tools were
present.

libssl-dev is also in the same error line but it IS already installed
— it gets satisfied once build-essential lands because of the way
checkbuilddeps re-resolves the full Build-Depends graph in one pass.

Ref #319 #323 #325 #326.

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
2026-05-22 08:02:01 +02:00
CyberMind
f56ece69ca
fix(kernel-build): force ZRAM_DEF_COMP_ZSTD via sed POST-olddefconfig (#328)
After three rounds of failed attempts (#323, #325, #326), the root cause
is clear: kconfig `choice` blocks are re-resolved by olddefconfig from
their declared `default <member>` in Kconfig, regardless of what's in
.config. `scripts/config --enable ZRAM_DEF_COMP_ZSTD` applied BEFORE
olddefconfig is silently undone by olddefconfig's choice pass; merging
a fragment that sets `CONFIG_ZRAM_DEF_COMP_ZSTD=y` is overridden the
same way.

The only reliable approach is post-olddefconfig sed surgery on the
choice members. Choice members are leaf symbols with no downstream
build-time deps, so no further olddefconfig is needed after the
override.

Workflow change:
  - move scripts/config out of the choice (only set ZSMALLOC/CRYPTO_*)
  - run olddefconfig to resolve the rest of the tree
  - sed the choice into ZSTD (LZORLE off, ZSTD on)
  - log .config before/after the fixup for diagnostics
  - keep the final assertion `^CONFIG_ZRAM_DEF_COMP_ZSTD=y`

Ref #323 #325 #326.

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
2026-05-22 07:05:13 +02:00
CyberMind
8e5e08449f
fix(kernel-build): enforce ZRAM choice via scripts/config after merge (#326)
merge_config.sh + olddefconfig don't always honour choice-symbol
overrides set in a fragment (kconfig special-cases choices). Run
scripts/config after the merge to set ZRAM_DEF_COMP_ZSTD explicitly
and disable the upstream-default ZRAM_DEF_COMP_LZORLE.

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
2026-05-22 06:45:59 +02:00
CyberMind
c24ddcfed3
fix(kernel-build): assert CONFIG_ZRAM_DEF_COMP_ZSTD not the auto-derived string (#323)
CI run 26222437600 failed at the sanity-check: kconfig didn't emit
CONFIG_ZRAM_DEF_COMP="zstd" verbatim because that symbol is the
auto-derived output of the CONFIG_ZRAM_DEF_COMP_<X> choice family,
not a settable variable. Check the choice itself instead.

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
2026-05-21 17:16:15 +02:00
CyberMind
c23d3256e8
ci(kernel-build): GitHub Actions cross-arm64 workflow (closes #297) (#298)
Manual-dispatch workflow that produces a fresh linux-image-*.deb
artifact when source-side kernel config changes (e.g. #295 ZRAM).

* workflow_dispatch inputs: kernel_version (default 6.12.85),
  revision (default 2secubox).
* Steps: checkout, install cross toolchain + build deps, fetch
  upstream tarball, apply repo patches, merge OpenWrt-merged base
  fragment + ZRAM fragment, sanity-grep key configs, run
  `make bindeb-pkg` to cross-build .deb, upload all .debs as
  artifact with 30-day retention.
* Concurrency-grouped on ref so a re-dispatch cancels the old run.
* 90-minute timeout (typical run ~25-40 min).

Operator workflow:
  gh workflow run "Build SecuBox kernel (cross arm64)"
  gh run download <run-id>
  scp linux-image-*.deb root@gk2:/tmp/
  ssh root@gk2 dpkg -i /tmp/linux-image-*.deb
  ssh root@gk2 reboot

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
2026-05-21 08:47:57 +02:00
CyberMind
edb3ce79b2
ci(build-packages): arch-aware matrix scheduling (closes #203) (#205)
The discover step previously scheduled an amd64 build for every
non-arch-all package, ignoring an explicit `Architecture: arm64`
or `Architecture: amd64` in debian/control. The amd64 job then
failed with `dpkg-genbuildinfo: error: binary build with no
binary artifacts found` because dpkg produces zero binaries for
an arch-foreign declared package on the build host.

Replace the implicit "amd64-always" logic with a case on the
binary stanza's Architecture field:

  all   -> amd64 (the arch-all .deb deployable on any arch)
  any   -> amd64 + arm64 (preserve current behavior)
  amd64 -> amd64 only
  arm64 -> arm64 only

Local dry-run on the current 133-package catalog: 132 amd64 + 1
arm64 combos (secubox-daemon is the only arch-any package), well
under the 256-jobs-per-matrix GH Actions cap.

Read the Architecture from the BINARY stanza (after Package:) so
the Source stanza's Architecture (which Debian convention sometimes
omits) doesn't interfere — awk pattern is `/^Package:/{p=1} p && /^Architecture:/{print $2; exit}`.

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 06:38:13 +02:00
CyberMind
3435cfa069
feat(scripts): add check-dashboard-cache.py lint + CI (closes #147) (#148)
Anti-regression for the double-cache pattern documented in CLAUDE.md
(« Performance Patterns — Double Caching »). Catches the bug class that
caused PR #146 (secubox-waf 17 % sustained CPU under SOC polling)
before it lands.

What's added
============
- scripts/check-dashboard-cache.py — AST-based audit. Walks every
  packages/secubox-*/api/main.py, flags hot dashboard routes
  (/stats, /alerts, /summary, /dashboard, /metrics, /overview,
  /events, /history, /recent, /bans, /sessions, /connections)
  whose handler body does per-request I/O (open, read_text,
  read_bytes, subprocess.run, Popen) AND doesn't go through a cache
  (module-level _cache instance + .get() call, OR
  asyncio.create_task(refresh_*) at startup).
    * --check : exit 1 on unjustified violations (CI mode)
    * --report : human-readable table (default)
    * --json : machine-readable

- .cache-lint-allowlist.toml — TOML allowlist for justified
  exceptions. Each entry needs a daemon + route + justification
  (issue link expected). Seeded with the 4 current legacy cases:
  secubox-waf (covered by PR #146 — will drop the entries when
  merged), secubox-config-advisor /history (admin drill-in, not
  polled), secubox-netdiag /connections (real-time TCP state),
  secubox-tor /summary (small file read, follow-up cleanup).

- .github/workflows/dashboard-cache-check.yml — mirror of
  license-check.yml. Runs the lint + the self-tests on PRs that
  touch packages/**/api/main.py, the lint script, the allowlist,
  or the workflow itself.

- docs/CACHE-PATTERN.md — short remediation guide. Three accepted
  shapes (read-through, background-refresh, pure in-memory), code
  example, allowlist syntax, references to the canonical
  implementations (secubox-crowdsec for read-through, secubox-system
  for background-refresh).

Tests (14/14 pass)
==================
- StatsCache pattern detection (compliant cases)
- Non-compliant detection: open, subprocess.run, multiple I/O ops
- Pure in-memory handler = compliant
- Background-refresh task = compliant
- Non-hot route (/health) not audited
- @router.get treated like @app.get
- Allowlist suppresses by exact (daemon, route) key
- Allowlist doesn't cross-pollinate across routes
- Missing allowlist file = no error
- CLI --check exits 1 on unjustified
- CLI --check exits 0 when clean
- CLI --json output well-formed
- **Real repo audit with seeded allowlist passes --check**
  (sanity gate: catches regression if anyone removes an allowlist
  entry without fixing the daemon)

Baseline against the real packages/ tree
========================================
122 daemons audited, 118 compliant, 4 with findings (all
allowlisted). When PR #146 merges, the secubox-waf entries can be
removed from the allowlist — the next lint run will then prove the
fix is wired correctly.

Follow-up
=========
- Drop secubox-waf allowlist entries when #146 merges.
- Per-daemon follow-up issues for the remaining 3 (config-advisor,
  netdiag, tor) — not blocking, allowlist documents the rationale.
- Consider extending HOT_ROUTES to /health / /status once the small
  daemons that call `systemctl is-active` adopt a 10 s cache.

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:21:20 +02:00
8e8708f95d ci+packaging: unblock v2.9.0 release — 4 package fixes + partial-release resilience
Five package builds failed on v2.9.0 first attempt, and the release
pipeline cascaded to skip all downstream jobs (collect/publish/images/
live-usb/create-release) because GH Actions treats *any* matrix failure
as a global failure for `needs:` purposes. This commit fixes both
root causes:

Package fixes:
- secubox-eye-square: drop debian/compat (conflicted with control's
  `Build-Depends: debhelper-compat (= 13)`)
- secubox-defaults: same — drop debian/compat
- secubox-metoblizer: switch from legacy `Build-Depends: debhelper
  (>= 13)` (which then requires a debian/compat file) to the modern
  `Build-Depends: debhelper-compat (= 13)` virtual package
- secubox-haproxy: override dh_usrlocal — it can only rehome /usr/local
  directories (Policy 9.1.2), not the individual admin tools we
  deliberately drop there per issue #44

Pipeline resilience:
- build-packages.yml `collect` job: `if: always() && != cancelled` so we
  bundle whatever .deb files the matrix produced even when entries failed
- build-packages.yml `publish` job: predicate now reads collect's success
  rather than the matrix's overall conclusion
- release.yml `build-images`, `build-live-usb`, `publish`,
  `create-release`: all gain `if: always() && needs.build-packages.result
  != 'cancelled'` so a partial build matrix doesn't black-hole the
  release. `create-release` already gracefully skips its
  `if: needs.build-images.result == 'success'` download steps when
  images failed, so the partial release ships what's available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 10:14:50 +02:00
8d02cdc97b ci(build-packages): wire env: for discover inputs + tighten find pattern
Follow-up to the previous matrix-cap fix:

1. YAML/shell quoting hell: the inline ${{ github.event.inputs.arch }}
   substitution inside a single-quoted heredoc broke parsing on push:tags
   events (where inputs are null). Moved both inputs to step `env:` block
   so the shell script reads them as ordinary env vars.

2. Find pattern was `-name control -path "*/debian/*"`, which also matched
   dpkg-build artifacts at `packages/<pkg>/debian/<pkg>/DEBIAN/control` on
   any non-clean checkout. CI's runners are clean so it worked there, but
   the pattern is fragile. Tightened to `-path "*/debian/control" -not
   -path "*/debian/*/DEBIAN/control"`.

Verified locally: 132 amd64 + 2 arm64-eligible = 134 combos, well under
GH Actions' 256 jobs-per-matrix cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 09:07:21 +02:00
65202dbf9a ci(build-packages): flat matrix to stay under 256-jobs-per-matrix cap
Discover now emits a single flat list of {package, arch} entries
pre-filtered to skip arch-all+arm64 combos. Build uses `matrix.include`
instead of a cross-product, so the job count == filtered combo count
regardless of package catalog size.

Why: with 134 packages, the previous package × [amd64, arm64] expansion
produced 268 matrix jobs, exceeding GitHub Actions' hard 256 cap. The
build job was silently skipped (zero matrix entries scheduled), which
in turn skipped collect → publish → build-images → build-live-usb →
create-release. Releases since v2.7.x have shipped only the installer
ISO artifact for this reason.

The post-discover skip step for "arch-all + arm64" becomes dead code
but remains harmless (the condition can never fire now); leaving in
place so a future re-introduction of cross-product matrices doesn't
silently re-break.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 09:04:27 +02:00
CyberMind
7c37415f88
feat(remote-ui): Phase 1 — extract common/ shared core (ref #127)
* feat(remote-ui/common): scaffold shared-core directory (ref #127)

* feat(remote-ui/common): extract palette.css from round/ (ref #127)

* fix(remote-ui/common): trim palette.css to spec (6 module + 8 C3BOX tokens) (ref #127)

* feat(remote-ui/common): extract base.css verbatim from round/ (ref #127)

* feat(remote-ui/common): extract ICONS to icons.js (ref #127)

* docs(remote-ui/common): correct icons.js header comment — sizes are {22,48,96} not 128 (ref #127)

* feat(remote-ui/common): extract RINGS + CX/CY/SA to modules-table.js (ref #127)

* feat(remote-ui/common): extract CFG to config.js (replaces jwt-helper.js per #127 plan revision)

* feat(remote-ui/common): extract TransportManager with onModuleTap/onTransportChange hooks (ref #127)

* feat(remote-ui/common): extract SIM + simStep to sim.js (ref #127)

* feat(remote-ui/common): move 24 SecuBox module PNG icons to common/assets/icons/ (ref #127)

* feat(remote-ui/common): move secubox-otg-gadget.sh, add GADGET_NAME env override + arm64 serial fallback (ref #127)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(remote-ui/common): trim whitespace from device-tree serial-number read (ref #127)

* feat(remote-ui/common): move secubox-otg-host-up.sh + variant-aware comment (ref #127)

* refactor(remote-ui/round): consume common/ via <link>/<script> tags (ref #127)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(secubox-system): add form_factor to RemoteUIConnectedRequest with TDD (ref #127)

* feat(remote-ui/round): deploy.sh bundles common/ alongside round/ (ref #127)

* fix(remote-ui/round): deploy.sh COMMON_SRC path resolution + rsync --delete (ref #127)

* feat(remote-ui/round): build-eye-remote-image.sh embeds common/ + nginx /common/ alias (ref #127)

* docs(remote-ui): document common/ dependency in round/ docs (ref #127)

* docs(remote-ui/round): clarify palette.css is forward-looking (ref #127)

* docs(remote-ui): Task 18 regression-gate report — structural verification (ref #127)

Full diffoscope gate blocked by missing hyperpixel2r.dtbo prerequisite.
Structural equivalence verified instead: Phase 1 changes are purely
additive (common/ embed + nginx /common/ alias + extracted JS/CSS/icons),
no behavioural change to round/'s existing logic.

User must run the full image diffoscope manually after sourcing the
hyperpixel2r.dtbo blob, before final merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(eye-remote): bump workflow VERSION env 2.2.0 → 2.2.1 (ref #127)

Pre-existing drift: build-eye-remote-image.sh writes
secubox-eye-remote-2.2.1.img but the workflow's Compress/Checksum/
Upload steps reference ${{ env.VERSION }} = 2.2.0, so the compress
step fails with "No such file or directory" after a successful build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 05:59:40 +02:00
4d937995ba ci(license): add License Headers workflow (ref #81)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:00:54 +02:00
bd7dda0c6f feat(secubox): complete meta-script generator Tasks 14-17
Task 14: Arch Profiles
- Add profiles/arch/arm64.yaml and profiles/arch/amd64.yaml
- Add ResolveWithArch() to merger for base → arch → tier chain
- Add tests for arch profile resolution

Task 15: Package secubox.yaml for All Packages
- Add scripts/generate-secubox-yaml.py generator script
- Generate 131 debian/secubox.yaml files with:
  - Category detection (security, network, system, etc.)
  - Tier assignment (all, lite, standard, pro)
  - API socket and UI path detection

Task 16: APT Repository Setup
- Add apt/conf/ reprepro configuration (multi-arch arm64/amd64)
- Add apt/hooks/lintian-check pre-publish validation
- Add scripts/apt-publish.sh and scripts/apt-sync.sh
- Add apt/README.md documentation

Task 17: CI Integration
- Add .github/workflows/build-secubox-cli.yml
- Build for linux-amd64 and linux-arm64
- Version injection via ldflags
- GitHub releases on tag push

Code Quality Fixes (Tasks 10-13):
- Add atomic writes for OTA boot control files (0600 perms)
- Fix NVME device extraction (nvme0n1p2 → nvme0n1)
- Add scanner.Err() checks in hardware detection
- Fix URL injection validation in fetch command
- Add proper cleanup on checksum failures

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-11 05:32:29 +02:00
45c2b7c020 feat: Add metoblizer, streamlit power management, replace espressobin with mochabin
- Add secubox-metoblizer package (centralized log aggregator)
- Streamlit: double-buffer caching, power management UI, autostart
- Streamlit UI: consolidate Components into Status tab
- GitHub Actions: replace espressobin-v7 with mochabin for live USB builds
- Various UI improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-10 18:37:58 +02:00
fa92fe722c feat(ci): Unified sync-all workflow + eyemote visual banner
- Add .github/workflows/sync-all.yml: unified CI/CD on all pushes
  - Discovers changed packages and computes project metrics
  - Auto-updates README badges (packages, migration, endpoints)
  - Auto-updates WIP.md with CI sync entries
  - Creates releases on tags
  - Generates GitHub step summary with metrics table
- Add docs/assets/secubox-eyemote-banner.svg: 6-ring status SVG
  - Matches Round UI design (AUTH/WALL/BOOT/MIND/ROOT/MESH rings)
  - Displays live metrics: 131 packages, 2000+ endpoints, 94% migration
  - Cyberpunk/hermetic palette with glow effects
- Update README.md: add eyemote banner at top
- Update docs/wiki/Home.md: add eyemote banner at top

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-10 09:28:00 +02:00
555600d5dd fix(multiboot): Ensure kernel files are properly copied to EFI partition
- Fix glob pattern in bash test (glob in [[ -f ]] doesn't work correctly)
- Use find command instead of glob for reliable kernel detection
- Add fallback to extract ARM64 kernel from live USB image if not in rootfs
- Add verification step in GitHub Actions to check boot files after build
- Sort kernel files by version to get latest when multiple exist

Fixes missing vmlinuz/Image on multiboot USB issue.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 19:29:09 +02:00
d42745aac3 feat(live-boot): Complete live RAM boot implementation v2.2.4-live
- Install live-boot package and rebuild initramfs with live-boot scripts
- Create squashfs filesystem (878MB) on data partition sda4
- Update boot.scr with live boot parameters (boot=live, toram)
- Fix wiki sidebar links from [[Page|Display]] to [Display](Page)
- Add Eye-Remote wiki page documentation
- Add sync-wiki.sh script for wiki repository sync
- Add patch-multiboot-efi.sh for post-build EFI patching

Partition layout:
- sda1 (512MB): EFI with kernel, initrd, dtbs, boot.scr
- sda2 (3GB): ARM64 rootfs reference
- sda3 (3GB): x86 rootfs for VirtualBox/QEMU
- sda4 (9.5GB): Data + /live/filesystem.squashfs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 13:30:50 +02:00
2e24fbb5fd ci(multiboot): Add GitHub Action for multiboot image builds
- Create build-multiboot.yml workflow with manual dispatch
- Support configurable image sizes (8/16/32GB)
- Build .deb packages first, then create multiboot image
- QEMU user-mode for cross-arch debootstrap
- XZ compression and GitHub Release integration
- Optional desktop environment inclusion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 11:04:28 +02:00
9516379e93 ci(eye-remote): Add v2.2.0 build workflow with menu system tests
- Add test-menu-system job for pytest validation
- Update VERSION to 2.2.0
- Add create_release manual trigger option
- Update release notes with radial menu features
- Change tag pattern to eye-remote-v* for clarity
- Add PR trigger for CI on changes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-24 11:49:07 +02:00
be92d7df0b docs(eye-remote): Add hardware wiki + sync CI workflow
- Eye-Remote-Hardware.md: GPIO pinout, USB OTG, DPI timings, gadget modes
- Update CI workflow to v2.0.0
- Fix build script: add DNS for chroot network access

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-22 08:56:21 +02:00
8f3d446a07 feat(eye-remote): OFFLINE mode v1.9.0 - pre-install packages via QEMU
Complete rewrite of build-eye-remote-image.sh for pure offline boot:

- Use QEMU ARM chroot to pre-install all packages at build time
- Expand image by 1GB for pre-installed packages (~500MB)
- Pre-installed: chromium, nginx, lightdm, openbox, python3-pil
- Configure lightdm autologin, openbox autostart, nginx in chroot
- Create secubox user with all groups during build
- No rc.local/firstrun needed - boots directly into kiosk

Build requirements: qemu-user-static, binfmt-support

Boot time comparison:
- v1.8.x: ~10 min (package download required)
- v1.9.0: ~60 sec (ready immediately, no internet)

Closes #30

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-20 21:10:56 +02:00
5b6da8a588 ci: Add GitHub Action for Eye Remote image build
- Downloads RPi OS Lite automatically
- Builds complete Eye Remote SD card image
- Compresses with xz and generates checksums
- Uploads to releases on version tags
- Supports manual trigger with WiFi/hostname options
- Part of Eye Remote v1.8.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-20 12:12:11 +02:00
f4806bf7fa fix(ci): Fix eMMC image check and RPi multi-initrd copy
- build-all-live-usb.yml: Check for .img.gz first since build-image.sh
  already compresses and removes the .img file
- build-rpi-usb.sh: Handle multiple kernel/initrd files by selecting
  the latest version instead of globbing which breaks cp

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-15 09:22:23 +02:00
4165d6d66c feat(network): Smart auto-IP with ARP collision detection (v1.7.0.2)
- Add ARP-based IP collision detection for multi-device environments
- MAC-based pseudo-random IP offset to spread devices across range
- Gratuitous ARP announcement to prevent IP conflicts
- Fix EspressoBin DSA network: target wan interface, not eth0 CPU port
- Static IP fallback 192.168.255.250 when DHCP unavailable
- Sync all build scripts to version 1.7.0
- Add screenshot script with 90+ module URLs
- Add mock HTML screenshots for documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-14 16:28:02 +02:00
698a0c728f fix(ci): Fix matrix context in workflow job-level if condition
GitHub Actions doesn't allow matrix.* in job-level 'if' conditions.
Moved platform filtering to step-level with skip output variable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 10:19:13 +02:00
6c7e6051bd feat(ci): Add unified multi-platform live USB workflow
- Add build-all-live-usb.yml with matrix for x64, EspressoBin V7, and RPi 400
- Update release.yml to include live USB builds in release pipeline
- Add RPi 400 netplan configuration for Ethernet and WiFi
- Deprecate build-live-usb.yml (now wrapper to unified workflow)

Platforms supported:
- x64 (amd64) - GRUB UEFI/BIOS boot
- EspressoBin V7 (arm64) - U-Boot distroboot with embedded eMMC image
- Raspberry Pi 400 (arm64) - Native Pi firmware boot

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-11 11:20:46 +02:00
17f4886a7f fix: Handle duplicate assets in release workflow retries
- Add step to delete existing assets before upload (handles workflow retries)
- Update to softprops/action-gh-release@v2
- Add fail_on_unmatched_files: false for graceful handling

Fixes 422 error when retrying failed release uploads.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-08 16:11:05 +02:00
5ebc80fc4b fix: Handle missing GPG_PRIVATE_KEY in CI publish job
Skip signing and deploy steps when secrets are not configured
instead of failing the entire workflow.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-08 10:07:11 +02:00
d7fdc4186e feat: Add QEMU ARM64 VM support and RPi 400 to releases
- Add create-qemu-arm64-vm.sh script for ARM64 emulation on x86 hosts
- Add wiki/QEMU-ARM64.md documentation
- Update CI workflows to include RPi 400 and VM scripts in releases
- Update release notes to document all platforms

Platforms now supported:
- MOCHAbin (Armada 7040)
- ESPRESSObin v7/Ultra (Armada 3720)
- Raspberry Pi 400 / Pi 4
- VirtualBox x64
- QEMU ARM64 emulation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-08 08:13:56 +02:00