diff --git a/common/configuration.nix b/common/configuration.nix index 44923a7..71002a0 100644 --- a/common/configuration.nix +++ b/common/configuration.nix @@ -1,5 +1,9 @@ {lib, pkgs, ...}: { - imports = [ ./ssh.nix ./airsane ]; + imports = [ + ./ssh.nix + ./airsane + ./homebox + ]; time.timeZone = "Europe/Moscow"; i18n.defaultLocale = "en_US.UTF-8"; diff --git a/common/homebox/default.nix b/common/homebox/default.nix new file mode 100644 index 0000000..9308f9b --- /dev/null +++ b/common/homebox/default.nix @@ -0,0 +1,223 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.homebox; + inherit (lib) types; + toString' = x: if builtins.isBool x then lib.boolToString x else toString x; + escapeSystemdArg = s: "\"${lib.escape [ "\"" ] s}\""; + attrsToArgs = lib.flip lib.pipe [ + (lib.filterAttrs (_: x: x != null)) + (lib.mapAttrs (_: toString')) + (lib.mapAttrsToList (name: value: "--${name}=${value}")) + ]; +in +{ + options.services.homebox = { + enable = lib.mkEnableOption "homebox"; + package = lib.mkPackageOption pkgs "homebox" { }; + settings = { + mode = lib.mkOption { + default = "production"; + type = types.enum [ + "development" + "production" + ]; + example = "development"; + }; + web = { + port = lib.mkOption { + default = 7745; + type = types.port; + }; + host = lib.mkOption { + default = null; + type = types.nullOr types.str; + }; + max-upload-size = lib.mkOption { + default = 10; + type = types.ints.positive; + }; + timeout = { + read = lib.mkOption { + default = 10; + type = types.ints.positive; + }; + write = lib.mkOption { + default = 10; + type = types.ints.positive; + }; + idle = lib.mkOption { + default = 30; + type = types.ints.positive; + }; + }; + }; + allow-registration = lib.mkOption { + default = true; + type = types.bool; + example = false; + }; + auto-increment-asset-id = lib.mkOption { + default = true; + type = types.bool; + example = false; + }; + currency-config = lib.mkOption { + type = types.nullOr types.path; + default = null; + }; + storage = lib.mkOption { + type = types.path; + default = "/var/lib/homebox/"; + }; + sqlite-url = lib.mkOption { + type = types.str; + default = "/var/lib/homebox/homebox.db?_fk=1"; + }; + log = { + level = lib.mkOption { + default = "info"; + type = types.enum [ + "trace" + "debug" + "info" + "warn" + "error" + "critical" + ]; + }; + format = lib.mkOption { + default = "text"; + type = types.enum [ + "text" + "json" + ]; + }; + }; + mailer = { + enable = lib.mkEnableOption "homebox.mailer"; + port = lib.mkOption { + default = 587; + type = types.port; + }; + host = lib.mkOption { type = types.str; }; + }; + swagger = { + enable = lib.mkEnableOption "homebox.swagger"; + host = lib.mkOption { default = "localhost"; }; + port = lib.mkOption { + default = 7745; + type = types.port; + }; + schema = lib.mkOption { + type = lib.enum [ + "http" + "https" + ]; + }; + }; + debug = { + enable = lib.mkEnableOption "homebox.debug"; + port = lib.mkOption { + default = 4000; + type = types.port; + }; + }; + }; + environmentFiles = lib.mkOption { + type = with lib.types; listOf path; + default = [ ]; + example = [ "/root/homebox.env" ]; + description = lib.mdDoc '' + File to load environment variables + from. This is helpful for specifying secrets. + Example content of environmentFile: + ``` + HBOX_MAILER_USERNAME=homebox@example.com + HBOX_MAILER_PASSWORD=password + ``` + ''; + }; + }; + + config = lib.mkIf cfg.enable { + nixpkgs.overlays = [ (final: prev: { homebox = final.callPackage ./package.nix { }; }) ]; + systemd.services = { + homebox = { + description = "Homebox Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + DynamicUser = true; + WorkingDirectory = "%S/homebox"; + StateDirectory = "homebox"; + StateDirectoryMode = "0700"; + UMask = "0007"; + ConfigurationDirectory = "homebox"; + EnvironmentFile = cfg.environmentFiles; + ExecStart = lib.concatMapStringsSep " " escapeSystemdArg ( + lib.singleton "${cfg.package}/bin/api" + ++ attrsToArgs ( + { + inherit (cfg.settings) mode; + web-port = cfg.settings.web.port; + web-host = cfg.settings.web.host; + web-max-upload-size = cfg.settings.web.max-upload-size; + storage-data = cfg.settings.storage; + storage-sqlite-url = cfg.settings.sqlite-url; + log-level = cfg.settings.log.level; + log-format = cfg.settings.log.format; + options-allow-registration = cfg.settings.allow-registration; + options-auto-increment-asset-id = cfg.settings.auto-increment-asset-id; + options-currency-config = cfg.settings.currency-config; + } + // lib.optionalAttrs cfg.settings.mailer.enable { + mailer-host = cfg.settings.mailer.host; + mailer-port = cfg.settings.mailer.port; + } + // lib.optionalAttrs cfg.settings.swagger.enable { + swagger-host = "${cfg.settings.swagger.host}:${cfg.settings.swagger.port}"; + swagger-scheme = cfg.settings.swagger.schema; + } + // lib.optionalAttrs cfg.settings.debug.enable { + debug-enabled = cfg.settings.debug.enable; + debug-port = cfg.settings.debug.port; + } + ) + ); + Restart = "on-failure"; + RestartSec = 15; + CapabilityBoundingSet = ""; + # Security + NoNewPrivileges = true; + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; + SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap"; + }; + }; + }; + }; +} diff --git a/common/homebox/package.nix b/common/homebox/package.nix new file mode 100644 index 0000000..9216634 --- /dev/null +++ b/common/homebox/package.nix @@ -0,0 +1,117 @@ +{ lib +, buildGoModule +, fetchFromGitHub +, stdenvNoCC +, jq +, moreutils +, nodePackages +, stdenv +, esbuild +, cacert +}: +let + pname = "homebox"; + version = "0.11.0"; + + src = fetchFromGitHub { + owner = "sysadminsmedia"; + repo = "homebox"; + rev = "v${version}"; + hash = "sha256-Xbhk9zbtuYLxToiLcgZm0DPBma5KeLOH3Fi3va9oAAU="; + }; + + pnpm-deps = stdenvNoCC.mkDerivation { + pname = "${pname}-pnpm-deps"; + src = "${src}/frontend"; + inherit version; + + nativeBuildInputs = [ + jq + moreutils + nodePackages.pnpm + cacert + ]; + + installPhase = '' + export HOME=$(mktemp -d) + pnpm config set store-dir $out + # use --ignore-script and --no-optional to avoid downloading binaries + # use --frozen-lockfile to avoid checking git deps + pnpm install --frozen-lockfile --no-optional --ignore-script + + # Remove timestamp and sort the json files + rm -rf $out/v3/tmp + for f in $(find $out -name "*.json"); do + sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f + jq --sort-keys . $f | sponge $f + done + ''; + + dontFixup = true; + outputHashMode = "recursive"; + outputHash = "sha256-CZP3rGLTHgFErskllqV2KEt3qEIN17cA8P+XSWBps44="; + }; + + frontend = stdenv.mkDerivation { + pname = "${pname}-frontend"; + src = "${src}/frontend"; + inherit version; + + nativeBuildInputs = [ + nodePackages.pnpm + ]; + + ESBUILD_BINARY_PATH = "${lib.getExe (esbuild.override { + buildGoModule = args: buildGoModule (args // rec { + version = "0.17.19"; + src = fetchFromGitHub { + owner = "evanw"; + repo = "esbuild"; + rev = "v${version}"; + hash = "sha256-PLC7OJLSOiDq4OjvrdfCawZPfbfuZix4Waopzrj8qsU="; + }; + vendorHash = "sha256-+BfxCyg0KkDQpHt/wycy/8CTG6YBA/VJvJFhhzUnSiQ="; + }); + })}"; + + preBuild = '' + export HOME=$(mktemp -d) + pnpm config set store-dir ${pnpm-deps} + pnpm install --offline --frozen-lockfile --no-optional --ignore-script --shamefully-hoist + + chmod -R +w ./node_modules + patchShebangs node_modules + NUXT_TELEMETRY_DISABLED=1 pnpm build + ''; + + installPhase = '' + runHook preInstall + + mv .output $out + + runHook postInstall + ''; + }; +in +buildGoModule { + inherit pname version; + src = "${src}/backend"; + + vendorHash = "sha256-Ju4w7Q4xeh7zRLPfhjemEbt/6EOdnaBT3VtCnUCv+D0="; + + passthru = { inherit frontend; }; + + preBuild = '' + mkdir -p app/api/static + cp -R ${frontend}/public app/api/static + ''; + + meta = with lib; { + description = "A inventory and organization system built for the Home User"; + homepage = "https://homebox.sysadminsmedia.com"; + license = licenses.agpl3Only; + maintainers = with maintainers; [ janik ]; + mainProgram = "api"; + platforms = platforms.all; + }; +} diff --git a/nodes/undef/configuration.nix b/nodes/undef/configuration.nix index e70da2a..79caeae 100644 --- a/nodes/undef/configuration.nix +++ b/nodes/undef/configuration.nix @@ -7,6 +7,7 @@ ./printing.nix ./jukebox.nix ./pipewire.nix + ./homebox.nix # inputs.tg-bot.nixosModule.x86_64-linux # ./wg.nix ]; diff --git a/nodes/undef/homebox.nix b/nodes/undef/homebox.nix new file mode 100644 index 0000000..d07f42a --- /dev/null +++ b/nodes/undef/homebox.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + services.homebox = { + enable = true; + settings.log.format = "json"; + settings.allow-registration = false; + }; +}