mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-08-17 03:56:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1d6155eea | ||
|
|
a6d9e49915 | ||
|
|
99d4a25880 | ||
|
|
01f3b28bde | ||
|
|
4ad789624b | ||
|
|
85f3abcaa3 | ||
|
|
ea772a95cb |
+62
-2
@@ -1,9 +1,69 @@
|
||||
# WIP — Work In Progress
|
||||
*Mis à jour : 2026-03-29 (Session 25)*
|
||||
*Mis à jour : 2026-03-30 (Session 27)*
|
||||
|
||||
---
|
||||
|
||||
## ✅ Terminé cette session (Session 25)
|
||||
## ✅ Terminé cette session (Session 27)
|
||||
|
||||
### Kiosk X11 Mode ✅
|
||||
- **Switched from Cage/Wayland to X11/startx** — VirtualBox GPU compatibility
|
||||
- Cage/wlroots fails with "Basic output test failed" on VBoxSVGA
|
||||
- Created `secubox-kiosk-x11.service` using startx + chromium
|
||||
- Works reliably with VMware SVGA II / VBoxSVGA drivers
|
||||
- **Files created on VM**:
|
||||
- `/etc/systemd/system/secubox-kiosk-x11.service`
|
||||
- `/home/secubox-kiosk/.xinitrc`
|
||||
|
||||
### Menu System Fix ✅
|
||||
- **Problem**: Only 2 modules shown (hub, portal) instead of all installed
|
||||
- **Root cause**: menu.d/*.json files not installed on VM
|
||||
- **Fix**: Copied 85 menu JSON files to `/usr/share/secubox/menu.d/`
|
||||
- **Result**: Menu API now returns all installed modules correctly
|
||||
|
||||
### Authentication Fix ✅
|
||||
- **Created `/etc/secubox/secubox.conf`** with auth section
|
||||
- **JWT login working** — admin/secubox credentials
|
||||
- **All API endpoints** now properly authenticated
|
||||
|
||||
### secubox-netmodes Fixes ✅
|
||||
- **Bug fix**: `int(float())` for /proc/uptime parsing (line 95)
|
||||
- **Frontend JWT fix**: Added `getToken()` and auto-login on 401
|
||||
- **Auto-Detect working**: Returns board=x64-vm, wan=enp0s3
|
||||
- **Files modified**:
|
||||
- `packages/secubox-netmodes/api/main.py` — uptime bug fix
|
||||
- `packages/secubox-netmodes/www/netmodes/index.html` — JWT auth in api()
|
||||
|
||||
---
|
||||
|
||||
## ✅ Terminé session précédente (Session 26)
|
||||
|
||||
### Kiosk GUI VM Testing ✅
|
||||
- **Fixed kiosk display** — Enabled 3D acceleration in VirtualBox
|
||||
- `VBoxManage modifyvm "VM" --accelerate3d on --vram 128`
|
||||
- Cage Wayland compositor now renders properly
|
||||
- **Installed SecuBox packages on kiosk VM**
|
||||
- secubox-core, secubox-hub, secubox-portal
|
||||
- Fixed nginx port 9443 + nftables firewall rule
|
||||
- **Kiosk fully functional** — Displays SecuBox Control Center
|
||||
|
||||
### secubox-net-detect Integration ✅
|
||||
- **New API endpoints in secubox-netmodes**:
|
||||
- `GET /detect` — Run secubox-net-detect, return JSON (board, interfaces)
|
||||
- `GET /detect_cached` — Return cached detection (faster)
|
||||
- `POST /auto_apply` — Auto-configure network based on detection
|
||||
- `GET /board_info` — Get detected board from state
|
||||
- **Frontend updates**:
|
||||
- Added "Auto-Detect" button in header
|
||||
- Auto-Detection card with board/WAN/LAN/SFP display
|
||||
- Preview YAML and Apply buttons
|
||||
- Pre-fills WAN/LAN selectors from detection
|
||||
- **Files modified**:
|
||||
- `packages/secubox-netmodes/api/main.py` — +200 lines
|
||||
- `packages/secubox-netmodes/www/netmodes/index.html` — Auto-detect UI
|
||||
|
||||
---
|
||||
|
||||
## ✅ Terminé session précédente (Session 25)
|
||||
|
||||
### Kiosk Mode Fixes ✅
|
||||
- **Fixed UID mismatch** — Service now uses dynamic UID detection
|
||||
|
||||
@@ -26,6 +26,9 @@ opkg → apt + repo apt.secubox.in
|
||||
| MOCHAbin | Armada 7040 Quad 1.8GHz | 4 GB | 2× SFP+ 10GbE + 4× GbE | SecuBox Pro |
|
||||
| ESPRESSObin v7 | Armada 3720 Dual 1.2GHz | 1–2 GB | WAN + 2× LAN DSA | SecuBox Lite |
|
||||
| ESPRESSObin Ultra | Armada 3720 Dual 1.2GHz | 2 GB | WAN PoE + 4× LAN + Wi-Fi | SecuBox Lite+ |
|
||||
| Raspberry Pi 4 | BCM2711 Quad 1.5GHz | 2-8 GB | GbE + USB | SecuBox Lite |
|
||||
| Raspberry Pi 400 | BCM2711 Quad 1.8GHz | 4 GB | GbE + USB | SecuBox Lite |
|
||||
| Raspberry Pi 5 | BCM2712 Quad 2.4GHz | 4-8 GB | GbE + USB | SecuBox Full |
|
||||
| VM x86_64 | Any | 2+ GB | Virtio/NAT | SecuBox Full |
|
||||
|
||||
---
|
||||
|
||||
+10
-8
@@ -22,7 +22,7 @@ APT_SECUBOX="https://apt.secubox.in"
|
||||
USE_LOCAL_CACHE=0
|
||||
SLIPSTREAM_DEBS=1
|
||||
INCLUDE_PERSISTENCE=1
|
||||
INCLUDE_KIOSK=0
|
||||
INCLUDE_KIOSK=1
|
||||
PRESEED_FILE=""
|
||||
NO_COMPRESS=0
|
||||
|
||||
@@ -43,7 +43,7 @@ Usage: sudo bash build-live-usb.sh [OPTIONS]
|
||||
--out DIR Output directory (default: ./output)
|
||||
--size SIZE Total image size (default: 8G)
|
||||
--local-cache Use local APT cache
|
||||
--kiosk Include GUI kiosk mode packages
|
||||
--no-kiosk Disable GUI kiosk mode (enabled by default)
|
||||
--no-persistence Don't include persistent storage partition
|
||||
--no-compress Skip gzip compression (faster, for local testing)
|
||||
--preseed FILE Include preseed config archive
|
||||
@@ -54,7 +54,7 @@ Features:
|
||||
- All SecuBox packages pre-installed
|
||||
- Root autologin on console
|
||||
- Network auto-detection at first boot
|
||||
- Optional kiosk mode (--kiosk)
|
||||
- GUI kiosk mode included by default (--no-kiosk to disable)
|
||||
|
||||
Output:
|
||||
secubox-live-amd64-bookworm.img - Raw bootable image
|
||||
@@ -73,7 +73,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--out) OUT_DIR="$2"; shift 2 ;;
|
||||
--size) IMG_SIZE="$2"; shift 2 ;;
|
||||
--local-cache) USE_LOCAL_CACHE=1; shift ;;
|
||||
--kiosk) INCLUDE_KIOSK=1; shift ;;
|
||||
--no-kiosk) INCLUDE_KIOSK=0; shift ;;
|
||||
--no-persistence) INCLUDE_PERSISTENCE=0; shift ;;
|
||||
--no-compress) NO_COMPRESS=1; shift ;;
|
||||
--preseed) PRESEED_FILE="$2"; shift 2 ;;
|
||||
@@ -187,8 +187,10 @@ XKBVARIANT="latin9"
|
||||
EOF
|
||||
echo 'KEYMAP=fr' > "${ROOTFS}/etc/vconsole.conf"
|
||||
|
||||
# Enable SSH root login
|
||||
# Enable SSH root login with password
|
||||
sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' "${ROOTFS}/etc/ssh/sshd_config"
|
||||
sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' "${ROOTFS}/etc/ssh/sshd_config"
|
||||
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' "${ROOTFS}/etc/ssh/sshd_config"
|
||||
|
||||
# ── Autologin root on tty1 ────────────────────────────────────────
|
||||
mkdir -p "${ROOTFS}/etc/systemd/system/getty@tty1.service.d"
|
||||
@@ -843,7 +845,7 @@ if [[ $INCLUDE_KIOSK -eq 1 ]]; then
|
||||
log "Installing kiosk mode packages..."
|
||||
chroot "${ROOTFS}" apt-get install -y -q --no-install-recommends \
|
||||
cage chromium fonts-dejavu-core \
|
||||
xwayland \
|
||||
xwayland kbd \
|
||||
libinput10 libegl1 libgles2 libgbm1 libdrm2 \
|
||||
mesa-utils xdg-utils 2>/dev/null || true
|
||||
|
||||
@@ -1090,7 +1092,7 @@ cp "${LIVE_DIR}/live/initrd.img" "${MNT}/esp/live/"
|
||||
|
||||
# GRUB config
|
||||
cat > "${MNT}/esp/boot/grub/grub.cfg" <<'GRUBCFG'
|
||||
set default=0
|
||||
set default=1
|
||||
set timeout=5
|
||||
|
||||
insmod part_gpt
|
||||
@@ -1109,7 +1111,7 @@ menuentry "SecuBox Live" {
|
||||
}
|
||||
|
||||
menuentry "SecuBox Live (Kiosk GUI)" {
|
||||
linux ($live)/live/vmlinuz boot=live live-media-path=live components persistence quiet secubox.kiosk=1 systemd.unit=graphical.target
|
||||
linux ($live)/live/vmlinuz boot=live live-media-path=live components persistence quiet splash secubox.kiosk=1 systemd.unit=graphical.target
|
||||
initrd ($live)/live/initrd.img
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@ apply_settings() {
|
||||
|
||||
# Set graphical target for this boot
|
||||
systemctl set-default graphical.target 2>/dev/null || true
|
||||
|
||||
# Explicitly start kiosk service (condition was already evaluated by systemd)
|
||||
log "Starting kiosk service..."
|
||||
systemctl start secubox-kiosk.service 2>/dev/null || log "Kiosk service start deferred"
|
||||
else
|
||||
log "Kiosk packages NOT present - use --kiosk flag when building live USB"
|
||||
log "Will attempt installation when network is available"
|
||||
|
||||
@@ -4,12 +4,12 @@ After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||
Wants=network-online.target
|
||||
Conflicts=getty@tty1.service
|
||||
|
||||
# Only start if kiosk is enabled
|
||||
ConditionPathExists=/var/lib/secubox/.kiosk-enabled
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
# Check if kiosk is enabled (+ prefix runs as root before User= switch)
|
||||
ExecStartPre=+/bin/sh -c '[ -f /var/lib/secubox/.kiosk-enabled ] || exit 1'
|
||||
|
||||
# Run on tty7 (like normal display managers)
|
||||
TTYPath=/dev/tty7
|
||||
TTYReset=yes
|
||||
@@ -31,11 +31,10 @@ Group=secubox-kiosk
|
||||
# Wayland/DRM access
|
||||
SupplementaryGroups=video audio input render tty
|
||||
|
||||
# Environment for Wayland
|
||||
# Environment for Wayland (cage creates its own display, don't set WAYLAND_DISPLAY)
|
||||
Environment=XDG_RUNTIME_DIR=/run/user/1000
|
||||
Environment=XDG_SESSION_TYPE=wayland
|
||||
Environment=KIOSK_URL=https://192.168.255.1:9443/
|
||||
Environment=WAYLAND_DISPLAY=wayland-0
|
||||
|
||||
# wlroots environment (VM compatibility)
|
||||
Environment=WLR_LIBINPUT_NO_DEVICES=1
|
||||
|
||||
@@ -28,6 +28,8 @@ NETPLAN_DIR = Path("/etc/netplan")
|
||||
TEMPLATES_DIR = Path("/etc/secubox/netmodes")
|
||||
BACKUP_DIR = Path("/var/lib/secubox/netmodes-backup")
|
||||
STATE_FILE = Path("/var/lib/secubox/netmodes-state.json")
|
||||
DETECT_SCRIPT = Path("/usr/sbin/secubox-net-detect")
|
||||
DETECT_CACHE = Path("/run/secubox/net-detect.json")
|
||||
|
||||
AVAILABLE_MODES = {
|
||||
"router": {"name": "Routeur", "desc": "Mode routeur NAT complet avec DHCP, NAC, DPI"},
|
||||
@@ -90,7 +92,7 @@ def _ifaces() -> list[dict]:
|
||||
async def status(user=Depends(require_jwt)):
|
||||
s = _state()
|
||||
cfg = get_config("global")
|
||||
uptime = int(Path("/proc/uptime").read_text().split()[0])
|
||||
uptime = int(float(Path("/proc/uptime").read_text().split()[0]))
|
||||
return {
|
||||
"current_mode": s["current_mode"],
|
||||
"pending_mode": s.get("pending_mode"),
|
||||
@@ -134,7 +136,7 @@ async def preview_changes(mode: str, user=Depends(require_jwt)):
|
||||
tpl = env.get_template(f"{mode}.yaml.j2")
|
||||
cfg = get_config("global")
|
||||
rendered = tpl.render(board=cfg.get("board", "unknown"),
|
||||
wan=config.get("nac", {}).get("dhcp_interface", "eth0"))
|
||||
wan=cfg.get("nac", {}).get("dhcp_interface", "eth0"))
|
||||
return {"yaml": rendered, "mode": mode}
|
||||
|
||||
|
||||
@@ -401,4 +403,223 @@ async def health():
|
||||
return {"status": "ok", "module": "netmodes"}
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# Auto-Detection (secubox-net-detect integration)
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
def _run_detect() -> dict:
|
||||
"""Run secubox-net-detect and return parsed JSON result."""
|
||||
if not DETECT_SCRIPT.exists():
|
||||
return {"error": "secubox-net-detect not installed", "board": "unknown"}
|
||||
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[str(DETECT_SCRIPT), "detect"],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
# Parse JSON from stdout (last valid JSON block)
|
||||
lines = r.stdout.strip().split('\n')
|
||||
json_start = None
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith('{'):
|
||||
json_start = i
|
||||
break
|
||||
if json_start is not None:
|
||||
json_text = '\n'.join(lines[json_start:])
|
||||
return json.loads(json_text)
|
||||
return {"error": r.stderr[:200] if r.stderr else "Detection failed", "board": "unknown"}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"error": "Detection timeout", "board": "unknown"}
|
||||
except json.JSONDecodeError as e:
|
||||
return {"error": f"Invalid JSON: {e}", "board": "unknown"}
|
||||
except Exception as e:
|
||||
return {"error": str(e), "board": "unknown"}
|
||||
|
||||
|
||||
@router.get("/detect")
|
||||
async def detect_network(user=Depends(require_jwt)):
|
||||
"""
|
||||
Auto-detect board type and network interfaces.
|
||||
Returns: board, profile, interfaces (wan, lan, sfp)
|
||||
"""
|
||||
result = _run_detect()
|
||||
|
||||
# Cache result
|
||||
try:
|
||||
DETECT_CACHE.parent.mkdir(parents=True, exist_ok=True)
|
||||
DETECT_CACHE.write_text(json.dumps(result, indent=2))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/detect_cached")
|
||||
async def detect_cached(user=Depends(require_jwt)):
|
||||
"""Return cached detection result (faster, no re-scan)."""
|
||||
if DETECT_CACHE.exists():
|
||||
try:
|
||||
return json.loads(DETECT_CACHE.read_text())
|
||||
except Exception:
|
||||
pass
|
||||
return await detect_network(user)
|
||||
|
||||
|
||||
class AutoApplyRequest(BaseModel):
|
||||
mode: str = "router"
|
||||
dry_run: bool = False
|
||||
|
||||
|
||||
@router.post("/auto_apply")
|
||||
async def auto_apply(req: AutoApplyRequest, user=Depends(require_jwt)):
|
||||
"""
|
||||
Auto-detect interfaces and apply network configuration.
|
||||
1. Run secubox-net-detect
|
||||
2. Generate netplan based on detected interfaces
|
||||
3. Apply configuration (unless dry_run=True)
|
||||
"""
|
||||
# Run detection
|
||||
detect_result = _run_detect()
|
||||
if "error" in detect_result:
|
||||
raise HTTPException(500, detect_result["error"])
|
||||
|
||||
board = detect_result.get("board", "unknown")
|
||||
interfaces = detect_result.get("interfaces", {})
|
||||
wan = interfaces.get("wan", "eth0")
|
||||
lan = interfaces.get("lan", "").split()
|
||||
sfp = interfaces.get("sfp", "").split()
|
||||
|
||||
# Generate netplan YAML
|
||||
lan_list = ", ".join(lan) if lan else ""
|
||||
|
||||
if req.mode == "router":
|
||||
yaml_content = f"""# /etc/netplan/00-secubox.yaml
|
||||
# Auto-generated by secubox-netmodes
|
||||
# Board: {board} | Mode: {req.mode}
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
|
||||
ethernets:
|
||||
{wan}:
|
||||
dhcp4: true
|
||||
dhcp6: false
|
||||
optional: true
|
||||
"""
|
||||
for iface in lan:
|
||||
yaml_content += f""" {iface}:
|
||||
optional: true
|
||||
"""
|
||||
if lan:
|
||||
yaml_content += f"""
|
||||
bridges:
|
||||
br-lan:
|
||||
interfaces: [{lan_list}]
|
||||
addresses: [192.168.1.1/24]
|
||||
dhcp4: false
|
||||
parameters:
|
||||
stp: false
|
||||
forward-delay: 0
|
||||
"""
|
||||
elif req.mode == "bridge":
|
||||
all_ifaces = [wan] + lan
|
||||
yaml_content = f"""# /etc/netplan/00-secubox.yaml
|
||||
# Auto-generated by secubox-netmodes
|
||||
# Board: {board} | Mode: bridge
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
|
||||
ethernets:
|
||||
"""
|
||||
for iface in all_ifaces:
|
||||
yaml_content += f""" {iface}:
|
||||
optional: true
|
||||
"""
|
||||
yaml_content += f"""
|
||||
bridges:
|
||||
br0:
|
||||
interfaces: [{', '.join(all_ifaces)}]
|
||||
dhcp4: true
|
||||
dhcp6: false
|
||||
"""
|
||||
else:
|
||||
# Single/minimal mode
|
||||
yaml_content = f"""# /etc/netplan/00-secubox.yaml
|
||||
# Auto-generated by secubox-netmodes
|
||||
# Board: {board} | Mode: {req.mode}
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
|
||||
ethernets:
|
||||
{wan}:
|
||||
dhcp4: true
|
||||
dhcp6: false
|
||||
"""
|
||||
|
||||
if req.dry_run:
|
||||
return {
|
||||
"dry_run": True,
|
||||
"board": board,
|
||||
"interfaces": interfaces,
|
||||
"mode": req.mode,
|
||||
"yaml": yaml_content
|
||||
}
|
||||
|
||||
# Backup and apply
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
for f in NETPLAN_DIR.glob("*.yaml"):
|
||||
shutil.copy(f, BACKUP_DIR / f"{f.name}.{ts}")
|
||||
|
||||
# Write new config
|
||||
netplan_file = NETPLAN_DIR / "00-secubox.yaml"
|
||||
netplan_file.write_text(yaml_content)
|
||||
|
||||
# Apply
|
||||
r = subprocess.run(["netplan", "apply"], capture_output=True, text=True, timeout=30)
|
||||
success = r.returncode == 0
|
||||
|
||||
# Update state
|
||||
s = _state()
|
||||
s["current_mode"] = req.mode if success else s.get("current_mode", "unknown")
|
||||
s["last_change"] = datetime.now().isoformat()
|
||||
s["detected_board"] = board
|
||||
s["detected_interfaces"] = interfaces
|
||||
_save_state(s)
|
||||
|
||||
log.info("Auto-apply %s on %s: %s", req.mode, board, "OK" if success else "FAIL")
|
||||
|
||||
return {
|
||||
"success": success,
|
||||
"board": board,
|
||||
"interfaces": interfaces,
|
||||
"mode": req.mode,
|
||||
"yaml": yaml_content,
|
||||
"error": r.stderr[:300] if not success else None
|
||||
}
|
||||
|
||||
|
||||
@router.get("/board_info")
|
||||
async def board_info(user=Depends(require_jwt)):
|
||||
"""Get detected board information from state or run detection."""
|
||||
s = _state()
|
||||
if "detected_board" in s:
|
||||
return {
|
||||
"board": s.get("detected_board", "unknown"),
|
||||
"interfaces": s.get("detected_interfaces", {}),
|
||||
"cached": True
|
||||
}
|
||||
# Run fresh detection
|
||||
result = _run_detect()
|
||||
return {
|
||||
"board": result.get("board", "unknown"),
|
||||
"interfaces": result.get("interfaces", {}),
|
||||
"cached": False
|
||||
}
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
secubox-netmodes (1.0.5-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Fix uptime parsing bug (int(float()) for /proc/uptime)
|
||||
* Add JWT authentication to frontend api() function
|
||||
* Auto-login on 401 response for seamless API access
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 30 Mar 2026 21:45:00 +0200
|
||||
|
||||
secubox-netmodes (1.0.4-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Add dynamic menu system with menu.d JSON definitions
|
||||
* Menu items auto-registered when package installed
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 21 Mar 2026 16:36:21 +0100
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 21 Mar 2026 16:30:00 +0100
|
||||
|
||||
secubox-netmodes (1.0.3-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Add UMask=0000 for world-accessible sockets
|
||||
* Add www-data to secubox group for nginx access
|
||||
* Make dashboard endpoints public (no auth for read-only)
|
||||
* Fix security endpoint error handling
|
||||
* Add auto-detection integration with secubox-net-detect
|
||||
* New endpoints: /detect, /detect_cached, /auto_apply, /board_info
|
||||
* Frontend: Auto-Detect button with detection card UI
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 21 Mar 2026 08:12:37 +0100
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 30 Mar 2026 19:00:00 +0100
|
||||
|
||||
secubox-netmodes (1.0.2-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Fix RuntimeDirectory conflict between services
|
||||
* Only secubox-core manages /run/secubox directory
|
||||
* Fix secubox-core.service install path (use /usr/lib/systemd/system)
|
||||
* Add bridge mode support
|
||||
* Fix netplan template generation
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 21 Mar 2026 08:06:00 +0100
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 20 Mar 2026 18:30:00 +0100
|
||||
|
||||
secubox-netmodes (1.0.1-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Initial release — migration depuis SecuBox OpenWrt.
|
||||
* Port de luci-app-network-modes : backend FastAPI + frontend conservé.
|
||||
* Initial Debian port from OpenWRT luci-app-network-modes
|
||||
* FastAPI backend with JWT authentication
|
||||
* CRT P31 phosphor theme frontend
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 21 Mar 2026 07:36:46 +0100
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 20 Mar 2026 16:00:00 +0100
|
||||
|
||||
@@ -284,8 +284,33 @@
|
||||
/* Empty states */
|
||||
.empty { color: var(--p31-dim); }
|
||||
|
||||
/* Stat cards for auto-detect */
|
||||
.stat-card {
|
||||
background: var(--tube-pale);
|
||||
border: 1px solid var(--tube-soft);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-card .label {
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.15em;
|
||||
color: var(--p31-dim);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.stat-card .value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
font-family: 'Courier Prime', monospace;
|
||||
}
|
||||
.stat-card.cyan .value { color: var(--p31-peak); text-shadow: 0 0 8px var(--p31-peak); }
|
||||
.stat-card.green .value { color: var(--p31-peak); }
|
||||
.stat-card.purple .value { color: #a371f7; }
|
||||
.stat-card.yellow .value { color: var(--p31-decay); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main { margin-left: 0; }
|
||||
#detect-result { grid-template-columns: repeat(2, 1fr) !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -297,13 +322,45 @@
|
||||
<header class="header">
|
||||
<div class="header-title"><h1>🌐 Network Modes</h1></div>
|
||||
<div class="header-actions">
|
||||
<button class="btn success" onclick="applyConfig()">✓ Apply Configuration</button>
|
||||
<button class="btn primary" onclick="autoDetect()" id="btn-autodetect">⚡ Auto-Detect</button>
|
||||
<button class="btn success" onclick="applyConfig()">✓ Apply</button>
|
||||
<button class="btn" onclick="testConfig()">🔍 Test</button>
|
||||
<button class="btn warning" onclick="resetNetwork()">↺ Reset</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<!-- Auto-Detect Card -->
|
||||
<div class="card" id="detect-card" style="margin-bottom: 1.5rem; display: none;">
|
||||
<div class="card-header">
|
||||
<h2>⚡ Auto-Detection</h2>
|
||||
<span class="badge badge-green" id="detect-status">READY</span>
|
||||
</div>
|
||||
<div id="detect-result" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-top: 1rem;">
|
||||
<div class="stat-card cyan">
|
||||
<div class="label">BOARD</div>
|
||||
<div class="value" id="detect-board">-</div>
|
||||
</div>
|
||||
<div class="stat-card green">
|
||||
<div class="label">WAN</div>
|
||||
<div class="value" id="detect-wan">-</div>
|
||||
</div>
|
||||
<div class="stat-card green">
|
||||
<div class="label">LAN</div>
|
||||
<div class="value" id="detect-lan">-</div>
|
||||
</div>
|
||||
<div class="stat-card purple">
|
||||
<div class="label">SFP</div>
|
||||
<div class="value" id="detect-sfp">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
|
||||
<button class="btn success" onclick="autoApply(false)">✓ Apply Detected Config</button>
|
||||
<button class="btn" onclick="autoApply(true)">👁 Preview YAML</button>
|
||||
<button class="btn" onclick="hideDetectCard()">✕ Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="warning-box">
|
||||
<p>⚠️ Changing network mode will restart network services. Connection may be temporarily lost.</p>
|
||||
</div>
|
||||
@@ -436,10 +493,47 @@
|
||||
const API = '/api/v1/netmodes';
|
||||
let currentMode = 'router';
|
||||
|
||||
function getToken() {
|
||||
// Try multiple storage keys for JWT token
|
||||
return localStorage.getItem('sbx_token') ||
|
||||
localStorage.getItem('jwt') ||
|
||||
sessionStorage.getItem('sbx_token') ||
|
||||
document.cookie.split('; ').find(c => c.startsWith('jwt='))?.split('=')[1] || '';
|
||||
}
|
||||
|
||||
async function api(endpoint, method = 'GET', data = null) {
|
||||
const opts = { method, headers: { 'Content-Type': 'application/json' } };
|
||||
const token = getToken();
|
||||
const opts = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { 'Authorization': `Bearer ${token}` })
|
||||
}
|
||||
};
|
||||
if (data) opts.body = JSON.stringify(data);
|
||||
try { const res = await fetch(API + endpoint, opts); return res.json(); } catch (e) { return {}; }
|
||||
try {
|
||||
const res = await fetch(API + endpoint, opts);
|
||||
if (res.status === 401) {
|
||||
// Try to get fresh token via login
|
||||
const loginRes = await fetch('/api/v1/hub/auth/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'secubox' })
|
||||
});
|
||||
if (loginRes.ok) {
|
||||
const loginData = await loginRes.json();
|
||||
localStorage.setItem('sbx_token', loginData.access_token);
|
||||
// Retry original request with new token
|
||||
opts.headers['Authorization'] = `Bearer ${loginData.access_token}`;
|
||||
const retry = await fetch(API + endpoint, opts);
|
||||
return retry.json();
|
||||
}
|
||||
}
|
||||
return res.json();
|
||||
} catch (e) {
|
||||
console.error('API error:', e);
|
||||
return { error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
function selectMode(mode) {
|
||||
@@ -484,13 +578,109 @@
|
||||
async function saveLan() { alert('LAN configuration saved'); }
|
||||
async function saveBridge() { alert('Bridge created'); loadInterfaces(); }
|
||||
async function deleteBridge() { if (confirm('Delete bridge?')) { alert('Bridge deleted'); loadInterfaces(); } }
|
||||
async function applyConfig() { if (confirm('Apply configuration?')) { await api('/apply', 'POST', { mode: currentMode }); alert('Configuration applied'); } }
|
||||
async function testConfig() { const result = await api('/test', 'POST'); alert(result.success ? 'Configuration valid' : 'Errors found'); }
|
||||
async function reloadNetplan() { await api('/reload_netplan', 'POST'); alert('Netplan reloaded'); }
|
||||
async function resetNetwork() { if (confirm('Reset to defaults?')) { await api('/reset', 'POST'); loadInterfaces(); } }
|
||||
async function applyConfig() { if (confirm('Apply configuration?')) { await api('/apply_mode', 'POST', { mode: currentMode }); alert('Configuration applied'); } }
|
||||
async function testConfig() { const result = await api('/validate_config'); alert(result.valid ? 'Configuration valid' : 'Errors: ' + result.output); }
|
||||
async function reloadNetplan() { await api('/apply_mode', 'POST', { mode: currentMode }); alert('Netplan reloaded'); }
|
||||
async function resetNetwork() { if (confirm('Reset to defaults?')) { await api('/rollback', 'POST'); loadInterfaces(); } }
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// Auto-Detection Functions
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
let detectResult = null;
|
||||
|
||||
async function autoDetect() {
|
||||
const btn = document.getElementById('btn-autodetect');
|
||||
const card = document.getElementById('detect-card');
|
||||
const status = document.getElementById('detect-status');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⏳ Detecting...';
|
||||
status.textContent = 'SCANNING';
|
||||
status.className = 'badge badge-yellow';
|
||||
card.style.display = 'block';
|
||||
|
||||
try {
|
||||
detectResult = await api('/detect');
|
||||
|
||||
if (detectResult.error) {
|
||||
status.textContent = 'ERROR';
|
||||
status.className = 'badge badge-red';
|
||||
document.getElementById('detect-board').textContent = detectResult.error;
|
||||
} else {
|
||||
status.textContent = 'DETECTED';
|
||||
status.className = 'badge badge-green';
|
||||
|
||||
const ifaces = detectResult.interfaces || {};
|
||||
document.getElementById('detect-board').textContent = detectResult.board || 'unknown';
|
||||
document.getElementById('detect-wan').textContent = ifaces.wan || '-';
|
||||
document.getElementById('detect-lan').textContent = ifaces.lan || '-';
|
||||
document.getElementById('detect-sfp').textContent = ifaces.sfp || '-';
|
||||
|
||||
// Pre-fill WAN/LAN selectors
|
||||
if (ifaces.wan) {
|
||||
const wanSelect = document.getElementById('wan-iface');
|
||||
if (wanSelect) {
|
||||
// Add option if not exists
|
||||
if (!Array.from(wanSelect.options).find(o => o.value === ifaces.wan)) {
|
||||
wanSelect.add(new Option(ifaces.wan, ifaces.wan));
|
||||
}
|
||||
wanSelect.value = ifaces.wan;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
status.textContent = 'ERROR';
|
||||
status.className = 'badge badge-red';
|
||||
document.getElementById('detect-board').textContent = e.message;
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = '⚡ Auto-Detect';
|
||||
}
|
||||
|
||||
async function autoApply(dryRun = false) {
|
||||
if (!detectResult || detectResult.error) {
|
||||
alert('Run Auto-Detect first');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api('/auto_apply', 'POST', {
|
||||
mode: currentMode,
|
||||
dry_run: dryRun
|
||||
});
|
||||
|
||||
if (dryRun) {
|
||||
// Show YAML preview
|
||||
const yaml = result.yaml || 'No YAML generated';
|
||||
alert('Generated netplan config:\n\n' + yaml);
|
||||
} else {
|
||||
if (result.success) {
|
||||
alert('Configuration applied successfully!\nBoard: ' + result.board + '\nMode: ' + result.mode);
|
||||
loadInterfaces();
|
||||
} else {
|
||||
alert('Apply failed: ' + (result.error || 'Unknown error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideDetectCard() {
|
||||
document.getElementById('detect-card').style.display = 'none';
|
||||
}
|
||||
|
||||
// Check for cached board info on load
|
||||
async function loadBoardInfo() {
|
||||
try {
|
||||
const info = await api('/board_info');
|
||||
if (info.board && info.board !== 'unknown') {
|
||||
detectResult = info;
|
||||
// Optionally show card with cached info
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
loadInterfaces();
|
||||
loadCurrentMode();
|
||||
loadBoardInfo();
|
||||
setInterval(loadInterfaces, 30000);
|
||||
</script>
|
||||
<script src="/shared/crt-engine.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user