mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-15 17:01:58 +00:00
Add Loki and Sentinel utility classes for web API endpoints
- Implemented LokiUtils class with GET and POST endpoints for managing scripts, jobs, and payloads. - Added SentinelUtils class with GET and POST endpoints for managing events, rules, devices, and notifications. - Both classes include error handling and JSON response formatting.
This commit is contained in:
585
bifrost/__init__.py
Normal file
585
bifrost/__init__.py
Normal file
@@ -0,0 +1,585 @@
|
||||
"""
|
||||
Bifrost — Pwnagotchi-compatible WiFi recon engine for Bjorn.
|
||||
Runs as a daemon thread alongside MANUAL/AUTO/AI modes.
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from logger import Logger
|
||||
|
||||
logger = Logger(name="bifrost", level=logging.DEBUG)
|
||||
|
||||
|
||||
class BifrostEngine:
|
||||
"""Main Bifrost lifecycle manager.
|
||||
|
||||
Manages the bettercap subprocess and BifrostAgent daemon loop.
|
||||
Pattern follows SentinelEngine (sentinel.py).
|
||||
"""
|
||||
|
||||
def __init__(self, shared_data):
|
||||
self.shared_data = shared_data
|
||||
self._thread = None
|
||||
self._stop_event = threading.Event()
|
||||
self._running = False
|
||||
self._bettercap_proc = None
|
||||
self._monitor_torn_down = False
|
||||
self._monitor_failed = False
|
||||
self.agent = None
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return bool(self.shared_data.config.get('bifrost_enabled', False))
|
||||
|
||||
def start(self):
|
||||
"""Start the Bifrost engine (bettercap + agent loop)."""
|
||||
if self._running:
|
||||
logger.warning("Bifrost already running")
|
||||
return
|
||||
|
||||
# Wait for any previous thread to finish before re-starting
|
||||
if self._thread and self._thread.is_alive():
|
||||
logger.warning("Previous Bifrost thread still running — waiting ...")
|
||||
self._stop_event.set()
|
||||
self._thread.join(timeout=15)
|
||||
|
||||
logger.info("Starting Bifrost engine ...")
|
||||
self._stop_event.clear()
|
||||
self._running = True
|
||||
self._monitor_failed = False
|
||||
self._monitor_torn_down = False
|
||||
|
||||
self._thread = threading.Thread(
|
||||
target=self._loop, daemon=True, name="BifrostEngine"
|
||||
)
|
||||
self._thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the Bifrost engine gracefully.
|
||||
|
||||
Signals the daemon loop to exit, then waits for it to finish.
|
||||
The loop's finally block handles bettercap shutdown and monitor teardown.
|
||||
"""
|
||||
if not self._running:
|
||||
return
|
||||
|
||||
logger.info("Stopping Bifrost engine ...")
|
||||
self._stop_event.set()
|
||||
self._running = False
|
||||
|
||||
if self._thread and self._thread.is_alive():
|
||||
self._thread.join(timeout=15)
|
||||
self._thread = None
|
||||
self.agent = None
|
||||
|
||||
# Safety net: teardown is idempotent, so this is a no-op if
|
||||
# _loop()'s finally already ran it.
|
||||
self._stop_bettercap()
|
||||
self._teardown_monitor_mode()
|
||||
logger.info("Bifrost engine stopped")
|
||||
|
||||
def _loop(self):
|
||||
"""Main daemon loop — setup monitor mode, start bettercap, create agent, run recon cycle."""
|
||||
try:
|
||||
# Install compatibility shim for pwnagotchi plugins
|
||||
from bifrost import plugins as bfplugins
|
||||
from bifrost.compat import install_shim
|
||||
install_shim(self.shared_data, bfplugins)
|
||||
|
||||
# Setup monitor mode on the WiFi interface
|
||||
self._setup_monitor_mode()
|
||||
|
||||
if self._monitor_failed:
|
||||
logger.error(
|
||||
"Monitor mode setup failed — Bifrost cannot operate without monitor "
|
||||
"mode. For Broadcom chips (Pi Zero W/2W), install nexmon: "
|
||||
"https://github.com/seemoo-lab/nexmon — "
|
||||
"Or use an external USB WiFi adapter with monitor mode support.")
|
||||
# Teardown first (restores network services) BEFORE switching mode,
|
||||
# so the orchestrator doesn't start scanning on a dead network.
|
||||
self._teardown_monitor_mode()
|
||||
self._running = False
|
||||
# Now switch mode back to AUTO — the network should be restored.
|
||||
# We set the flag directly FIRST (bypass setter to avoid re-stopping),
|
||||
# then ensure manual_mode/ai_mode are cleared so getter returns AUTO.
|
||||
try:
|
||||
self.shared_data.config["bifrost_enabled"] = False
|
||||
self.shared_data.config["manual_mode"] = False
|
||||
self.shared_data.config["ai_mode"] = False
|
||||
self.shared_data.manual_mode = False
|
||||
self.shared_data.ai_mode = False
|
||||
self.shared_data.invalidate_config_cache()
|
||||
logger.info("Bifrost auto-disabled due to monitor mode failure — mode: AUTO")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
# Start bettercap
|
||||
self._start_bettercap()
|
||||
self._stop_event.wait(3) # Give bettercap time to initialize
|
||||
if self._stop_event.is_set():
|
||||
return
|
||||
|
||||
# Create agent (pass stop_event so its threads exit cleanly)
|
||||
from bifrost.agent import BifrostAgent
|
||||
self.agent = BifrostAgent(self.shared_data, stop_event=self._stop_event)
|
||||
|
||||
# Load plugins
|
||||
bfplugins.load(self.shared_data.config)
|
||||
|
||||
# Initialize agent
|
||||
self.agent.start()
|
||||
|
||||
logger.info("Bifrost agent started — entering recon cycle")
|
||||
|
||||
# Main recon loop (port of do_auto_mode from pwnagotchi)
|
||||
while not self._stop_event.is_set():
|
||||
try:
|
||||
# Full spectrum scan
|
||||
self.agent.recon()
|
||||
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
|
||||
# Get APs grouped by channel
|
||||
channels = self.agent.get_access_points_by_channel()
|
||||
|
||||
# For each channel
|
||||
for ch, aps in channels:
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
|
||||
self.agent.set_channel(ch)
|
||||
|
||||
# For each AP on this channel
|
||||
for ap in aps:
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
|
||||
# Send association frame for PMKID
|
||||
self.agent.associate(ap)
|
||||
|
||||
# Deauth all clients for full handshake
|
||||
for sta in ap.get('clients', []):
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
self.agent.deauth(ap, sta)
|
||||
|
||||
if not self._stop_event.is_set():
|
||||
self.agent.next_epoch()
|
||||
|
||||
except Exception as e:
|
||||
if 'wifi.interface not set' in str(e):
|
||||
logger.error("WiFi interface lost: %s", e)
|
||||
self._stop_event.wait(60)
|
||||
if not self._stop_event.is_set():
|
||||
self.agent.next_epoch()
|
||||
else:
|
||||
logger.error("Recon loop error: %s", e)
|
||||
self._stop_event.wait(5)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Bifrost engine fatal error: %s", e)
|
||||
finally:
|
||||
from bifrost import plugins as bfplugins
|
||||
bfplugins.shutdown()
|
||||
self._stop_bettercap()
|
||||
self._teardown_monitor_mode()
|
||||
self._running = False
|
||||
|
||||
# ── Monitor mode management ─────────────────────────
|
||||
|
||||
# ── Nexmon helpers ────────────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _has_nexmon():
|
||||
"""Check if nexmon firmware patches are installed."""
|
||||
import shutil
|
||||
if not shutil.which('nexutil'):
|
||||
return False
|
||||
# Verify patched firmware via dmesg
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['dmesg'], capture_output=True, text=True, timeout=5)
|
||||
if 'nexmon' in r.stdout.lower():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
# nexutil exists — assume usable even without dmesg confirmation
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _is_brcmfmac(iface):
|
||||
"""Check if the interface uses the brcmfmac driver (Broadcom)."""
|
||||
driver_path = '/sys/class/net/%s/device/driver' % iface
|
||||
try:
|
||||
real = os.path.realpath(driver_path)
|
||||
return 'brcmfmac' in real
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _detect_phy(self, iface):
|
||||
"""Detect the phy name for a given interface (e.g. 'phy0')."""
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['iw', 'dev', iface, 'info'],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
for line in r.stdout.splitlines():
|
||||
if 'wiphy' in line:
|
||||
idx = line.strip().split()[-1]
|
||||
return 'phy%s' % idx
|
||||
except Exception:
|
||||
pass
|
||||
return 'phy0'
|
||||
|
||||
def _setup_monitor_mode(self):
|
||||
"""Put the WiFi interface into monitor mode.
|
||||
|
||||
Strategy order:
|
||||
1. Nexmon — for Broadcom brcmfmac chips (Pi Zero W / Pi Zero 2 W)
|
||||
Uses: iw phy <phy> interface add mon0 type monitor + nexutil -m2
|
||||
2. airmon-ng — for chipsets with proper driver support (Atheros, Realtek, etc.)
|
||||
3. iw — direct fallback for other drivers
|
||||
"""
|
||||
self._monitor_torn_down = False
|
||||
self._nexmon_used = False
|
||||
cfg = self.shared_data.config
|
||||
iface = cfg.get('bifrost_iface', 'wlan0mon')
|
||||
|
||||
# If configured iface already ends with 'mon', derive the base name
|
||||
if iface.endswith('mon'):
|
||||
base_iface = iface[:-3] # e.g. 'wlan0mon' -> 'wlan0'
|
||||
else:
|
||||
base_iface = iface
|
||||
|
||||
# Store original interface name for teardown
|
||||
self._base_iface = base_iface
|
||||
self._mon_iface = iface
|
||||
|
||||
# Check if a monitor interface already exists
|
||||
if iface != base_iface and self._iface_exists(iface):
|
||||
logger.info("Monitor interface %s already exists", iface)
|
||||
return
|
||||
|
||||
# ── Strategy 1: Nexmon (Broadcom brcmfmac) ────────────────
|
||||
if self._is_brcmfmac(base_iface):
|
||||
logger.info("Broadcom brcmfmac chip detected on %s", base_iface)
|
||||
if self._has_nexmon():
|
||||
if self._setup_nexmon(base_iface, cfg):
|
||||
return
|
||||
# nexmon setup failed — don't try other strategies, they won't work either
|
||||
self._monitor_failed = True
|
||||
return
|
||||
else:
|
||||
logger.error(
|
||||
"Broadcom brcmfmac chip requires nexmon firmware patches for "
|
||||
"monitor mode. Install nexmon manually using install_nexmon.sh "
|
||||
"or visit: https://github.com/seemoo-lab/nexmon")
|
||||
self._monitor_failed = True
|
||||
return
|
||||
|
||||
# ── Strategy 2: airmon-ng (Atheros, Realtek, etc.) ────────
|
||||
airmon_ok = False
|
||||
try:
|
||||
logger.info("Killing interfering processes ...")
|
||||
subprocess.run(
|
||||
['airmon-ng', 'check', 'kill'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
timeout=15,
|
||||
)
|
||||
logger.info("Starting monitor mode: airmon-ng start %s", base_iface)
|
||||
result = subprocess.run(
|
||||
['airmon-ng', 'start', base_iface],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
combined = (result.stdout + result.stderr).strip()
|
||||
logger.info("airmon-ng output: %s", combined)
|
||||
|
||||
if 'Operation not supported' in combined or 'command failed' in combined:
|
||||
logger.warning("airmon-ng failed: %s", combined)
|
||||
else:
|
||||
# airmon-ng may rename the interface (wlan0 -> wlan0mon)
|
||||
if self._iface_exists(iface):
|
||||
logger.info("Monitor mode active: %s", iface)
|
||||
airmon_ok = True
|
||||
elif self._iface_exists(base_iface):
|
||||
logger.info("Interface %s is now in monitor mode (no rename)", base_iface)
|
||||
cfg['bifrost_iface'] = base_iface
|
||||
self._mon_iface = base_iface
|
||||
airmon_ok = True
|
||||
|
||||
if airmon_ok:
|
||||
return
|
||||
except FileNotFoundError:
|
||||
logger.warning("airmon-ng not found, trying iw fallback ...")
|
||||
except Exception as e:
|
||||
logger.warning("airmon-ng failed: %s, trying iw fallback ...", e)
|
||||
|
||||
# ── Strategy 3: iw (direct fallback) ──────────────────────
|
||||
try:
|
||||
subprocess.run(
|
||||
['ip', 'link', 'set', base_iface, 'down'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
result = subprocess.run(
|
||||
['iw', 'dev', base_iface, 'set', 'type', 'monitor'],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
err = result.stderr.strip()
|
||||
logger.error("iw set monitor failed (rc=%d): %s", result.returncode, err)
|
||||
self._monitor_failed = True
|
||||
subprocess.run(
|
||||
['ip', 'link', 'set', base_iface, 'up'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
return
|
||||
subprocess.run(
|
||||
['ip', 'link', 'set', base_iface, 'up'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
logger.info("Monitor mode set via iw on %s", base_iface)
|
||||
cfg['bifrost_iface'] = base_iface
|
||||
self._mon_iface = base_iface
|
||||
except Exception as e:
|
||||
logger.error("Failed to set monitor mode: %s", e)
|
||||
self._monitor_failed = True
|
||||
|
||||
def _setup_nexmon(self, base_iface, cfg):
|
||||
"""Enable monitor mode using nexmon (for Broadcom brcmfmac chips).
|
||||
|
||||
Creates a separate monitor interface (mon0) so wlan0 can potentially
|
||||
remain usable for management traffic (like pwnagotchi does).
|
||||
|
||||
Returns True on success, False on failure.
|
||||
"""
|
||||
mon_iface = 'mon0'
|
||||
phy = self._detect_phy(base_iface)
|
||||
logger.info("Nexmon: setting up monitor mode on %s (phy=%s)", base_iface, phy)
|
||||
|
||||
try:
|
||||
# Kill interfering services (same as pwnagotchi)
|
||||
for svc in ('wpa_supplicant', 'NetworkManager', 'dhcpcd'):
|
||||
subprocess.run(
|
||||
['systemctl', 'stop', svc],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
|
||||
# Remove old mon0 if it exists
|
||||
if self._iface_exists(mon_iface):
|
||||
subprocess.run(
|
||||
['iw', 'dev', mon_iface, 'del'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5,
|
||||
)
|
||||
|
||||
# Create monitor interface via iw phy
|
||||
result = subprocess.run(
|
||||
['iw', 'phy', phy, 'interface', 'add', mon_iface, 'type', 'monitor'],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.error("Failed to create %s: %s", mon_iface, result.stderr.strip())
|
||||
return False
|
||||
|
||||
# Bring monitor interface up
|
||||
subprocess.run(
|
||||
['ifconfig', mon_iface, 'up'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
|
||||
# Enable monitor mode with radiotap headers via nexutil
|
||||
result = subprocess.run(
|
||||
['nexutil', '-m2'],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.warning("nexutil -m2 returned rc=%d: %s", result.returncode, result.stderr.strip())
|
||||
|
||||
# Verify
|
||||
verify = subprocess.run(
|
||||
['nexutil', '-m'],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
mode_val = verify.stdout.strip()
|
||||
logger.info("nexutil -m reports: %s", mode_val)
|
||||
|
||||
if not self._iface_exists(mon_iface):
|
||||
logger.error("Monitor interface %s not created", mon_iface)
|
||||
return False
|
||||
|
||||
# Success — update config to use mon0
|
||||
cfg['bifrost_iface'] = mon_iface
|
||||
self._mon_iface = mon_iface
|
||||
self._nexmon_used = True
|
||||
logger.info("Nexmon monitor mode active on %s (phy=%s)", mon_iface, phy)
|
||||
return True
|
||||
|
||||
except FileNotFoundError as e:
|
||||
logger.error("Required tool not found: %s", e)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error("Nexmon setup error: %s", e)
|
||||
return False
|
||||
|
||||
def _teardown_monitor_mode(self):
|
||||
"""Restore the WiFi interface to managed mode (idempotent)."""
|
||||
if self._monitor_torn_down:
|
||||
return
|
||||
base_iface = getattr(self, '_base_iface', None)
|
||||
mon_iface = getattr(self, '_mon_iface', None)
|
||||
if not base_iface:
|
||||
return
|
||||
self._monitor_torn_down = True
|
||||
|
||||
logger.info("Restoring managed mode for %s ...", base_iface)
|
||||
|
||||
if getattr(self, '_nexmon_used', False):
|
||||
# ── Nexmon teardown ──
|
||||
try:
|
||||
subprocess.run(
|
||||
['nexutil', '-m0'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5,
|
||||
)
|
||||
logger.info("Nexmon monitor mode disabled (nexutil -m0)")
|
||||
except Exception:
|
||||
pass
|
||||
# Remove the mon0 interface
|
||||
if mon_iface and mon_iface != base_iface and self._iface_exists(mon_iface):
|
||||
try:
|
||||
subprocess.run(
|
||||
['iw', 'dev', mon_iface, 'del'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5,
|
||||
)
|
||||
logger.info("Removed monitor interface %s", mon_iface)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# ── airmon-ng / iw teardown ──
|
||||
try:
|
||||
iface_to_stop = mon_iface or base_iface
|
||||
subprocess.run(
|
||||
['airmon-ng', 'stop', iface_to_stop],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
timeout=15,
|
||||
)
|
||||
logger.info("Monitor mode stopped via airmon-ng")
|
||||
except FileNotFoundError:
|
||||
try:
|
||||
subprocess.run(
|
||||
['ip', 'link', 'set', base_iface, 'down'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
subprocess.run(
|
||||
['iw', 'dev', base_iface, 'set', 'type', 'managed'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
subprocess.run(
|
||||
['ip', 'link', 'set', base_iface, 'up'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
|
||||
)
|
||||
logger.info("Managed mode restored via iw on %s", base_iface)
|
||||
except Exception as e:
|
||||
logger.error("Failed to restore managed mode: %s", e)
|
||||
except Exception as e:
|
||||
logger.warning("airmon-ng stop failed: %s", e)
|
||||
|
||||
# Restart network services that were killed
|
||||
restarted = False
|
||||
for svc in ('wpa_supplicant', 'dhcpcd', 'NetworkManager'):
|
||||
try:
|
||||
subprocess.run(
|
||||
['systemctl', 'start', svc],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=15,
|
||||
)
|
||||
restarted = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Wait for network services to actually reconnect before handing
|
||||
# control back so the orchestrator doesn't scan a dead interface.
|
||||
if restarted:
|
||||
logger.info("Waiting for network services to reconnect ...")
|
||||
time.sleep(5)
|
||||
|
||||
@staticmethod
|
||||
def _iface_exists(iface_name):
|
||||
"""Check if a network interface exists."""
|
||||
return os.path.isdir('/sys/class/net/%s' % iface_name)
|
||||
|
||||
# ── Bettercap subprocess management ────────────────
|
||||
|
||||
def _start_bettercap(self):
|
||||
"""Spawn bettercap subprocess with REST API."""
|
||||
cfg = self.shared_data.config
|
||||
iface = cfg.get('bifrost_iface', 'wlan0mon')
|
||||
host = cfg.get('bifrost_bettercap_host', '127.0.0.1')
|
||||
port = str(cfg.get('bifrost_bettercap_port', 8081))
|
||||
user = cfg.get('bifrost_bettercap_user', 'user')
|
||||
password = cfg.get('bifrost_bettercap_pass', 'pass')
|
||||
|
||||
cmd = [
|
||||
'bettercap', '-iface', iface, '-no-colors',
|
||||
'-eval', 'set api.rest.address %s' % host,
|
||||
'-eval', 'set api.rest.port %s' % port,
|
||||
'-eval', 'set api.rest.username %s' % user,
|
||||
'-eval', 'set api.rest.password %s' % password,
|
||||
'-eval', 'api.rest on',
|
||||
]
|
||||
|
||||
logger.info("Starting bettercap: %s", ' '.join(cmd))
|
||||
try:
|
||||
self._bettercap_proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
logger.info("bettercap PID: %d", self._bettercap_proc.pid)
|
||||
except FileNotFoundError:
|
||||
logger.error("bettercap not found! Install with: apt install bettercap")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed to start bettercap: %s", e)
|
||||
raise
|
||||
|
||||
def _stop_bettercap(self):
|
||||
"""Kill the bettercap subprocess."""
|
||||
if self._bettercap_proc:
|
||||
try:
|
||||
self._bettercap_proc.terminate()
|
||||
self._bettercap_proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._bettercap_proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
self._bettercap_proc = None
|
||||
logger.info("bettercap stopped")
|
||||
|
||||
# ── Status for web API ────────────────────────────────
|
||||
|
||||
def get_status(self):
|
||||
"""Return full engine status for web API."""
|
||||
base = {
|
||||
'enabled': self.enabled,
|
||||
'running': self._running,
|
||||
'monitor_failed': self._monitor_failed,
|
||||
}
|
||||
if self.agent and self._running:
|
||||
base.update(self.agent.get_status())
|
||||
else:
|
||||
base.update({
|
||||
'mood': 'sleeping',
|
||||
'face': '(-.-) zzZ',
|
||||
'voice': '',
|
||||
'channel': 0,
|
||||
'num_aps': 0,
|
||||
'num_handshakes': 0,
|
||||
'uptime': 0,
|
||||
'epoch': 0,
|
||||
'mode': 'auto',
|
||||
'last_pwnd': '',
|
||||
'reward': 0,
|
||||
})
|
||||
return base
|
||||
Reference in New Issue
Block a user