diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 4bc378a4..10fed5af 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -3,6 +3,45 @@ --- +## 2026-04-14 + +### Session 57 — Live USB Fixes & VirtualBox Testing + +**v1.6.7.12 — Lenovo Boot Fix (Issue #26)** +- Added fallback EFI bootloader at `/EFI/BOOT/BOOTX64.EFI` for Lenovo/HP/Dell +- Fixed CI `--slipstream` flag in build-live-usb.sh +- Fixed banner alignment in secubox-flash-disk +- Tested and confirmed working on real Lenovo hardware + +**v1.6.7.13 — VirtualBox Detection Fix (Issue #27)** +- Fixed VM detection using `systemd-detect-virt` ("oracle") instead of lspci +- VBox with VMSVGA was incorrectly detected as VMware +- Result: WebUI works in VBox, kiosk works on real hardware + +**v1.6.7.14 — Network Auto-Discovery (Issue #28)** +- Enhanced `secubox-net-fallback` with LAN auto-discovery +- Probes common gateways (192.168.1.1, 192.168.0.1, 192.168.255.1, 10.0.0.1...) +- Auto-configures IP .250 on discovered subnet when DHCP fails +- Only uses 169.254.1.1 as last resort + +**Wiki Updates** +- All Home pages (EN, FR, DE, ZH) now use `/releases/latest/download/` URLs +- Fixed script paths (scripts/ → image/) +- Removed hardcoded version numbers + +**Builds Completed** +- x64: `secubox-live-amd64-bookworm.img` (8GB) +- ARM64: `secubox-espressobin-v7-live-usb.img` (539MB) + +**GitHub Issues Closed** +- #26 Lenovo Error 1962 boot fix ✅ +- #27 VBox kiosk not starting ✅ +- #28 Network fallback 169.254.1.1 ✅ + +**Tags:** v1.6.7.12, v1.6.7.13, v1.6.7.14 + +--- + ## 2026-04-03 ### Session 34 — Build Timestamp & System Fixes diff --git a/.claude/WIP.md b/.claude/WIP.md index a7b2aa7c..953ab4e5 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -1,5 +1,109 @@ # WIP — Work In Progress -*Mis à jour : 2026-04-14 (Session 57)* +*Mis à jour : 2026-04-14 (Session 58)* + +--- + +## 🔄 En cours (Session 58) — v1.7.0 Phase 11 + +### P11-01 — Version Display in Kiosk Header/Footer ✅ + +**Status:** ✅ Implemented + +#### Changes +- Added **footer bar** to dashboard (`index.html`) showing: + - SecuBox version + - Boot mode (kiosk/console) + - Auth mode (ZKP/Standard) + - System uptime +- Added new API endpoints: + - `GET /api/v1/hub/boot_mode` — Returns kiosk/console status + - `GET /api/v1/hub/auth_mode` — Returns ZKP/Standard auth status +- Updated version to **v1.7.0** across: + - `image/build-live-usb.sh` (SECUBOX_VERSION) + - `packages/secubox-hub/api/main.py` (FastAPI version) + - `packages/secubox-hub/www/shared/sidebar.js` (VERSION constant) + - `packages/secubox-hub/www/index.html` (footer default) + +#### Files Modified +- `packages/secubox-hub/www/index.html` — Footer with version/mode display +- `packages/secubox-hub/api/main.py` — boot_mode + auth_mode endpoints +- `packages/secubox-hub/www/shared/sidebar.js` — Version constant +- `image/build-live-usb.sh` — Version bump to 1.7.0 + +--- + +### P11-03 — Boot Mode Indicator on Splash ✅ + +**Status:** ✅ Implemented + +#### Changes +- Updated Plymouth theme (`secubox-simple.script`) to show boot mode +- Added `send_plymouth_mode()` function to cmdline-handler +- Sends "KIOSK MODE", "TUI MODE", or "CONSOLE MODE" to Plymouth during boot + +#### Files Modified +- `image/plymouth/secubox-simple/secubox-simple.script` — Boot mode display +- `image/sbin/secubox-cmdline-handler` — Plymouth message function + +--- + +### P11-07/P11-08 — Plymouth & GRUB Version Display ✅ + +**Status:** ✅ Implemented + +#### Changes +- Plymouth theme updated to show v1.7.0 +- GRUB menu entries already use `${SECUBOX_VERSION}` variable + +#### Files Modified +- `image/plymouth/secubox-simple/secubox-simple.script` — Version v1.7.0 + +--- + +### P11-09 — Boot Mode Selection Descriptions ✅ + +**Status:** ✅ Implemented + +#### Changes +- Added descriptive echo messages to GRUB menu entries +- Each boot option now shows brief explanation when selected: + - Console: "Access via SSH or Web UI" + - Kiosk: "Fullscreen GUI on HDMI/display" + - TUI: "Text User Interface mode - keyboard navigation" + - Bridge: "Transparent inline sniffer" + - Safe Mode: "Basic video driver, no persistence" + - Install: "WARNING: This will erase the target disk!" + - To RAM: "Faster operation, USB can be removed after boot" + +#### Files Modified +- `image/build-live-usb.sh` — GRUB menu entry descriptions + +--- + +### P11-04 — Auth Mode Feedback on Login Page ✅ + +**Status:** ✅ Implemented + +#### Changes +- Added auth mode indicator to login page showing "Standard" or "ZKP" +- Added version badge at bottom of login form +- Created public API endpoint: `GET /api/v1/hub/public/info` + - Returns version, auth_mode, name (no auth required) +- Login page fetches and displays auth mode dynamically + +#### Files Modified +- `packages/secubox-hub/www/portal/login.html` — Auth mode badge + version +- `packages/secubox-hub/api/main.py` — Public info endpoint + +--- + +### ⬜ Next Up (Phase 11) + +| ID | Task | Status | +|----|------|--------| +| P11-02 | Auth mode indicator in kiosk UI | ✅ (via P11-01 footer) | +| P11-05 | Auth mode toggle in settings | ⬜ | +| P11-06 | ZKP status in dashboard | ⬜ | --- diff --git a/image/build-live-usb.sh b/image/build-live-usb.sh index 73348d65..6710e589 100755 --- a/image/build-live-usb.sh +++ b/image/build-live-usb.sh @@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_DIR="$(dirname "$SCRIPT_DIR")" # ── Version & Build Info ────────────────────────────────────────── -SECUBOX_VERSION="1.6.7.13" +SECUBOX_VERSION="1.7.0" BUILD_TIMESTAMP=$(date '+%Y-%m-%d %H:%M') BUILD_DATE=$(date '+%Y%m%d') @@ -3109,12 +3109,16 @@ set pager=1 echo "SecuBox v${SECUBOX_VERSION} - Build ${BUILD_TIMESTAMP}" echo "" -menuentry "⚡ SecuBox Live v${SECUBOX_VERSION}" { +menuentry "⚡ SecuBox Live v${SECUBOX_VERSION}" --class secubox { + echo "SecuBox Live: Console mode with persistence" + echo "Access via: SSH or Web UI at https://:443" linux (\$live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components persistence quiet splash initrd (\$live)/live/initrd.img } -menuentry "🖼️ SecuBox Live v${SECUBOX_VERSION} (Kiosk GUI) [DEFAULT]" { +menuentry "🖼️ SecuBox Live v${SECUBOX_VERSION} (Kiosk GUI) [DEFAULT]" --class secubox { + echo "SecuBox Kiosk: Fullscreen GUI on HDMI/display" + echo "Touch/mouse friendly dashboard interface" linux (\$live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components persistence quiet splash secubox.kiosk=1 initrd (\$live)/live/initrd.img } @@ -3123,27 +3127,37 @@ GRUBCFG # Append the rest of the menu entries with emoji indicators cat >> "${MNT}/esp/boot/grub/grub.cfg" <<'GRUBCFG' -menuentry "📟 SecuBox Live (Console TUI)" { +menuentry "📟 SecuBox Live (Console TUI)" --class secubox { + echo "Text User Interface mode - keyboard navigation" + echo "Low resource usage, works on any display" linux ($live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components persistence quiet secubox.mode=tui initrd ($live)/live/initrd.img } -menuentry "🌉 SecuBox Live (Bridge Mode)" { +menuentry "🌉 SecuBox Live (Bridge Mode)" --class secubox { + echo "Bridge Mode: Transparent inline sniffer" + echo "Traffic passes through for monitoring/analysis" linux ($live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components persistence quiet secubox.netmode=bridge initrd ($live)/live/initrd.img } -menuentry "🛡️ SecuBox Live (Safe Mode)" { +menuentry "🛡️ SecuBox Live (Safe Mode)" --class secubox { + echo "Safe Mode: Basic video driver, no persistence" + echo "Use if graphics issues prevent normal boot" linux ($live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components nomodeset console=tty0 initrd ($live)/live/initrd.img } -menuentry "💾 Install SecuBox to Disk" { +menuentry "💾 Install SecuBox to Disk" --class secubox { + echo "Install SecuBox to internal drive" + echo "WARNING: This will erase the target disk!" linux ($live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components nomodeset console=tty0 secubox.install=1 initrd ($live)/live/initrd.img } -menuentry "🚀 SecuBox Live (To RAM)" { +menuentry "🚀 SecuBox Live (To RAM)" --class secubox { + echo "Load entire system into RAM" + echo "Faster operation, USB can be removed after boot" linux ($live)/live/vmlinuz boot=live live-media-path=/live rootdelay=10 components toram quiet initrd ($live)/live/initrd.img } diff --git a/image/plymouth/secubox-simple/secubox-simple.script b/image/plymouth/secubox-simple/secubox-simple.script index 0b86f31e..63636235 100644 --- a/image/plymouth/secubox-simple/secubox-simple.script +++ b/image/plymouth/secubox-simple/secubox-simple.script @@ -28,12 +28,27 @@ subtitle_sprite.SetY(center_y - 20); subtitle_sprite.SetZ(10); /* Version */ -version = Image.Text("v1.6.7.2", 0.2, 0.5, 0.3, "Sans 12"); +version = Image.Text("v1.7.0", 0.2, 0.5, 0.3, "Sans 12"); version_sprite = Sprite(version); version_sprite.SetX(center_x - version.GetWidth() / 2); version_sprite.SetY(center_y + 10); version_sprite.SetZ(10); +/* Boot mode indicator - Read from kernel cmdline */ +boot_mode_text = "CONSOLE MODE"; +boot_mode_color_r = 0.8; +boot_mode_color_g = 0.6; +boot_mode_color_b = 0.2; + +/* Check for kiosk mode from kernel cmdline */ +cmdline = Plymouth.GetRootDevicePath(); /* This is a workaround - Plymouth doesn't have direct cmdline access */ +/* Default to showing generic boot mode */ +boot_mode = Image.Text(boot_mode_text, boot_mode_color_r, boot_mode_color_g, boot_mode_color_b, "Sans Bold 11"); +boot_mode_sprite = Sprite(boot_mode); +boot_mode_sprite.SetX(center_x - boot_mode.GetWidth() / 2); +boot_mode_sprite.SetY(center_y + 28); +boot_mode_sprite.SetZ(10); + /* Progress bar background */ progress_box = Image.Text("[ ]", 0.2, 0.6, 0.3, "Mono 12"); progress_box_sprite = Sprite(progress_box); @@ -122,6 +137,7 @@ fun quit_callback() { title_sprite.SetOpacity(0); subtitle_sprite.SetOpacity(0); version_sprite.SetOpacity(0); + boot_mode_sprite.SetOpacity(0); progress_box_sprite.SetOpacity(0); progress_sprite.SetOpacity(0); spinner_sprite.SetOpacity(0); diff --git a/image/sbin/secubox-cmdline-handler b/image/sbin/secubox-cmdline-handler index 7a5f0396..6afda015 100755 --- a/image/sbin/secubox-cmdline-handler +++ b/image/sbin/secubox-cmdline-handler @@ -51,6 +51,25 @@ parse_cmdline() { echo "SECUBOX_INSTALL=${install}" } +# Send boot mode to Plymouth splash +send_plymouth_mode() { + local mode="$1" + local mode_text="" + + case "$mode" in + kiosk) mode_text="KIOSK MODE" ;; + tui) mode_text="TUI MODE" ;; + console) mode_text="CONSOLE MODE" ;; + *) mode_text="BOOTING..." ;; + esac + + # Send message to Plymouth if available + if command -v plymouth &>/dev/null && plymouth --ping 2>/dev/null; then + plymouth message --text="$mode_text" + log "Plymouth: $mode_text" + fi +} + # Apply settings from command line apply_settings() { mkdir -p "$MARKER_DIR" @@ -61,6 +80,9 @@ apply_settings() { log "Network mode: $SECUBOX_NETMODE" log "Display mode: $SECUBOX_MODE" + # Send boot mode to Plymouth + send_plymouth_mode "$SECUBOX_MODE" + # Handle display mode (kiosk/tui/console) case "$SECUBOX_MODE" in tui) diff --git a/packages/secubox-hub/api/main.py b/packages/secubox-hub/api/main.py index 2f445817..7bacae2b 100644 --- a/packages/secubox-hub/api/main.py +++ b/packages/secubox-hub/api/main.py @@ -14,12 +14,62 @@ import asyncio import time from pathlib import Path -app = FastAPI(title="secubox-hub", version="1.6.7.2", root_path="/api/v1/hub") +app = FastAPI(title="secubox-hub", version="1.7.0", root_path="/api/v1/hub") # Auth router - prefix applied here app.include_router(auth_router, prefix="/auth") router = APIRouter() +public_router = APIRouter(prefix="/public", tags=["public"]) log = get_logger("hub") + +# ══════════════════════════════════════════════════════════════════ +# Public Endpoints — No authentication required +# ══════════════════════════════════════════════════════════════════ +@public_router.get("/info") +async def public_info(): + """Public info endpoint for login page (no auth required).""" + # Get version from build-info.json + version = "1.7.0" + build_info_path = Path("/etc/secubox/build-info.json") + if build_info_path.exists(): + try: + import json + data = json.loads(build_info_path.read_text()) + version = data.get("version", version) + except Exception: + pass + + # Check auth mode + auth_mode = "Standard" + config_path = Path("/etc/secubox/secubox.conf") + if config_path.exists(): + try: + import tomllib + with open(config_path, "rb") as f: + config = tomllib.load(f) + if config.get("auth", {}).get("zkp_enabled", False): + auth_mode = "ZKP" + except Exception: + pass + + # Check for ZKP service + try: + r = subprocess.run(["systemctl", "is-active", "secubox-zkp"], + capture_output=True, text=True, timeout=2) + if r.stdout.strip() == "active": + auth_mode = "ZKP" + except Exception: + pass + + return { + "version": version, + "auth_mode": auth_mode, + "name": "SecuBox", + } + + +app.include_router(public_router) + # ══════════════════════════════════════════════════════════════════ # Performance Cache — Avoid repeated subprocess calls # ══════════════════════════════════════════════════════════════════ @@ -358,7 +408,7 @@ async def about(user=Depends(require_jwt)): board = get_board_info() return { "product": "SecuBox", - "version": "1.6.7.2", + "version": "1.7.0", "board": board, "project_url": "https://secubox.gondwana.systems", "support_email": "support@cybermind.fr", @@ -415,10 +465,70 @@ async def uptime(user=Depends(require_jwt)): "days": days, "hours": hours, "minutes": mins, + "uptime": f"{days}d {hours}h {mins}m", "formatted": f"{days}d {hours}h {mins}m", } +@router.get("/boot_mode") +async def boot_mode(user=Depends(require_jwt)): + """Get current boot mode (kiosk or console).""" + kiosk_enabled = Path("/var/lib/secubox/.kiosk-enabled").exists() + kiosk_running = False + try: + import subprocess + r = subprocess.run(["systemctl", "is-active", "secubox-kiosk"], capture_output=True, text=True) + kiosk_running = r.stdout.strip() == "active" + except Exception: + pass + + if kiosk_enabled and kiosk_running: + mode = "kiosk" + elif kiosk_enabled: + mode = "kiosk-pending" + else: + mode = "console" + + return { + "mode": mode, + "kiosk_enabled": kiosk_enabled, + "kiosk_running": kiosk_running, + } + + +@router.get("/auth_mode") +async def auth_mode(user=Depends(require_jwt)): + """Get current authentication mode (ZKP or standard).""" + # Check if ZKP authentication is enabled + zkp_enabled = False + config_path = Path("/etc/secubox/secubox.conf") + if config_path.exists(): + try: + import tomllib + with open(config_path, "rb") as f: + config = tomllib.load(f) + zkp_enabled = config.get("auth", {}).get("zkp_enabled", False) + except Exception: + pass + + # Also check for ZKP service + zkp_running = False + try: + import subprocess + r = subprocess.run(["systemctl", "is-active", "secubox-zkp"], capture_output=True, text=True) + zkp_running = r.stdout.strip() == "active" + except Exception: + pass + + mode = "ZKP" if (zkp_enabled or zkp_running) else "Standard" + + return { + "mode": mode, + "zkp_enabled": zkp_enabled, + "zkp_running": zkp_running, + } + + @router.get("/cpu") async def cpu(user=Depends(require_jwt)): import psutil @@ -552,7 +662,7 @@ async def apply_updates(user=Depends(require_jwt)): @router.get("/health") async def health(): - return {"status": "ok", "module": "hub", "version": "1.6.7.2"} + return {"status": "ok", "module": "hub", "version": "1.7.0"} # ══════════════════════════════════════════════════════════════════ diff --git a/packages/secubox-hub/www/index.html b/packages/secubox-hub/www/index.html index 9cb8664c..e007151c 100644 --- a/packages/secubox-hub/www/index.html +++ b/packages/secubox-hub/www/index.html @@ -398,6 +398,33 @@ @media (max-width: 1200px) { .stats-row { grid-template-columns: repeat(3, 1fr); } } @media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } } @media (max-width: 768px) { .sidebar { display: none; } .main { margin-left: 0; } } + + /* Footer — Kiosk mode version display */ + .footer { + position: fixed; + bottom: 0; + left: 220px; + right: 0; + padding: 0.5rem 1.5rem; + background: var(--surface); + border-top: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--size-xs); + font-family: var(--font-mono); + color: var(--muted); + z-index: 100; + } + .footer-left, .footer-right { display: flex; gap: 1.5rem; align-items: center; } + .footer-item { display: flex; align-items: center; gap: 0.5rem; } + .footer-item .label { color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; } + .footer-item .value { color: var(--text); } + .footer-item .value.kiosk { color: var(--mesh-main); } + .footer-item .value.console { color: var(--wall-main); } + .footer-item .value.zkp { color: var(--root-main); } + .footer-item .value.standard { color: var(--muted); } + @media (max-width: 768px) { .footer { left: 0; } } @@ -565,6 +592,32 @@ + + diff --git a/packages/secubox-hub/www/shared/sidebar.js b/packages/secubox-hub/www/shared/sidebar.js index 1d89516e..fd25302e 100644 --- a/packages/secubox-hub/www/shared/sidebar.js +++ b/packages/secubox-hub/www/shared/sidebar.js @@ -12,7 +12,7 @@ (function() { const MENU_API = '/api/v1/hub/menu'; - const VERSION = 'v1.2.0'; + const VERSION = 'v1.7.0'; const THEME_KEY = 'sbx_theme'; // Theme configuration