diff --git a/.gitignore b/.gitignore index 27012f2..a024d28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# Rust /target + +# Nix result result-* + +# Nix VM +*.qcow2 diff --git a/flake.nix b/flake.nix index 1742d11..6599704 100644 --- a/flake.nix +++ b/flake.nix @@ -32,6 +32,20 @@ }; in f system pkgs toolchain); in { + apps = let + mkApp = program: description: { + type = "app"; + program = toString program; + meta = { + inherit description; + }; + }; + mkVm = name: mkApp "${self.nixosConfigurations.${name}.config.system.build.vm}/bin/run-nixos-vm"; + in forAllSystems (system: pkgs: _: { + default = self.apps.${system}.vm; + vm = mkVm "vm" "Start a NixOS VM with bro installed"; + }); + devShells = forAllSystems (system: pkgs: toolchain: { default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ @@ -53,6 +67,15 @@ }; }; + nixosModules = { + default = self.nixosModules.bro; + bro = import ./nix/module.nix; + }; + + nixosConfigurations = { + vm = import ./nix/vm.nix { inherit self nixpkgs; }; + }; + packages = forAllSystems (system: pkgs: _: let cargoToml = fromTOML (builtins.readFile ./Cargo.toml); diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..609281b --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,169 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.bro; +in +{ + options.services.bro = { + enable = lib.mkEnableOption ""; + + package = lib.mkPackageOption pkgs "bro" { }; + + instances = lib.mkOption { + default = { }; + type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: { + options = { + enable = lib.mkEnableOption ""; + + client = { + logLevel = lib.mkOption { + type = lib.types.nullOr (lib.types.enum [ "trace" "debug" "info" "warn" "error" ]); + default = null; + }; + + settings = lib.mkOption { + default = { }; + type = lib.types.submodule { + freeformType = with lib.types; oneOf [ str bool ]; + options = { + BRO_SOCKET_PATH = lib.mkOption { + type = lib.types.path; + default = "/run/bro/${name}.sock"; + defaultText = lib.literalExpression "\"/run/bro/${name}.sock\""; + }; + + BRO_FORWARD_ENV = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = config.server.settings.allowed-env; + example = [ "LS_COLORS" "TIME_STYLE" "QUOTING_STYLE" ]; + }; + + BRO_FILE_FLAGS = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "-f" "--file" ]; + }; + + BRO_FILE_ARGS = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + }; + + BRO_CAPTURE_TTY_STDIN = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + }; + }; + }; + }; + + programName = lib.mkOption { + type = lib.types.str; + default = lib.baseNameOf config.server.settings.executable; + defaultText = lib.literalExpression "lib.baseNameOf config.services.bro.instances..server.settings.executable"; + }; + + package = lib.mkOption { + type = lib.types.package; + readOnly = true; + default = ( + lib.warnIf + (!cfg.enable || !config.enable) + "Bro wrapper for instance '${name}' should not be used unless it is enabled." + (pkgs.writeShellApplication { + name = config.client.programName; + runtimeEnv = lib.pipe config.client.settings [ + (lib.mapAttrs (_: v: if lib.isBool v then (if v == true then "1" else null) else v)) + (lib.mapAttrs (_: v: if lib.isList v then (if v == [ ] then null else lib.concatStringsSep "," v) else v)) + (env: env // { + RUST_LOG = config.client.logLevel; + }) + (lib.filterAttrs (_: v: v != null)) + ]; + text = ''exec ${lib.getExe' cfg.package "bro-client"} "$@"''; + })).overrideAttrs { name = "bro-${name}-wrapper"; }; + }; + }; + + server = { + listenStreams = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "/run/bro/${name}.sock" ]; + }; + + logLevel = lib.mkOption { + type = lib.types.nullOr (lib.types.enum [ "trace" "debug" "info" "warn" "error" ]); + default = "info"; + }; + + settings = lib.mkOption { + default = { }; + type = lib.types.submodule { + freeformType = lib.types.str; + options = { + executable = lib.mkOption { + type = lib.types.str; + }; + + fd-passing = lib.mkEnableOption "" // { + default = true; + }; + + allowed-env = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "LS_COLORS" "TIME_STYLE" "QUOTING_STYLE" ]; + }; + + inherit-env = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "LS_COLORS" "TIME_STYLE" "QUOTING_STYLE" ]; + }; + }; + }; + }; + }; + }; + })); + }; + }; + + config = lib.mkIf cfg.enable { + systemd = { + sockets = lib.mapAttrs' (name: instance: { + name = "bro-${name}"; + value = { + wantedBy = [ "sockets.target" ]; + listenStreams = instance.server.listenStreams; + socketConfig = { + AcceptFileDescriptors = lib.mkIf instance.server.settings.fd-passing true; + }; + }; + }) (lib.filterAttrs (name: instance: instance.enable) cfg.instances); + + services = lib.mapAttrs' (name: instance: { + name = "bro-${name}"; + value = { + environment.RUST_LOG = lib.mkIf (instance.server.logLevel != null) instance.server.logLevel; + serviceConfig = { + Type = "notify-reload"; + ExecStart = let + args = lib.pipe instance.server.settings [ + (lib.filterAttrs (_: v: v != [ ])) + (lib.mapAttrs (_: v: if lib.isList v then lib.concatStringsSep "," v else v)) + (settings: settings // { + systemd-socket = true; + }) + (lib.cli.toCommandLineShellGNU { }) + ]; + in "${lib.getExe' cfg.package "bro-server"} ${args}"; + Restart = "on-failure"; + RestartSec = "5s"; + }; + }; + }) (lib.filterAttrs (name: instance: instance.enable) cfg.instances); + }; + }; +} diff --git a/nix/vm.nix b/nix/vm.nix new file mode 100644 index 0000000..529b56a --- /dev/null +++ b/nix/vm.nix @@ -0,0 +1,69 @@ +{ self, nixpkgs }: +nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ + self.overlays.bro + ]; + }; + modules = [ + "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" + "${nixpkgs}/nixos/tests/common/user-account.nix" + + self.nixosModules.default + + ({ config, lib, pkgs, ... }: { + system.stateVersion = config.system.nixos.release; + virtualisation.graphics = false; + + services.getty.autologinUser = "alice"; + + users = { + users.alice.extraGroups = [ + "wheel" + "systemd-journal" + ]; + extraUsers.root.password = "root"; + motd = '' + ================================= + Welcome to the bro vm! + + Password for alice is 'foobar' + Password for root is 'root' + + To exit, press Ctrl+A, then X + ================================= + ''; + }; + + services.bro = { + enable = true; + + instances.ls = { + enable = true; + + client = { + settings.BRO_FILE_ARGS = true; + }; + + server = { + settings = { + executable = lib.getExe' pkgs.coreutils "ls"; + allowed-env = [ "LS_COLORS" "TIME_STYLE" "QUOTING_STYLE" ]; + }; + }; + }; + }; + + environment.systemPackages = [ + config.services.bro.instances.ls.client.package + ]; + + programs.vim = { + enable = true; + defaultEditor = true; + }; + }) + ]; +}