diff --git a/flake.nix b/flake.nix index 44e6145..b673c55 100644 --- a/flake.nix +++ b/flake.nix @@ -19,6 +19,15 @@ outputs = { self, nixpkgs, nixpkgs-unstable, sops-nix, ... }@inputs: let system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + pkgs-unstable = import nixpkgs-unstable { + inherit system; + config = { + allowUnfree = true; + permittedInsecurePackages = [ "olm-3.2.16" ]; + }; + }; + opencode = inputs.opencode.packages.${system}.default; in { # Pre-deploy checks: nix flake check checks.${system} = { @@ -27,6 +36,21 @@ # Verify VM config evaluates (lighter weight) ops-jrz1-vm-config = self.nixosConfigurations.ops-jrz1-vm.config.system.build.toplevel; + + # Shell script linting (errors and warnings) + shellcheck = pkgs.runCommand "shellcheck-scripts" { + nativeBuildInputs = [ pkgs.shellcheck ]; + src = ./scripts; + } '' + cd $src + shellcheck *.sh killswitch cpu-watchdog egress-watchdog egress-status + touch $out + ''; + + # VM integration test - boots VM and verifies services + vm-integration = import ./tests/vm-integration.nix { + inherit pkgs pkgs-unstable opencode; + }; }; nixosConfigurations = { diff --git a/hosts/ops-jrz1-vm.nix b/hosts/ops-jrz1-vm.nix index 8f338fc..3abd548 100644 --- a/hosts/ops-jrz1-vm.nix +++ b/hosts/ops-jrz1-vm.nix @@ -65,7 +65,7 @@ services.openssh.settings.PasswordAuthentication = lib.mkForce true; # VM-specific: Simple root password for testing - users.users.root.password = "test"; + users.users.root.initialHashedPassword = ""; # Empty password for VM testing # VM-specific: More permissive firewall for testing networking.firewall = { diff --git a/scripts/sanitize-files.sh b/scripts/sanitize-files.sh index 07cdd2d..ca82211 100755 --- a/scripts/sanitize-files.sh +++ b/scripts/sanitize-files.sh @@ -11,15 +11,9 @@ set -euo pipefail # Colors for output -RED='\033[0;31m' GREEN='\033[0;32m' -YELLOW='\033[1;33m' NC='\033[0m' # No Color -# Script directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - # Check arguments if [ $# -ne 2 ]; then echo "Usage: $0 " diff --git a/tests/vm-integration.nix b/tests/vm-integration.nix new file mode 100644 index 0000000..5948883 --- /dev/null +++ b/tests/vm-integration.nix @@ -0,0 +1,148 @@ +# NixOS VM integration test for ops-jrz1 +# Boots a VM and verifies core services start correctly +# +# Run with: nix flake check (as part of checks) +# Or standalone: nix build .#checks.x86_64-linux.vm-integration + +{ pkgs, pkgs-unstable, opencode }: + +pkgs.nixosTest { + name = "ops-jrz1-integration"; + # Type checker doesn't know about injected node variables + skipTypeCheck = true; + # Linter (pyflakes) can't see dynamically injected node names like 'server' + skipLint = true; + + nodes.server = { lib, ... }: { + # Disable built-in NixOS maubot module to use our custom version + disabledModules = [ "services/matrix/maubot.nix" ]; + + imports = [ + ../modules/matrix-continuwuity.nix + ../modules/mautrix-slack.nix + ../modules/mautrix-whatsapp.nix + ../modules/mautrix-gmessages.nix + ../modules/maubot.nix + ../modules/dev-services.nix + ../modules/security/fail2ban.nix + ../modules/security/ssh-hardening.nix + ]; + + # Provide pkgs-unstable to modules that need it + _module.args = { + inherit pkgs-unstable opencode; + }; + + # VM-specific settings + networking.hostName = "ops-jrz1-vm"; + + # Local DNS resolver for conduwuit (VM sandbox has no external network) + services.dnsmasq = { + enable = true; + settings = { + # Resolve only specific test hostnames (more explicit than wildcard) + address = [ + "/matrix.example.org/127.0.0.1" + "/example.org/127.0.0.1" + ]; + no-resolv = true; + no-hosts = false; + }; + }; + networking.nameservers = lib.mkForce [ "127.0.0.1" ]; + + # Enable Matrix homeserver + services.matrix-homeserver = { + enable = true; + domain = "matrix.example.org"; + port = 8008; + enableRegistration = false; # Disabled - conduwuit refuses open registration + enableFederation = false; + }; + + # Enable Slack bridge + services.mautrix-slack = { + enable = true; + matrix = { + homeserverUrl = "http://127.0.0.1:8008"; + serverName = "matrix.example.org"; + }; + bridge.permissions = { + "matrix.example.org" = "user"; + "@admin:matrix.example.org" = "admin"; + }; + }; + + # PostgreSQL for bridge databases + services.postgresql = { + enable = true; + ensureDatabases = [ "mautrix_slack" ]; + ensureUsers = [{ + name = "mautrix_slack"; + ensureDBOwnership = true; + }]; + }; + + # Configure nginx directly for VM testing (plain HTTP, no ACME) + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts."matrix.example.org" = { + locations."/_matrix" = { + proxyPass = "http://127.0.0.1:8008"; + proxyWebsockets = true; + }; + locations."/.well-known/matrix" = { + proxyPass = "http://127.0.0.1:8008"; + }; + }; + }; + + # VM testing settings + services.openssh.settings.PasswordAuthentication = lib.mkForce true; + users.users.root.initialHashedPassword = ""; # Empty password for VM test (silences warning) + + networking.firewall = { + enable = true; + allowedTCPPorts = [ 22 80 443 8008 3000 ]; + }; + + # Required for VM + virtualisation.memorySize = 2048; + }; + + testScript = '' + # NixOS test framework uses hostname (with hyphens->underscores) as Python variable + machine = ops_jrz1_vm + + machine.start() + machine.wait_for_unit("multi-user.target") + + # DNS must be up first (conduwuit needs it) + machine.wait_for_unit("dnsmasq.service") + + # Test PostgreSQL + machine.wait_for_unit("postgresql.service") + machine.succeed("sudo -u postgres psql -c 'SELECT 1'") + + # Test Matrix homeserver (continuwuity/conduwuit) + machine.wait_for_unit("continuwuity.service", timeout=60) + machine.wait_for_open_port(8008) + machine.succeed("curl -sf http://localhost:8008/_matrix/client/versions | grep -q versions") + + # Verify Matrix API returns valid JSON with versions array + machine.succeed("curl -sf http://localhost:8008/_matrix/client/versions | grep -q 'v1.'") + + # Test nginx reverse proxy (plain HTTP) + machine.wait_for_unit("nginx.service") + machine.wait_for_open_port(80) + + # Test nginx proxies to Matrix homeserver correctly + machine.succeed("curl -sf http://matrix.example.org/_matrix/client/versions | grep -q versions") + + # Verify nginx adds forwarding headers (X-Forwarded-For should be set) + machine.succeed("curl -sf -H 'Host: matrix.example.org' http://127.0.0.1/_matrix/client/versions | grep -q versions") + + machine.log("All core services started successfully!") + ''; +}