diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index a24092c84d6b..9e4781a8640a 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -16,6 +16,8 @@ - [Bazecor](https://github.com/Dygmalab/Bazecor), the graphical configurator for Dygma Products. +- [scanservjs](https://github.com/sbs20/scanservjs/), a web UI for SANE scanners. Available at [services.scanservjs](#opt-services.scanservjs.enable). + - [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](options.html#opt-services.kimai). - [Omnom](https://github.com/asciimoo/omnom), a webpage bookmarking and snapshotting service. Available as [services.omnom](options.html#opt-services.omnom.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c00465542af6..2f0e4fe6fbca 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -624,6 +624,7 @@ ./services/hardware/sane_extra_backends/brscan4.nix ./services/hardware/sane_extra_backends/brscan5.nix ./services/hardware/sane_extra_backends/dsseries.nix + ./services/hardware/scanservjs.nix ./services/hardware/spacenavd.nix ./services/hardware/supergfxd.nix ./services/hardware/tcsd.nix diff --git a/nixos/modules/services/hardware/scanservjs.nix b/nixos/modules/services/hardware/scanservjs.nix new file mode 100644 index 000000000000..7f361767c499 --- /dev/null +++ b/nixos/modules/services/hardware/scanservjs.nix @@ -0,0 +1,155 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.scanservjs; + settings = { + scanimage = lib.getExe' config.hardware.sane.backends-package "scanimage"; + convert = lib.getExe' pkgs.imagemagick "convert"; + tesseract = lib.getExe pkgs.tesseract; + # it defaults to config/devices.json, but "config" dir doesn't exist by default and scanservjs doesn't create it + devicesPath = "devices.json"; + } // cfg.settings; + settingsFormat = pkgs.formats.json { }; + + leafs = + attrs: + builtins.concatLists ( + lib.mapAttrsToList (k: v: if builtins.isAttrs v then leafs v else [ v ]) attrs + ); + + package = pkgs.scanservjs; + + configFile = pkgs.writeText "config.local.js" '' + /* eslint-disable no-unused-vars */ + module.exports = { + afterConfig(config) { + ${ + builtins.concatStringsSep "" ( + leafs ( + lib.mapAttrsRecursive (path: val: '' + ${builtins.concatStringsSep "." path} = ${builtins.toJSON val}; + '') { config = settings; } + ) + ) + } + ${cfg.extraConfig} + }, + + afterDevices(devices) { + ${cfg.extraDevicesConfig} + }, + + async afterScan(fileInfo) { + ${cfg.runAfterScan} + }, + + actions: [ + ${builtins.concatStringsSep ",\n" cfg.extraActions} + ], + }; + ''; + +in +{ + options.services.scanservjs = { + enable = lib.mkEnableOption "scanservjs"; + stateDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/scanservjs"; + description = '' + State directory for scanservjs. + ''; + }; + settings = lib.mkOption { + default = { }; + description = '' + Config to set in config.local.js's `afterConfig`. + ''; + type = lib.types.submodule { + freeformType = settingsFormat.type; + options.host = lib.mkOption { + type = lib.types.str; + description = "The IP to listen on."; + default = "127.0.0.1"; + }; + options.port = lib.mkOption { + type = lib.types.port; + description = "The port to listen on."; + default = 8080; + }; + }; + }; + extraConfig = lib.mkOption { + default = ""; + type = lib.types.lines; + description = '' + Extra code to add to config.local.js's `afterConfig`. + ''; + }; + extraDevicesConfig = lib.mkOption { + default = ""; + type = lib.types.lines; + description = '' + Extra code to add to config.local.js's `afterDevices`. + ''; + }; + runAfterScan = lib.mkOption { + default = ""; + type = lib.types.lines; + description = '' + Extra code to add to config.local.js's `afterScan`. + ''; + }; + extraActions = lib.mkOption { + default = [ ]; + type = lib.types.listOf lib.types.lines; + description = "Actions to add to config.local.js's `actions`."; + }; + }; + + config = lib.mkIf cfg.enable { + hardware.sane.enable = true; + users.users.scanservjs = { + group = "scanservjs"; + extraGroups = [ + "scanner" + "lp" + ]; + home = cfg.stateDir; + isSystemUser = true; + createHome = true; + }; + users.groups.scanservjs = { }; + + systemd.services.scanservjs = { + description = "scanservjs"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + # yes, those paths are configurable, but the config option isn't always used... + # a lot of the time scanservjs just takes those from PATH + path = with pkgs; [ + coreutils + config.hardware.sane.backends-package + imagemagick + tesseract + ]; + environment = { + NIX_SCANSERVJS_CONFIG_PATH = configFile; + SANE_CONFIG_DIR = "/etc/sane-config"; + LD_LIBRARY_PATH = "/etc/sane-libs"; + }; + serviceConfig = { + ExecStart = lib.getExe package; + Restart = "always"; + User = "scanservjs"; + Group = "scanservjs"; + WorkingDirectory = cfg.stateDir; + }; + }; + }; +} diff --git a/pkgs/by-name/sc/scanservjs/decouple-from-source-tree.patch b/pkgs/by-name/sc/scanservjs/decouple-from-source-tree.patch new file mode 100644 index 000000000000..d39da70e5c5f --- /dev/null +++ b/pkgs/by-name/sc/scanservjs/decouple-from-source-tree.patch @@ -0,0 +1,64 @@ +diff --git a/packages/server/src/api.js b/packages/server/src/api.js +index bd43842..71ce7c9 100644 +--- a/packages/server/src/api.js ++++ b/packages/server/src/api.js +@@ -105,7 +105,7 @@ module.exports = new class Api { + } + + // If not then it's possible the default image is not quite the correct aspect ratio +- const buffer = FileInfo.create(`${config.previewDirectory}/default.jpg`).toBuffer(); ++ const buffer = FileInfo.create('NIX_OUT_PLACEHOLDER/lib/node_modules/scanservjs-api/data/preview/default.jpg').toBuffer(); + + try { + // We need to know the correct aspect ratio from the device +diff --git a/packages/server/src/application.js b/packages/server/src/application.js +index 2771036..0c2a4c0 100644 +--- a/packages/server/src/application.js ++++ b/packages/server/src/application.js +@@ -26,7 +26,7 @@ module.exports = new class Application { + + userOptions() { + if (this._userOptions === null) { +- this._userOptions = new UserOptions('../../config/config.local.js'); ++ this._userOptions = new UserOptions(process.env.NIX_SCANSERVJS_CONFIG_PATH); + } + return this._userOptions; + } +diff --git a/packages/server/src/classes/user-options.js b/packages/server/src/classes/user-options.js +index f129e3c..c71e754 100644 +--- a/packages/server/src/classes/user-options.js ++++ b/packages/server/src/classes/user-options.js +@@ -4,7 +4,7 @@ const path = require('path'); + module.exports = class UserOptions { + constructor(localConfigPath) { + if (localConfigPath) { +- const localPath = path.join(__dirname, localConfigPath); ++ const localPath = localConfigPath; + if (fs.existsSync(localPath)) { + this.local = require(localPath); + } +diff --git a/packages/server/src/configure.js b/packages/server/src/configure.js +index c9e5ed8..484949c 100644 +--- a/packages/server/src/configure.js ++++ b/packages/server/src/configure.js +@@ -71,6 +71,7 @@ function initialize(rootPath) { + + try { + fs.mkdirSync(config.outputDirectory, { recursive: true }); ++ fs.mkdirSync(config.previewDirectory, { recursive: true }); + fs.mkdirSync(config.tempDirectory, { recursive: true }); + } catch (exception) { + log.warn(`Error ensuring output and temp directories exist: ${exception}`); +diff --git a/packages/server/src/server.js b/packages/server/src/server.js +index e1a9fb0..3d58d37 100644 +--- a/packages/server/src/server.js ++++ b/packages/server/src/server.js +@@ -5,7 +5,7 @@ const configure = require('./configure'); + const config = application.config(); + const app = express(); + +-app.use(express.static('client')); ++app.use(express.static('@client@')); + + configure(app); + diff --git a/pkgs/by-name/sc/scanservjs/package.nix b/pkgs/by-name/sc/scanservjs/package.nix new file mode 100644 index 000000000000..ccacd814a569 --- /dev/null +++ b/pkgs/by-name/sc/scanservjs/package.nix @@ -0,0 +1,93 @@ +{ + lib, + fetchFromGitHub, + buildNpmPackage, + fetchNpmDeps, + nodejs, + substituteAll, +}: + +let + version = "2.27.1"; + src = fetchFromGitHub { + owner = "sbs20"; + repo = "scanservjs"; + # rev = "v${version}"; + # 2.27.1 doesn't have a tag + rev = "b15adc6f97fb152fd9819371bb1a9b8118baf55b"; + hash = "sha256-ne9fEF/eurWPXzmJQzBn5jiy+JgxMWiCXsOdmu2fj6E="; + }; + + depsHashes = { + server = "sha256-M8t+TrE+ntZaI9X7hEel94bz34DPtW32n0KKMSoCfIs="; + client = "sha256-C31WBYE8ba0t4mfKFAuYWrCZtSdN7tQIYmCflDRKuBM="; + }; + + serverDepsForClient = fetchNpmDeps { + inherit src nodejs; + sourceRoot = "${src.name}/packages/server"; + name = "scanservjs"; + hash = depsHashes.server or lib.fakeHash; + }; + + # static client files + client = buildNpmPackage { + pname = "scanservjs-client"; + inherit version src nodejs; + + sourceRoot = "${src.name}/packages/client"; + npmDepsHash = depsHashes.client or lib.fakeHash; + + preBuild = '' + cd ../server + chmod +w package-lock.json . /build/source/ + npmDeps=${serverDepsForClient} npmConfigHook + cd ../client + ''; + + env.NODE_OPTIONS = "--openssl-legacy-provider"; + + dontNpmInstall = true; + installPhase = '' + mv /build/source/dist/client $out + ''; + }; + +in +buildNpmPackage { + pname = "scanservjs"; + inherit version src nodejs; + + sourceRoot = "${src.name}/packages/server"; + npmDepsHash = depsHashes.server or lib.fakeHash; + + # can't use "patches" since they change the server deps' hash for building the client + # (I don't want to maintain one more hash) + preBuild = '' + chmod +w /build/source + patch -p3 <${ + substituteAll { + src = ./decouple-from-source-tree.patch; + inherit client; + } + } + substituteInPlace src/api.js --replace 'NIX_OUT_PLACEHOLDER' "$out" + ''; + + postInstall = '' + mkdir -p $out/bin + makeWrapper ${lib.getExe nodejs} $out/bin/scanservjs \ + --set NODE_ENV production \ + --add-flags "'$out/lib/node_modules/scanservjs-api/src/server.js'" + ''; + + meta = { + description = "SANE scanner nodejs web ui"; + longDescription = "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation."; + homepage = "https://github.com/sbs20/scanservjs"; + license = lib.licenses.gpl2Only; + mainProgram = "scanservjs"; + maintainers = with lib.maintainers; [ chayleaf ]; + platforms = lib.platforms.linux; + }; +}