From cf3aef48c8f8e582e540b57ba912ea55bcfbf4f7 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 12 Jun 2026 23:46:29 +0200 Subject: [PATCH] feat(toolbox): serve Android APK from /wg/toolbox.apk + onboard button (#536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/build-android-apk.yml | 22 +++++++++- packages/secubox-toolbox/debian/changelog | 18 ++++++++ packages/secubox-toolbox/debian/postinst | 8 ++++ packages/secubox-toolbox/debian/rules | 3 ++ .../sbin/secubox-toolbox-fetch-apk | 44 +++++++++++++++++++ .../secubox-toolbox/secubox_toolbox/api.py | 39 ++++++++++++++++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 packages/secubox-toolbox/sbin/secubox-toolbox-fetch-apk diff --git a/.github/workflows/build-android-apk.yml b/.github/workflows/build-android-apk.yml index 59ed6d8f..0d8fe932 100644 --- a/.github/workflows/build-android-apk.yml +++ b/.github/workflows/build-android-apk.yml @@ -16,7 +16,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: write # needed to attach the APK to a release on tags jobs: build: @@ -44,9 +44,27 @@ jobs: - name: Build debug APK run: gradle :app:assembleDebug --no-daemon --stacktrace - - name: Upload APK + - name: Upload APK artifact uses: actions/upload-artifact@v4 with: name: secubox-toolbox-android-debug path: clients/android-toolbox/app/build/outputs/apk/debug/*.apk if-no-files-found: error + + # On android-v* tags, publish the APK as a release asset under the + # stable name the toolbox fetch helper + /wg/toolbox.apk expect + # (#536). `latest/download/secubox-toolbox-android.apk` resolves to + # whichever release is newest. + - name: Stage release asset + if: startsWith(github.ref, 'refs/tags/android-v') + run: | + mkdir -p "$GITHUB_WORKSPACE/release" + cp app/build/outputs/apk/debug/app-debug.apk \ + "$GITHUB_WORKSPACE/release/secubox-toolbox-android.apk" + + - name: Publish release + if: startsWith(github.ref, 'refs/tags/android-v') + uses: softprops/action-gh-release@v2 + with: + files: release/secubox-toolbox-android.apk + fail_on_unmatched_files: true diff --git a/packages/secubox-toolbox/debian/changelog b/packages/secubox-toolbox/debian/changelog index 3d915179..083359d6 100644 --- a/packages/secubox-toolbox/debian/changelog +++ b/packages/secubox-toolbox/debian/changelog @@ -1,3 +1,21 @@ +secubox-toolbox (2.6.13-1~bookworm1) bookworm; urgency=medium + + * Serve the Android ToolBox APK from the toolbox (#536, follow-up #531). + - api.py GET /wg/toolbox.apk : serves the local APK + (/var/lib/secubox/toolbox/android/village3b-toolbox.apk) with + content-type application/vnd.android.package-archive ; if absent, + 302 → the latest public GitHub release asset (button never + dead-ends, offline-capable once fetched). + - /wg/onboard Android panel (both the inline + the _install_panels + variants) : new "📱 Installer l'app ToolBoX (1-tap)" button. + - sbin/secubox-toolbox-fetch-apk : pulls the latest release asset + into the serve path (best-effort, APK-magic sanity check). + - postinst : create the android serve dir + best-effort first fetch. + - build-android-apk.yml : on android-v* tags, publish the APK as a + release asset named secubox-toolbox-android.apk (contents:write). + + -- Gerald KERMA Thu, 12 Jun 2026 14:30:00 +0200 + secubox-toolbox (2.6.12-1~bookworm1) bookworm; urgency=medium * fix(threat_intel) #530 — ThreatFox ingested 0 IOCs for weeks because diff --git a/packages/secubox-toolbox/debian/postinst b/packages/secubox-toolbox/debian/postinst index 015bfb8e..6b789a79 100755 --- a/packages/secubox-toolbox/debian/postinst +++ b/packages/secubox-toolbox/debian/postinst @@ -44,6 +44,14 @@ case "$1" in # 4. Storage dir (SQLite + future PDF reports) install -d -m 0750 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox + # #536 : Android APK serve dir + best-effort fetch of the latest + # release asset (so GET /wg/toolbox.apk serves it locally/offline). + # Non-blocking : if there's no release yet / no network, the endpoint + # falls back to redirecting to the public release. + install -d -m 0755 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox/android + if [ -x /usr/sbin/secubox-toolbox-fetch-apk ]; then + /usr/sbin/secubox-toolbox-fetch-apk 2>&1 | head -2 || true + fi # /var/log/secubox is a SHARED parent traversed by many service users # (the aggregator runs as `secubox` and reads waf-threats.log under # here). It MUST be 0755 — a 0750 owned by secubox-toolbox silently diff --git a/packages/secubox-toolbox/debian/rules b/packages/secubox-toolbox/debian/rules index cf8b7dd2..d0b92a7a 100755 --- a/packages/secubox-toolbox/debian/rules +++ b/packages/secubox-toolbox/debian/rules @@ -102,6 +102,9 @@ execute_after_dh_auto_install: debian/secubox-toolbox/lib/systemd/system/ install -m 0644 systemd/secubox-escalate.timer \ debian/secubox-toolbox/lib/systemd/system/ + # #536 : Android APK fetch helper. + install -m 0755 sbin/secubox-toolbox-fetch-apk \ + debian/secubox-toolbox/usr/sbin/ install -m 0755 sbin/secubox-toolbox-wg-restore \ debian/secubox-toolbox/usr/sbin/ install -m 0644 systemd/secubox-toolbox-wg-restore.service \ diff --git a/packages/secubox-toolbox/sbin/secubox-toolbox-fetch-apk b/packages/secubox-toolbox/sbin/secubox-toolbox-fetch-apk new file mode 100644 index 00000000..52e7f17a --- /dev/null +++ b/packages/secubox-toolbox/sbin/secubox-toolbox-fetch-apk @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# +# SecuBox-Deb :: secubox-toolbox-fetch-apk (#536) +# +# Pull the latest Android ToolBox APK (published as a GitHub release +# asset by build-android-apk.yml on android-v* tags) into the toolbox +# serve path, so GET /wg/toolbox.apk serves it locally (offline-capable +# sideload from the cabine). Best-effort : a failure leaves any existing +# APK in place ; the endpoint falls back to the public release redirect. +set -euo pipefail +readonly MODULE="secubox-toolbox-fetch-apk" + +DEST_DIR="/var/lib/secubox/toolbox/android" +DEST="${DEST_DIR}/village3b-toolbox.apk" +RELEASE_URL="https://github.com/CyberMind-FR/secubox-deb/releases/latest/download/secubox-toolbox-android.apk" + +log() { logger -t "$MODULE" -- "$*" 2>/dev/null || echo "[$MODULE] $*" >&2; } + +install -d -m 0755 -o secubox-toolbox -g secubox-toolbox "$DEST_DIR" 2>/dev/null \ + || mkdir -p "$DEST_DIR" + +TMP=$(mktemp --suffix=.apk) +trap 'rm -f "$TMP"' EXIT + +if command -v wget >/dev/null 2>&1; then + if wget -q --timeout=20 --tries=2 "$RELEASE_URL" -O "$TMP" && [ -s "$TMP" ]; then + # Sanity : APK is a ZIP — must start with PK\x03\x04. + if head -c 2 "$TMP" | grep -q "PK"; then + install -m 0644 "$TMP" "$DEST" + chown secubox-toolbox:secubox-toolbox "$DEST" 2>/dev/null || true + log "fetched APK -> ${DEST} ($(stat -c%s "$DEST" 2>/dev/null) bytes)" + exit 0 + else + log "downloaded file is not an APK (no release asset yet?) — keeping existing" + fi + else + log "fetch failed (no release yet / network) — /wg/toolbox.apk will redirect to the release" + fi +else + log "wget missing — cannot fetch APK" +fi +exit 0 diff --git a/packages/secubox-toolbox/secubox_toolbox/api.py b/packages/secubox-toolbox/secubox_toolbox/api.py index b5f62a72..4149d8f4 100644 --- a/packages/secubox-toolbox/secubox_toolbox/api.py +++ b/packages/secubox-toolbox/secubox_toolbox/api.py @@ -621,6 +621,11 @@ pre{background:#1a1a25;color:var(--phos-hot);padding:0.6rem 0.8rem;border-radius
+📱 Installer l'app ToolBoX (1-tap) +
+✨ Le plus simple : l'app fait tout (CA + tunnel + vérif) en 5 étapes. +Active « sources inconnues » à l'installation. Sinon, méthode manuelle ci-dessous : +
📥 Télécharger CA (.crt format Android)
⚠ Chrome ne peut PAS installer un CA directement (sécurité Android 11+). @@ -1185,6 +1190,9 @@ _ONBOARD_BODY = {

Si rien ne se passe : Réglages → Batterie → désactive le mode économie (il coupe parfois les VPN).

""", "android": """ +

✨ Le plus simple — l'app ToolBoX fait tout :

+📱 Installer l'app ToolBoX (.apk, 1-tap) +

Active « sources inconnues » à l'installation. L'app installe le CA, importe le tunnel et vérifie le R3 en 5 étapes. Sinon, méthode manuelle :

  1. Installe l'app WireGuard depuis le Play Store.
  2. Dans l'app, tap "+" → "Scan from QR code" → scanne ton QR :
    QR code
  3. @@ -1326,6 +1334,37 @@ async def wg_ca_der() -> Response: ) +# Android ToolBox app (#531/#536). CI publishes the APK as a GitHub +# release asset on `android-v*` tags ; secubox-toolbox-fetch-apk pulls it +# into the serve path below. If absent, we redirect to the public +# release so the button always works. +_ANDROID_APK = Path("/var/lib/secubox/toolbox/android/village3b-toolbox.apk") +_ANDROID_APK_RELEASE = ( + "https://github.com/CyberMind-FR/secubox-deb/releases/latest/download/" + "secubox-toolbox-android.apk" +) + + +@router.get("/wg/toolbox.apk") +async def wg_toolbox_apk() -> Response: + """Serve the Android ToolBox installer APK (#536). + + Local file first (sideload from the cabine, works offline) ; if it + hasn't been fetched yet, 302 to the latest public GitHub release + asset so the onboard button never dead-ends. + """ + if _ANDROID_APK.exists() and _ANDROID_APK.stat().st_size > 0: + return Response( + content=_ANDROID_APK.read_bytes(), + media_type="application/vnd.android.package-archive", + headers={ + "Content-Disposition": "attachment; filename=village3b-toolbox.apk", + "Cache-Control": "public, max-age=300", + }, + ) + return RedirectResponse(url=_ANDROID_APK_RELEASE, status_code=302) + + @router.get("/wg/ca.mobileconfig") async def wg_ca_mobileconfig() -> Response: """iOS profile that installs the mitm-wg CA in trust store."""