scanservjs: init at 2.27.1; nixos/scanservjs: init (#249806)

Changed files
+315
nixos
doc
manual
release-notes
modules
services
hardware
pkgs
+2
nixos/doc/manual/release-notes/rl-2505.section.md
···
- [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).
+1
nixos/modules/module-list.nix
···
./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
+155
nixos/modules/services/hardware/scanservjs.nix
···
+
{
+
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;
+
};
+
};
+
};
+
}
+64
pkgs/by-name/sc/scanservjs/decouple-from-source-tree.patch
···
+
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);
+
+93
pkgs/by-name/sc/scanservjs/package.nix
···
+
{
+
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;
+
};
+
}