Set static UID/GIDs

This commit is contained in:
rasmus-kirk
2025-06-01 14:53:23 +02:00
parent 58dd1ee446
commit 1c2fb5754e
20 changed files with 349 additions and 184 deletions
+3 -2
View File
@@ -3,13 +3,14 @@
## Unreleased ## Unreleased
Added: Added:
- Added Readarr Audiobook for running two readarr instances - Added Readarr Audiobook for running two readarr instances (one for audiobooks, one for regular books)
- Audiobookshelf service, with expose options
- Port configurations on: - Port configurations on:
- Radarr - Radarr
- Sonarr - Sonarr
- Prowlarr - Prowlarr
- Readarr - Readarr
- Audiobookshelf service, with expose options - Lidarr
## 2025-05-28 ## 2025-05-28
+10 -13
View File
@@ -6,9 +6,7 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.audiobookshelf; cfg = config.nixarr.audiobookshelf;
uid = 242; globals = config.util-nixarr.globals;
user = "streamer";
group = "streamer";
port = 9292; port = 9292;
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
@@ -153,21 +151,20 @@ in {
]; ];
users = { users = {
groups."${group}" = {}; groups.${globals.audiobookshelf.group}.gid = globals.gids.${globals.audiobookshelf.group};
users."${user}" = { users.${globals.audiobookshelf.user} = {
isSystemUser = true; isSystemUser = true;
group = group; group = globals.audiobookshelf.group;
uid = uid; uid = globals.uids.${globals.audiobookshelf.user};
}; };
}; };
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} root - -" "d '${cfg.stateDir}' 0700 ${globals.audiobookshelf.user} root - -"
# Media Dirs # Media Dirs
"d '${nixarr.mediaDir}/library/books' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/audiobooks' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/audio-books' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/podcasts' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/podcasts' 0775 ${user} ${group} - -"
]; ];
systemd.services.audiobookshelf = { systemd.services.audiobookshelf = {
@@ -179,8 +176,8 @@ in {
serviceConfig = { serviceConfig = {
IOSchedulingPriority = 0; IOSchedulingPriority = 0;
Type = "simple"; Type = "simple";
User = user; User = globals.audiobookshelf.user;
Group = group; Group = globals.audiobookshelf.group;
StateDirectory = cfg.stateDir; StateDirectory = cfg.stateDir;
WorkingDirectory = cfg.stateDir; WorkingDirectory = cfg.stateDir;
ExecStart = "${cfg.package}/bin/audiobookshelf --host ${host} --port ${toString cfg.port}"; ExecStart = "${cfg.package}/bin/audiobookshelf --host ${host} --port ${toString cfg.port}";
+8 -9
View File
@@ -6,10 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.autobrr; cfg = config.nixarr.autobrr;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
# Externalize username and group
user = "autobrr";
group = "autobrr";
# Define config format and template # Define config format and template
configFormat = pkgs.formats.toml {}; configFormat = pkgs.formats.toml {};
@@ -97,16 +95,17 @@ in {
]; ];
users = { users = {
groups.${group} = {}; groups.${globals.autobrr.group}.gid = globals.gids.${globals.autobrr.group};
users.${user} = { users.${globals.autobrr.user} = {
isSystemUser = true; isSystemUser = true;
group = group; group = globals.autobrr.group;
uid = globals.uids.${globals.autobrr.user};
}; };
}; };
# Create state directory with proper permissions # Create state directory with proper permissions
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} ${group} - -" "d '${cfg.stateDir}' 0700 ${globals.autobrr.user} root - -"
]; ];
# Configure the autobrr service # Configure the autobrr service
@@ -134,8 +133,8 @@ in {
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
User = user; User = globals.autobrr.user;
Group = "root"; Group = globals.autobrr.group;
UMask = 066; UMask = 066;
DynamicUser = lib.mkForce false; DynamicUser = lib.mkForce false;
# disable SecretFilec # disable SecretFilec
+8 -8
View File
@@ -6,9 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.bazarr; cfg = config.nixarr.bazarr;
globals = config.util-nixarr.globals;
port = 6767; port = 6767;
user = "bazarr";
group = "media";
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
options.nixarr.bazarr = { options.nixarr.bazarr = {
@@ -80,7 +79,7 @@ in {
]; ];
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} root - -" "d '${cfg.stateDir}' 0700 ${globals.bazarr.user} root - -"
]; ];
systemd.services.bazarr = { systemd.services.bazarr = {
@@ -90,8 +89,8 @@ in {
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
User = user; User = globals.bazarr.user;
Group = group; Group = globals.bazarr.group;
SyslogIdentifier = "bazarr"; SyslogIdentifier = "bazarr";
ExecStart = pkgs.writeShellScript "start-bazarr" '' ExecStart = pkgs.writeShellScript "start-bazarr" ''
${pkgs.bazarr}/bin/bazarr \ ${pkgs.bazarr}/bin/bazarr \
@@ -108,11 +107,12 @@ in {
}; };
users = { users = {
users."${user}" = { groups.${globals.bazarr.group}.gid = globals.gids.${globals.bazarr.group};
users.${globals.bazarr.user} = {
isSystemUser = true; isSystemUser = true;
group = group; group = globals.bazarr.group;
uid = globals.uids.${globals.bazarr.user};
}; };
groups."${group}" = {};
}; };
# Enable and specify VPN namespace to confine service in. # Enable and specify VPN namespace to confine service in.
+22 -21
View File
@@ -6,6 +6,7 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr; cfg = config.nixarr;
globals = config.util-nixarr.globals;
list-unlinked = pkgs.writeShellApplication { list-unlinked = pkgs.writeShellApplication {
name = "list-unlinked"; name = "list-unlinked";
runtimeInputs = with pkgs; [util-linux]; runtimeInputs = with pkgs; [util-linux];
@@ -30,72 +31,72 @@ with lib; let
find "${cfg.mediaDir}" \( -type d -exec chmod 0775 {} + -true \) -o \( -exec chmod 0664 {} + \) find "${cfg.mediaDir}" \( -type d -exec chmod 0775 {} + -true \) -o \( -exec chmod 0664 {} + \)
'' ''
+ strings.optionalString cfg.jellyfin.enable '' + strings.optionalString cfg.jellyfin.enable ''
chown -R streamer:media "${cfg.mediaDir}/library" chown -R ${globals.libraryOwner.user}:${globals.libraryOwner.group} "${cfg.mediaDir}/library"
chown -R streamer:root "${cfg.jellyfin.stateDir}" chown -R ${globals.jellyfin.user}:root "${cfg.jellyfin.stateDir}"
find "${cfg.jellyfin.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.jellyfin.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.plex.enable '' + strings.optionalString cfg.plex.enable ''
chown -R streamer:media "${cfg.mediaDir}/library" chown -R ${globals.libraryOwner.user}:${globals.libraryOwner.group} "${cfg.mediaDir}/library"
chown -R streamer:root "${cfg.plex.stateDir}" chown -R ${globals.plex.user}:root "${cfg.plex.stateDir}"
find "${cfg.plex.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.plex.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.audiobookshelf.enable '' + strings.optionalString cfg.audiobookshelf.enable ''
chown -R streamer:media "${cfg.mediaDir}/library" chown -R ${globals.libraryOwner.user}:${globals.libraryOwner.group} "${cfg.mediaDir}/library"
chown -R streamer:root "${cfg.audiobookshelf.stateDir}" chown -R ${globals.audiobookshelf.user}:root "${cfg.audiobookshelf.stateDir}"
find "${cfg.audiobookshelf.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.audiobookshelf.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.transmission.enable '' + strings.optionalString cfg.transmission.enable ''
chown -R torrenter:media "${cfg.mediaDir}/torrents" chown -R ${globals.transmission.user}:${globals.transmission.group} "${cfg.mediaDir}/torrents"
chown -R torrenter:cross-seed "${cfg.transmission.stateDir}" chown -R ${globals.transmission.user}:${globals.transmission.group} "${cfg.transmission.stateDir}"
find "${cfg.transmission.stateDir}" \( -type d -exec chmod 0750 {} + -true \) -o \( -exec chmod 0640 {} + \) find "${cfg.transmission.stateDir}" \( -type d -exec chmod 0750 {} + -true \) -o \( -exec chmod 0640 {} + \)
'' ''
+ strings.optionalString cfg.sabnzbd.enable '' + strings.optionalString cfg.sabnzbd.enable ''
chown -R usenet:media "${cfg.mediaDir}/usenet" chown -R ${globals.sabnzbd.user}:${globals.sabnzbd.group} "${cfg.mediaDir}/usenet"
chown -R usenet:root "${cfg.sabnzbd.stateDir}" chown -R ${globals.sabnzbd.user}:root "${cfg.sabnzbd.stateDir}"
find "${cfg.sabnzbd.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.sabnzbd.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.transmission.privateTrackers.cross-seed.enable '' + strings.optionalString cfg.transmission.privateTrackers.cross-seed.enable ''
chown -R cross-seed:root "${cfg.transmission.privateTrackers.cross-seed.stateDir}" chown -R ${globals.cross-seed.user}:root "${cfg.transmission.privateTrackers.cross-seed.stateDir}"
find "${cfg.transmission.privateTrackers.cross-seed.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.transmission.privateTrackers.cross-seed.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.prowlarr.enable '' + strings.optionalString cfg.prowlarr.enable ''
chown -R prowlarr:root "${cfg.prowlarr.stateDir}" chown -R ${globals.prowlarr.user}:root "${cfg.prowlarr.stateDir}"
find "${cfg.prowlarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.prowlarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.sonarr.enable '' + strings.optionalString cfg.sonarr.enable ''
chown -R sonarr:root "${cfg.sonarr.stateDir}" chown -R ${globals.sonarr.user}:root "${cfg.sonarr.stateDir}"
find "${cfg.sonarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.sonarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.radarr.enable '' + strings.optionalString cfg.radarr.enable ''
chown -R radarr:root "${cfg.radarr.stateDir}" chown -R ${globals.radarr.user}:root "${cfg.radarr.stateDir}"
find "${cfg.radarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.radarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.lidarr.enable '' + strings.optionalString cfg.lidarr.enable ''
chown -R lidarr:root "${cfg.lidarr.stateDir}" chown -R ${globals.lidarr.user}:root "${cfg.lidarr.stateDir}"
find "${cfg.lidarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.lidarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.bazarr.enable '' + strings.optionalString cfg.bazarr.enable ''
chown -R bazarr:root "${cfg.bazarr.stateDir}" chown -R ${globals.bazarr.user}:root "${cfg.bazarr.stateDir}"
find "${cfg.bazarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.bazarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.readarr.enable '' + strings.optionalString cfg.readarr.enable ''
chown -R readarr:root "${cfg.readarr.stateDir}" chown -R ${globals.readarr.user}:root "${cfg.readarr.stateDir}"
find "${cfg.readarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.readarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.readarr-audiobook.enable '' + strings.optionalString cfg.readarr-audiobook.enable ''
chown -R readarr:root "${cfg.readarr-audiobook.stateDir}" chown -R ${globals.readarr.user}:root "${cfg.readarr-audiobook.stateDir}"
find "${cfg.readarr-audiobook.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.readarr-audiobook.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.jellyseerr.enable '' + strings.optionalString cfg.jellyseerr.enable ''
chown -R jellyseerr:root "${cfg.jellyseerr.stateDir}" chown -R ${globals.jellyseerr.user}:root "${cfg.jellyseerr.stateDir}"
find "${cfg.jellyseerr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.jellyseerr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.autobrr.enable '' + strings.optionalString cfg.autobrr.enable ''
chown -R autobrr:root "${cfg.autobrr.stateDir}" chown -R ${globals.autobrr.user}:root "${cfg.autobrr.stateDir}"
find "${cfg.autobrr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.autobrr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
'' ''
+ strings.optionalString cfg.recyclarr.enable '' + strings.optionalString cfg.recyclarr.enable ''
chown -R recyclarr:root "${cfg.recyclarr.stateDir}" chown -R ${globals.recyclarr.user}:root "${cfg.recyclarr.stateDir}"
find "${cfg.recyclarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \) find "${cfg.recyclarr.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
''; '';
}; };
+18 -15
View File
@@ -6,10 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.jellyfin; cfg = config.nixarr.jellyfin;
globals = config.util-nixarr.globals;
defaultPort = 8096; defaultPort = 8096;
uid = 242;
user = "streamer";
group = "streamer";
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
options.nixarr.jellyfin = { options.nixarr.jellyfin = {
@@ -141,23 +139,28 @@ in {
]; ];
users = { users = {
groups."${group}" = {}; groups.${globals.jellyfin.group}.gid = globals.gids.${globals.jellyfin.group};
users."${user}" = { users.${globals.jellyfin.user} = {
isSystemUser = true; isSystemUser = true;
group = group; group = globals.jellyfin.group;
uid = uid; uid = globals.uids.${globals.jellyfin.user};
}; };
}; };
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} root - -" "d '${cfg.stateDir}' 0700 ${globals.jellyfin.user} root - -"
"d '${cfg.stateDir}/log' 0700 ${globals.jellyfin.user} root - -"
"d '${cfg.stateDir}/cache' 0700 ${globals.jellyfin.user} root - -"
"d '${cfg.stateDir}/data' 0700 ${globals.jellyfin.user} root - -"
"d '${cfg.stateDir}/config' 0700 ${globals.jellyfin.user} root - -"
# Media Dirs # Media Dirs
"d '${nixarr.mediaDir}/library' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/shows' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/shows' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/movies' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/movies' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/music' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/music' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/books' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/books' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/audiobooks' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
]; ];
# Always prioritise Jellyfin IO # Always prioritise Jellyfin IO
@@ -166,8 +169,8 @@ in {
services.jellyfin = { services.jellyfin = {
enable = cfg.enable; enable = cfg.enable;
package = cfg.package; package = cfg.package;
user = user; user = globals.jellyfin.user;
group = group; group = globals.jellyfin.group;
openFirewall = cfg.openFirewall; openFirewall = cfg.openFirewall;
logDir = "${cfg.stateDir}/log"; logDir = "${cfg.stateDir}/log";
cacheDir = "${cfg.stateDir}/cache"; cacheDir = "${cfg.stateDir}/cache";
+10 -12
View File
@@ -6,11 +6,9 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.jellyseerr; cfg = config.nixarr.jellyseerr;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
port = 5055; port = 5055;
uid = 294;
user = "jellyseerr";
group = "jellyseerr";
in { in {
options.nixarr.jellyseerr = { options.nixarr.jellyseerr = {
enable = mkOption { enable = mkOption {
@@ -146,7 +144,7 @@ in {
]; ];
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.configDir}' 0700 ${cfg.user} ${cfg.group} - -" "d '${cfg.stateDir}' 0700 ${globals.jellyseerr.user} root - -"
]; ];
systemd.services.jellyseerr = { systemd.services.jellyseerr = {
@@ -155,15 +153,15 @@ in {
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
environment = { environment = {
PORT = toString cfg.port; PORT = toString cfg.port;
CONFIG_DIRECTORY = cfg.configDir; CONFIG_DIRECTORY = cfg.stateDir;
}; };
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
StateDirectory = "jellyseerr"; StateDirectory = "jellyseerr";
DynamicUser = false; DynamicUser = false;
User = cfg.user; User = globals.jellyseerr.user;
Group = cfg.group; Group = globals.jellyseerr.group;
ExecStart = lib.getExe cfg.package; ExecStart = lib.getExe cfg.package;
Restart = "on-failure"; Restart = "on-failure";
@@ -183,17 +181,17 @@ in {
RemoveIPC = true; RemoveIPC = true;
PrivateMounts = true; PrivateMounts = true;
ProtectSystem = "strict"; ProtectSystem = "strict";
ReadWritePaths = [cfg.configDir]; ReadWritePaths = [cfg.stateDir];
}; };
}; };
users = { users = {
users."${user}" = { groups.${globals.jellyseerr.group}.gid = globals.gids.${globals.jellyseerr.group};
users.${globals.jellyseerr.user} = {
isSystemUser = true; isSystemUser = true;
group = group; group = globals.jellyseerr.group;
uid = uid; uid = globals.uids.${globals.jellyseerr.user};
}; };
groups."${group}" = {};
}; };
networking.firewall = mkIf cfg.expose.https.enable { networking.firewall = mkIf cfg.expose.https.enable {
+17 -4
View File
@@ -6,10 +6,9 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.lidarr; cfg = config.nixarr.lidarr;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
port = 8686; port = 8686;
user = "lidarr";
group = "media";
in { in {
options.nixarr.lidarr = { options.nixarr.lidarr = {
enable = mkOption { enable = mkOption {
@@ -79,11 +78,25 @@ in {
} }
]; ];
users = {
groups.${globals.lidarr.group}.gid = globals.gids.${globals.lidarr.group};
users.${globals.lidarr.user} = {
isSystemUser = true;
group = globals.lidarr.group;
uid = globals.uids.${globals.lidarr.user};
};
};
systemd.tmpfiles.rules = [
"d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/music' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
];
services.lidarr = { services.lidarr = {
enable = cfg.enable; enable = cfg.enable;
package = cfg.package; package = cfg.package;
user = user; user = globals.lidarr.user;
group = group; group = globals.lidarr.group;
settings.server.port = cfg.port; settings.server.port = cfg.port;
openFirewall = cfg.openFirewall; openFirewall = cfg.openFirewall;
dataDir = cfg.stateDir; dataDir = cfg.stateDir;
+12 -16
View File
@@ -6,10 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.plex; cfg = config.nixarr.plex;
globals = config.util-nixarr.globals;
defaultPort = 32400; defaultPort = 32400;
uid = 242;
user = "streamer";
group = "media";
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
options.nixarr.plex = { options.nixarr.plex = {
@@ -141,23 +139,21 @@ in {
]; ];
users = { users = {
groups."${group}" = {}; groups.${globals.plex.group}.gid = globals.gids.${globals.plex.group};
users."${user}" = { users.${globals.plex.user} = {
isSystemUser = true; isSystemUser = true;
group = group; group = group;
uid = uid; uid = globals.uids.${globals.plex.user};
}; };
}; };
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} root - -" "d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/shows' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
# Media Dirs "d '${nixarr.mediaDir}/library/movies' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/music' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/shows' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/books' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/movies' 0775 ${user} ${group} - -" "d '${nixarr.mediaDir}/library/audiobooks' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/music' 0775 ${user} ${group} - -"
"d '${nixarr.mediaDir}/library/books' 0775 ${user} ${group} - -"
]; ];
# Always prioritise Plex IO # Always prioritise Plex IO
@@ -166,8 +162,8 @@ in {
services.plex = { services.plex = {
enable = cfg.enable; enable = cfg.enable;
package = cfg.package; package = cfg.package;
user = user; user = globals.plex.user;
group = group; group = globals.plex.group;
openFirewall = cfg.openFirewall; openFirewall = cfg.openFirewall;
dataDir = cfg.stateDir; dataDir = cfg.stateDir;
}; };
+9 -11
View File
@@ -6,10 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.prowlarr; cfg = config.nixarr.prowlarr;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
uid = 293;
user = "prowlarr";
group = "prowlarr";
port = 9696; port = 9696;
in { in {
options.nixarr.prowlarr = { options.nixarr.prowlarr = {
@@ -83,7 +81,7 @@ in {
]; ];
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} ${group} - -" "d '${cfg.stateDir}' 0700 ${globals.prowlarr.user} root - -"
]; ];
systemd.services.prowlarr = { systemd.services.prowlarr = {
@@ -94,8 +92,8 @@ in {
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
User = user; User = globals.prowlarr.user;
Group = group; Group = globals.prowlarr.group;
ExecStart = "${lib.getExe cfg.package} -nobrowser -data=${cfg.stateDir}"; ExecStart = "${lib.getExe cfg.package} -nobrowser -data=${cfg.stateDir}";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -106,11 +104,11 @@ in {
}; };
users = { users = {
groups."${group}" = {}; groups.${globals.prowlarr.group}.gid = globals.gids.${globals.prowlarr.group};
users."${user}" = { users.${globals.prowlarr.user} = {
group = "prowlarr"; isSystemUser = true;
home = cfg.stateDir; group = globals.prowlarr.group;
uid = uid; uid = globals.uids.${globals.prowlarr.user};
}; };
}; };
+17 -4
View File
@@ -6,9 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.radarr; cfg = config.nixarr.radarr;
globals = config.util-nixarr.globals;
port = 7878; port = 7878;
user = "radarr";
group = "media";
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
options.nixarr.radarr = { options.nixarr.radarr = {
@@ -79,11 +78,25 @@ in {
} }
]; ];
systemd.tmpfiles.rules = [
"d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/movies' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
];
users = {
groups.${globals.radarr.group}.gid = globals.gids.${globals.radarr.group};
users.${globals.radarr.user} = {
isSystemUser = true;
group = globals.radarr.group;
uid = globals.uids.${globals.radarr.user};
};
};
services.radarr = { services.radarr = {
enable = cfg.enable; enable = cfg.enable;
package = cfg.package; package = cfg.package;
user = "radarr"; user = globals.radarr.user;
group = "media"; group = globals.radarr.group;
settings.server.port = cfg.port; settings.server.port = cfg.port;
openFirewall = cfg.openFirewall; openFirewall = cfg.openFirewall;
dataDir = cfg.stateDir; dataDir = cfg.stateDir;
+16 -13
View File
@@ -6,10 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.readarr-audiobook; cfg = config.nixarr.readarr-audiobook;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
uid = 269;
user = "readarr";
group = "readarr";
port = 9494; port = 9494;
in { in {
options.nixarr.readarr-audiobook = { options.nixarr.readarr-audiobook = {
@@ -82,8 +80,20 @@ in {
} }
]; ];
users = {
groups.${globals.readarr-audiobook.group}.gid = globals.gids.${globals.readarr-audiobook.group};
users.${globals.readarr-audiobook.user} = {
isSystemUser = true;
group = globals.readarr-audiobook.group;
uid = globals.uids.${globals.readarr-audiobook.user};
};
};
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} ${group} - -" "d '${cfg.stateDir}' 0700 ${globals.readarr-audiobook.user} root - -"
"d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/audiobooks' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
]; ];
systemd.services.readarr-audiobook = { systemd.services.readarr-audiobook = {
@@ -94,8 +104,8 @@ in {
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
User = user; User = globals.readarr-audiobook.user;
Group = group; Group = globals.readarr-audiobook.group;
ExecStart = "${lib.getExe cfg.package} -nobrowser -data=${cfg.stateDir}"; ExecStart = "${lib.getExe cfg.package} -nobrowser -data=${cfg.stateDir}";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -105,13 +115,6 @@ in {
allowedTCPPorts = [cfg.port]; allowedTCPPorts = [cfg.port];
}; };
users.users."${user}" = {
group = group;
home = cfg.stateDir;
uid = uid;
};
users.groups."${group}" = {};
# Enable and specify VPN namespace to confine service in. # Enable and specify VPN namespace to confine service in.
systemd.services.readarr-audiobook.vpnConfinement = mkIf cfg.vpn.enable { systemd.services.readarr-audiobook.vpnConfinement = mkIf cfg.vpn.enable {
enable = true; enable = true;
+16 -13
View File
@@ -6,10 +6,8 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.readarr; cfg = config.nixarr.readarr;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
uid = 250;
user = "readarr";
group = "readarr";
port = 8787; port = 8787;
in { in {
options.nixarr.readarr = { options.nixarr.readarr = {
@@ -80,8 +78,20 @@ in {
} }
]; ];
users = {
groups.${globals.readarr.group}.gid = globals.gids.${globals.readarr.group};
users.${globals.readarr.user} = {
isSystemUser = true;
group = globals.readarr.group;
uid = globals.uids.${globals.readarr.user};
};
};
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 ${user} ${group} - -" "d '${cfg.stateDir}' 0700 ${globals.readarr.user} root - -"
"d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/books' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
]; ];
systemd.services.readarr = { systemd.services.readarr = {
@@ -92,8 +102,8 @@ in {
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
User = user; User = globals.readarr.user;
Group = group; Group = globals.readarr.group;
ExecStart = "${lib.getExe cfg.package} -nobrowser -data=${cfg.stateDir}"; ExecStart = "${lib.getExe cfg.package} -nobrowser -data=${cfg.stateDir}";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -103,13 +113,6 @@ in {
allowedTCPPorts = [cfg.port]; allowedTCPPorts = [cfg.port];
}; };
users.users.readarr = {
group = group;
home = cfg.stateDir;
uid = uid;
};
users.groups.readarr = {};
# Enable and specify VPN namespace to confine service in. # Enable and specify VPN namespace to confine service in.
systemd.services.readarr.vpnConfinement = mkIf cfg.vpn.enable { systemd.services.readarr.vpnConfinement = mkIf cfg.vpn.enable {
enable = true; enable = true;
+10
View File
@@ -7,6 +7,7 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.recyclarr; cfg = config.nixarr.recyclarr;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
format = pkgs.formats.yaml {}; format = pkgs.formats.yaml {};
@@ -186,6 +187,15 @@ in {
} }
]; ];
users = {
groups.${globals.recyclarr.group}.gid = globals.gids.${globals.recyclarr.group};
users.${globals.recyclarr.user} = {
isSystemUser = true;
group = globals.recyclarr.group;
uid = globals.uids.${globals.recyclarr.user};
};
};
services.recyclarr = { services.recyclarr = {
enable = true; enable = true;
package = cfg.package; package = cfg.package;
+16 -14
View File
@@ -6,6 +6,7 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.sabnzbd; cfg = config.nixarr.sabnzbd;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
options.nixarr.sabnzbd = { options.nixarr.sabnzbd = {
@@ -185,33 +186,34 @@ in {
]; ];
users = { users = {
groups.usenet = {}; groups.${globals.sabnzbd.group}.gid = globals.gids.${globals.sabnzbd.group};
users.usenet = { users.${globals.sabnzbd.user} = {
isSystemUser = true; isSystemUser = true;
group = "usenet"; group = group;
uid = globals.uids.${globals.sabnzbd.user};
}; };
}; };
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0700 usenet root - -" "d '${cfg.stateDir}' 0700 ${globals.sabnzbd.user} root - -"
"C ${cfg.stateDir}/sabnzbd.ini - - - - ${ini-base-config-file}" "C ${cfg.stateDir}/sabnzbd.ini - - - - ${ini-base-config-file}"
# Media dirs # Media dirs
"d '${nixarr.mediaDir}/usenet' 0755 usenet media - -" "d '${nixarr.mediaDir}/usenet' 0755 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/.incomplete' 0755 usenet media - -" "d '${nixarr.mediaDir}/usenet/.incomplete' 0755 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/.watch' 0755 usenet media - -" "d '${nixarr.mediaDir}/usenet/.watch' 0755 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/manual' 0775 usenet media - -" "d '${nixarr.mediaDir}/usenet/manual' 0775 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/lidarr' 0775 usenet media - -" "d '${nixarr.mediaDir}/usenet/lidarr' 0775 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/radarr' 0775 usenet media - -" "d '${nixarr.mediaDir}/usenet/radarr' 0775 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/sonarr' 0775 usenet media - -" "d '${nixarr.mediaDir}/usenet/sonarr' 0775 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
"d '${nixarr.mediaDir}/usenet/readarr' 0775 usenet media - -" "d '${nixarr.mediaDir}/usenet/readarr' 0775 ${globals.sabnzbd.user} ${globals.sabnzbd.group} - -"
]; ];
services.sabnzbd = { services.sabnzbd = {
enable = true; enable = true;
package = cfg.package; package = cfg.package;
user = "usenet"; user = globals.sabnzbd.user;
group = "media"; group = globals.sabnzbd.group;
configFile = "${cfg.stateDir}/sabnzbd.ini"; configFile = "${cfg.stateDir}/sabnzbd.ini";
}; };
+17 -2
View File
@@ -6,6 +6,7 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.sonarr; cfg = config.nixarr.sonarr;
globals = config.util-nixarr.globals;
defaultPort = 8989; defaultPort = 8989;
nixarr = config.nixarr; nixarr = config.nixarr;
in { in {
@@ -71,11 +72,25 @@ in {
} }
]; ];
users = {
groups.${globals.sonarr.group}.gid = globals.gids.${globals.sonarr.group};
users.${globals.sonarr.user} = {
isSystemUser = true;
group = globals.sonarr.group;
uid = globals.uids.${globals.sonarr.user};
};
};
systemd.tmpfiles.rules = [
"d '${nixarr.mediaDir}/library' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
"d '${nixarr.mediaDir}/library/shows' 0775 ${globals.libraryOwner.user} ${globals.libraryOwner.group} - -"
];
services.sonarr = { services.sonarr = {
enable = cfg.enable; enable = cfg.enable;
package = cfg.package; package = cfg.package;
user = "sonarr"; user = globals.sonarr.user;
group = "media"; group = globals.sonarr.group;
openFirewall = cfg.openFirewall; openFirewall = cfg.openFirewall;
dataDir = cfg.stateDir; dataDir = cfg.stateDir;
}; };
+5 -7
View File
@@ -85,7 +85,7 @@ in {
systemd.tmpfiles.rules = systemd.tmpfiles.rules =
[ [
"L+ '${cfg.dataDir}'/config.js - - - - ${configJs}" "L+ '${cfg.dataDir}'/config.js - - - - ${configJs}"
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}' 0700 ${cfg.user} root - -"
] ]
++ ( ++ (
if cfg.settings.outputDir != null if cfg.settings.outputDir != null
@@ -119,15 +119,13 @@ in {
}; };
}; };
users.users = mkIf (cfg.user == "cross-seed") { users = {
cross-seed = { groups.${cfg.group}.gid = globals.gids.${cfg.group};
users.${cfg.user} = {
isSystemUser = true; isSystemUser = true;
group = cfg.group; group = cfg.group;
uid = globals.uids.${cfg.user};
}; };
}; };
users.groups = mkIf (cfg.group == "cross-seed") {
cross-seed = {};
};
}; };
} }
+22 -20
View File
@@ -6,7 +6,9 @@
}: }:
with lib; let with lib; let
cfg = config.nixarr.transmission; cfg = config.nixarr.transmission;
globals = config.util-nixarr.globals;
nixarr = config.nixarr; nixarr = config.nixarr;
cfg-cross-seed = config.nixarr.transmission.privateTrackers.cross-seed; cfg-cross-seed = config.nixarr.transmission.privateTrackers.cross-seed;
downloadDir = "${nixarr.mediaDir}/torrents"; downloadDir = "${nixarr.mediaDir}/torrents";
transmissionCrossSeedScript = with builtins; transmissionCrossSeedScript = with builtins;
@@ -284,37 +286,37 @@ in {
]; ];
users = { users = {
groups = { groups.${globals.transmission.group}.gid = globals.gids.${globals.transmission.group};
torrenter = {}; groups.${globals.cross-seed.group}.gid = globals.gids.${globals.cross-seed.group};
cross-seed = {}; users.${globals.transmission.user} = {
};
users.torrenter = {
isSystemUser = true; isSystemUser = true;
group = "torrenter"; group = globals.transmission.group;
uid = globals.uids.${globals.transmission.user};
}; };
}; };
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' 0750 torrenter cross-seed - -" "d '${cfg.stateDir}' 0750 ${globals.transmission.user} ${globals.cross-seed.group} - -"
# This is fixes a bug in nixpks (https://github.com/NixOS/nixpkgs/issues/291883) # This is fixes a bug in nixpks (https://github.com/NixOS/nixpkgs/issues/291883)
"d '${cfg.stateDir}/.config' 0750 torrenter cross-seed - -" "d '${cfg.stateDir}/.config' 0750 ${globals.transmission.user} ${globals.cross-seed.group} - -"
"d '${cfg.stateDir}/.config/transmission-daemon' 0750 torrenter cross-seed - -" "d '${cfg.stateDir}/.config/transmission-daemon' 0750 ${globals.transmission.user} ${globals.cross-seed.group} - -"
# Media Dirs # Media Dirs
"d '${nixarr.mediaDir}/torrents' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/.incomplete' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/.incomplete' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/.watch' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/.watch' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/manual' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/manual' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/lidarr' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/lidarr' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/radarr' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/radarr' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/sonarr' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/sonarr' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
"d '${nixarr.mediaDir}/torrents/readarr' 0755 torrenter media - -" "d '${nixarr.mediaDir}/torrents/readarr' 0755 ${globals.transmission.user} ${globals.transmission.group} - -"
]; ];
util-nixarr.services.cross-seed = mkIf cfg-cross-seed.enable { util-nixarr.services.cross-seed = mkIf cfg-cross-seed.enable {
enable = true; enable = true;
dataDir = cfg-cross-seed.stateDir; dataDir = cfg-cross-seed.stateDir;
group = "cross-seed"; user = globals.cross-seed.user;
group = globals.cross-seed.group;
settings = settings =
{ {
torrentDir = "${cfg.stateDir}/.config/transmission-daemon/torrents"; torrentDir = "${cfg.stateDir}/.config/transmission-daemon/torrents";
@@ -352,8 +354,8 @@ in {
services.transmission = { services.transmission = {
enable = true; enable = true;
user = "torrenter"; user = globals.transmission.user;
group = "media"; group = globals.transmission.group;
home = cfg.stateDir; home = cfg.stateDir;
webHome = webHome =
if cfg.flood.enable if cfg.flood.enable
+1
View File
@@ -1,5 +1,6 @@
{ {
imports = [ imports = [
./upnp ./upnp
./globals
]; ];
} }
+112
View File
@@ -0,0 +1,112 @@
# TODO: Dir creation and file permissions in nix
{
pkgs,
config,
lib,
...
}:
with lib; let
globals = config.util-nixarr.globals;
in {
options.util-nixarr.globals = mkOption {
type = types.attrs;
default = {};
};
config.util-nixarr.globals = {
libraryOwner.user = "root";
libraryOwner.group = "media";
uids = {
plex = 193;
jellyfin = 146;
audiobookshelf = 156;
autobrr = 188;
bazarr = 232;
lidarr = 306;
prowlarr = 293;
jellyseerr = 250;
sonarr = 274;
radarr = 275;
readarr = 250;
readarr-audiobook = 211;
recyclarr = 269;
sabnzbd = 38;
transmission = 70;
cross-seed = 183;
};
gids = {
autobrr = 188;
prowlarr = 287;
cross-seed = 183;
jellyseerr = 250;
media = 169;
recyclarr = 269;
};
audiobookshelf = {
user = "audiobookshelf";
group = globals.libraryOwner.group;
};
autobrr = {
user = "autobrr";
group = "autobrr";
};
bazarr = {
user = "bazarr";
group = globals.libraryOwner.group;
};
jellyfin = {
user = "jellyfin";
group = globals.libraryOwner.group;
};
jellyseerr = {
user = "jellyseerr";
group = "jellyseerr";
};
lidarr = {
user = "lidarr";
group = globals.libraryOwner.group;
};
plex = {
user = "plex";
group = globals.libraryOwner.group;
};
prowlarr = {
user = "prowlarr";
group = "prowlarr";
};
radarr = {
user = "radarr";
group = globals.libraryOwner.group;
};
readarr = {
user = "readarr";
group = globals.libraryOwner.group;
};
readarr-audiobook = {
user = "readarr-audiobook";
readarr-group = globals.libraryOwner.group;
};
recyclarr = {
user = "recyclarr";
group = "recyclarr";
};
sabnzbd = {
user = "sabnzbd";
group = globals.libraryOwner.group;
};
sonarr = {
user = "sonarr";
group = globals.libraryOwner.group;
};
transmission = {
user = "transmission";
group = globals.libraryOwner.group;
};
cross-seed = {
user = "cross-seed";
group = "cross-seed";
};
};
}