mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 09:14:00 +00:00
feat(kernel): Add complete nftables support + busybox rescue docs
Kernel nftables fix (issue #64): - Added CONFIG_NF_TABLES_INET, CONFIG_NF_TABLES_IPV4/IPV6 - Added NFT_CT, NFT_LOG, NFT_LIMIT, NFT_REJECT, NFT_COUNTER - CrowdSec firewall bouncer now works correctly Build script safety: - Added deploy_to_device() with safe module extraction - Prevents /lib symlink overwrite disaster Documentation: - FAQ-BUSYBOX-RESCUE.md: Emergency recovery when libc inaccessible - PROMPT-BUSYBOX-TECHTIP.md: Gemini prompt for visual tech tip - QUICKSHEET: Added emergency recovery section Sidebar v2.32.0: - Per-LED tooltips (Hardware/Services/Security) - Fixed tooltip positioning for sidebar elements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
81ec2ede51
commit
00766234d2
|
|
@ -4,6 +4,40 @@
|
|||
---
|
||||
## 2026-05-08
|
||||
|
||||
### Session 128 — LED Tooltips + Kernel nftables Fix
|
||||
|
||||
**Sidebar v2.32.0:**
|
||||
- Per-LED tooltips: each LED row (Hardware/Services/Security) now has its own tooltip
|
||||
- Hardware tooltip: CPU/MEM/DISK/LOAD histograms with current/average/max values
|
||||
- Services tooltip: OK/WARN/ERROR/UNKNOWN service counts
|
||||
- Security tooltip: Active bans and recent alerts from CrowdSec
|
||||
- Fixed tooltip positioning for sidebar elements (was offset by navbar width)
|
||||
|
||||
**SOC Page Fix:**
|
||||
- Fixed nginx `/soc/` route that was incorrectly proxying to API instead of serving static files
|
||||
- SOC dashboard HTML now loads correctly
|
||||
|
||||
**Kernel nftables Issue (GitHub #64):**
|
||||
- Discovered kernel 6.6.137 missing critical nftables options:
|
||||
- `CONFIG_NF_TABLES_INET` - inet family support
|
||||
- `CONFIG_NF_TABLES_IPV4` - IPv4 rules
|
||||
- `CONFIG_NFT_CT`, `CONFIG_NFT_LOG`, `CONFIG_NFT_REJECT`, etc.
|
||||
- CrowdSec bouncer in restart loop due to `Operation not supported` errors
|
||||
- Updated `board/mochabin/kernel/config-6.12-openwrt-merged.fragment` with complete nftables config
|
||||
- Created issue #64 with bouncer health alert requirements (CSPN critical)
|
||||
|
||||
**Files Updated:**
|
||||
- `www/shared/sidebar.js` — v2.32.0 with per-LED tooltips
|
||||
- `www/shared/hybrid-skin.css` — Service/Security tooltip CSS styles
|
||||
- `board/mochabin/kernel/config-6.12-openwrt-merged.fragment` — Full nftables support
|
||||
|
||||
---
|
||||
|
||||
### Session 127 — Smart Strip + LED Pulsing + Round UI Virtual
|
||||
*(earlier today - see v2.25.0 to v2.31.0 changes)*
|
||||
|
||||
---
|
||||
|
||||
### Session 126 — Hybrid Skin License & Centralized Injection
|
||||
|
||||
**License Headers Added:**
|
||||
|
|
|
|||
|
|
@ -117,6 +117,72 @@ Each module card contains:
|
|||
|
||||
---
|
||||
|
||||
## 🚨 Emergency Recovery — Busybox Anti-Crash
|
||||
|
||||
### Problem: `/lib` symlink broken (libc inaccessible)
|
||||
|
||||
On modern Debian, `/lib` is a symlink to `/usr/lib`. If a tarball extraction replaces this symlink with a directory, ALL external commands fail:
|
||||
|
||||
```
|
||||
-bash: /usr/bin/ls: cannot execute: required file not found
|
||||
```
|
||||
|
||||
### Solution: Busybox (statically linked)
|
||||
|
||||
Busybox is often compiled statically and doesn't need libc:
|
||||
|
||||
```bash
|
||||
# Check if busybox exists
|
||||
echo /bin/busybox
|
||||
echo /usr/bin/busybox
|
||||
|
||||
# If found, use it to repair:
|
||||
/bin/busybox rm -rf /lib
|
||||
/bin/busybox ln -s usr/lib /lib
|
||||
|
||||
# Verify
|
||||
/bin/busybox ls -la /lib
|
||||
```
|
||||
|
||||
### Prevention: Safe module tarball extraction
|
||||
|
||||
```bash
|
||||
# WRONG - can overwrite /lib symlink
|
||||
cd / && tar xzf modules.tar.gz
|
||||
|
||||
# RIGHT - extract to specific path
|
||||
tar xzf modules.tar.gz -C /lib/modules/ --strip-components=2
|
||||
```
|
||||
|
||||
### Shell built-ins when nothing works
|
||||
|
||||
If no busybox, use bash built-ins to diagnose:
|
||||
|
||||
```bash
|
||||
# List files (glob expansion)
|
||||
echo /lib/*
|
||||
echo /usr/lib/aarch64-linux-gnu/libc*
|
||||
|
||||
# Read file content
|
||||
while IFS= read -r line; do echo "$line"; done < /etc/os-release
|
||||
|
||||
# Check if path exists
|
||||
[[ -e /lib/aarch64-linux-gnu ]] && echo "exists" || echo "missing"
|
||||
```
|
||||
|
||||
### Last resort: Rescue boot
|
||||
|
||||
Boot from USB/SD rescue media:
|
||||
```bash
|
||||
mount /dev/mmcblk0p2 /mnt
|
||||
rm -rf /mnt/lib
|
||||
ln -s usr/lib /mnt/lib
|
||||
umount /mnt
|
||||
reboot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Brand Footer
|
||||
|
||||
*CyberMind · Gondwana · Notre-Dame-du-Cruet · Savoie*
|
||||
|
|
@ -124,3 +190,4 @@ Each module card contains:
|
|||
---
|
||||
|
||||
*Reference extracted: 2026-04-08*
|
||||
*Emergency recovery added: 2026-05-08*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,33 @@
|
|||
# WIP — Work In Progress
|
||||
*Mis à jour : 2026-05-08 (Session 127)*
|
||||
*Mis à jour : 2026-05-08 (Session 128)*
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Session 128: LED Tooltips + Kernel NFTables Fix
|
||||
|
||||
### Sidebar v2.32.0 — IN PROGRESS
|
||||
- [x] Per-LED tooltips (separate for Hardware/Services/Security)
|
||||
- [x] Fixed tooltip positioning for sidebar elements
|
||||
- [x] Service layer tooltip with OK/WARN/ERROR/UNKNOWN counts
|
||||
- [x] Security layer tooltip with bans/alerts stats
|
||||
- [x] Hardware layer with CPU/MEM/DISK/LOAD histograms
|
||||
|
||||
### SOC Page Fix — COMPLETE
|
||||
- [x] Fixed nginx route for /soc/ (was proxying to API, now serves static files)
|
||||
- [x] SOC page HTML now loads correctly
|
||||
|
||||
### Kernel nftables Issue #64 — CRITICAL
|
||||
- [x] Identified missing kernel options: `CONFIG_NF_TABLES_INET`, `CONFIG_NF_TABLES_IPV4`
|
||||
- [x] Created GitHub issue #64 with full config requirements
|
||||
- [x] Updated `board/mochabin/kernel/config-6.12-openwrt-merged.fragment` with complete nftables config
|
||||
- [ ] Rebuild kernel with new config
|
||||
- [ ] Deploy new kernel to MOCHAbin
|
||||
- [ ] Add nftables modules to initramfs for early boot
|
||||
|
||||
### CrowdSec Bouncer Alert — TODO (ref issue #64)
|
||||
- [ ] Add pre-flight nftables check to bouncer startup
|
||||
- [ ] Alert SOC dashboard if firewall offline
|
||||
- [ ] Set LED3 (Security) to RED if nftables fails
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -143,13 +143,66 @@ CONFIG_IP_ADVANCED_ROUTER=y
|
|||
CONFIG_IP_MULTIPLE_TABLES=y
|
||||
CONFIG_IP_ROUTE_MULTIPATH=y
|
||||
CONFIG_IPV6=y
|
||||
# NETFILTER / NFTABLES - Complete firewall support (ref: GitHub issue #64)
|
||||
CONFIG_NETFILTER=y
|
||||
CONFIG_NF_CONNTRACK=y
|
||||
CONFIG_NETFILTER_ADVANCED=y
|
||||
CONFIG_NETFILTER_NETLINK=y
|
||||
CONFIG_NETFILTER_NETLINK_ACCT=y
|
||||
CONFIG_NETFILTER_NETLINK_QUEUE=y
|
||||
CONFIG_NETFILTER_NETLINK_LOG=y
|
||||
CONFIG_NF_CONNTRACK=y
|
||||
CONFIG_NF_CONNTRACK_MARK=y
|
||||
CONFIG_NF_CONNTRACK_ZONES=y
|
||||
CONFIG_NF_CONNTRACK_EVENTS=y
|
||||
CONFIG_NF_CONNTRACK_TIMEOUT=y
|
||||
CONFIG_NF_CONNTRACK_TIMESTAMP=y
|
||||
CONFIG_NF_CT_PROTO_DCCP=y
|
||||
CONFIG_NF_CT_PROTO_SCTP=y
|
||||
CONFIG_NF_CT_PROTO_UDPLITE=y
|
||||
CONFIG_NF_CT_NETLINK=y
|
||||
CONFIG_NF_NAT=y
|
||||
CONFIG_NF_NAT_MASQUERADE=y
|
||||
CONFIG_NF_TABLES=y
|
||||
CONFIG_NFT_NAT=y
|
||||
CONFIG_NF_TABLES_INET=y
|
||||
CONFIG_NF_TABLES_IPV4=y
|
||||
CONFIG_NF_TABLES_IPV6=y
|
||||
CONFIG_NF_TABLES_NETDEV=y
|
||||
CONFIG_NFT_NUMGEN=y
|
||||
CONFIG_NFT_CT=y
|
||||
CONFIG_NFT_COUNTER=y
|
||||
CONFIG_NFT_CONNLIMIT=y
|
||||
CONFIG_NFT_LOG=y
|
||||
CONFIG_NFT_LIMIT=y
|
||||
CONFIG_NFT_MASQ=y
|
||||
CONFIG_NFT_REDIR=y
|
||||
CONFIG_NFT_NAT=y
|
||||
CONFIG_NFT_REJECT=y
|
||||
CONFIG_NFT_HASH=y
|
||||
CONFIG_NFT_QUOTA=y
|
||||
CONFIG_NFT_SOCKET=y
|
||||
CONFIG_NFT_TPROXY=y
|
||||
CONFIG_NFT_FIB=y
|
||||
CONFIG_NFT_FIB_INET=y
|
||||
CONFIG_NFT_FIB_IPV4=y
|
||||
CONFIG_NFT_FIB_IPV6=y
|
||||
CONFIG_NFT_CHAIN_NAT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_STATE=y
|
||||
CONFIG_NETFILTER_XT_MATCH_COMMENT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_LIMIT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
|
||||
CONFIG_NETFILTER_XT_TARGET_LOG=y
|
||||
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
|
||||
CONFIG_NF_DEFRAG_IPV4=y
|
||||
CONFIG_NF_DEFRAG_IPV6=y
|
||||
CONFIG_IP_NF_IPTABLES=y
|
||||
CONFIG_IP_NF_FILTER=y
|
||||
CONFIG_IP_NF_NAT=y
|
||||
CONFIG_IP_NF_TARGET_MASQUERADE=y
|
||||
CONFIG_IP6_NF_IPTABLES=y
|
||||
CONFIG_IP6_NF_FILTER=y
|
||||
CONFIG_IP6_NF_NAT=y
|
||||
CONFIG_IP6_NF_TARGET_MASQUERADE=y
|
||||
|
||||
# VLAN
|
||||
CONFIG_VLAN_8021Q=y
|
||||
|
|
|
|||
150
docs/FAQ-BUSYBOX-RESCUE.md
Normal file
150
docs/FAQ-BUSYBOX-RESCUE.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# FAQ: Busybox Emergency Recovery
|
||||
|
||||
## The Problem
|
||||
|
||||
**Symptom:** All commands fail with "cannot execute: required file not found"
|
||||
|
||||
```bash
|
||||
root@host:~# ls
|
||||
-bash: /usr/bin/ls: cannot execute: required file not found
|
||||
root@host:~# ln -s foo bar
|
||||
-bash: /usr/bin/ln: cannot execute: required file not found
|
||||
```
|
||||
|
||||
**Cause:** The `/lib` symlink (pointing to `/usr/lib`) was replaced by a directory, breaking access to `libc.so` and the dynamic linker.
|
||||
|
||||
**Common trigger:** Extracting a tarball containing `lib/` at root level:
|
||||
```bash
|
||||
# DANGEROUS - overwrites /lib symlink!
|
||||
cd / && tar xzf modules.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Solution: Busybox
|
||||
|
||||
Busybox is typically **statically linked** - it doesn't need libc to run.
|
||||
|
||||
### Step 1: Check if busybox exists
|
||||
```bash
|
||||
echo /bin/busybox
|
||||
echo /usr/bin/busybox
|
||||
```
|
||||
|
||||
### Step 2: Use busybox to fix the symlink
|
||||
```bash
|
||||
# Remove broken /lib directory
|
||||
/bin/busybox rm -rf /lib
|
||||
|
||||
# Recreate the symlink
|
||||
/bin/busybox ln -s usr/lib /lib
|
||||
|
||||
# Verify
|
||||
/bin/busybox ls -la /lib
|
||||
```
|
||||
|
||||
### Step 3: Test recovery
|
||||
```bash
|
||||
ls -la /lib/aarch64-linux-gnu/libc.so*
|
||||
cat /etc/os-release
|
||||
uname -r
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why This Works
|
||||
|
||||
| Component | Location | Status when /lib broken |
|
||||
|-----------|----------|------------------------|
|
||||
| libc.so | `/usr/lib/aarch64-linux-gnu/` | Exists but inaccessible |
|
||||
| ld-linux | `/usr/lib/aarch64-linux-gnu/` | Exists but inaccessible |
|
||||
| /lib | Should be symlink to `usr/lib` | **Broken** - is a directory |
|
||||
| busybox | `/bin/busybox` | **Works** - statically linked |
|
||||
|
||||
Busybox contains 300+ commands in a single static binary. When libc is inaccessible, it's your lifeline.
|
||||
|
||||
---
|
||||
|
||||
## Prevention
|
||||
|
||||
### Safe tarball extraction for kernel modules:
|
||||
```bash
|
||||
# WRONG
|
||||
cd / && tar xzf modules.tar.gz
|
||||
|
||||
# RIGHT - extract only the modules subdirectory
|
||||
tar xzf modules.tar.gz -C /lib/modules/ --strip-components=2
|
||||
|
||||
# OR create tarball with correct structure
|
||||
cd /build/output/modules/lib/modules
|
||||
tar czf modules-6.6.137.tar.gz 6.6.137
|
||||
# Then on target:
|
||||
cd /lib/modules && tar xzf /tmp/modules-6.6.137.tar.gz
|
||||
```
|
||||
|
||||
### Check before extracting:
|
||||
```bash
|
||||
# Always check tarball contents first!
|
||||
tar tzf modules.tar.gz | head -20
|
||||
|
||||
# Look for dangerous top-level paths:
|
||||
# lib/ <- DANGER if extracted at /
|
||||
# usr/ <- DANGER
|
||||
# bin/ <- DANGER
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shell Built-ins When Nothing Works
|
||||
|
||||
If no busybox available, bash built-ins still work:
|
||||
|
||||
```bash
|
||||
# List files (glob expansion)
|
||||
echo /lib/*
|
||||
echo /usr/lib/aarch64-linux-gnu/libc*
|
||||
|
||||
# Read file content
|
||||
while IFS= read -r line; do echo "$line"; done < /etc/os-release
|
||||
|
||||
# Check if path exists
|
||||
[[ -d /lib/aarch64-linux-gnu ]] && echo "exists" || echo "missing"
|
||||
|
||||
# Write to file
|
||||
echo "test" > /tmp/test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Last Resort: Rescue Boot
|
||||
|
||||
If no busybox and shell built-ins can't help:
|
||||
|
||||
1. Boot from USB/SD rescue media
|
||||
2. Mount the broken rootfs:
|
||||
```bash
|
||||
mount /dev/mmcblk0p2 /mnt
|
||||
```
|
||||
3. Fix the symlink:
|
||||
```bash
|
||||
rm -rf /mnt/lib
|
||||
ln -s usr/lib /mnt/lib
|
||||
```
|
||||
4. Reboot normally
|
||||
|
||||
---
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
1. **Always have busybox installed** (statically linked)
|
||||
2. **Never extract tarballs blindly at /**
|
||||
3. **Check tarball contents before extracting**
|
||||
4. **Know your shell built-ins** (echo, read, [[ ]])
|
||||
5. **Keep rescue media ready** for emergencies
|
||||
|
||||
---
|
||||
|
||||
*SecuBox-Deb Emergency Recovery Guide*
|
||||
*CyberMind — https://cybermind.fr*
|
||||
*Author: Gerald Kerma <gandalf@gk2.net>*
|
||||
*Incident: 2026-05-08*
|
||||
105
docs/PROMPT-BUSYBOX-TECHTIP.md
Normal file
105
docs/PROMPT-BUSYBOX-TECHTIP.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Gemini Prompt: Busybox Emergency Recovery Tech Tip
|
||||
|
||||
## Prompt for Quick Visual Sketch
|
||||
|
||||
```
|
||||
Create a quick tech tip visual sketch/infographic about "Busybox Emergency Recovery" for Linux sysadmins.
|
||||
|
||||
SCENARIO:
|
||||
- All commands fail: "cannot execute: required file not found"
|
||||
- Cause: /lib symlink to /usr/lib was accidentally replaced by a directory
|
||||
- libc.so exists but is inaccessible
|
||||
- System appears completely broken
|
||||
|
||||
THE HERO: BUSYBOX
|
||||
- Busybox is STATICALLY LINKED - doesn't need libc!
|
||||
- Contains 300+ commands in one binary
|
||||
- Your lifeline when dynamic linking breaks
|
||||
|
||||
RECOVERY STEPS (3 commands):
|
||||
1. /bin/busybox rm -rf /lib
|
||||
2. /bin/busybox ln -s usr/lib /lib
|
||||
3. /bin/busybox ls -la /lib (verify)
|
||||
|
||||
VISUAL ELEMENTS:
|
||||
- Show broken chain (dynamic linking) vs solid block (static busybox)
|
||||
- Traffic light: RED (broken) → GREEN (fixed)
|
||||
- Terminal screenshots of the error and fix
|
||||
- Busybox Swiss Army knife icon
|
||||
|
||||
PREVENTION TIP:
|
||||
"Never extract tarballs blindly at root!"
|
||||
tar tzf file.tar.gz | head # ALWAYS check first!
|
||||
|
||||
STYLE:
|
||||
- Dark terminal aesthetic (green on black)
|
||||
- Minimalist, quick-reference format
|
||||
- One page maximum
|
||||
- Logo: CyberMind SecuBox
|
||||
|
||||
TAGLINE:
|
||||
"When libc fails, busybox prevails"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternative: ASCII Art Version
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🚨 BUSYBOX EMERGENCY RECOVERY │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ SYMPTOM: │
|
||||
│ $ ls │
|
||||
│ -bash: /usr/bin/ls: cannot execute: required file not found│
|
||||
│ │
|
||||
│ CAUSE: /lib symlink broken → libc inaccessible │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ /lib │ ──X─│ libc.so │ Dynamic linking BROKEN │
|
||||
│ │ (dir) │ │ │ │
|
||||
│ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ BUSYBOX │ Static binary = WORKS! │
|
||||
│ │ [===============] │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ │
|
||||
│ FIX (3 commands): │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ /bin/busybox rm -rf /lib │ │
|
||||
│ │ /bin/busybox ln -s usr/lib /lib │ │
|
||||
│ │ /bin/busybox ls -la /lib # verify │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ✓ System restored! │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 💡 PREVENTION: tar tzf file.tar.gz | head # check first! │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ "When libc fails, busybox prevails" │
|
||||
│ — CyberMind SecuBox Tech Tips │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Short Version (Tweet/Toot)
|
||||
|
||||
```
|
||||
🚨 Linux Emergency: All commands fail with "cannot execute"?
|
||||
|
||||
Your /lib symlink is broken! Fix with static busybox:
|
||||
|
||||
/bin/busybox rm -rf /lib
|
||||
/bin/busybox ln -s usr/lib /lib
|
||||
|
||||
"When libc fails, busybox prevails" 🔧
|
||||
|
||||
#Linux #SysAdmin #TechTip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*CyberMind SecuBox — 2026-05-08*
|
||||
|
|
@ -65,6 +65,27 @@ install_output() {
|
|||
ls -la "$OUTPUT_DIR/"
|
||||
}
|
||||
|
||||
deploy_to_device() {
|
||||
local HOST="${1:-root@192.168.1.200}"
|
||||
log "Deploying to $HOST..."
|
||||
|
||||
# Deploy kernel and DTB
|
||||
scp "$OUTPUT_DIR/Image-${KERNEL_VERSION}-secubox" "$HOST:/boot/"
|
||||
scp "$OUTPUT_DIR/armada-7040-mochabin.dtb" "$HOST:/boot/"
|
||||
|
||||
# SAFE module deployment - don't overwrite /lib symlink!
|
||||
# Create tarball with correct structure for /lib/modules/ extraction
|
||||
log "Creating safe modules tarball..."
|
||||
cd "$OUTPUT_DIR/modules/lib/modules"
|
||||
tar czf "/tmp/modules-${KERNEL_VERSION}.tar.gz" "${KERNEL_VERSION}"
|
||||
|
||||
# Deploy and extract safely
|
||||
scp "/tmp/modules-${KERNEL_VERSION}.tar.gz" "$HOST:/tmp/"
|
||||
ssh "$HOST" "mkdir -p /lib/modules && cd /lib/modules && tar xzf /tmp/modules-${KERNEL_VERSION}.tar.gz && depmod -a ${KERNEL_VERSION} || true"
|
||||
|
||||
log "Deployed! Add boot entry and reboot."
|
||||
}
|
||||
|
||||
case "${1:-build}" in
|
||||
build) check_deps; download_kernel; apply_patches; configure_kernel; build_kernel; install_output ;;
|
||||
deps) check_deps ;;
|
||||
|
|
|
|||
|
|
@ -1250,20 +1250,39 @@ body.hybrid-skin .map {
|
|||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Header LEDs (sidebar header - next to SECUBOX logo) */
|
||||
.header-hw-leds {
|
||||
/* Header LEDs with metrics (sidebar header - next to SECUBOX logo) */
|
||||
.header-leds-metrics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 4px 6px;
|
||||
gap: 2px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 5px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.led-metric-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.led-metric-val {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-mono, 'JetBrains Mono', monospace);
|
||||
color: var(--text-light, #e8e6d9);
|
||||
min-width: 28px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.led-metric-val.ok { color: #00dd44; }
|
||||
.led-metric-val.warn { color: #ffb347; }
|
||||
.led-metric-val.error { color: #ff4466; }
|
||||
.led-metric-val.info { color: #4488ff; }
|
||||
|
||||
.sidebar-header {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
|
|
@ -1276,10 +1295,24 @@ body.hybrid-skin .map {
|
|||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.header-hw-leds:hover .hw-led-v {
|
||||
.header-leds-metrics:hover .hw-led-v {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
/* Legacy - kept for compatibility */
|
||||
.header-hw-leds {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 4px 6px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Hardware LED Tooltip */
|
||||
.hw-led-tooltip {
|
||||
display: none;
|
||||
|
|
@ -1397,6 +1430,74 @@ body.hybrid-skin .map {
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hw-tooltip-footer {
|
||||
font-size: 9px;
|
||||
color: var(--muted-dark, #6b6b7a);
|
||||
text-align: center;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Services layer tooltip */
|
||||
.svc-metrics {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
gap: 8px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.svc-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.svc-count {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono, 'JetBrains Mono', monospace);
|
||||
}
|
||||
|
||||
.svc-label {
|
||||
font-size: 8px;
|
||||
color: var(--muted-dark, #6b6b7a);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Security layer tooltip */
|
||||
.sec-metrics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.sec-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sec-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sec-count {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono, 'JetBrains Mono', monospace);
|
||||
color: var(--text-light, #e8e6d9);
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.sec-label {
|
||||
font-size: 11px;
|
||||
color: var(--muted-dark, #6b6b7a);
|
||||
}
|
||||
|
||||
/* Mini LED Strip (legacy - kept for compatibility) */
|
||||
.mini-led-strip {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
(function() {
|
||||
const MENU_API = '/api/v1/hub/public/menu';
|
||||
const BATCH_HEALTH_API = '/api/v1/hub/public/health-batch';
|
||||
const VERSION = 'v2.30.0';
|
||||
const VERSION = 'v2.32.0';
|
||||
|
||||
// Round UI Module Colors (6-module system)
|
||||
const MODULE_COLORS = {
|
||||
|
|
@ -360,12 +360,13 @@
|
|||
}
|
||||
|
||||
// Hardware LED tooltip - shows detailed health info with multi-layer histogram
|
||||
function showHwLedTooltip(el) {
|
||||
// layer: 1=Hardware, 2=Services, 3=Security
|
||||
function showHwLedTooltip(el, layer) {
|
||||
var tooltip = document.getElementById('hw-led-tooltip');
|
||||
if (!tooltip) return;
|
||||
|
||||
var healthLabels = { ok: 'NOMINAL', warn: 'WARNING', error: 'CRITICAL' };
|
||||
var healthColors = { ok: '#00dd44', warn: '#ffb347', error: '#ff4466' };
|
||||
var healthColors = { ok: '#00dd44', warn: '#ffb347', error: '#ff4466', info: '#4488ff' };
|
||||
|
||||
// Calculate stats from history (top/max/medium)
|
||||
function getStats(arr) {
|
||||
|
|
@ -377,11 +378,6 @@
|
|||
return { cur: cur, max: max, avg: avg };
|
||||
}
|
||||
|
||||
var cpuStats = getStats(stripMetricsHistory.cpu);
|
||||
var memStats = getStats(stripMetricsHistory.mem);
|
||||
var diskStats = getStats(stripMetricsHistory.disk);
|
||||
var loadStats = getStats(stripMetricsHistory.load);
|
||||
|
||||
// Multi-layer histogram bars (top=max, medium=avg, bottom=current)
|
||||
function makeHistoBar(stats, color) {
|
||||
var maxPct = Math.min(100, stats.max);
|
||||
|
|
@ -394,29 +390,84 @@
|
|||
'</div>';
|
||||
}
|
||||
|
||||
// Build tooltip content with multi-layer histograms
|
||||
var content = '<div class="hw-tooltip-header" style="color:' + healthColors[systemHealthLevel] + '">' +
|
||||
'<span class="hw-tooltip-icon">●</span> ' + healthLabels[systemHealthLevel] +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-metrics">' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">CPU</span>' + makeHistoBar(cpuStats, '#C04E24') + '<span class="hw-metric-vals">' + cpuStats.cur + ' / ' + cpuStats.max + '</span></div>' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">MEM</span>' + makeHistoBar(memStats, '#9A6010') + '<span class="hw-metric-vals">' + memStats.cur + ' / ' + memStats.max + '</span></div>' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">DISK</span>' + makeHistoBar(diskStats, '#803018') + '<span class="hw-metric-vals">' + diskStats.cur + ' / ' + diskStats.max + '</span></div>' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">LOAD</span>' + makeHistoBar({cur: loadStats.cur * 25, max: loadStats.max * 25, avg: loadStats.avg * 25}, '#3D35A0') + '<span class="hw-metric-vals">' + (loadStats.cur).toFixed(1) + ' / ' + (loadStats.max).toFixed(1) + '</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-legend"><span>■ Current</span><span style="opacity:0.5">■ Average</span><span style="opacity:0.3">■ Max</span></div>' +
|
||||
'<div class="hw-tooltip-leds">' +
|
||||
'<div><span style="color:#00dd44">●</span> System</div>' +
|
||||
'<div><span style="color:' + (systemHealthLevel === 'ok' ? '#00dd44' : '#ffb347') + '">●</span> Services</div>' +
|
||||
'<div><span style="color:' + healthColors[systemHealthLevel] + '">●</span> Security</div>' +
|
||||
'</div>';
|
||||
var content = '';
|
||||
var layerTitles = { 1: 'HARDWARE', 2: 'SERVICES', 3: 'SECURITY' };
|
||||
var layerIcons = { 1: '⚙️', 2: '🔧', 3: '🛡️' };
|
||||
|
||||
if (layer === 1) {
|
||||
// Hardware layer - CPU, MEM, DISK, LOAD
|
||||
var cpuStats = getStats(stripMetricsHistory.cpu);
|
||||
var memStats = getStats(stripMetricsHistory.mem);
|
||||
var diskStats = getStats(stripMetricsHistory.disk);
|
||||
var loadStats = getStats(stripMetricsHistory.load);
|
||||
var hwMax = Math.max(cpuStats.cur, memStats.cur);
|
||||
var hwLevel = hwMax > 80 ? 'error' : hwMax > 50 ? 'warn' : 'ok';
|
||||
|
||||
content = '<div class="hw-tooltip-header" style="color:' + healthColors[hwLevel] + '">' +
|
||||
'<span class="hw-tooltip-icon">' + layerIcons[1] + '</span> ' + layerTitles[1] + ' — ' + healthLabels[hwLevel] +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-metrics">' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">CPU</span>' + makeHistoBar(cpuStats, '#C04E24') + '<span class="hw-metric-vals">' + cpuStats.cur + '% / ' + cpuStats.max + '%</span></div>' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">MEM</span>' + makeHistoBar(memStats, '#9A6010') + '<span class="hw-metric-vals">' + memStats.cur + '% / ' + memStats.max + '%</span></div>' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">DISK</span>' + makeHistoBar(diskStats, '#803018') + '<span class="hw-metric-vals">' + diskStats.cur + '% / ' + diskStats.max + '%</span></div>' +
|
||||
'<div class="hw-metric-row"><span class="hw-metric-label">LOAD</span>' + makeHistoBar({cur: loadStats.cur * 25, max: loadStats.max * 25, avg: loadStats.avg * 25}, '#3D35A0') + '<span class="hw-metric-vals">' + (loadStats.cur).toFixed(1) + ' / ' + (loadStats.max).toFixed(1) + '</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-legend"><span>■ Current</span><span style="opacity:0.5">■ Average</span><span style="opacity:0.3">■ Max</span></div>';
|
||||
|
||||
} else if (layer === 2) {
|
||||
// Services layer - count ok/warn/error/total
|
||||
var counts = { ok: 0, warn: 0, error: 0, unknown: 0 };
|
||||
Object.keys(healthCache).forEach(function(mod) {
|
||||
var st = healthCache[mod] && healthCache[mod].status;
|
||||
if (st === 'ok') counts.ok++;
|
||||
else if (st === 'warn') counts.warn++;
|
||||
else if (st === 'error') counts.error++;
|
||||
else counts.unknown++;
|
||||
});
|
||||
var total = counts.ok + counts.warn + counts.error + counts.unknown;
|
||||
var svcLevel = counts.error > 0 ? 'error' : counts.warn > 0 ? 'warn' : 'ok';
|
||||
|
||||
content = '<div class="hw-tooltip-header" style="color:' + healthColors[svcLevel] + '">' +
|
||||
'<span class="hw-tooltip-icon">' + layerIcons[2] + '</span> ' + layerTitles[2] + ' — ' + healthLabels[svcLevel] +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-metrics svc-metrics">' +
|
||||
'<div class="svc-stat"><span class="svc-count" style="color:#00dd44">' + counts.ok + '</span><span class="svc-label">OK</span></div>' +
|
||||
'<div class="svc-stat"><span class="svc-count" style="color:#ffb347">' + counts.warn + '</span><span class="svc-label">WARN</span></div>' +
|
||||
'<div class="svc-stat"><span class="svc-count" style="color:#ff4466">' + counts.error + '</span><span class="svc-label">ERROR</span></div>' +
|
||||
'<div class="svc-stat"><span class="svc-count" style="color:#666">' + counts.unknown + '</span><span class="svc-label">UNKNOWN</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-footer">' + counts.ok + ' / ' + total + ' services healthy</div>';
|
||||
|
||||
} else if (layer === 3) {
|
||||
// Security layer - bans, alerts, crowdsec status
|
||||
var bans = currentStripMetrics.bans || 0;
|
||||
var alerts = currentStripMetrics.alerts || 0;
|
||||
var secLevel = bans > 50 ? 'info' : bans > 20 ? 'warn' : alerts > 0 ? 'warn' : 'ok';
|
||||
var secLabel = bans > 50 ? 'ACTIVE MITIGATION' : healthLabels[secLevel === 'info' ? 'ok' : secLevel];
|
||||
|
||||
content = '<div class="hw-tooltip-header" style="color:' + healthColors[secLevel] + '">' +
|
||||
'<span class="hw-tooltip-icon">' + layerIcons[3] + '</span> ' + layerTitles[3] + ' — ' + secLabel +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-metrics sec-metrics">' +
|
||||
'<div class="sec-stat"><span class="sec-icon">🚫</span><span class="sec-count">' + bans + '</span><span class="sec-label">Active Bans</span></div>' +
|
||||
'<div class="sec-stat"><span class="sec-icon">⚠️</span><span class="sec-count">' + alerts + '</span><span class="sec-label">Recent Alerts</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="hw-tooltip-footer">CrowdSec ' + (bans > 0 ? 'actively blocking threats' : 'monitoring') + '</div>';
|
||||
}
|
||||
|
||||
tooltip.innerHTML = content;
|
||||
|
||||
// Position tooltip
|
||||
// Position tooltip - check if inside sidebar (left < 220px)
|
||||
var rect = el.getBoundingClientRect();
|
||||
tooltip.style.left = rect.left + 'px';
|
||||
tooltip.style.top = (rect.bottom + 8) + 'px';
|
||||
var tooltipLeft = rect.left;
|
||||
|
||||
// If element is in sidebar (left < 220), position tooltip to the right of it
|
||||
if (rect.left < 220) {
|
||||
tooltipLeft = rect.right + 10;
|
||||
}
|
||||
|
||||
tooltip.style.left = tooltipLeft + 'px';
|
||||
tooltip.style.top = rect.top + 'px';
|
||||
tooltip.style.display = 'block';
|
||||
}
|
||||
|
||||
|
|
@ -662,8 +713,60 @@
|
|||
healthEl.innerHTML = emoji + ' ' + ok + '/' + total;
|
||||
healthEl.className = 'status-value ' + (err > 0 ? 'error' : warn > 0 ? 'warn' : 'ok');
|
||||
healthEl.title = ok + ' OK, ' + warn + ' warnings, ' + err + ' errors';
|
||||
|
||||
// Update LED metrics in sidebar header (sync with health bumper)
|
||||
var lmHw = document.getElementById('lm-hw');
|
||||
var lmSvc = document.getElementById('lm-svc');
|
||||
var lmSec = document.getElementById('lm-sec');
|
||||
|
||||
// LED1 (Hardware): CPU%
|
||||
if (lmHw) {
|
||||
var hwMax = Math.max(currentStripMetrics.cpu || 0, currentStripMetrics.mem || 0);
|
||||
lmHw.textContent = hwMax + '%';
|
||||
lmHw.className = 'led-metric-val ' + (hwMax > 80 ? 'error' : hwMax > 50 ? 'warn' : 'ok');
|
||||
}
|
||||
|
||||
// LED2 (Services): ok/total
|
||||
if (lmSvc) {
|
||||
lmSvc.textContent = ok + '/' + total;
|
||||
lmSvc.className = 'led-metric-val ' + (err > 0 ? 'error' : warn > 0 ? 'warn' : 'ok');
|
||||
}
|
||||
|
||||
// LED3 (Security): fetch bans from CrowdSec
|
||||
if (lmSec) {
|
||||
// Will be updated by separate security fetch
|
||||
lmSec.textContent = '...';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch security metrics (CrowdSec bans)
|
||||
try {
|
||||
var secRes = await fetch('/api/v1/crowdsec/stats', { headers: headers });
|
||||
if (secRes.ok) {
|
||||
var secData = await secRes.json();
|
||||
var lmSec = document.getElementById('lm-sec');
|
||||
if (lmSec) {
|
||||
var bans = secData.active_bans || secData.decisions || 0;
|
||||
var alerts = secData.alerts_today || 0;
|
||||
lmSec.textContent = bans + 'B';
|
||||
lmSec.title = bans + ' bans, ' + alerts + ' alerts today';
|
||||
// Blue if actively blocking, else based on alert level
|
||||
if (bans > 20) {
|
||||
lmSec.className = 'led-metric-val info'; // Blue - active mitigation
|
||||
} else if (alerts > 50) {
|
||||
lmSec.className = 'led-metric-val error';
|
||||
} else if (alerts > 10) {
|
||||
lmSec.className = 'led-metric-val warn';
|
||||
} else {
|
||||
lmSec.className = 'led-metric-val ok';
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
var lmSec = document.getElementById('lm-sec');
|
||||
if (lmSec) lmSec.textContent = '?';
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[Sidebar] Status bar data error:', e);
|
||||
}
|
||||
|
|
@ -1295,7 +1398,11 @@
|
|||
hideUnknownEnabled = getHideUnknownPref();
|
||||
|
||||
sidebar.innerHTML = '<div class="sidebar-header"><a href="/"><span class="logo-icon">🔒</span><div><span class="logo">SECUBOX</span><span class="logo-version">🚀 ' + VERSION + '</span></div></a>' +
|
||||
'<div class="header-hw-leds" id="header-hw-leds"><div class="hw-led-v" id="hw-led-3"></div><div class="hw-led-v" id="hw-led-2"></div><div class="hw-led-v" id="hw-led-1"></div></div></div>' +
|
||||
'<div class="header-leds-metrics" id="header-leds-metrics">' +
|
||||
'<div class="led-metric-row" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this,3)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()"><div class="hw-led-v" id="hw-led-3"></div><span class="led-metric-val" id="lm-sec">--</span></div>' +
|
||||
'<div class="led-metric-row" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this,2)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()"><div class="hw-led-v" id="hw-led-2"></div><span class="led-metric-val" id="lm-svc">--</span></div>' +
|
||||
'<div class="led-metric-row" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this,1)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()"><div class="hw-led-v" id="hw-led-1"></div><span class="led-metric-val" id="lm-hw">--</span></div>' +
|
||||
'</div></div>' +
|
||||
'<div class="sidebar-nav"><div class="nav-section"><div class="nav-section-title"><span>Loading...</span></div></div></div>';
|
||||
|
||||
try {
|
||||
|
|
@ -1368,10 +1475,10 @@
|
|||
});
|
||||
|
||||
sidebar.innerHTML = '<div class="sidebar-header"><a href="/"><span class="logo-icon">🔒</span><div><span class="logo">SECUBOX</span><span class="logo-version">🚀 ' + VERSION + '</span></div></a>' +
|
||||
'<div class="header-hw-leds" id="header-hw-leds" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()">' +
|
||||
'<div class="hw-led-v" id="hw-led-3" title="Security"></div>' +
|
||||
'<div class="hw-led-v" id="hw-led-2" title="Services"></div>' +
|
||||
'<div class="hw-led-v" id="hw-led-1" title="Hardware"></div>' +
|
||||
'<div class="header-leds-metrics" id="header-leds-metrics">' +
|
||||
'<div class="led-metric-row" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this,3)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()"><div class="hw-led-v" id="hw-led-3"></div><span class="led-metric-val" id="lm-sec">--</span></div>' +
|
||||
'<div class="led-metric-row" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this,2)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()"><div class="hw-led-v" id="hw-led-2"></div><span class="led-metric-val" id="lm-svc">--</span></div>' +
|
||||
'<div class="led-metric-row" onmouseenter="SecuBoxSidebar.showHwLedTooltip(this,1)" onmouseleave="SecuBoxSidebar.hideHwLedTooltip()"><div class="hw-led-v" id="hw-led-1"></div><span class="led-metric-val" id="lm-hw">--</span></div>' +
|
||||
'</div></div>' +
|
||||
'<div class="sidebar-nav">' + menuHTML + '</div>' +
|
||||
'<div class="sidebar-footer">' +
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user