Add pi3b hardware tier and resource guards for condo Pi deployment
- Add pi3b tier (700MB RAM limit, 8 passive/2 active modules, no bettercap/mitmproxy) - Update detect_hardware_tier() to detect Raspberry Pi 3B by model string and BCM2837 - Add pi3b section to hardware_tiers.yaml - Add systemd MemoryMax/CPUQuota/OOMScoreAdj to all three service files - Fix bigbrother-watchdog.service ExecStart (was calling non-existent ResourceMonitor().run()) - Add scripts/run_watchdog.py as standalone health monitor daemon
This commit is contained in:
@@ -23,6 +23,27 @@ opi_zero3:
|
|||||||
wifi_attacks: true # Built-in WiFi 5 supports evil twin
|
wifi_attacks: true # Built-in WiFi 5 supports evil twin
|
||||||
inline_bridge: true # Via USB Ethernet adapter
|
inline_bridge: true # Via USB Ethernet adapter
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Raspberry Pi 3B — Condo / Secondary Implant
|
||||||
|
# BCM2837B0, 4x Cortex-A53 @ 1.2GHz, WiFi 4 + BT 4.2, 1x 100M Ethernet
|
||||||
|
# 1GB RAM — passive sniffing + limited active capability
|
||||||
|
# OS: Ubuntu 24.04 LTS (arm64)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
pi3b:
|
||||||
|
max_passive: 8 # 8 passive modules (RAM-limited)
|
||||||
|
max_active: 2 # Minimal active capability
|
||||||
|
max_ram_mb: 700 # 900MB total - ~200MB OS overhead
|
||||||
|
zstd_level: 3 # Fast compression (CPU-limited)
|
||||||
|
kdf: "pbkdf2" # argon2id too expensive for 1GB
|
||||||
|
mitmproxy: false # Too RAM-heavy
|
||||||
|
lkm: false # No kernel headers
|
||||||
|
cellular: false # No USB header
|
||||||
|
overlayfs_mb: 64 # Small tmpfs overlay
|
||||||
|
thermal_warning: 60 # Celsius — Pi 3B runs warm
|
||||||
|
thermal_shed: 75 # Shed modules at this temp
|
||||||
|
wifi_attacks: false # Single wlan0 = uplink, can't spare for monitor
|
||||||
|
inline_bridge: false # eth0 is down at condo
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Raspberry Pi Zero 2W — Jumpbox / Beacon
|
# Raspberry Pi Zero 2W — Jumpbox / Beacon
|
||||||
# BCM2710A1, 4x Cortex-A53 @ 1.0GHz, WiFi 4, no Ethernet (USB adapter)
|
# BCM2710A1, 4x Cortex-A53 @ 1.0GHz, WiFi 4, no Ethernet (USB adapter)
|
||||||
|
|||||||
+16
-1
@@ -37,6 +37,14 @@ HARDWARE_TIERS = {
|
|||||||
"allow_mitmproxy": True,
|
"allow_mitmproxy": True,
|
||||||
"allow_bettercap": True,
|
"allow_bettercap": True,
|
||||||
},
|
},
|
||||||
|
"pi3b": {
|
||||||
|
"max_passive": 8,
|
||||||
|
"max_active": 2,
|
||||||
|
"max_ram_mb": 700, # 900MB total - ~200MB OS overhead
|
||||||
|
"max_cpu_pct": 75,
|
||||||
|
"allow_mitmproxy": False,
|
||||||
|
"allow_bettercap": False,
|
||||||
|
},
|
||||||
"pi_zero": {
|
"pi_zero": {
|
||||||
"max_passive": 4,
|
"max_passive": 4,
|
||||||
"max_active": 0,
|
"max_active": 0,
|
||||||
@@ -78,9 +86,16 @@ def detect_hardware_tier() -> str:
|
|||||||
if "allwinner" in cpuinfo and "h618" in cpuinfo:
|
if "allwinner" in cpuinfo and "h618" in cpuinfo:
|
||||||
return "opi_zero3"
|
return "opi_zero3"
|
||||||
|
|
||||||
if "raspberry pi zero 2" in model or "bcm2710" in cpuinfo:
|
if "raspberry pi zero 2" in model:
|
||||||
return "pi_zero"
|
return "pi_zero"
|
||||||
|
|
||||||
|
if "raspberry pi 3" in model:
|
||||||
|
return "pi3b"
|
||||||
|
|
||||||
|
# BCM2710/BCM2837 without a clear model string — Pi 3B family
|
||||||
|
if "bcm2837" in cpuinfo or ("bcm2710" in cpuinfo and "raspberry pi zero 2" not in model):
|
||||||
|
return "pi3b"
|
||||||
|
|
||||||
return "generic"
|
return "generic"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Standalone resource monitor daemon for bigbrother-watchdog.service.
|
||||||
|
|
||||||
|
Runs periodic memory/disk/thermal checks independently of the main BB engine.
|
||||||
|
Logs warnings and kills the lowest-priority BB process on OOM.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Ensure /opt/.cache/bb is on the path when run from the service
|
||||||
|
sys.path.insert(0, "/opt/.cache/bb")
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.WARNING,
|
||||||
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("bb.watchdog")
|
||||||
|
|
||||||
|
CHECK_INTERVAL = 15.0
|
||||||
|
THERMAL_WARN = 60
|
||||||
|
THERMAL_SHED = 75
|
||||||
|
MEM_WARN_PCT = 80
|
||||||
|
MEM_CRIT_PCT = 92
|
||||||
|
DISK_WARN_PCT = 80
|
||||||
|
DISK_CRIT_PCT = 90
|
||||||
|
STORAGE_PATH = "/opt/.cache/bb/storage"
|
||||||
|
|
||||||
|
|
||||||
|
def _read_meminfo():
|
||||||
|
info = {}
|
||||||
|
try:
|
||||||
|
with open("/proc/meminfo") as f:
|
||||||
|
for line in f:
|
||||||
|
k, v = line.split(":", 1)
|
||||||
|
info[k.strip()] = int(v.split()[0])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def _read_temp():
|
||||||
|
base = "/sys/class/thermal"
|
||||||
|
try:
|
||||||
|
import os as _os
|
||||||
|
for zone in sorted(_os.listdir(base)):
|
||||||
|
p = f"{base}/{zone}/temp"
|
||||||
|
if _os.path.exists(p):
|
||||||
|
return int(open(p).read().strip()) / 1000.0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _disk_pct(path):
|
||||||
|
try:
|
||||||
|
st = os.statvfs(path)
|
||||||
|
total = st.f_blocks * st.f_frsize
|
||||||
|
free = st.f_bavail * st.f_frsize
|
||||||
|
return (total - free) / total * 100 if total else 0
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _check():
|
||||||
|
mem = _read_meminfo()
|
||||||
|
total = mem.get("MemTotal", 0)
|
||||||
|
avail = mem.get("MemAvailable", 0)
|
||||||
|
if total:
|
||||||
|
used_pct = (total - avail) / total * 100
|
||||||
|
if used_pct >= MEM_CRIT_PCT:
|
||||||
|
logger.critical("Memory critical: %.0f%% used — OOM risk", used_pct)
|
||||||
|
elif used_pct >= MEM_WARN_PCT:
|
||||||
|
logger.warning("Memory warning: %.0f%% used", used_pct)
|
||||||
|
|
||||||
|
temp = _read_temp()
|
||||||
|
if temp is not None:
|
||||||
|
if temp >= THERMAL_SHED:
|
||||||
|
logger.critical("Thermal critical: %.1fC", temp)
|
||||||
|
elif temp >= THERMAL_WARN:
|
||||||
|
logger.warning("Thermal warning: %.1fC", temp)
|
||||||
|
|
||||||
|
disk = _disk_pct(STORAGE_PATH if os.path.exists(STORAGE_PATH) else "/")
|
||||||
|
if disk >= DISK_CRIT_PCT:
|
||||||
|
logger.critical("Disk critical: %.0f%% used at %s", disk, STORAGE_PATH)
|
||||||
|
elif disk >= DISK_WARN_PCT:
|
||||||
|
logger.warning("Disk warning: %.0f%% used at %s", disk, STORAGE_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
stop = threading.Event()
|
||||||
|
|
||||||
|
def _sig(*_):
|
||||||
|
stop.set()
|
||||||
|
|
||||||
|
signal.signal(signal.SIGTERM, _sig)
|
||||||
|
signal.signal(signal.SIGINT, _sig)
|
||||||
|
|
||||||
|
logger.warning("bb-watchdog started (interval=%.0fs)", CHECK_INTERVAL)
|
||||||
|
while not stop.is_set():
|
||||||
|
try:
|
||||||
|
_check()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Check error: %s", e)
|
||||||
|
stop.wait(CHECK_INTERVAL)
|
||||||
|
logger.warning("bb-watchdog stopped")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -12,6 +12,9 @@ RestartSec=5
|
|||||||
User=root
|
User=root
|
||||||
StandardOutput=null
|
StandardOutput=null
|
||||||
StandardError=null
|
StandardError=null
|
||||||
|
MemoryMax=80M
|
||||||
|
CPUQuota=50%
|
||||||
|
OOMScoreAdj=300
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ RestartSec=10
|
|||||||
User=root
|
User=root
|
||||||
StandardOutput=null
|
StandardOutput=null
|
||||||
StandardError=null
|
StandardError=null
|
||||||
|
MemoryMax=600M
|
||||||
|
MemorySwapMax=200M
|
||||||
|
CPUQuota=200%
|
||||||
|
OOMScoreAdj=200
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ Requires=bigbrother-core.service
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/opt/.cache/bb/.venv/bin/python3 -c "from core.resource_monitor import ResourceMonitor; ResourceMonitor().run()"
|
ExecStart=/opt/.cache/bb/.venv/bin/python3 /opt/.cache/bb/scripts/run_watchdog.py
|
||||||
WorkingDirectory=/opt/.cache/bb
|
WorkingDirectory=/opt/.cache/bb
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=30
|
RestartSec=30
|
||||||
User=root
|
User=root
|
||||||
StandardOutput=null
|
StandardOutput=null
|
||||||
StandardError=null
|
StandardError=null
|
||||||
|
MemoryMax=64M
|
||||||
|
CPUQuota=25%
|
||||||
|
OOMScoreAdj=400
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Reference in New Issue
Block a user