uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
179
nixos/modules/services/misc/airsonic.nix
Normal file
179
nixos/modules/services/misc/airsonic.nix
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.airsonic;
|
||||
opt = options.services.airsonic;
|
||||
in {
|
||||
options = {
|
||||
|
||||
services.airsonic = {
|
||||
enable = mkEnableOption "Airsonic, the Free and Open Source media streaming server (fork of Subsonic and Libresonic)";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "airsonic";
|
||||
description = "User account under which airsonic runs.";
|
||||
};
|
||||
|
||||
home = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/airsonic";
|
||||
description = ''
|
||||
The directory where Airsonic will create files.
|
||||
Make sure it is writable.
|
||||
'';
|
||||
};
|
||||
|
||||
virtualHost = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
The host name or IP address on which to bind Airsonic.
|
||||
The default value is appropriate for first launch, when the
|
||||
default credentials are easy to guess. It is also appropriate
|
||||
if you intend to use the virtualhost option in the service
|
||||
module. In other cases, you may want to change this to a
|
||||
specific IP or 0.0.0.0 to listen on all interfaces.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 4040;
|
||||
description = ''
|
||||
The port on which Airsonic will listen for
|
||||
incoming HTTP traffic. Set to 0 to disable.
|
||||
'';
|
||||
};
|
||||
|
||||
contextPath = mkOption {
|
||||
type = types.path;
|
||||
default = "/";
|
||||
description = ''
|
||||
The context path, i.e., the last part of the Airsonic
|
||||
URL. Typically '/' or '/airsonic'. Default '/'
|
||||
'';
|
||||
};
|
||||
|
||||
maxMemory = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
description = ''
|
||||
The memory limit (max Java heap size) in megabytes.
|
||||
Default: 100
|
||||
'';
|
||||
};
|
||||
|
||||
transcoders = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
|
||||
defaultText = literalExpression ''[ "''${pkgs.ffmpeg.bin}/bin/ffmpeg" ]'';
|
||||
description = ''
|
||||
List of paths to transcoder executables that should be accessible
|
||||
from Airsonic. Symlinks will be created to each executable inside
|
||||
''${config.${opt.home}}/transcoders.
|
||||
'';
|
||||
};
|
||||
|
||||
jre = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.jre8;
|
||||
defaultText = literalExpression "pkgs.jre8";
|
||||
description = ''
|
||||
JRE package to use.
|
||||
|
||||
Airsonic only supports Java 8, airsonic-advanced requires at least
|
||||
Java 11.
|
||||
'';
|
||||
};
|
||||
|
||||
war = mkOption {
|
||||
type = types.path;
|
||||
default = "${pkgs.airsonic}/webapps/airsonic.war";
|
||||
defaultText = literalExpression ''"''${pkgs.airsonic}/webapps/airsonic.war"'';
|
||||
description = "Airsonic war file to use.";
|
||||
};
|
||||
|
||||
jvmOptions = mkOption {
|
||||
description = ''
|
||||
Extra command line options for the JVM running AirSonic.
|
||||
Useful for sending jukebox output to non-default alsa
|
||||
devices.
|
||||
'';
|
||||
default = [
|
||||
];
|
||||
type = types.listOf types.str;
|
||||
example = [
|
||||
"-Djavax.sound.sampled.Clip='#CODEC [plughw:1,0]'"
|
||||
"-Djavax.sound.sampled.Port='#Port CODEC [hw:1]'"
|
||||
"-Djavax.sound.sampled.SourceDataLine='#CODEC [plughw:1,0]'"
|
||||
"-Djavax.sound.sampled.TargetDataLine='#CODEC [plughw:1,0]'"
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.airsonic = {
|
||||
description = "Airsonic Media Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
# Install transcoders.
|
||||
rm -rf ${cfg.home}/transcode
|
||||
mkdir -p ${cfg.home}/transcode
|
||||
for exe in ${toString cfg.transcoders}; do
|
||||
ln -sf "$exe" ${cfg.home}/transcode
|
||||
done
|
||||
'';
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${cfg.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
|
||||
-Dairsonic.home=${cfg.home} \
|
||||
-Dserver.address=${cfg.listenAddress} \
|
||||
-Dserver.port=${toString cfg.port} \
|
||||
-Dairsonic.contextPath=${cfg.contextPath} \
|
||||
-Djava.awt.headless=true \
|
||||
${optionalString (cfg.virtualHost != null)
|
||||
"-Dserver.use-forward-headers=true"} \
|
||||
${toString cfg.jvmOptions} \
|
||||
-verbose:gc \
|
||||
-jar ${cfg.war}
|
||||
'';
|
||||
Restart = "always";
|
||||
User = "airsonic";
|
||||
UMask = "0022";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = mkIf (cfg.virtualHost != null) {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
virtualHosts.${cfg.virtualHost} = {
|
||||
locations.${cfg.contextPath}.proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.airsonic = {
|
||||
description = "Airsonic service user";
|
||||
group = "airsonic";
|
||||
name = cfg.user;
|
||||
home = cfg.home;
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.airsonic = {};
|
||||
};
|
||||
}
|
||||
107
nixos/modules/services/misc/ananicy.nix
Normal file
107
nixos/modules/services/misc/ananicy.nix
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.ananicy;
|
||||
configFile = pkgs.writeText "ananicy.conf" (generators.toKeyValue { } cfg.settings);
|
||||
extraRules = pkgs.writeText "extraRules" cfg.extraRules;
|
||||
servicename = if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.ananicy = {
|
||||
enable = mkEnableOption "Ananicy, an auto nice daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.ananicy;
|
||||
defaultText = literalExpression "pkgs.ananicy";
|
||||
example = literalExpression "pkgs.ananicy-cpp";
|
||||
description = ''
|
||||
Which ananicy package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ int bool str ]);
|
||||
default = { };
|
||||
example = {
|
||||
apply_nice = false;
|
||||
};
|
||||
description = ''
|
||||
See <link xlink:href="https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf"/>
|
||||
'';
|
||||
};
|
||||
|
||||
extraRules = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra rules in json format on separate lines. See:
|
||||
<link xlink:href="https://github.com/Nefelim4ag/Ananicy#configuration"/>
|
||||
<link xlink:href="https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration"/>
|
||||
'';
|
||||
example = literalExpression ''
|
||||
'''
|
||||
{ "name": "eog", "type": "Image-View" }
|
||||
{ "name": "fdupes", "type": "BG_CPUIO" }
|
||||
'''
|
||||
'';
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment = {
|
||||
systemPackages = [ cfg.package ];
|
||||
etc."ananicy.d".source = pkgs.runCommandLocal "ananicyfiles" { } ''
|
||||
mkdir -p $out
|
||||
# ananicy-cpp does not include rules or settings on purpose
|
||||
cp -r ${pkgs.ananicy}/etc/ananicy.d/* $out
|
||||
rm $out/ananicy.conf
|
||||
cp ${configFile} $out/ananicy.conf
|
||||
${optionalString (cfg.extraRules != "") "cp ${extraRules} $out/nixRules.rules"}
|
||||
'';
|
||||
};
|
||||
|
||||
# ananicy and ananicy-cpp have different default settings
|
||||
services.ananicy.settings =
|
||||
let
|
||||
mkOD = mkOptionDefault;
|
||||
in
|
||||
{
|
||||
cgroup_load = mkOD true;
|
||||
type_load = mkOD true;
|
||||
rule_load = mkOD true;
|
||||
apply_nice = mkOD true;
|
||||
apply_ioclass = mkOD true;
|
||||
apply_ionice = mkOD true;
|
||||
apply_sched = mkOD true;
|
||||
apply_oom_score_adj = mkOD true;
|
||||
apply_cgroup = mkOD true;
|
||||
} // (if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then {
|
||||
# https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
|
||||
loglevel = mkOD "warn"; # default is info but its spammy
|
||||
cgroup_realtime_workaround = mkOD config.systemd.enableUnifiedCgroupHierarchy;
|
||||
} else {
|
||||
# https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
|
||||
check_disks_schedulers = mkOD true;
|
||||
check_freq = mkOD 5;
|
||||
});
|
||||
|
||||
systemd = {
|
||||
# https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups applies to both ananicy and -cpp
|
||||
enableUnifiedCgroupHierarchy = mkDefault false;
|
||||
packages = [ cfg.package ];
|
||||
services."${servicename}" = {
|
||||
wantedBy = [ "default.target" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with maintainers; [ artturin ];
|
||||
};
|
||||
}
|
||||
79
nixos/modules/services/misc/ankisyncd.nix
Normal file
79
nixos/modules/services/misc/ankisyncd.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.ankisyncd;
|
||||
|
||||
name = "ankisyncd";
|
||||
|
||||
stateDir = "/var/lib/${name}";
|
||||
|
||||
authDbPath = "${stateDir}/auth.db";
|
||||
|
||||
sessionDbPath = "${stateDir}/session.db";
|
||||
|
||||
configFile = pkgs.writeText "ankisyncd.conf" (lib.generators.toINI {} {
|
||||
sync_app = {
|
||||
host = cfg.host;
|
||||
port = cfg.port;
|
||||
data_root = stateDir;
|
||||
auth_db_path = authDbPath;
|
||||
session_db_path = sessionDbPath;
|
||||
|
||||
base_url = "/sync/";
|
||||
base_media_url = "/msync/";
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
options.services.ankisyncd = {
|
||||
enable = mkEnableOption "ankisyncd";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.ankisyncd;
|
||||
defaultText = literalExpression "pkgs.ankisyncd";
|
||||
description = "The package to use for the ankisyncd command.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "ankisyncd host";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 27701;
|
||||
description = "ankisyncd port";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether to open the firewall for the specified port.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
||||
|
||||
environment.etc."ankisyncd/ankisyncd.conf".source = configFile;
|
||||
|
||||
systemd.services.ankisyncd = {
|
||||
description = "ankisyncd - Anki sync server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ cfg.package ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
DynamicUser = true;
|
||||
StateDirectory = name;
|
||||
ExecStart = "${cfg.package}/bin/ankisyncd";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
151
nixos/modules/services/misc/apache-kafka.nix
Normal file
151
nixos/modules/services/misc/apache-kafka.nix
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.apache-kafka;
|
||||
|
||||
serverProperties =
|
||||
if cfg.serverProperties != null then
|
||||
cfg.serverProperties
|
||||
else
|
||||
''
|
||||
# Generated by nixos
|
||||
broker.id=${toString cfg.brokerId}
|
||||
port=${toString cfg.port}
|
||||
host.name=${cfg.hostname}
|
||||
log.dirs=${concatStringsSep "," cfg.logDirs}
|
||||
zookeeper.connect=${cfg.zookeeper}
|
||||
${toString cfg.extraProperties}
|
||||
'';
|
||||
|
||||
serverConfig = pkgs.writeText "server.properties" serverProperties;
|
||||
logConfig = pkgs.writeText "log4j.properties" cfg.log4jProperties;
|
||||
|
||||
in {
|
||||
|
||||
options.services.apache-kafka = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable Apache Kafka.";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
brokerId = mkOption {
|
||||
description = "Broker ID.";
|
||||
default = -1;
|
||||
type = types.int;
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
description = "Port number the broker should listen on.";
|
||||
default = 9092;
|
||||
type = types.int;
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
description = "Hostname the broker should bind to.";
|
||||
default = "localhost";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
logDirs = mkOption {
|
||||
description = "Log file directories";
|
||||
default = [ "/tmp/kafka-logs" ];
|
||||
type = types.listOf types.path;
|
||||
};
|
||||
|
||||
zookeeper = mkOption {
|
||||
description = "Zookeeper connection string";
|
||||
default = "localhost:2181";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
extraProperties = mkOption {
|
||||
description = "Extra properties for server.properties.";
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
};
|
||||
|
||||
serverProperties = mkOption {
|
||||
description = ''
|
||||
Complete server.properties content. Other server.properties config
|
||||
options will be ignored if this option is used.
|
||||
'';
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
};
|
||||
|
||||
log4jProperties = mkOption {
|
||||
description = "Kafka log4j property configuration.";
|
||||
default = ''
|
||||
log4j.rootLogger=INFO, stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n
|
||||
'';
|
||||
type = types.lines;
|
||||
};
|
||||
|
||||
jvmOptions = mkOption {
|
||||
description = "Extra command line options for the JVM running Kafka.";
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
example = [
|
||||
"-Djava.net.preferIPv4Stack=true"
|
||||
"-Dcom.sun.management.jmxremote"
|
||||
"-Dcom.sun.management.jmxremote.local.only=true"
|
||||
];
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
description = "The kafka package to use";
|
||||
default = pkgs.apacheKafka;
|
||||
defaultText = literalExpression "pkgs.apacheKafka";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
jre = mkOption {
|
||||
description = "The JRE with which to run Kafka";
|
||||
default = cfg.package.passthru.jre;
|
||||
defaultText = literalExpression "pkgs.apacheKafka.passthru.jre";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [cfg.package];
|
||||
|
||||
users.users.apache-kafka = {
|
||||
isSystemUser = true;
|
||||
group = "apache-kafka";
|
||||
description = "Apache Kafka daemon user";
|
||||
home = head cfg.logDirs;
|
||||
};
|
||||
users.groups.apache-kafka = {};
|
||||
|
||||
systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.logDirs;
|
||||
|
||||
systemd.services.apache-kafka = {
|
||||
description = "Apache Kafka Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${cfg.jre}/bin/java \
|
||||
-cp "${cfg.package}/libs/*" \
|
||||
-Dlog4j.configuration=file:${logConfig} \
|
||||
${toString cfg.jvmOptions} \
|
||||
kafka.Kafka \
|
||||
${serverConfig}
|
||||
'';
|
||||
User = "apache-kafka";
|
||||
SuccessExitStatus = "0 143";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
100
nixos/modules/services/misc/autofs.nix
Normal file
100
nixos/modules/services/misc/autofs.nix
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.autofs;
|
||||
|
||||
autoMaster = pkgs.writeText "auto.master" cfg.autoMaster;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.autofs = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Mount filesystems on demand. Unmount them automatically.
|
||||
You may also be interested in afuse.
|
||||
'';
|
||||
};
|
||||
|
||||
autoMaster = mkOption {
|
||||
type = types.str;
|
||||
example = literalExpression ''
|
||||
let
|
||||
mapConf = pkgs.writeText "auto" '''
|
||||
kernel -ro,soft,intr ftp.kernel.org:/pub/linux
|
||||
boot -fstype=ext2 :/dev/hda1
|
||||
windoze -fstype=smbfs ://windoze/c
|
||||
removable -fstype=ext2 :/dev/hdd
|
||||
cd -fstype=iso9660,ro :/dev/hdc
|
||||
floppy -fstype=auto :/dev/fd0
|
||||
server -rw,hard,intr / -ro myserver.me.org:/ \
|
||||
/usr myserver.me.org:/usr \
|
||||
/home myserver.me.org:/home
|
||||
''';
|
||||
in '''
|
||||
/auto file:''${mapConf}
|
||||
'''
|
||||
'';
|
||||
description = ''
|
||||
Contents of <literal>/etc/auto.master</literal> file. See <command>auto.master(5)</command> and <command>autofs(5)</command>.
|
||||
'';
|
||||
};
|
||||
|
||||
timeout = mkOption {
|
||||
type = types.int;
|
||||
default = 600;
|
||||
description = "Set the global minimum timeout, in seconds, until directories are unmounted";
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Pass -d and -7 to automount and write log to the system journal.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
boot.kernelModules = [ "autofs4" ];
|
||||
|
||||
systemd.services.autofs =
|
||||
{ description = "Automounts filesystems on demand";
|
||||
after = [ "network.target" "ypbind.service" "sssd.service" "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
# There should be only one autofs service managed by systemd, so this should be safe.
|
||||
rm -f /tmp/autofs-running
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
PIDFile = "/run/autofs.pid";
|
||||
ExecStart = "${pkgs.autofs5}/bin/automount ${optionalString cfg.debug "-d"} -p /run/autofs.pid -t ${builtins.toString cfg.timeout} ${autoMaster}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
359
nixos/modules/services/misc/autorandr.nix
Normal file
359
nixos/modules/services/misc/autorandr.nix
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.autorandr;
|
||||
hookType = types.lines;
|
||||
|
||||
matrixOf = n: m: elemType:
|
||||
mkOptionType rec {
|
||||
name = "matrixOf";
|
||||
description =
|
||||
"${toString n}×${toString m} matrix of ${elemType.description}s";
|
||||
check = xss:
|
||||
let listOfSize = l: xs: isList xs && length xs == l;
|
||||
in listOfSize n xss
|
||||
&& all (xs: listOfSize m xs && all elemType.check xs) xss;
|
||||
merge = mergeOneOption;
|
||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" "*" ]);
|
||||
getSubModules = elemType.getSubModules;
|
||||
substSubModules = mod: matrixOf n m (elemType.substSubModules mod);
|
||||
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||
};
|
||||
|
||||
profileModule = types.submodule {
|
||||
options = {
|
||||
fingerprint = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
description = ''
|
||||
Output name to EDID mapping.
|
||||
Use <code>autorandr --fingerprint</code> to get current setup values.
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.attrsOf configModule;
|
||||
description = "Per output profile configuration.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
hooks = mkOption {
|
||||
type = hooksModule;
|
||||
description = "Profile hook scripts.";
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
configModule = types.submodule {
|
||||
options = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
description = "Whether to enable the output.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
crtc = mkOption {
|
||||
type = types.nullOr types.ints.unsigned;
|
||||
description = "Output video display controller.";
|
||||
default = null;
|
||||
example = 0;
|
||||
};
|
||||
|
||||
primary = mkOption {
|
||||
type = types.bool;
|
||||
description = "Whether output should be marked as primary";
|
||||
default = false;
|
||||
};
|
||||
|
||||
position = mkOption {
|
||||
type = types.str;
|
||||
description = "Output position";
|
||||
default = "";
|
||||
example = "5760x0";
|
||||
};
|
||||
|
||||
mode = mkOption {
|
||||
type = types.str;
|
||||
description = "Output resolution.";
|
||||
default = "";
|
||||
example = "3840x2160";
|
||||
};
|
||||
|
||||
rate = mkOption {
|
||||
type = types.str;
|
||||
description = "Output framerate.";
|
||||
default = "";
|
||||
example = "60.00";
|
||||
};
|
||||
|
||||
gamma = mkOption {
|
||||
type = types.str;
|
||||
description = "Output gamma configuration.";
|
||||
default = "";
|
||||
example = "1.0:0.909:0.833";
|
||||
};
|
||||
|
||||
rotate = mkOption {
|
||||
type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]);
|
||||
description = "Output rotate configuration.";
|
||||
default = null;
|
||||
example = "left";
|
||||
};
|
||||
|
||||
transform = mkOption {
|
||||
type = types.nullOr (matrixOf 3 3 types.float);
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
[
|
||||
[ 0.6 0.0 0.0 ]
|
||||
[ 0.0 0.6 0.0 ]
|
||||
[ 0.0 0.0 1.0 ]
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Refer to
|
||||
<citerefentry>
|
||||
<refentrytitle>xrandr</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</citerefentry>
|
||||
for the documentation of the transform matrix.
|
||||
'';
|
||||
};
|
||||
|
||||
dpi = mkOption {
|
||||
type = types.nullOr types.ints.positive;
|
||||
description = "Output DPI configuration.";
|
||||
default = null;
|
||||
example = 96;
|
||||
};
|
||||
|
||||
scale = mkOption {
|
||||
type = types.nullOr (types.submodule {
|
||||
options = {
|
||||
method = mkOption {
|
||||
type = types.enum [ "factor" "pixel" ];
|
||||
description = "Output scaling method.";
|
||||
default = "factor";
|
||||
example = "pixel";
|
||||
};
|
||||
|
||||
x = mkOption {
|
||||
type = types.either types.float types.ints.positive;
|
||||
description = "Horizontal scaling factor/pixels.";
|
||||
};
|
||||
|
||||
y = mkOption {
|
||||
type = types.either types.float types.ints.positive;
|
||||
description = "Vertical scaling factor/pixels.";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = ''
|
||||
Output scale configuration.
|
||||
</para><para>
|
||||
Either configure by pixels or a scaling factor. When using pixel method the
|
||||
<citerefentry>
|
||||
<refentrytitle>xrandr</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</citerefentry>
|
||||
option
|
||||
<parameter class="command">--scale-from</parameter>
|
||||
will be used; when using factor method the option
|
||||
<parameter class="command">--scale</parameter>
|
||||
will be used.
|
||||
</para><para>
|
||||
This option is a shortcut version of the transform option and they are mutually
|
||||
exclusive.
|
||||
'';
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
{
|
||||
x = 1.25;
|
||||
y = 1.25;
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
hooksModule = types.submodule {
|
||||
options = {
|
||||
postswitch = mkOption {
|
||||
type = types.attrsOf hookType;
|
||||
description = "Postswitch hook executed after mode switch.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
preswitch = mkOption {
|
||||
type = types.attrsOf hookType;
|
||||
description = "Preswitch hook executed before mode switch.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
predetect = mkOption {
|
||||
type = types.attrsOf hookType;
|
||||
description = ''
|
||||
Predetect hook executed before autorandr attempts to run xrandr.
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
hookToFile = folder: name: hook:
|
||||
nameValuePair "xdg/autorandr/${folder}/${name}" {
|
||||
source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook";
|
||||
};
|
||||
profileToFiles = name: profile:
|
||||
with profile;
|
||||
mkMerge ([
|
||||
{
|
||||
"xdg/autorandr/${name}/setup".text = concatStringsSep "\n"
|
||||
(mapAttrsToList fingerprintToString fingerprint);
|
||||
"xdg/autorandr/${name}/config".text =
|
||||
concatStringsSep "\n" (mapAttrsToList configToString profile.config);
|
||||
}
|
||||
(mapAttrs' (hookToFile "${name}/postswitch.d") hooks.postswitch)
|
||||
(mapAttrs' (hookToFile "${name}/preswitch.d") hooks.preswitch)
|
||||
(mapAttrs' (hookToFile "${name}/predetect.d") hooks.predetect)
|
||||
]);
|
||||
fingerprintToString = name: edid: "${name} ${edid}";
|
||||
configToString = name: config:
|
||||
if config.enable then
|
||||
concatStringsSep "\n" ([ "output ${name}" ]
|
||||
++ optional (config.position != "") "pos ${config.position}"
|
||||
++ optional (config.crtc != null) "crtc ${toString config.crtc}"
|
||||
++ optional config.primary "primary"
|
||||
++ optional (config.dpi != null) "dpi ${toString config.dpi}"
|
||||
++ optional (config.gamma != "") "gamma ${config.gamma}"
|
||||
++ optional (config.mode != "") "mode ${config.mode}"
|
||||
++ optional (config.rate != "") "rate ${config.rate}"
|
||||
++ optional (config.rotate != null) "rotate ${config.rotate}"
|
||||
++ optional (config.transform != null) ("transform "
|
||||
+ concatMapStringsSep "," toString (flatten config.transform))
|
||||
++ optional (config.scale != null)
|
||||
((if config.scale.method == "factor" then "scale" else "scale-from")
|
||||
+ " ${toString config.scale.x}x${toString config.scale.y}"))
|
||||
else ''
|
||||
output ${name}
|
||||
off
|
||||
'';
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
services.autorandr = {
|
||||
enable = mkEnableOption "handling of hotplug and sleep events by autorandr";
|
||||
|
||||
defaultTarget = mkOption {
|
||||
default = "default";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Fallback if no monitor layout can be detected. See the docs
|
||||
(https://github.com/phillipberndt/autorandr/blob/v1.0/README.md#how-to-use)
|
||||
for further reference.
|
||||
'';
|
||||
};
|
||||
|
||||
hooks = mkOption {
|
||||
type = hooksModule;
|
||||
description = "Global hook scripts";
|
||||
default = { };
|
||||
example = ''
|
||||
{
|
||||
postswitch = {
|
||||
"notify-i3" = "''${pkgs.i3}/bin/i3-msg restart";
|
||||
"change-background" = readFile ./change-background.sh;
|
||||
"change-dpi" = '''
|
||||
case "$AUTORANDR_CURRENT_PROFILE" in
|
||||
default)
|
||||
DPI=120
|
||||
;;
|
||||
home)
|
||||
DPI=192
|
||||
;;
|
||||
work)
|
||||
DPI=144
|
||||
;;
|
||||
*)
|
||||
echo "Unknown profle: $AUTORANDR_CURRENT_PROFILE"
|
||||
exit 1
|
||||
esac
|
||||
echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge
|
||||
'''
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
profiles = mkOption {
|
||||
type = types.attrsOf profileModule;
|
||||
description = "Autorandr profiles specification.";
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"work" = {
|
||||
fingerprint = {
|
||||
eDP1 = "<EDID>";
|
||||
DP1 = "<EDID>";
|
||||
};
|
||||
config = {
|
||||
eDP1.enable = false;
|
||||
DP1 = {
|
||||
enable = true;
|
||||
crtc = 0;
|
||||
primary = true;
|
||||
position = "0x0";
|
||||
mode = "3840x2160";
|
||||
gamma = "1.0:0.909:0.833";
|
||||
rate = "60.00";
|
||||
rotate = "left";
|
||||
};
|
||||
};
|
||||
hooks.postswitch = readFile ./work-postswitch.sh;
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.udev.packages = [ pkgs.autorandr ];
|
||||
|
||||
environment = {
|
||||
systemPackages = [ pkgs.autorandr ];
|
||||
etc = mkMerge ([
|
||||
(mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch)
|
||||
(mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch)
|
||||
(mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect)
|
||||
(mkMerge (mapAttrsToList profileToFiles cfg.profiles))
|
||||
]);
|
||||
};
|
||||
|
||||
systemd.services.autorandr = {
|
||||
wantedBy = [ "sleep.target" ];
|
||||
description = "Autorandr execution hook";
|
||||
after = [ "sleep.target" ];
|
||||
|
||||
startLimitIntervalSec = 5;
|
||||
startLimitBurst = 1;
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = false;
|
||||
KillMode = "process";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ alexnortung ];
|
||||
}
|
||||
77
nixos/modules/services/misc/bazarr.nix
Normal file
77
nixos/modules/services/misc/bazarr.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.bazarr;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.bazarr = {
|
||||
enable = mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr";
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the bazarr web interface.";
|
||||
};
|
||||
|
||||
listenPort = mkOption {
|
||||
type = types.port;
|
||||
default = 6767;
|
||||
description = "Port on which the bazarr web interface should listen";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bazarr";
|
||||
description = "User account under which bazarr runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "bazarr";
|
||||
description = "Group under which bazarr runs.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.bazarr = {
|
||||
description = "bazarr";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = rec {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
StateDirectory = "bazarr";
|
||||
SyslogIdentifier = "bazarr";
|
||||
ExecStart = pkgs.writeShellScript "start-bazarr" ''
|
||||
${pkgs.bazarr}/bin/bazarr \
|
||||
--config '/var/lib/${StateDirectory}' \
|
||||
--port ${toString cfg.listenPort} \
|
||||
--no-update True
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.listenPort ];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "bazarr") {
|
||||
bazarr = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = "/var/lib/${config.systemd.services.bazarr.serviceConfig.StateDirectory}";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "bazarr") {
|
||||
bazarr = {};
|
||||
};
|
||||
};
|
||||
}
|
||||
63
nixos/modules/services/misc/beanstalkd.nix
Normal file
63
nixos/modules/services/misc/beanstalkd.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.beanstalkd;
|
||||
pkg = pkgs.beanstalkd;
|
||||
in
|
||||
|
||||
{
|
||||
# interface
|
||||
|
||||
options = {
|
||||
services.beanstalkd = {
|
||||
enable = mkEnableOption "the Beanstalk work queue";
|
||||
|
||||
listen = {
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
description = "TCP port that will be used to accept client connections.";
|
||||
default = 11300;
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
description = "IP address to listen on.";
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
};
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to open ports in the firewall for the server.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.listen.port ];
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkg ];
|
||||
|
||||
systemd.services.beanstalkd = {
|
||||
description = "Beanstalk Work Queue";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
Restart = "always";
|
||||
ExecStart = "${pkg}/bin/beanstalkd -l ${cfg.listen.address} -p ${toString cfg.listen.port} -b $STATE_DIRECTORY";
|
||||
StateDirectory = "beanstalkd";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
132
nixos/modules/services/misc/bees.nix
Normal file
132
nixos/modules/services/misc/bees.nix
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.beesd;
|
||||
|
||||
logLevels = { emerg = 0; alert = 1; crit = 2; err = 3; warning = 4; notice = 5; info = 6; debug = 7; };
|
||||
|
||||
fsOptions = with types; {
|
||||
options.spec = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Description of how to identify the filesystem to be duplicated by this
|
||||
instance of bees. Note that deduplication crosses subvolumes; one must
|
||||
not configure multiple instances for subvolumes of the same filesystem
|
||||
(or block devices which are part of the same filesystem), but only for
|
||||
completely independent btrfs filesystems.
|
||||
</para>
|
||||
<para>
|
||||
This must be in a format usable by findmnt; that could be a key=value
|
||||
pair, or a bare path to a mount point.
|
||||
Using bare paths will allow systemd to start the beesd service only
|
||||
after mounting the associated path.
|
||||
'';
|
||||
example = "LABEL=MyBulkDataDrive";
|
||||
};
|
||||
options.hashTableSizeMB = mkOption {
|
||||
type = types.addCheck types.int (n: mod n 16 == 0);
|
||||
default = 1024; # 1GB; default from upstream beesd script
|
||||
description = ''
|
||||
Hash table size in MB; must be a multiple of 16.
|
||||
</para>
|
||||
<para>
|
||||
A larger ratio of index size to storage size means smaller blocks of
|
||||
duplicate content are recognized.
|
||||
</para>
|
||||
<para>
|
||||
If you have 1TB of data, a 4GB hash table (which is to say, a value of
|
||||
4096) will permit 4KB extents (the smallest possible size) to be
|
||||
recognized, whereas a value of 1024 -- creating a 1GB hash table --
|
||||
will recognize only aligned duplicate blocks of 16KB.
|
||||
'';
|
||||
};
|
||||
options.verbosity = mkOption {
|
||||
type = types.enum (attrNames logLevels ++ attrValues logLevels);
|
||||
apply = v: if isString v then logLevels.${v} else v;
|
||||
default = "info";
|
||||
description = "Log verbosity (syslog keyword/level).";
|
||||
};
|
||||
options.workDir = mkOption {
|
||||
type = str;
|
||||
default = ".beeshome";
|
||||
description = ''
|
||||
Name (relative to the root of the filesystem) of the subvolume where
|
||||
the hash table will be stored.
|
||||
'';
|
||||
};
|
||||
options.extraOptions = mkOption {
|
||||
type = listOf str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Extra command-line options passed to the daemon. See upstream bees documentation.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[ "--thread-count" "4" ]
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options.services.beesd = {
|
||||
filesystems = mkOption {
|
||||
type = with types; attrsOf (submodule fsOptions);
|
||||
description = "BTRFS filesystems to run block-level deduplication on.";
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
root = {
|
||||
spec = "LABEL=root";
|
||||
hashTableSizeMB = 2048;
|
||||
verbosity = "crit";
|
||||
extraOptions = [ "--loadavg-target" "5.0" ];
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = {
|
||||
systemd.services = mapAttrs'
|
||||
(name: fs: nameValuePair "beesd@${name}" {
|
||||
description = "Block-level BTRFS deduplication for %i";
|
||||
after = [ "sysinit.target" ];
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
configOpts = [
|
||||
fs.spec
|
||||
"verbosity=${toString fs.verbosity}"
|
||||
"idxSizeMB=${toString fs.hashTableSizeMB}"
|
||||
"workDir=${fs.workDir}"
|
||||
];
|
||||
configOptsStr = escapeShellArgs configOpts;
|
||||
in
|
||||
{
|
||||
# Values from https://github.com/Zygo/bees/blob/v0.6.5/scripts/beesd@.service.in
|
||||
ExecStart = "${pkgs.bees}/bin/bees-service-wrapper run ${configOptsStr} -- --no-timestamps ${escapeShellArgs fs.extraOptions}";
|
||||
ExecStopPost = "${pkgs.bees}/bin/bees-service-wrapper cleanup ${configOptsStr}";
|
||||
CPUAccounting = true;
|
||||
CPUSchedulingPolicy = "batch";
|
||||
CPUWeight = 12;
|
||||
IOSchedulingClass = "idle";
|
||||
IOSchedulingPriority = 7;
|
||||
IOWeight = 10;
|
||||
KillMode = "control-group";
|
||||
KillSignal = "SIGTERM";
|
||||
MemoryAccounting = true;
|
||||
Nice = 19;
|
||||
Restart = "on-abnormal";
|
||||
StartupCPUWeight = 25;
|
||||
StartupIOWeight = 25;
|
||||
SyslogIdentifier = "beesd"; # would otherwise be "bees-service-wrapper"
|
||||
};
|
||||
unitConfig.RequiresMountsFor = lib.mkIf (lib.hasPrefix "/" fs.spec) fs.spec;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
})
|
||||
cfg.filesystems;
|
||||
};
|
||||
}
|
||||
179
nixos/modules/services/misc/bepasty.nix
Normal file
179
nixos/modules/services/misc/bepasty.nix
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
gunicorn = pkgs.python3Packages.gunicorn;
|
||||
bepasty = pkgs.bepasty;
|
||||
gevent = pkgs.python3Packages.gevent;
|
||||
python = pkgs.python3Packages.python;
|
||||
cfg = config.services.bepasty;
|
||||
user = "bepasty";
|
||||
group = "bepasty";
|
||||
default_home = "/var/lib/bepasty";
|
||||
in
|
||||
{
|
||||
options.services.bepasty = {
|
||||
enable = mkEnableOption "Bepasty servers";
|
||||
|
||||
servers = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
configure a number of bepasty servers which will be started with
|
||||
gunicorn.
|
||||
'';
|
||||
type = with types ; attrsOf (submodule ({ config, ... } : {
|
||||
|
||||
options = {
|
||||
|
||||
bind = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Bind address to be used for this server.
|
||||
'';
|
||||
example = "0.0.0.0:8000";
|
||||
default = "127.0.0.1:8000";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Path to the directory where the pastes will be saved to
|
||||
'';
|
||||
default = default_home+"/data";
|
||||
};
|
||||
|
||||
defaultPermissions = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
default permissions for all unauthenticated accesses.
|
||||
'';
|
||||
example = "read,create,delete";
|
||||
default = "read";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Extra configuration for bepasty server to be appended on the
|
||||
configuration.
|
||||
see https://bepasty-server.readthedocs.org/en/latest/quickstart.html#configuring-bepasty
|
||||
for all options.
|
||||
'';
|
||||
default = "";
|
||||
example = ''
|
||||
PERMISSIONS = {
|
||||
'myadminsecret': 'admin,list,create,read,delete',
|
||||
}
|
||||
MAX_ALLOWED_FILE_SIZE = 5 * 1000 * 1000
|
||||
'';
|
||||
};
|
||||
|
||||
secretKey = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
server secret for safe session cookies, must be set.
|
||||
|
||||
Warning: this secret is stored in the WORLD-READABLE Nix store!
|
||||
|
||||
It's recommended to use <option>secretKeyFile</option>
|
||||
which takes precedence over <option>secretKey</option>.
|
||||
'';
|
||||
default = "";
|
||||
};
|
||||
|
||||
secretKeyFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
A file that contains the server secret for safe session cookies, must be set.
|
||||
|
||||
<option>secretKeyFile</option> takes precedence over <option>secretKey</option>.
|
||||
|
||||
Warning: when <option>secretKey</option> is non-empty <option>secretKeyFile</option>
|
||||
defaults to a file in the WORLD-READABLE Nix store containing that secret.
|
||||
'';
|
||||
};
|
||||
|
||||
workDir = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Path to the working directory (used for config and pidfile).
|
||||
Defaults to the users home directory.
|
||||
'';
|
||||
default = default_home;
|
||||
};
|
||||
|
||||
};
|
||||
config = {
|
||||
secretKeyFile = mkDefault (
|
||||
if config.secretKey != ""
|
||||
then toString (pkgs.writeTextFile {
|
||||
name = "bepasty-secret-key";
|
||||
text = config.secretKey;
|
||||
})
|
||||
else null
|
||||
);
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ bepasty ];
|
||||
|
||||
# creates gunicorn systemd service for each configured server
|
||||
systemd.services = mapAttrs' (name: server:
|
||||
nameValuePair ("bepasty-server-${name}-gunicorn")
|
||||
({
|
||||
description = "Bepasty Server ${name}";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
restartIfChanged = true;
|
||||
|
||||
environment = let
|
||||
penv = python.buildEnv.override {
|
||||
extraLibs = [ bepasty gevent ];
|
||||
};
|
||||
in {
|
||||
BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf";
|
||||
PYTHONPATH= "${penv}/${python.sitePackages}/";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
PrivateTmp = true;
|
||||
ExecStartPre = assert server.secretKeyFile != null; pkgs.writeScript "bepasty-server.${name}-init" ''
|
||||
#!/bin/sh
|
||||
mkdir -p "${server.workDir}"
|
||||
mkdir -p "${server.dataDir}"
|
||||
chown ${user}:${group} "${server.workDir}" "${server.dataDir}"
|
||||
cat > ${server.workDir}/bepasty-${name}.conf <<EOF
|
||||
SITENAME="${name}"
|
||||
STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}"
|
||||
SECRET_KEY="$(cat "${server.secretKeyFile}")"
|
||||
DEFAULT_PERMISSIONS="${server.defaultPermissions}"
|
||||
${server.extraConfig}
|
||||
EOF
|
||||
'';
|
||||
ExecStart = ''${gunicorn}/bin/gunicorn bepasty.wsgi --name ${name} \
|
||||
-u ${user} \
|
||||
-g ${group} \
|
||||
--workers 3 --log-level=info \
|
||||
--bind=${server.bind} \
|
||||
--pid ${server.workDir}/gunicorn-${name}.pid \
|
||||
-k gevent
|
||||
'';
|
||||
};
|
||||
})
|
||||
) cfg.servers;
|
||||
|
||||
users.users.${user} =
|
||||
{ uid = config.ids.uids.bepasty;
|
||||
group = group;
|
||||
home = default_home;
|
||||
};
|
||||
|
||||
users.groups.${group}.gid = config.ids.gids.bepasty;
|
||||
};
|
||||
}
|
||||
86
nixos/modules/services/misc/calibre-server.nix
Normal file
86
nixos/modules/services/misc/calibre-server.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.calibre-server;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
imports = [
|
||||
(mkChangedOptionModule [ "services" "calibre-server" "libraryDir" ] [ "services" "calibre-server" "libraries" ]
|
||||
(config:
|
||||
let libraryDir = getAttrFromPath [ "services" "calibre-server" "libraryDir" ] config;
|
||||
in [ libraryDir ]
|
||||
)
|
||||
)
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.calibre-server = {
|
||||
|
||||
enable = mkEnableOption "calibre-server";
|
||||
|
||||
libraries = mkOption {
|
||||
description = ''
|
||||
The directories of the libraries to serve. They must be readable for the user under which the server runs.
|
||||
'';
|
||||
type = types.listOf types.path;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
description = "The user under which calibre-server runs.";
|
||||
type = types.str;
|
||||
default = "calibre-server";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
description = "The group under which calibre-server runs.";
|
||||
type = types.str;
|
||||
default = "calibre-server";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.calibre-server = {
|
||||
description = "Calibre Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Restart = "always";
|
||||
ExecStart = "${pkgs.calibre}/bin/calibre-server ${lib.concatStringsSep " " cfg.libraries}";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.calibre ];
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "calibre-server") {
|
||||
calibre-server = {
|
||||
home = "/var/lib/calibre-server";
|
||||
createHome = true;
|
||||
uid = config.ids.uids.calibre-server;
|
||||
group = cfg.group;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "calibre-server") {
|
||||
calibre-server = {
|
||||
gid = config.ids.gids.calibre-server;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
37
nixos/modules/services/misc/canto-daemon.nix
Normal file
37
nixos/modules/services/misc/canto-daemon.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.canto-daemon;
|
||||
|
||||
in {
|
||||
|
||||
##### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.canto-daemon = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the canto RSS daemon.";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
##### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.user.services.canto-daemon = {
|
||||
description = "Canto RSS Daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig.ExecStart = "${pkgs.canto-daemon}/bin/canto-daemon";
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
82
nixos/modules/services/misc/cfdyndns.nix
Normal file
82
nixos/modules/services/misc/cfdyndns.nix
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.cfdyndns;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule
|
||||
[ "services" "cfdyndns" "apikey" ]
|
||||
"Use services.cfdyndns.apikeyFile instead.")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.cfdyndns = {
|
||||
enable = mkEnableOption "Cloudflare Dynamic DNS Client";
|
||||
|
||||
email = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The email address to use to authenticate to CloudFlare.
|
||||
'';
|
||||
};
|
||||
|
||||
apikeyFile = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
The path to a file containing the API Key
|
||||
used to authenticate with CloudFlare.
|
||||
'';
|
||||
};
|
||||
|
||||
records = mkOption {
|
||||
default = [];
|
||||
example = [ "host.tld" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
The records to update in CloudFlare.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.cfdyndns = {
|
||||
description = "CloudFlare Dynamic DNS Client";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startAt = "*:0/5";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = config.ids.uids.cfdyndns;
|
||||
Group = config.ids.gids.cfdyndns;
|
||||
};
|
||||
environment = {
|
||||
CLOUDFLARE_EMAIL="${cfg.email}";
|
||||
CLOUDFLARE_RECORDS="${concatStringsSep "," cfg.records}";
|
||||
};
|
||||
script = ''
|
||||
${optionalString (cfg.apikeyFile != null) ''
|
||||
export CLOUDFLARE_APIKEY="$(cat ${escapeShellArg cfg.apikeyFile})"
|
||||
''}
|
||||
${pkgs.cfdyndns}/bin/cfdyndns
|
||||
'';
|
||||
};
|
||||
|
||||
users.users = {
|
||||
cfdyndns = {
|
||||
group = "cfdyndns";
|
||||
uid = config.ids.uids.cfdyndns;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = {
|
||||
cfdyndns = {
|
||||
gid = config.ids.gids.cfdyndns;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
148
nixos/modules/services/misc/cgminer.nix
Normal file
148
nixos/modules/services/misc/cgminer.nix
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.cgminer;
|
||||
|
||||
convType = with builtins;
|
||||
v: if isBool v then boolToString v else toString v;
|
||||
mergedHwConfig =
|
||||
mapAttrsToList (n: v: ''"${n}": "${(concatStringsSep "," (map convType v))}"'')
|
||||
(foldAttrs (n: a: [n] ++ a) [] cfg.hardware);
|
||||
mergedConfig = with builtins;
|
||||
mapAttrsToList (n: v: ''"${n}": ${if isBool v then "" else ''"''}${convType v}${if isBool v then "" else ''"''}'')
|
||||
cfg.config;
|
||||
|
||||
cgminerConfig = pkgs.writeText "cgminer.conf" ''
|
||||
{
|
||||
${concatStringsSep ",\n" mergedHwConfig},
|
||||
${concatStringsSep ",\n" mergedConfig},
|
||||
"pools": [
|
||||
${concatStringsSep ",\n"
|
||||
(map (v: ''{"url": "${v.url}", "user": "${v.user}", "pass": "${v.pass}"}'')
|
||||
cfg.pools)}]
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
options = {
|
||||
|
||||
services.cgminer = {
|
||||
|
||||
enable = mkEnableOption "cgminer, an ASIC/FPGA/GPU miner for bitcoin and litecoin";
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.cgminer;
|
||||
defaultText = literalExpression "pkgs.cgminer";
|
||||
description = "Which cgminer derivation to use.";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "cgminer";
|
||||
description = "User account under which cgminer runs";
|
||||
};
|
||||
|
||||
pools = mkOption {
|
||||
default = []; # Run benchmark
|
||||
type = types.listOf (types.attrsOf types.str);
|
||||
description = "List of pools where to mine";
|
||||
example = [{
|
||||
url = "http://p2pool.org:9332";
|
||||
username = "17EUZxTvs9uRmPsjPZSYUU3zCz9iwstudk";
|
||||
password="X";
|
||||
}];
|
||||
};
|
||||
|
||||
hardware = mkOption {
|
||||
default = []; # Run without options
|
||||
type = types.listOf (types.attrsOf (types.either types.str types.int));
|
||||
description= "List of config options for every GPU";
|
||||
example = [
|
||||
{
|
||||
intensity = 9;
|
||||
gpu-engine = "0-985";
|
||||
gpu-fan = "0-85";
|
||||
gpu-memclock = 860;
|
||||
gpu-powertune = 20;
|
||||
temp-cutoff = 95;
|
||||
temp-overheat = 85;
|
||||
temp-target = 75;
|
||||
}
|
||||
{
|
||||
intensity = 9;
|
||||
gpu-engine = "0-950";
|
||||
gpu-fan = "0-85";
|
||||
gpu-memclock = 825;
|
||||
gpu-powertune = 20;
|
||||
temp-cutoff = 95;
|
||||
temp-overheat = 85;
|
||||
temp-target = 75;
|
||||
}];
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf (types.either types.bool types.int);
|
||||
description = "Additional config";
|
||||
example = {
|
||||
auto-fan = true;
|
||||
auto-gpu = true;
|
||||
expiry = 120;
|
||||
failover-only = true;
|
||||
gpu-threads = 2;
|
||||
log = 5;
|
||||
queue = 1;
|
||||
scan-time = 60;
|
||||
temp-histeresys = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.cgminer.enable {
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "cgminer") {
|
||||
cgminer = {
|
||||
isSystemUser = true;
|
||||
group = "cgminer";
|
||||
description = "Cgminer user";
|
||||
};
|
||||
};
|
||||
users.groups = optionalAttrs (cfg.user == "cgminer") {
|
||||
cgminer = {};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.services.cgminer = {
|
||||
path = [ pkgs.cgminer ];
|
||||
|
||||
after = [ "network.target" "display-manager.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
LD_LIBRARY_PATH = "/run/opengl-driver/lib:/run/opengl-driver-32/lib";
|
||||
DISPLAY = ":${toString config.services.xserver.display}";
|
||||
GPU_MAX_ALLOC_PERCENT = "100";
|
||||
GPU_USE_SYNC_OBJECTS = "1";
|
||||
};
|
||||
|
||||
startLimitIntervalSec = 60; # 1 min
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}";
|
||||
User = cfg.user;
|
||||
RestartSec = "30s";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
31
nixos/modules/services/misc/clipcat.nix
Normal file
31
nixos/modules/services/misc/clipcat.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.clipcat;
|
||||
in {
|
||||
|
||||
options.services.clipcat= {
|
||||
enable = mkEnableOption "Clipcat clipboard daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.clipcat;
|
||||
defaultText = literalExpression "pkgs.clipcat";
|
||||
description = "clipcat derivation to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.user.services.clipcat = {
|
||||
enable = true;
|
||||
description = "clipcat daemon";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
serviceConfig.ExecStart = "${cfg.package}/bin/clipcatd --no-daemon";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
};
|
||||
}
|
||||
31
nixos/modules/services/misc/clipmenu.nix
Normal file
31
nixos/modules/services/misc/clipmenu.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.clipmenu;
|
||||
in {
|
||||
|
||||
options.services.clipmenu = {
|
||||
enable = mkEnableOption "clipmenu, the clipboard management daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.clipmenu;
|
||||
defaultText = literalExpression "pkgs.clipmenu";
|
||||
description = "clipmenu derivation to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.user.services.clipmenu = {
|
||||
enable = true;
|
||||
description = "Clipboard management daemon";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
serviceConfig.ExecStart = "${cfg.package}/bin/clipmenud";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
};
|
||||
}
|
||||
90
nixos/modules/services/misc/confd.nix
Executable file
90
nixos/modules/services/misc/confd.nix
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.confd;
|
||||
|
||||
confdConfig = ''
|
||||
backend = "${cfg.backend}"
|
||||
confdir = "${cfg.confDir}"
|
||||
interval = ${toString cfg.interval}
|
||||
nodes = [ ${concatMapStringsSep "," (s: ''"${s}"'') cfg.nodes}, ]
|
||||
prefix = "${cfg.prefix}"
|
||||
log-level = "${cfg.logLevel}"
|
||||
watch = ${boolToString cfg.watch}
|
||||
'';
|
||||
|
||||
in {
|
||||
options.services.confd = {
|
||||
enable = mkEnableOption "confd service";
|
||||
|
||||
backend = mkOption {
|
||||
description = "Confd config storage backend to use.";
|
||||
default = "etcd";
|
||||
type = types.enum ["etcd" "consul" "redis" "zookeeper"];
|
||||
};
|
||||
|
||||
interval = mkOption {
|
||||
description = "Confd check interval.";
|
||||
default = 10;
|
||||
type = types.int;
|
||||
};
|
||||
|
||||
nodes = mkOption {
|
||||
description = "Confd list of nodes to connect to.";
|
||||
default = [ "http://127.0.0.1:2379" ];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
watch = mkOption {
|
||||
description = "Confd, whether to watch etcd config for changes.";
|
||||
default = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
prefix = mkOption {
|
||||
description = "The string to prefix to keys.";
|
||||
default = "/";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
description = "Confd log level.";
|
||||
default = "info";
|
||||
type = types.enum ["info" "debug"];
|
||||
};
|
||||
|
||||
confDir = mkOption {
|
||||
description = "The path to the confd configs.";
|
||||
default = "/etc/confd";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
description = "Confd package to use.";
|
||||
default = pkgs.confd;
|
||||
defaultText = literalExpression "pkgs.confd";
|
||||
type = types.package;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.confd = {
|
||||
description = "Confd Service.";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/confd";
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc = {
|
||||
"confd/confd.toml".text = confdConfig;
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
services.etcd.enable = mkIf (cfg.backend == "etcd") (mkDefault true);
|
||||
};
|
||||
}
|
||||
66
nixos/modules/services/misc/cpuminer-cryptonight.nix
Normal file
66
nixos/modules/services/misc/cpuminer-cryptonight.nix
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.cpuminer-cryptonight;
|
||||
|
||||
json = builtins.toJSON (
|
||||
cfg // {
|
||||
enable = null;
|
||||
threads =
|
||||
if cfg.threads == 0 then null else toString cfg.threads;
|
||||
}
|
||||
);
|
||||
|
||||
confFile = builtins.toFile "cpuminer.json" json;
|
||||
in
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
services.cpuminer-cryptonight = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the cpuminer cryptonight miner.
|
||||
'';
|
||||
};
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
description = "URL of mining server";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "Username for mining server";
|
||||
};
|
||||
pass = mkOption {
|
||||
type = types.str;
|
||||
default = "x";
|
||||
description = "Password for mining server";
|
||||
};
|
||||
threads = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = "Number of miner threads, defaults to available processors";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf config.services.cpuminer-cryptonight.enable {
|
||||
|
||||
systemd.services.cpuminer-cryptonight = {
|
||||
description = "Cryptonight cpuminer";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.cpuminer-multi}/bin/minerd --syslog --config=${confFile}";
|
||||
User = "nobody";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
297
nixos/modules/services/misc/dendrite.nix
Normal file
297
nixos/modules/services/misc/dendrite.nix
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.services.dendrite;
|
||||
settingsFormat = pkgs.formats.yaml { };
|
||||
configurationYaml = settingsFormat.generate "dendrite.yaml" cfg.settings;
|
||||
workingDir = "/var/lib/dendrite";
|
||||
in
|
||||
{
|
||||
options.services.dendrite = {
|
||||
enable = lib.mkEnableOption "matrix.org dendrite";
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.port;
|
||||
default = 8008;
|
||||
description = ''
|
||||
The port to listen for HTTP requests on.
|
||||
'';
|
||||
};
|
||||
httpsPort = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.port;
|
||||
default = null;
|
||||
description = ''
|
||||
The port to listen for HTTPS requests on.
|
||||
'';
|
||||
};
|
||||
tlsCert = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
example = "/var/lib/dendrite/server.cert";
|
||||
default = null;
|
||||
description = ''
|
||||
The path to the TLS certificate.
|
||||
|
||||
<programlisting>
|
||||
nix-shell -p dendrite --command "generate-keys --tls-cert server.crt --tls-key server.key"
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
tlsKey = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
example = "/var/lib/dendrite/server.key";
|
||||
default = null;
|
||||
description = ''
|
||||
The path to the TLS key.
|
||||
|
||||
<programlisting>
|
||||
nix-shell -p dendrite --command "generate-keys --tls-cert server.crt --tls-key server.key"
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
example = "/var/lib/dendrite/registration_secret";
|
||||
default = null;
|
||||
description = ''
|
||||
Environment file as defined in <citerefentry>
|
||||
<refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
|
||||
</citerefentry>.
|
||||
Secrets may be passed to the service without adding them to the world-readable
|
||||
Nix store, by specifying placeholder variables as the option value in Nix and
|
||||
setting these variables accordingly in the environment file. Currently only used
|
||||
for the registration secret to allow secure registration when
|
||||
client_api.registration_disabled is true.
|
||||
|
||||
<programlisting>
|
||||
# snippet of dendrite-related config
|
||||
services.dendrite.settings.client_api.registration_shared_secret = "$REGISTRATION_SHARED_SECRET";
|
||||
</programlisting>
|
||||
|
||||
<programlisting>
|
||||
# content of the environment file
|
||||
REGISTRATION_SHARED_SECRET=verysecretpassword
|
||||
</programlisting>
|
||||
|
||||
Note that this file needs to be available on the host on which
|
||||
<literal>dendrite</literal> is running.
|
||||
'';
|
||||
};
|
||||
loadCredential = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "private_key:/path/to/my_private_key" ];
|
||||
description = ''
|
||||
This can be used to pass secrets to the systemd service without adding them to
|
||||
the nix store.
|
||||
To use the example setting, see the example of
|
||||
<option>services.dendrite.settings.global.private_key</option>.
|
||||
See the LoadCredential section of systemd.exec manual for more information.
|
||||
'';
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
options.global = {
|
||||
server_name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "example.com";
|
||||
description = ''
|
||||
The domain name of the server, with optional explicit port.
|
||||
This is used by remote servers to connect to this server.
|
||||
This is also the last part of your UserID.
|
||||
'';
|
||||
};
|
||||
private_key = lib.mkOption {
|
||||
type = lib.types.either
|
||||
lib.types.path
|
||||
(lib.types.strMatching "^\\$CREDENTIALS_DIRECTORY/.+");
|
||||
example = "$CREDENTIALS_DIRECTORY/private_key";
|
||||
description = ''
|
||||
The path to the signing private key file, used to sign
|
||||
requests and events.
|
||||
|
||||
<programlisting>
|
||||
nix-shell -p dendrite --command "generate-keys --private-key matrix_key.pem"
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
trusted_third_party_id_servers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
example = [ "matrix.org" ];
|
||||
default = [ "matrix.org" "vector.im" ];
|
||||
description = ''
|
||||
Lists of domains that the server will trust as identity
|
||||
servers to verify third party identifiers such as phone
|
||||
numbers and email addresses
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.app_service_api.database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:federationapi.db";
|
||||
description = ''
|
||||
Database for the Appservice API.
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.client_api = {
|
||||
registration_disabled = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to disable user registration to the server
|
||||
without the shared secret.
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.federation_api.database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:federationapi.db";
|
||||
description = ''
|
||||
Database for the Federation API.
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.key_server.database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:keyserver.db";
|
||||
description = ''
|
||||
Database for the Key Server (for end-to-end encryption).
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.media_api = {
|
||||
database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:mediaapi.db";
|
||||
description = ''
|
||||
Database for the Media API.
|
||||
'';
|
||||
};
|
||||
};
|
||||
base_path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${workingDir}/media_store";
|
||||
description = ''
|
||||
Storage path for uploaded media.
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.room_server.database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:roomserver.db";
|
||||
description = ''
|
||||
Database for the Room Server.
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.sync_api.database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:syncserver.db";
|
||||
description = ''
|
||||
Database for the Sync API.
|
||||
'';
|
||||
};
|
||||
};
|
||||
options.user_api = {
|
||||
account_database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:userapi_accounts.db";
|
||||
description = ''
|
||||
Database for the User API, accounts.
|
||||
'';
|
||||
};
|
||||
};
|
||||
device_database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:userapi_devices.db";
|
||||
description = ''
|
||||
Database for the User API, devices.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
options.mscs = {
|
||||
database = {
|
||||
connection_string = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "file:mscs.db";
|
||||
description = ''
|
||||
Database for exerimental MSC's.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
description = ''
|
||||
Configuration for dendrite, see:
|
||||
<link xlink:href="https://github.com/matrix-org/dendrite/blob/master/dendrite-config.yaml"/>
|
||||
for available options with which to populate settings.
|
||||
'';
|
||||
};
|
||||
openRegistration = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Allow open registration without secondary verification (reCAPTCHA).
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [{
|
||||
assertion = cfg.httpsPort != null -> (cfg.tlsCert != null && cfg.tlsKey != null);
|
||||
message = ''
|
||||
If Dendrite is configured to use https, tlsCert and tlsKey must be provided.
|
||||
|
||||
nix-shell -p dendrite --command "generate-keys --tls-cert server.crt --tls-key server.key"
|
||||
'';
|
||||
}];
|
||||
|
||||
systemd.services.dendrite = {
|
||||
description = "Dendrite Matrix homeserver";
|
||||
after = [
|
||||
"network.target"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "dendrite";
|
||||
WorkingDirectory = workingDir;
|
||||
RuntimeDirectory = "dendrite";
|
||||
RuntimeDirectoryMode = "0700";
|
||||
LimitNOFILE = 65535;
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
||||
LoadCredential = cfg.loadCredential;
|
||||
ExecStartPre = ''
|
||||
${pkgs.envsubst}/bin/envsubst \
|
||||
-i ${configurationYaml} \
|
||||
-o /run/dendrite/dendrite.yaml
|
||||
'';
|
||||
ExecStart = lib.strings.concatStringsSep " " ([
|
||||
"${pkgs.dendrite}/bin/dendrite-monolith-server"
|
||||
"--config /run/dendrite/dendrite.yaml"
|
||||
] ++ lib.optionals (cfg.httpPort != null) [
|
||||
"--http-bind-address :${builtins.toString cfg.httpPort}"
|
||||
] ++ lib.optionals (cfg.httpsPort != null) [
|
||||
"--https-bind-address :${builtins.toString cfg.httpsPort}"
|
||||
"--tls-cert ${cfg.tlsCert}"
|
||||
"--tls-key ${cfg.tlsKey}"
|
||||
] ++ lib.optionals cfg.openRegistration [
|
||||
"--really-enable-open-registration"
|
||||
]);
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
meta.maintainers = lib.teams.matrix.members;
|
||||
}
|
||||
25
nixos/modules/services/misc/devmon.nix
Normal file
25
nixos/modules/services/misc/devmon.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{ pkgs, config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.devmon;
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.devmon = {
|
||||
enable = mkEnableOption "devmon, an automatic device mounting daemon";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.user.services.devmon = {
|
||||
description = "devmon automatic device mounting daemon";
|
||||
wantedBy = [ "default.target" ];
|
||||
path = [ pkgs.udevil pkgs.procps pkgs.udisks2 pkgs.which ];
|
||||
serviceConfig.ExecStart = "${pkgs.udevil}/bin/devmon";
|
||||
};
|
||||
|
||||
services.udisks2.enable = true;
|
||||
};
|
||||
}
|
||||
65
nixos/modules/services/misc/dictd.nix
Normal file
65
nixos/modules/services/misc/dictd.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dictd;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.dictd = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the DICT.org dictionary server.
|
||||
'';
|
||||
};
|
||||
|
||||
DBs = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = with pkgs.dictdDBs; [ wiktionary wordnet ];
|
||||
defaultText = literalExpression "with pkgs.dictdDBs; [ wiktionary wordnet ]";
|
||||
example = literalExpression "[ pkgs.dictdDBs.nld2eng ]";
|
||||
description = "List of databases to make available.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = let dictdb = pkgs.dictDBCollector { dictlist = map (x: {
|
||||
name = x.name;
|
||||
filename = x; } ) cfg.DBs; };
|
||||
in mkIf cfg.enable {
|
||||
|
||||
# get the command line client on system path to make some use of the service
|
||||
environment.systemPackages = [ pkgs.dict ];
|
||||
|
||||
users.users.dictd =
|
||||
{ group = "dictd";
|
||||
description = "DICT.org dictd server";
|
||||
home = "${dictdb}/share/dictd";
|
||||
uid = config.ids.uids.dictd;
|
||||
};
|
||||
|
||||
users.groups.dictd.gid = config.ids.gids.dictd;
|
||||
|
||||
systemd.services.dictd = {
|
||||
description = "DICT.org Dictionary Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; };
|
||||
serviceConfig.Type = "forking";
|
||||
script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8";
|
||||
};
|
||||
};
|
||||
}
|
||||
98
nixos/modules/services/misc/disnix.nix
Normal file
98
nixos/modules/services/misc/disnix.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Disnix server
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.disnix;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.disnix = {
|
||||
|
||||
enable = mkEnableOption "Disnix";
|
||||
|
||||
enableMultiUser = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to support multi-user mode by enabling the Disnix D-Bus service";
|
||||
};
|
||||
|
||||
useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat";
|
||||
|
||||
package = mkOption {
|
||||
type = types.path;
|
||||
description = "The Disnix package";
|
||||
default = pkgs.disnix;
|
||||
defaultText = literalExpression "pkgs.disnix";
|
||||
};
|
||||
|
||||
enableProfilePath = mkEnableOption "exposing the Disnix profiles in the system's PATH";
|
||||
|
||||
profiles = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "default" ];
|
||||
description = "Names of the Disnix profiles to expose in the system's PATH";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
dysnomia.enable = true;
|
||||
|
||||
environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService;
|
||||
environment.variables.PATH = lib.optionals cfg.enableProfilePath (map (profileName: "/nix/var/nix/profiles/disnix/${profileName}/bin" ) cfg.profiles);
|
||||
environment.variables.DISNIX_REMOTE_CLIENT = lib.optionalString (cfg.enableMultiUser) "disnix-client";
|
||||
|
||||
services.dbus.enable = true;
|
||||
services.dbus.packages = [ pkgs.disnix ];
|
||||
|
||||
services.tomcat.enable = cfg.useWebServiceInterface;
|
||||
services.tomcat.extraGroups = [ "disnix" ];
|
||||
services.tomcat.javaOpts = "${optionalString cfg.useWebServiceInterface "-Djava.library.path=${pkgs.libmatthew_java}/lib/jni"} ";
|
||||
services.tomcat.sharedLibs = optional cfg.useWebServiceInterface "${pkgs.DisnixWebService}/share/java/DisnixConnection.jar"
|
||||
++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar";
|
||||
services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService;
|
||||
|
||||
users.groups.disnix.gid = config.ids.gids.disnix;
|
||||
|
||||
systemd.services = {
|
||||
disnix = mkIf cfg.enableMultiUser {
|
||||
description = "Disnix server";
|
||||
wants = [ "dysnomia.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "dbus.service" ]
|
||||
++ optional config.services.httpd.enable "httpd.service"
|
||||
++ optional config.services.mysql.enable "mysql.service"
|
||||
++ optional config.services.postgresql.enable "postgresql.service"
|
||||
++ optional config.services.tomcat.enable "tomcat.service"
|
||||
++ optional config.services.svnserve.enable "svnserve.service"
|
||||
++ optional config.services.mongodb.enable "mongodb.service"
|
||||
++ optional config.services.influxdb.enable "influxdb.service";
|
||||
|
||||
restartIfChanged = false;
|
||||
|
||||
path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ];
|
||||
|
||||
environment = {
|
||||
HOME = "/root";
|
||||
}
|
||||
// (if config.environment.variables ? DYSNOMIA_CONTAINERS_PATH then { inherit (config.environment.variables) DYSNOMIA_CONTAINERS_PATH; } else {})
|
||||
// (if config.environment.variables ? DYSNOMIA_MODULES_PATH then { inherit (config.environment.variables) DYSNOMIA_MODULES_PATH; } else {});
|
||||
|
||||
serviceConfig.ExecStart = "${cfg.package}/bin/disnix-service";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
159
nixos/modules/services/misc/docker-registry.nix
Normal file
159
nixos/modules/services/misc/docker-registry.nix
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dockerRegistry;
|
||||
|
||||
blobCache = if cfg.enableRedisCache
|
||||
then "redis"
|
||||
else "inmemory";
|
||||
|
||||
registryConfig = {
|
||||
version = "0.1";
|
||||
log.fields.service = "registry";
|
||||
storage = {
|
||||
cache.blobdescriptor = blobCache;
|
||||
delete.enabled = cfg.enableDelete;
|
||||
} // (if cfg.storagePath != null
|
||||
then { filesystem.rootdirectory = cfg.storagePath; }
|
||||
else {});
|
||||
http = {
|
||||
addr = "${cfg.listenAddress}:${builtins.toString cfg.port}";
|
||||
headers.X-Content-Type-Options = ["nosniff"];
|
||||
};
|
||||
health.storagedriver = {
|
||||
enabled = true;
|
||||
interval = "10s";
|
||||
threshold = 3;
|
||||
};
|
||||
};
|
||||
|
||||
registryConfig.redis = mkIf cfg.enableRedisCache {
|
||||
addr = "${cfg.redisUrl}";
|
||||
password = "${cfg.redisPassword}";
|
||||
db = 0;
|
||||
dialtimeout = "10ms";
|
||||
readtimeout = "10ms";
|
||||
writetimeout = "10ms";
|
||||
pool = {
|
||||
maxidle = 16;
|
||||
maxactive = 64;
|
||||
idletimeout = "300s";
|
||||
};
|
||||
};
|
||||
|
||||
configFile = pkgs.writeText "docker-registry-config.yml" (builtins.toJSON (recursiveUpdate registryConfig cfg.extraConfig));
|
||||
|
||||
in {
|
||||
options.services.dockerRegistry = {
|
||||
enable = mkEnableOption "Docker Registry";
|
||||
|
||||
listenAddress = mkOption {
|
||||
description = "Docker registry host or ip to bind to.";
|
||||
default = "127.0.0.1";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
description = "Docker registry port to bind to.";
|
||||
default = 5000;
|
||||
type = types.port;
|
||||
};
|
||||
|
||||
storagePath = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "/var/lib/docker-registry";
|
||||
description = ''
|
||||
Docker registry storage path for the filesystem storage backend. Set to
|
||||
null to configure another backend via extraConfig.
|
||||
'';
|
||||
};
|
||||
|
||||
enableDelete = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable delete for manifests and blobs.";
|
||||
};
|
||||
|
||||
enableRedisCache = mkEnableOption "redis as blob cache";
|
||||
|
||||
redisUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost:6379";
|
||||
description = "Set redis host and port.";
|
||||
};
|
||||
|
||||
redisPassword = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Set redis password.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
description = ''
|
||||
Docker extra registry configuration via environment variables.
|
||||
'';
|
||||
default = {};
|
||||
type = types.attrs;
|
||||
};
|
||||
|
||||
enableGarbageCollect = mkEnableOption "garbage collect";
|
||||
|
||||
garbageCollectDates = mkOption {
|
||||
default = "daily";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Specification (in the format described by
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>) of the time at
|
||||
which the garbage collect will occur.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.docker-registry = {
|
||||
description = "Docker Container Registry";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
script = ''
|
||||
${pkgs.docker-distribution}/bin/registry serve ${configFile}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
User = "docker-registry";
|
||||
WorkingDirectory = cfg.storagePath;
|
||||
AmbientCapabilities = mkIf (cfg.port < 1024) "cap_net_bind_service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.docker-registry-garbage-collect = {
|
||||
description = "Run Garbage Collection for docker registry";
|
||||
|
||||
restartIfChanged = false;
|
||||
unitConfig.X-StopOnRemoval = false;
|
||||
|
||||
serviceConfig.Type = "oneshot";
|
||||
|
||||
script = ''
|
||||
${pkgs.docker-distribution}/bin/registry garbage-collect ${configFile}
|
||||
/run/current-system/systemd/bin/systemctl restart docker-registry.service
|
||||
'';
|
||||
|
||||
startAt = optional cfg.enableGarbageCollect cfg.garbageCollectDates;
|
||||
};
|
||||
|
||||
users.users.docker-registry =
|
||||
(if cfg.storagePath != null
|
||||
then {
|
||||
createHome = true;
|
||||
home = cfg.storagePath;
|
||||
}
|
||||
else {}) // {
|
||||
group = "docker-registry";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.docker-registry = {};
|
||||
};
|
||||
}
|
||||
51
nixos/modules/services/misc/domoticz.nix
Normal file
51
nixos/modules/services/misc/domoticz.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{ lib, pkgs, config, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.domoticz;
|
||||
pkgDesc = "Domoticz home automation";
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
services.domoticz = {
|
||||
enable = mkEnableOption pkgDesc;
|
||||
|
||||
bind = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "IP address to bind to.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 8080;
|
||||
description = "Port to bind to for HTTP, set to 0 to disable HTTP.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services."domoticz" = {
|
||||
description = pkgDesc;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "domoticz";
|
||||
Restart = "always";
|
||||
ExecStart = ''
|
||||
${pkgs.domoticz}/bin/domoticz -noupdates -www ${toString cfg.port} -wwwbind ${cfg.bind} -sslwww 0 -userdata /var/lib/domoticz -approot ${pkgs.domoticz}/share/domoticz/ -pidfile /var/run/domoticz.pid
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
39
nixos/modules/services/misc/duckling.nix
Normal file
39
nixos/modules/services/misc/duckling.nix
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.duckling;
|
||||
in {
|
||||
options = {
|
||||
services.duckling = {
|
||||
enable = mkEnableOption "duckling";
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
description = ''
|
||||
Port on which duckling will run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.duckling = {
|
||||
description = "Duckling server service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
environment = {
|
||||
PORT = builtins.toString cfg.port;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.haskellPackages.duckling}/bin/duckling-example-exe --no-access-log --no-error-log";
|
||||
Restart = "always";
|
||||
DynamicUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
73
nixos/modules/services/misc/dwm-status.nix
Normal file
73
nixos/modules/services/misc/dwm-status.nix
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dwm-status;
|
||||
|
||||
order = concatMapStringsSep "," (feature: ''"${feature}"'') cfg.order;
|
||||
|
||||
configFile = pkgs.writeText "dwm-status.toml" ''
|
||||
order = [${order}]
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.dwm-status = {
|
||||
|
||||
enable = mkEnableOption "dwm-status user service";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.dwm-status;
|
||||
defaultText = literalExpression "pkgs.dwm-status";
|
||||
example = literalExpression "pkgs.dwm-status.override { enableAlsaUtils = false; }";
|
||||
description = ''
|
||||
Which dwm-status package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
order = mkOption {
|
||||
type = types.listOf (types.enum [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]);
|
||||
description = ''
|
||||
List of enabled features in order.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra config in TOML format.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.upower.enable = elem "battery" cfg.order;
|
||||
|
||||
systemd.user.services.dwm-status = {
|
||||
description = "Highly performant and configurable DWM status service";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
partOf = [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig.ExecStart = "${cfg.package}/bin/dwm-status ${configFile}";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
265
nixos/modules/services/misc/dysnomia.nix
Normal file
265
nixos/modules/services/misc/dysnomia.nix
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
{pkgs, lib, config, ...}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.dysnomia;
|
||||
|
||||
printProperties = properties:
|
||||
concatMapStrings (propertyName:
|
||||
let
|
||||
property = properties.${propertyName};
|
||||
in
|
||||
if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties.${propertyName})})\n"
|
||||
else "${propertyName}=\"${toString property}\"\n"
|
||||
) (builtins.attrNames properties);
|
||||
|
||||
properties = pkgs.stdenv.mkDerivation {
|
||||
name = "dysnomia-properties";
|
||||
buildCommand = ''
|
||||
cat > $out << "EOF"
|
||||
${printProperties cfg.properties}
|
||||
EOF
|
||||
'';
|
||||
};
|
||||
|
||||
containersDir = pkgs.stdenv.mkDerivation {
|
||||
name = "dysnomia-containers";
|
||||
buildCommand = ''
|
||||
mkdir -p $out
|
||||
cd $out
|
||||
|
||||
${concatMapStrings (containerName:
|
||||
let
|
||||
containerProperties = cfg.containers.${containerName};
|
||||
in
|
||||
''
|
||||
cat > ${containerName} <<EOF
|
||||
${printProperties containerProperties}
|
||||
type=${containerName}
|
||||
EOF
|
||||
''
|
||||
) (builtins.attrNames cfg.containers)}
|
||||
'';
|
||||
};
|
||||
|
||||
linkMutableComponents = {containerName}:
|
||||
''
|
||||
mkdir ${containerName}
|
||||
|
||||
${concatMapStrings (componentName:
|
||||
let
|
||||
component = cfg.components.${containerName}.${componentName};
|
||||
in
|
||||
"ln -s ${component} ${containerName}/${componentName}\n"
|
||||
) (builtins.attrNames (cfg.components.${containerName} or {}))}
|
||||
'';
|
||||
|
||||
componentsDir = pkgs.stdenv.mkDerivation {
|
||||
name = "dysnomia-components";
|
||||
buildCommand = ''
|
||||
mkdir -p $out
|
||||
cd $out
|
||||
|
||||
${concatMapStrings (containerName:
|
||||
linkMutableComponents { inherit containerName; }
|
||||
) (builtins.attrNames cfg.components)}
|
||||
'';
|
||||
};
|
||||
|
||||
dysnomiaFlags = {
|
||||
enableApacheWebApplication = config.services.httpd.enable;
|
||||
enableAxis2WebService = config.services.tomcat.axis2.enable;
|
||||
enableDockerContainer = config.virtualisation.docker.enable;
|
||||
enableEjabberdDump = config.services.ejabberd.enable;
|
||||
enableMySQLDatabase = config.services.mysql.enable;
|
||||
enablePostgreSQLDatabase = config.services.postgresql.enable;
|
||||
enableTomcatWebApplication = config.services.tomcat.enable;
|
||||
enableMongoDatabase = config.services.mongodb.enable;
|
||||
enableSubversionRepository = config.services.svnserve.enable;
|
||||
enableInfluxDatabase = config.services.influxdb.enable;
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
dysnomia = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable Dysnomia";
|
||||
};
|
||||
|
||||
enableAuthentication = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to publish privacy-sensitive authentication credentials";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.path;
|
||||
description = "The Dysnomia package";
|
||||
};
|
||||
|
||||
properties = mkOption {
|
||||
description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions.";
|
||||
default = {};
|
||||
type = types.attrs;
|
||||
};
|
||||
|
||||
containers = mkOption {
|
||||
description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties";
|
||||
default = {};
|
||||
type = types.attrsOf types.attrs;
|
||||
};
|
||||
|
||||
components = mkOption {
|
||||
description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state";
|
||||
default = {};
|
||||
type = types.attrsOf types.attrs;
|
||||
};
|
||||
|
||||
extraContainerProperties = mkOption {
|
||||
description = "An attribute set providing additional container settings in addition to the default properties";
|
||||
default = {};
|
||||
type = types.attrs;
|
||||
};
|
||||
|
||||
extraContainerPaths = mkOption {
|
||||
description = "A list of paths containing additional container configurations that are added to the search folders";
|
||||
default = [];
|
||||
type = types.listOf types.path;
|
||||
};
|
||||
|
||||
extraModulePaths = mkOption {
|
||||
description = "A list of paths containing additional modules that are added to the search folders";
|
||||
default = [];
|
||||
type = types.listOf types.path;
|
||||
};
|
||||
|
||||
enableLegacyModules = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to enable Dysnomia legacy process and wrapper modules";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.etc = {
|
||||
"dysnomia/containers" = {
|
||||
source = containersDir;
|
||||
};
|
||||
"dysnomia/components" = {
|
||||
source = componentsDir;
|
||||
};
|
||||
"dysnomia/properties" = {
|
||||
source = properties;
|
||||
};
|
||||
};
|
||||
|
||||
environment.variables = {
|
||||
DYSNOMIA_STATEDIR = "/var/state/dysnomia-nixos";
|
||||
DYSNOMIA_CONTAINERS_PATH = "${lib.concatMapStrings (containerPath: "${containerPath}:") cfg.extraContainerPaths}/etc/dysnomia/containers";
|
||||
DYSNOMIA_MODULES_PATH = "${lib.concatMapStrings (modulePath: "${modulePath}:") cfg.extraModulePaths}/etc/dysnomia/modules";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
dysnomia.package = pkgs.dysnomia.override (origArgs: dysnomiaFlags // lib.optionalAttrs (cfg.enableLegacyModules) {
|
||||
enableLegacy = builtins.trace ''
|
||||
WARNING: Dysnomia has been configured to use the legacy 'process' and 'wrapper'
|
||||
modules for compatibility reasons! If you rely on these modules, consider
|
||||
migrating to better alternatives.
|
||||
|
||||
More information: https://raw.githubusercontent.com/svanderburg/dysnomia/f65a9a84827bcc4024d6b16527098b33b02e4054/README-legacy.md
|
||||
|
||||
If you have migrated already or don't rely on these Dysnomia modules, you can
|
||||
disable legacy mode with the following NixOS configuration option:
|
||||
|
||||
dysnomia.enableLegacyModules = false;
|
||||
|
||||
In a future version of Dysnomia (and NixOS) the legacy option will go away!
|
||||
'' true;
|
||||
});
|
||||
|
||||
dysnomia.properties = {
|
||||
hostname = config.networking.hostName;
|
||||
inherit (config.nixpkgs.localSystem) system;
|
||||
|
||||
supportedTypes = [
|
||||
"echo"
|
||||
"fileset"
|
||||
"process"
|
||||
"wrapper"
|
||||
|
||||
# These are not base modules, but they are still enabled because they work with technology that are always enabled in NixOS
|
||||
"systemd-unit"
|
||||
"sysvinit-script"
|
||||
"nixos-configuration"
|
||||
]
|
||||
++ optional (dysnomiaFlags.enableApacheWebApplication) "apache-webapplication"
|
||||
++ optional (dysnomiaFlags.enableAxis2WebService) "axis2-webservice"
|
||||
++ optional (dysnomiaFlags.enableDockerContainer) "docker-container"
|
||||
++ optional (dysnomiaFlags.enableEjabberdDump) "ejabberd-dump"
|
||||
++ optional (dysnomiaFlags.enableInfluxDatabase) "influx-database"
|
||||
++ optional (dysnomiaFlags.enableMySQLDatabase) "mysql-database"
|
||||
++ optional (dysnomiaFlags.enablePostgreSQLDatabase) "postgresql-database"
|
||||
++ optional (dysnomiaFlags.enableTomcatWebApplication) "tomcat-webapplication"
|
||||
++ optional (dysnomiaFlags.enableMongoDatabase) "mongo-database"
|
||||
++ optional (dysnomiaFlags.enableSubversionRepository) "subversion-repository";
|
||||
};
|
||||
|
||||
dysnomia.containers = lib.recursiveUpdate ({
|
||||
process = {};
|
||||
wrapper = {};
|
||||
}
|
||||
// lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = {
|
||||
documentRoot = config.services.httpd.virtualHosts.localhost.documentRoot;
|
||||
}; }
|
||||
// lib.optionalAttrs (config.services.tomcat.axis2.enable) { axis2-webservice = {}; }
|
||||
// lib.optionalAttrs (config.services.ejabberd.enable) { ejabberd-dump = {
|
||||
ejabberdUser = config.services.ejabberd.user;
|
||||
}; }
|
||||
// lib.optionalAttrs (config.services.mysql.enable) { mysql-database = {
|
||||
mysqlPort = config.services.mysql.port;
|
||||
mysqlSocket = "/run/mysqld/mysqld.sock";
|
||||
} // lib.optionalAttrs cfg.enableAuthentication {
|
||||
mysqlUsername = "root";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (config.services.postgresql.enable) { postgresql-database = {
|
||||
} // lib.optionalAttrs (cfg.enableAuthentication) {
|
||||
postgresqlUsername = "postgres";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (config.services.tomcat.enable) { tomcat-webapplication = {
|
||||
tomcatPort = 8080;
|
||||
}; }
|
||||
// lib.optionalAttrs (config.services.mongodb.enable) { mongo-database = {}; }
|
||||
// lib.optionalAttrs (config.services.influxdb.enable) {
|
||||
influx-database = {
|
||||
influxdbUsername = config.services.influxdb.user;
|
||||
influxdbDataDir = "${config.services.influxdb.dataDir}/data";
|
||||
influxdbMetaDir = "${config.services.influxdb.dataDir}/meta";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (config.services.svnserve.enable) { subversion-repository = {
|
||||
svnBaseDir = config.services.svnserve.svnBaseDir;
|
||||
}; }) cfg.extraContainerProperties;
|
||||
|
||||
boot.extraSystemdUnitPaths = [ "/etc/systemd-mutable/system" ];
|
||||
|
||||
system.activationScripts.dysnomia = ''
|
||||
mkdir -p /etc/systemd-mutable/system
|
||||
if [ ! -f /etc/systemd-mutable/system/dysnomia.target ]
|
||||
then
|
||||
( echo "[Unit]"
|
||||
echo "Description=Services that are activated and deactivated by Dysnomia"
|
||||
echo "After=final.target"
|
||||
) > /etc/systemd-mutable/system/dysnomia.target
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
||||
104
nixos/modules/services/misc/errbot.nix
Normal file
104
nixos/modules/services/misc/errbot.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.errbot;
|
||||
pluginEnv = plugins: pkgs.buildEnv {
|
||||
name = "errbot-plugins";
|
||||
paths = plugins;
|
||||
};
|
||||
mkConfigDir = instanceCfg: dataDir: pkgs.writeTextDir "config.py" ''
|
||||
import logging
|
||||
BACKEND = '${instanceCfg.backend}'
|
||||
BOT_DATA_DIR = '${dataDir}'
|
||||
BOT_EXTRA_PLUGIN_DIR = '${pluginEnv instanceCfg.plugins}'
|
||||
|
||||
BOT_LOG_LEVEL = logging.${instanceCfg.logLevel}
|
||||
BOT_LOG_FILE = False
|
||||
|
||||
BOT_ADMINS = (${concatMapStringsSep "," (name: "'${name}'") instanceCfg.admins})
|
||||
|
||||
BOT_IDENTITY = ${builtins.toJSON instanceCfg.identity}
|
||||
|
||||
${instanceCfg.extraConfig}
|
||||
'';
|
||||
in {
|
||||
options = {
|
||||
services.errbot.instances = mkOption {
|
||||
default = {};
|
||||
description = "Errbot instance configs";
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
dataDir = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Data directory for errbot instance.";
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
description = "List of errbot plugin derivations.";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.str;
|
||||
default = "INFO";
|
||||
description = "Errbot log level";
|
||||
};
|
||||
|
||||
admins = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "List of identifiers of errbot admins.";
|
||||
};
|
||||
|
||||
backend = mkOption {
|
||||
type = types.str;
|
||||
default = "XMPP";
|
||||
description = "Errbot backend name.";
|
||||
};
|
||||
|
||||
identity = mkOption {
|
||||
type = types.attrs;
|
||||
description = "Errbot identity configuration";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "String to be appended to the config verbatim";
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.instances != {}) {
|
||||
users.users.errbot = {
|
||||
group = "errbot";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.errbot = {};
|
||||
|
||||
systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
|
||||
let
|
||||
dataDir = if instanceCfg.dataDir != null then instanceCfg.dataDir else
|
||||
"/var/lib/errbot/${name}";
|
||||
in {
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${dataDir}
|
||||
chown -R errbot:errbot ${dataDir}
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = "errbot";
|
||||
Restart = "on-failure";
|
||||
ExecStart = "${pkgs.errbot}/bin/errbot -c ${mkConfigDir instanceCfg dataDir}/config.py";
|
||||
PermissionsStartOnly = true;
|
||||
};
|
||||
})) cfg.instances;
|
||||
};
|
||||
}
|
||||
205
nixos/modules/services/misc/etcd.nix
Normal file
205
nixos/modules/services/misc/etcd.nix
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.etcd;
|
||||
opt = options.services.etcd;
|
||||
|
||||
in {
|
||||
|
||||
options.services.etcd = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable etcd.";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
description = "Etcd unique node name.";
|
||||
default = config.networking.hostName;
|
||||
defaultText = literalExpression "config.networking.hostName";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
advertiseClientUrls = mkOption {
|
||||
description = "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
|
||||
default = cfg.listenClientUrls;
|
||||
defaultText = literalExpression "config.${opt.listenClientUrls}";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
listenClientUrls = mkOption {
|
||||
description = "Etcd list of URLs to listen on for client traffic.";
|
||||
default = ["http://127.0.0.1:2379"];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
listenPeerUrls = mkOption {
|
||||
description = "Etcd list of URLs to listen on for peer traffic.";
|
||||
default = ["http://127.0.0.1:2380"];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
initialAdvertisePeerUrls = mkOption {
|
||||
description = "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
|
||||
default = cfg.listenPeerUrls;
|
||||
defaultText = literalExpression "config.${opt.listenPeerUrls}";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
initialCluster = mkOption {
|
||||
description = "Etcd initial cluster configuration for bootstrapping.";
|
||||
default = ["${cfg.name}=http://127.0.0.1:2380"];
|
||||
defaultText = literalExpression ''["''${config.${opt.name}}=http://127.0.0.1:2380"]'';
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
initialClusterState = mkOption {
|
||||
description = "Etcd initial cluster configuration for bootstrapping.";
|
||||
default = "new";
|
||||
type = types.enum ["new" "existing"];
|
||||
};
|
||||
|
||||
initialClusterToken = mkOption {
|
||||
description = "Etcd initial cluster token for etcd cluster during bootstrap.";
|
||||
default = "etcd-cluster";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
discovery = mkOption {
|
||||
description = "Etcd discovery url";
|
||||
default = "";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
clientCertAuth = mkOption {
|
||||
description = "Whether to use certs for client authentication";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
trustedCaFile = mkOption {
|
||||
description = "Certificate authority file to use for clients";
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
certFile = mkOption {
|
||||
description = "Cert file to use for clients";
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
description = "Key file to use for clients";
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
peerCertFile = mkOption {
|
||||
description = "Cert file to use for peer to peer communication";
|
||||
default = cfg.certFile;
|
||||
defaultText = literalExpression "config.${opt.certFile}";
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
peerKeyFile = mkOption {
|
||||
description = "Key file to use for peer to peer communication";
|
||||
default = cfg.keyFile;
|
||||
defaultText = literalExpression "config.${opt.keyFile}";
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
peerTrustedCaFile = mkOption {
|
||||
description = "Certificate authority file to use for peer to peer communication";
|
||||
default = cfg.trustedCaFile;
|
||||
defaultText = literalExpression "config.${opt.trustedCaFile}";
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
peerClientCertAuth = mkOption {
|
||||
description = "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
extraConf = mkOption {
|
||||
description = ''
|
||||
Etcd extra configuration. See
|
||||
<link xlink:href='https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags' />
|
||||
'';
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
"CORS" = "*";
|
||||
"NAME" = "default-name";
|
||||
"MAX_RESULT_BUFFER" = "1024";
|
||||
"MAX_CLUSTER_SIZE" = "9";
|
||||
"MAX_RETRY_ATTEMPTS" = "3";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/etcd";
|
||||
description = "Etcd data directory.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0700 etcd - - -"
|
||||
];
|
||||
|
||||
systemd.services.etcd = {
|
||||
description = "etcd key-value store";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
environment = (filterAttrs (n: v: v != null) {
|
||||
ETCD_NAME = cfg.name;
|
||||
ETCD_DISCOVERY = cfg.discovery;
|
||||
ETCD_DATA_DIR = cfg.dataDir;
|
||||
ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls;
|
||||
ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
|
||||
ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
|
||||
ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
|
||||
ETCD_PEER_CERT_FILE = cfg.peerCertFile;
|
||||
ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
|
||||
ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
|
||||
ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
|
||||
ETCD_CERT_FILE = cfg.certFile;
|
||||
ETCD_KEY_FILE = cfg.keyFile;
|
||||
}) // (optionalAttrs (cfg.discovery == ""){
|
||||
ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
|
||||
ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
|
||||
ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
|
||||
}) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf);
|
||||
|
||||
unitConfig = {
|
||||
Documentation = "https://github.com/coreos/etcd";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStart = "${pkgs.etcd}/bin/etcd";
|
||||
User = "etcd";
|
||||
LimitNOFILE = 40000;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.etcd ];
|
||||
|
||||
users.users.etcd = {
|
||||
isSystemUser = true;
|
||||
group = "etcd";
|
||||
description = "Etcd daemon user";
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
users.groups.etcd = {};
|
||||
};
|
||||
}
|
||||
226
nixos/modules/services/misc/etebase-server.nix
Normal file
226
nixos/modules/services/misc/etebase-server.nix
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.etebase-server;
|
||||
|
||||
pythonEnv = pkgs.python3.withPackages (ps: with ps;
|
||||
[ etebase-server daphne ]);
|
||||
|
||||
iniFmt = pkgs.formats.ini {};
|
||||
|
||||
configIni = iniFmt.generate "etebase-server.ini" cfg.settings;
|
||||
|
||||
defaultUser = "etebase-server";
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule
|
||||
[ "services" "etebase-server" "customIni" ]
|
||||
"Set the option `services.etebase-server.settings' instead.")
|
||||
(mkRemovedOptionModule
|
||||
[ "services" "etebase-server" "database" ]
|
||||
"Set the option `services.etebase-server.settings.database' instead.")
|
||||
(mkRenamedOptionModule
|
||||
[ "services" "etebase-server" "secretFile" ]
|
||||
[ "services" "etebase-server" "settings" "secret_file" ])
|
||||
(mkRenamedOptionModule
|
||||
[ "services" "etebase-server" "host" ]
|
||||
[ "services" "etebase-server" "settings" "allowed_hosts" "allowed_host1" ])
|
||||
];
|
||||
|
||||
options = {
|
||||
services.etebase-server = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Whether to enable the Etebase server.
|
||||
|
||||
Once enabled you need to create an admin user by invoking the
|
||||
shell command <literal>etebase-server createsuperuser</literal> with
|
||||
the user specified by the <literal>user</literal> option or a superuser.
|
||||
Then you can login and create accounts on your-etebase-server.com/admin
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/etebase-server";
|
||||
description = "Directory to store the Etebase server data.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = with types; nullOr port;
|
||||
default = 8001;
|
||||
description = "Port to listen on.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open ports in the firewall for the server.
|
||||
'';
|
||||
};
|
||||
|
||||
unixSocket = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "The path to the socket to bind to.";
|
||||
example = "/run/etebase-server/etebase-server.sock";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = iniFmt.type;
|
||||
|
||||
options = {
|
||||
global = {
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to set django's DEBUG flag.
|
||||
'';
|
||||
};
|
||||
secret_file = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The path to a file containing the secret
|
||||
used as django's SECRET_KEY.
|
||||
'';
|
||||
};
|
||||
static_root = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.dataDir}/static";
|
||||
defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/static"'';
|
||||
description = "The directory for static files.";
|
||||
};
|
||||
media_root = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.dataDir}/media";
|
||||
defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/media"'';
|
||||
description = "The media directory.";
|
||||
};
|
||||
};
|
||||
allowed_hosts = {
|
||||
allowed_host1 = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
example = "localhost";
|
||||
description = ''
|
||||
The main host that is allowed access.
|
||||
'';
|
||||
};
|
||||
};
|
||||
database = {
|
||||
engine = mkOption {
|
||||
type = types.enum [ "django.db.backends.sqlite3" "django.db.backends.postgresql" ];
|
||||
default = "django.db.backends.sqlite3";
|
||||
description = "The database engine to use.";
|
||||
};
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.dataDir}/db.sqlite3";
|
||||
defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/db.sqlite3"'';
|
||||
description = "The database name.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
description = ''
|
||||
Configuration for <package>etebase-server</package>. Refer to
|
||||
<link xlink:href="https://github.com/etesync/server/blob/master/etebase-server.ini.example" />
|
||||
and <link xlink:href="https://github.com/etesync/server/wiki" />
|
||||
for details on supported values.
|
||||
'';
|
||||
example = {
|
||||
global = {
|
||||
debug = true;
|
||||
media_root = "/path/to/media";
|
||||
};
|
||||
allowed_hosts = {
|
||||
allowed_host2 = "localhost";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = defaultUser;
|
||||
description = "User under which Etebase server runs.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
(runCommand "etebase-server" {
|
||||
buildInputs = [ makeWrapper ];
|
||||
} ''
|
||||
makeWrapper ${pythonEnv}/bin/etebase-server \
|
||||
$out/bin/etebase-server \
|
||||
--chdir ${escapeShellArg cfg.dataDir} \
|
||||
--prefix ETEBASE_EASY_CONFIG_PATH : "${configIni}"
|
||||
'')
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.etebase-server = {
|
||||
description = "An Etebase (EteSync 2.0) server";
|
||||
after = [ "network.target" "systemd-tmpfiles-setup.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Restart = "always";
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
};
|
||||
environment = {
|
||||
PYTHONPATH = "${pythonEnv}/${pkgs.python3.sitePackages}";
|
||||
ETEBASE_EASY_CONFIG_PATH = configIni;
|
||||
};
|
||||
preStart = ''
|
||||
# Auto-migrate on first run or if the package has changed
|
||||
versionFile="${cfg.dataDir}/src-version"
|
||||
if [[ $(cat "$versionFile" 2>/dev/null) != ${pkgs.etebase-server} ]]; then
|
||||
${pythonEnv}/bin/etebase-server migrate --no-input
|
||||
${pythonEnv}/bin/etebase-server collectstatic --no-input --clear
|
||||
echo ${pkgs.etebase-server} > "$versionFile"
|
||||
fi
|
||||
'';
|
||||
script =
|
||||
let
|
||||
networking = if cfg.unixSocket != null
|
||||
then "-u ${cfg.unixSocket}"
|
||||
else "-b 0.0.0.0 -p ${toString cfg.port}";
|
||||
in ''
|
||||
cd "${pythonEnv}/lib/etebase-server";
|
||||
${pythonEnv}/bin/daphne ${networking} \
|
||||
etebase_server.asgi:application
|
||||
'';
|
||||
};
|
||||
|
||||
users = optionalAttrs (cfg.user == defaultUser) {
|
||||
users.${defaultUser} = {
|
||||
isSystemUser = true;
|
||||
group = defaultUser;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
|
||||
groups.${defaultUser} = {};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
92
nixos/modules/services/misc/etesync-dav.nix
Normal file
92
nixos/modules/services/misc/etesync-dav.nix
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.etesync-dav;
|
||||
in
|
||||
{
|
||||
options.services.etesync-dav = {
|
||||
enable = mkEnableOption "etesync-dav";
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "The server host address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 37358;
|
||||
description = "The server host port.";
|
||||
};
|
||||
|
||||
apiUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "https://api.etesync.com/";
|
||||
description = "The url to the etesync API.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether to open the firewall for the specified port.";
|
||||
};
|
||||
|
||||
sslCertificate = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/etesync.crt";
|
||||
description = ''
|
||||
Path to server SSL certificate. It will be copied into
|
||||
etesync-dav's data directory.
|
||||
'';
|
||||
};
|
||||
|
||||
sslCertificateKey = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/etesync.key";
|
||||
description = ''
|
||||
Path to server SSL certificate key. It will be copied into
|
||||
etesync-dav's data directory.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
||||
|
||||
systemd.services.etesync-dav = {
|
||||
description = "etesync-dav - A CalDAV and CardDAV adapter for EteSync";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.etesync-dav ];
|
||||
environment = {
|
||||
ETESYNC_LISTEN_ADDRESS = cfg.host;
|
||||
ETESYNC_LISTEN_PORT = toString cfg.port;
|
||||
ETESYNC_URL = cfg.apiUrl;
|
||||
ETESYNC_DATA_DIR = "/var/lib/etesync-dav";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "etesync-dav";
|
||||
ExecStart = "${pkgs.etesync-dav}/bin/etesync-dav";
|
||||
ExecStartPre = mkIf (cfg.sslCertificate != null || cfg.sslCertificateKey != null) (
|
||||
pkgs.writers.writeBash "etesync-dav-copy-keys" ''
|
||||
${optionalString (cfg.sslCertificate != null) ''
|
||||
cp ${toString cfg.sslCertificate} $STATE_DIRECTORY/etesync.crt
|
||||
''}
|
||||
${optionalString (cfg.sslCertificateKey != null) ''
|
||||
cp ${toString cfg.sslCertificateKey} $STATE_DIRECTORY/etesync.key
|
||||
''}
|
||||
''
|
||||
);
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30min 1s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
117
nixos/modules/services/misc/ethminer.nix
Normal file
117
nixos/modules/services/misc/ethminer.nix
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.ethminer;
|
||||
poolUrl = escapeShellArg "stratum1+tcp://${cfg.wallet}@${cfg.pool}:${toString cfg.stratumPort}/${cfg.rig}/${cfg.registerMail}";
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.ethminer = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable ethminer ether mining.";
|
||||
};
|
||||
|
||||
recheckInterval = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 2000;
|
||||
description = "Interval in milliseconds between farm rechecks.";
|
||||
};
|
||||
|
||||
toolkit = mkOption {
|
||||
type = types.enum [ "cuda" "opencl" ];
|
||||
default = "cuda";
|
||||
description = "Cuda or opencl toolkit.";
|
||||
};
|
||||
|
||||
apiPort = mkOption {
|
||||
type = types.int;
|
||||
default = -3333;
|
||||
description = "Ethminer api port. minus sign puts api in read-only mode.";
|
||||
};
|
||||
|
||||
wallet = mkOption {
|
||||
type = types.str;
|
||||
example = "0x0123456789abcdef0123456789abcdef01234567";
|
||||
description = "Ethereum wallet address.";
|
||||
};
|
||||
|
||||
pool = mkOption {
|
||||
type = types.str;
|
||||
example = "eth-us-east1.nanopool.org";
|
||||
description = "Mining pool address.";
|
||||
};
|
||||
|
||||
stratumPort = mkOption {
|
||||
type = types.port;
|
||||
default = 9999;
|
||||
description = "Stratum protocol tcp port.";
|
||||
};
|
||||
|
||||
rig = mkOption {
|
||||
type = types.str;
|
||||
default = "mining-rig-name";
|
||||
description = "Mining rig name.";
|
||||
};
|
||||
|
||||
registerMail = mkOption {
|
||||
type = types.str;
|
||||
example = "email%40example.org";
|
||||
description = "Url encoded email address to register with pool.";
|
||||
};
|
||||
|
||||
maxPower = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 113;
|
||||
description = "Miner max watt usage.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.ethminer = {
|
||||
path = optional (cfg.toolkit == "cuda") [ pkgs.cudaPackages.cudatoolkit ];
|
||||
description = "ethminer ethereum mining service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStartPre = "${pkgs.ethminer}/bin/.ethminer-wrapped --list-devices";
|
||||
ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}";
|
||||
Restart = "always";
|
||||
};
|
||||
|
||||
environment = mkIf (cfg.toolkit == "cuda") {
|
||||
LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
|
||||
};
|
||||
|
||||
script = ''
|
||||
${pkgs.ethminer}/bin/.ethminer-wrapped \
|
||||
--farm-recheck ${toString cfg.recheckInterval} \
|
||||
--report-hashrate \
|
||||
--${cfg.toolkit} \
|
||||
--api-port ${toString cfg.apiPort} \
|
||||
--pool ${poolUrl}
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
422
nixos/modules/services/misc/exhibitor.nix
Normal file
422
nixos/modules/services/misc/exhibitor.nix
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.exhibitor;
|
||||
opt = options.services.exhibitor;
|
||||
exhibitorConfig = ''
|
||||
zookeeper-install-directory=${cfg.baseDir}/zookeeper
|
||||
zookeeper-data-directory=${cfg.zkDataDir}
|
||||
zookeeper-log-directory=${cfg.zkLogDir}
|
||||
zoo-cfg-extra=${cfg.zkExtraCfg}
|
||||
client-port=${toString cfg.zkClientPort}
|
||||
connect-port=${toString cfg.zkConnectPort}
|
||||
election-port=${toString cfg.zkElectionPort}
|
||||
cleanup-period-ms=${toString cfg.zkCleanupPeriod}
|
||||
servers-spec=${concatStringsSep "," cfg.zkServersSpec}
|
||||
auto-manage-instances=${toString cfg.autoManageInstances}
|
||||
${cfg.extraConf}
|
||||
'';
|
||||
# NB: toString rather than lib.boolToString on cfg.autoManageInstances is intended.
|
||||
# Exhibitor tests if it's an integer not equal to 0, so the empty string (toString false)
|
||||
# will operate in the same fashion as a 0.
|
||||
configDir = pkgs.writeTextDir "exhibitor.properties" exhibitorConfig;
|
||||
cliOptionsCommon = {
|
||||
configtype = cfg.configType;
|
||||
defaultconfig = "${configDir}/exhibitor.properties";
|
||||
port = toString cfg.port;
|
||||
hostname = cfg.hostname;
|
||||
headingtext = if (cfg.headingText != null) then (lib.escapeShellArg cfg.headingText) else null;
|
||||
nodemodification = lib.boolToString cfg.nodeModification;
|
||||
configcheckms = toString cfg.configCheckMs;
|
||||
jquerystyle = cfg.jqueryStyle;
|
||||
loglines = toString cfg.logLines;
|
||||
servo = lib.boolToString cfg.servo;
|
||||
timeout = toString cfg.timeout;
|
||||
};
|
||||
s3CommonOptions = { s3region = cfg.s3Region; s3credentials = cfg.s3Credentials; };
|
||||
cliOptionsPerConfig = {
|
||||
s3 = {
|
||||
s3config = "${cfg.s3Config.bucketName}:${cfg.s3Config.objectKey}";
|
||||
s3configprefix = cfg.s3Config.configPrefix;
|
||||
};
|
||||
zookeeper = {
|
||||
zkconfigconnect = concatStringsSep "," cfg.zkConfigConnect;
|
||||
zkconfigexhibitorpath = cfg.zkConfigExhibitorPath;
|
||||
zkconfigpollms = toString cfg.zkConfigPollMs;
|
||||
zkconfigretry = "${toString cfg.zkConfigRetry.sleepMs}:${toString cfg.zkConfigRetry.retryQuantity}";
|
||||
zkconfigzpath = cfg.zkConfigZPath;
|
||||
zkconfigexhibitorport = toString cfg.zkConfigExhibitorPort; # NB: This might be null
|
||||
};
|
||||
file = {
|
||||
fsconfigdir = cfg.fsConfigDir;
|
||||
fsconfiglockprefix = cfg.fsConfigLockPrefix;
|
||||
fsConfigName = fsConfigName;
|
||||
};
|
||||
none = {
|
||||
noneconfigdir = configDir;
|
||||
};
|
||||
};
|
||||
cliOptions = concatStringsSep " " (mapAttrsToList (k: v: "--${k} ${v}") (filterAttrs (k: v: v != null && v != "") (cliOptionsCommon //
|
||||
cliOptionsPerConfig.${cfg.configType} //
|
||||
s3CommonOptions //
|
||||
optionalAttrs cfg.s3Backup { s3backup = "true"; } //
|
||||
optionalAttrs cfg.fileSystemBackup { filesystembackup = "true"; }
|
||||
)));
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.exhibitor = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "
|
||||
Whether to enable the exhibitor server.
|
||||
";
|
||||
};
|
||||
# See https://github.com/soabase/exhibitor/wiki/Running-Exhibitor for what these mean
|
||||
# General options for any type of config
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 8080;
|
||||
description = ''
|
||||
The port for exhibitor to listen on and communicate with other exhibitors.
|
||||
'';
|
||||
};
|
||||
baseDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/exhibitor";
|
||||
description = ''
|
||||
Baseline directory for exhibitor runtime config.
|
||||
'';
|
||||
};
|
||||
configType = mkOption {
|
||||
type = types.enum [ "file" "s3" "zookeeper" "none" ];
|
||||
description = ''
|
||||
Which configuration type you want to use. Additional config will be
|
||||
required depending on which type you are using.
|
||||
'';
|
||||
};
|
||||
hostname = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Hostname to use and advertise
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
nodeModification = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether the Explorer UI will allow nodes to be modified (use with caution).
|
||||
'';
|
||||
default = true;
|
||||
};
|
||||
configCheckMs = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
Period (ms) to check for shared config updates.
|
||||
'';
|
||||
default = 30000;
|
||||
};
|
||||
headingText = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Extra text to display in UI header
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
jqueryStyle = mkOption {
|
||||
type = types.enum [ "red" "black" "custom" ];
|
||||
description = ''
|
||||
Styling used for the JQuery-based UI.
|
||||
'';
|
||||
default = "red";
|
||||
};
|
||||
logLines = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
Max lines of logging to keep in memory for display.
|
||||
'';
|
||||
default = 1000;
|
||||
};
|
||||
servo = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
ZooKeeper will be queried once a minute for its state via the 'mntr' four
|
||||
letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish
|
||||
this data via JMX.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
timeout = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
Connection timeout (ms) for ZK connections.
|
||||
'';
|
||||
default = 30000;
|
||||
};
|
||||
autoManageInstances = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Automatically manage ZooKeeper instances in the ensemble
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
zkDataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.baseDir}/zkData";
|
||||
defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkData"'';
|
||||
description = ''
|
||||
The Zookeeper data directory
|
||||
'';
|
||||
};
|
||||
zkLogDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.baseDir}/zkLogs";
|
||||
defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkLogs"'';
|
||||
description = ''
|
||||
The Zookeeper logs directory
|
||||
'';
|
||||
};
|
||||
extraConf = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra Exhibitor configuration to put in the ZooKeeper config file.
|
||||
'';
|
||||
};
|
||||
zkExtraCfg = mkOption {
|
||||
type = types.str;
|
||||
default = "initLimit=5&syncLimit=2&tickTime=2000";
|
||||
description = ''
|
||||
Extra options to pass into Zookeeper
|
||||
'';
|
||||
};
|
||||
zkClientPort = mkOption {
|
||||
type = types.int;
|
||||
default = 2181;
|
||||
description = ''
|
||||
Zookeeper client port
|
||||
'';
|
||||
};
|
||||
zkConnectPort = mkOption {
|
||||
type = types.int;
|
||||
default = 2888;
|
||||
description = ''
|
||||
The port to use for followers to talk to each other.
|
||||
'';
|
||||
};
|
||||
zkElectionPort = mkOption {
|
||||
type = types.int;
|
||||
default = 3888;
|
||||
description = ''
|
||||
The port for Zookeepers to use for leader election.
|
||||
'';
|
||||
};
|
||||
zkCleanupPeriod = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
How often (in milliseconds) to run the Zookeeper log cleanup task.
|
||||
'';
|
||||
};
|
||||
zkServersSpec = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Zookeeper server spec for all servers in the ensemble.
|
||||
'';
|
||||
example = [ "S:1:zk1.example.com" "S:2:zk2.example.com" "S:3:zk3.example.com" "O:4:zk-observer.example.com" ];
|
||||
};
|
||||
|
||||
# Backup options
|
||||
s3Backup = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable backups to S3
|
||||
'';
|
||||
};
|
||||
fileSystemBackup = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enables file system backup of ZooKeeper log files
|
||||
'';
|
||||
};
|
||||
|
||||
# Options for using zookeeper configType
|
||||
zkConfigConnect = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
The initial connection string for ZooKeeper shared config storage
|
||||
'';
|
||||
example = ["host1:2181" "host2:2181"];
|
||||
};
|
||||
zkConfigExhibitorPath = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call
|
||||
'';
|
||||
default = "/";
|
||||
};
|
||||
zkConfigExhibitorPort = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
description = ''
|
||||
If the ZooKeeper shared config is also running Exhibitor, the port that
|
||||
Exhibitor is listening on. IMPORTANT: if this value is not set it implies
|
||||
that Exhibitor is not being used on the ZooKeeper shared config.
|
||||
'';
|
||||
};
|
||||
zkConfigPollMs = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
The period in ms to check for changes in the config ensemble
|
||||
'';
|
||||
default = 10000;
|
||||
};
|
||||
zkConfigRetry = {
|
||||
sleepMs = mkOption {
|
||||
type = types.int;
|
||||
default = 1000;
|
||||
description = ''
|
||||
Retry sleep time connecting to the ZooKeeper config
|
||||
'';
|
||||
};
|
||||
retryQuantity = mkOption {
|
||||
type = types.int;
|
||||
default = 3;
|
||||
description = ''
|
||||
Retries connecting to the ZooKeeper config
|
||||
'';
|
||||
};
|
||||
};
|
||||
zkConfigZPath = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The base ZPath that Exhibitor should use
|
||||
'';
|
||||
example = "/exhibitor/config";
|
||||
};
|
||||
|
||||
# Config options for s3 configType
|
||||
s3Config = {
|
||||
bucketName = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Bucket name to store config
|
||||
'';
|
||||
};
|
||||
objectKey = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
S3 key name to store the config
|
||||
'';
|
||||
};
|
||||
configPrefix = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
When using AWS S3 shared config files, the prefix to use for values such as locks
|
||||
'';
|
||||
default = "exhibitor-";
|
||||
};
|
||||
};
|
||||
|
||||
# The next two are used for either s3backup or s3 configType
|
||||
s3Credentials = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
description = ''
|
||||
Optional credentials to use for s3backup or s3config. Argument is the path
|
||||
to an AWS credential properties file with two properties:
|
||||
com.netflix.exhibitor.s3.access-key-id and com.netflix.exhibitor.s3.access-secret-key
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
s3Region = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Optional region for S3 calls
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
# Config options for file config type
|
||||
fsConfigDir = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Directory to store Exhibitor properties (cannot be used with s3config).
|
||||
Exhibitor uses file system locks so you can specify a shared location
|
||||
so as to enable complete ensemble management.
|
||||
'';
|
||||
};
|
||||
fsConfigLockPrefix = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
A prefix for a locking mechanism used in conjunction with fsconfigdir
|
||||
'';
|
||||
default = "exhibitor-lock-";
|
||||
};
|
||||
fsConfigName = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The name of the file to store config in
|
||||
'';
|
||||
default = "exhibitor.properties";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.exhibitor = {
|
||||
description = "Exhibitor Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
environment = {
|
||||
ZOO_LOG_DIR = cfg.baseDir;
|
||||
};
|
||||
serviceConfig = {
|
||||
/***
|
||||
Exhibitor is a bit un-nixy. It wants to present to you a user interface in order to
|
||||
mutate the configuration of both itself and ZooKeeper, and to coordinate changes
|
||||
among the members of the Zookeeper ensemble. I'm going for a different approach here,
|
||||
which is to manage all the configuration via nix and have it write out the configuration
|
||||
files that exhibitor will use, and to reduce the amount of inter-exhibitor orchestration.
|
||||
***/
|
||||
ExecStart = ''
|
||||
${pkgs.exhibitor}/bin/startExhibitor.sh ${cliOptions}
|
||||
'';
|
||||
User = "zookeeper";
|
||||
PermissionsStartOnly = true;
|
||||
};
|
||||
# This is a bit wonky, but the reason for this is that Exhibitor tries to write to
|
||||
# ${cfg.baseDir}/zookeeper/bin/../conf/zoo.cfg
|
||||
# I want everything but the conf directory to be in the immutable nix store, and I want defaults
|
||||
# from the nix store
|
||||
# If I symlink the bin directory in, then bin/../ will resolve to the parent of the symlink in the
|
||||
# immutable nix store. Bind mounting a writable conf over the existing conf might work, but it gets very
|
||||
# messy with trying to copy the existing out into a mutable store.
|
||||
# Another option is to try to patch upstream exhibitor, but the current package just pulls down the
|
||||
# prebuild JARs off of Maven, rather than building them ourselves, as Maven support in Nix isn't
|
||||
# very mature. So, it seems like a reasonable compromise is to just copy out of the immutable store
|
||||
# just before starting the service, so we're running binaries from the immutable store, but we work around
|
||||
# Exhibitor's desire to mutate its current installation.
|
||||
preStart = ''
|
||||
mkdir -m 0700 -p ${cfg.baseDir}/zookeeper
|
||||
# Not doing a chown -R to keep the base ZK files owned by root
|
||||
chown zookeeper ${cfg.baseDir} ${cfg.baseDir}/zookeeper
|
||||
cp -Rf ${pkgs.zookeeper}/* ${cfg.baseDir}/zookeeper
|
||||
chown -R zookeeper ${cfg.baseDir}/zookeeper/conf
|
||||
chmod -R u+w ${cfg.baseDir}/zookeeper/conf
|
||||
replace_what=$(echo ${pkgs.zookeeper} | sed 's/[\/&]/\\&/g')
|
||||
replace_with=$(echo ${cfg.baseDir}/zookeeper | sed 's/[\/&]/\\&/g')
|
||||
sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh
|
||||
'';
|
||||
};
|
||||
users.users.zookeeper = {
|
||||
uid = config.ids.uids.zookeeper;
|
||||
description = "Zookeeper daemon user";
|
||||
home = cfg.baseDir;
|
||||
};
|
||||
};
|
||||
}
|
||||
104
nixos/modules/services/misc/felix.nix
Normal file
104
nixos/modules/services/misc/felix.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Felix server
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.felix;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.felix = {
|
||||
|
||||
enable = mkEnableOption "the Apache Felix OSGi service";
|
||||
|
||||
bundles = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [ pkgs.felix_remoteshell ];
|
||||
defaultText = literalExpression "[ pkgs.felix_remoteshell ]";
|
||||
description = "List of bundles that should be activated on startup";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "osgi";
|
||||
description = "User account under which Apache Felix runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "osgi";
|
||||
description = "Group account under which Apache Felix runs.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.groups.osgi.gid = config.ids.gids.osgi;
|
||||
|
||||
users.users.osgi =
|
||||
{ uid = config.ids.uids.osgi;
|
||||
description = "OSGi user";
|
||||
home = "/homeless-shelter";
|
||||
};
|
||||
|
||||
systemd.services.felix = {
|
||||
description = "Felix server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
# Initialise felix instance on first startup
|
||||
if [ ! -d /var/felix ]
|
||||
then
|
||||
# Symlink system files
|
||||
|
||||
mkdir -p /var/felix
|
||||
chown ${cfg.user}:${cfg.group} /var/felix
|
||||
|
||||
for i in ${pkgs.felix}/*
|
||||
do
|
||||
if [ "$i" != "${pkgs.felix}/bundle" ]
|
||||
then
|
||||
ln -sfn $i /var/felix/$(basename $i)
|
||||
fi
|
||||
done
|
||||
|
||||
# Symlink bundles
|
||||
mkdir -p /var/felix/bundle
|
||||
chown ${cfg.user}:${cfg.group} /var/felix/bundle
|
||||
|
||||
for i in ${pkgs.felix}/bundle/* ${toString cfg.bundles}
|
||||
do
|
||||
if [ -f $i ]
|
||||
then
|
||||
ln -sfn $i /var/felix/bundle/$(basename $i)
|
||||
elif [ -d $i ]
|
||||
then
|
||||
for j in $i/bundle/*
|
||||
do
|
||||
ln -sfn $j /var/felix/bundle/$(basename $j)
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
'';
|
||||
|
||||
script = ''
|
||||
cd /var/felix
|
||||
${pkgs.su}/bin/su -s ${pkgs.bash}/bin/sh ${cfg.user} -c '${pkgs.jre}/bin/java -jar bin/felix.jar'
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
104
nixos/modules/services/misc/freeswitch.nix
Normal file
104
nixos/modules/services/misc/freeswitch.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{ config, lib, pkgs, ...}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.freeswitch;
|
||||
pkg = cfg.package;
|
||||
configDirectory = pkgs.runCommand "freeswitch-config-d" { } ''
|
||||
mkdir -p $out
|
||||
cp -rT ${cfg.configTemplate} $out
|
||||
chmod -R +w $out
|
||||
${concatStringsSep "\n" (mapAttrsToList (fileName: filePath: ''
|
||||
mkdir -p $out/$(dirname ${fileName})
|
||||
cp ${filePath} $out/${fileName}
|
||||
'') cfg.configDir)}
|
||||
'';
|
||||
configPath = if cfg.enableReload
|
||||
then "/etc/freeswitch"
|
||||
else configDirectory;
|
||||
in {
|
||||
options = {
|
||||
services.freeswitch = {
|
||||
enable = mkEnableOption "FreeSWITCH";
|
||||
enableReload = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Issue the <literal>reloadxml</literal> command to FreeSWITCH when configuration directory changes (instead of restart).
|
||||
See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Reloading">FreeSWITCH documentation</link> for more info.
|
||||
The configuration directory is exposed at <filename>/etc/freeswitch</filename>.
|
||||
See also <literal>systemd.services.*.restartIfChanged</literal>.
|
||||
'';
|
||||
};
|
||||
configTemplate = mkOption {
|
||||
type = types.path;
|
||||
default = "${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
|
||||
defaultText = literalExpression ''"''${config.services.freeswitch.package}/share/freeswitch/conf/vanilla"'';
|
||||
example = literalExpression ''"''${config.services.freeswitch.package}/share/freeswitch/conf/minimal"'';
|
||||
description = ''
|
||||
Configuration template to use.
|
||||
See available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
|
||||
You can also set your own configuration directory.
|
||||
'';
|
||||
};
|
||||
configDir = mkOption {
|
||||
type = with types; attrsOf path;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"freeswitch.xml" = ./freeswitch.xml;
|
||||
"dialplan/default.xml" = pkgs.writeText "dialplan-default.xml" '''
|
||||
[xml lines]
|
||||
''';
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Override file in FreeSWITCH config template directory.
|
||||
Each top-level attribute denotes a file path in the configuration directory, its value is the file path.
|
||||
See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Default+Configuration">FreeSWITCH documentation</link> for more info.
|
||||
Also check available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
|
||||
'';
|
||||
};
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.freeswitch;
|
||||
defaultText = literalExpression "pkgs.freeswitch";
|
||||
description = ''
|
||||
FreeSWITCH package.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
environment.etc.freeswitch = mkIf cfg.enableReload {
|
||||
source = configDirectory;
|
||||
};
|
||||
systemd.services.freeswitch-config-reload = mkIf cfg.enableReload {
|
||||
before = [ "freeswitch.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ configDirectory ];
|
||||
serviceConfig = {
|
||||
ExecStart = "/run/current-system/systemd/bin/systemctl try-reload-or-restart freeswitch.service";
|
||||
RemainAfterExit = true;
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
systemd.services.freeswitch = {
|
||||
description = "Free and open-source application server for real-time communication";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "freeswitch";
|
||||
ExecStart = "${pkg}/bin/freeswitch -nf \\
|
||||
-mod ${pkg}/lib/freeswitch/mod \\
|
||||
-conf ${configPath} \\
|
||||
-base /var/lib/freeswitch";
|
||||
ExecReload = "${pkg}/bin/fs_cli -x reloadxml";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
CPUSchedulingPolicy = "fifo";
|
||||
};
|
||||
};
|
||||
environment.systemPackages = [ pkg ];
|
||||
};
|
||||
}
|
||||
46
nixos/modules/services/misc/fstrim.nix
Normal file
46
nixos/modules/services/misc/fstrim.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.fstrim;
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
services.fstrim = {
|
||||
enable = mkEnableOption "periodic SSD TRIM of mounted partitions in background";
|
||||
|
||||
interval = mkOption {
|
||||
type = types.str;
|
||||
default = "weekly";
|
||||
description = ''
|
||||
How often we run fstrim. For most desktop and server systems
|
||||
a sufficient trimming frequency is once a week.
|
||||
|
||||
The format is described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.packages = [ pkgs.util-linux ];
|
||||
|
||||
systemd.timers.fstrim = {
|
||||
timerConfig = {
|
||||
OnCalendar = cfg.interval;
|
||||
};
|
||||
wantedBy = [ "timers.target" ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ ];
|
||||
}
|
||||
253
nixos/modules/services/misc/gammu-smsd.nix
Normal file
253
nixos/modules/services/misc/gammu-smsd.nix
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
{ pkgs, lib, config, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.gammu-smsd;
|
||||
|
||||
configFile = pkgs.writeText "gammu-smsd.conf" ''
|
||||
[gammu]
|
||||
Device = ${cfg.device.path}
|
||||
Connection = ${cfg.device.connection}
|
||||
SynchronizeTime = ${if cfg.device.synchronizeTime then "yes" else "no"}
|
||||
LogFormat = ${cfg.log.format}
|
||||
${if (cfg.device.pin != null) then "PIN = ${cfg.device.pin}" else ""}
|
||||
${cfg.extraConfig.gammu}
|
||||
|
||||
|
||||
[smsd]
|
||||
LogFile = ${cfg.log.file}
|
||||
Service = ${cfg.backend.service}
|
||||
|
||||
${optionalString (cfg.backend.service == "files") ''
|
||||
InboxPath = ${cfg.backend.files.inboxPath}
|
||||
OutboxPath = ${cfg.backend.files.outboxPath}
|
||||
SentSMSPath = ${cfg.backend.files.sentSMSPath}
|
||||
ErrorSMSPath = ${cfg.backend.files.errorSMSPath}
|
||||
''}
|
||||
|
||||
${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "sqlite") ''
|
||||
Driver = ${cfg.backend.sql.driver}
|
||||
DBDir = ${cfg.backend.sql.database}
|
||||
''}
|
||||
|
||||
${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "native_pgsql") (
|
||||
with cfg.backend; ''
|
||||
Driver = ${sql.driver}
|
||||
${if (sql.database!= null) then "Database = ${sql.database}" else ""}
|
||||
${if (sql.host != null) then "Host = ${sql.host}" else ""}
|
||||
${if (sql.user != null) then "User = ${sql.user}" else ""}
|
||||
${if (sql.password != null) then "Password = ${sql.password}" else ""}
|
||||
'')}
|
||||
|
||||
${cfg.extraConfig.smsd}
|
||||
'';
|
||||
|
||||
initDBDir = "share/doc/gammu/examples/sql";
|
||||
|
||||
gammuPackage = with cfg.backend; (pkgs.gammu.override {
|
||||
dbiSupport = (service == "sql" && sql.driver == "sqlite");
|
||||
postgresSupport = (service == "sql" && sql.driver == "native_pgsql");
|
||||
});
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.gammu-smsd = {
|
||||
|
||||
enable = mkEnableOption "gammu-smsd daemon";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "smsd";
|
||||
description = "User that has access to the device";
|
||||
};
|
||||
|
||||
device = {
|
||||
path = mkOption {
|
||||
type = types.path;
|
||||
description = "Device node or address of the phone";
|
||||
example = "/dev/ttyUSB2";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "root";
|
||||
description = "Owner group of the device";
|
||||
example = "dialout";
|
||||
};
|
||||
|
||||
connection = mkOption {
|
||||
type = types.str;
|
||||
default = "at";
|
||||
description = "Protocol which will be used to talk to the phone";
|
||||
};
|
||||
|
||||
synchronizeTime = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to set time from computer to the phone during starting connection";
|
||||
};
|
||||
|
||||
pin = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "PIN code for the simcard";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
log = {
|
||||
file = mkOption {
|
||||
type = types.str;
|
||||
default = "syslog";
|
||||
description = "Path to file where information about communication will be stored";
|
||||
};
|
||||
|
||||
format = mkOption {
|
||||
type = types.enum [ "nothing" "text" "textall" "textalldate" "errors" "errorsdate" "binary" ];
|
||||
default = "errors";
|
||||
description = "Determines what will be logged to the LogFile";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
extraConfig = {
|
||||
gammu = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Extra config lines to be added into [gammu] section";
|
||||
};
|
||||
|
||||
|
||||
smsd = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Extra config lines to be added into [smsd] section";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
backend = {
|
||||
service = mkOption {
|
||||
type = types.enum [ "null" "files" "sql" ];
|
||||
default = "null";
|
||||
description = "Service to use to store sms data.";
|
||||
};
|
||||
|
||||
files = {
|
||||
inboxPath = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/spool/sms/inbox/";
|
||||
description = "Where the received SMSes are stored";
|
||||
};
|
||||
|
||||
outboxPath = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/spool/sms/outbox/";
|
||||
description = "Where SMSes to be sent should be placed";
|
||||
};
|
||||
|
||||
sentSMSPath = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/spool/sms/sent/";
|
||||
description = "Where the transmitted SMSes are placed";
|
||||
};
|
||||
|
||||
errorSMSPath = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/spool/sms/error/";
|
||||
description = "Where SMSes with error in transmission is placed";
|
||||
};
|
||||
};
|
||||
|
||||
sql = {
|
||||
driver = mkOption {
|
||||
type = types.enum [ "native_mysql" "native_pgsql" "odbc" "dbi" ];
|
||||
description = "DB driver to use";
|
||||
};
|
||||
|
||||
sqlDialect = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "SQL dialect to use (odbc driver only)";
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Database name to store sms data";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Database server address";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "User name used for connection to the database";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "User password used for connetion to the database";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.users.${cfg.user} = {
|
||||
description = "gammu-smsd user";
|
||||
isSystemUser = true;
|
||||
group = cfg.device.group;
|
||||
};
|
||||
|
||||
environment.systemPackages = with cfg.backend; [ gammuPackage ]
|
||||
++ optionals (service == "sql" && sql.driver == "sqlite") [ pkgs.sqlite ];
|
||||
|
||||
systemd.services.gammu-smsd = {
|
||||
description = "gammu-smsd daemon";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
wants = with cfg.backend; [ ]
|
||||
++ optionals (service == "sql" && sql.driver == "native_pgsql") [ "postgresql.service" ];
|
||||
|
||||
preStart = with cfg.backend;
|
||||
|
||||
optionalString (service == "files") (with files; ''
|
||||
mkdir -m 755 -p ${inboxPath} ${outboxPath} ${sentSMSPath} ${errorSMSPath}
|
||||
chown ${cfg.user} -R ${inboxPath}
|
||||
chown ${cfg.user} -R ${outboxPath}
|
||||
chown ${cfg.user} -R ${sentSMSPath}
|
||||
chown ${cfg.user} -R ${errorSMSPath}
|
||||
'')
|
||||
+ optionalString (service == "sql" && sql.driver == "sqlite") ''
|
||||
cat "${gammuPackage}/${initDBDir}/sqlite.sql" \
|
||||
| ${pkgs.sqlite.bin}/bin/sqlite3 ${sql.database}
|
||||
''
|
||||
+ (let execPsql = extraArgs: concatStringsSep " " [
|
||||
(optionalString (sql.password != null) "PGPASSWORD=${sql.password}")
|
||||
"${config.services.postgresql.package}/bin/psql"
|
||||
(optionalString (sql.host != null) "-h ${sql.host}")
|
||||
(optionalString (sql.user != null) "-U ${sql.user}")
|
||||
"$extraArgs"
|
||||
"${sql.database}"
|
||||
]; in optionalString (service == "sql" && sql.driver == "native_pgsql") ''
|
||||
echo '\i '"${gammuPackage}/${initDBDir}/pgsql.sql" | ${execPsql ""}
|
||||
'');
|
||||
|
||||
serviceConfig = {
|
||||
User = "${cfg.user}";
|
||||
Group = "${cfg.device.group}";
|
||||
PermissionsStartOnly = true;
|
||||
ExecStart = "${gammuPackage}/bin/gammu-smsd -c ${configFile}";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
187
nixos/modules/services/misc/geoipupdate.nix
Normal file
187
nixos/modules/services/misc/geoipupdate.nix
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.geoipupdate;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(lib.mkRemovedOptionModule [ "services" "geoip-updater" ] "services.geoip-updater has been removed, use services.geoipupdate instead.")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.geoipupdate = {
|
||||
enable = lib.mkEnableOption ''
|
||||
periodic downloading of GeoIP databases using
|
||||
<productname>geoipupdate</productname>.
|
||||
'';
|
||||
|
||||
interval = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "weekly";
|
||||
description = ''
|
||||
Update the GeoIP databases at this time / interval.
|
||||
The format is described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
description = ''
|
||||
<productname>geoipupdate</productname> configuration
|
||||
options. See
|
||||
<link xlink:href="https://github.com/maxmind/geoipupdate/blob/main/doc/GeoIP.conf.md" />
|
||||
for a full list of available options.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
freeformType =
|
||||
with lib.types;
|
||||
let
|
||||
type = oneOf [str int bool];
|
||||
in
|
||||
attrsOf (either type (listOf type));
|
||||
|
||||
options = {
|
||||
|
||||
AccountID = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = ''
|
||||
Your MaxMind account ID.
|
||||
'';
|
||||
};
|
||||
|
||||
EditionIDs = lib.mkOption {
|
||||
type = with lib.types; listOf (either str int);
|
||||
example = [
|
||||
"GeoLite2-ASN"
|
||||
"GeoLite2-City"
|
||||
"GeoLite2-Country"
|
||||
];
|
||||
description = ''
|
||||
List of database edition IDs. This includes new string
|
||||
IDs like <literal>GeoIP2-City</literal> and old
|
||||
numeric IDs like <literal>106</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
LicenseKey = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
A file containing the <productname>MaxMind</productname>
|
||||
license key.
|
||||
'';
|
||||
};
|
||||
|
||||
DatabaseDirectory = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/GeoIP";
|
||||
example = "/run/GeoIP";
|
||||
description = ''
|
||||
The directory to store the database files in. The
|
||||
directory will be automatically created, the owner
|
||||
changed to <literal>geoip</literal> and permissions
|
||||
set to world readable. This applies if the directory
|
||||
already exists as well, so don't use a directory with
|
||||
sensitive contents.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
services.geoipupdate.settings = {
|
||||
LockFile = "/run/geoipupdate/.lock";
|
||||
};
|
||||
|
||||
systemd.services.geoipupdate-create-db-dir = {
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
mkdir -p ${cfg.settings.DatabaseDirectory}
|
||||
chmod 0755 ${cfg.settings.DatabaseDirectory}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.geoipupdate = {
|
||||
description = "GeoIP Updater";
|
||||
requires = [ "geoipupdate-create-db-dir.service" ];
|
||||
after = [
|
||||
"geoipupdate-create-db-dir.service"
|
||||
"network-online.target"
|
||||
"nss-lookup.target"
|
||||
];
|
||||
wants = [ "network-online.target" ];
|
||||
startAt = cfg.interval;
|
||||
serviceConfig = {
|
||||
ExecStartPre =
|
||||
let
|
||||
geoipupdateKeyValue = lib.generators.toKeyValue {
|
||||
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " " rec {
|
||||
mkValueString = v: with builtins;
|
||||
if isInt v then toString v
|
||||
else if isString v then v
|
||||
else if true == v then "1"
|
||||
else if false == v then "0"
|
||||
else if isList v then lib.concatMapStringsSep " " mkValueString v
|
||||
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
|
||||
};
|
||||
};
|
||||
|
||||
geoipupdateConf = pkgs.writeText "geoipupdate.conf" (geoipupdateKeyValue cfg.settings);
|
||||
|
||||
script = ''
|
||||
chown geoip "${cfg.settings.DatabaseDirectory}"
|
||||
|
||||
cp ${geoipupdateConf} /run/geoipupdate/GeoIP.conf
|
||||
${pkgs.replace-secret}/bin/replace-secret '${cfg.settings.LicenseKey}' \
|
||||
'${cfg.settings.LicenseKey}' \
|
||||
/run/geoipupdate/GeoIP.conf
|
||||
'';
|
||||
in
|
||||
"+${pkgs.writeShellScript "start-pre-full-privileges" script}";
|
||||
ExecStart = "${pkgs.geoipupdate}/bin/geoipupdate -f /run/geoipupdate/GeoIP.conf";
|
||||
User = "geoip";
|
||||
DynamicUser = true;
|
||||
ReadWritePaths = cfg.settings.DatabaseDirectory;
|
||||
RuntimeDirectory = "geoipupdate";
|
||||
RuntimeDirectoryMode = 0700;
|
||||
CapabilityBoundingSet = "";
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictRealtime = true;
|
||||
RestrictNamespaces = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
LockPersonality = true;
|
||||
SystemCallArchitectures = "native";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.geoipupdate-initial-run = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
unitConfig.ConditionPathExists = "!${cfg.settings.DatabaseDirectory}";
|
||||
timerConfig = {
|
||||
Unit = "geoipupdate.service";
|
||||
OnActiveSec = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = [ lib.maintainers.talyz ];
|
||||
}
|
||||
658
nixos/modules/services/misc/gitea.nix
Normal file
658
nixos/modules/services/misc/gitea.nix
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gitea;
|
||||
opt = options.services.gitea;
|
||||
gitea = cfg.package;
|
||||
pg = config.services.postgresql;
|
||||
useMysql = cfg.database.type == "mysql";
|
||||
usePostgresql = cfg.database.type == "postgres";
|
||||
useSqlite = cfg.database.type == "sqlite3";
|
||||
configFile = pkgs.writeText "app.ini" ''
|
||||
APP_NAME = ${cfg.appName}
|
||||
RUN_USER = ${cfg.user}
|
||||
RUN_MODE = prod
|
||||
|
||||
${generators.toINI {} cfg.settings}
|
||||
|
||||
${optionalString (cfg.extraConfig != null) cfg.extraConfig}
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.gitea = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Enable Gitea Service.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.gitea;
|
||||
type = types.package;
|
||||
defaultText = literalExpression "pkgs.gitea";
|
||||
description = "gitea derivation to use";
|
||||
};
|
||||
|
||||
useWizard = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
default = "/var/lib/gitea";
|
||||
type = types.str;
|
||||
description = "gitea data directory.";
|
||||
};
|
||||
|
||||
log = {
|
||||
rootPath = mkOption {
|
||||
default = "${cfg.stateDir}/log";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
|
||||
type = types.str;
|
||||
description = "Root path for log files.";
|
||||
};
|
||||
level = mkOption {
|
||||
default = "Info";
|
||||
type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
|
||||
description = "General log level.";
|
||||
};
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "gitea";
|
||||
description = "User account under which gitea runs.";
|
||||
};
|
||||
|
||||
database = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "sqlite3" "mysql" "postgres" ];
|
||||
example = "mysql";
|
||||
default = "sqlite3";
|
||||
description = "Database engine to use.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Database host address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = (if !usePostgresql then 3306 else pg.port);
|
||||
defaultText = literalExpression ''
|
||||
if config.${opt.database.type} != "postgresql"
|
||||
then 3306
|
||||
else config.${options.services.postgresql.port}
|
||||
'';
|
||||
description = "Database host port.";
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "gitea";
|
||||
description = "Database name.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "gitea";
|
||||
description = "Database user.";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
The password corresponding to <option>database.user</option>.
|
||||
Warning: this is stored in cleartext in the Nix store!
|
||||
Use <option>database.passwordFile</option> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/gitea-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
<option>database.user</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
socket = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
|
||||
defaultText = literalExpression "null";
|
||||
example = "/run/mysqld/mysqld.sock";
|
||||
description = "Path to the unix socket file to use for authentication.";
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/data/gitea.db";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/gitea.db"'';
|
||||
description = "Path to the sqlite3 database file.";
|
||||
};
|
||||
|
||||
createDatabase = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to create a local database automatically.";
|
||||
};
|
||||
};
|
||||
|
||||
dump = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable a timer that runs gitea dump to generate backup-files of the
|
||||
current gitea database and repositories.
|
||||
'';
|
||||
};
|
||||
|
||||
interval = mkOption {
|
||||
type = types.str;
|
||||
default = "04:31";
|
||||
example = "hourly";
|
||||
description = ''
|
||||
Run a gitea dump at this interval. Runs by default at 04:31 every day.
|
||||
|
||||
The format is described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>.
|
||||
'';
|
||||
};
|
||||
|
||||
backupDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/dump";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"'';
|
||||
description = "Path to the dump files.";
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = types.enum [ "zip" "rar" "tar" "sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" ];
|
||||
default = "zip";
|
||||
description = "Archive format used to store the dump file.";
|
||||
};
|
||||
|
||||
file = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Filename to be used for the dump. If `null` a default name is choosen by gitea.";
|
||||
example = "gitea-dump";
|
||||
};
|
||||
};
|
||||
|
||||
ssh = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable external SSH feature.";
|
||||
};
|
||||
|
||||
clonePort = mkOption {
|
||||
type = types.int;
|
||||
default = 22;
|
||||
example = 2222;
|
||||
description = ''
|
||||
SSH port displayed in clone URL.
|
||||
The option is required to configure a service when the external visible port
|
||||
differs from the local listening port i.e. if port forwarding is used.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
lfs = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enables git-lfs support.";
|
||||
};
|
||||
|
||||
contentDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/data/lfs";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"'';
|
||||
description = "Where to store LFS files.";
|
||||
};
|
||||
};
|
||||
|
||||
appName = mkOption {
|
||||
type = types.str;
|
||||
default = "gitea: Gitea Service";
|
||||
description = "Application name.";
|
||||
};
|
||||
|
||||
repositoryRoot = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/repositories";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
|
||||
description = "Path to the git repositories.";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Domain name of your server.";
|
||||
};
|
||||
|
||||
rootUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "http://localhost:3000/";
|
||||
description = "Full public URL of gitea server.";
|
||||
};
|
||||
|
||||
httpAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "HTTP listen address.";
|
||||
};
|
||||
|
||||
httpPort = mkOption {
|
||||
type = types.int;
|
||||
default = 3000;
|
||||
description = "HTTP listen port.";
|
||||
};
|
||||
|
||||
enableUnixSocket = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Configure Gitea to listen on a unix socket instead of the default TCP port.";
|
||||
};
|
||||
|
||||
cookieSecure = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Marks session cookies as "secure" as a hint for browsers to only send
|
||||
them via HTTPS. This option is recommend, if gitea is being served over HTTPS.
|
||||
'';
|
||||
};
|
||||
|
||||
staticRootPath = mkOption {
|
||||
type = types.either types.str types.path;
|
||||
default = gitea.data;
|
||||
defaultText = literalExpression "package.data";
|
||||
example = "/var/lib/gitea/data";
|
||||
description = "Upper level of template and static files path.";
|
||||
};
|
||||
|
||||
mailerPasswordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/var/lib/secrets/gitea/mailpw";
|
||||
description = "Path to a file containing the SMTP password.";
|
||||
};
|
||||
|
||||
disableRegistration = mkEnableOption "the registration lock" // {
|
||||
description = ''
|
||||
By default any user can create an account on this <literal>gitea</literal> instance.
|
||||
This can be disabled by using this option.
|
||||
|
||||
<emphasis>Note:</emphasis> please keep in mind that this should be added after the initial
|
||||
deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link>
|
||||
is <literal>true</literal> as the first registered user will be the administrator if
|
||||
no install wizard is used.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
|
||||
default = {};
|
||||
description = ''
|
||||
Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
"cron.sync_external_users" = {
|
||||
RUN_AT_START = true;
|
||||
SCHEDULE = "@every 24h";
|
||||
UPDATE_EXISTING = true;
|
||||
};
|
||||
mailer = {
|
||||
ENABLED = true;
|
||||
MAILER_TYPE = "sendmail";
|
||||
FROM = "do-not-reply@example.org";
|
||||
SENDMAIL_PATH = "''${pkgs.system-sendmail}/bin/sendmail";
|
||||
};
|
||||
other = {
|
||||
SHOW_FOOTER_VERSION = false;
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "Configuration lines appended to the generated gitea configuration file.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{ assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
|
||||
message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
|
||||
}
|
||||
];
|
||||
|
||||
services.gitea.settings = {
|
||||
database = mkMerge [
|
||||
{
|
||||
DB_TYPE = cfg.database.type;
|
||||
}
|
||||
(mkIf (useMysql || usePostgresql) {
|
||||
HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
|
||||
NAME = cfg.database.name;
|
||||
USER = cfg.database.user;
|
||||
PASSWD = "#dbpass#";
|
||||
})
|
||||
(mkIf useSqlite {
|
||||
PATH = cfg.database.path;
|
||||
})
|
||||
(mkIf usePostgresql {
|
||||
SSL_MODE = "disable";
|
||||
})
|
||||
];
|
||||
|
||||
repository = {
|
||||
ROOT = cfg.repositoryRoot;
|
||||
};
|
||||
|
||||
server = mkMerge [
|
||||
{
|
||||
DOMAIN = cfg.domain;
|
||||
STATIC_ROOT_PATH = toString cfg.staticRootPath;
|
||||
LFS_JWT_SECRET = "#lfsjwtsecret#";
|
||||
ROOT_URL = cfg.rootUrl;
|
||||
}
|
||||
(mkIf cfg.enableUnixSocket {
|
||||
PROTOCOL = "unix";
|
||||
HTTP_ADDR = "/run/gitea/gitea.sock";
|
||||
})
|
||||
(mkIf (!cfg.enableUnixSocket) {
|
||||
HTTP_ADDR = cfg.httpAddress;
|
||||
HTTP_PORT = cfg.httpPort;
|
||||
})
|
||||
(mkIf cfg.ssh.enable {
|
||||
DISABLE_SSH = false;
|
||||
SSH_PORT = cfg.ssh.clonePort;
|
||||
})
|
||||
(mkIf (!cfg.ssh.enable) {
|
||||
DISABLE_SSH = true;
|
||||
})
|
||||
(mkIf cfg.lfs.enable {
|
||||
LFS_START_SERVER = true;
|
||||
LFS_CONTENT_PATH = cfg.lfs.contentDir;
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
session = {
|
||||
COOKIE_NAME = "session";
|
||||
COOKIE_SECURE = cfg.cookieSecure;
|
||||
};
|
||||
|
||||
security = {
|
||||
SECRET_KEY = "#secretkey#";
|
||||
INTERNAL_TOKEN = "#internaltoken#";
|
||||
INSTALL_LOCK = true;
|
||||
};
|
||||
|
||||
log = {
|
||||
ROOT_PATH = cfg.log.rootPath;
|
||||
LEVEL = cfg.log.level;
|
||||
};
|
||||
|
||||
service = {
|
||||
DISABLE_REGISTRATION = cfg.disableRegistration;
|
||||
};
|
||||
|
||||
mailer = mkIf (cfg.mailerPasswordFile != null) {
|
||||
PASSWD = "#mailerpass#";
|
||||
};
|
||||
|
||||
oauth2 = {
|
||||
JWT_SECRET = "#oauth2jwtsecret#";
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
|
||||
enable = mkDefault true;
|
||||
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{ name = cfg.database.user;
|
||||
ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
|
||||
enable = mkDefault true;
|
||||
package = mkDefault pkgs.mariadb;
|
||||
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{ name = cfg.database.user;
|
||||
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
|
||||
"Z '${cfg.dump.backupDir}' - ${cfg.user} gitea - -"
|
||||
"d '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
|
||||
"Z '${cfg.lfs.contentDir}' - ${cfg.user} gitea - -"
|
||||
"d '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
|
||||
"Z '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
|
||||
"d '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
|
||||
"d '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
|
||||
"d '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
|
||||
"d '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
|
||||
"d '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} gitea - -"
|
||||
"z '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
|
||||
"z '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
|
||||
"Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
|
||||
|
||||
# If we have a folder or symlink with gitea locales, remove it
|
||||
# And symlink the current gitea locales in place
|
||||
"L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
|
||||
];
|
||||
|
||||
systemd.services.gitea = {
|
||||
description = "gitea";
|
||||
after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ gitea pkgs.git ];
|
||||
|
||||
# In older versions the secret naming for JWT was kind of confusing.
|
||||
# The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
|
||||
# wasn't persistant at all.
|
||||
# To fix that, there is now the file oauth2_jwt_secret containing the
|
||||
# values for JWT_SECRET and the file jwt_secret gets renamed to
|
||||
# lfs_jwt_secret.
|
||||
# We have to consider this to stay compatible with older installations.
|
||||
preStart = let
|
||||
runConfig = "${cfg.stateDir}/custom/conf/app.ini";
|
||||
secretKey = "${cfg.stateDir}/custom/conf/secret_key";
|
||||
oauth2JwtSecret = "${cfg.stateDir}/custom/conf/oauth2_jwt_secret";
|
||||
oldLfsJwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret"; # old file for LFS_JWT_SECRET
|
||||
lfsJwtSecret = "${cfg.stateDir}/custom/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
|
||||
internalToken = "${cfg.stateDir}/custom/conf/internal_token";
|
||||
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
|
||||
in ''
|
||||
# copy custom configuration and generate a random secret key if needed
|
||||
${optionalString (cfg.useWizard == false) ''
|
||||
function gitea_setup {
|
||||
cp -f ${configFile} ${runConfig}
|
||||
|
||||
if [ ! -e ${secretKey} ]; then
|
||||
${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
|
||||
fi
|
||||
|
||||
# Migrate LFS_JWT_SECRET filename
|
||||
if [[ -e ${oldLfsJwtSecret} && ! -e ${lfsJwtSecret} ]]; then
|
||||
mv ${oldLfsJwtSecret} ${lfsJwtSecret}
|
||||
fi
|
||||
|
||||
if [ ! -e ${oauth2JwtSecret} ]; then
|
||||
${gitea}/bin/gitea generate secret JWT_SECRET > ${oauth2JwtSecret}
|
||||
fi
|
||||
|
||||
if [ ! -e ${lfsJwtSecret} ]; then
|
||||
${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
|
||||
fi
|
||||
|
||||
if [ ! -e ${internalToken} ]; then
|
||||
${gitea}/bin/gitea generate secret INTERNAL_TOKEN > ${internalToken}
|
||||
fi
|
||||
|
||||
chmod u+w '${runConfig}'
|
||||
${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
|
||||
${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
|
||||
${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
|
||||
${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
|
||||
${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
|
||||
|
||||
${lib.optionalString (cfg.mailerPasswordFile != null) ''
|
||||
${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
|
||||
''}
|
||||
chmod u-w '${runConfig}'
|
||||
}
|
||||
(umask 027; gitea_setup)
|
||||
''}
|
||||
|
||||
# run migrations/init the database
|
||||
${gitea}/bin/gitea migrate
|
||||
|
||||
# update all hooks' binary paths
|
||||
${gitea}/bin/gitea admin regenerate hooks
|
||||
|
||||
# update command option in authorized_keys
|
||||
if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
|
||||
then
|
||||
${gitea}/bin/gitea admin regenerate keys
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = "gitea";
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
ExecStart = "${gitea}/bin/gitea web --pid /run/gitea/gitea.pid";
|
||||
Restart = "always";
|
||||
# Runtime directory and mode
|
||||
RuntimeDirectory = "gitea";
|
||||
RuntimeDirectoryMode = "0755";
|
||||
# Access write directories
|
||||
ReadWritePaths = [ cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
|
||||
UMask = "0027";
|
||||
# Capabilities
|
||||
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 @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
|
||||
};
|
||||
|
||||
environment = {
|
||||
USER = cfg.user;
|
||||
HOME = cfg.stateDir;
|
||||
GITEA_WORK_DIR = cfg.stateDir;
|
||||
};
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "gitea") {
|
||||
gitea = {
|
||||
description = "Gitea Service";
|
||||
home = cfg.stateDir;
|
||||
useDefaultShell = true;
|
||||
group = "gitea";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups.gitea = {};
|
||||
|
||||
warnings =
|
||||
optional (cfg.database.password != "") "config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead." ++
|
||||
optional (cfg.extraConfig != null) ''
|
||||
services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`.
|
||||
'';
|
||||
|
||||
# Create database passwordFile default when password is configured.
|
||||
services.gitea.database.passwordFile =
|
||||
(mkDefault (toString (pkgs.writeTextFile {
|
||||
name = "gitea-database-password";
|
||||
text = cfg.database.password;
|
||||
})));
|
||||
|
||||
systemd.services.gitea-dump = mkIf cfg.dump.enable {
|
||||
description = "gitea dump";
|
||||
after = [ "gitea.service" ];
|
||||
wantedBy = [ "default.target" ];
|
||||
path = [ gitea ];
|
||||
|
||||
environment = {
|
||||
USER = cfg.user;
|
||||
HOME = cfg.stateDir;
|
||||
GITEA_WORK_DIR = cfg.stateDir;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = cfg.user;
|
||||
ExecStart = "${gitea}/bin/gitea dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
|
||||
WorkingDirectory = cfg.dump.backupDir;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.gitea-dump = mkIf cfg.dump.enable {
|
||||
description = "Update timer for gitea-dump";
|
||||
partOf = [ "gitea-dump.service" ];
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig.OnCalendar = cfg.dump.interval;
|
||||
};
|
||||
};
|
||||
meta.maintainers = with lib.maintainers; [ srhb ma27 ];
|
||||
}
|
||||
725
nixos/modules/services/misc/gitit.nix
Normal file
725
nixos/modules/services/misc/gitit.nix
Normal file
|
|
@ -0,0 +1,725 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.gitit;
|
||||
|
||||
homeDir = "/var/lib/gitit";
|
||||
|
||||
toYesNo = b: if b then "yes" else "no";
|
||||
|
||||
gititShared = with cfg.haskellPackages; gitit + "/share/" + ghc.targetPrefix + ghc.haskellCompilerName + "/" + gitit.pname + "-" + gitit.version;
|
||||
|
||||
gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self));
|
||||
|
||||
gititSh = hsPkgs: extras: with pkgs; let
|
||||
env = gititWithPkgs hsPkgs extras;
|
||||
in writeScript "gitit" ''
|
||||
#!${runtimeShell}
|
||||
cd $HOME
|
||||
export NIX_GHC="${env}/bin/ghc"
|
||||
export NIX_GHCPKG="${env}/bin/ghc-pkg"
|
||||
export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html"
|
||||
export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir )
|
||||
${env}/bin/gitit -f ${configFile}
|
||||
'';
|
||||
|
||||
gititOptions = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable the gitit service.";
|
||||
};
|
||||
|
||||
haskellPackages = mkOption {
|
||||
default = pkgs.haskellPackages;
|
||||
defaultText = literalExpression "pkgs.haskellPackages";
|
||||
example = literalExpression "pkgs.haskell.packages.ghc784";
|
||||
description = "haskellPackages used to build gitit and plugins.";
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = types.functionTo (types.listOf types.package);
|
||||
default = self: [];
|
||||
example = literalExpression ''
|
||||
haskellPackages: [
|
||||
haskellPackages.wreq
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Extra packages available to ghc when running gitit. The
|
||||
value must be a function which receives the attrset defined
|
||||
in <varname>haskellPackages</varname> as the sole argument.
|
||||
'';
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "IP address on which the web server will listen.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 5001;
|
||||
description = "Port on which the web server will run.";
|
||||
};
|
||||
|
||||
wikiTitle = mkOption {
|
||||
type = types.str;
|
||||
default = "Gitit!";
|
||||
description = "The wiki title.";
|
||||
};
|
||||
|
||||
repositoryType = mkOption {
|
||||
type = types.enum ["git" "darcs" "mercurial"];
|
||||
default = "git";
|
||||
description = "Specifies the type of repository used for wiki content.";
|
||||
};
|
||||
|
||||
repositoryPath = mkOption {
|
||||
type = types.path;
|
||||
default = homeDir + "/wiki";
|
||||
description = ''
|
||||
Specifies the path of the repository directory. If it does not
|
||||
exist, gitit will create it on startup.
|
||||
'';
|
||||
};
|
||||
|
||||
requireAuthentication = mkOption {
|
||||
type = types.enum [ "none" "modify" "read" ];
|
||||
default = "modify";
|
||||
description = ''
|
||||
If 'none', login is never required, and pages can be edited
|
||||
anonymously. If 'modify', login is required to modify the wiki
|
||||
(edit, add, delete pages, upload files). If 'read', login is
|
||||
required to see any wiki pages.
|
||||
'';
|
||||
};
|
||||
|
||||
authenticationMethod = mkOption {
|
||||
type = types.enum [ "form" "http" "generic" "github" ];
|
||||
default = "form";
|
||||
description = ''
|
||||
'form' means that users will be logged in and registered using forms
|
||||
in the gitit web interface. 'http' means that gitit will assume that
|
||||
HTTP authentication is in place and take the logged in username from
|
||||
the "Authorization" field of the HTTP request header (in addition,
|
||||
the login/logout and registration links will be suppressed).
|
||||
'generic' means that gitit will assume that some form of
|
||||
authentication is in place that directly sets REMOTE_USER to the name
|
||||
of the authenticated user (e.g. mod_auth_cas on apache). 'rpx' means
|
||||
that gitit will attempt to log in through https://rpxnow.com. This
|
||||
requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below,
|
||||
and that 'curl' be in the system path.
|
||||
'';
|
||||
};
|
||||
|
||||
userFile = mkOption {
|
||||
type = types.path;
|
||||
default = homeDir + "/gitit-users";
|
||||
description = ''
|
||||
Specifies the path of the file containing user login information. If
|
||||
it does not exist, gitit will create it (with an empty user list).
|
||||
This file is not used if 'http' is selected for
|
||||
authentication-method.
|
||||
'';
|
||||
};
|
||||
|
||||
sessionTimeout = mkOption {
|
||||
type = types.int;
|
||||
default = 60;
|
||||
description = ''
|
||||
Number of minutes of inactivity before a session expires.
|
||||
'';
|
||||
};
|
||||
|
||||
staticDir = mkOption {
|
||||
type = types.path;
|
||||
default = gititShared + "/data/static";
|
||||
description = ''
|
||||
Specifies the path of the static directory (containing javascript,
|
||||
css, and images). If it does not exist, gitit will create it and
|
||||
populate it with required scripts, stylesheets, and images.
|
||||
'';
|
||||
};
|
||||
|
||||
defaultPageType = mkOption {
|
||||
type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ];
|
||||
default = "markdown";
|
||||
description = ''
|
||||
Specifies the type of markup used to interpret pages in the wiki.
|
||||
Possible values are markdown, rst, latex, html, markdown+lhs,
|
||||
rst+lhs, and latex+lhs. (the +lhs variants treat the input as
|
||||
literate Haskell. See pandoc's documentation for more details.) If
|
||||
Markdown is selected, pandoc's syntax extensions (for footnotes,
|
||||
delimited code blocks, etc.) will be enabled. Note that pandoc's
|
||||
restructuredtext parser is not complete, so some pages may not be
|
||||
rendered correctly if rst is selected. The same goes for latex and
|
||||
html.
|
||||
'';
|
||||
};
|
||||
|
||||
math = mkOption {
|
||||
type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ];
|
||||
default = "mathml";
|
||||
description = ''
|
||||
Specifies how LaTeX math is to be displayed. Possible values are
|
||||
mathml, raw, mathjax, jsmath, and google. If mathml is selected,
|
||||
gitit will convert LaTeX math to MathML and link in a script,
|
||||
MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers,
|
||||
IE + mathplayer, and Opera. In other browsers you may get a jumble of
|
||||
characters. If raw is selected, the LaTeX math will be displayed as
|
||||
raw LaTeX math. If mathjax is selected, gitit will link to the
|
||||
remote mathjax script. If jsMath is selected, gitit will link to the
|
||||
script /js/jsMath/easy/load.js, and will assume that jsMath has been
|
||||
installed into the js/jsMath directory. This is the most portable
|
||||
solution. If google is selected, the google chart API is called to
|
||||
render the formula as an image. This requires a connection to google,
|
||||
and might raise a technical or a privacy problem.
|
||||
'';
|
||||
};
|
||||
|
||||
mathJaxScript = mkOption {
|
||||
type = types.str;
|
||||
default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
|
||||
description = ''
|
||||
Specifies the path to MathJax rendering script. You might want to
|
||||
use your own MathJax script to render formulas without Internet
|
||||
connection or if you want to use some special LaTeX packages. Note:
|
||||
path specified there cannot be an absolute path to a script on your
|
||||
hdd, instead you should run your (local if you wish) HTTP server
|
||||
which will serve the MathJax.js script. You can easily (in four lines
|
||||
of code) serve MathJax.js using
|
||||
http://happstack.com/docs/crashcourse/FileServing.html Do not forget
|
||||
the "http://" prefix (e.g. http://localhost:1234/MathJax.js).
|
||||
'';
|
||||
};
|
||||
|
||||
showLhsBirdTracks = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Specifies whether to show Haskell code blocks in "bird style", with
|
||||
"> " at the beginning of each line.
|
||||
'';
|
||||
};
|
||||
|
||||
templatesDir = mkOption {
|
||||
type = types.path;
|
||||
default = gititShared + "/data/templates";
|
||||
description = ''
|
||||
Specifies the path of the directory containing page templates. If it
|
||||
does not exist, gitit will create it with default templates. Users
|
||||
may wish to edit the templates to customize the appearance of their
|
||||
wiki. The template files are HStringTemplate templates. Variables to
|
||||
be interpolated appear between $\'s. Literal $\'s must be
|
||||
backslash-escaped.
|
||||
'';
|
||||
};
|
||||
|
||||
logFile = mkOption {
|
||||
type = types.path;
|
||||
default = homeDir + "/gitit.log";
|
||||
description = ''
|
||||
Specifies the path of gitit's log file. If it does not exist, gitit
|
||||
will create it. The log is in Apache combined log format.
|
||||
'';
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ];
|
||||
default = "ERROR";
|
||||
description = ''
|
||||
Determines how much information is logged. Possible values (from
|
||||
most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR,
|
||||
CRITICAL, ALERT, EMERGENCY.
|
||||
'';
|
||||
};
|
||||
|
||||
frontPage = mkOption {
|
||||
type = types.str;
|
||||
default = "Front Page";
|
||||
description = ''
|
||||
Specifies which wiki page is to be used as the wiki's front page.
|
||||
Gitit creates a default front page on startup, if one does not exist
|
||||
already.
|
||||
'';
|
||||
};
|
||||
|
||||
noDelete = mkOption {
|
||||
type = types.str;
|
||||
default = "Front Page, Help";
|
||||
description = ''
|
||||
Specifies pages that cannot be deleted through the web interface.
|
||||
(They can still be deleted directly using git or darcs.) A
|
||||
comma-separated list of page names. Leave blank to allow every page
|
||||
to be deleted.
|
||||
'';
|
||||
};
|
||||
|
||||
noEdit = mkOption {
|
||||
type = types.str;
|
||||
default = "Help";
|
||||
description = ''
|
||||
Specifies pages that cannot be edited through the web interface.
|
||||
Leave blank to allow every page to be edited.
|
||||
'';
|
||||
};
|
||||
|
||||
defaultSummary = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Specifies text to be used in the change description if the author
|
||||
leaves the "description" field blank. If default-summary is blank
|
||||
(the default), the author will be required to fill in the description
|
||||
field.
|
||||
'';
|
||||
};
|
||||
|
||||
tableOfContents = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Specifies whether to print a tables of contents (with links to
|
||||
sections) on each wiki page.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ (gititShared + "/plugins/Dot.hs") ];
|
||||
description = ''
|
||||
Specifies a list of plugins to load. Plugins may be specified either
|
||||
by their path or by their module name. If the plugin name starts
|
||||
with Gitit.Plugin., gitit will assume that the plugin is an installed
|
||||
module and will not try to find a source file.
|
||||
'';
|
||||
};
|
||||
|
||||
useCache = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Specifies whether to cache rendered pages. Note that if use-feed is
|
||||
selected, feeds will be cached regardless of the value of use-cache.
|
||||
'';
|
||||
};
|
||||
|
||||
cacheDir = mkOption {
|
||||
type = types.path;
|
||||
default = homeDir + "/cache";
|
||||
description = "Path where rendered pages will be cached.";
|
||||
};
|
||||
|
||||
maxUploadSize = mkOption {
|
||||
type = types.str;
|
||||
default = "1000K";
|
||||
description = ''
|
||||
Specifies an upper limit on the size (in bytes) of files uploaded
|
||||
through the wiki's web interface. To disable uploads, set this to
|
||||
0K. This will result in the uploads link disappearing and the
|
||||
_upload url becoming inactive.
|
||||
'';
|
||||
};
|
||||
|
||||
maxPageSize = mkOption {
|
||||
type = types.str;
|
||||
default = "1000K";
|
||||
description = "Specifies an upper limit on the size (in bytes) of pages.";
|
||||
};
|
||||
|
||||
debugMode = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Causes debug information to be logged while gitit is running.";
|
||||
};
|
||||
|
||||
compressResponses = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Specifies whether HTTP responses should be compressed.";
|
||||
};
|
||||
|
||||
mimeTypesFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/mime/types.info";
|
||||
description = ''
|
||||
Specifies the path of a file containing mime type mappings. Each
|
||||
line of the file should contain two fields, separated by whitespace.
|
||||
The first field is the mime type, the second is a file extension.
|
||||
For example:
|
||||
<programlisting>
|
||||
video/x-ms-wmx wmx
|
||||
</programlisting>
|
||||
If the file is not found, some simple defaults will be used.
|
||||
'';
|
||||
};
|
||||
|
||||
useReCaptcha = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If true, causes gitit to use the reCAPTCHA service
|
||||
(http://recaptcha.net) to prevent bots from creating accounts.
|
||||
'';
|
||||
};
|
||||
|
||||
reCaptchaPrivateKey = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Specifies the private key for the reCAPTCHA service. To get
|
||||
these, you need to create an account at http://recaptcha.net.
|
||||
'';
|
||||
};
|
||||
|
||||
reCaptchaPublicKey = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Specifies the public key for the reCAPTCHA service. To get
|
||||
these, you need to create an account at http://recaptcha.net.
|
||||
'';
|
||||
};
|
||||
|
||||
accessQuestion = mkOption {
|
||||
type = types.str;
|
||||
default = "What is the code given to you by Ms. X?";
|
||||
description = ''
|
||||
Specifies a question that users must answer when they attempt to
|
||||
create an account
|
||||
'';
|
||||
};
|
||||
|
||||
accessQuestionAnswers = mkOption {
|
||||
type = types.str;
|
||||
default = "RED DOG, red dog";
|
||||
description = ''
|
||||
Specifies a question that users must answer when they attempt to
|
||||
create an account, along with a comma-separated list of acceptable
|
||||
answers. This can be used to institute a rudimentary password for
|
||||
signing up as a user on the wiki, or as an alternative to reCAPTCHA.
|
||||
Example:
|
||||
access-question: What is the code given to you by Ms. X?
|
||||
access-question-answers: RED DOG, red dog
|
||||
'';
|
||||
};
|
||||
|
||||
rpxDomain = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Specifies the domain and key of your RPX account. The domain is just
|
||||
the prefix of the complete RPX domain, so if your full domain is
|
||||
'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain.
|
||||
'';
|
||||
};
|
||||
|
||||
rpxKey = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "RPX account access key.";
|
||||
};
|
||||
|
||||
mailCommand = mkOption {
|
||||
type = types.str;
|
||||
default = "sendmail %s";
|
||||
description = ''
|
||||
Specifies the command to use to send notification emails. '%s' will
|
||||
be replaced by the destination email address. The body of the
|
||||
message will be read from stdin. If this field is left blank,
|
||||
password reset will not be offered.
|
||||
'';
|
||||
};
|
||||
|
||||
resetPasswordMessage = mkOption {
|
||||
type = types.lines;
|
||||
default = ''
|
||||
> From: gitit@$hostname$
|
||||
> To: $useremail$
|
||||
> Subject: Wiki password reset
|
||||
>
|
||||
> Hello $username$,
|
||||
>
|
||||
> To reset your password, please follow the link below:
|
||||
> http://$hostname$:$port$$resetlink$
|
||||
>
|
||||
> Regards
|
||||
'';
|
||||
description = ''
|
||||
Gives the text of the message that will be sent to the user should
|
||||
she want to reset her password, or change other registration info.
|
||||
The lines must be indented, and must begin with '>'. The initial
|
||||
spaces and '> ' will be stripped off. $username$ will be replaced by
|
||||
the user's username, $useremail$ by her email address, $hostname$ by
|
||||
the hostname on which the wiki is running (as returned by the
|
||||
hostname system call), $port$ by the port on which the wiki is
|
||||
running, and $resetlink$ by the relative path of a reset link derived
|
||||
from the user's existing hashed password. If your gitit wiki is being
|
||||
proxied to a location other than the root path of $port$, you should
|
||||
change the link to reflect this: for example, to
|
||||
http://$hostname$/path/to/wiki$resetlink$ or
|
||||
http://gitit.$hostname$$resetlink$
|
||||
'';
|
||||
};
|
||||
|
||||
useFeed = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Specifies whether an ATOM feed should be enabled (for the site and
|
||||
for individual pages).
|
||||
'';
|
||||
};
|
||||
|
||||
baseUrl = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The base URL of the wiki, to be used in constructing feed IDs and RPX
|
||||
token_urls. Set this if useFeed is false or authentication-method
|
||||
is 'rpx'.
|
||||
'';
|
||||
};
|
||||
|
||||
absoluteUrls = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Make wikilinks absolute with respect to the base-url. So, for
|
||||
example, in a wiki served at the base URL '/wiki', on a page
|
||||
Sub/Page, the wikilink '[Cactus]()' will produce a link to
|
||||
'/wiki/Cactus' if absoluteUrls is true, and a relative link to
|
||||
'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'.
|
||||
'';
|
||||
};
|
||||
|
||||
feedDays = mkOption {
|
||||
type = types.int;
|
||||
default = 14;
|
||||
description = "Number of days to be included in feeds.";
|
||||
};
|
||||
|
||||
feedRefreshTime = mkOption {
|
||||
type = types.int;
|
||||
default = 60;
|
||||
description = "Number of minutes to cache feeds before refreshing.";
|
||||
};
|
||||
|
||||
pdfExport = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If true, PDF will appear in export options. PDF will be created using
|
||||
pdflatex, which must be installed and in the path. Note that PDF
|
||||
exports create significant additional server load.
|
||||
'';
|
||||
};
|
||||
|
||||
pandocUserData = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
If a directory is specified, this will be searched for pandoc
|
||||
customizations. These can include a templates/ directory for custom
|
||||
templates for various export formats, an S5 directory for custom S5
|
||||
styles, and a reference.odt for ODT exports. If no directory is
|
||||
specified, $HOME/.pandoc will be searched. See pandoc's README for
|
||||
more information.
|
||||
'';
|
||||
};
|
||||
|
||||
xssSanitize = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If true, all HTML (including that produced by pandoc) is filtered
|
||||
through xss-sanitize. Set to no only if you trust all of your users.
|
||||
'';
|
||||
};
|
||||
|
||||
oauthClientId = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "OAuth client ID";
|
||||
};
|
||||
|
||||
oauthClientSecret = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "OAuth client secret";
|
||||
};
|
||||
|
||||
oauthCallback = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "OAuth callback URL";
|
||||
};
|
||||
|
||||
oauthAuthorizeEndpoint = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "OAuth authorize endpoint";
|
||||
};
|
||||
|
||||
oauthAccessTokenEndpoint = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "OAuth access token endpoint";
|
||||
};
|
||||
|
||||
githubOrg = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "Github organization";
|
||||
};
|
||||
};
|
||||
|
||||
configFile = pkgs.writeText "gitit.conf" ''
|
||||
address: ${cfg.address}
|
||||
port: ${toString cfg.port}
|
||||
wiki-title: ${cfg.wikiTitle}
|
||||
repository-type: ${cfg.repositoryType}
|
||||
repository-path: ${cfg.repositoryPath}
|
||||
require-authentication: ${cfg.requireAuthentication}
|
||||
authentication-method: ${cfg.authenticationMethod}
|
||||
user-file: ${cfg.userFile}
|
||||
session-timeout: ${toString cfg.sessionTimeout}
|
||||
static-dir: ${cfg.staticDir}
|
||||
default-page-type: ${cfg.defaultPageType}
|
||||
math: ${cfg.math}
|
||||
mathjax-script: ${cfg.mathJaxScript}
|
||||
show-lhs-bird-tracks: ${toYesNo cfg.showLhsBirdTracks}
|
||||
templates-dir: ${cfg.templatesDir}
|
||||
log-file: ${cfg.logFile}
|
||||
log-level: ${cfg.logLevel}
|
||||
front-page: ${cfg.frontPage}
|
||||
no-delete: ${cfg.noDelete}
|
||||
no-edit: ${cfg.noEdit}
|
||||
default-summary: ${cfg.defaultSummary}
|
||||
table-of-contents: ${toYesNo cfg.tableOfContents}
|
||||
plugins: ${concatStringsSep "," cfg.plugins}
|
||||
use-cache: ${toYesNo cfg.useCache}
|
||||
cache-dir: ${cfg.cacheDir}
|
||||
max-upload-size: ${cfg.maxUploadSize}
|
||||
max-page-size: ${cfg.maxPageSize}
|
||||
debug-mode: ${toYesNo cfg.debugMode}
|
||||
compress-responses: ${toYesNo cfg.compressResponses}
|
||||
mime-types-file: ${cfg.mimeTypesFile}
|
||||
use-recaptcha: ${toYesNo cfg.useReCaptcha}
|
||||
recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey}
|
||||
recaptcha-public-key: ${toString cfg.reCaptchaPublicKey}
|
||||
access-question: ${cfg.accessQuestion}
|
||||
access-question-answers: ${cfg.accessQuestionAnswers}
|
||||
rpx-domain: ${toString cfg.rpxDomain}
|
||||
rpx-key: ${toString cfg.rpxKey}
|
||||
mail-command: ${cfg.mailCommand}
|
||||
reset-password-message: ${cfg.resetPasswordMessage}
|
||||
use-feed: ${toYesNo cfg.useFeed}
|
||||
base-url: ${toString cfg.baseUrl}
|
||||
absolute-urls: ${toYesNo cfg.absoluteUrls}
|
||||
feed-days: ${toString cfg.feedDays}
|
||||
feed-refresh-time: ${toString cfg.feedRefreshTime}
|
||||
pdf-export: ${toYesNo cfg.pdfExport}
|
||||
pandoc-user-data: ${toString cfg.pandocUserData}
|
||||
xss-sanitize: ${toYesNo cfg.xssSanitize}
|
||||
|
||||
[Github]
|
||||
oauthclientid: ${toString cfg.oauthClientId}
|
||||
oauthclientsecret: ${toString cfg.oauthClientSecret}
|
||||
oauthcallback: ${toString cfg.oauthCallback}
|
||||
oauthauthorizeendpoint: ${toString cfg.oauthAuthorizeEndpoint}
|
||||
oauthaccesstokenendpoint: ${toString cfg.oauthAccessTokenEndpoint}
|
||||
github-org: ${toString cfg.githubOrg}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options.services.gitit = gititOptions;
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.gitit = {
|
||||
group = config.users.groups.gitit.name;
|
||||
description = "Gitit user";
|
||||
home = homeDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.gitit;
|
||||
};
|
||||
|
||||
users.groups.gitit.gid = config.ids.gids.gitit;
|
||||
|
||||
systemd.services.gitit = let
|
||||
uid = toString config.ids.uids.gitit;
|
||||
gid = toString config.ids.gids.gitit;
|
||||
in {
|
||||
description = "Git and Pandoc Powered Wiki";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [ curl ]
|
||||
++ optional cfg.pdfExport texlive.combined.scheme-basic
|
||||
++ optional (cfg.repositoryType == "darcs") darcs
|
||||
++ optional (cfg.repositoryType == "mercurial") mercurial
|
||||
++ optional (cfg.repositoryType == "git") git;
|
||||
|
||||
preStart = let
|
||||
gm = "gitit@${config.networking.hostName}";
|
||||
in
|
||||
with cfg; ''
|
||||
chown ${uid}:${gid} -R ${homeDir}
|
||||
for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir}
|
||||
do
|
||||
if [ ! -d $dir ]
|
||||
then
|
||||
mkdir -p $dir
|
||||
find $dir -type d -exec chmod 0750 {} +
|
||||
find $dir -type f -exec chmod 0640 {} +
|
||||
fi
|
||||
done
|
||||
cd ${repositoryPath}
|
||||
${
|
||||
if repositoryType == "darcs" then
|
||||
''
|
||||
if [ ! -d _darcs ]
|
||||
then
|
||||
${pkgs.darcs}/bin/darcs initialize
|
||||
echo "${gm}" > _darcs/prefs/email
|
||||
''
|
||||
else if repositoryType == "mercurial" then
|
||||
''
|
||||
if [ ! -d .hg ]
|
||||
then
|
||||
${pkgs.mercurial}/bin/hg init
|
||||
cat >> .hg/hgrc <<NAMED
|
||||
[ui]
|
||||
username = gitit ${gm}
|
||||
NAMED
|
||||
''
|
||||
else
|
||||
''
|
||||
if [ ! -d .git ]
|
||||
then
|
||||
${pkgs.git}/bin/git init
|
||||
${pkgs.git}/bin/git config user.email "${gm}"
|
||||
${pkgs.git}/bin/git config user.name "gitit"
|
||||
''}
|
||||
chown ${uid}:${gid} -R ${repositoryPath}
|
||||
fi
|
||||
cd -
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
User = config.users.users.gitit.name;
|
||||
Group = config.users.groups.gitit.name;
|
||||
ExecStart = with cfg; gititSh haskellPackages extraPackages;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
1460
nixos/modules/services/misc/gitlab.nix
Normal file
1460
nixos/modules/services/misc/gitlab.nix
Normal file
File diff suppressed because it is too large
Load diff
151
nixos/modules/services/misc/gitlab.xml
Normal file
151
nixos/modules/services/misc/gitlab.xml
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<chapter xmlns="http://docbook.org/ns/docbook"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
version="5.0"
|
||||
xml:id="module-services-gitlab">
|
||||
<title>GitLab</title>
|
||||
<para>
|
||||
GitLab is a feature-rich git hosting service.
|
||||
</para>
|
||||
<section xml:id="module-services-gitlab-prerequisites">
|
||||
<title>Prerequisites</title>
|
||||
|
||||
<para>
|
||||
The <literal>gitlab</literal> service exposes only an Unix socket at
|
||||
<literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to
|
||||
configure a webserver to proxy HTTP requests to the socket.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For instance, the following configuration could be used to use nginx as
|
||||
frontend proxy:
|
||||
<programlisting>
|
||||
<link linkend="opt-services.nginx.enable">services.nginx</link> = {
|
||||
<link linkend="opt-services.nginx.enable">enable</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
|
||||
<link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
|
||||
<link linkend="opt-services.nginx.virtualHosts">virtualHosts</link>."git.example.com" = {
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/".proxyPass</link> = "http://unix:/run/gitlab/gitlab-workhorse.socket";
|
||||
};
|
||||
};
|
||||
</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
<section xml:id="module-services-gitlab-configuring">
|
||||
<title>Configuring</title>
|
||||
|
||||
<para>
|
||||
GitLab depends on both PostgreSQL and Redis and will automatically enable
|
||||
both services. In the case of PostgreSQL, a database and a role will be
|
||||
created.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The default state dir is <literal>/var/gitlab/state</literal>. This is where
|
||||
all data like the repositories and uploads will be stored.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A basic configuration with some custom settings could look like this:
|
||||
<programlisting>
|
||||
services.gitlab = {
|
||||
<link linkend="opt-services.gitlab.enable">enable</link> = true;
|
||||
<link linkend="opt-services.gitlab.databasePasswordFile">databasePasswordFile</link> = "/var/keys/gitlab/db_password";
|
||||
<link linkend="opt-services.gitlab.initialRootPasswordFile">initialRootPasswordFile</link> = "/var/keys/gitlab/root_password";
|
||||
<link linkend="opt-services.gitlab.https">https</link> = true;
|
||||
<link linkend="opt-services.gitlab.host">host</link> = "git.example.com";
|
||||
<link linkend="opt-services.gitlab.port">port</link> = 443;
|
||||
<link linkend="opt-services.gitlab.user">user</link> = "git";
|
||||
<link linkend="opt-services.gitlab.group">group</link> = "git";
|
||||
smtp = {
|
||||
<link linkend="opt-services.gitlab.smtp.enable">enable</link> = true;
|
||||
<link linkend="opt-services.gitlab.smtp.address">address</link> = "localhost";
|
||||
<link linkend="opt-services.gitlab.smtp.port">port</link> = 25;
|
||||
};
|
||||
secrets = {
|
||||
<link linkend="opt-services.gitlab.secrets.dbFile">dbFile</link> = "/var/keys/gitlab/db";
|
||||
<link linkend="opt-services.gitlab.secrets.secretFile">secretFile</link> = "/var/keys/gitlab/secret";
|
||||
<link linkend="opt-services.gitlab.secrets.otpFile">otpFile</link> = "/var/keys/gitlab/otp";
|
||||
<link linkend="opt-services.gitlab.secrets.jwsFile">jwsFile</link> = "/var/keys/gitlab/jws";
|
||||
};
|
||||
<link linkend="opt-services.gitlab.extraConfig">extraConfig</link> = {
|
||||
gitlab = {
|
||||
email_from = "gitlab-no-reply@example.com";
|
||||
email_display_name = "Example GitLab";
|
||||
email_reply_to = "gitlab-no-reply@example.com";
|
||||
default_projects_features = { builds = false; };
|
||||
};
|
||||
};
|
||||
};
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If you're setting up a new GitLab instance, generate new
|
||||
secrets. You for instance use <literal>tr -dc A-Za-z0-9 <
|
||||
/dev/urandom | head -c 128 > /var/keys/gitlab/db</literal> to
|
||||
generate a new db secret. Make sure the files can be read by, and
|
||||
only by, the user specified by <link
|
||||
linkend="opt-services.gitlab.user">services.gitlab.user</link>. GitLab
|
||||
encrypts sensitive data stored in the database. If you're restoring
|
||||
an existing GitLab instance, you must specify the secrets secret
|
||||
from <literal>config/secrets.yml</literal> located in your GitLab
|
||||
state folder.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When <literal>incoming_mail.enabled</literal> is set to <literal>true</literal>
|
||||
in <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> an additional
|
||||
service called <literal>gitlab-mailroom</literal> is enabled for fetching incoming mail.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Refer to <xref linkend="ch-options" /> for all available configuration
|
||||
options for the
|
||||
<link linkend="opt-services.gitlab.enable">services.gitlab</link> module.
|
||||
</para>
|
||||
</section>
|
||||
<section xml:id="module-services-gitlab-maintenance">
|
||||
<title>Maintenance</title>
|
||||
|
||||
<section xml:id="module-services-gitlab-maintenance-backups">
|
||||
<title>Backups</title>
|
||||
<para>
|
||||
Backups can be configured with the options in <link
|
||||
linkend="opt-services.gitlab.backup.keepTime">services.gitlab.backup</link>. Use
|
||||
the <link
|
||||
linkend="opt-services.gitlab.backup.startAt">services.gitlab.backup.startAt</link>
|
||||
option to configure regular backups.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To run a manual backup, start the <literal>gitlab-backup</literal> service:
|
||||
<screen>
|
||||
<prompt>$ </prompt>systemctl start gitlab-backup.service
|
||||
</screen>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section xml:id="module-services-gitlab-maintenance-rake">
|
||||
<title>Rake tasks</title>
|
||||
|
||||
<para>
|
||||
You can run GitLab's rake tasks with <literal>gitlab-rake</literal>
|
||||
which will be available on the system when GitLab is enabled. You
|
||||
will have to run the command as the user that you configured to run
|
||||
GitLab with.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A list of all availabe rake tasks can be obtained by running:
|
||||
<screen>
|
||||
<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
|
||||
</screen>
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
234
nixos/modules/services/misc/gitolite.nix
Normal file
234
nixos/modules/services/misc/gitolite.nix
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gitolite;
|
||||
# Use writeTextDir to not leak Nix store hash into file name
|
||||
pubkeyFile = (pkgs.writeTextDir "gitolite-admin.pub" cfg.adminPubkey) + "/gitolite-admin.pub";
|
||||
hooks = lib.concatMapStrings (hook: "${hook} ") cfg.commonHooks;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.gitolite = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable gitolite management under the
|
||||
<literal>gitolite</literal> user. After
|
||||
switching to a configuration with Gitolite enabled, you can
|
||||
then run <literal>git clone
|
||||
gitolite@host:gitolite-admin.git</literal> to manage it further.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/gitolite";
|
||||
description = ''
|
||||
The gitolite home directory used to store all repositories. If left as the default value
|
||||
this directory will automatically be created before the gitolite server starts, otherwise
|
||||
the sysadmin is responsible for ensuring the directory exists with appropriate ownership
|
||||
and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
adminPubkey = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Initial administrative public key for Gitolite. This should
|
||||
be an SSH Public Key. Note that this key will only be used
|
||||
once, upon the first initialization of the Gitolite user.
|
||||
The key string cannot have any line breaks in it.
|
||||
'';
|
||||
};
|
||||
|
||||
enableGitAnnex = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable git-annex support. Uses the <literal>extraGitoliteRc</literal> option
|
||||
to apply the necessary configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
commonHooks = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = ''
|
||||
A list of custom git hooks that get copied to <literal>~/.gitolite/hooks/common</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
extraGitoliteRc = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = literalExpression ''
|
||||
'''
|
||||
$RC{UMASK} = 0027;
|
||||
$RC{SITE_INFO} = 'This is our private repository host';
|
||||
push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature
|
||||
@{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
|
||||
'''
|
||||
'';
|
||||
description = ''
|
||||
Extra configuration to append to the default <literal>~/.gitolite.rc</literal>.
|
||||
|
||||
This should be Perl code that modifies the <literal>%RC</literal>
|
||||
configuration variable. The default <literal>~/.gitolite.rc</literal>
|
||||
content is generated by invoking <literal>gitolite print-default-rc</literal>,
|
||||
and extra configuration from this option is appended to it. The result
|
||||
is placed to Nix store, and the <literal>~/.gitolite.rc</literal> file
|
||||
becomes a symlink to it.
|
||||
|
||||
If you already have a customized (or otherwise changed)
|
||||
<literal>~/.gitolite.rc</literal> file, NixOS will refuse to replace
|
||||
it with a symlink, and the `gitolite-init` initialization service
|
||||
will fail. In this situation, in order to use this option, you
|
||||
will need to take any customizations you may have in
|
||||
<literal>~/.gitolite.rc</literal>, convert them to appropriate Perl
|
||||
statements, add them to this option, and remove the file.
|
||||
|
||||
See also the <literal>enableGitAnnex</literal> option.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "gitolite";
|
||||
description = ''
|
||||
Gitolite user account. This is the username of the gitolite endpoint.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "gitolite";
|
||||
description = ''
|
||||
Primary group of the Gitolite user account.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (
|
||||
let
|
||||
manageGitoliteRc = cfg.extraGitoliteRc != "";
|
||||
rcDir = pkgs.runCommand "gitolite-rc" { preferLocalBuild = true; } rcDirScript;
|
||||
rcDirScript =
|
||||
''
|
||||
mkdir "$out"
|
||||
export HOME=temp-home
|
||||
mkdir -p "$HOME/.gitolite/logs" # gitolite can't run without it
|
||||
'${pkgs.gitolite}'/bin/gitolite print-default-rc >>"$out/gitolite.rc.default"
|
||||
cat <<END >>"$out/gitolite.rc"
|
||||
# This file is managed by NixOS.
|
||||
# Use services.gitolite options to control it.
|
||||
|
||||
END
|
||||
cat "$out/gitolite.rc.default" >>"$out/gitolite.rc"
|
||||
'' +
|
||||
optionalString (cfg.extraGitoliteRc != "") ''
|
||||
echo -n ${escapeShellArg ''
|
||||
|
||||
# Added by NixOS:
|
||||
${removeSuffix "\n" cfg.extraGitoliteRc}
|
||||
|
||||
# per perl rules, this should be the last line in such a file:
|
||||
1;
|
||||
''} >>"$out/gitolite.rc"
|
||||
'';
|
||||
in {
|
||||
services.gitolite.extraGitoliteRc = optionalString cfg.enableGitAnnex ''
|
||||
# Enable git-annex support:
|
||||
push( @{$RC{ENABLE}}, 'git-annex-shell ua');
|
||||
'';
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
description = "Gitolite user";
|
||||
home = cfg.dataDir;
|
||||
uid = config.ids.uids.gitolite;
|
||||
group = cfg.group;
|
||||
useDefaultShell = true;
|
||||
};
|
||||
users.groups.${cfg.group}.gid = config.ids.gids.gitolite;
|
||||
|
||||
systemd.services.gitolite-init = {
|
||||
description = "Gitolite initialization";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
unitConfig.RequiresMountsFor = cfg.dataDir;
|
||||
|
||||
environment = {
|
||||
GITOLITE_RC = ".gitolite.rc";
|
||||
GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
|
||||
};
|
||||
|
||||
serviceConfig = mkMerge [
|
||||
(mkIf (cfg.dataDir == "/var/lib/gitolite") {
|
||||
StateDirectory = "gitolite gitolite/.gitolite gitolite/.gitolite/logs";
|
||||
StateDirectoryMode = "0750";
|
||||
})
|
||||
{
|
||||
Type = "oneshot";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = "~";
|
||||
RemainAfterExit = true;
|
||||
}
|
||||
];
|
||||
|
||||
path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ];
|
||||
script =
|
||||
let
|
||||
rcSetupScriptIfCustomFile =
|
||||
if manageGitoliteRc then ''
|
||||
cat <<END
|
||||
<3>ERROR: NixOS can't apply declarative configuration
|
||||
<3>to your .gitolite.rc file, because it seems to be
|
||||
<3>already customized manually.
|
||||
<3>See the services.gitolite.extraGitoliteRc option
|
||||
<3>in "man configuration.nix" for more information.
|
||||
END
|
||||
# Not sure if the line below addresses the issue directly or just
|
||||
# adds a delay, but without it our error message often doesn't
|
||||
# show up in `systemctl status gitolite-init`.
|
||||
journalctl --flush
|
||||
exit 1
|
||||
'' else ''
|
||||
:
|
||||
'';
|
||||
rcSetupScriptIfDefaultFileOrStoreSymlink =
|
||||
if manageGitoliteRc then ''
|
||||
ln -sf "${rcDir}/gitolite.rc" "$GITOLITE_RC"
|
||||
'' else ''
|
||||
[[ -L "$GITOLITE_RC" ]] && rm -f "$GITOLITE_RC"
|
||||
'';
|
||||
in
|
||||
''
|
||||
if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) ||
|
||||
( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) ||
|
||||
( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] )
|
||||
then
|
||||
'' + rcSetupScriptIfDefaultFileOrStoreSymlink +
|
||||
''
|
||||
else
|
||||
'' + rcSetupScriptIfCustomFile +
|
||||
''
|
||||
fi
|
||||
|
||||
if [ ! -d repositories ]; then
|
||||
gitolite setup -pk ${pubkeyFile}
|
||||
fi
|
||||
if [ -n "${hooks}" ]; then
|
||||
cp -f ${hooks} .gitolite/hooks/common/
|
||||
chmod +x .gitolite/hooks/common/*
|
||||
fi
|
||||
gitolite setup # Upgrade if needed
|
||||
'';
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.gitolite pkgs.git ]
|
||||
++ optional cfg.enableGitAnnex pkgs.git-annex;
|
||||
});
|
||||
}
|
||||
60
nixos/modules/services/misc/gitweb.nix
Normal file
60
nixos/modules/services/misc/gitweb.nix
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gitweb;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options.services.gitweb = {
|
||||
|
||||
projectroot = mkOption {
|
||||
default = "/srv/git";
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to git projects (bare repositories) that should be served by
|
||||
gitweb. Must not end with a slash.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Verbatim configuration text appended to the generated gitweb.conf file.
|
||||
'';
|
||||
example = ''
|
||||
$feature{'highlight'}{'default'} = [1];
|
||||
$feature{'ctags'}{'default'} = [1];
|
||||
$feature{'avatar'}{'default'} = ['gravatar'];
|
||||
'';
|
||||
};
|
||||
|
||||
gitwebTheme = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Use an alternative theme for gitweb, strongly inspired by GitHub.
|
||||
'';
|
||||
};
|
||||
|
||||
gitwebConfigFile = mkOption {
|
||||
default = pkgs.writeText "gitweb.conf" ''
|
||||
# path to git projects (<project>.git)
|
||||
$projectroot = "${cfg.projectroot}";
|
||||
$highlight_bin = "${pkgs.highlight}/bin/highlight";
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
defaultText = literalDocBook "generated config file";
|
||||
type = types.path;
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ ];
|
||||
|
||||
}
|
||||
274
nixos/modules/services/misc/gogs.nix
Normal file
274
nixos/modules/services/misc/gogs.nix
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gogs;
|
||||
opt = options.services.gogs;
|
||||
configFile = pkgs.writeText "app.ini" ''
|
||||
APP_NAME = ${cfg.appName}
|
||||
RUN_USER = ${cfg.user}
|
||||
RUN_MODE = prod
|
||||
|
||||
[database]
|
||||
DB_TYPE = ${cfg.database.type}
|
||||
HOST = ${cfg.database.host}:${toString cfg.database.port}
|
||||
NAME = ${cfg.database.name}
|
||||
USER = ${cfg.database.user}
|
||||
PASSWD = #dbpass#
|
||||
PATH = ${cfg.database.path}
|
||||
|
||||
[repository]
|
||||
ROOT = ${cfg.repositoryRoot}
|
||||
|
||||
[server]
|
||||
DOMAIN = ${cfg.domain}
|
||||
HTTP_ADDR = ${cfg.httpAddress}
|
||||
HTTP_PORT = ${toString cfg.httpPort}
|
||||
ROOT_URL = ${cfg.rootUrl}
|
||||
|
||||
[session]
|
||||
COOKIE_NAME = session
|
||||
COOKIE_SECURE = ${boolToString cfg.cookieSecure}
|
||||
|
||||
[security]
|
||||
SECRET_KEY = #secretkey#
|
||||
INSTALL_LOCK = true
|
||||
|
||||
[log]
|
||||
ROOT_PATH = ${cfg.stateDir}/log
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.gogs = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Enable Go Git Service.";
|
||||
};
|
||||
|
||||
useWizard = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
default = "/var/lib/gogs";
|
||||
type = types.str;
|
||||
description = "Gogs data directory.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "gogs";
|
||||
description = "User account under which Gogs runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "gogs";
|
||||
description = "Group account under which Gogs runs.";
|
||||
};
|
||||
|
||||
database = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "sqlite3" "mysql" "postgres" ];
|
||||
example = "mysql";
|
||||
default = "sqlite3";
|
||||
description = "Database engine to use.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Database host address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 3306;
|
||||
description = "Database host port.";
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "gogs";
|
||||
description = "Database name.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "gogs";
|
||||
description = "Database user.";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
The password corresponding to <option>database.user</option>.
|
||||
Warning: this is stored in cleartext in the Nix store!
|
||||
Use <option>database.passwordFile</option> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/gogs-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
<option>database.user</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/data/gogs.db";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/gogs.db"'';
|
||||
description = "Path to the sqlite3 database file.";
|
||||
};
|
||||
};
|
||||
|
||||
appName = mkOption {
|
||||
type = types.str;
|
||||
default = "Gogs: Go Git Service";
|
||||
description = "Application name.";
|
||||
};
|
||||
|
||||
repositoryRoot = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/repositories";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
|
||||
description = "Path to the git repositories.";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Domain name of your server.";
|
||||
};
|
||||
|
||||
rootUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "http://localhost:3000/";
|
||||
description = "Full public URL of Gogs server.";
|
||||
};
|
||||
|
||||
httpAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "HTTP listen address.";
|
||||
};
|
||||
|
||||
httpPort = mkOption {
|
||||
type = types.int;
|
||||
default = 3000;
|
||||
description = "HTTP listen port.";
|
||||
};
|
||||
|
||||
cookieSecure = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Marks session cookies as "secure" as a hint for browsers to only send
|
||||
them via HTTPS. This option is recommend, if Gogs is being served over HTTPS.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Configuration lines appended to the generated Gogs configuration file.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.gogs = {
|
||||
description = "Gogs (Go Git Service)";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.gogs ];
|
||||
|
||||
preStart = let
|
||||
runConfig = "${cfg.stateDir}/custom/conf/app.ini";
|
||||
secretKey = "${cfg.stateDir}/custom/conf/secret_key";
|
||||
in ''
|
||||
mkdir -p ${cfg.stateDir}
|
||||
|
||||
# copy custom configuration and generate a random secret key if needed
|
||||
${optionalString (cfg.useWizard == false) ''
|
||||
mkdir -p ${cfg.stateDir}/custom/conf
|
||||
cp -f ${configFile} ${runConfig}
|
||||
|
||||
if [ ! -e ${secretKey} ]; then
|
||||
head -c 16 /dev/urandom | base64 > ${secretKey}
|
||||
fi
|
||||
|
||||
KEY=$(head -n1 ${secretKey})
|
||||
DBPASS=$(head -n1 ${cfg.database.passwordFile})
|
||||
sed -e "s,#secretkey#,$KEY,g" \
|
||||
-e "s,#dbpass#,$DBPASS,g" \
|
||||
-i ${runConfig}
|
||||
chmod 440 ${runConfig} ${secretKey}
|
||||
''}
|
||||
|
||||
mkdir -p ${cfg.repositoryRoot}
|
||||
# update all hooks' binary paths
|
||||
HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*")
|
||||
if [ "$HOOKS" ]
|
||||
then
|
||||
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs}/bin/gogs,g' $HOOKS
|
||||
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
|
||||
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
|
||||
sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
ExecStart = "${pkgs.gogs}/bin/gogs web";
|
||||
Restart = "always";
|
||||
};
|
||||
|
||||
environment = {
|
||||
USER = cfg.user;
|
||||
HOME = cfg.stateDir;
|
||||
GOGS_WORK_DIR = cfg.stateDir;
|
||||
};
|
||||
};
|
||||
|
||||
users = mkIf (cfg.user == "gogs") {
|
||||
users.gogs = {
|
||||
description = "Go Git Service";
|
||||
uid = config.ids.uids.gogs;
|
||||
group = "gogs";
|
||||
home = cfg.stateDir;
|
||||
createHome = true;
|
||||
shell = pkgs.bash;
|
||||
};
|
||||
groups.gogs.gid = config.ids.gids.gogs;
|
||||
};
|
||||
|
||||
warnings = optional (cfg.database.password != "")
|
||||
''config.services.gogs.database.password will be stored as plaintext
|
||||
in the Nix store. Use database.passwordFile instead.'';
|
||||
|
||||
# Create database passwordFile default when password is configured.
|
||||
services.gogs.database.passwordFile =
|
||||
(mkDefault (toString (pkgs.writeTextFile {
|
||||
name = "gogs-database-password";
|
||||
text = cfg.database.password;
|
||||
})));
|
||||
};
|
||||
}
|
||||
135
nixos/modules/services/misc/gollum.nix
Normal file
135
nixos/modules/services/misc/gollum.nix
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gollum;
|
||||
in
|
||||
|
||||
{
|
||||
options.services.gollum = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable the Gollum service.";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "IP address on which the web server will listen.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 4567;
|
||||
description = "Port on which the web server will run.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Content of the configuration file";
|
||||
};
|
||||
|
||||
mathjax = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable support for math rendering using MathJax";
|
||||
};
|
||||
|
||||
allowUploads = mkOption {
|
||||
type = types.nullOr (types.enum [ "dir" "page" ]);
|
||||
default = null;
|
||||
description = "Enable uploads of external files";
|
||||
};
|
||||
|
||||
user-icons = mkOption {
|
||||
type = types.nullOr (types.enum [ "gravatar" "identicon" ]);
|
||||
default = null;
|
||||
description = "User icons for history view";
|
||||
};
|
||||
|
||||
emoji = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Parse and interpret emoji tags";
|
||||
};
|
||||
|
||||
h1-title = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Use the first h1 as page title";
|
||||
};
|
||||
|
||||
no-edit = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable editing pages";
|
||||
};
|
||||
|
||||
branch = mkOption {
|
||||
type = types.str;
|
||||
default = "master";
|
||||
example = "develop";
|
||||
description = "Git branch to serve";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/gollum";
|
||||
description = "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.gollum = {
|
||||
group = config.users.users.gollum.name;
|
||||
description = "Gollum user";
|
||||
createHome = false;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.gollum = { };
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' - ${config.users.users.gollum.name} ${config.users.groups.gollum.name} - -"
|
||||
];
|
||||
|
||||
systemd.services.gollum = {
|
||||
description = "Gollum wiki";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.git ];
|
||||
|
||||
preStart = ''
|
||||
# This is safe to be run on an existing repo
|
||||
git init ${cfg.stateDir}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
User = config.users.users.gollum.name;
|
||||
Group = config.users.groups.gollum.name;
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
ExecStart = ''
|
||||
${pkgs.gollum}/bin/gollum \
|
||||
--port ${toString cfg.port} \
|
||||
--host ${cfg.address} \
|
||||
--config ${pkgs.writeText "gollum-config.rb" cfg.extraConfig} \
|
||||
--ref ${cfg.branch} \
|
||||
${optionalString cfg.mathjax "--mathjax"} \
|
||||
${optionalString cfg.emoji "--emoji"} \
|
||||
${optionalString cfg.h1-title "--h1-title"} \
|
||||
${optionalString cfg.no-edit "--no-edit"} \
|
||||
${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
|
||||
${optionalString (cfg.user-icons != null) "--user-icons ${cfg.user-icons}"} \
|
||||
${cfg.stateDir}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ erictapen bbenno ];
|
||||
}
|
||||
116
nixos/modules/services/misc/gpsd.nix
Normal file
116
nixos/modules/services/misc/gpsd.nix
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
uid = config.ids.uids.gpsd;
|
||||
gid = config.ids.gids.gpsd;
|
||||
cfg = config.services.gpsd;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.gpsd = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable `gpsd', a GPS service daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
device = mkOption {
|
||||
type = types.str;
|
||||
default = "/dev/ttyUSB0";
|
||||
description = ''
|
||||
A device may be a local serial device for GPS input, or a URL of the form:
|
||||
<literal>[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]</literal>
|
||||
in which case it specifies an input source for DGPS or ntrip data.
|
||||
'';
|
||||
};
|
||||
|
||||
readonly = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable the broken-device-safety, otherwise
|
||||
known as read-only mode. Some popular bluetooth and USB
|
||||
receivers lock up or become totally inaccessible when
|
||||
probed or reconfigured. This switch prevents gpsd from
|
||||
writing to a receiver. This means that gpsd cannot
|
||||
configure the receiver for optimal performance, but it
|
||||
also means that gpsd cannot break the receiver. A better
|
||||
solution would be for Bluetooth to not be so fragile. A
|
||||
platform independent method to identify
|
||||
serial-over-Bluetooth devices would also be nice.
|
||||
'';
|
||||
};
|
||||
|
||||
nowait = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
don't wait for client connects to poll GPS
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 2947;
|
||||
description = ''
|
||||
The port where to listen for TCP connections.
|
||||
'';
|
||||
};
|
||||
|
||||
debugLevel = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
The debugging level.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.gpsd =
|
||||
{ inherit uid;
|
||||
group = "gpsd";
|
||||
description = "gpsd daemon user";
|
||||
home = "/var/empty";
|
||||
};
|
||||
|
||||
users.groups.gpsd = { inherit gid; };
|
||||
|
||||
systemd.services.gpsd = {
|
||||
description = "GPSD daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = ''
|
||||
${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}" \
|
||||
-S "${toString cfg.port}" \
|
||||
${optionalString cfg.readonly "-b"} \
|
||||
${optionalString cfg.nowait "-n"} \
|
||||
"${cfg.device}"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
31
nixos/modules/services/misc/greenclip.nix
Normal file
31
nixos/modules/services/misc/greenclip.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.greenclip;
|
||||
in {
|
||||
|
||||
options.services.greenclip = {
|
||||
enable = mkEnableOption "Greenclip daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.haskellPackages.greenclip;
|
||||
defaultText = literalExpression "pkgs.haskellPackages.greenclip";
|
||||
description = "greenclip derivation to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.user.services.greenclip = {
|
||||
enable = true;
|
||||
description = "greenclip daemon";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
serviceConfig.ExecStart = "${cfg.package}/bin/greenclip daemon";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
};
|
||||
}
|
||||
89
nixos/modules/services/misc/headphones.nix
Normal file
89
nixos/modules/services/misc/headphones.nix
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
name = "headphones";
|
||||
|
||||
cfg = config.services.headphones;
|
||||
opt = options.services.headphones;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.headphones = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the headphones server.";
|
||||
};
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/${name}";
|
||||
description = "Path where to store data files.";
|
||||
};
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.dataDir}/config.ini";
|
||||
defaultText = literalExpression ''"''${config.${opt.dataDir}}/config.ini"'';
|
||||
description = "Path to config file.";
|
||||
};
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Host to listen on.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.ints.u16;
|
||||
default = 8181;
|
||||
description = "Port to bind to.";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "User to run the service as";
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "Group to run the service as";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users = optionalAttrs (cfg.user == name) {
|
||||
${name} = {
|
||||
uid = config.ids.uids.headphones;
|
||||
group = cfg.group;
|
||||
description = "headphones user";
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == name) {
|
||||
${name}.gid = config.ids.gids.headphones;
|
||||
};
|
||||
|
||||
systemd.services.headphones = {
|
||||
description = "Headphones Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${pkgs.headphones}/bin/headphones --datadir ${cfg.dataDir} --config ${cfg.configFile} --host ${cfg.host} --port ${toString cfg.port}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
222
nixos/modules/services/misc/heisenbridge.nix
Normal file
222
nixos/modules/services/misc/heisenbridge.nix
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.heisenbridge;
|
||||
|
||||
pkg = config.services.heisenbridge.package;
|
||||
bin = "${pkg}/bin/heisenbridge";
|
||||
|
||||
jsonType = (pkgs.formats.json { }).type;
|
||||
|
||||
registrationFile = "/var/lib/heisenbridge/registration.yml";
|
||||
# JSON is a proper subset of YAML
|
||||
bridgeConfig = builtins.toFile "heisenbridge-registration.yml" (builtins.toJSON {
|
||||
id = "heisenbridge";
|
||||
url = cfg.registrationUrl;
|
||||
# Don't specify as_token and hs_token
|
||||
rate_limited = false;
|
||||
sender_localpart = "heisenbridge";
|
||||
namespaces = cfg.namespaces;
|
||||
});
|
||||
in
|
||||
{
|
||||
options.services.heisenbridge = {
|
||||
enable = mkEnableOption "the Matrix to IRC bridge";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.heisenbridge;
|
||||
defaultText = "pkgs.heisenbridge";
|
||||
example = "pkgs.heisenbridge.override { … = …; }";
|
||||
description = ''
|
||||
Package of the application to run, exposed for overriding purposes.
|
||||
'';
|
||||
};
|
||||
|
||||
homeserver = mkOption {
|
||||
type = types.str;
|
||||
description = "The URL to the home server for client-server API calls";
|
||||
example = "http://localhost:8008";
|
||||
};
|
||||
|
||||
registrationUrl = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The URL where the application service is listening for HS requests, from the Matrix HS perspective.#
|
||||
The default value assumes the bridge runs on the same host as the home server, in the same network.
|
||||
'';
|
||||
example = "https://matrix.example.org";
|
||||
default = "http://${cfg.address}:${toString cfg.port}";
|
||||
defaultText = "http://$${cfg.address}:$${toString cfg.port}";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
description = "Address to listen on. IPv6 does not seem to be supported.";
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
description = "The port to listen on";
|
||||
default = 9898;
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
description = "More verbose logging. Recommended during initial setup.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
owner = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Set owner MXID otherwise first talking local user will claim the bridge
|
||||
'';
|
||||
default = null;
|
||||
example = "@admin:example.org";
|
||||
};
|
||||
|
||||
namespaces = mkOption {
|
||||
description = "Configure the 'namespaces' section of the registration.yml for the bridge and the server";
|
||||
# TODO link to Matrix documentation of the format
|
||||
type = types.submodule {
|
||||
freeformType = jsonType;
|
||||
};
|
||||
|
||||
default = {
|
||||
users = [
|
||||
{
|
||||
regex = "@irc_.*";
|
||||
exclusive = true;
|
||||
}
|
||||
];
|
||||
aliases = [ ];
|
||||
rooms = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
identd.enable = mkEnableOption "identd service support";
|
||||
identd.port = mkOption {
|
||||
type = types.port;
|
||||
description = "identd listen port";
|
||||
default = 113;
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "Heisenbridge is configured over the command line. Append extra arguments here";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.heisenbridge = {
|
||||
description = "Matrix<->IRC bridge";
|
||||
before = [ "matrix-synapse.service" ]; # So the registration file can be used by Synapse
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
umask 077
|
||||
set -e -u -o pipefail
|
||||
|
||||
if ! [ -f "${registrationFile}" ]; then
|
||||
# Generate registration file if not present (actually, we only care about the tokens in it)
|
||||
${bin} --generate --config ${registrationFile}
|
||||
fi
|
||||
|
||||
# Overwrite the registration file with our generated one (the config may have changed since then),
|
||||
# but keep the tokens. Two step procedure to be failure safe
|
||||
${pkgs.yq}/bin/yq --slurp \
|
||||
'.[0] + (.[1] | {as_token, hs_token})' \
|
||||
${bridgeConfig} \
|
||||
${registrationFile} \
|
||||
> ${registrationFile}.new
|
||||
mv -f ${registrationFile}.new ${registrationFile}
|
||||
|
||||
# Grant Synapse access to the registration
|
||||
if ${getBin pkgs.glibc}/bin/getent group matrix-synapse > /dev/null; then
|
||||
chgrp -v matrix-synapse ${registrationFile}
|
||||
chmod -v g+r ${registrationFile}
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = rec {
|
||||
Type = "simple";
|
||||
ExecStart = lib.concatStringsSep " " (
|
||||
[
|
||||
bin
|
||||
(if cfg.debug then "-vvv" else "-v")
|
||||
"--config"
|
||||
registrationFile
|
||||
"--listen-address"
|
||||
(lib.escapeShellArg cfg.address)
|
||||
"--listen-port"
|
||||
(toString cfg.port)
|
||||
]
|
||||
++ (lib.optionals (cfg.owner != null) [
|
||||
"--owner"
|
||||
(lib.escapeShellArg cfg.owner)
|
||||
])
|
||||
++ (lib.optionals cfg.identd.enable [
|
||||
"--identd"
|
||||
"--identd-port"
|
||||
(toString cfg.identd.port)
|
||||
])
|
||||
++ [
|
||||
(lib.escapeShellArg cfg.homeserver)
|
||||
]
|
||||
++ (map (lib.escapeShellArg) cfg.extraArgs)
|
||||
);
|
||||
|
||||
# Hardening options
|
||||
|
||||
User = "heisenbridge";
|
||||
Group = "heisenbridge";
|
||||
RuntimeDirectory = "heisenbridge";
|
||||
RuntimeDirectoryMode = "0700";
|
||||
StateDirectory = "heisenbridge";
|
||||
StateDirectoryMode = "0755";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictSUIDSGID = true;
|
||||
PrivateMounts = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RestrictNamespaces = true;
|
||||
RemoveIPC = true;
|
||||
UMask = "0077";
|
||||
|
||||
CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.port < 1024 || (cfg.identd.enable && cfg.identd.port < 1024)) "CAP_NET_BIND_SERVICE";
|
||||
AmbientCapabilities = CapabilityBoundingSet;
|
||||
NoNewPrivileges = true;
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
|
||||
SystemCallArchitectures = "native";
|
||||
RestrictAddressFamilies = "AF_INET AF_INET6";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups.heisenbridge = {};
|
||||
users.users.heisenbridge = {
|
||||
description = "Service user for the Heisenbridge";
|
||||
group = "heisenbridge";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = [ lib.maintainers.piegames ];
|
||||
}
|
||||
65
nixos/modules/services/misc/ihaskell.nix
Normal file
65
nixos/modules/services/misc/ihaskell.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{ pkgs, lib, config, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.ihaskell;
|
||||
ihaskell = pkgs.ihaskell.override {
|
||||
packages = cfg.extraPackages;
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.ihaskell = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Autostart an IHaskell notebook service.";
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = types.functionTo (types.listOf types.package);
|
||||
default = haskellPackages: [];
|
||||
defaultText = literalExpression "haskellPackages: []";
|
||||
example = literalExpression ''
|
||||
haskellPackages: [
|
||||
haskellPackages.wreq
|
||||
haskellPackages.lens
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Extra packages available to ghc when running ihaskell. The
|
||||
value must be a function which receives the attrset defined
|
||||
in <varname>haskellPackages</varname> as the sole argument.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.ihaskell = {
|
||||
group = config.users.groups.ihaskell.name;
|
||||
description = "IHaskell user";
|
||||
home = "/var/lib/ihaskell";
|
||||
createHome = true;
|
||||
uid = config.ids.uids.ihaskell;
|
||||
};
|
||||
|
||||
users.groups.ihaskell.gid = config.ids.gids.ihaskell;
|
||||
|
||||
systemd.services.ihaskell = {
|
||||
description = "IHaskell notebook instance";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
User = config.users.users.ihaskell.name;
|
||||
Group = config.users.groups.ihaskell.name;
|
||||
ExecStart = "${pkgs.runtimeShell} -c \"cd $HOME;${ihaskell}/bin/ihaskell-notebook\"";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
30
nixos/modules/services/misc/input-remapper.nix
Normal file
30
nixos/modules/services/misc/input-remapper.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{ pkgs, lib, config, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let cfg = config.services.input-remapper; in
|
||||
{
|
||||
options = {
|
||||
services.input-remapper = {
|
||||
enable = mkEnableOption "input-remapper, an easy to use tool to change the mapping of your input device buttons.";
|
||||
package = options.mkPackageOption pkgs "input-remapper" { };
|
||||
enableUdevRules = mkEnableOption "udev rules added by input-remapper to handle hotplugged devices. Currently disabled by default due to https://github.com/sezanzeb/input-remapper/issues/140";
|
||||
serviceWantedBy = mkOption {
|
||||
default = [ "graphical.target" ];
|
||||
example = [ "multi-user.target" ];
|
||||
type = types.listOf types.str;
|
||||
description = "Specifies the WantedBy setting for the input-remapper service.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.udev.packages = mkIf cfg.enableUdevRules [ cfg.package ];
|
||||
services.dbus.packages = [ cfg.package ];
|
||||
systemd.packages = [ cfg.package ];
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
systemd.services.input-remapper.wantedBy = cfg.serviceWantedBy;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ LunNova ];
|
||||
}
|
||||
67
nixos/modules/services/misc/irkerd.nix
Normal file
67
nixos/modules/services/misc/irkerd.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.irkerd;
|
||||
ports = [ 6659 ];
|
||||
in
|
||||
{
|
||||
options.services.irkerd = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable irker, an IRC notification daemon.";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
openPorts = mkOption {
|
||||
description = "Open ports in the firewall for irkerd";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
default = "localhost";
|
||||
example = "0.0.0.0";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Specifies the bind address on which the irker daemon listens.
|
||||
The default is localhost.
|
||||
|
||||
Irker authors strongly warn about the risks of running this on
|
||||
a publicly accessible interface, so change this with caution.
|
||||
'';
|
||||
};
|
||||
|
||||
nick = mkOption {
|
||||
default = "irker";
|
||||
type = types.str;
|
||||
description = "Nick to use for irker";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.irkerd = {
|
||||
description = "Internet Relay Chat (IRC) notification daemon";
|
||||
documentation = [ "man:irkerd(8)" "man:irkerhook(1)" "man:irk(1)" ];
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.irker}/bin/irkerd -H ${cfg.listenAddress} -n ${cfg.nick}";
|
||||
User = "irkerd";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.irker ];
|
||||
|
||||
users.users.irkerd = {
|
||||
description = "Irker daemon user";
|
||||
isSystemUser = true;
|
||||
group = "irkerd";
|
||||
};
|
||||
users.groups.irkerd = {};
|
||||
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openPorts ports;
|
||||
networking.firewall.allowedUDPPorts = mkIf cfg.openPorts ports;
|
||||
};
|
||||
}
|
||||
82
nixos/modules/services/misc/jackett.nix
Normal file
82
nixos/modules/services/misc/jackett.nix
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.jackett;
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.jackett = {
|
||||
enable = mkEnableOption "Jackett";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/jackett/.config/Jackett";
|
||||
description = "The directory where Jackett stores its data files.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the Jackett web interface.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "jackett";
|
||||
description = "User account under which Jackett runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "jackett";
|
||||
description = "Group under which Jackett runs.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.jackett;
|
||||
defaultText = literalExpression "pkgs.jackett";
|
||||
description = "Jackett package to use.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.jackett = {
|
||||
description = "Jackett";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 9117 ];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "jackett") {
|
||||
jackett = {
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
uid = config.ids.uids.jackett;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "jackett") {
|
||||
jackett.gid = config.ids.gids.jackett;
|
||||
};
|
||||
};
|
||||
}
|
||||
125
nixos/modules/services/misc/jellyfin.nix
Normal file
125
nixos/modules/services/misc/jellyfin.nix
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.jellyfin;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.jellyfin = {
|
||||
enable = mkEnableOption "Jellyfin Media Server";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "jellyfin";
|
||||
description = "User account under which Jellyfin runs.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.jellyfin;
|
||||
defaultText = literalExpression "pkgs.jellyfin";
|
||||
description = ''
|
||||
Jellyfin package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "jellyfin";
|
||||
description = "Group under which jellyfin runs.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open the default ports in the firewall for the media server. The
|
||||
HTTP/HTTPS ports can be changed in the Web UI, so this option should
|
||||
only be used if they are unchanged.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.jellyfin = {
|
||||
description = "Jellyfin Media Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = rec {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
StateDirectory = "jellyfin";
|
||||
StateDirectoryMode = "0700";
|
||||
CacheDirectory = "jellyfin";
|
||||
CacheDirectoryMode = "0700";
|
||||
UMask = "0077";
|
||||
ExecStart = "${cfg.package}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
|
||||
Restart = "on-failure";
|
||||
|
||||
# Security options:
|
||||
|
||||
NoNewPrivileges = true;
|
||||
|
||||
AmbientCapabilities = "";
|
||||
CapabilityBoundingSet = "";
|
||||
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
|
||||
LockPersonality = true;
|
||||
|
||||
PrivateTmp = true;
|
||||
# Disabled to allow Jellyfin to access hw accel devices endpoints
|
||||
# PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
|
||||
# Disabled as it does not allow Jellyfin to interface with CUDA devices
|
||||
# ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
|
||||
RemoveIPC = true;
|
||||
|
||||
RestrictNamespaces = true;
|
||||
# AF_NETLINK needed because Jellyfin monitors the network connection
|
||||
RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET" "AF_INET6" "AF_UNIX" ];
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "jellyfin") {
|
||||
jellyfin = {
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "jellyfin") {
|
||||
jellyfin = {};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
# from https://jellyfin.org/docs/general/networking/index.html
|
||||
allowedTCPPorts = [ 8096 8920 ];
|
||||
allowedUDPPorts = [ 1900 7359 ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ minijackson ];
|
||||
}
|
||||
117
nixos/modules/services/misc/klipper.nix
Normal file
117
nixos/modules/services/misc/klipper.nix
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.klipper;
|
||||
format = pkgs.formats.ini {
|
||||
# https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
|
||||
listToValue = l:
|
||||
if builtins.length l == 1 then generators.mkValueStringDefault {} (head l)
|
||||
else lib.concatMapStrings (s: "\n ${generators.mkValueStringDefault {} s}") l;
|
||||
mkKeyValue = generators.mkKeyValueDefault {} ":";
|
||||
};
|
||||
in
|
||||
{
|
||||
##### interface
|
||||
options = {
|
||||
services.klipper = {
|
||||
enable = mkEnableOption "Klipper, the 3D printer firmware";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.klipper;
|
||||
defaultText = literalExpression "pkgs.klipper";
|
||||
description = "The Klipper package.";
|
||||
};
|
||||
|
||||
inputTTY = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/klipper/tty";
|
||||
description = "Path of the virtual printer symlink to create.";
|
||||
};
|
||||
|
||||
apiSocket = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "/run/klipper/api";
|
||||
description = "Path of the API socket to create.";
|
||||
};
|
||||
|
||||
octoprintIntegration = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Allows Octoprint to control Klipper.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
User account under which Klipper runs.
|
||||
|
||||
If null is specified (default), a temporary user will be created by systemd.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Group account under which Klipper runs.
|
||||
|
||||
If null is specified (default), a temporary user will be created by systemd.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = { };
|
||||
description = ''
|
||||
Configuration for Klipper. See the <link xlink:href="https://www.klipper3d.org/Overview.html#configuration-and-tuning-guides">documentation</link>
|
||||
for supported values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
##### implementation
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.octoprintIntegration -> config.services.octoprint.enable;
|
||||
message = "Option klipper.octoprintIntegration requires Octoprint to be enabled on this system. Please enable services.octoprint to use it.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.user != null -> cfg.group != null;
|
||||
message = "Option klipper.group is not set when a user is specified.";
|
||||
}
|
||||
];
|
||||
|
||||
environment.etc."klipper.cfg".source = format.generate "klipper.cfg" cfg.settings;
|
||||
|
||||
services.klipper = mkIf cfg.octoprintIntegration {
|
||||
user = config.services.octoprint.user;
|
||||
group = config.services.octoprint.group;
|
||||
};
|
||||
|
||||
systemd.services.klipper = let
|
||||
klippyArgs = "--input-tty=${cfg.inputTTY}"
|
||||
+ optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}";
|
||||
in {
|
||||
description = "Klipper 3D Printer Firmware";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/lib/klipper/klippy.py ${klippyArgs} /etc/klipper.cfg";
|
||||
RuntimeDirectory = "klipper";
|
||||
SupplementaryGroups = [ "dialout" ];
|
||||
WorkingDirectory = "${cfg.package}/lib";
|
||||
} // (if cfg.user != null then {
|
||||
Group = cfg.group;
|
||||
User = cfg.user;
|
||||
} else {
|
||||
DynamicUser = true;
|
||||
User = "klipper";
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
62
nixos/modules/services/misc/leaps.nix
Normal file
62
nixos/modules/services/misc/leaps.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.leaps;
|
||||
stateDir = "/var/lib/leaps/";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.leaps = {
|
||||
enable = mkEnableOption "leaps";
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
description = "A port where leaps listens for incoming http requests";
|
||||
};
|
||||
address = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
example = "127.0.0.1";
|
||||
description = "Hostname or IP-address to listen to. By default it will listen on all interfaces.";
|
||||
};
|
||||
path = mkOption {
|
||||
default = "/";
|
||||
type = types.path;
|
||||
description = "Subdirectory used for reverse proxy setups";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users = {
|
||||
users.leaps = {
|
||||
uid = config.ids.uids.leaps;
|
||||
description = "Leaps server user";
|
||||
group = "leaps";
|
||||
home = stateDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
groups.leaps = {
|
||||
gid = config.ids.gids.leaps;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.leaps = {
|
||||
description = "leaps service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = "leaps";
|
||||
Group = "leaps";
|
||||
Restart = "on-failure";
|
||||
WorkingDirectory = stateDir;
|
||||
PrivateTmp = true;
|
||||
ExecStart = "${pkgs.leaps}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
84
nixos/modules/services/misc/libreddit.nix
Normal file
84
nixos/modules/services/misc/libreddit.nix
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.libreddit;
|
||||
|
||||
args = concatStringsSep " " ([
|
||||
"--port ${toString cfg.port}"
|
||||
"--address ${cfg.address}"
|
||||
]);
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.libreddit = {
|
||||
enable = mkEnableOption "Private front-end for Reddit";
|
||||
|
||||
address = mkOption {
|
||||
default = "0.0.0.0";
|
||||
example = "127.0.0.1";
|
||||
type = types.str;
|
||||
description = "The address to listen on";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
default = 8080;
|
||||
example = 8000;
|
||||
type = types.port;
|
||||
description = "The port to listen on";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the libreddit web interface";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.libreddit = {
|
||||
description = "Private front-end for Reddit";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${pkgs.libreddit}/bin/libreddit ${args}";
|
||||
AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
|
||||
Restart = "on-failure";
|
||||
RestartSec = "2s";
|
||||
# Hardening
|
||||
CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
|
||||
DeviceAllow = [ "" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
# A private user cannot have process capabilities on the host's user
|
||||
# namespace and thus CAP_NET_BIND_SERVICE has no effect.
|
||||
PrivateUsers = (cfg.port >= 1024);
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
89
nixos/modules/services/misc/lidarr.nix
Normal file
89
nixos/modules/services/misc/lidarr.nix
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.lidarr;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.lidarr = {
|
||||
enable = mkEnableOption "Lidarr";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/lidarr/.config/Lidarr";
|
||||
description = "The directory where Lidarr stores its data files.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.lidarr;
|
||||
defaultText = literalExpression "pkgs.lidarr";
|
||||
description = "The Lidarr package to use";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for Lidarr
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "lidarr";
|
||||
description = ''
|
||||
User account under which Lidarr runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "lidarr";
|
||||
description = ''
|
||||
Group under which Lidarr runs.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.lidarr = {
|
||||
description = "Lidarr";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${cfg.package}/bin/Lidarr -nobrowser -data='${cfg.dataDir}'";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 8686 ];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "lidarr") {
|
||||
lidarr = {
|
||||
group = cfg.group;
|
||||
home = "/var/lib/lidarr";
|
||||
uid = config.ids.uids.lidarr;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "lidarr") {
|
||||
lidarr = {
|
||||
gid = config.ids.gids.lidarr;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
164
nixos/modules/services/misc/lifecycled.nix
Normal file
164
nixos/modules/services/misc/lifecycled.nix
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.lifecycled;
|
||||
|
||||
# TODO: Add the ability to extend this with an rfc 42-like interface.
|
||||
# In the meantime, one can modify the environment (as
|
||||
# long as it's not overriding anything from here) with
|
||||
# systemd.services.lifecycled.serviceConfig.Environment
|
||||
configFile = pkgs.writeText "lifecycled" ''
|
||||
LIFECYCLED_HANDLER=${cfg.handler}
|
||||
${lib.optionalString (cfg.cloudwatchGroup != null) "LIFECYCLED_CLOUDWATCH_GROUP=${cfg.cloudwatchGroup}"}
|
||||
${lib.optionalString (cfg.cloudwatchStream != null) "LIFECYCLED_CLOUDWATCH_STREAM=${cfg.cloudwatchStream}"}
|
||||
${lib.optionalString cfg.debug "LIFECYCLED_DEBUG=${lib.boolToString cfg.debug}"}
|
||||
${lib.optionalString (cfg.instanceId != null) "LIFECYCLED_INSTANCE_ID=${cfg.instanceId}"}
|
||||
${lib.optionalString cfg.json "LIFECYCLED_JSON=${lib.boolToString cfg.json}"}
|
||||
${lib.optionalString cfg.noSpot "LIFECYCLED_NO_SPOT=${lib.boolToString cfg.noSpot}"}
|
||||
${lib.optionalString (cfg.snsTopic != null) "LIFECYCLED_SNS_TOPIC=${cfg.snsTopic}"}
|
||||
${lib.optionalString (cfg.awsRegion != null) "AWS_REGION=${cfg.awsRegion}"}
|
||||
'';
|
||||
in
|
||||
{
|
||||
meta.maintainers = with maintainers; [ cole-h grahamc ];
|
||||
|
||||
options = {
|
||||
services.lifecycled = {
|
||||
enable = mkEnableOption "lifecycled";
|
||||
|
||||
queueCleaner = {
|
||||
enable = mkEnableOption "lifecycled-queue-cleaner";
|
||||
|
||||
frequency = mkOption {
|
||||
type = types.str;
|
||||
default = "hourly";
|
||||
description = ''
|
||||
How often to trigger the queue cleaner.
|
||||
|
||||
NOTE: This string should be a valid value for a systemd
|
||||
timer's <literal>OnCalendar</literal> configuration. See
|
||||
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for more information.
|
||||
'';
|
||||
};
|
||||
|
||||
parallel = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 20;
|
||||
description = ''
|
||||
The number of parallel deletes to run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
instanceId = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The instance ID to listen for events for.
|
||||
'';
|
||||
};
|
||||
|
||||
snsTopic = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The SNS topic that receives events.
|
||||
'';
|
||||
};
|
||||
|
||||
noSpot = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Disable the spot termination listener.
|
||||
'';
|
||||
};
|
||||
|
||||
handler = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
The script to invoke to handle events.
|
||||
'';
|
||||
};
|
||||
|
||||
json = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable JSON logging.
|
||||
'';
|
||||
};
|
||||
|
||||
cloudwatchGroup = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Write logs to a specific Cloudwatch Logs group.
|
||||
'';
|
||||
};
|
||||
|
||||
cloudwatchStream = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Write logs to a specific Cloudwatch Logs stream. Defaults to the instance ID.
|
||||
'';
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable debugging information.
|
||||
'';
|
||||
};
|
||||
|
||||
# XXX: Can be removed if / when
|
||||
# https://github.com/buildkite/lifecycled/pull/91 is merged.
|
||||
awsRegion = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The region used for accessing AWS services.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
### Implementation ###
|
||||
|
||||
config = mkMerge [
|
||||
(mkIf cfg.enable {
|
||||
environment.etc."lifecycled".source = configFile;
|
||||
|
||||
systemd.packages = [ pkgs.lifecycled ];
|
||||
systemd.services.lifecycled = {
|
||||
wantedBy = [ "network-online.target" ];
|
||||
restartTriggers = [ configFile ];
|
||||
};
|
||||
})
|
||||
|
||||
(mkIf cfg.queueCleaner.enable {
|
||||
systemd.services.lifecycled-queue-cleaner = {
|
||||
description = "Lifecycle Daemon Queue Cleaner";
|
||||
environment = optionalAttrs (cfg.awsRegion != null) { AWS_REGION = cfg.awsRegion; };
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.lifecycled}/bin/lifecycled-queue-cleaner -parallel ${toString cfg.queueCleaner.parallel}";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.lifecycled-queue-cleaner = {
|
||||
description = "Lifecycle Daemon Queue Cleaner Timer";
|
||||
wantedBy = [ "timers.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
timerConfig = {
|
||||
Unit = "lifecycled-queue-cleaner.service";
|
||||
OnCalendar = "${cfg.queueCleaner.frequency}";
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
30
nixos/modules/services/misc/logkeys.nix
Normal file
30
nixos/modules/services/misc/logkeys.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.logkeys;
|
||||
in {
|
||||
options.services.logkeys = {
|
||||
enable = mkEnableOption "logkeys service";
|
||||
|
||||
device = mkOption {
|
||||
description = "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
example = "/dev/input/event15";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.logkeys = {
|
||||
description = "LogKeys Keylogger Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.logkeys}/bin/logkeys -s${lib.optionalString (cfg.device != null) " -d ${cfg.device}"}";
|
||||
ExecStop = "${pkgs.logkeys}/bin/logkeys -k";
|
||||
Type = "forking";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
69
nixos/modules/services/misc/mame.nix
Normal file
69
nixos/modules/services/misc/mame.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.mame;
|
||||
mame = "mame${lib.optionalString pkgs.stdenv.is64bit "64"}";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.mame = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to setup TUN/TAP Ethernet interface for MAME emulator.
|
||||
'';
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
User from which you run MAME binary.
|
||||
'';
|
||||
};
|
||||
hostAddr = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
IP address of the host system. Usually an address of the main network
|
||||
adapter or the adapter through which you get an internet connection.
|
||||
'';
|
||||
example = "192.168.31.156";
|
||||
};
|
||||
emuAddr = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
IP address of the guest system. The same you set inside guest OS under
|
||||
MAME. Should be on the same subnet as <option>services.mame.hostAddr</option>.
|
||||
'';
|
||||
example = "192.168.31.155";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.mame ];
|
||||
|
||||
security.wrappers."${mame}" = {
|
||||
owner = "root";
|
||||
group = "root";
|
||||
capabilities = "cap_net_admin,cap_net_raw+eip";
|
||||
source = "${pkgs.mame}/bin/${mame}";
|
||||
};
|
||||
|
||||
systemd.services.mame = {
|
||||
description = "MAME TUN/TAP Ethernet interface";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.iproute2 ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${pkgs.mame}/bin/taputil.sh -c ${cfg.user} ${cfg.emuAddr} ${cfg.hostAddr} -";
|
||||
ExecStop = "${pkgs.mame}/bin/taputil.sh -d ${cfg.user}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ ];
|
||||
}
|
||||
161
nixos/modules/services/misc/matrix-appservice-discord.nix
Normal file
161
nixos/modules/services/misc/matrix-appservice-discord.nix
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
{ config, options, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
dataDir = "/var/lib/matrix-appservice-discord";
|
||||
registrationFile = "${dataDir}/discord-registration.yaml";
|
||||
appDir = "${pkgs.matrix-appservice-discord}/${pkgs.matrix-appservice-discord.passthru.nodeAppDir}";
|
||||
cfg = config.services.matrix-appservice-discord;
|
||||
opt = options.services.matrix-appservice-discord;
|
||||
# TODO: switch to configGen.json once RFC42 is implemented
|
||||
settingsFile = pkgs.writeText "matrix-appservice-discord-settings.json" (builtins.toJSON cfg.settings);
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.matrix-appservice-discord = {
|
||||
enable = mkEnableOption "a bridge between Matrix and Discord";
|
||||
|
||||
settings = mkOption rec {
|
||||
# TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
|
||||
type = types.attrs;
|
||||
apply = recursiveUpdate default;
|
||||
default = {
|
||||
database = {
|
||||
filename = "${dataDir}/discord.db";
|
||||
};
|
||||
|
||||
# empty values necessary for registration file generation
|
||||
# actual values defined in environmentFile
|
||||
auth = {
|
||||
clientID = "";
|
||||
botToken = "";
|
||||
};
|
||||
};
|
||||
example = literalExpression ''
|
||||
{
|
||||
bridge = {
|
||||
domain = "public-domain.tld";
|
||||
homeserverUrl = "http://public-domain.tld:8008";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
<filename>config.yaml</filename> configuration as a Nix attribute set.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Configuration options should match those described in
|
||||
<link xlink:href="https://github.com/Half-Shot/matrix-appservice-discord/blob/master/config/config.sample.yaml">
|
||||
config.sample.yaml</link>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<option>config.bridge.domain</option> and <option>config.bridge.homeserverUrl</option>
|
||||
should be set to match the public host name of the Matrix homeserver for webhooks and avatars to work.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Secret tokens should be specified using <option>environmentFile</option>
|
||||
instead of this world-readable attribute set.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing environment variables to be passed to the matrix-appservice-discord service,
|
||||
in which secret tokens can be specified securely by defining values for
|
||||
<literal>APPSERVICE_DISCORD_AUTH_CLIENT_I_D</literal> and
|
||||
<literal>APPSERVICE_DISCORD_AUTH_BOT_TOKEN</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
default = "http://localhost:${toString cfg.port}";
|
||||
defaultText = literalExpression ''"http://localhost:''${toString config.${opt.port}}"'';
|
||||
description = ''
|
||||
The URL where the application service is listening for HS requests.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 9005; # from https://github.com/Half-Shot/matrix-appservice-discord/blob/master/package.json#L11
|
||||
description = ''
|
||||
Port number on which the bridge should listen for internal communication with the Matrix homeserver.
|
||||
'';
|
||||
};
|
||||
|
||||
localpart = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The user_id localpart to assign to the AS.
|
||||
'';
|
||||
};
|
||||
|
||||
serviceDependencies = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
|
||||
defaultText = literalExpression ''
|
||||
optional config.services.matrix-synapse.enable "matrix-synapse.service"
|
||||
'';
|
||||
description = ''
|
||||
List of Systemd services to require and wait for when starting the application service,
|
||||
such as the Matrix homeserver if it's running on the same host.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.matrix-appservice-discord = {
|
||||
description = "A bridge between Matrix and Discord.";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
|
||||
preStart = ''
|
||||
if [ ! -f '${registrationFile}' ]; then
|
||||
${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
|
||||
--generate-registration \
|
||||
--url=${escapeShellArg cfg.url} \
|
||||
${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \
|
||||
--config='${settingsFile}' \
|
||||
--file='${registrationFile}'
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
|
||||
DynamicUser = true;
|
||||
PrivateTmp = true;
|
||||
WorkingDirectory = appDir;
|
||||
StateDirectory = baseNameOf dataDir;
|
||||
UMask = 0027;
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
|
||||
ExecStart = ''
|
||||
${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
|
||||
--file='${registrationFile}' \
|
||||
--config='${settingsFile}' \
|
||||
--port='${toString cfg.port}'
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ pacien ];
|
||||
}
|
||||
232
nixos/modules/services/misc/matrix-appservice-irc.nix
Normal file
232
nixos/modules/services/misc/matrix-appservice-irc.nix
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.matrix-appservice-irc;
|
||||
|
||||
pkg = pkgs.matrix-appservice-irc;
|
||||
bin = "${pkg}/bin/matrix-appservice-irc";
|
||||
|
||||
jsonType = (pkgs.formats.json {}).type;
|
||||
|
||||
configFile = pkgs.runCommand "matrix-appservice-irc.yml" {
|
||||
# Because this program will be run at build time, we need `nativeBuildInputs`
|
||||
nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ ps.pyyaml ps.jsonschema ])) ];
|
||||
preferLocalBuild = true;
|
||||
|
||||
config = builtins.toJSON cfg.settings;
|
||||
passAsFile = [ "config" ];
|
||||
} ''
|
||||
# The schema is given as yaml, we need to convert it to json
|
||||
python -c 'import json; import yaml; import sys; json.dump(yaml.safe_load(sys.stdin), sys.stdout)' \
|
||||
< ${pkg}/lib/node_modules/matrix-appservice-irc/config.schema.yml \
|
||||
> config.schema.json
|
||||
python -m jsonschema config.schema.json -i $configPath
|
||||
cp "$configPath" "$out"
|
||||
'';
|
||||
registrationFile = "/var/lib/matrix-appservice-irc/registration.yml";
|
||||
in {
|
||||
options.services.matrix-appservice-irc = with types; {
|
||||
enable = mkEnableOption "the Matrix/IRC bridge";
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "The port to listen on";
|
||||
default = 8009;
|
||||
};
|
||||
|
||||
needBindingCap = mkOption {
|
||||
type = bool;
|
||||
description = "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)";
|
||||
default = false;
|
||||
};
|
||||
|
||||
passwordEncryptionKeyLength = mkOption {
|
||||
type = ints.unsigned;
|
||||
description = "Length of the key to encrypt IRC passwords with";
|
||||
default = 4096;
|
||||
example = 8192;
|
||||
};
|
||||
|
||||
registrationUrl = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The URL where the application service is listening for homeserver requests,
|
||||
from the Matrix homeserver perspective.
|
||||
'';
|
||||
example = "http://localhost:8009";
|
||||
};
|
||||
|
||||
localpart = mkOption {
|
||||
type = str;
|
||||
description = "The user_id localpart to assign to the appservice";
|
||||
default = "appservice-irc";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Configuration for the appservice, see
|
||||
<link xlink:href="https://github.com/matrix-org/matrix-appservice-irc/blob/${pkgs.matrix-appservice-irc.version}/config.sample.yaml"/>
|
||||
for supported values
|
||||
'';
|
||||
default = {};
|
||||
type = submodule {
|
||||
freeformType = jsonType;
|
||||
|
||||
options = {
|
||||
homeserver = mkOption {
|
||||
description = "Homeserver configuration";
|
||||
default = {};
|
||||
type = submodule {
|
||||
freeformType = jsonType;
|
||||
|
||||
options = {
|
||||
url = mkOption {
|
||||
type = str;
|
||||
description = "The URL to the home server for client-server API calls";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The 'domain' part for user IDs on this home server. Usually
|
||||
(but not always) is the "domain name" part of the homeserver URL.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
default = {};
|
||||
description = "Configuration for the database";
|
||||
type = submodule {
|
||||
freeformType = jsonType;
|
||||
|
||||
options = {
|
||||
engine = mkOption {
|
||||
type = str;
|
||||
description = "Which database engine to use";
|
||||
default = "nedb";
|
||||
example = "postgres";
|
||||
};
|
||||
|
||||
connectionString = mkOption {
|
||||
type = str;
|
||||
description = "The database connection string";
|
||||
default = "nedb://var/lib/matrix-appservice-irc/data";
|
||||
example = "postgres://username:password@host:port/databasename";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ircService = mkOption {
|
||||
default = {};
|
||||
description = "IRC bridge configuration";
|
||||
type = submodule {
|
||||
freeformType = jsonType;
|
||||
|
||||
options = {
|
||||
passwordEncryptionKeyPath = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Location of the key with which IRC passwords are encrypted
|
||||
for storage. Will be generated on first run if not present.
|
||||
'';
|
||||
default = "/var/lib/matrix-appservice-irc/passkey.pem";
|
||||
};
|
||||
|
||||
servers = mkOption {
|
||||
type = submodule { freeformType = jsonType; };
|
||||
description = "IRC servers to connect to";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.matrix-appservice-irc = {
|
||||
description = "Matrix-IRC bridge";
|
||||
before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
umask 077
|
||||
# Generate key for crypting passwords
|
||||
if ! [ -f "${cfg.settings.ircService.passwordEncryptionKeyPath}" ]; then
|
||||
${pkgs.openssl}/bin/openssl genpkey \
|
||||
-out "${cfg.settings.ircService.passwordEncryptionKeyPath}" \
|
||||
-outform PEM \
|
||||
-algorithm RSA \
|
||||
-pkeyopt "rsa_keygen_bits:${toString cfg.passwordEncryptionKeyLength}"
|
||||
fi
|
||||
# Generate registration file
|
||||
if ! [ -f "${registrationFile}" ]; then
|
||||
# The easy case: the file has not been generated yet
|
||||
${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart}
|
||||
else
|
||||
# The tricky case: we already have a generation file. Because the NixOS configuration might have changed, we need to
|
||||
# regenerate it. But this would give the service a new random ID and tokens, so we need to back up and restore them.
|
||||
# 1. Backup
|
||||
id=$(grep "^id:.*$" ${registrationFile})
|
||||
hs_token=$(grep "^hs_token:.*$" ${registrationFile})
|
||||
as_token=$(grep "^as_token:.*$" ${registrationFile})
|
||||
# 2. Regenerate
|
||||
${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart}
|
||||
# 3. Restore
|
||||
sed -i "s/^id:.*$/$id/g" ${registrationFile}
|
||||
sed -i "s/^hs_token:.*$/$hs_token/g" ${registrationFile}
|
||||
sed -i "s/^as_token:.*$/$as_token/g" ${registrationFile}
|
||||
fi
|
||||
# Allow synapse access to the registration
|
||||
if ${getBin pkgs.glibc}/bin/getent group matrix-synapse > /dev/null; then
|
||||
chgrp matrix-synapse ${registrationFile}
|
||||
chmod g+r ${registrationFile}
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = rec {
|
||||
Type = "simple";
|
||||
ExecStart = "${bin} --config ${configFile} --file ${registrationFile} --port ${toString cfg.port}";
|
||||
|
||||
ProtectHome = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
StateDirectory = "matrix-appservice-irc";
|
||||
StateDirectoryMode = "755";
|
||||
|
||||
User = "matrix-appservice-irc";
|
||||
Group = "matrix-appservice-irc";
|
||||
|
||||
CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE";
|
||||
AmbientCapabilities = CapabilityBoundingSet;
|
||||
NoNewPrivileges = true;
|
||||
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
PrivateMounts = true;
|
||||
SystemCallFilter = "~@aio @clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @setuid @swap";
|
||||
SystemCallArchitectures = "native";
|
||||
# AF_UNIX is required to connect to a postgres socket.
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups.matrix-appservice-irc = {};
|
||||
users.users.matrix-appservice-irc = {
|
||||
description = "Service user for the Matrix-IRC bridge";
|
||||
group = "matrix-appservice-irc";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
# uses attributes of the linked package
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
||||
149
nixos/modules/services/misc/matrix-conduit.nix
Normal file
149
nixos/modules/services/misc/matrix-conduit.nix
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.matrix-conduit;
|
||||
|
||||
format = pkgs.formats.toml {};
|
||||
configFile = format.generate "conduit.toml" cfg.settings;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with maintainers; [ pstn piegames ];
|
||||
options.services.matrix-conduit = {
|
||||
enable = mkEnableOption "matrix-conduit";
|
||||
|
||||
extraEnvironment = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
description = "Extra Environment variables to pass to the conduit server.";
|
||||
default = {};
|
||||
example = { RUST_BACKTRACE="yes"; };
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.matrix-conduit;
|
||||
defaultText = "pkgs.matrix-conduit";
|
||||
example = "pkgs.matrix-conduit";
|
||||
description = ''
|
||||
Package of the conduit matrix server to use.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = format.type;
|
||||
options = {
|
||||
global.server_name = mkOption {
|
||||
type = types.str;
|
||||
example = "example.com";
|
||||
description = "The server_name is the name of this server. It is used as a suffix for user # and room ids.";
|
||||
};
|
||||
global.port = mkOption {
|
||||
type = types.port;
|
||||
default = 6167;
|
||||
description = "The port Conduit will be running on. You need to set up a reverse proxy in your web server (e.g. apache or nginx), so all requests to /_matrix on port 443 and 8448 will be forwarded to the Conduit instance running on this port";
|
||||
};
|
||||
global.max_request_size = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 20000000;
|
||||
description = "Max request size in bytes. Don't forget to also change it in the proxy.";
|
||||
};
|
||||
global.allow_registration = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether new users can register on this server.";
|
||||
};
|
||||
global.allow_encryption = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether new encrypted rooms can be created. Note: existing rooms will continue to work.";
|
||||
};
|
||||
global.allow_federation = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether this server federates with other servers.
|
||||
'';
|
||||
};
|
||||
global.trusted_servers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "matrix.org" ];
|
||||
description = "Servers trusted with signing server keys.";
|
||||
};
|
||||
global.address = mkOption {
|
||||
type = types.str;
|
||||
default = "::1";
|
||||
description = "Address to listen on for connections by the reverse proxy/tls terminator.";
|
||||
};
|
||||
global.database_path = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/matrix-conduit/";
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Path to the conduit database, the directory where conduit will save its data.
|
||||
Note that due to using the DynamicUser feature of systemd, this value should not be changed
|
||||
and is set to be read only.
|
||||
'';
|
||||
};
|
||||
global.database_backend = mkOption {
|
||||
type = types.enum [ "sqlite" "rocksdb" ];
|
||||
default = "sqlite";
|
||||
example = "rocksdb";
|
||||
description = ''
|
||||
The database backend for the service. Switching it on an existing
|
||||
instance will require manual migration of data.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
description = ''
|
||||
Generates the conduit.toml configuration file. Refer to
|
||||
<link xlink:href="https://gitlab.com/famedly/conduit/-/blob/master/conduit-example.toml"/>
|
||||
for details on supported values.
|
||||
Note that database_path can not be edited because the service's reliance on systemd StateDir.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.conduit = {
|
||||
description = "Conduit Matrix Server";
|
||||
documentation = [ "https://gitlab.com/famedly/conduit/" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = lib.mkMerge ([
|
||||
{ CONDUIT_CONFIG = configFile; }
|
||||
cfg.extraEnvironment
|
||||
]);
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
User = "conduit";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateUsers = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
];
|
||||
StateDirectory = "matrix-conduit";
|
||||
ExecStart = "${cfg.package}/bin/conduit";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 10;
|
||||
StartLimitBurst = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
195
nixos/modules/services/misc/mautrix-facebook.nix
Normal file
195
nixos/modules/services/misc/mautrix-facebook.nix
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.mautrix-facebook;
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
settingsFile = settingsFormat.generate "mautrix-facebook-config.json" cfg.settings;
|
||||
|
||||
puppetRegex = concatStringsSep
|
||||
".*"
|
||||
(map
|
||||
escapeRegex
|
||||
(splitString
|
||||
"{userid}"
|
||||
cfg.settings.bridge.username_template));
|
||||
in {
|
||||
options = {
|
||||
services.mautrix-facebook = {
|
||||
enable = mkEnableOption "Mautrix-Facebook, a Matrix-Facebook hybrid puppeting/relaybot bridge";
|
||||
|
||||
settings = mkOption rec {
|
||||
apply = recursiveUpdate default;
|
||||
type = settingsFormat.type;
|
||||
default = {
|
||||
homeserver = {
|
||||
address = "http://localhost:8008";
|
||||
};
|
||||
|
||||
appservice = rec {
|
||||
address = "http://${hostname}:${toString port}";
|
||||
hostname = "localhost";
|
||||
port = 29319;
|
||||
|
||||
database = "postgresql://";
|
||||
|
||||
bot_username = "facebookbot";
|
||||
};
|
||||
|
||||
metrics.enabled = false;
|
||||
manhole.enabled = false;
|
||||
|
||||
bridge = {
|
||||
encryption = {
|
||||
allow = true;
|
||||
default = true;
|
||||
};
|
||||
username_template = "facebook_{userid}";
|
||||
};
|
||||
|
||||
logging = {
|
||||
version = 1;
|
||||
formatters.journal_fmt.format = "%(name)s: %(message)s";
|
||||
handlers.journal = {
|
||||
class = "systemd.journal.JournalHandler";
|
||||
formatter = "journal_fmt";
|
||||
SYSLOG_IDENTIFIER = "mautrix-facebook";
|
||||
};
|
||||
root = {
|
||||
level = "INFO";
|
||||
handlers = ["journal"];
|
||||
};
|
||||
};
|
||||
};
|
||||
example = literalExpression ''
|
||||
{
|
||||
homeserver = {
|
||||
address = "http://localhost:8008";
|
||||
domain = "mydomain.example";
|
||||
};
|
||||
|
||||
bridge.permissions = {
|
||||
"@admin:mydomain.example" = "admin";
|
||||
"mydomain.example" = "user";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
<filename>config.yaml</filename> configuration as a Nix attribute set.
|
||||
Configuration options should match those described in
|
||||
<link xlink:href="https://github.com/mautrix/facebook/blob/master/mautrix_facebook/example-config.yaml">
|
||||
example-config.yaml</link>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Secret tokens should be specified using <option>environmentFile</option>
|
||||
instead of this world-readable attribute set.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing environment variables to be passed to the mautrix-telegram service.
|
||||
|
||||
Any config variable can be overridden by setting <literal>MAUTRIX_FACEBOOK_SOME_KEY</literal> to override the <literal>some.key</literal> variable.
|
||||
'';
|
||||
};
|
||||
|
||||
configurePostgresql = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable PostgreSQL and create a user and database for mautrix-facebook. The default <literal>settings</literal> reference this database, if you disable this option you must provide a database URL.
|
||||
'';
|
||||
};
|
||||
|
||||
registrationData = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Output data for appservice registration. Simply make any desired changes and serialize to JSON. Note that this data contains secrets so think twice before putting it into the nix store.
|
||||
|
||||
Currently <literal>as_token</literal> and <literal>hs_token</literal> need to be added as they are not known to this module.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.users.mautrix-facebook = {
|
||||
group = "mautrix-facebook";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
services.postgresql = mkIf cfg.configurePostgresql {
|
||||
ensureDatabases = ["mautrix-facebook"];
|
||||
ensureUsers = [{
|
||||
name = "mautrix-facebook";
|
||||
ensurePermissions = {
|
||||
"DATABASE \"mautrix-facebook\"" = "ALL PRIVILEGES";
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
systemd.services.mautrix-facebook = rec {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [
|
||||
"network-online.target"
|
||||
] ++ optional config.services.matrix-synapse.enable "matrix-synapse.service"
|
||||
++ optional cfg.configurePostgresql "postgresql.service";
|
||||
after = wants;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
|
||||
User = "mautrix-facebook";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
PrivateTmp = true;
|
||||
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
|
||||
ExecStart = ''
|
||||
${pkgs.mautrix-facebook}/bin/mautrix-facebook --config=${settingsFile}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
services.mautrix-facebook = {
|
||||
registrationData = {
|
||||
id = "mautrix-facebook";
|
||||
|
||||
namespaces = {
|
||||
users = [
|
||||
{
|
||||
exclusive = true;
|
||||
regex = escapeRegex "@${cfg.settings.appservice.bot_username}:${cfg.settings.homeserver.domain}";
|
||||
}
|
||||
{
|
||||
exclusive = true;
|
||||
regex = "@${puppetRegex}:${escapeRegex cfg.settings.homeserver.domain}";
|
||||
}
|
||||
];
|
||||
aliases = [];
|
||||
};
|
||||
|
||||
url = cfg.settings.appservice.address;
|
||||
sender_localpart = "mautrix-facebook-sender";
|
||||
|
||||
rate_limited = false;
|
||||
"de.sorunome.msc2409.push_ephemeral" = true;
|
||||
push_ephemeral = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ kevincox ];
|
||||
}
|
||||
181
nixos/modules/services/misc/mautrix-telegram.nix
Normal file
181
nixos/modules/services/misc/mautrix-telegram.nix
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
dataDir = "/var/lib/mautrix-telegram";
|
||||
registrationFile = "${dataDir}/telegram-registration.yaml";
|
||||
cfg = config.services.mautrix-telegram;
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
settingsFileUnsubstituted = settingsFormat.generate "mautrix-telegram-config-unsubstituted.json" cfg.settings;
|
||||
settingsFile = "${dataDir}/config.json";
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.mautrix-telegram = {
|
||||
enable = mkEnableOption "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge";
|
||||
|
||||
settings = mkOption rec {
|
||||
apply = recursiveUpdate default;
|
||||
inherit (settingsFormat) type;
|
||||
default = {
|
||||
appservice = rec {
|
||||
database = "sqlite:///${dataDir}/mautrix-telegram.db";
|
||||
database_opts = {};
|
||||
hostname = "0.0.0.0";
|
||||
port = 8080;
|
||||
address = "http://localhost:${toString port}";
|
||||
};
|
||||
|
||||
bridge = {
|
||||
permissions."*" = "relaybot";
|
||||
relaybot.whitelist = [ ];
|
||||
double_puppet_server_map = {};
|
||||
login_shared_secret_map = {};
|
||||
};
|
||||
|
||||
logging = {
|
||||
version = 1;
|
||||
|
||||
formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
|
||||
|
||||
handlers.console = {
|
||||
class = "logging.StreamHandler";
|
||||
formatter = "precise";
|
||||
};
|
||||
|
||||
loggers = {
|
||||
mau.level = "INFO";
|
||||
telethon.level = "INFO";
|
||||
|
||||
# prevent tokens from leaking in the logs:
|
||||
# https://github.com/tulir/mautrix-telegram/issues/351
|
||||
aiohttp.level = "WARNING";
|
||||
};
|
||||
|
||||
# log to console/systemd instead of file
|
||||
root = {
|
||||
level = "INFO";
|
||||
handlers = [ "console" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
example = literalExpression ''
|
||||
{
|
||||
homeserver = {
|
||||
address = "http://localhost:8008";
|
||||
domain = "public-domain.tld";
|
||||
};
|
||||
|
||||
appservice.public = {
|
||||
prefix = "/public";
|
||||
external = "https://public-appservice-address/public";
|
||||
};
|
||||
|
||||
bridge.permissions = {
|
||||
"example.com" = "full";
|
||||
"@admin:example.com" = "admin";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
<filename>config.yaml</filename> configuration as a Nix attribute set.
|
||||
Configuration options should match those described in
|
||||
<link xlink:href="https://github.com/tulir/mautrix-telegram/blob/master/example-config.yaml">
|
||||
example-config.yaml</link>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Secret tokens should be specified using <option>environmentFile</option>
|
||||
instead of this world-readable attribute set.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing environment variables to be passed to the mautrix-telegram service,
|
||||
in which secret tokens can be specified securely by defining values for
|
||||
<literal>MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN</literal>,
|
||||
<literal>MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN</literal>,
|
||||
<literal>MAUTRIX_TELEGRAM_TELEGRAM_API_ID</literal>,
|
||||
<literal>MAUTRIX_TELEGRAM_TELEGRAM_API_HASH</literal> and optionally
|
||||
<literal>MAUTRIX_TELEGRAM_TELEGRAM_BOT_TOKEN</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
serviceDependencies = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
|
||||
defaultText = literalExpression ''
|
||||
optional config.services.matrix-synapse.enable "matrix-synapse.service"
|
||||
'';
|
||||
description = ''
|
||||
List of Systemd services to require and wait for when starting the application service.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.mautrix-telegram = {
|
||||
description = "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge.";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
|
||||
preStart = ''
|
||||
# Not all secrets can be passed as environment variable (yet)
|
||||
# https://github.com/tulir/mautrix-telegram/issues/584
|
||||
[ -f ${settingsFile} ] && rm -f ${settingsFile}
|
||||
old_umask=$(umask)
|
||||
umask 0177
|
||||
${pkgs.envsubst}/bin/envsubst \
|
||||
-o ${settingsFile} \
|
||||
-i ${settingsFileUnsubstituted}
|
||||
umask $old_umask
|
||||
|
||||
# generate the appservice's registration file if absent
|
||||
if [ ! -f '${registrationFile}' ]; then
|
||||
${pkgs.mautrix-telegram}/bin/mautrix-telegram \
|
||||
--generate-registration \
|
||||
--base-config='${pkgs.mautrix-telegram}/${pkgs.mautrix-telegram.pythonModule.sitePackages}/mautrix_telegram/example-config.yaml' \
|
||||
--config='${settingsFile}' \
|
||||
--registration='${registrationFile}'
|
||||
fi
|
||||
'' + lib.optionalString (pkgs.mautrix-telegram ? alembic) ''
|
||||
# run automatic database init and migration scripts
|
||||
${pkgs.mautrix-telegram.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
|
||||
DynamicUser = true;
|
||||
PrivateTmp = true;
|
||||
WorkingDirectory = pkgs.mautrix-telegram; # necessary for the database migration scripts to be found
|
||||
StateDirectory = baseNameOf dataDir;
|
||||
UMask = 0027;
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
|
||||
ExecStart = ''
|
||||
${pkgs.mautrix-telegram}/bin/mautrix-telegram \
|
||||
--config='${settingsFile}'
|
||||
'';
|
||||
};
|
||||
|
||||
restartTriggers = [ settingsFileUnsubstituted ];
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ pacien vskilet ];
|
||||
}
|
||||
101
nixos/modules/services/misc/mbpfan.nix
Normal file
101
nixos/modules/services/misc/mbpfan.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.mbpfan;
|
||||
verbose = if cfg.verbose then "v" else "";
|
||||
settingsFormat = pkgs.formats.ini {};
|
||||
settingsFile = settingsFormat.generate "mbpfan.ini" cfg.settings;
|
||||
|
||||
in {
|
||||
options.services.mbpfan = {
|
||||
enable = mkEnableOption "mbpfan, fan controller daemon for Apple Macs and MacBooks";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.mbpfan;
|
||||
defaultText = literalExpression "pkgs.mbpfan";
|
||||
description = ''
|
||||
The package used for the mbpfan daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
verbose = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If true, sets the log level to verbose.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = {};
|
||||
description = "INI configuration for Mbpfan.";
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options.general.min_fan1_speed = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = 2000;
|
||||
description = ''
|
||||
You can check minimum and maximum fan limits with
|
||||
"cat /sys/devices/platform/applesmc.768/fan*_min" and
|
||||
"cat /sys/devices/platform/applesmc.768/fan*_max" respectively.
|
||||
Setting to null implies using default value from applesmc.
|
||||
'';
|
||||
};
|
||||
options.general.low_temp = mkOption {
|
||||
type = types.int;
|
||||
default = 55;
|
||||
description = "If temperature is below this, fans will run at minimum speed.";
|
||||
};
|
||||
options.general.high_temp = mkOption {
|
||||
type = types.int;
|
||||
default = 58;
|
||||
description = "If temperature is above this, fan speed will gradually increase.";
|
||||
};
|
||||
options.general.max_temp = mkOption {
|
||||
type = types.int;
|
||||
default = 86;
|
||||
description = "If temperature is above this, fans will run at maximum speed.";
|
||||
};
|
||||
options.general.polling_interval = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = "The polling interval.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "mbpfan" "pollingInterval" ] [ "services" "mbpfan" "settings" "general" "polling_interval" ])
|
||||
(mkRenamedOptionModule [ "services" "mbpfan" "maxTemp" ] [ "services" "mbpfan" "settings" "general" "max_temp" ])
|
||||
(mkRenamedOptionModule [ "services" "mbpfan" "lowTemp" ] [ "services" "mbpfan" "settings" "general" "low_temp" ])
|
||||
(mkRenamedOptionModule [ "services" "mbpfan" "highTemp" ] [ "services" "mbpfan" "settings" "general" "high_temp" ])
|
||||
(mkRenamedOptionModule [ "services" "mbpfan" "minFanSpeed" ] [ "services" "mbpfan" "settings" "general" "min_fan1_speed" ])
|
||||
(mkRenamedOptionModule [ "services" "mbpfan" "maxFanSpeed" ] [ "services" "mbpfan" "settings" "general" "max_fan1_speed" ])
|
||||
];
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
boot.kernelModules = [ "coretemp" "applesmc" ];
|
||||
|
||||
environment.etc."mbpfan.conf".source = settingsFile;
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.services.mbpfan = {
|
||||
description = "A fan manager daemon for MacBook Pro";
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
after = [ "syslog.target" "sysinit.target" ];
|
||||
restartTriggers = [ config.environment.etc."mbpfan.conf".source ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
PIDFile = "/run/mbpfan.pid";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
394
nixos/modules/services/misc/mediatomb.nix
Normal file
394
nixos/modules/services/misc/mediatomb.nix
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
gid = config.ids.gids.mediatomb;
|
||||
cfg = config.services.mediatomb;
|
||||
opt = options.services.mediatomb;
|
||||
name = cfg.package.pname;
|
||||
pkg = cfg.package;
|
||||
optionYesNo = option: if option then "yes" else "no";
|
||||
# configuration on media directory
|
||||
mediaDirectory = {
|
||||
options = {
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Absolute directory path to the media directory to index.
|
||||
'';
|
||||
};
|
||||
recursive = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether the indexation must take place recursively or not.";
|
||||
};
|
||||
hidden-files = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to index the hidden files or not.";
|
||||
};
|
||||
};
|
||||
};
|
||||
toMediaDirectory = d: "<directory location=\"${d.path}\" mode=\"inotify\" recursive=\"${optionYesNo d.recursive}\" hidden-files=\"${optionYesNo d.hidden-files}\" />\n";
|
||||
|
||||
transcodingConfig = if cfg.transcoding then with pkgs; ''
|
||||
<transcoding enabled="yes">
|
||||
<mimetype-profile-mappings>
|
||||
<transcode mimetype="video/x-flv" using="vlcmpeg" />
|
||||
<transcode mimetype="application/ogg" using="vlcmpeg" />
|
||||
<transcode mimetype="audio/ogg" using="ogg2mp3" />
|
||||
<transcode mimetype="audio/x-flac" using="oggflac2raw"/>
|
||||
</mimetype-profile-mappings>
|
||||
<profiles>
|
||||
<profile name="ogg2mp3" enabled="no" type="external">
|
||||
<mimetype>audio/mpeg</mimetype>
|
||||
<accept-url>no</accept-url>
|
||||
<first-resource>yes</first-resource>
|
||||
<accept-ogg-theora>no</accept-ogg-theora>
|
||||
<agent command="${ffmpeg}/bin/ffmpeg" arguments="-y -i %in -f mp3 %out" />
|
||||
<buffer size="1048576" chunk-size="131072" fill-size="262144" />
|
||||
</profile>
|
||||
<profile name="vlcmpeg" enabled="no" type="external">
|
||||
<mimetype>video/mpeg</mimetype>
|
||||
<accept-url>yes</accept-url>
|
||||
<first-resource>yes</first-resource>
|
||||
<accept-ogg-theora>yes</accept-ogg-theora>
|
||||
<agent command="${libsForQt5.vlc}/bin/vlc"
|
||||
arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit" />
|
||||
<buffer size="14400000" chunk-size="512000" fill-size="120000" />
|
||||
</profile>
|
||||
</profiles>
|
||||
</transcoding>
|
||||
'' else ''
|
||||
<transcoding enabled="no">
|
||||
</transcoding>
|
||||
'';
|
||||
|
||||
configText = optionalString (! cfg.customCfg) ''
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config version="2" xmlns="http://mediatomb.cc/config/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mediatomb.cc/config/2 http://mediatomb.cc/config/2.xsd">
|
||||
<server>
|
||||
<ui enabled="yes" show-tooltips="yes">
|
||||
<accounts enabled="no" session-timeout="30">
|
||||
<account user="${name}" password="${name}"/>
|
||||
</accounts>
|
||||
</ui>
|
||||
<name>${cfg.serverName}</name>
|
||||
<udn>uuid:${cfg.uuid}</udn>
|
||||
<home>${cfg.dataDir}</home>
|
||||
<interface>${cfg.interface}</interface>
|
||||
<webroot>${pkg}/share/${name}/web</webroot>
|
||||
<pc-directory upnp-hide="${optionYesNo cfg.pcDirectoryHide}"/>
|
||||
<storage>
|
||||
<sqlite3 enabled="yes">
|
||||
<database-file>${name}.db</database-file>
|
||||
</sqlite3>
|
||||
</storage>
|
||||
<protocolInfo extend="${optionYesNo cfg.ps3Support}"/>
|
||||
${optionalString cfg.dsmSupport ''
|
||||
<custom-http-headers>
|
||||
<add header="X-User-Agent: redsonic"/>
|
||||
</custom-http-headers>
|
||||
|
||||
<manufacturerURL>redsonic.com</manufacturerURL>
|
||||
<modelNumber>105</modelNumber>
|
||||
''}
|
||||
${optionalString cfg.tg100Support ''
|
||||
<upnp-string-limit>101</upnp-string-limit>
|
||||
''}
|
||||
<extended-runtime-options>
|
||||
<mark-played-items enabled="yes" suppress-cds-updates="yes">
|
||||
<string mode="prepend">*</string>
|
||||
<mark>
|
||||
<content>video</content>
|
||||
</mark>
|
||||
</mark-played-items>
|
||||
</extended-runtime-options>
|
||||
</server>
|
||||
<import hidden-files="no">
|
||||
<autoscan use-inotify="auto">
|
||||
${concatMapStrings toMediaDirectory cfg.mediaDirectories}
|
||||
</autoscan>
|
||||
<scripting script-charset="UTF-8">
|
||||
<common-script>${pkg}/share/${name}/js/common.js</common-script>
|
||||
<playlist-script>${pkg}/share/${name}/js/playlists.js</playlist-script>
|
||||
<virtual-layout type="builtin">
|
||||
<import-script>${pkg}/share/${name}/js/import.js</import-script>
|
||||
</virtual-layout>
|
||||
</scripting>
|
||||
<mappings>
|
||||
<extension-mimetype ignore-unknown="no">
|
||||
<map from="mp3" to="audio/mpeg"/>
|
||||
<map from="ogx" to="application/ogg"/>
|
||||
<map from="ogv" to="video/ogg"/>
|
||||
<map from="oga" to="audio/ogg"/>
|
||||
<map from="ogg" to="audio/ogg"/>
|
||||
<map from="ogm" to="video/ogg"/>
|
||||
<map from="asf" to="video/x-ms-asf"/>
|
||||
<map from="asx" to="video/x-ms-asf"/>
|
||||
<map from="wma" to="audio/x-ms-wma"/>
|
||||
<map from="wax" to="audio/x-ms-wax"/>
|
||||
<map from="wmv" to="video/x-ms-wmv"/>
|
||||
<map from="wvx" to="video/x-ms-wvx"/>
|
||||
<map from="wm" to="video/x-ms-wm"/>
|
||||
<map from="wmx" to="video/x-ms-wmx"/>
|
||||
<map from="m3u" to="audio/x-mpegurl"/>
|
||||
<map from="pls" to="audio/x-scpls"/>
|
||||
<map from="flv" to="video/x-flv"/>
|
||||
<map from="mkv" to="video/x-matroska"/>
|
||||
<map from="mka" to="audio/x-matroska"/>
|
||||
${optionalString cfg.ps3Support ''
|
||||
<map from="avi" to="video/divx"/>
|
||||
''}
|
||||
${optionalString cfg.dsmSupport ''
|
||||
<map from="avi" to="video/avi"/>
|
||||
''}
|
||||
</extension-mimetype>
|
||||
<mimetype-upnpclass>
|
||||
<map from="audio/*" to="object.item.audioItem.musicTrack"/>
|
||||
<map from="video/*" to="object.item.videoItem"/>
|
||||
<map from="image/*" to="object.item.imageItem"/>
|
||||
</mimetype-upnpclass>
|
||||
<mimetype-contenttype>
|
||||
<treat mimetype="audio/mpeg" as="mp3"/>
|
||||
<treat mimetype="application/ogg" as="ogg"/>
|
||||
<treat mimetype="audio/ogg" as="ogg"/>
|
||||
<treat mimetype="audio/x-flac" as="flac"/>
|
||||
<treat mimetype="audio/x-ms-wma" as="wma"/>
|
||||
<treat mimetype="audio/x-wavpack" as="wv"/>
|
||||
<treat mimetype="image/jpeg" as="jpg"/>
|
||||
<treat mimetype="audio/x-mpegurl" as="playlist"/>
|
||||
<treat mimetype="audio/x-scpls" as="playlist"/>
|
||||
<treat mimetype="audio/x-wav" as="pcm"/>
|
||||
<treat mimetype="audio/L16" as="pcm"/>
|
||||
<treat mimetype="video/x-msvideo" as="avi"/>
|
||||
<treat mimetype="video/mp4" as="mp4"/>
|
||||
<treat mimetype="audio/mp4" as="mp4"/>
|
||||
<treat mimetype="application/x-iso9660" as="dvd"/>
|
||||
<treat mimetype="application/x-iso9660-image" as="dvd"/>
|
||||
</mimetype-contenttype>
|
||||
</mappings>
|
||||
<online-content>
|
||||
<YouTube enabled="no" refresh="28800" update-at-start="no" purge-after="604800" racy-content="exclude" format="mp4" hd="no">
|
||||
<favorites user="${name}"/>
|
||||
<standardfeed feed="most_viewed" time-range="today"/>
|
||||
<playlists user="${name}"/>
|
||||
<uploads user="${name}"/>
|
||||
<standardfeed feed="recently_featured" time-range="today"/>
|
||||
</YouTube>
|
||||
</online-content>
|
||||
</import>
|
||||
${transcodingConfig}
|
||||
</config>
|
||||
'';
|
||||
defaultFirewallRules = {
|
||||
# udp 1900 port needs to be opened for SSDP (not configurable within
|
||||
# mediatomb/gerbera) cf.
|
||||
# http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
|
||||
allowedUDPPorts = [ 1900 cfg.port ];
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.mediatomb = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the Gerbera/Mediatomb DLNA server.
|
||||
'';
|
||||
};
|
||||
|
||||
serverName = mkOption {
|
||||
type = types.str;
|
||||
default = "Gerbera (Mediatomb)";
|
||||
description = ''
|
||||
How to identify the server on the network.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.gerbera;
|
||||
defaultText = literalExpression "pkgs.gerbera";
|
||||
description = ''
|
||||
Underlying package to be used with the module.
|
||||
'';
|
||||
};
|
||||
|
||||
ps3Support = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable ps3 specific tweaks.
|
||||
WARNING: incompatible with DSM 320 support.
|
||||
'';
|
||||
};
|
||||
|
||||
dsmSupport = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable D-Link DSM 320 specific tweaks.
|
||||
WARNING: incompatible with ps3 support.
|
||||
'';
|
||||
};
|
||||
|
||||
tg100Support = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable Telegent TG100 specific tweaks.
|
||||
'';
|
||||
};
|
||||
|
||||
transcoding = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable transcoding.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/${name}";
|
||||
defaultText = literalExpression ''"/var/lib/''${config.${opt.package}.pname}"'';
|
||||
description = ''
|
||||
The directory where Gerbera/Mediatomb stores its state, data, etc.
|
||||
'';
|
||||
};
|
||||
|
||||
pcDirectoryHide = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to list the top-level directory or not (from upnp client standpoint).
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "mediatomb";
|
||||
description = "User account under which the service runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "mediatomb";
|
||||
description = "Group account under which the service runs.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 49152;
|
||||
description = ''
|
||||
The network port to listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
A specific interface to bind to.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If false (the default), this is up to the user to declare the firewall rules.
|
||||
If true, this opens port 1900 (tcp and udp) and the port specified by
|
||||
<option>sercvices.mediatomb.port</option>.
|
||||
|
||||
If the option <option>services.mediatomb.interface</option> is set,
|
||||
the firewall rules opened are dedicated to that interface. Otherwise,
|
||||
those rules are opened globally.
|
||||
'';
|
||||
};
|
||||
|
||||
uuid = mkOption {
|
||||
type = types.str;
|
||||
default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687";
|
||||
description = ''
|
||||
A unique (on your network) to identify the server by.
|
||||
'';
|
||||
};
|
||||
|
||||
mediaDirectories = mkOption {
|
||||
type = with types; listOf (submodule mediaDirectory);
|
||||
default = [];
|
||||
description = ''
|
||||
Declare media directories to index.
|
||||
'';
|
||||
example = [
|
||||
{ path = "/data/pictures"; recursive = false; hidden-files = false; }
|
||||
{ path = "/data/audio"; recursive = true; hidden-files = false; }
|
||||
];
|
||||
};
|
||||
|
||||
customCfg = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Allow the service to create and use its own config file inside the <literal>dataDir</literal> as
|
||||
configured by <option>services.mediatomb.dataDir</option>.
|
||||
Deactivated by default, the service then runs with the configuration generated from this module.
|
||||
Otherwise, when enabled, no service configuration is generated. Gerbera/Mediatomb then starts using
|
||||
config.xml within the configured <literal>dataDir</literal>. It's up to the user to make a correct
|
||||
configuration file.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = let binaryCommand = "${pkg}/bin/${name}";
|
||||
interfaceFlag = optionalString ( cfg.interface != "") "--interface ${cfg.interface}";
|
||||
configFlag = optionalString (! cfg.customCfg) "--config ${pkgs.writeText "config.xml" configText}";
|
||||
in mkIf cfg.enable {
|
||||
systemd.services.mediatomb = {
|
||||
description = "${cfg.serverName} media Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.ExecStart = "${binaryCommand} --port ${toString cfg.port} ${interfaceFlag} ${configFlag} --home ${cfg.dataDir}";
|
||||
serviceConfig.User = cfg.user;
|
||||
serviceConfig.Group = cfg.group;
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "mediatomb") {
|
||||
mediatomb.gid = gid;
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "mediatomb") {
|
||||
mediatomb = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
description = "${name} DLNA Server User";
|
||||
};
|
||||
};
|
||||
|
||||
# Open firewall only if users enable it
|
||||
networking.firewall = mkMerge [
|
||||
(mkIf (cfg.openFirewall && cfg.interface != "") {
|
||||
interfaces."${cfg.interface}" = defaultFirewallRules;
|
||||
})
|
||||
(mkIf (cfg.openFirewall && cfg.interface == "") defaultFirewallRules)
|
||||
];
|
||||
};
|
||||
}
|
||||
103
nixos/modules/services/misc/metabase.nix
Normal file
103
nixos/modules/services/misc/metabase.nix
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.metabase;
|
||||
|
||||
inherit (lib) mkEnableOption mkIf mkOption;
|
||||
inherit (lib) optional optionalAttrs types;
|
||||
|
||||
dataDir = "/var/lib/metabase";
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
services.metabase = {
|
||||
enable = mkEnableOption "Metabase service";
|
||||
|
||||
listen = {
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = ''
|
||||
IP address that Metabase should listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 3000;
|
||||
description = ''
|
||||
Listen port for Metabase.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
ssl = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable SSL (https) support.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8443;
|
||||
description = ''
|
||||
Listen port over SSL (https) for Metabase.
|
||||
'';
|
||||
};
|
||||
|
||||
keystore = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "${dataDir}/metabase.jks";
|
||||
example = "/etc/secrets/keystore.jks";
|
||||
description = ''
|
||||
<link xlink:href="https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores">Java KeyStore</link> file containing the certificates.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for Metabase.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.metabase = {
|
||||
description = "Metabase server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
environment = {
|
||||
MB_PLUGINS_DIR = "${dataDir}/plugins";
|
||||
MB_DB_FILE = "${dataDir}/metabase.db";
|
||||
MB_JETTY_HOST = cfg.listen.ip;
|
||||
MB_JETTY_PORT = toString cfg.listen.port;
|
||||
} // optionalAttrs (cfg.ssl.enable) {
|
||||
MB_JETTY_SSL = true;
|
||||
MB_JETTY_SSL_PORT = toString cfg.ssl.port;
|
||||
MB_JETTY_SSL_KEYSTORE = cfg.ssl.keystore;
|
||||
};
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = baseNameOf dataDir;
|
||||
ExecStart = "${pkgs.metabase}/bin/metabase";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.listen.port ] ++ optional cfg.ssl.enable cfg.ssl.port;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
178
nixos/modules/services/misc/moonraker.nix
Normal file
178
nixos/modules/services/misc/moonraker.nix
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
pkg = pkgs.moonraker;
|
||||
cfg = config.services.moonraker;
|
||||
opt = options.services.moonraker;
|
||||
format = pkgs.formats.ini {
|
||||
# https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
|
||||
listToValue = l:
|
||||
if builtins.length l == 1 then generators.mkValueStringDefault {} (head l)
|
||||
else lib.concatMapStrings (s: "\n ${generators.mkValueStringDefault {} s}") l;
|
||||
mkKeyValue = generators.mkKeyValueDefault {} ":";
|
||||
};
|
||||
in {
|
||||
options = {
|
||||
services.moonraker = {
|
||||
enable = mkEnableOption "Moonraker, an API web server for Klipper";
|
||||
|
||||
klipperSocket = mkOption {
|
||||
type = types.path;
|
||||
default = config.services.klipper.apiSocket;
|
||||
defaultText = literalExpression "config.services.klipper.apiSocket";
|
||||
description = "Path to Klipper's API socket.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/moonraker";
|
||||
description = "The directory containing the Moonraker databases.";
|
||||
};
|
||||
|
||||
configDir = mkOption {
|
||||
type = types.path;
|
||||
default = cfg.stateDir + "/config";
|
||||
defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
|
||||
description = ''
|
||||
The directory containing client-writable configuration files.
|
||||
|
||||
Clients will be able to edit files in this directory via the API. This directory must be writable.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "moonraker";
|
||||
description = "User account under which Moonraker runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "moonraker";
|
||||
description = "Group account under which Moonraker runs.";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
description = "The IP or host to listen on.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 7125;
|
||||
description = "The port to listen on.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = { };
|
||||
example = {
|
||||
authorization = {
|
||||
trusted_clients = [ "10.0.0.0/24" ];
|
||||
cors_domains = [ "https://app.fluidd.xyz" ];
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Configuration for Moonraker. See the <link xlink:href="https://moonraker.readthedocs.io/en/latest/configuration/">documentation</link>
|
||||
for supported values.
|
||||
'';
|
||||
};
|
||||
|
||||
allowSystemControl = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to allow Moonraker to perform system-level operations.
|
||||
|
||||
Moonraker exposes APIs to perform system-level operations, such as
|
||||
reboot, shutdown, and management of systemd units. See the
|
||||
<link xlink:href="https://moonraker.readthedocs.io/en/latest/web_api/#machine-commands">documentation</link>
|
||||
for details on what clients are able to do.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
warnings = optional (cfg.settings ? update_manager)
|
||||
''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.'';
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.allowSystemControl -> config.security.polkit.enable;
|
||||
message = "services.moonraker.allowSystemControl requires polkit to be enabled (security.polkit.enable).";
|
||||
}
|
||||
];
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "moonraker") {
|
||||
moonraker = {
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.moonraker;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "moonraker") {
|
||||
moonraker.gid = config.ids.gids.moonraker;
|
||||
};
|
||||
|
||||
environment.etc."moonraker.cfg".source = let
|
||||
forcedConfig = {
|
||||
server = {
|
||||
host = cfg.address;
|
||||
port = cfg.port;
|
||||
klippy_uds_address = cfg.klipperSocket;
|
||||
config_path = cfg.configDir;
|
||||
database_path = "${cfg.stateDir}/database";
|
||||
};
|
||||
};
|
||||
fullConfig = recursiveUpdate cfg.settings forcedConfig;
|
||||
in format.generate "moonraker.cfg" fullConfig;
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.moonraker = {
|
||||
description = "Moonraker, an API web server for Klipper";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ]
|
||||
++ optional config.services.klipper.enable "klipper.service";
|
||||
|
||||
# Moonraker really wants its own config to be writable...
|
||||
script = ''
|
||||
cp /etc/moonraker.cfg ${cfg.configDir}/moonraker-temp.cfg
|
||||
chmod u+w ${cfg.configDir}/moonraker-temp.cfg
|
||||
exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg
|
||||
'';
|
||||
|
||||
# Needs `ip` command
|
||||
path = [ pkgs.iproute2 ];
|
||||
|
||||
serviceConfig = {
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
Group = cfg.group;
|
||||
User = cfg.user;
|
||||
};
|
||||
};
|
||||
|
||||
security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl ''
|
||||
// nixos/moonraker: Allow Moonraker to perform system-level operations
|
||||
//
|
||||
// This was enabled via services.moonraker.allowSystemControl.
|
||||
polkit.addRule(function(action, subject) {
|
||||
if ((action.id == "org.freedesktop.systemd1.manage-units" ||
|
||||
action.id == "org.freedesktop.login1.power-off" ||
|
||||
action.id == "org.freedesktop.login1.power-off-multiple-sessions" ||
|
||||
action.id == "org.freedesktop.login1.reboot" ||
|
||||
action.id == "org.freedesktop.login1.reboot-multiple-sessions" ||
|
||||
action.id.startsWith("org.freedesktop.packagekit.")) &&
|
||||
subject.user == "${cfg.user}") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
'';
|
||||
};
|
||||
}
|
||||
122
nixos/modules/services/misc/mx-puppet-discord.nix
Normal file
122
nixos/modules/services/misc/mx-puppet-discord.nix
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
dataDir = "/var/lib/mx-puppet-discord";
|
||||
registrationFile = "${dataDir}/discord-registration.yaml";
|
||||
cfg = config.services.mx-puppet-discord;
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
settingsFile = settingsFormat.generate "mx-puppet-discord-config.json" cfg.settings;
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.mx-puppet-discord = {
|
||||
enable = mkEnableOption ''
|
||||
mx-puppet-discord is a discord puppeting bridge for matrix.
|
||||
It handles bridging private and group DMs, as well as Guilds (servers)
|
||||
'';
|
||||
|
||||
settings = mkOption rec {
|
||||
apply = recursiveUpdate default;
|
||||
inherit (settingsFormat) type;
|
||||
default = {
|
||||
bridge.port = 8434;
|
||||
presence = {
|
||||
enabled = true;
|
||||
interval = 500;
|
||||
};
|
||||
provisioning.whitelist = [ ];
|
||||
relay.whitelist = [ ];
|
||||
|
||||
# variables are preceded by a colon.
|
||||
namePatterns = {
|
||||
user = ":name";
|
||||
userOverride = ":displayname";
|
||||
room = ":name";
|
||||
group = ":name";
|
||||
};
|
||||
|
||||
#defaults to sqlite but can be configured to use postgresql with
|
||||
#connstring
|
||||
database.filename = "${dataDir}/database.db";
|
||||
logging = {
|
||||
console = "info";
|
||||
lineDateFormat = "MMM-D HH:mm:ss.SSS";
|
||||
};
|
||||
};
|
||||
example = literalExpression ''
|
||||
{
|
||||
bridge = {
|
||||
bindAddress = "localhost";
|
||||
domain = "example.com";
|
||||
homeserverUrl = "https://example.com";
|
||||
};
|
||||
|
||||
provisioning.whitelist = [ "@admin:example.com" ];
|
||||
relay.whitelist = [ "@.*:example.com" ];
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
<filename>config.yaml</filename> configuration as a Nix attribute set.
|
||||
Configuration options should match those described in
|
||||
<link xlink:href="https://github.com/matrix-discord/mx-puppet-discord/blob/master/sample.config.yaml">
|
||||
sample.config.yaml</link>.
|
||||
'';
|
||||
};
|
||||
serviceDependencies = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
|
||||
defaultText = literalExpression ''
|
||||
optional config.services.matrix-synapse.enable "matrix-synapse.service"
|
||||
'';
|
||||
description = ''
|
||||
List of Systemd services to require and wait for when starting the application service.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.mx-puppet-discord = {
|
||||
description = "Matrix to Discord puppeting bridge";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
|
||||
|
||||
preStart = ''
|
||||
# generate the appservice's registration file if absent
|
||||
if [ ! -f '${registrationFile}' ]; then
|
||||
${pkgs.mx-puppet-discord}/bin/mx-puppet-discord -r -c ${settingsFile} \
|
||||
-f ${registrationFile}
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
|
||||
DynamicUser = true;
|
||||
PrivateTmp = true;
|
||||
WorkingDirectory = pkgs.mx-puppet-discord;
|
||||
StateDirectory = baseNameOf dataDir;
|
||||
UMask = 0027;
|
||||
|
||||
ExecStart = ''
|
||||
${pkgs.mx-puppet-discord}/bin/mx-puppet-discord \
|
||||
-c ${settingsFile} \
|
||||
-f ${registrationFile}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ govanify ];
|
||||
}
|
||||
79
nixos/modules/services/misc/n8n.nix
Normal file
79
nixos/modules/services/misc/n8n.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.n8n;
|
||||
format = pkgs.formats.json {};
|
||||
configFile = format.generate "n8n.json" cfg.settings;
|
||||
in
|
||||
{
|
||||
options.services.n8n = {
|
||||
|
||||
enable = mkEnableOption "n8n server";
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the n8n web interface.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = {};
|
||||
description = ''
|
||||
Configuration for n8n, see <link xlink:href="https://docs.n8n.io/reference/configuration.html"/>
|
||||
for supported values.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.n8n.settings = {
|
||||
# We use this to open the firewall, so we need to know about the default at eval time
|
||||
port = lib.mkDefault 5678;
|
||||
};
|
||||
|
||||
systemd.services.n8n = {
|
||||
description = "N8N service";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
# This folder must be writeable as the application is storing
|
||||
# its data in it, so the StateDirectory is a good choice
|
||||
N8N_USER_FOLDER = "/var/lib/n8n";
|
||||
HOME = "/var/lib/n8n";
|
||||
N8N_CONFIG_FILES = "${configFile}";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkgs.n8n}/bin/n8n";
|
||||
Restart = "on-failure";
|
||||
StateDirectory = "n8n";
|
||||
|
||||
# Basic Hardening
|
||||
NoNewPrivileges = "yes";
|
||||
PrivateTmp = "yes";
|
||||
PrivateDevices = "yes";
|
||||
DevicePolicy = "closed";
|
||||
DynamicUser = "true";
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = "read-only";
|
||||
ProtectControlGroups = "yes";
|
||||
ProtectKernelModules = "yes";
|
||||
ProtectKernelTunables = "yes";
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
RestrictNamespaces = "yes";
|
||||
RestrictRealtime = "yes";
|
||||
RestrictSUIDSGID = "yes";
|
||||
MemoryDenyWriteExecute = "no"; # v8 JIT requires memory segments to be Writable-Executable.
|
||||
LockPersonality = "yes";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.settings.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
358
nixos/modules/services/misc/nitter.nix
Normal file
358
nixos/modules/services/misc/nitter.nix
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.nitter;
|
||||
configFile = pkgs.writeText "nitter.conf" ''
|
||||
${generators.toINI {
|
||||
# String values need to be quoted
|
||||
mkKeyValue = generators.mkKeyValueDefault {
|
||||
mkValueString = v:
|
||||
if isString v then "\"" + (strings.escape ["\""] (toString v)) + "\""
|
||||
else generators.mkValueStringDefault {} v;
|
||||
} " = ";
|
||||
} (lib.recursiveUpdate {
|
||||
Server = cfg.server;
|
||||
Cache = cfg.cache;
|
||||
Config = cfg.config // { hmacKey = "@hmac@"; };
|
||||
Preferences = cfg.preferences;
|
||||
} cfg.settings)}
|
||||
'';
|
||||
# `hmac` is a secret used for cryptographic signing of video URLs.
|
||||
# Generate it on first launch, then copy configuration and replace
|
||||
# `@hmac@` with this value.
|
||||
# We are not using sed as it would leak the value in the command line.
|
||||
preStart = pkgs.writers.writePython3 "nitter-prestart" {} ''
|
||||
import os
|
||||
import secrets
|
||||
|
||||
state_dir = os.environ.get("STATE_DIRECTORY")
|
||||
if not os.path.isfile(f"{state_dir}/hmac"):
|
||||
# Generate hmac on first launch
|
||||
hmac = secrets.token_hex(32)
|
||||
with open(f"{state_dir}/hmac", "w") as f:
|
||||
f.write(hmac)
|
||||
else:
|
||||
# Load previously generated hmac
|
||||
with open(f"{state_dir}/hmac", "r") as f:
|
||||
hmac = f.read()
|
||||
|
||||
configFile = "${configFile}"
|
||||
with open(configFile, "r") as f_in:
|
||||
with open(f"{state_dir}/nitter.conf", "w") as f_out:
|
||||
f_out.write(f_in.read().replace("@hmac@", hmac))
|
||||
'';
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.nitter = {
|
||||
enable = mkEnableOption "If enabled, start Nitter.";
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.nitter;
|
||||
type = types.package;
|
||||
defaultText = literalExpression "pkgs.nitter";
|
||||
description = "The nitter derivation to use.";
|
||||
};
|
||||
|
||||
server = {
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
example = "127.0.0.1";
|
||||
description = "The address to listen on.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
example = 8000;
|
||||
description = "The port to listen on.";
|
||||
};
|
||||
|
||||
https = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Set secure attribute on cookies. Keep it disabled to enable cookies when not using HTTPS.";
|
||||
};
|
||||
|
||||
httpMaxConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
description = "Maximum number of HTTP connections.";
|
||||
};
|
||||
|
||||
staticDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.package}/share/nitter/public";
|
||||
defaultText = literalExpression ''"''${config.services.nitter.package}/share/nitter/public"'';
|
||||
description = "Path to the static files directory.";
|
||||
};
|
||||
|
||||
title = mkOption {
|
||||
type = types.str;
|
||||
default = "nitter";
|
||||
description = "Title of the instance.";
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
example = "nitter.net";
|
||||
description = "Hostname of the instance.";
|
||||
};
|
||||
};
|
||||
|
||||
cache = {
|
||||
listMinutes = mkOption {
|
||||
type = types.int;
|
||||
default = 240;
|
||||
description = "How long to cache list info (not the tweets, so keep it high).";
|
||||
};
|
||||
|
||||
rssMinutes = mkOption {
|
||||
type = types.int;
|
||||
default = 10;
|
||||
description = "How long to cache RSS queries.";
|
||||
};
|
||||
|
||||
redisHost = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Redis host.";
|
||||
};
|
||||
|
||||
redisPort = mkOption {
|
||||
type = types.port;
|
||||
default = 6379;
|
||||
description = "Redis port.";
|
||||
};
|
||||
|
||||
redisConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 20;
|
||||
description = "Redis connection pool size.";
|
||||
};
|
||||
|
||||
redisMaxConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 30;
|
||||
description = ''
|
||||
Maximum number of connections to Redis.
|
||||
|
||||
New connections are opened when none are available, but if the
|
||||
pool size goes above this, they are closed when released, do not
|
||||
worry about this unless you receive tons of requests per second.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
base64Media = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Use base64 encoding for proxied media URLs.";
|
||||
};
|
||||
|
||||
tokenCount = mkOption {
|
||||
type = types.int;
|
||||
default = 10;
|
||||
description = ''
|
||||
Minimum amount of usable tokens.
|
||||
|
||||
Tokens are used to authorize API requests, but they expire after
|
||||
~1 hour, and have a limit of 187 requests. The limit gets reset
|
||||
every 15 minutes, and the pool is filled up so there is always at
|
||||
least tokenCount usable tokens. Only increase this if you receive
|
||||
major bursts all the time.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
preferences = {
|
||||
replaceTwitter = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "nitter.net";
|
||||
description = "Replace Twitter links with links to this instance (blank to disable).";
|
||||
};
|
||||
|
||||
replaceYouTube = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "piped.kavin.rocks";
|
||||
description = "Replace YouTube links with links to this instance (blank to disable).";
|
||||
};
|
||||
|
||||
replaceInstagram = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Replace Instagram links with links to this instance (blank to disable).";
|
||||
};
|
||||
|
||||
mp4Playback = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable MP4 video playback.";
|
||||
};
|
||||
|
||||
hlsPlayback = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable HLS video streaming (requires JavaScript).";
|
||||
};
|
||||
|
||||
proxyVideos = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Proxy video streaming through the server (might be slow).";
|
||||
};
|
||||
|
||||
muteVideos = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Mute videos by default.";
|
||||
};
|
||||
|
||||
autoplayGifs = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Autoplay GIFs.";
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = types.str;
|
||||
default = "Nitter";
|
||||
description = "Instance theme.";
|
||||
};
|
||||
|
||||
infiniteScroll = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Infinite scrolling (requires JavaScript, experimental!).";
|
||||
};
|
||||
|
||||
stickyProfile = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Make profile sidebar stick to top.";
|
||||
};
|
||||
|
||||
bidiSupport = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Support bidirectional text (makes clicking on tweets harder).";
|
||||
};
|
||||
|
||||
hideTweetStats = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide tweet stats (replies, retweets, likes).";
|
||||
};
|
||||
|
||||
hideBanner = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide profile banner.";
|
||||
};
|
||||
|
||||
hidePins = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide pinned tweets.";
|
||||
};
|
||||
|
||||
hideReplies = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide tweet replies.";
|
||||
};
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Add settings here to override NixOS module generated settings.
|
||||
|
||||
Check the official repository for the available settings:
|
||||
https://github.com/zedeus/nitter/blob/master/nitter.example.conf
|
||||
'';
|
||||
};
|
||||
|
||||
redisCreateLocally = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Configure local Redis server for Nitter.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for Nitter web interface.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !cfg.redisCreateLocally || (cfg.cache.redisHost == "localhost" && cfg.cache.redisPort == 6379);
|
||||
message = "When services.nitter.redisCreateLocally is enabled, you need to use localhost:6379 as a cache server.";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.nitter = {
|
||||
description = "Nitter (An alternative Twitter front-end)";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "nitter";
|
||||
Environment = [ "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf" ];
|
||||
# Some parts of Nitter expect `public` folder in working directory,
|
||||
# see https://github.com/zedeus/nitter/issues/414
|
||||
WorkingDirectory = "${cfg.package}/share/nitter";
|
||||
ExecStart = "${cfg.package}/bin/nitter";
|
||||
ExecStartPre = "${preStart}";
|
||||
AmbientCapabilities = lib.mkIf (cfg.server.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
# Hardening
|
||||
CapabilityBoundingSet = if (cfg.server.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
|
||||
DeviceAllow = [ "" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
# A private user cannot have process capabilities on the host's user
|
||||
# namespace and thus CAP_NET_BIND_SERVICE has no effect.
|
||||
PrivateUsers = (cfg.server.port >= 1024);
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
|
||||
services.redis = lib.mkIf (cfg.redisCreateLocally) {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.server.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
826
nixos/modules/services/misc/nix-daemon.nix
Normal file
826
nixos/modules/services/misc/nix-daemon.nix
Normal file
|
|
@ -0,0 +1,826 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.nix;
|
||||
|
||||
nixPackage = cfg.package.out;
|
||||
|
||||
isNixAtLeast = versionAtLeast (getVersion nixPackage);
|
||||
|
||||
makeNixBuildUser = nr: {
|
||||
name = "nixbld${toString nr}";
|
||||
value = {
|
||||
description = "Nix build user ${toString nr}";
|
||||
|
||||
/*
|
||||
For consistency with the setgid(2), setuid(2), and setgroups(2)
|
||||
calls in `libstore/build.cc', don't add any supplementary group
|
||||
here except "nixbld".
|
||||
*/
|
||||
uid = builtins.add config.ids.uids.nixbld nr;
|
||||
isSystemUser = true;
|
||||
group = "nixbld";
|
||||
extraGroups = [ "nixbld" ];
|
||||
};
|
||||
};
|
||||
|
||||
nixbldUsers = listToAttrs (map makeNixBuildUser (range 1 cfg.nrBuildUsers));
|
||||
|
||||
nixConf =
|
||||
assert isNixAtLeast "2.2";
|
||||
let
|
||||
|
||||
mkValueString = v:
|
||||
if v == null then ""
|
||||
else if isInt v then toString v
|
||||
else if isBool v then boolToString v
|
||||
else if isFloat v then floatToString v
|
||||
else if isList v then toString v
|
||||
else if isDerivation v then toString v
|
||||
else if builtins.isPath v then toString v
|
||||
else if isString v then v
|
||||
else if isCoercibleToString v then toString v
|
||||
else abort "The nix conf value: ${toPretty {} v} can not be encoded";
|
||||
|
||||
mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}";
|
||||
|
||||
mkKeyValuePairs = attrs: concatStringsSep "\n" (mapAttrsToList mkKeyValue attrs);
|
||||
|
||||
in
|
||||
pkgs.writeTextFile {
|
||||
name = "nix.conf";
|
||||
text = ''
|
||||
# WARNING: this file is generated from the nix.* options in
|
||||
# your NixOS configuration, typically
|
||||
# /etc/nixos/configuration.nix. Do not edit it!
|
||||
${mkKeyValuePairs cfg.settings}
|
||||
${cfg.extraOptions}
|
||||
'';
|
||||
checkPhase =
|
||||
if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
|
||||
echo "Ignoring validation for cross-compilation"
|
||||
''
|
||||
else ''
|
||||
echo "Validating generated nix.conf"
|
||||
ln -s $out ./nix.conf
|
||||
set -e
|
||||
set +o pipefail
|
||||
NIX_CONF_DIR=$PWD \
|
||||
${cfg.package}/bin/nix show-config ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
|
||||
${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
|
||||
|& sed -e 's/^warning:/error:/' \
|
||||
| (! grep '${if cfg.checkConfig then "^error:" else "^error: unknown setting"}')
|
||||
set -o pipefail
|
||||
'';
|
||||
};
|
||||
|
||||
legacyConfMappings = {
|
||||
useSandbox = "sandbox";
|
||||
buildCores = "cores";
|
||||
maxJobs = "max-jobs";
|
||||
sandboxPaths = "extra-sandbox-paths";
|
||||
binaryCaches = "substituters";
|
||||
trustedBinaryCaches = "trusted-substituters";
|
||||
binaryCachePublicKeys = "trusted-public-keys";
|
||||
autoOptimiseStore = "auto-optimise-store";
|
||||
requireSignedBinaryCaches = "require-sigs";
|
||||
trustedUsers = "trusted-users";
|
||||
allowedUsers = "allowed-users";
|
||||
systemFeatures = "system-features";
|
||||
};
|
||||
|
||||
semanticConfType = with types;
|
||||
let
|
||||
confAtom = nullOr
|
||||
(oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
package
|
||||
]) // {
|
||||
description = "Nix config atom (null, bool, int, float, str, path or package)";
|
||||
};
|
||||
in
|
||||
attrsOf (either confAtom (listOf confAtom));
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "useChroot" ]; to = [ "nix" "useSandbox" ]; })
|
||||
(mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "chrootDirs" ]; to = [ "nix" "sandboxPaths" ]; })
|
||||
(mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" "daemonIONiceLevel" ]; to = [ "nix" "daemonIOSchedPriority" ]; })
|
||||
(mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.")
|
||||
] ++ mapAttrsToList (oldConf: newConf: mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" oldConf ]; to = [ "nix" "settings" newConf ]; }) legacyConfMappings;
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
nix = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable Nix.
|
||||
Disabling Nix makes the system hard to modify and the Nix programs and configuration will not be made available by NixOS itself.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.nix;
|
||||
defaultText = literalExpression "pkgs.nix";
|
||||
description = ''
|
||||
This option specifies the Nix package instance to use throughout the system.
|
||||
'';
|
||||
};
|
||||
|
||||
distributedBuilds = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to distribute builds to the machines listed in
|
||||
<option>nix.buildMachines</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
daemonCPUSchedPolicy = mkOption {
|
||||
type = types.enum [ "other" "batch" "idle" ];
|
||||
default = "other";
|
||||
example = "batch";
|
||||
description = ''
|
||||
Nix daemon process CPU scheduling policy. This policy propagates to
|
||||
build processes. <literal>other</literal> is the default scheduling
|
||||
policy for regular tasks. The <literal>batch</literal> policy is
|
||||
similar to <literal>other</literal>, but optimised for
|
||||
non-interactive tasks. <literal>idle</literal> is for extremely
|
||||
low-priority tasks that should only be run when no other task
|
||||
requires CPU time.
|
||||
|
||||
Please note that while using the <literal>idle</literal> policy may
|
||||
greatly improve responsiveness of a system performing expensive
|
||||
builds, it may also slow down and potentially starve crucial
|
||||
configuration updates during load.
|
||||
|
||||
<literal>idle</literal> may therefore be a sensible policy for
|
||||
systems that experience only intermittent phases of high CPU load,
|
||||
such as desktop or portable computers used interactively. Other
|
||||
systems should use the <literal>other</literal> or
|
||||
<literal>batch</literal> policy instead.
|
||||
|
||||
For more fine-grained resource control, please refer to
|
||||
<citerefentry><refentrytitle>systemd.resource-control
|
||||
</refentrytitle><manvolnum>5</manvolnum></citerefentry> and adjust
|
||||
<option>systemd.services.nix-daemon</option> directly.
|
||||
'';
|
||||
};
|
||||
|
||||
daemonIOSchedClass = mkOption {
|
||||
type = types.enum [ "best-effort" "idle" ];
|
||||
default = "best-effort";
|
||||
example = "idle";
|
||||
description = ''
|
||||
Nix daemon process I/O scheduling class. This class propagates to
|
||||
build processes. <literal>best-effort</literal> is the default
|
||||
class for regular tasks. The <literal>idle</literal> class is for
|
||||
extremely low-priority tasks that should only perform I/O when no
|
||||
other task does.
|
||||
|
||||
Please note that while using the <literal>idle</literal> scheduling
|
||||
class can improve responsiveness of a system performing expensive
|
||||
builds, it might also slow down or starve crucial configuration
|
||||
updates during load.
|
||||
|
||||
<literal>idle</literal> may therefore be a sensible class for
|
||||
systems that experience only intermittent phases of high I/O load,
|
||||
such as desktop or portable computers used interactively. Other
|
||||
systems should use the <literal>best-effort</literal> class.
|
||||
'';
|
||||
};
|
||||
|
||||
daemonIOSchedPriority = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
example = 1;
|
||||
description = ''
|
||||
Nix daemon process I/O scheduling priority. This priority propagates
|
||||
to build processes. The supported priorities depend on the
|
||||
scheduling policy: With idle, priorities are not used in scheduling
|
||||
decisions. best-effort supports values in the range 0 (high) to 7
|
||||
(low).
|
||||
'';
|
||||
};
|
||||
|
||||
buildMachines = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
hostName = mkOption {
|
||||
type = types.str;
|
||||
example = "nixbuilder.example.org";
|
||||
description = ''
|
||||
The hostname of the build machine.
|
||||
'';
|
||||
};
|
||||
system = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "x86_64-linux";
|
||||
description = ''
|
||||
The system type the build machine can execute derivations on.
|
||||
Either this attribute or <varname>systems</varname> must be
|
||||
present, where <varname>system</varname> takes precedence if
|
||||
both are set.
|
||||
'';
|
||||
};
|
||||
systems = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "x86_64-linux" "aarch64-linux" ];
|
||||
description = ''
|
||||
The system types the build machine can execute derivations on.
|
||||
Either this attribute or <varname>system</varname> must be
|
||||
present, where <varname>system</varname> takes precedence if
|
||||
both are set.
|
||||
'';
|
||||
};
|
||||
sshUser = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "builder";
|
||||
description = ''
|
||||
The username to log in as on the remote host. This user must be
|
||||
able to log in and run nix commands non-interactively. It must
|
||||
also be privileged to build derivations, so must be included in
|
||||
<option>nix.settings.trusted-users</option>.
|
||||
'';
|
||||
};
|
||||
sshKey = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/root/.ssh/id_buildhost_builduser";
|
||||
description = ''
|
||||
The path to the SSH private key with which to authenticate on
|
||||
the build machine. The private key must not have a passphrase.
|
||||
If null, the building user (root on NixOS machines) must have an
|
||||
appropriate ssh configuration to log in non-interactively.
|
||||
|
||||
Note that for security reasons, this path must point to a file
|
||||
in the local filesystem, *not* to the nix store.
|
||||
'';
|
||||
};
|
||||
maxJobs = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
The number of concurrent jobs the build machine supports. The
|
||||
build machine will enforce its own limits, but this allows hydra
|
||||
to schedule better since there is no work-stealing between build
|
||||
machines.
|
||||
'';
|
||||
};
|
||||
speedFactor = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
The relative speed of this builder. This is an arbitrary integer
|
||||
that indicates the speed of this builder, relative to other
|
||||
builders. Higher is faster.
|
||||
'';
|
||||
};
|
||||
mandatoryFeatures = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "big-parallel" ];
|
||||
description = ''
|
||||
A list of features mandatory for this builder. The builder will
|
||||
be ignored for derivations that don't require all features in
|
||||
this list. All mandatory features are automatically included in
|
||||
<varname>supportedFeatures</varname>.
|
||||
'';
|
||||
};
|
||||
supportedFeatures = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "kvm" "big-parallel" ];
|
||||
description = ''
|
||||
A list of features supported by this builder. The builder will
|
||||
be ignored for derivations that require features not in this
|
||||
list.
|
||||
'';
|
||||
};
|
||||
publicHostKey = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The (base64-encoded) public host key of this builder. The field
|
||||
is calculated via <command>base64 -w0 /etc/ssh/ssh_host_type_key.pub</command>.
|
||||
If null, SSH will use its regular known-hosts file when connecting.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
description = ''
|
||||
This option lists the machines to be used if distributed builds are
|
||||
enabled (see <option>nix.distributedBuilds</option>).
|
||||
Nix will perform derivations on those machines via SSH by copying the
|
||||
inputs to the Nix store on the remote machine, starting the build,
|
||||
then copying the output back to the local Nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
# Environment variables for running Nix.
|
||||
envVars = mkOption {
|
||||
type = types.attrs;
|
||||
internal = true;
|
||||
default = { };
|
||||
description = "Environment variables used by Nix.";
|
||||
};
|
||||
|
||||
nrBuildUsers = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
Number of <literal>nixbld</literal> user accounts created to
|
||||
perform secure concurrent builds. If you receive an error
|
||||
message saying that “all build users are currently in use”,
|
||||
you should increase this value.
|
||||
'';
|
||||
};
|
||||
|
||||
readOnlyStore = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If set, NixOS will enforce the immutability of the Nix store
|
||||
by making <filename>/nix/store</filename> a read-only bind
|
||||
mount. Nix will automatically make the store writable when
|
||||
needed.
|
||||
'';
|
||||
};
|
||||
|
||||
nixPath = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
|
||||
"nixos-config=/etc/nixos/configuration.nix"
|
||||
"/nix/var/nix/profiles/per-user/root/channels"
|
||||
];
|
||||
description = ''
|
||||
The default Nix expression search path, used by the Nix
|
||||
evaluator to look up paths enclosed in angle brackets
|
||||
(e.g. <literal><nixpkgs></literal>).
|
||||
'';
|
||||
};
|
||||
|
||||
checkConfig = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If enabled (the default), checks for data type mismatches and that Nix
|
||||
can parse the generated nix.conf.
|
||||
'';
|
||||
};
|
||||
|
||||
registry = mkOption {
|
||||
type = types.attrsOf (types.submodule (
|
||||
let
|
||||
referenceAttrs = with types; attrsOf (oneOf [
|
||||
str
|
||||
int
|
||||
bool
|
||||
package
|
||||
]);
|
||||
in
|
||||
{ config, name, ... }:
|
||||
{
|
||||
options = {
|
||||
from = mkOption {
|
||||
type = referenceAttrs;
|
||||
example = { type = "indirect"; id = "nixpkgs"; };
|
||||
description = "The flake reference to be rewritten.";
|
||||
};
|
||||
to = mkOption {
|
||||
type = referenceAttrs;
|
||||
example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
|
||||
description = "The flake reference <option>from</option> is rewritten to.";
|
||||
};
|
||||
flake = mkOption {
|
||||
type = types.nullOr types.attrs;
|
||||
default = null;
|
||||
example = literalExpression "nixpkgs";
|
||||
description = ''
|
||||
The flake input <option>from</option> is rewritten to.
|
||||
'';
|
||||
};
|
||||
exact = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the <option>from</option> reference needs to match exactly. If set,
|
||||
a <option>from</option> reference like <literal>nixpkgs</literal> does not
|
||||
match with a reference like <literal>nixpkgs/nixos-20.03</literal>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = {
|
||||
from = mkDefault { type = "indirect"; id = name; };
|
||||
to = mkIf (config.flake != null) (mkDefault
|
||||
{
|
||||
type = "path";
|
||||
path = config.flake.outPath;
|
||||
} // filterAttrs
|
||||
(n: _: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
|
||||
config.flake);
|
||||
};
|
||||
}
|
||||
));
|
||||
default = { };
|
||||
description = ''
|
||||
A system-wide flake registry.
|
||||
'';
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
keep-outputs = true
|
||||
keep-derivations = true
|
||||
'';
|
||||
description = "Additional text appended to <filename>nix.conf</filename>.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = semanticConfType;
|
||||
|
||||
options = {
|
||||
max-jobs = mkOption {
|
||||
type = types.either types.int (types.enum [ "auto" ]);
|
||||
default = "auto";
|
||||
example = 64;
|
||||
description = ''
|
||||
This option defines the maximum number of jobs that Nix will try to
|
||||
build in parallel. The default is auto, which means it will use all
|
||||
available logical cores. It is recommend to set it to the total
|
||||
number of logical cores in your system (e.g., 16 for two CPUs with 4
|
||||
cores each and hyper-threading).
|
||||
'';
|
||||
};
|
||||
|
||||
auto-optimise-store = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
If set to true, Nix automatically detects files in the store that have
|
||||
identical contents, and replaces them with hard links to a single copy.
|
||||
This saves disk space. If set to false (the default), you can still run
|
||||
nix-store --optimise to get rid of duplicate files.
|
||||
'';
|
||||
};
|
||||
|
||||
cores = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
example = 64;
|
||||
description = ''
|
||||
This option defines the maximum number of concurrent tasks during
|
||||
one build. It affects, e.g., -j option for make.
|
||||
The special value 0 means that the builder should use all
|
||||
available CPU cores in the system. Some builds may become
|
||||
non-deterministic with this option; use with care! Packages will
|
||||
only be affected if enableParallelBuilding is set for them.
|
||||
'';
|
||||
};
|
||||
|
||||
sandbox = mkOption {
|
||||
type = types.either types.bool (types.enum [ "relaxed" ]);
|
||||
default = true;
|
||||
description = ''
|
||||
If set, Nix will perform builds in a sandboxed environment that it
|
||||
will set up automatically for each build. This prevents impurities
|
||||
in builds by disallowing access to dependencies outside of the Nix
|
||||
store by using network and mount namespaces in a chroot environment.
|
||||
This is enabled by default even though it has a possible performance
|
||||
impact due to the initial setup time of a sandbox for each build. It
|
||||
doesn't affect derivation hashes, so changing this option will not
|
||||
trigger a rebuild of packages.
|
||||
'';
|
||||
};
|
||||
|
||||
extra-sandbox-paths = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "/dev" "/proc" ];
|
||||
description = ''
|
||||
Directories from the host filesystem to be included
|
||||
in the sandbox.
|
||||
'';
|
||||
};
|
||||
|
||||
substituters = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
List of binary cache URLs used to obtain pre-built binaries
|
||||
of Nix packages.
|
||||
|
||||
By default https://cache.nixos.org/ is added.
|
||||
'';
|
||||
};
|
||||
|
||||
trusted-substituters = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "https://hydra.nixos.org/" ];
|
||||
description = ''
|
||||
List of binary cache URLs that non-root users can use (in
|
||||
addition to those specified using
|
||||
<option>nix.settings.substituters</option>) by passing
|
||||
<literal>--option binary-caches</literal> to Nix commands.
|
||||
'';
|
||||
};
|
||||
|
||||
require-sigs = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If enabled (the default), Nix will only download binaries from binary caches if
|
||||
they are cryptographically signed with any of the keys listed in
|
||||
<option>nix.settings.trusted-public-keys</option>. If disabled, signatures are neither
|
||||
required nor checked, so it's strongly recommended that you use only
|
||||
trustworthy caches and https to prevent man-in-the-middle attacks.
|
||||
'';
|
||||
};
|
||||
|
||||
trusted-public-keys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ];
|
||||
description = ''
|
||||
List of public keys used to sign binary caches. If
|
||||
<option>nix.settings.trusted-public-keys</option> is enabled,
|
||||
then Nix will use a binary from a binary cache if and only
|
||||
if it is signed by <emphasis>any</emphasis> of the keys
|
||||
listed here. By default, only the key for
|
||||
<uri>cache.nixos.org</uri> is included.
|
||||
'';
|
||||
};
|
||||
|
||||
trusted-users = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "root" ];
|
||||
example = [ "root" "alice" "@wheel" ];
|
||||
description = ''
|
||||
A list of names of users that have additional rights when
|
||||
connecting to the Nix daemon, such as the ability to specify
|
||||
additional binary caches, or to import unsigned NARs. You
|
||||
can also specify groups by prefixing them with
|
||||
<literal>@</literal>; for instance,
|
||||
<literal>@wheel</literal> means all users in the wheel
|
||||
group.
|
||||
'';
|
||||
};
|
||||
|
||||
system-features = mkOption {
|
||||
type = types.listOf types.str;
|
||||
example = [ "kvm" "big-parallel" "gccarch-skylake" ];
|
||||
description = ''
|
||||
The set of features supported by the machine. Derivations
|
||||
can express dependencies on system features through the
|
||||
<literal>requiredSystemFeatures</literal> attribute.
|
||||
|
||||
By default, pseudo-features <literal>nixos-test</literal>, <literal>benchmark</literal>,
|
||||
and <literal>big-parallel</literal> used in Nixpkgs are set, <literal>kvm</literal>
|
||||
is also included in it is avaliable.
|
||||
'';
|
||||
};
|
||||
|
||||
allowed-users = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "*" ];
|
||||
example = [ "@wheel" "@builders" "alice" "bob" ];
|
||||
description = ''
|
||||
A list of names of users (separated by whitespace) that are
|
||||
allowed to connect to the Nix daemon. As with
|
||||
<option>nix.settings.trusted-users</option>, you can specify groups by
|
||||
prefixing them with <literal>@</literal>. Also, you can
|
||||
allow all users by specifying <literal>*</literal>. The
|
||||
default is <literal>*</literal>. Note that trusted users are
|
||||
always allowed to connect.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
use-sandbox = true;
|
||||
show-trace = true;
|
||||
|
||||
system-features = [ "big-parallel" "kvm" "recursive-nix" ];
|
||||
sandbox-paths = { "/bin/sh" = "''${pkgs.busybox-sandbox-shell.out}/bin/busybox"; };
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Configuration for Nix, see
|
||||
<link xlink:href="https://nixos.org/manual/nix/stable/#sec-conf-file"/> or
|
||||
<citerefentry>
|
||||
<refentrytitle>nix.conf</refentrytitle>
|
||||
<manvolnum>5</manvolnum>
|
||||
</citerefentry> for avalaible options.
|
||||
The value declared here will be translated directly to the key-value pairs Nix expects.
|
||||
</para>
|
||||
<para>
|
||||
You can use <command>nix-instantiate --eval --strict '<nixpkgs/nixos>' -A config.nix.settings</command>
|
||||
to view the current value. By default it is empty.
|
||||
</para>
|
||||
<para>
|
||||
Nix configurations defined under <option>nix.*</option> will be translated and applied to this
|
||||
option. In addition, configuration specified in <option>nix.extraOptions</option> which will be appended
|
||||
verbatim to the resulting config file.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages =
|
||||
[
|
||||
nixPackage
|
||||
pkgs.nix-info
|
||||
]
|
||||
++ optional (config.programs.bash.enableCompletion) pkgs.nix-bash-completions;
|
||||
|
||||
environment.etc."nix/nix.conf".source = nixConf;
|
||||
|
||||
environment.etc."nix/registry.json".text = builtins.toJSON {
|
||||
version = 2;
|
||||
flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
|
||||
};
|
||||
|
||||
# List of machines for distributed Nix builds in the format
|
||||
# expected by build-remote.pl.
|
||||
environment.etc."nix/machines" = mkIf (cfg.buildMachines != [ ]) {
|
||||
text =
|
||||
concatMapStrings
|
||||
(machine:
|
||||
(concatStringsSep " " ([
|
||||
"${optionalString (machine.sshUser != null) "${machine.sshUser}@"}${machine.hostName}"
|
||||
(if machine.system != null then machine.system else if machine.systems != [ ] then concatStringsSep "," machine.systems else "-")
|
||||
(if machine.sshKey != null then machine.sshKey else "-")
|
||||
(toString machine.maxJobs)
|
||||
(toString machine.speedFactor)
|
||||
(concatStringsSep "," (machine.supportedFeatures ++ machine.mandatoryFeatures))
|
||||
(concatStringsSep "," machine.mandatoryFeatures)
|
||||
]
|
||||
++ optional (isNixAtLeast "2.4pre") (if machine.publicHostKey != null then machine.publicHostKey else "-")))
|
||||
+ "\n"
|
||||
)
|
||||
cfg.buildMachines;
|
||||
};
|
||||
|
||||
assertions =
|
||||
let badMachine = m: m.system == null && m.systems == [ ];
|
||||
in
|
||||
[
|
||||
{
|
||||
assertion = !(any badMachine cfg.buildMachines);
|
||||
message = ''
|
||||
At least one system type (via <varname>system</varname> or
|
||||
<varname>systems</varname>) must be set for every build machine.
|
||||
Invalid machine specifications:
|
||||
'' + " " +
|
||||
(concatStringsSep "\n "
|
||||
(map (m: m.hostName)
|
||||
(filter (badMachine) cfg.buildMachines)));
|
||||
}
|
||||
];
|
||||
|
||||
systemd.packages = [ nixPackage ];
|
||||
|
||||
# Will only work once https://github.com/NixOS/nix/pull/6285 is merged
|
||||
# systemd.tmpfiles.packages = [ nixPackage ];
|
||||
|
||||
# Can be dropped for Nix > https://github.com/NixOS/nix/pull/6285
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /nix/var/nix/daemon-socket 0755 root root - -"
|
||||
];
|
||||
|
||||
systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
|
||||
|
||||
systemd.services.nix-daemon =
|
||||
{
|
||||
path = [ nixPackage pkgs.util-linux config.programs.ssh.package ]
|
||||
++ optionals cfg.distributedBuilds [ pkgs.gzip ];
|
||||
|
||||
environment = cfg.envVars
|
||||
// { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
|
||||
// config.networking.proxy.envVars;
|
||||
|
||||
unitConfig.RequiresMountsFor = "/nix/store";
|
||||
|
||||
serviceConfig =
|
||||
{
|
||||
CPUSchedulingPolicy = cfg.daemonCPUSchedPolicy;
|
||||
IOSchedulingClass = cfg.daemonIOSchedClass;
|
||||
IOSchedulingPriority = cfg.daemonIOSchedPriority;
|
||||
LimitNOFILE = 1048576;
|
||||
};
|
||||
|
||||
restartTriggers = [ nixConf ];
|
||||
|
||||
# `stopIfChanged = false` changes to switch behavior
|
||||
# from stop -> update units -> start
|
||||
# to update units -> restart
|
||||
#
|
||||
# The `stopIfChanged` setting therefore controls a trade-off between a
|
||||
# more predictable lifecycle, which runs the correct "version" of
|
||||
# the `ExecStop` line, and on the other hand the availability of
|
||||
# sockets during the switch, as the effectiveness of the stop operation
|
||||
# depends on the socket being stopped as well.
|
||||
#
|
||||
# As `nix-daemon.service` does not make use of `ExecStop`, we prefer
|
||||
# to keep the socket up and available. This is important for machines
|
||||
# that run Nix-based services, such as automated build, test, and deploy
|
||||
# services, that expect the daemon socket to be available at all times.
|
||||
#
|
||||
# Notably, the Nix client does not retry on failure to connect to the
|
||||
# daemon socket, and the in-process RemoteStore instance will disable
|
||||
# itself. This makes retries infeasible even for services that are
|
||||
# aware of the issue. Failure to connect can affect not only new client
|
||||
# processes, but also new RemoteStore instances in existing processes,
|
||||
# as well as existing RemoteStore instances that have not saturated
|
||||
# their connection pool.
|
||||
#
|
||||
# Also note that `stopIfChanged = true` does not kill existing
|
||||
# connection handling daemons, as one might wish to happen before a
|
||||
# breaking Nix upgrade (which is rare). The daemon forks that handle
|
||||
# the individual connections split off into their own sessions, causing
|
||||
# them not to be stopped by systemd.
|
||||
# If a Nix upgrade does require all existing daemon processes to stop,
|
||||
# nix-daemon must do so on its own accord, and only when the new version
|
||||
# starts and detects that Nix's persistent state needs an upgrade.
|
||||
stopIfChanged = false;
|
||||
|
||||
};
|
||||
|
||||
# Set up the environment variables for running Nix.
|
||||
environment.sessionVariables = cfg.envVars // { NIX_PATH = cfg.nixPath; };
|
||||
|
||||
environment.extraInit =
|
||||
''
|
||||
if [ -e "$HOME/.nix-defexpr/channels" ]; then
|
||||
export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
|
||||
fi
|
||||
'';
|
||||
|
||||
nix.nrBuildUsers = mkDefault (max 32 (if cfg.settings.max-jobs == "auto" then 0 else cfg.settings.max-jobs));
|
||||
|
||||
users.users = nixbldUsers;
|
||||
|
||||
services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
|
||||
|
||||
system.activationScripts.nix = stringAfter [ "etc" "users" ]
|
||||
''
|
||||
install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
|
||||
|
||||
# Subscribe the root user to the NixOS channel by default.
|
||||
if [ ! -e "/root/.nix-channels" ]; then
|
||||
echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
|
||||
fi
|
||||
'';
|
||||
|
||||
# Legacy configuration conversion.
|
||||
nix.settings = mkMerge [
|
||||
{
|
||||
trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
|
||||
substituters = mkAfter [ "https://cache.nixos.org/" ];
|
||||
|
||||
system-features = mkDefault (
|
||||
[ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
|
||||
optionals (pkgs.hostPlatform ? gcc.arch) (
|
||||
# a builder can run code for `gcc.arch` and inferior architectures
|
||||
[ "gccarch-${pkgs.hostPlatform.gcc.arch}" ] ++
|
||||
map (x: "gccarch-${x}") systems.architectures.inferiors.${pkgs.hostPlatform.gcc.arch}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
(mkIf (!cfg.distributedBuilds) { builders = null; })
|
||||
|
||||
(mkIf (isNixAtLeast "2.3pre") { sandbox-fallback = false; })
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
106
nixos/modules/services/misc/nix-gc.nix
Normal file
106
nixos/modules/services/misc/nix-gc.nix
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.nix.gc;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
nix.gc = {
|
||||
|
||||
automatic = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Automatically run the garbage collector at a specific time.";
|
||||
};
|
||||
|
||||
dates = mkOption {
|
||||
type = types.str;
|
||||
default = "03:15";
|
||||
example = "weekly";
|
||||
description = ''
|
||||
How often or when garbage collection is performed. For most desktop and server systems
|
||||
a sufficient garbage collection is once a week.
|
||||
|
||||
The format is described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>.
|
||||
'';
|
||||
};
|
||||
|
||||
randomizedDelaySec = mkOption {
|
||||
default = "0";
|
||||
type = types.str;
|
||||
example = "45min";
|
||||
description = ''
|
||||
Add a randomized delay before each garbage collection.
|
||||
The delay will be chosen between zero and this value.
|
||||
This value must be a time span in the format specified by
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>
|
||||
'';
|
||||
};
|
||||
|
||||
persistent = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
example = false;
|
||||
description = ''
|
||||
Takes a boolean argument. If true, the time when the service
|
||||
unit was last triggered is stored on disk. When the timer is
|
||||
activated, the service unit is triggered immediately if it
|
||||
would have been triggered at least once during the time when
|
||||
the timer was inactive. Such triggering is nonetheless
|
||||
subject to the delay imposed by RandomizedDelaySec=. This is
|
||||
useful to catch up on missed runs of the service when the
|
||||
system was powered down.
|
||||
'';
|
||||
};
|
||||
|
||||
options = mkOption {
|
||||
default = "";
|
||||
example = "--max-freed $((64 * 1024**3))";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Options given to <filename>nix-collect-garbage</filename> when the
|
||||
garbage collector is run automatically.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.automatic -> config.nix.enable;
|
||||
message = ''nix.gc.automatic requires nix.enable'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.nix-gc = lib.mkIf config.nix.enable {
|
||||
description = "Nix Garbage Collector";
|
||||
script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
|
||||
startAt = optional cfg.automatic cfg.dates;
|
||||
};
|
||||
|
||||
systemd.timers.nix-gc = lib.mkIf cfg.automatic {
|
||||
timerConfig = {
|
||||
RandomizedDelaySec = cfg.randomizedDelaySec;
|
||||
Persistent = cfg.persistent;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
57
nixos/modules/services/misc/nix-optimise.nix
Normal file
57
nixos/modules/services/misc/nix-optimise.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.nix.optimise;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
nix.optimise = {
|
||||
|
||||
automatic = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Automatically run the nix store optimiser at a specific time.";
|
||||
};
|
||||
|
||||
dates = mkOption {
|
||||
default = ["03:45"];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Specification (in the format described by
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>) of the time at
|
||||
which the optimiser will run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.automatic -> config.nix.enable;
|
||||
message = ''nix.optimise.automatic requires nix.enable'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.nix-optimise = lib.mkIf config.nix.enable
|
||||
{ description = "Nix Store Optimiser";
|
||||
# No point this if the nix daemon (and thus the nix store) is outside
|
||||
unitConfig.ConditionPathIsReadWrite = "/nix/var/nix/daemon-socket";
|
||||
serviceConfig.ExecStart = "${config.nix.package}/bin/nix-store --optimise";
|
||||
startAt = optionals cfg.automatic cfg.dates;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
69
nixos/modules/services/misc/nix-ssh-serve.nix
Normal file
69
nixos/modules/services/misc/nix-ssh-serve.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let cfg = config.nix.sshServe;
|
||||
command =
|
||||
if cfg.protocol == "ssh"
|
||||
then "nix-store --serve ${lib.optionalString cfg.write "--write"}"
|
||||
else "nix-daemon --stdio";
|
||||
in {
|
||||
options = {
|
||||
|
||||
nix.sshServe = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable serving the Nix store as a remote store via SSH.";
|
||||
};
|
||||
|
||||
write = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable writing to the Nix store as a remote store via SSH. Note: the sshServe user is named nix-ssh and is not a trusted-user. nix-ssh should be added to the <option>nix.settings.trusted-users</option> option in most use cases, such as allowing remote building of derivations.";
|
||||
};
|
||||
|
||||
keys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "ssh-dss AAAAB3NzaC1k... alice@example.org" ];
|
||||
description = "A list of SSH public keys allowed to access the binary cache via SSH.";
|
||||
};
|
||||
|
||||
protocol = mkOption {
|
||||
type = types.enum [ "ssh" "ssh-ng" ];
|
||||
default = "ssh";
|
||||
description = "The specific Nix-over-SSH protocol to use.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.nix-ssh = {
|
||||
description = "Nix SSH store user";
|
||||
isSystemUser = true;
|
||||
group = "nix-ssh";
|
||||
useDefaultShell = true;
|
||||
};
|
||||
users.groups.nix-ssh = {};
|
||||
|
||||
services.openssh.enable = true;
|
||||
|
||||
services.openssh.extraConfig = ''
|
||||
Match User nix-ssh
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
PermitTTY no
|
||||
PermitTunnel no
|
||||
X11Forwarding no
|
||||
ForceCommand ${config.nix.package.out}/bin/${command}
|
||||
Match All
|
||||
'';
|
||||
|
||||
users.users.nix-ssh.openssh.authorizedKeys.keys = cfg.keys;
|
||||
|
||||
};
|
||||
}
|
||||
31
nixos/modules/services/misc/novacomd.nix
Normal file
31
nixos/modules/services/misc/novacomd.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.novacomd;
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
services.novacomd = {
|
||||
enable = mkEnableOption "Novacom service for connecting to WebOS devices";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.webos.novacom ];
|
||||
|
||||
systemd.services.novacomd = {
|
||||
description = "Novacom WebOS daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.webos.novacomd}/sbin/novacomd";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ dtzWill ];
|
||||
}
|
||||
117
nixos/modules/services/misc/nzbget.nix
Normal file
117
nixos/modules/services/misc/nzbget.nix
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.nzbget;
|
||||
pkg = pkgs.nzbget;
|
||||
stateDir = "/var/lib/nzbget";
|
||||
configFile = "${stateDir}/nzbget.conf";
|
||||
configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${escapeShellArg (toStr value)}") cfg.settings);
|
||||
toStr = v:
|
||||
if v == true then "yes"
|
||||
else if v == false then "no"
|
||||
else if isInt v then toString v
|
||||
else v;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.")
|
||||
(mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.")
|
||||
(mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.")
|
||||
];
|
||||
|
||||
# interface
|
||||
|
||||
options = {
|
||||
services.nzbget = {
|
||||
enable = mkEnableOption "NZBGet";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "nzbget";
|
||||
description = "User account under which NZBGet runs";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "nzbget";
|
||||
description = "Group under which NZBGet runs";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ bool int str ]);
|
||||
default = {};
|
||||
description = ''
|
||||
NZBGet configuration, passed via command line using switch -o. Refer to
|
||||
<link xlink:href="https://github.com/nzbget/nzbget/blob/master/nzbget.conf"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
example = {
|
||||
MainDir = "/data";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.nzbget.settings = {
|
||||
# allows nzbget to run as a "simple" service
|
||||
OutputMode = "loggable";
|
||||
# use journald for logging
|
||||
WriteLog = "none";
|
||||
ErrorTarget = "screen";
|
||||
WarningTarget = "screen";
|
||||
InfoTarget = "screen";
|
||||
DetailTarget = "screen";
|
||||
# required paths
|
||||
ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
|
||||
WebDir = "${pkg}/share/nzbget/webui";
|
||||
# nixos handles package updates
|
||||
UpdateCheck = "none";
|
||||
};
|
||||
|
||||
systemd.services.nzbget = {
|
||||
description = "NZBGet Daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [
|
||||
unrar
|
||||
p7zip
|
||||
];
|
||||
|
||||
preStart = ''
|
||||
if [ ! -f ${configFile} ]; then
|
||||
${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
StateDirectory = "nzbget";
|
||||
StateDirectoryMode = "0750";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
UMask = "0002";
|
||||
Restart = "on-failure";
|
||||
ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
|
||||
ExecStop = "${pkg}/bin/nzbget --quit";
|
||||
};
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "nzbget") {
|
||||
nzbget = {
|
||||
home = stateDir;
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.nzbget;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "nzbget") {
|
||||
nzbget = {
|
||||
gid = config.ids.gids.nzbget;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
78
nixos/modules/services/misc/nzbhydra2.nix
Normal file
78
nixos/modules/services/misc/nzbhydra2.nix
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let cfg = config.services.nzbhydra2;
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.nzbhydra2 = {
|
||||
enable = mkEnableOption "NZBHydra2";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/nzbhydra2";
|
||||
description = "The directory where NZBHydra2 stores its data files.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description =
|
||||
"Open ports in the firewall for the NZBHydra2 web interface.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.nzbhydra2;
|
||||
defaultText = literalExpression "pkgs.nzbhydra2";
|
||||
description = "NZBHydra2 package to use.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules =
|
||||
[ "d '${cfg.dataDir}' 0700 nzbhydra2 nzbhydra2 - -" ];
|
||||
|
||||
systemd.services.nzbhydra2 = {
|
||||
description = "NZBHydra2";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "nzbhydra2";
|
||||
Group = "nzbhydra2";
|
||||
ExecStart =
|
||||
"${cfg.package}/bin/nzbhydra2 --nobrowser --datafolder '${cfg.dataDir}'";
|
||||
Restart = "on-failure";
|
||||
# Hardening
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
DevicePolicy = "closed";
|
||||
ProtectSystem = "strict";
|
||||
ReadWritePaths = cfg.dataDir;
|
||||
ProtectHome = "read-only";
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
RestrictAddressFamilies ="AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ 5076 ]; };
|
||||
|
||||
users.users.nzbhydra2 = {
|
||||
group = "nzbhydra2";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.nzbhydra2 = {};
|
||||
};
|
||||
}
|
||||
133
nixos/modules/services/misc/octoprint.nix
Normal file
133
nixos/modules/services/misc/octoprint.nix
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.octoprint;
|
||||
|
||||
baseConfig = {
|
||||
plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
|
||||
server.host = cfg.host;
|
||||
server.port = cfg.port;
|
||||
webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
|
||||
};
|
||||
|
||||
fullConfig = recursiveUpdate cfg.extraConfig baseConfig;
|
||||
|
||||
cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
|
||||
|
||||
pluginsEnv = package.python.withPackages (ps: [ps.octoprint] ++ (cfg.plugins ps));
|
||||
|
||||
package = pkgs.octoprint;
|
||||
|
||||
in
|
||||
{
|
||||
##### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.octoprint = {
|
||||
|
||||
enable = mkEnableOption "OctoPrint, web interface for 3D printers";
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = ''
|
||||
Host to bind OctoPrint to.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 5000;
|
||||
description = ''
|
||||
Port to bind OctoPrint to.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "octoprint";
|
||||
description = "User for the daemon.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "octoprint";
|
||||
description = "Group for the daemon.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/octoprint";
|
||||
description = "State directory of the daemon.";
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = types.functionTo (types.listOf types.package);
|
||||
default = plugins: [];
|
||||
defaultText = literalExpression "plugins: []";
|
||||
example = literalExpression "plugins: with plugins; [ themeify stlviewer ]";
|
||||
description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = "Extra options which are added to OctoPrint's YAML configuration file.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
##### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "octoprint") {
|
||||
octoprint = {
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.octoprint;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "octoprint") {
|
||||
octoprint.gid = config.ids.gids.octoprint;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.octoprint = {
|
||||
description = "OctoPrint, web interface for 3D printers";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
path = [ pluginsEnv ];
|
||||
|
||||
preStart = ''
|
||||
if [ -e "${cfg.stateDir}/config.yaml" ]; then
|
||||
${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
|
||||
mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
|
||||
else
|
||||
cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
|
||||
chmod 600 "${cfg.stateDir}/config.yaml"
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
SupplementaryGroups = [
|
||||
"dialout"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
81
nixos/modules/services/misc/ombi.nix
Normal file
81
nixos/modules/services/misc/ombi.nix
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let cfg = config.services.ombi;
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.ombi = {
|
||||
enable = mkEnableOption ''
|
||||
Ombi.
|
||||
Optionally see <link xlink:href="https://docs.ombi.app/info/reverse-proxy"/>
|
||||
on how to set up a reverse proxy
|
||||
'';
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/ombi";
|
||||
description = "The directory where Ombi stores its data files.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 5000;
|
||||
description = "The port for the Ombi web interface.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the Ombi web interface.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "ombi";
|
||||
description = "User account under which Ombi runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "ombi";
|
||||
description = "Group under which Ombi runs.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.ombi = {
|
||||
description = "Ombi";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${pkgs.ombi}/bin/Ombi --storage '${cfg.dataDir}' --host 'http://*:${toString cfg.port}'";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "ombi") {
|
||||
ombi = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "ombi") { ombi = { }; };
|
||||
};
|
||||
}
|
||||
86
nixos/modules/services/misc/osrm.nix
Normal file
86
nixos/modules/services/misc/osrm.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.osrm;
|
||||
in
|
||||
|
||||
{
|
||||
options.services.osrm = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable the OSRM service.";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "IP address on which the web server will listen.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 5000;
|
||||
description = "Port on which the web server will run.";
|
||||
};
|
||||
|
||||
threads = mkOption {
|
||||
type = types.int;
|
||||
default = 4;
|
||||
description = "Number of threads to use.";
|
||||
};
|
||||
|
||||
algorithm = mkOption {
|
||||
type = types.enum [ "CH" "CoreCH" "MLD" ];
|
||||
default = "MLD";
|
||||
description = "Algorithm to use for the data. Must be one of CH, CoreCH, MLD";
|
||||
};
|
||||
|
||||
extraFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "--max-table-size 1000" "--max-matching-size 1000" ];
|
||||
description = "Extra command line arguments passed to osrm-routed";
|
||||
};
|
||||
|
||||
dataFile = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/lib/osrm/berlin-latest.osrm";
|
||||
description = "Data file location";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.osrm = {
|
||||
group = config.users.users.osrm.name;
|
||||
description = "OSRM user";
|
||||
createHome = false;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.osrm = { };
|
||||
|
||||
systemd.services.osrm = {
|
||||
description = "OSRM service";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = config.users.users.osrm.name;
|
||||
ExecStart = ''
|
||||
${pkgs.osrm-backend}/bin/osrm-routed \
|
||||
--ip ${cfg.address} \
|
||||
--port ${toString cfg.port} \
|
||||
--threads ${toString cfg.threads} \
|
||||
--algorithm ${cfg.algorithm} \
|
||||
${toString cfg.extraFlags} \
|
||||
${cfg.dataFile}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
98
nixos/modules/services/misc/owncast.nix
Normal file
98
nixos/modules/services/misc/owncast.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{ lib, pkgs, config, ... }:
|
||||
with lib;
|
||||
let cfg = config.services.owncast;
|
||||
in {
|
||||
|
||||
options.services.owncast = {
|
||||
|
||||
enable = mkEnableOption "owncast";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/owncast";
|
||||
description = ''
|
||||
The directory where owncast stores its data files. If left as the default value this directory will automatically be created before the owncast server starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open the appropriate ports in the firewall for owncast.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "owncast";
|
||||
description = "User account under which owncast runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "owncast";
|
||||
description = "Group under which owncast runs.";
|
||||
};
|
||||
|
||||
listen = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
description = "The IP address to bind the owncast web server to.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
description = ''
|
||||
TCP port where owncast web-gui listens.
|
||||
'';
|
||||
};
|
||||
|
||||
rtmp-port = mkOption {
|
||||
type = types.port;
|
||||
default = 1935;
|
||||
description = ''
|
||||
TCP port where owncast rtmp service listens.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.owncast = {
|
||||
description = "A self-hosted live video and web chat server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = mkMerge [
|
||||
{
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
ExecStart = "${pkgs.owncast}/bin/owncast -webserverport ${toString cfg.port} -rtmpport ${toString cfg.rtmp-port} -webserverip ${cfg.listen}";
|
||||
Restart = "on-failure";
|
||||
}
|
||||
(mkIf (cfg.dataDir == "/var/lib/owncast") {
|
||||
StateDirectory = "owncast";
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "owncast") {
|
||||
owncast = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
description = "owncast system user";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "owncast") { owncast = { }; };
|
||||
|
||||
networking.firewall =
|
||||
mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.rtmp-port ] ++ optional (cfg.listen != "127.0.0.1") cfg.port; };
|
||||
|
||||
};
|
||||
meta = { maintainers = with lib.maintainers; [ MayNiklas ]; };
|
||||
}
|
||||
74
nixos/modules/services/misc/packagekit.nix
Normal file
74
nixos/modules/services/misc/packagekit.nix
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.packagekit;
|
||||
|
||||
inherit (lib)
|
||||
mkEnableOption mkOption mkIf mkRemovedOptionModule types
|
||||
listToAttrs recursiveUpdate;
|
||||
|
||||
iniFmt = pkgs.formats.ini { };
|
||||
|
||||
confFiles = [
|
||||
(iniFmt.generate "PackageKit.conf" (recursiveUpdate
|
||||
{
|
||||
Daemon = {
|
||||
DefaultBackend = "nix";
|
||||
KeepCache = false;
|
||||
};
|
||||
}
|
||||
cfg.settings))
|
||||
|
||||
(iniFmt.generate "Vendor.conf" (recursiveUpdate
|
||||
{
|
||||
PackagesNotFound = rec {
|
||||
DefaultUrl = "https://github.com/NixOS/nixpkgs";
|
||||
CodecUrl = DefaultUrl;
|
||||
HardwareUrl = DefaultUrl;
|
||||
FontUrl = DefaultUrl;
|
||||
MimeUrl = DefaultUrl;
|
||||
};
|
||||
}
|
||||
cfg.vendorSettings))
|
||||
];
|
||||
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "packagekit" "backend" ] "Always set to Nix.")
|
||||
];
|
||||
|
||||
options.services.packagekit = {
|
||||
enable = mkEnableOption ''
|
||||
PackageKit provides a cross-platform D-Bus abstraction layer for
|
||||
installing software. Software utilizing PackageKit can install
|
||||
software regardless of the package manager.
|
||||
'';
|
||||
|
||||
settings = mkOption {
|
||||
type = iniFmt.type;
|
||||
default = { };
|
||||
description = "Additional settings passed straight through to PackageKit.conf";
|
||||
};
|
||||
|
||||
vendorSettings = mkOption {
|
||||
type = iniFmt.type;
|
||||
default = { };
|
||||
description = "Additional settings passed straight through to Vendor.conf";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.dbus.packages = with pkgs; [ packagekit ];
|
||||
|
||||
environment.systemPackages = with pkgs; [ packagekit ];
|
||||
|
||||
systemd.packages = with pkgs; [ packagekit ];
|
||||
|
||||
environment.etc = listToAttrs (map
|
||||
(e:
|
||||
lib.nameValuePair "PackageKit/${e.name}" { source = e; })
|
||||
confFiles);
|
||||
};
|
||||
}
|
||||
320
nixos/modules/services/misc/paperless.nix
Normal file
320
nixos/modules/services/misc/paperless.nix
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.paperless;
|
||||
|
||||
defaultUser = "paperless";
|
||||
|
||||
# Don't start a redis instance if the user sets a custom redis connection
|
||||
enableRedis = !hasAttr "PAPERLESS_REDIS" cfg.extraConfig;
|
||||
redisServer = config.services.redis.servers.paperless;
|
||||
|
||||
env = {
|
||||
PAPERLESS_DATA_DIR = cfg.dataDir;
|
||||
PAPERLESS_MEDIA_ROOT = cfg.mediaDir;
|
||||
PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
|
||||
GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
|
||||
} // (
|
||||
lib.mapAttrs (_: toString) cfg.extraConfig
|
||||
) // (optionalAttrs enableRedis {
|
||||
PAPERLESS_REDIS = "unix://${redisServer.unixSocket}";
|
||||
});
|
||||
|
||||
manage = let
|
||||
setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
|
||||
in pkgs.writeShellScript "manage" ''
|
||||
${setupEnv}
|
||||
exec ${cfg.package}/bin/paperless-ngx "$@"
|
||||
'';
|
||||
|
||||
# Secure the services
|
||||
defaultServiceConfig = {
|
||||
TemporaryFileSystem = "/:ro";
|
||||
BindReadOnlyPaths = [
|
||||
"/nix/store"
|
||||
"-/etc/resolv.conf"
|
||||
"-/etc/nsswitch.conf"
|
||||
"-/etc/hosts"
|
||||
"-/etc/localtime"
|
||||
"-/run/postgresql"
|
||||
] ++ (optional enableRedis redisServer.unixSocket);
|
||||
BindPaths = [
|
||||
cfg.consumptionDir
|
||||
cfg.dataDir
|
||||
cfg.mediaDir
|
||||
];
|
||||
CapabilityBoundingSet = "";
|
||||
# ProtectClock adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = true;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
# Breaks if the home dir of the user is in /home
|
||||
# Also does not add much value in combination with the TemporaryFileSystem.
|
||||
# ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
# Would re-mount paths ignored by temporary root
|
||||
#ProtectSystem = "strict";
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
# Don't restrict ProcSubset because django-q requires read access to /proc/stat
|
||||
# to query CPU and memory information.
|
||||
# Note that /proc only contains processes of user `paperless`, so this is safe.
|
||||
# ProcSubset = "pid";
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SupplementaryGroups = optional enableRedis redisServer.user;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
|
||||
# Does not work well with the temporary root
|
||||
#UMask = "0066";
|
||||
};
|
||||
in
|
||||
{
|
||||
meta.maintainers = with maintainers; [ earvstedt Flakebi ];
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ])
|
||||
];
|
||||
|
||||
options.services.paperless = {
|
||||
enable = mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable Paperless.
|
||||
|
||||
When started, the Paperless database is automatically created if it doesn't
|
||||
exist and updated if the Paperless package has changed.
|
||||
Both tasks are achieved by running a Django migration.
|
||||
|
||||
A script to manage the Paperless instance (by wrapping Django's manage.py) is linked to
|
||||
<literal>''${dataDir}/paperless-manage</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/paperless";
|
||||
description = "Directory to store the Paperless data.";
|
||||
};
|
||||
|
||||
mediaDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.dataDir}/media";
|
||||
defaultText = literalExpression ''"''${dataDir}/media"'';
|
||||
description = "Directory to store the Paperless documents.";
|
||||
};
|
||||
|
||||
consumptionDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.dataDir}/consume";
|
||||
defaultText = literalExpression ''"''${dataDir}/consume"'';
|
||||
description = "Directory from which new documents are imported.";
|
||||
};
|
||||
|
||||
consumptionDirIsPublic = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether all users can write to the consumption dir.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/paperless-password";
|
||||
description = ''
|
||||
A file containing the superuser password.
|
||||
|
||||
A superuser is required to access the web interface.
|
||||
If unset, you can create a superuser manually by running
|
||||
<literal>''${dataDir}/paperless-manage createsuperuser</literal>.
|
||||
|
||||
The default superuser name is <literal>admin</literal>. To change it, set
|
||||
option <option>extraConfig.PAPERLESS_ADMIN_USER</option>.
|
||||
WARNING: When changing the superuser name after the initial setup, the old superuser
|
||||
will continue to exist.
|
||||
|
||||
To disable login for the web interface, set the following:
|
||||
<literal>extraConfig.PAPERLESS_AUTO_LOGIN_USERNAME = "admin";</literal>.
|
||||
WARNING: Only use this on a trusted system without internet access to Paperless.
|
||||
'';
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Web interface address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 28981;
|
||||
description = "Web interface port.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Extra paperless config options.
|
||||
|
||||
See <link xlink:href="https://paperless-ngx.readthedocs.io/en/latest/configuration.html">the documentation</link>
|
||||
for available options.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
PAPERLESS_OCR_LANGUAGE = "deu+eng";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = defaultUser;
|
||||
description = "User under which Paperless runs.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.paperless-ngx;
|
||||
defaultText = literalExpression "pkgs.paperless-ngx";
|
||||
description = "The Paperless package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.redis.servers.paperless.enable = mkIf enableRedis true;
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
|
||||
"d '${cfg.mediaDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
|
||||
(if cfg.consumptionDirIsPublic then
|
||||
"d '${cfg.consumptionDir}' 777 - - - -"
|
||||
else
|
||||
"d '${cfg.consumptionDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
|
||||
)
|
||||
];
|
||||
|
||||
systemd.services.paperless-scheduler = {
|
||||
description = "Paperless scheduler";
|
||||
serviceConfig = defaultServiceConfig // {
|
||||
User = cfg.user;
|
||||
ExecStart = "${cfg.package}/bin/paperless-ngx qcluster";
|
||||
Restart = "on-failure";
|
||||
# The `mbind` syscall is needed for running the classifier.
|
||||
SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ];
|
||||
# Needs to talk to mail server for automated import rules
|
||||
PrivateNetwork = false;
|
||||
};
|
||||
environment = env;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "paperless-consumer.service" "paperless-web.service" ];
|
||||
|
||||
preStart = ''
|
||||
ln -sf ${manage} ${cfg.dataDir}/paperless-manage
|
||||
|
||||
# Auto-migrate on first run or if the package has changed
|
||||
versionFile="${cfg.dataDir}/src-version"
|
||||
if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
|
||||
${cfg.package}/bin/paperless-ngx migrate
|
||||
echo ${cfg.package} > "$versionFile"
|
||||
fi
|
||||
''
|
||||
+ optionalString (cfg.passwordFile != null) ''
|
||||
export PAPERLESS_ADMIN_USER="''${PAPERLESS_ADMIN_USER:-admin}"
|
||||
export PAPERLESS_ADMIN_PASSWORD=$(cat "${cfg.dataDir}/superuser-password")
|
||||
superuserState="$PAPERLESS_ADMIN_USER:$PAPERLESS_ADMIN_PASSWORD"
|
||||
superuserStateFile="${cfg.dataDir}/superuser-state"
|
||||
|
||||
if [[ $(cat "$superuserStateFile" 2>/dev/null) != $superuserState ]]; then
|
||||
${cfg.package}/bin/paperless-ngx manage_superuser
|
||||
echo "$superuserState" > "$superuserStateFile"
|
||||
fi
|
||||
'';
|
||||
} // optionalAttrs enableRedis {
|
||||
after = [ "redis-paperless.service" ];
|
||||
};
|
||||
|
||||
# Reading the user-provided password file requires root access
|
||||
systemd.services.paperless-copy-password = mkIf (cfg.passwordFile != null) {
|
||||
requiredBy = [ "paperless-scheduler.service" ];
|
||||
before = [ "paperless-scheduler.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${pkgs.coreutils}/bin/install --mode 600 --owner '${cfg.user}' --compare \
|
||||
'${cfg.passwordFile}' '${cfg.dataDir}/superuser-password'
|
||||
'';
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.paperless-consumer = {
|
||||
description = "Paperless document consumer";
|
||||
serviceConfig = defaultServiceConfig // {
|
||||
User = cfg.user;
|
||||
ExecStart = "${cfg.package}/bin/paperless-ngx document_consumer";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
environment = env;
|
||||
# Bind to `paperless-scheduler` so that the consumer never runs
|
||||
# during migrations
|
||||
bindsTo = [ "paperless-scheduler.service" ];
|
||||
after = [ "paperless-scheduler.service" ];
|
||||
};
|
||||
|
||||
systemd.services.paperless-web = {
|
||||
description = "Paperless web server";
|
||||
serviceConfig = defaultServiceConfig // {
|
||||
User = cfg.user;
|
||||
ExecStart = ''
|
||||
${pkgs.python3Packages.gunicorn}/bin/gunicorn \
|
||||
-c ${cfg.package}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
|
||||
# gunicorn needs setuid
|
||||
SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid" ];
|
||||
# Needs to serve web page
|
||||
PrivateNetwork = false;
|
||||
};
|
||||
environment = env // {
|
||||
PATH = mkForce cfg.package.path;
|
||||
PYTHONPATH = "${cfg.package.pythonPath}:${cfg.package}/lib/paperless-ngx/src";
|
||||
};
|
||||
# Allow the web interface to access the private /tmp directory of the server.
|
||||
# This is required to support uploading files via the web interface.
|
||||
unitConfig.JoinsNamespaceOf = "paperless-scheduler.service";
|
||||
# Bind to `paperless-scheduler` so that the web server never runs
|
||||
# during migrations
|
||||
bindsTo = [ "paperless-scheduler.service" ];
|
||||
after = [ "paperless-scheduler.service" ];
|
||||
};
|
||||
|
||||
users = optionalAttrs (cfg.user == defaultUser) {
|
||||
users.${defaultUser} = {
|
||||
group = defaultUser;
|
||||
uid = config.ids.uids.paperless;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
|
||||
groups.${defaultUser} = {
|
||||
gid = config.ids.gids.paperless;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
129
nixos/modules/services/misc/parsoid.nix
Normal file
129
nixos/modules/services/misc/parsoid.nix
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.parsoid;
|
||||
|
||||
parsoid = pkgs.nodePackages.parsoid;
|
||||
|
||||
confTree = {
|
||||
worker_heartbeat_timeout = 300000;
|
||||
logging = { level = "info"; };
|
||||
services = [{
|
||||
module = "lib/index.js";
|
||||
entrypoint = "apiServiceWorker";
|
||||
conf = {
|
||||
mwApis = map (x: if isAttrs x then x else { uri = x; }) cfg.wikis;
|
||||
serverInterface = cfg.interface;
|
||||
serverPort = cfg.port;
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
confFile = pkgs.writeText "config.yml" (builtins.toJSON (recursiveUpdate confTree cfg.extraConfig));
|
||||
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead")
|
||||
];
|
||||
|
||||
##### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.parsoid = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable Parsoid -- bidirectional
|
||||
wikitext parser.
|
||||
'';
|
||||
};
|
||||
|
||||
wikis = mkOption {
|
||||
type = types.listOf (types.either types.str types.attrs);
|
||||
example = [ "http://localhost/api.php" ];
|
||||
description = ''
|
||||
Used MediaWiki API endpoints.
|
||||
'';
|
||||
};
|
||||
|
||||
workers = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = ''
|
||||
Number of Parsoid workers.
|
||||
'';
|
||||
};
|
||||
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
Interface to listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 8000;
|
||||
description = ''
|
||||
Port to listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Extra configuration to add to parsoid configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
##### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.parsoid = {
|
||||
description = "Bidirectional wikitext parser";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${parsoid}/lib/node_modules/parsoid/bin/server.js -c ${confFile} -n ${toString cfg.workers}";
|
||||
|
||||
DynamicUser = true;
|
||||
User = "parsoid";
|
||||
Group = "parsoid";
|
||||
|
||||
CapabilityBoundingSet = "";
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
#MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RemoveIPC = true;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
60
nixos/modules/services/misc/persistent-evdev.nix
Normal file
60
nixos/modules/services/misc/persistent-evdev.nix
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.persistent-evdev;
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
|
||||
configFile = settingsFormat.generate "persistent-evdev-config" {
|
||||
cache = "/var/cache/persistent-evdev";
|
||||
devices = lib.mapAttrs (virt: phys: "/dev/input/by-id/${phys}") cfg.devices;
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.persistent-evdev = {
|
||||
enable = lib.mkEnableOption "virtual input devices that persist even if the backing device is hotplugged";
|
||||
|
||||
devices = lib.mkOption {
|
||||
default = {};
|
||||
type = with lib.types; attrsOf str;
|
||||
description = ''
|
||||
A set of virtual proxy device labels with backing physical device ids.
|
||||
|
||||
Physical devices should already exist in <filename class="devicefile">/dev/input/by-id/</filename>.
|
||||
Proxy devices will be automatically given a <literal>uinput-</literal> prefix.
|
||||
|
||||
See the <link xlink:href="https://github.com/aiberia/persistent-evdev#example-usage-with-libvirt">
|
||||
project page</link> for example configuration of virtual devices with libvirt
|
||||
and remember to add <literal>uinput-*</literal> devices to the qemu
|
||||
<literal>cgroup_device_acl</literal> list (see <xref linkend="opt-virtualisation.libvirtd.qemu.verbatimConfig"/>).
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
persist-mouse0 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-event-if01";
|
||||
persist-mouse1 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-event-mouse";
|
||||
persist-mouse2 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-if01-event-kbd";
|
||||
persist-keyboard0 = "usb-Microsoft_Natural®_Ergonomic_Keyboard_4000-event-kbd";
|
||||
persist-keyboard1 = "usb-Microsoft_Natural®_Ergonomic_Keyboard_4000-if01-event-kbd";
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
systemd.services.persistent-evdev = {
|
||||
documentation = [ "https://github.com/aiberia/persistent-evdev/blob/master/README.md" ];
|
||||
description = "Persistent evdev proxy";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
ExecStart = "${pkgs.persistent-evdev}/bin/persistent-evdev.py ${configFile}";
|
||||
CacheDirectory = "persistent-evdev";
|
||||
};
|
||||
};
|
||||
|
||||
services.udev.packages = [ pkgs.persistent-evdev ];
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ lodi ];
|
||||
}
|
||||
103
nixos/modules/services/misc/pinnwand.nix
Normal file
103
nixos/modules/services/misc/pinnwand.nix
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.pinnwand;
|
||||
|
||||
format = pkgs.formats.toml {};
|
||||
configFile = format.generate "pinnwand.toml" cfg.settings;
|
||||
in
|
||||
{
|
||||
options.services.pinnwand = {
|
||||
enable = mkEnableOption "Pinnwand";
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
description = "The port to listen on.";
|
||||
default = 8000;
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
description = ''
|
||||
Your <filename>pinnwand.toml</filename> as a Nix attribute set. Look up
|
||||
possible options in the <link xlink:href="https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example">pinnwand.toml-example</link>.
|
||||
'';
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.pinnwand.settings = {
|
||||
database_uri = mkDefault "sqlite:////var/lib/pinnwand/pinnwand.db";
|
||||
paste_size = mkDefault 262144;
|
||||
paste_help = mkDefault ''
|
||||
<p>Welcome to pinnwand, this site is a pastebin. It allows you to share code with others. If you write code in the text area below and press the paste button you will be given a link you can share with others so they can view your code as well.</p><p>People with the link can view your pasted code, only you can remove your paste and it expires automatically. Note that anyone could guess the URI to your paste so don't rely on it being private.</p>
|
||||
'';
|
||||
footer = mkDefault ''
|
||||
View <a href="//github.com/supakeen/pinnwand" target="_BLANK">source code</a>, the <a href="/removal">removal</a> or <a href="/expiry">expiry</a> stories, or read the <a href="/about">about</a> page.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services = let
|
||||
hardeningOptions = {
|
||||
User = "pinnwand";
|
||||
DynamicUser = true;
|
||||
|
||||
StateDirectory = "pinnwand";
|
||||
StateDirectoryMode = "0700";
|
||||
|
||||
AmbientCapabilities = [];
|
||||
CapabilityBoundingSet = "";
|
||||
DevicePolicy = "closed";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_UNIX"
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = "@system-service";
|
||||
UMask = "0077";
|
||||
};
|
||||
|
||||
command = "${pkgs.pinnwand}/bin/pinnwand --configuration-path ${configFile}";
|
||||
in {
|
||||
pinnwand = {
|
||||
description = "Pinnwannd HTTP Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
unitConfig.Documentation = "https://pinnwand.readthedocs.io/en/latest/";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${command} http --port ${toString(cfg.port)}";
|
||||
} // hardeningOptions;
|
||||
};
|
||||
|
||||
pinnwand-reaper = {
|
||||
description = "Pinnwand Reaper";
|
||||
startAt = "daily";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${command} -vvvv reap"; # verbosity increased to show number of deleted pastes
|
||||
} // hardeningOptions;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
180
nixos/modules/services/misc/plex.nix
Normal file
180
nixos/modules/services/misc/plex.nix
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.plex;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "plex" "managePlugins" ] "Please omit or define the option: `services.plex.extraPlugins' instead.")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.plex = {
|
||||
enable = mkEnableOption "Plex Media Server";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/plex";
|
||||
description = ''
|
||||
The directory where Plex stores its data files.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for the media server.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "plex";
|
||||
description = ''
|
||||
User account under which Plex runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "plex";
|
||||
description = ''
|
||||
Group under which Plex runs.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPlugins = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = ''
|
||||
A list of paths to extra plugin bundles to install in Plex's plugin
|
||||
directory. Every time the systemd unit for Plex starts up, all of the
|
||||
symlinks in Plex's plugin directory will be cleared and this module
|
||||
will symlink all of the paths specified here to that directory.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
(builtins.path {
|
||||
name = "Audnexus.bundle";
|
||||
path = pkgs.fetchFromGitHub {
|
||||
owner = "djdembeck";
|
||||
repo = "Audnexus.bundle";
|
||||
rev = "v0.2.8";
|
||||
sha256 = "sha256-IWOSz3vYL7zhdHan468xNc6C/eQ2C2BukQlaJNLXh7E=";
|
||||
};
|
||||
})
|
||||
]
|
||||
'';
|
||||
};
|
||||
|
||||
extraScanners = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = ''
|
||||
A list of paths to extra scanners to install in Plex's scanners
|
||||
directory.
|
||||
|
||||
Every time the systemd unit for Plex starts up, all of the symlinks
|
||||
in Plex's scanners directory will be cleared and this module will
|
||||
symlink all of the paths specified here to that directory.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[
|
||||
(fetchFromGitHub {
|
||||
owner = "ZeroQI";
|
||||
repo = "Absolute-Series-Scanner";
|
||||
rev = "773a39f502a1204b0b0255903cee4ed02c46fde0";
|
||||
sha256 = "4l+vpiDdC8L/EeJowUgYyB3JPNTZ1sauN8liFAcK+PY=";
|
||||
})
|
||||
]
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.plex;
|
||||
defaultText = literalExpression "pkgs.plex";
|
||||
description = ''
|
||||
The Plex package to use. Plex subscribers may wish to use their own
|
||||
package here, pointing to subscriber-only server versions.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Most of this is just copied from the RPM package's systemd service file.
|
||||
systemd.services.plex = {
|
||||
description = "Plex Media Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
||||
# Run the pre-start script with full permissions (the "!" prefix) so it
|
||||
# can create the data directory if necessary.
|
||||
ExecStartPre = let
|
||||
preStartScript = pkgs.writeScript "plex-run-prestart" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
|
||||
# Create data directory if it doesn't exist
|
||||
if ! test -d "$PLEX_DATADIR"; then
|
||||
echo "Creating initial Plex data directory in: $PLEX_DATADIR"
|
||||
install -d -m 0755 -o "${cfg.user}" -g "${cfg.group}" "$PLEX_DATADIR"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
"!${preStartScript}";
|
||||
|
||||
ExecStart = "${cfg.package}/bin/plexmediaserver";
|
||||
KillSignal = "SIGQUIT";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
|
||||
environment = {
|
||||
# Configuration for our FHS userenv script
|
||||
PLEX_DATADIR=cfg.dataDir;
|
||||
PLEX_PLUGINS=concatMapStringsSep ":" builtins.toString cfg.extraPlugins;
|
||||
PLEX_SCANNERS=concatMapStringsSep ":" builtins.toString cfg.extraScanners;
|
||||
|
||||
# The following variables should be set by the FHS userenv script:
|
||||
# PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR
|
||||
# PLEX_MEDIA_SERVER_HOME
|
||||
|
||||
# Allow access to GPU acceleration; the Plex LD_LIBRARY_PATH is added
|
||||
# by the FHS userenv script.
|
||||
LD_LIBRARY_PATH="/run/opengl-driver/lib";
|
||||
|
||||
PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS="6";
|
||||
PLEX_MEDIA_SERVER_TMPDIR="/tmp";
|
||||
PLEX_MEDIA_SERVER_USE_SYSLOG="true";
|
||||
LC_ALL="en_US.UTF-8";
|
||||
LANG="en_US.UTF-8";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 32400 3005 8324 32469 ];
|
||||
allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "plex") {
|
||||
plex = {
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.plex;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "plex") {
|
||||
plex = {
|
||||
gid = config.ids.gids.plex;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
82
nixos/modules/services/misc/plikd.nix
Normal file
82
nixos/modules/services/misc/plikd.nix
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.plikd;
|
||||
|
||||
format = pkgs.formats.toml {};
|
||||
plikdCfg = format.generate "plikd.cfg" cfg.settings;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.plikd = {
|
||||
enable = mkEnableOption "the plikd server";
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the plikd.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = {};
|
||||
description = ''
|
||||
Configuration for plikd, see <link xlink:href="https://github.com/root-gg/plik/blob/master/server/plikd.cfg"/>
|
||||
for supported values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.plikd.settings = mapAttrs (name: mkDefault) {
|
||||
ListenPort = 8080;
|
||||
ListenAddress = "localhost";
|
||||
DataBackend = "file";
|
||||
DataBackendConfig = {
|
||||
Directory = "/var/lib/plikd";
|
||||
};
|
||||
MetadataBackendConfig = {
|
||||
Driver = "sqlite3";
|
||||
ConnectionString = "/var/lib/plikd/plik.db";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.plikd = {
|
||||
description = "Plikd file sharing server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkgs.plikd}/bin/plikd --config ${plikdCfg}";
|
||||
Restart = "on-failure";
|
||||
StateDirectory = "plikd";
|
||||
LogsDirectory = "plikd";
|
||||
DynamicUser = true;
|
||||
|
||||
# Basic hardening
|
||||
NoNewPrivileges = "yes";
|
||||
PrivateTmp = "yes";
|
||||
PrivateDevices = "yes";
|
||||
DevicePolicy = "closed";
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = "read-only";
|
||||
ProtectControlGroups = "yes";
|
||||
ProtectKernelModules = "yes";
|
||||
ProtectKernelTunables = "yes";
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
RestrictNamespaces = "yes";
|
||||
RestrictRealtime = "yes";
|
||||
RestrictSUIDSGID = "yes";
|
||||
MemoryDenyWriteExecute = "yes";
|
||||
LockPersonality = "yes";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.settings.ListenPort ];
|
||||
};
|
||||
};
|
||||
}
|
||||
50
nixos/modules/services/misc/podgrab.nix
Normal file
50
nixos/modules/services/misc/podgrab.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.services.podgrab;
|
||||
in
|
||||
{
|
||||
options.services.podgrab = with lib; {
|
||||
enable = mkEnableOption "Podgrab, a self-hosted podcast manager";
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "/run/secrets/password.env";
|
||||
description = ''
|
||||
The path to a file containing the PASSWORD environment variable
|
||||
definition for Podgrab's authentification.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
example = 4242;
|
||||
description = "The port on which Podgrab will listen for incoming HTTP traffic.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.podgrab = {
|
||||
description = "Podgrab podcast manager";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
CONFIG = "/var/lib/podgrab/config";
|
||||
DATA = "/var/lib/podgrab/data";
|
||||
GIN_MODE = "release";
|
||||
PORT = toString cfg.port;
|
||||
};
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
EnvironmentFile = lib.optional (cfg.passwordFile != null) [
|
||||
cfg.passwordFile
|
||||
];
|
||||
ExecStart = "${pkgs.podgrab}/bin/podgrab";
|
||||
WorkingDirectory = "${pkgs.podgrab}/share";
|
||||
StateDirectory = [ "podgrab/config" "podgrab/data" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ ambroisie ];
|
||||
}
|
||||
41
nixos/modules/services/misc/prowlarr.nix
Normal file
41
nixos/modules/services/misc/prowlarr.nix
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.prowlarr;
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.prowlarr = {
|
||||
enable = mkEnableOption "Prowlarr";
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the Prowlarr web interface.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.prowlarr = {
|
||||
description = "Prowlarr";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "prowlarr";
|
||||
ExecStart = "${pkgs.prowlarr}/bin/Prowlarr -nobrowser -data=/var/lib/prowlarr";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 9696 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
92
nixos/modules/services/misc/pykms.nix
Normal file
92
nixos/modules/services/misc/pykms.nix
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.pykms;
|
||||
libDir = "/var/lib/pykms";
|
||||
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ peterhoeg ];
|
||||
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "pykms" "verbose" ] "Use services.pykms.logLevel instead")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.pykms = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the PyKMS service.";
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "The IP address on which to listen.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 1688;
|
||||
description = "The port on which to listen.";
|
||||
};
|
||||
|
||||
openFirewallPort = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether the listening port should be opened automatically.";
|
||||
};
|
||||
|
||||
memoryLimit = mkOption {
|
||||
type = types.str;
|
||||
default = "64M";
|
||||
description = "How much memory to use at most.";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MININFO" ];
|
||||
default = "INFO";
|
||||
description = "How much to log";
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Additional arguments";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewallPort [ cfg.port ];
|
||||
|
||||
systemd.services.pykms = {
|
||||
description = "Python KMS";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# python programs with DynamicUser = true require HOME to be set
|
||||
environment.HOME = libDir;
|
||||
serviceConfig = with pkgs; {
|
||||
DynamicUser = true;
|
||||
StateDirectory = baseNameOf libDir;
|
||||
ExecStartPre = "${getBin pykms}/libexec/create_pykms_db.sh ${libDir}/clients.db";
|
||||
ExecStart = lib.concatStringsSep " " ([
|
||||
"${getBin pykms}/bin/server"
|
||||
"--logfile=STDOUT"
|
||||
"--loglevel=${cfg.logLevel}"
|
||||
"--sqlite=${libDir}/clients.db"
|
||||
] ++ cfg.extraArgs ++ [
|
||||
cfg.listenAddress
|
||||
(toString cfg.port)
|
||||
]);
|
||||
ProtectHome = "tmpfs";
|
||||
WorkingDirectory = libDir;
|
||||
SyslogIdentifier = "pykms";
|
||||
Restart = "on-failure";
|
||||
MemoryLimit = cfg.memoryLimit;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
75
nixos/modules/services/misc/radarr.nix
Normal file
75
nixos/modules/services/misc/radarr.nix
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.radarr;
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.radarr = {
|
||||
enable = mkEnableOption "Radarr";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/radarr/.config/Radarr";
|
||||
description = "The directory where Radarr stores its data files.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the Radarr web interface.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "radarr";
|
||||
description = "User account under which Radarr runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "radarr";
|
||||
description = "Group under which Radarr runs.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.radarr = {
|
||||
description = "Radarr";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${pkgs.radarr}/bin/Radarr -nobrowser -data='${cfg.dataDir}'";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 7878 ];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "radarr") {
|
||||
radarr = {
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
uid = config.ids.uids.radarr;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "radarr") {
|
||||
radarr.gid = config.ids.gids.radarr;
|
||||
};
|
||||
};
|
||||
}
|
||||
384
nixos/modules/services/misc/redmine.nix
Normal file
384
nixos/modules/services/misc/redmine.nix
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkBefore mkDefault mkEnableOption mkIf mkOption mkRemovedOptionModule types;
|
||||
inherit (lib) concatStringsSep literalExpression mapAttrsToList;
|
||||
inherit (lib) optional optionalAttrs optionalString;
|
||||
|
||||
cfg = config.services.redmine;
|
||||
format = pkgs.formats.yaml {};
|
||||
bundle = "${cfg.package}/share/redmine/bin/bundle";
|
||||
|
||||
databaseYml = pkgs.writeText "database.yml" ''
|
||||
production:
|
||||
adapter: ${cfg.database.type}
|
||||
database: ${cfg.database.name}
|
||||
host: ${if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host}
|
||||
port: ${toString cfg.database.port}
|
||||
username: ${cfg.database.user}
|
||||
password: #dbpass#
|
||||
${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
|
||||
'';
|
||||
|
||||
configurationYml = format.generate "configuration.yml" cfg.settings;
|
||||
additionalEnvironment = pkgs.writeText "additional_environment.rb" cfg.extraEnv;
|
||||
|
||||
unpackTheme = unpack "theme";
|
||||
unpackPlugin = unpack "plugin";
|
||||
unpack = id: (name: source:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "redmine-${id}-${name}";
|
||||
nativeBuildInputs = [ pkgs.unzip ];
|
||||
buildCommand = ''
|
||||
mkdir -p $out
|
||||
cd $out
|
||||
unpackFile ${source}
|
||||
'';
|
||||
});
|
||||
|
||||
mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql2";
|
||||
pgsqlLocal = cfg.database.createLocally && cfg.database.type == "postgresql";
|
||||
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "redmine" "extraConfig" ] "Use services.redmine.settings instead.")
|
||||
(mkRemovedOptionModule [ "services" "redmine" "database" "password" ] "Use services.redmine.database.passwordFile instead.")
|
||||
];
|
||||
|
||||
# interface
|
||||
options = {
|
||||
services.redmine = {
|
||||
enable = mkEnableOption "Redmine";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.redmine;
|
||||
defaultText = literalExpression "pkgs.redmine";
|
||||
description = "Which Redmine package to use.";
|
||||
example = literalExpression "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "redmine";
|
||||
description = "User under which Redmine is ran.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "redmine";
|
||||
description = "Group under which Redmine is ran.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 3000;
|
||||
description = "Port on which Redmine is ran.";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/redmine";
|
||||
description = "The state directory, logs and plugins are stored here.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = {};
|
||||
description = ''
|
||||
Redmine configuration (<filename>configuration.yml</filename>). Refer to
|
||||
<link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
|
||||
for details.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
email_delivery = {
|
||||
delivery_method = "smtp";
|
||||
smtp_settings = {
|
||||
address = "mail.example.com";
|
||||
port = 25;
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraEnv = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra configuration in additional_environment.rb.
|
||||
|
||||
See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
|
||||
for details.
|
||||
'';
|
||||
example = ''
|
||||
config.logger.level = Logger::DEBUG
|
||||
'';
|
||||
};
|
||||
|
||||
themes = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
description = "Set of themes.";
|
||||
example = literalExpression ''
|
||||
{
|
||||
dkuk-redmine_alex_skin = builtins.fetchurl {
|
||||
url = "https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip";
|
||||
sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = {};
|
||||
description = "Set of plugins.";
|
||||
example = literalExpression ''
|
||||
{
|
||||
redmine_env_auth = builtins.fetchurl {
|
||||
url = "https://github.com/Intera/redmine_env_auth/archive/0.6.zip";
|
||||
sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
database = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "mysql2" "postgresql" ];
|
||||
example = "postgresql";
|
||||
default = "mysql2";
|
||||
description = "Database engine to use.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Database host address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = if cfg.database.type == "postgresql" then 5432 else 3306;
|
||||
defaultText = literalExpression "3306";
|
||||
description = "Database host port.";
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "redmine";
|
||||
description = "Database name.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "redmine";
|
||||
description = "Database user.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/redmine-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
<option>database.user</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
socket = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default =
|
||||
if mysqlLocal then "/run/mysqld/mysqld.sock"
|
||||
else if pgsqlLocal then "/run/postgresql"
|
||||
else null;
|
||||
defaultText = literalExpression "/run/mysqld/mysqld.sock";
|
||||
example = "/run/mysqld/mysqld.sock";
|
||||
description = "Path to the unix socket file to use for authentication.";
|
||||
};
|
||||
|
||||
createLocally = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Create the database and database user locally.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# implementation
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{ assertion = cfg.database.passwordFile != null || cfg.database.socket != null;
|
||||
message = "one of services.redmine.database.socket or services.redmine.database.passwordFile must be set";
|
||||
}
|
||||
{ assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
|
||||
message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
|
||||
}
|
||||
{ assertion = cfg.database.createLocally -> cfg.database.socket != null;
|
||||
message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
|
||||
}
|
||||
{ assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
|
||||
message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
|
||||
}
|
||||
];
|
||||
|
||||
services.redmine.settings = {
|
||||
production = {
|
||||
scm_subversion_command = "${pkgs.subversion}/bin/svn";
|
||||
scm_mercurial_command = "${pkgs.mercurial}/bin/hg";
|
||||
scm_git_command = "${pkgs.git}/bin/git";
|
||||
scm_cvs_command = "${pkgs.cvs}/bin/cvs";
|
||||
scm_bazaar_command = "${pkgs.breezy}/bin/bzr";
|
||||
scm_darcs_command = "${pkgs.darcs}/bin/darcs";
|
||||
};
|
||||
};
|
||||
|
||||
services.redmine.extraEnv = mkBefore ''
|
||||
config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576)
|
||||
config.logger.level = Logger::INFO
|
||||
'';
|
||||
|
||||
services.mysql = mkIf mysqlLocal {
|
||||
enable = true;
|
||||
package = mkDefault pkgs.mariadb;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{ name = cfg.database.user;
|
||||
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.postgresql = mkIf pgsqlLocal {
|
||||
enable = true;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{ name = cfg.database.user;
|
||||
ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# create symlinks for the basic directory layout the redmine package expects
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/cache' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/config' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/files' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/plugins' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/public' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/public/plugin_assets' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/public/themes' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.stateDir}/tmp' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
|
||||
"d /run/redmine - - - - -"
|
||||
"d /run/redmine/public - - - - -"
|
||||
"L+ /run/redmine/config - - - - ${cfg.stateDir}/config"
|
||||
"L+ /run/redmine/files - - - - ${cfg.stateDir}/files"
|
||||
"L+ /run/redmine/log - - - - ${cfg.stateDir}/log"
|
||||
"L+ /run/redmine/plugins - - - - ${cfg.stateDir}/plugins"
|
||||
"L+ /run/redmine/public/plugin_assets - - - - ${cfg.stateDir}/public/plugin_assets"
|
||||
"L+ /run/redmine/public/themes - - - - ${cfg.stateDir}/public/themes"
|
||||
"L+ /run/redmine/tmp - - - - ${cfg.stateDir}/tmp"
|
||||
];
|
||||
|
||||
systemd.services.redmine = {
|
||||
after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment.RAILS_ENV = "production";
|
||||
environment.RAILS_CACHE = "${cfg.stateDir}/cache";
|
||||
environment.REDMINE_LANG = "en";
|
||||
environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
|
||||
path = with pkgs; [
|
||||
imagemagick
|
||||
breezy
|
||||
cvs
|
||||
darcs
|
||||
git
|
||||
mercurial
|
||||
subversion
|
||||
];
|
||||
preStart = ''
|
||||
rm -rf "${cfg.stateDir}/plugins/"*
|
||||
rm -rf "${cfg.stateDir}/public/themes/"*
|
||||
|
||||
# start with a fresh config directory
|
||||
# the config directory is copied instead of linked as some mutable data is stored in there
|
||||
find "${cfg.stateDir}/config" ! -name "secret_token.rb" -type f -exec rm -f {} +
|
||||
cp -r ${cfg.package}/share/redmine/config.dist/* "${cfg.stateDir}/config/"
|
||||
|
||||
chmod -R u+w "${cfg.stateDir}/config"
|
||||
|
||||
# link in the application configuration
|
||||
ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml"
|
||||
|
||||
# link in the additional environment configuration
|
||||
ln -fs ${additionalEnvironment} "${cfg.stateDir}/config/additional_environment.rb"
|
||||
|
||||
|
||||
# link in all user specified themes
|
||||
for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do
|
||||
ln -fs $theme/* "${cfg.stateDir}/public/themes"
|
||||
done
|
||||
|
||||
# link in redmine provided themes
|
||||
ln -sf ${cfg.package}/share/redmine/public/themes.dist/* "${cfg.stateDir}/public/themes/"
|
||||
|
||||
|
||||
# link in all user specified plugins
|
||||
for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do
|
||||
ln -fs $plugin/* "${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}"
|
||||
done
|
||||
|
||||
|
||||
# handle database.passwordFile & permissions
|
||||
DBPASS=${optionalString (cfg.database.passwordFile != null) "$(head -n1 ${cfg.database.passwordFile})"}
|
||||
cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml"
|
||||
sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
|
||||
chmod 440 "${cfg.stateDir}/config/database.yml"
|
||||
|
||||
|
||||
# generate a secret token if required
|
||||
if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
|
||||
${bundle} exec rake generate_secret_token
|
||||
chmod 440 "${cfg.stateDir}/config/initializers/secret_token.rb"
|
||||
fi
|
||||
|
||||
# execute redmine required commands prior to starting the application
|
||||
${bundle} exec rake db:migrate
|
||||
${bundle} exec rake redmine:plugins:migrate
|
||||
${bundle} exec rake redmine:load_default_data
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
TimeoutSec = "300";
|
||||
WorkingDirectory = "${cfg.package}/share/redmine";
|
||||
ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "redmine") {
|
||||
redmine = {
|
||||
group = cfg.group;
|
||||
home = cfg.stateDir;
|
||||
uid = config.ids.uids.redmine;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "redmine") {
|
||||
redmine.gid = config.ids.gids.redmine;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
195
nixos/modules/services/misc/ripple-data-api.nix
Normal file
195
nixos/modules/services/misc/ripple-data-api.nix
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.rippleDataApi;
|
||||
|
||||
deployment_env_config = builtins.toJSON {
|
||||
production = {
|
||||
port = toString cfg.port;
|
||||
maxSockets = 150;
|
||||
batchSize = 100;
|
||||
startIndex = 32570;
|
||||
rippleds = cfg.rippleds;
|
||||
redis = {
|
||||
enable = cfg.redis.enable;
|
||||
host = cfg.redis.host;
|
||||
port = cfg.redis.port;
|
||||
options.auth_pass = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
db_config = builtins.toJSON {
|
||||
production = {
|
||||
username = optional (cfg.couchdb.pass != "") cfg.couchdb.user;
|
||||
password = optional (cfg.couchdb.pass != "") cfg.couchdb.pass;
|
||||
host = cfg.couchdb.host;
|
||||
port = cfg.couchdb.port;
|
||||
database = cfg.couchdb.db;
|
||||
protocol = "http";
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.rippleDataApi = {
|
||||
enable = mkEnableOption "ripple data api";
|
||||
|
||||
port = mkOption {
|
||||
description = "Ripple data api port";
|
||||
default = 5993;
|
||||
type = types.int;
|
||||
};
|
||||
|
||||
importMode = mkOption {
|
||||
description = "Ripple data api import mode.";
|
||||
default = "liveOnly";
|
||||
type = types.enum ["live" "liveOnly"];
|
||||
};
|
||||
|
||||
minLedger = mkOption {
|
||||
description = "Ripple data api minimal ledger to fetch.";
|
||||
default = null;
|
||||
type = types.nullOr types.int;
|
||||
};
|
||||
|
||||
maxLedger = mkOption {
|
||||
description = "Ripple data api maximal ledger to fetch.";
|
||||
default = null;
|
||||
type = types.nullOr types.int;
|
||||
};
|
||||
|
||||
redis = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable caching of ripple data to redis.";
|
||||
default = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
description = "Ripple data api redis host.";
|
||||
default = "localhost";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
description = "Ripple data api redis port.";
|
||||
default = 5984;
|
||||
type = types.int;
|
||||
};
|
||||
};
|
||||
|
||||
couchdb = {
|
||||
host = mkOption {
|
||||
description = "Ripple data api couchdb host.";
|
||||
default = "localhost";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
description = "Ripple data api couchdb port.";
|
||||
default = 5984;
|
||||
type = types.int;
|
||||
};
|
||||
|
||||
db = mkOption {
|
||||
description = "Ripple data api couchdb database.";
|
||||
default = "rippled";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
description = "Ripple data api couchdb username.";
|
||||
default = "rippled";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
pass = mkOption {
|
||||
description = "Ripple data api couchdb password.";
|
||||
default = "";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
create = mkOption {
|
||||
description = "Whether to create couchdb database needed by ripple data api.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
rippleds = mkOption {
|
||||
description = "List of rippleds to be used by ripple data api.";
|
||||
default = [
|
||||
"http://s_east.ripple.com:51234"
|
||||
"http://s_west.ripple.com:51234"
|
||||
];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enable) {
|
||||
services.couchdb.enable = mkDefault true;
|
||||
services.couchdb.bindAddress = mkDefault "0.0.0.0";
|
||||
services.redis.enable = mkDefault true;
|
||||
|
||||
systemd.services.ripple-data-api = {
|
||||
after = [ "couchdb.service" "redis.service" "ripple-data-api-importer.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config;
|
||||
DB_CONFIG = pkgs.writeText "db.config.json" db_config;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.ripple-data-api}/bin/api";
|
||||
Restart = "always";
|
||||
User = "ripple-data-api";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.ripple-data-importer = {
|
||||
after = [ "couchdb.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.curl ];
|
||||
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config;
|
||||
DB_CONFIG = pkgs.writeText "db.config.json" db_config;
|
||||
LOG_FILE = "/dev/null";
|
||||
};
|
||||
|
||||
serviceConfig = let
|
||||
importMode =
|
||||
if cfg.minLedger != null && cfg.maxLedger != null then
|
||||
"${toString cfg.minLedger} ${toString cfg.maxLedger}"
|
||||
else
|
||||
cfg.importMode;
|
||||
in {
|
||||
ExecStart = "${pkgs.ripple-data-api}/bin/importer ${importMode} debug";
|
||||
Restart = "always";
|
||||
User = "ripple-data-api";
|
||||
};
|
||||
|
||||
preStart = mkMerge [
|
||||
(mkIf (cfg.couchdb.create) ''
|
||||
HOST="http://${optionalString (cfg.couchdb.pass != "") "${cfg.couchdb.user}:${cfg.couchdb.pass}@"}${cfg.couchdb.host}:${toString cfg.couchdb.port}"
|
||||
curl -X PUT $HOST/${cfg.couchdb.db} || true
|
||||
'')
|
||||
"${pkgs.ripple-data-api}/bin/update-views"
|
||||
];
|
||||
};
|
||||
|
||||
users.users.ripple-data-api =
|
||||
{ description = "Ripple data api user";
|
||||
isSystemUser = true;
|
||||
group = "ripple-data-api";
|
||||
};
|
||||
users.groups.ripple-data-api = {};
|
||||
};
|
||||
}
|
||||
438
nixos/modules/services/misc/rippled.nix
Normal file
438
nixos/modules/services/misc/rippled.nix
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.rippled;
|
||||
opt = options.services.rippled;
|
||||
|
||||
b2i = val: if val then "1" else "0";
|
||||
|
||||
dbCfg = db: ''
|
||||
type=${db.type}
|
||||
path=${db.path}
|
||||
${optionalString (db.compression != null) ("compression=${b2i db.compression}") }
|
||||
${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
|
||||
${optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")}
|
||||
${db.extraOpts}
|
||||
'';
|
||||
|
||||
rippledCfg = ''
|
||||
[server]
|
||||
${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)}
|
||||
|
||||
${concatMapStrings (p: ''
|
||||
[port_${p.name}]
|
||||
ip=${p.ip}
|
||||
port=${toString p.port}
|
||||
protocol=${concatStringsSep "," p.protocol}
|
||||
${optionalString (p.user != "") "user=${p.user}"}
|
||||
${optionalString (p.password != "") "user=${p.password}"}
|
||||
admin=${concatStringsSep "," p.admin}
|
||||
${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
|
||||
${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
|
||||
${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
|
||||
'') (attrValues cfg.ports)}
|
||||
|
||||
[database_path]
|
||||
${cfg.databasePath}
|
||||
|
||||
[node_db]
|
||||
${dbCfg cfg.nodeDb}
|
||||
|
||||
${optionalString (cfg.tempDb != null) ''
|
||||
[temp_db]
|
||||
${dbCfg cfg.tempDb}''}
|
||||
|
||||
${optionalString (cfg.importDb != null) ''
|
||||
[import_db]
|
||||
${dbCfg cfg.importDb}''}
|
||||
|
||||
[ips]
|
||||
${concatStringsSep "\n" cfg.ips}
|
||||
|
||||
[ips_fixed]
|
||||
${concatStringsSep "\n" cfg.ipsFixed}
|
||||
|
||||
[validators]
|
||||
${concatStringsSep "\n" cfg.validators}
|
||||
|
||||
[node_size]
|
||||
${cfg.nodeSize}
|
||||
|
||||
[ledger_history]
|
||||
${toString cfg.ledgerHistory}
|
||||
|
||||
[fetch_depth]
|
||||
${toString cfg.fetchDepth}
|
||||
|
||||
[validation_quorum]
|
||||
${toString cfg.validationQuorum}
|
||||
|
||||
[sntp_servers]
|
||||
${concatStringsSep "\n" cfg.sntpServers}
|
||||
|
||||
${optionalString cfg.statsd.enable ''
|
||||
[insight]
|
||||
server=statsd
|
||||
address=${cfg.statsd.address}
|
||||
prefix=${cfg.statsd.prefix}
|
||||
''}
|
||||
|
||||
[rpc_startup]
|
||||
{ "command": "log_level", "severity": "${cfg.logLevel}" }
|
||||
'' + cfg.extraConfig;
|
||||
|
||||
portOptions = { name, ...}: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
internal = true;
|
||||
default = name;
|
||||
};
|
||||
|
||||
ip = mkOption {
|
||||
default = "127.0.0.1";
|
||||
description = "Ip where rippled listens.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
description = "Port where rippled listens.";
|
||||
type = types.int;
|
||||
};
|
||||
|
||||
protocol = mkOption {
|
||||
description = "Protocols expose by rippled.";
|
||||
type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
description = "When set, these credentials will be required on HTTP/S requests.";
|
||||
type = types.str;
|
||||
default = "";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
description = "When set, these credentials will be required on HTTP/S requests.";
|
||||
type = types.str;
|
||||
default = "";
|
||||
};
|
||||
|
||||
admin = mkOption {
|
||||
description = "A comma-separated list of admin IP addresses.";
|
||||
type = types.listOf types.str;
|
||||
default = ["127.0.0.1"];
|
||||
};
|
||||
|
||||
ssl = {
|
||||
key = mkOption {
|
||||
description = ''
|
||||
Specifies the filename holding the SSL key in PEM format.
|
||||
'';
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
cert = mkOption {
|
||||
description = ''
|
||||
Specifies the path to the SSL certificate file in PEM format.
|
||||
This is not needed if the chain includes it.
|
||||
'';
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
chain = mkOption {
|
||||
description = ''
|
||||
If you need a certificate chain, specify the path to the
|
||||
certificate chain here. The chain may include the end certificate.
|
||||
'';
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dbOptions = {
|
||||
options = {
|
||||
type = mkOption {
|
||||
description = "Rippled database type.";
|
||||
type = types.enum ["rocksdb" "nudb"];
|
||||
default = "rocksdb";
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
description = "Location to store the database.";
|
||||
type = types.path;
|
||||
default = cfg.databasePath;
|
||||
defaultText = literalExpression "config.${opt.databasePath}";
|
||||
};
|
||||
|
||||
compression = mkOption {
|
||||
description = "Whether to enable snappy compression.";
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
};
|
||||
|
||||
onlineDelete = mkOption {
|
||||
description = "Enable automatic purging of older ledger information.";
|
||||
type = types.nullOr (types.addCheck types.int (v: v > 256));
|
||||
default = cfg.ledgerHistory;
|
||||
defaultText = literalExpression "config.${opt.ledgerHistory}";
|
||||
};
|
||||
|
||||
advisoryDelete = mkOption {
|
||||
description = ''
|
||||
If set, then require administrative RPC call "can_delete"
|
||||
to enable online deletion of ledger records.
|
||||
'';
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
};
|
||||
|
||||
extraOpts = mkOption {
|
||||
description = "Extra database options.";
|
||||
type = types.lines;
|
||||
default = "";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.rippled = {
|
||||
enable = mkEnableOption "rippled";
|
||||
|
||||
package = mkOption {
|
||||
description = "Which rippled package to use.";
|
||||
type = types.package;
|
||||
default = pkgs.rippled;
|
||||
defaultText = literalExpression "pkgs.rippled";
|
||||
};
|
||||
|
||||
ports = mkOption {
|
||||
description = "Ports exposed by rippled";
|
||||
type = with types; attrsOf (submodule portOptions);
|
||||
default = {
|
||||
rpc = {
|
||||
port = 5005;
|
||||
admin = ["127.0.0.1"];
|
||||
protocol = ["http"];
|
||||
};
|
||||
|
||||
peer = {
|
||||
port = 51235;
|
||||
ip = "0.0.0.0";
|
||||
protocol = ["peer"];
|
||||
};
|
||||
|
||||
ws_public = {
|
||||
port = 5006;
|
||||
ip = "0.0.0.0";
|
||||
protocol = ["ws" "wss"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nodeDb = mkOption {
|
||||
description = "Rippled main database options.";
|
||||
type = with types; nullOr (submodule dbOptions);
|
||||
default = {
|
||||
type = "rocksdb";
|
||||
extraOpts = ''
|
||||
open_files=2000
|
||||
filter_bits=12
|
||||
cache_mb=256
|
||||
file_size_pb=8
|
||||
file_size_mult=2;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
tempDb = mkOption {
|
||||
description = "Rippled temporary database options.";
|
||||
type = with types; nullOr (submodule dbOptions);
|
||||
default = null;
|
||||
};
|
||||
|
||||
importDb = mkOption {
|
||||
description = "Settings for performing a one-time import.";
|
||||
type = with types; nullOr (submodule dbOptions);
|
||||
default = null;
|
||||
};
|
||||
|
||||
nodeSize = mkOption {
|
||||
description = ''
|
||||
Rippled size of the node you are running.
|
||||
"tiny", "small", "medium", "large", and "huge"
|
||||
'';
|
||||
type = types.enum ["tiny" "small" "medium" "large" "huge"];
|
||||
default = "small";
|
||||
};
|
||||
|
||||
ips = mkOption {
|
||||
description = ''
|
||||
List of hostnames or ips where the Ripple protocol is served.
|
||||
For a starter list, you can either copy entries from:
|
||||
https://ripple.com/ripple.txt or if you prefer you can let it
|
||||
default to r.ripple.com 51235
|
||||
|
||||
A port may optionally be specified after adding a space to the
|
||||
address. By convention, if known, IPs are listed in from most
|
||||
to least trusted.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = ["r.ripple.com 51235"];
|
||||
};
|
||||
|
||||
ipsFixed = mkOption {
|
||||
description = ''
|
||||
List of IP addresses or hostnames to which rippled should always
|
||||
attempt to maintain peer connections with. This is useful for
|
||||
manually forming private networks, for example to configure a
|
||||
validation server that connects to the Ripple network through a
|
||||
public-facing server, or for building a set of cluster peers.
|
||||
|
||||
A port may optionally be specified after adding a space to the address
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
validators = mkOption {
|
||||
description = ''
|
||||
List of nodes to always accept as validators. Nodes are specified by domain
|
||||
or public key.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1"
|
||||
"n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2"
|
||||
"n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3"
|
||||
"n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4"
|
||||
"n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5"
|
||||
];
|
||||
};
|
||||
|
||||
databasePath = mkOption {
|
||||
description = ''
|
||||
Path to the ripple database.
|
||||
'';
|
||||
type = types.path;
|
||||
default = "/var/lib/rippled";
|
||||
};
|
||||
|
||||
validationQuorum = mkOption {
|
||||
description = ''
|
||||
The minimum number of trusted validations a ledger must have before
|
||||
the server considers it fully validated.
|
||||
'';
|
||||
type = types.int;
|
||||
default = 3;
|
||||
};
|
||||
|
||||
ledgerHistory = mkOption {
|
||||
description = ''
|
||||
The number of past ledgers to acquire on server startup and the minimum
|
||||
to maintain while running.
|
||||
'';
|
||||
type = types.either types.int (types.enum ["full"]);
|
||||
default = 1296000; # 1 month
|
||||
};
|
||||
|
||||
fetchDepth = mkOption {
|
||||
description = ''
|
||||
The number of past ledgers to serve to other peers that request historical
|
||||
ledger data (or "full" for no limit).
|
||||
'';
|
||||
type = types.either types.int (types.enum ["full"]);
|
||||
default = "full";
|
||||
};
|
||||
|
||||
sntpServers = mkOption {
|
||||
description = ''
|
||||
IP address or domain of NTP servers to use for time synchronization.;
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"time.windows.com"
|
||||
"time.apple.com"
|
||||
"time.nist.gov"
|
||||
"pool.ntp.org"
|
||||
];
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
description = "Logging verbosity.";
|
||||
type = types.enum ["debug" "error" "info"];
|
||||
default = "error";
|
||||
};
|
||||
|
||||
statsd = {
|
||||
enable = mkEnableOption "statsd monitoring for rippled";
|
||||
|
||||
address = mkOption {
|
||||
description = "The UDP address and port of the listening StatsD server.";
|
||||
default = "127.0.0.1:8125";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
prefix = mkOption {
|
||||
description = "A string prepended to each collected metric.";
|
||||
default = "";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Extra lines to be added verbatim to the rippled.cfg configuration file.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
internal = true;
|
||||
default = pkgs.writeText "rippled.conf" rippledCfg;
|
||||
defaultText = literalDocBook "generated config file";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.rippled = {
|
||||
description = "Ripple server user";
|
||||
isSystemUser = true;
|
||||
group = "rippled";
|
||||
home = cfg.databasePath;
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.rippled = {};
|
||||
|
||||
systemd.services.rippled = {
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
|
||||
User = "rippled";
|
||||
Restart = "on-failure";
|
||||
LimitNOFILE=10000;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
};
|
||||
}
|
||||
147
nixos/modules/services/misc/rmfakecloud.nix
Normal file
147
nixos/modules/services/misc/rmfakecloud.nix
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.rmfakecloud;
|
||||
serviceDataDir = "/var/lib/rmfakecloud";
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.rmfakecloud = {
|
||||
enable = mkEnableOption "rmfakecloud remarkable self-hosted cloud";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.rmfakecloud;
|
||||
defaultText = literalExpression "pkgs.rmfakecloud";
|
||||
description = ''
|
||||
rmfakecloud package to use.
|
||||
|
||||
The default does not include the web user interface.
|
||||
'';
|
||||
};
|
||||
|
||||
storageUrl = mkOption {
|
||||
type = types.str;
|
||||
example = "https://local.appspot.com";
|
||||
description = ''
|
||||
URL used by the tablet to access the rmfakecloud service.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 3000;
|
||||
description = ''
|
||||
Listening port number.
|
||||
'';
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum [ "info" "debug" "warn" "error" ];
|
||||
default = "info";
|
||||
description = ''
|
||||
Logging level.
|
||||
'';
|
||||
};
|
||||
|
||||
extraSettings = mkOption {
|
||||
type = with types; attrsOf str;
|
||||
default = { };
|
||||
example = { DATADIR = "/custom/path/for/rmfakecloud/data"; };
|
||||
description = ''
|
||||
Extra settings in the form of a set of key-value pairs.
|
||||
For tokens and secrets, use `environmentFile` instead.
|
||||
|
||||
Available settings are listed on
|
||||
https://ddvk.github.io/rmfakecloud/install/configuration/.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "/etc/secrets/rmfakecloud.env";
|
||||
description = ''
|
||||
Path to an environment file loaded for the rmfakecloud service.
|
||||
|
||||
This can be used to securely store tokens and secrets outside of the
|
||||
world-readable Nix store. Since this file is read by systemd, it may
|
||||
have permission 0400 and be owned by root.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.rmfakecloud = {
|
||||
description = "rmfakecloud remarkable self-hosted cloud";
|
||||
|
||||
environment = {
|
||||
STORAGE_URL = cfg.storageUrl;
|
||||
PORT = toString cfg.port;
|
||||
LOGLEVEL = cfg.logLevel;
|
||||
} // cfg.extraSettings;
|
||||
|
||||
preStart = ''
|
||||
# Generate the secret key used to sign client session tokens.
|
||||
# Replacing it invalidates the previously established sessions.
|
||||
if [ -z "$JWT_SECRET_KEY" ] && [ ! -f jwt_secret_key ]; then
|
||||
(umask 077; touch jwt_secret_key)
|
||||
cat /dev/urandom | tr -cd '[:alnum:]' | head -c 48 >> jwt_secret_key
|
||||
fi
|
||||
'';
|
||||
|
||||
script = ''
|
||||
if [ -z "$JWT_SECRET_KEY" ]; then
|
||||
export JWT_SECRET_KEY="$(cat jwt_secret_key)"
|
||||
fi
|
||||
|
||||
${cfg.package}/bin/rmfakecloud
|
||||
'';
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
|
||||
EnvironmentFile =
|
||||
mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
||||
|
||||
AmbientCapabilities =
|
||||
mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
|
||||
|
||||
DynamicUser = true;
|
||||
PrivateDevices = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DevicePolicy = "closed";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
WorkingDirectory = serviceDataDir;
|
||||
StateDirectory = baseNameOf serviceDataDir;
|
||||
UMask = 0027;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ pacien ];
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue