Adding webviewer nixos option and improving tests

Use with
services.meshcore-bot = {
  enable = true;
  webviewer.enable = true;
 }
This commit is contained in:
Tilman Baumann committed 2026-01-08 19:17:18 +01:00
1 parent ae45d1e95b
commit 18996cdd78
4 files changed
+127 -60

No files matched your search

+23
View File
@@ -76,6 +76,29 @@ sudo systemctl status meshcore-bot
See [SERVICE-INSTALLATION.md](SERVICE-INSTALLATION.md) for detailed service installation instructions.
## NixOS
Use the Nix flake via flake.nix
```nix
meshcore-bot.url = "github:agessaman/meshcore-bot/";
```
And in your system config
```nix
{
imports = [inputs.meshcore-bot.nixosModules.default];
services.meshcore-bot = {
enable = true;
webviewer.enable = true;
settings = {
Connection.connection_type = "serial";
Connection.serial_port = "/dev/ttyUSB0";
Bot.bot_name = "MyBot";
};
};
}
```
## Configuration
The bot uses `config.ini` for all settings. Key configuration sections:
Generated
+6 -5
View File
@@ -5,16 +5,17 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1765835352,
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
"lastModified": 1767609335,
"narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
"rev": "250481aafeb741edfe23d29195671c19b36b6dca",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
+76
View File
@@ -47,6 +47,22 @@
description = "The meshcore-bot package to use.";
};
webviewer = {
enable = lib.mkEnableOption "MeshCore Bot Web Viewer service";
host = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Host to bind the web viewer to.";
};
port = lib.mkOption {
type = lib.types.int;
default = 8080;
description = "Port to bind the web viewer to.";
};
};
settings = lib.mkOption {
type = lib.types.attrsOf (lib.types.attrsOf lib.types.anything);
default = {};
@@ -85,6 +101,10 @@
};
Web_Viewer = {
enabled = false;
db_path = "${cfg.dataDir}/meshcore-bot.db";
auto_start = false; #We start it in a seperate unit
host = cfg.webviewer.host;
port = cfg.webviewer.port;
};
};
in
@@ -173,6 +193,62 @@
# Install package data (translations, templates, etc.)
environment.systemPackages = [cfg.package];
# Web viewer service
systemd.services.meshcore-bot-viewer = lib.mkIf cfg.webviewer.enable {
description = "MeshCore Bot Web Viewer - Web interface for MeshCore Bot";
after = ["network.target" "meshcore-bot.service"];
wantedBy = ["multi-user.target"];
# Service configuration
serviceConfig = {
Type = "simple";
User = "meshcore-bot";
Group = "meshcore-bot";
# Working directory - systemd will create it automatically
WorkingDirectory = cfg.dataDir;
# StateDirectory creates /var/lib/meshcore-bot with correct ownership
# This is the NixOS/systemd way to manage service state directories
StateDirectory = "meshcore-bot";
StateDirectoryMode = "0750";
# LogsDirectory creates /var/log/meshcore-bot with correct ownership
LogsDirectory = "meshcore-bot";
LogsDirectoryMode = "0750";
# Start command
ExecStart = "${cfg.package}/bin/meshcore-viewer --host ${cfg.webviewer.host} --port ${toString cfg.webviewer.port} --config ${configFile}";
# Restart policy
Restart = "on-failure";
RestartSec = "10s";
# Security hardening
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
# Additional hardening
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictNamespaces = true;
LockPersonality = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
PrivateMounts = true;
};
# Environment
environment = {
PYTHONUNBUFFERED = "1";
};
};
};
};
}
+22 -55
View File
@@ -39,6 +39,9 @@
};
};
};
environment.systemPackages = [
pkgs.sqlite
];
};
testScript = ''
@@ -66,50 +69,28 @@
# The database is created during bot initialization in DBManager.__init__
# If the bot started successfully, the database should exist
print("\n=== File Checks ===")
# Check if service directory exists and has correct permissions
machine.succeed("test -d /var/lib/meshcore-bot")
machine.succeed("stat -c '%U:%G' /var/lib/meshcore-bot | grep -q meshcore-bot:meshcore-bot")
print("✓ Data directory exists with correct ownership")
# Check if database file exists (non-fatal check)
# The database is created during MeshCoreBot.__init__ -> DBManager.__init__
# If bot fails before initialization, DB won't exist - that's OK for this test
print("Checking for database file...")
# Use a command that always succeeds to check file existence
db_result = machine.succeed("ls /var/lib/meshcore-bot/meshcore-bot.db 2>/dev/null && echo 'exists' || echo 'missing'")
if "exists" in db_result:
print("✓ Database file created at /var/lib/meshcore-bot/meshcore-bot.db")
# Verify it's a valid SQLite database
machine.succeed("sqlite3 /var/lib/meshcore-bot/meshcore-bot.db 'SELECT 1' > /dev/null 2>&1")
print("✓ Database file is valid SQLite database")
else:
# Database not created - this is OK if bot failed early
# The important thing for NixOS module test is service structure
print("⚠ Database file not found (bot may have failed before DB initialization)")
# Check if there are critical errors (import failures, etc.)
critical_errors = machine.succeed("journalctl -u meshcore-bot.service | grep -iE 'import.*error|module.*not found|no module named|traceback' | head -3 || echo 'none'")
critical_errors_clean = critical_errors.strip()
if "none" not in critical_errors and len(critical_errors_clean) > 10:
print(f"⚠ Critical errors found: {critical_errors_clean[:150]}")
else:
print("✓ No critical import/initialization errors (connection failures are expected)")
machine.succeed("test -f /var/lib/meshcore-bot/meshcore-bot.db")
print("✓ Database file created at /var/lib/meshcore-bot/meshcore-bot.db")
# Verify it's a valid SQLite database
machine.succeed("sqlite3 /var/lib/meshcore-bot/meshcore-bot.db 'SELECT 1' > /dev/null ")
print("✓ Database file is valid SQLite database")
# Check if log file is created in the correct location (non-fatal)
# Log file is created by the bot's logging setup, may not exist if bot fails early
print("Checking for log file...")
log_result = machine.succeed("ls /var/log/meshcore-bot/meshcore-bot.log 2>/dev/null && echo 'exists' || echo 'missing'")
if "exists" in log_result:
print("✓ Log file created at /var/log/meshcore-bot/meshcore-bot.log")
else:
# Log file not created - check if logging directory exists
log_dir_check = machine.succeed("test -d /var/log/meshcore-bot && echo 'exists' || echo 'missing'")
if "exists" in log_dir_check:
print("⚠ Log file not found (bot may have failed before logging setup)")
print("✓ Log directory exists with correct permissions")
else:
print("⚠ Log directory not found")
machine.succeed("test -f /var/log/meshcore-bot/meshcore-bot.log")
print("✓ Log file created at /var/log/meshcore-bot/meshcore-bot.log")
# Check if config was generated
machine.succeed("test -f /nix/store/*-meshcore-bot-config.ini")
@@ -125,11 +106,8 @@
print("✓ User meshcore-bot is in dialout group")
# Verify it tried to connect via TCP (if bot got that far)
tcp_attempt = machine.succeed("journalctl -u meshcore-bot.service | grep -qi 'tcp\|localhost\|connection' && echo 'yes' || echo 'no'")
if "yes" in tcp_attempt:
print("✓ Bot attempted TCP connection")
else:
print("⚠ No TCP connection attempt found (bot may have failed earlier)")
machine.succeed("journalctl -u meshcore-bot.service | grep -qi 'tcp\|localhost\|connection'")
print("✓ Bot attempted TCP connection")
'';
};
@@ -141,6 +119,7 @@
services.meshcore-bot = {
enable = true;
webviewer.enable = true;
settings = {
Connection = {
connection_type = "tcp";
@@ -153,11 +132,7 @@
enabled = true;
};
Web_Viewer = {
enabled = true;
host = "127.0.0.1";
port = 8080;
debug = false;
auto_start = true;
};
Logging = {
log_level = "DEBUG";
@@ -167,29 +142,21 @@
};
testScript = ''
machine.wait_for_unit("meshcore-bot.service")
machine.wait_for_unit("meshcore-bot-viewer.service")
# Give it time to start and initialize web viewer
machine.sleep(10)
# Check logs show the bot loaded the webviewer plugin
machine.succeed("journalctl -u meshcore-bot.service | grep -q 'Successfully loaded plugin: webviewer'")
machine.sleep(5)
# FIXME meshcore-bot doesn't open the port.
# probably because it never connected to it's companion socket.
# I don't want to mock it.
# Wait for web viewer to start listening on port 8080
# This is the NixOS test framework way to check if a port is open
#print("\n=== Web Viewer Port Check ===")
#machine.wait_for_open_port(8080, timeout=30)
#print("✓ Web viewer is listening on port 8080")
print("\n=== Web Viewer Port Check ===")
machine.wait_for_open_port(8080, timeout=30)
print("✓ Web viewer is listening on port 8080")
# Verify we can connect to the web viewer
#machine.succeed("curl -f http://127.0.0.1:8080/ || curl -f http://localhost:8080/")
#print("✓ Web viewer responds to HTTP requests")
machine.succeed("curl -f http://127.0.0.1:8080/")
print("✓ Web viewer responds to HTTP requests")
# Check logs for web viewer startup
machine.succeed("journalctl -u meshcore-bot.service | grep -qi 'web.*viewer' || journalctl -u meshcore-bot.service | grep -qi 'flask'")
print("✓ Web viewer startup logged")
'';
};
};