Compare commits

..

2 Commits

Author SHA1 Message Date
14be017c64
modules/betanin: support settings 2023-09-27 15:00:25 +10:00
2c1b5ceb00
modules/betanin: init 2023-09-27 15:00:18 +10:00
4 changed files with 182 additions and 198 deletions

View File

@ -1,36 +1,21 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
inherit (lib) mkIf mkOption optionalAttrs optionalString types; inherit (lib) mkIf mkOption optionalAttrs types;
cfg = config.services.betanin; cfg = config.services.betanin;
defaultUser = "betanin"; defaultUser = "betanin";
defaultGroup = "betanin"; defaultGroup = "betanin";
defaultSettings = {
notifications = {
services = { };
strings = {
title = "[betanin] torrent `$name` $status";
body = "@ $time. view/use the console at http://127.0.0.1:${toString cfg.port}/$console_path";
};
};
};
finalSettings = finalSettings =
let let
base = lib.filterAttrsRecursive (n: _: !(lib.hasSuffix "_file" n)) cfg.settings; base = lib.filterAttrsRecursive (n: _: lib.hasSuffix "_file" n) cfg.settings;
clean = { clean = {
frontend.password = frontend.password = cfg.settings.frontend.password or "@password@";
if cfg.settings.frontend.password_file != null clients.api_key = cfg.settings.clients.api_key or "@api_key@";
then "@password@"
else cfg.settings.frontend.password;
clients.api_key =
if cfg.settings.clients.api_key_file != null
then "@api_key@"
else cfg.settings.clients.api_key;
}; };
in in
lib.foldl' lib.recursiveUpdate defaultSettings [ base clean ]; lib.recursiveUpdate base clean;
settingsFormat = pkgs.formats.toml { }; settingsFormat = pkgs.formats.toml { };
settingsFile = settingsFormat.generate "betanin.toml" finalSettings; settingsFile = settingsFormat.generate "betanin.toml" finalSettings;
@ -39,9 +24,7 @@ let
beetsFile = beetsFile =
if (cfg.beetsFile != null) if (cfg.beetsFile != null)
then cfg.beetsFile then cfg.beetsFile
else if (cfg.beetsConfig != { }) else beetsFormat.generate "betanin-beets.yaml" cfg.beetsConfig;
then beetsFormat.generate "betanin-beets.yaml" cfg.beetsConfig
else null;
in in
{ {
options = { options = {
@ -86,15 +69,15 @@ in
settings = mkOption { settings = mkOption {
type = types.submodule { type = types.submodule {
freeformType = settingsFormat.type; freeformType = settinsgFormat.type;
options.frontend.username = mkOption { frontend.username = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Username used to log into the frontend. Must be set."; description = "Username used to log into the frontend. Must be set.";
}; };
options.frontend.password = mkOption { frontend.password = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = '' description = ''
@ -103,7 +86,7 @@ in
''; '';
}; };
options.frontend.password_file = mkOption { frontend.password_file = mkOption {
type = with types; nullOr (either str path); type = with types; nullOr (either str path);
default = null; default = null;
description = '' description = ''
@ -116,7 +99,7 @@ in
''; '';
}; };
options.clients.api_key = mkOption { clients.api_key = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = ""; default = "";
description = '' description = ''
@ -124,7 +107,7 @@ in
''; '';
}; };
options.clients.api_key_file = mkOption { clients.api_key_file = mkOption {
type = with types; nullOr (either str path); type = with types; nullOr (either str path);
default = null; default = null;
description = '' description = ''
@ -136,9 +119,20 @@ in
the API key is still stored in plain text in the service data the API key is still stored in plain text in the service data
directory. directory.
''; '';
notifications.strings.title = mkOption {
type = types.str;
default = "[betanin] torrent `$name` $status";
description = "Notification title.";
};
notifications.strings.body = mkOption {
type = types.str;
default = "@ $time. view/use the console at http://127.0.0.1:${cfg.port}/$console_path";
description = "Notification body.";
};
}; };
}; };
default = defaultSettings;
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
frontend = { frontend = {
@ -172,71 +166,69 @@ in
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [ assertions = [
{ {
assertion = cfg.settings.frontend.username != ""; assertion = cfg.settings.frontend.username != "";
message = "services.betanin.settings.frontend.username is required"; message = "services.betanin.settings.frontend.username is required";
} }
{ {
assertion = (cfg.settings.frontend.password == "") != (cfg.settings.frontend.password_file == null); assertion = (cfg.settings.frontend.password == "") != (cfg.settings.frontend.password_file);
message = "services.betanin.settings.frontend.password or services.betanin.settings.frontend.password_file is required"; message = "services.betanin.settings.frontend.password or services.betanin.settings.frontend.password_file is required";
} }
{ {
assertion = (cfg.settings.clients.api_key == "") != (cfg.settings.clients.api_key_file == null); assertion = (cfg.settings.clients.api_key == "") != (cfg.settings.clients.api_key_file);
message = "services.betanin.settings.clients.api_key or services.betanin.settings.clients.api_key_file is required"; message = "services.betanin.settings.clients.api_key or services.betanin.settings.clients.api_key_file is required";
} }
{
assertion = (cfg.beetsConfig == { }) != (cfg.beetsFile == null);
message = "either services.betanin.beetsConfig or services.betanin.beetsFile is required";
}
]; ];
networking.firewall = mkIf cfg.openFirewall { networking.firwall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ]; allowedTCPPorts = [ cfg.port ];
}; };
systemd.services.betanin = systemd.services.betanin = {
let description = "Betanin service";
replaceSecret = secretFile: placeholder: targetFile: wantedBy = [ "multi-user.target" ];
optionalString (secretFile != null) '' after = [ "networking.target" ];
${pkgs.replace-secret}/bin/replace-secret ${placeholder} ${secretFile} ${targetFile} environment = {
''; HOME = cfg.dataDir;
replaceConfigSecret = secretFile: placeholder:
replaceSecret secretFile placeholder "${cfg.dataDir}/.config/betanin/config.toml";
in
{
description = "Betanin service";
wantedBy = [ "multi-user.target" ];
after = [ "networking.target" ];
environment = {
HOME = cfg.dataDir;
};
path = [ pkgs.replace-secret ];
script = ''
mkdir -p ${cfg.dataDir}/.config/betanin \
${cfg.dataDir}/.config/beets \
${cfg.dataDir}/.local/share/betanin
cat ${settingsFile} > ${cfg.dataDir}/.config/betanin/config.toml
${optionalString (beetsFile != null) ''
ln -sf ${beetsFile} ${cfg.dataDir}/.config/betanin/config.toml
''}
${replaceConfigSecret cfg.settings.frontend.password_file "@password@"}
${replaceConfigSecret cfg.settings.frontend.api_key_file "@api_key@"}
${cfg.package}/bin/betanin --port ${toString cfg.port}
'';
serviceConfig = lib.mkMerge [
{
User = cfg.user;
Group = cfg.group;
PrivateTmp = true;
Restart = "always";
WorkingDirectory = cfg.dataDir;
}
(mkIf (cfg.dataDir == "/var/lib/betanin") {
StateDirectory = "betanin";
})
];
}; };
script = ''
#!/bin/sh
mkdir -p ${cfg.dataDir}/.config/betanin
mkdir -p ${cfg.dataDir}/.config/beets
mkdir -p ${cfg.dataDir}/.local/share/betanin
cat ${settingsFile} > ${cfg.dataDir}/.config/betanin/config.toml
ln -sf ${beetsFile} ${cfg.dataDir}/.config/betanin/config.toml
'' ++ lib.optionalString (cfg.settings.frontend.password_file != null) ''
sed -i "s/@password@/$(cat ${cfg.settings.frontend.password_file})/" \
${cfg.dataDir}/.config/betanin/config.toml
'' ++ lib.optionalString (cfg.settings.frontend.api_key_file != null) ''
sed -i "s/@api_key@/$(cat ${cfg.settings.frontend.api_key_file})/" \
${cfg.dataDir}/.config/betanin/config.toml
'' ++ ''
chmod -w ${cfg.dataDir}/.config/betanin/config.toml
${cfg.package}/bin/betanin --port ${cfg.port}
'';
serviceConfig = lib.mkMerge [
{
User = cfg.user;
Group = cfg.group;
PrivateTmp = true;
Restart = "always";
WorkingDirectory = cfg.dataDir;
}
(mkIf (cfg.dataDir == "/var/lib/betanin") {
StateDirectory = "betanin";
})
];
};
users.users = optionalAttrs (cfg.user == defaultUser) { users.users = optionalAttrs (cfg.user == defaultUser) {
${cfg.user} = { ${cfg.user} = {
isSystemUser = true; isSystemUser = true;

View File

@ -1,11 +1,8 @@
# This file has been generated by node2nix 1.11.1. Do not edit! # This file has been generated by node2nix 1.11.1. Do not edit!
{ pkgs ? import <nixpkgs> { {pkgs ? import <nixpkgs> {
inherit system; inherit system;
} }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs_18"}:
, system ? builtins.currentSystem
, nodejs ? pkgs."nodejs_18"
}:
let let
nodeEnv = import ./node-env.nix { nodeEnv = import ./node-env.nix {

View File

@ -1,6 +1,6 @@
# This file originates from node2nix # This file originates from node2nix
{ lib, stdenv, nodejs, python2, pkgs, libtool, runCommand, writeTextFile, writeShellScript }: {lib, stdenv, nodejs, python2, pkgs, libtool, runCommand, writeTextFile, writeShellScript}:
let let
# Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master # Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master
@ -9,7 +9,7 @@ let
python = if nodejs ? python then nodejs.python else python2; python = if nodejs ? python then nodejs.python else python2;
# Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise # Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise
tarWrapper = runCommand "tarWrapper" { } '' tarWrapper = runCommand "tarWrapper" {} ''
mkdir -p $out/bin mkdir -p $out/bin
cat > $out/bin/tar <<EOF cat > $out/bin/tar <<EOF
@ -90,28 +90,26 @@ let
# Bundle the dependencies of the package # Bundle the dependencies of the package
# #
# Only include dependencies if they don't exist. They may also be bundled in the package. # Only include dependencies if they don't exist. They may also be bundled in the package.
includeDependencies = { dependencies }: includeDependencies = {dependencies}:
lib.optionalString (dependencies != [ ]) ( lib.optionalString (dependencies != []) (
'' ''
mkdir -p node_modules mkdir -p node_modules
cd node_modules cd node_modules
'' ''
+ (lib.concatMapStrings + (lib.concatMapStrings (dependency:
(dependency: ''
'' if [ ! -e "${dependency.packageName}" ]; then
if [ ! -e "${dependency.packageName}" ]; then ${composePackage dependency}
${composePackage dependency} fi
fi ''
'' ) dependencies)
)
dependencies)
+ '' + ''
cd .. cd ..
'' ''
); );
# Recursively composes the dependencies of a package # Recursively composes the dependencies of a package
composePackage = { name, packageName, src, dependencies ? [ ], ... }@args: composePackage = { name, packageName, src, dependencies ? [], ... }@args:
builtins.addErrorContext "while evaluating node package '${packageName}'" '' builtins.addErrorContext "while evaluating node package '${packageName}'" ''
installPackage "${packageName}" "${src}" installPackage "${packageName}" "${src}"
${includeDependencies { inherit dependencies; }} ${includeDependencies { inherit dependencies; }}
@ -119,7 +117,7 @@ let
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
''; '';
pinpointDependencies = { dependencies, production }: pinpointDependencies = {dependencies, production}:
let let
pinpointDependenciesFromPackageJSON = writeTextFile { pinpointDependenciesFromPackageJSON = writeTextFile {
name = "pinpointDependencies.js"; name = "pinpointDependencies.js";
@ -196,7 +194,7 @@ let
# dependencies in the package.json file to the versions that are actually # dependencies in the package.json file to the versions that are actually
# being used. # being used.
pinpointDependenciesOfPackage = { packageName, dependencies ? [ ], production ? true, ... }@args: pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args:
'' ''
if [ -d "${packageName}" ] if [ -d "${packageName}" ]
then then
@ -209,7 +207,7 @@ let
# Extract the Node.js source code which is used to compile packages with # Extract the Node.js source code which is used to compile packages with
# native bindings # native bindings
nodeSources = runCommand "node-sources" { } '' nodeSources = runCommand "node-sources" {} ''
tar --no-same-owner --no-same-permissions -xf ${nodejs.src} tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
mv node-* $out mv node-* $out
''; '';
@ -416,64 +414,64 @@ let
''; '';
}; };
prepareAndInvokeNPM = { packageName, bypassCache, reconstructLock, npmFlags, production }: prepareAndInvokeNPM = {packageName, bypassCache, reconstructLock, npmFlags, production}:
let let
forceOfflineFlag = if bypassCache then "--offline" else "--registry http://www.example.com"; forceOfflineFlag = if bypassCache then "--offline" else "--registry http://www.example.com";
in in
'' ''
# Pinpoint the versions of all dependencies to the ones that are actually being used # Pinpoint the versions of all dependencies to the ones that are actually being used
echo "pinpointing versions of dependencies..." echo "pinpointing versions of dependencies..."
source $pinpointDependenciesScriptPath source $pinpointDependenciesScriptPath
# Patch the shebangs of the bundled modules to prevent them from # Patch the shebangs of the bundled modules to prevent them from
# calling executables outside the Nix store as much as possible # calling executables outside the Nix store as much as possible
patchShebangs . patchShebangs .
# Deploy the Node.js package by running npm install. Since the # Deploy the Node.js package by running npm install. Since the
# dependencies have been provided already by ourselves, it should not # dependencies have been provided already by ourselves, it should not
# attempt to install them again, which is good, because we want to make # attempt to install them again, which is good, because we want to make
# it Nix's responsibility. If it needs to install any dependencies # it Nix's responsibility. If it needs to install any dependencies
# anyway (e.g. because the dependency parameters are # anyway (e.g. because the dependency parameters are
# incomplete/incorrect), it fails. # incomplete/incorrect), it fails.
# #
# The other responsibilities of NPM are kept -- version checks, build # The other responsibilities of NPM are kept -- version checks, build
# steps, postprocessing etc. # steps, postprocessing etc.
export HOME=$TMPDIR export HOME=$TMPDIR
cd "${packageName}" cd "${packageName}"
runHook preRebuild runHook preRebuild
${lib.optionalString bypassCache '' ${lib.optionalString bypassCache ''
${lib.optionalString reconstructLock '' ${lib.optionalString reconstructLock ''
if [ -f package-lock.json ] if [ -f package-lock.json ]
then then
echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!" echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!"
echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!" echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!"
rm package-lock.json rm package-lock.json
else else
echo "No package-lock.json file found, reconstructing..." echo "No package-lock.json file found, reconstructing..."
fi fi
node ${reconstructPackageLock} node ${reconstructPackageLock}
''}
node ${addIntegrityFieldsScript}
''} ''}
node ${addIntegrityFieldsScript} npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild
''}
npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild runHook postRebuild
runHook postRebuild if [ "''${dontNpmInstall-}" != "1" ]
then
# NPM tries to download packages even when they already exist if npm-shrinkwrap is used.
rm -f npm-shrinkwrap.json
if [ "''${dontNpmInstall-}" != "1" ] npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install
then fi
# NPM tries to download packages even when they already exist if npm-shrinkwrap is used.
rm -f npm-shrinkwrap.json
npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install # Link executables defined in package.json
fi node ${linkBinsScript}
# Link executables defined in package.json
node ${linkBinsScript}
''; '';
# Builds and composes an NPM package including all its dependencies # Builds and composes an NPM package including all its dependencies
@ -481,8 +479,8 @@ let
{ name { name
, packageName , packageName
, version ? null , version ? null
, dependencies ? [ ] , dependencies ? []
, buildInputs ? [ ] , buildInputs ? []
, production ? true , production ? true
, npmFlags ? "" , npmFlags ? ""
, dontNpmInstall ? false , dontNpmInstall ? false
@ -492,9 +490,8 @@ let
, dontStrip ? true , dontStrip ? true
, unpackPhase ? "true" , unpackPhase ? "true"
, buildPhase ? "true" , buildPhase ? "true"
, meta ? { } , meta ? {}
, ... , ... }@args:
}@args:
let let
extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ]; extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ];
@ -575,8 +572,8 @@ let
, packageName , packageName
, version ? null , version ? null
, src , src
, dependencies ? [ ] , dependencies ? []
, buildInputs ? [ ] , buildInputs ? []
, production ? true , production ? true
, npmFlags ? "" , npmFlags ? ""
, dontNpmInstall ? false , dontNpmInstall ? false
@ -585,61 +582,60 @@ let
, dontStrip ? true , dontStrip ? true
, unpackPhase ? "true" , unpackPhase ? "true"
, buildPhase ? "true" , buildPhase ? "true"
, ... , ... }@args:
}@args:
let let
extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" ]; extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" ];
in in
stdenv.mkDerivation ({ stdenv.mkDerivation ({
name = "node-dependencies-${name}${if version == null then "" else "-${version}"}"; name = "node-dependencies-${name}${if version == null then "" else "-${version}"}";
buildInputs = [ tarWrapper python nodejs ] buildInputs = [ tarWrapper python nodejs ]
++ lib.optional (stdenv.isLinux) utillinux ++ lib.optional (stdenv.isLinux) utillinux
++ lib.optional (stdenv.isDarwin) libtool ++ lib.optional (stdenv.isDarwin) libtool
++ buildInputs; ++ buildInputs;
inherit dontStrip; # Stripping may fail a build for some package deployments inherit dontStrip; # Stripping may fail a build for some package deployments
inherit dontNpmInstall unpackPhase buildPhase; inherit dontNpmInstall unpackPhase buildPhase;
includeScript = includeDependencies { inherit dependencies; }; includeScript = includeDependencies { inherit dependencies; };
pinpointDependenciesScript = pinpointDependenciesOfPackage args; pinpointDependenciesScript = pinpointDependenciesOfPackage args;
passAsFile = [ "includeScript" "pinpointDependenciesScript" ]; passAsFile = [ "includeScript" "pinpointDependenciesScript" ];
installPhase = '' installPhase = ''
source ${installPackage} source ${installPackage}
mkdir -p $out/${packageName} mkdir -p $out/${packageName}
cd $out/${packageName} cd $out/${packageName}
source $includeScriptPath source $includeScriptPath
# Create fake package.json to make the npm commands work properly # Create fake package.json to make the npm commands work properly
cp ${src}/package.json . cp ${src}/package.json .
chmod 644 package.json chmod 644 package.json
${lib.optionalString bypassCache '' ${lib.optionalString bypassCache ''
if [ -f ${src}/package-lock.json ] if [ -f ${src}/package-lock.json ]
then then
cp ${src}/package-lock.json . cp ${src}/package-lock.json .
chmod 644 package-lock.json chmod 644 package-lock.json
fi fi
''} ''}
# Go to the parent folder to make sure that all packages are pinpointed # Go to the parent folder to make sure that all packages are pinpointed
cd .. cd ..
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }} ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }}
# Expose the executables that were installed # Expose the executables that were installed
cd .. cd ..
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
mv ${packageName} lib mv ${packageName} lib
ln -s $out/lib/node_modules/.bin $out/bin ln -s $out/lib/node_modules/.bin $out/bin
''; '';
} // extraArgs); } // extraArgs);
# Builds a development shell # Builds a development shell
buildNodeShell = buildNodeShell =
@ -647,8 +643,8 @@ let
, packageName , packageName
, version ? null , version ? null
, src , src
, dependencies ? [ ] , dependencies ? []
, buildInputs ? [ ] , buildInputs ? []
, production ? true , production ? true
, npmFlags ? "" , npmFlags ? ""
, dontNpmInstall ? false , dontNpmInstall ? false
@ -657,8 +653,7 @@ let
, dontStrip ? true , dontStrip ? true
, unpackPhase ? "true" , unpackPhase ? "true"
, buildPhase ? "true" , buildPhase ? "true"
, ... , ... }@args:
}@args:
let let
nodeDependencies = buildNodeDependencies args; nodeDependencies = buildNodeDependencies args;
@ -680,7 +675,7 @@ let
# Provide the dependencies in a development shell through the NODE_PATH environment variable # Provide the dependencies in a development shell through the NODE_PATH environment variable
inherit nodeDependencies; inherit nodeDependencies;
shellHook = lib.optionalString (dependencies != [ ]) '' shellHook = lib.optionalString (dependencies != []) ''
export NODE_PATH=${nodeDependencies}/lib/node_modules export NODE_PATH=${nodeDependencies}/lib/node_modules
export PATH="${nodeDependencies}/bin:$PATH" export PATH="${nodeDependencies}/bin:$PATH"
''; '';

View File

@ -1,6 +1,6 @@
# This file has been generated by node2nix 1.11.1. Do not edit! # This file has been generated by node2nix 1.11.1. Do not edit!
{ nodeEnv, fetchurl, fetchgit, nix-gitignore, stdenv, lib, globalBuildInputs ? [ ] }: {nodeEnv, fetchurl, fetchgit, nix-gitignore, stdenv, lib, globalBuildInputs ? []}:
let let
sources = { sources = {
@ -9196,7 +9196,8 @@ let
}) })
]; ];
buildInputs = globalBuildInputs; buildInputs = globalBuildInputs;
meta = { }; meta = {
};
production = false; production = false;
bypassCache = true; bypassCache = true;
reconstructLock = false; reconstructLock = false;
@ -9215,8 +9216,7 @@ in
"*" "*"
"!package.json" "!package.json"
"!package-lock.json" "!package-lock.json"
] ] args.src;
args.src;
dontBuild = true; dontBuild = true;
installPhase = "mkdir -p $out; cp -r ./* $out;"; installPhase = "mkdir -p $out; cp -r ./* $out;";
}; };