nixos/amule: improve NixOS module
This commit is contained in:
@@ -1,89 +1,323 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
utils,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.amule;
|
||||
opt = options.services.amule;
|
||||
user = if cfg.user != null then cfg.user else "amule";
|
||||
in
|
||||
|
||||
{
|
||||
settingsFormat = pkgs.formats.ini { };
|
||||
|
||||
###### interface
|
||||
inherit (lib)
|
||||
mkOption
|
||||
mkEnableOption
|
||||
mkPackageOption
|
||||
types
|
||||
optionalAttrs
|
||||
mkIf
|
||||
mkMerge
|
||||
literalExpression
|
||||
optionalString
|
||||
optionals
|
||||
getExe
|
||||
;
|
||||
|
||||
options = {
|
||||
|
||||
services.amule = {
|
||||
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
settingsOptions = {
|
||||
eMule = {
|
||||
Port = mkOption {
|
||||
type = types.port;
|
||||
default = 4662;
|
||||
description = ''
|
||||
Whether to run the AMule daemon. You need to manually run "amuled --ec-config" to configure the service for the first time.
|
||||
TCP port for eD2k connections.
|
||||
Required for connecting to servers and achieving a High ID.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/home/${user}/";
|
||||
defaultText = lib.literalExpression ''
|
||||
"/home/''${config.${opt.user}}/"
|
||||
'';
|
||||
UDPPort = mkOption {
|
||||
type = types.port;
|
||||
default = 4672;
|
||||
description = ''
|
||||
The directory holding configuration, incoming and temporary files.
|
||||
UDP port for eD2k traffic (searches, source exchange) and all Kad network communication.
|
||||
Essential for a High ID on both networks and proper Kad functioning.
|
||||
'';
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
TempDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.dataDir}/Temp";
|
||||
defaultText = literalExpression "\${config.services.amule.dataDir}/Temp";
|
||||
description = ''
|
||||
The user the AMule daemon should run as.
|
||||
Directory where aMule stores incomplete downloads (.part/.part.met files).
|
||||
'';
|
||||
};
|
||||
|
||||
IncomingDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.dataDir}/Incoming";
|
||||
defaultText = literalExpression "\${config.services.amule.dataDir}/Incoming";
|
||||
description = ''
|
||||
Directory where aMule moves completed downloads.
|
||||
Files in this directory are automatically shared.
|
||||
Ensure the aMule service has write permissions
|
||||
'';
|
||||
};
|
||||
OSDirectory = mkOption {
|
||||
type = types.path;
|
||||
default = cfg.dataDir;
|
||||
defaultText = literalExpression "\${config.services.amule.dataDir}";
|
||||
description = "On-disk state directory, probably you don't want to change this";
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
ExternalConnect = {
|
||||
AcceptExternalConnections = mkOption {
|
||||
type = types.enum [
|
||||
0
|
||||
1
|
||||
];
|
||||
default = 1;
|
||||
description = "Whether to accept external connections, if disabled amuled refuses to start";
|
||||
internal = true;
|
||||
};
|
||||
ECPort = mkOption {
|
||||
type = types.port;
|
||||
default = 4712;
|
||||
description = "TCP port for external connections, like remote control via amule-gui";
|
||||
};
|
||||
ECPassword = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
MD5 hash of the password, obtainaible with `echo "<password>" | md5sum | cut -d ' ' -f 1`
|
||||
'';
|
||||
};
|
||||
};
|
||||
WebServer = {
|
||||
Enabled = lib.mkOption {
|
||||
type = types.enum [
|
||||
0
|
||||
1
|
||||
];
|
||||
default = 0;
|
||||
description = "Set to 1 to enable the web server";
|
||||
};
|
||||
Password = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
MD5 hash of the password, obtainaible with `echo "<password>" | md5sum | cut -d ' ' -f 1`
|
||||
'';
|
||||
};
|
||||
Port = mkOption {
|
||||
type = types.port;
|
||||
default = 4711;
|
||||
description = "Web server port";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
webServerEnabled = cfg.settings.WebServer.Enabled == 1;
|
||||
in
|
||||
{
|
||||
options.services.amule = {
|
||||
enable = mkEnableOption "aMule daemon";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
package = mkPackageOption pkgs "amule-daemon" { };
|
||||
|
||||
users.users = lib.mkIf (cfg.user == null) [
|
||||
amuleWebPackage = mkPackageOption pkgs "amule-web" { };
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Additional passed arguments";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/amuled";
|
||||
description = "Directory holding configuration and by default also incoming and temporary files";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "amule";
|
||||
description = "The user the aMule daemon should run as";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "amule";
|
||||
description = "Group under which amule runs";
|
||||
};
|
||||
|
||||
openPeerPorts = mkEnableOption "open the peer port(s) in the firewall";
|
||||
|
||||
openExternalConnectPort = mkEnableOption "open the external connect port";
|
||||
|
||||
openWebServerPort = mkEnableOption "open the web server port";
|
||||
|
||||
ExternalConnectPasswordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing the password for connecting with amule-gui,
|
||||
set this only if you didn't set `settings.ExternalConnect.ECPassword`
|
||||
'';
|
||||
};
|
||||
|
||||
WebServerPasswordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing the password for connecting to the web server,
|
||||
set this only if you didn't set `settings.ExternalConnect.ECPassword`
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
options = settingsOptions;
|
||||
};
|
||||
description = ''
|
||||
Free form attribute set for aMule settings.
|
||||
The final configuration file is generated merging the default settings with these options.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
eMule = {
|
||||
IncomingDir = "/mnt/hd/amule/Incoming";
|
||||
TempDir = "/mnt/hd/amule/Temp";
|
||||
};
|
||||
WebServer.Enabled = 1;
|
||||
}
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
name = "amule";
|
||||
description = "AMule daemon";
|
||||
group = "amule";
|
||||
assertion = isNull cfg.ExternalConnectPasswordFile -> cfg.settings.ExternalConnect.ECPassword != "";
|
||||
message = "Set only one between `ExternalConnectPasswordFile` and `settings.ExternalConnect.ECPassword`";
|
||||
}
|
||||
]
|
||||
++ optionals webServerEnabled [
|
||||
{
|
||||
assertion = isNull cfg.WebServerPasswordFile -> cfg.settings.WebServer.Password != "";
|
||||
message = "Set only one between `ExternalWebServerFile` `settings.WebServer.Password`";
|
||||
}
|
||||
];
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "amule") {
|
||||
amule = {
|
||||
group = cfg.group;
|
||||
description = "aMule user";
|
||||
isSystemUser = true;
|
||||
uid = config.ids.uids.amule;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = lib.mkIf (cfg.user == null) [
|
||||
{
|
||||
name = "amule";
|
||||
gid = config.ids.gids.amule;
|
||||
}
|
||||
];
|
||||
users.groups = optionalAttrs (cfg.group == "amule") {
|
||||
amule.gid = config.ids.gids.amule;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."10-amuled".${cfg.dataDir}.d = {
|
||||
inherit (cfg) user group;
|
||||
mode = "0755";
|
||||
};
|
||||
|
||||
services.amule.settings = {
|
||||
eMule.AppVersion = lib.getVersion cfg.package;
|
||||
};
|
||||
|
||||
systemd.services.amuled = {
|
||||
description = "AMule daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.crudini ];
|
||||
|
||||
preStart = ''
|
||||
mkdir -p ${cfg.dataDir}
|
||||
chown ${user} ${cfg.dataDir}
|
||||
AMULE_CONF="${cfg.dataDir}/amule.conf"
|
||||
|
||||
if [ ! -f "$AMULE_CONF" ]; then
|
||||
echo "First run detected: starting aMule to generate default configuration..."
|
||||
echo "aMule will fail with an error - this is expected and normal"
|
||||
rm -f ${cfg.dataDir}/lastversion
|
||||
set +e
|
||||
${getExe cfg.package} --config-dir ${cfg.dataDir}
|
||||
set -e
|
||||
fi
|
||||
''
|
||||
+ (lib.concatMapAttrsStringSep "" (
|
||||
section:
|
||||
lib.concatMapAttrsStringSep "" (
|
||||
param: value: ''
|
||||
crudini --inplace --set "$AMULE_CONF" "${section}" "${param}" "${builtins.toString value}"
|
||||
''
|
||||
)
|
||||
) cfg.settings)
|
||||
+ optionalString (!isNull cfg.ExternalConnectPasswordFile) ''
|
||||
EC_PASSWORD=$(cat ${cfg.ExternalConnectPasswordFile} | md5sum | cut -d ' ' -f 1)
|
||||
crudini --inplace --set "$AMULE_CONF" "ExternalConnect" "ECPassword" "$EC_PASSWORD"
|
||||
''
|
||||
+ optionalString (!isNull cfg.WebServerPasswordFile) ''
|
||||
WEB_PASSWORD=$(cat ${cfg.WebServerPasswordFile} | md5sum | cut -d ' ' -f 1)
|
||||
crudini --inplace --set "$AMULE_CONF" "WebServer" "Password" "$WEB_PASSWORD"
|
||||
'';
|
||||
|
||||
script = ''
|
||||
${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${user} \
|
||||
-c 'HOME="${cfg.dataDir}" ${pkgs.amule-daemon}/bin/amuled'
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
||||
ExecStart = utils.escapeSystemdExecArgs (
|
||||
[
|
||||
(getExe cfg.package)
|
||||
"--config-dir"
|
||||
cfg.dataDir
|
||||
]
|
||||
++ optionals webServerEnabled [ "--use-amuleweb=${getExe cfg.amuleWebPackage}" ]
|
||||
++ cfg.extraArgs
|
||||
);
|
||||
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
|
||||
# Hardening
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
DevicePolicy = "closed";
|
||||
ProtectSystem = "strict";
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ReadWritePaths = [
|
||||
cfg.dataDir
|
||||
cfg.settings.eMule.TempDir
|
||||
cfg.settings.eMule.IncomingDir
|
||||
];
|
||||
RestrictAddressFamilies = "AF_INET";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkMerge [
|
||||
(mkIf cfg.openPeerPorts {
|
||||
allowedTCPPorts = [ cfg.settings.eMule.Port ];
|
||||
allowedUDPPorts = [ cfg.settings.eMule.UDPPort ];
|
||||
})
|
||||
(mkIf cfg.openWebServerPort {
|
||||
allowedTCPPorts = [ cfg.settings.WebServer.Port ];
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ aciceri ];
|
||||
doc = ./amuled.md;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user