From 18996cdd784b1317664359fca3c98ab86e45bc9c Mon Sep 17 00:00:00 2001 From: Tilman Baumann Date: Thu, 8 Jan 2026 13:56:31 +0100 Subject: [PATCH] Adding webviewer nixos option and improving tests Use with services.meshcore-bot = { enable = true; webviewer.enable = true; } --- README.md | 23 +++++++++++++ flake.lock | 11 ++++--- nix/nixos-module.nix | 76 +++++++++++++++++++++++++++++++++++++++++++ nix/nixos-test.nix | 77 +++++++++++++------------------------------- 4 files changed, 127 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 2b5a92f..622cc1d 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/flake.lock b/flake.lock index 51b0edc..b7a0f5e 100644 --- a/flake.lock +++ b/flake.lock @@ -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": { diff --git a/nix/nixos-module.nix b/nix/nixos-module.nix index e162d85..45f300c 100644 --- a/nix/nixos-module.nix +++ b/nix/nixos-module.nix @@ -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"; + }; + }; }; }; } diff --git a/nix/nixos-test.nix b/nix/nixos-test.nix index d47b0e2..bf8d894 100644 --- a/nix/nixos-test.nix +++ b/nix/nixos-test.nix @@ -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") ''; }; };