aboutsummaryrefslogtreecommitdiff
path: root/modules/nixosModules
diff options
context:
space:
mode:
authorspl3g <notspl3g@duck.com>2026-03-18 18:01:41 +0300
committerspl3g <notspl3g@duck.com>2026-03-18 18:01:59 +0300
commit03648b3d9f177227df40129bed22558f6924b91c (patch)
tree8a22eda142beeafd9002a8d5901ba9428a77ad52 /modules/nixosModules
parentdc19a2b583b3ab50d8e36ff0a90ca633495f675f (diff)
so.. v2 i guess
Diffstat (limited to 'modules/nixosModules')
-rw-r--r--modules/nixosModules/booklore.nix176
-rw-r--r--modules/nixosModules/directories.nix90
-rw-r--r--modules/nixosModules/gonic.nix114
-rw-r--r--modules/nixosModules/nfs.nix118
-rw-r--r--modules/nixosModules/nginxProxy.nix217
-rw-r--r--modules/nixosModules/watcharr.nix74
6 files changed, 789 insertions, 0 deletions
diff --git a/modules/nixosModules/booklore.nix b/modules/nixosModules/booklore.nix
new file mode 100644
index 0000000..3eeb3b9
--- /dev/null
+++ b/modules/nixosModules/booklore.nix
@@ -0,0 +1,176 @@
+{
+ inputs,
+ self,
+ ...
+}: {
+ flake.nixosModules.booklore = {
+ config,
+ lib,
+ pkgs,
+ ...
+ }:
+ with lib; let
+ cfg = config.services.booklore;
+ in {
+ options = {
+ services.booklore = {
+ enable = mkEnableOption "Enable booklore service";
+ subdomain = mkOption {
+ type = types.str;
+ description = ''
+ Subdomain to use for nginx.
+ '';
+ };
+ uid = mkOption {
+ type = types.str;
+ description = ''
+ UID for the container user.
+ '';
+ };
+ gid = mkOption {
+ type = types.str;
+ description = ''
+ GID for the container user.
+ '';
+ };
+ settings = {
+ timezone = mkOption {
+ type = types.str;
+ description = ''
+ Timezone string;
+ '';
+ };
+ dataDir = mkOption {
+ type = types.path;
+ description = ''
+ Booklore data directory.
+ '';
+ default = "/var/lib/booklore";
+ };
+ bookdropDir = mkOption {
+ type = types.path;
+ description = ''
+ Directory where booklore will injest books. It is not created automatically, you should create it with the sufficient permissions for the uid and gid you provided.
+ '';
+ };
+ booksDir = mkOption {
+ type = types.path;
+ description = ''
+ Directory where booklore will store books. It is not created automatically, you should create it with the sufficient permissions for the uid and gid you provided.
+ '';
+ };
+ };
+ database = {
+ name = mkOption {
+ type = types.str;
+ default = "booklore";
+ };
+ user = mkOption {
+ type = types.str;
+ default = "files";
+ };
+ password = mkOption {
+ type = types.str;
+ default = "booklore";
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ createPaths = {
+ "${cfg.settings.dataDir}" = {
+ owner = cfg.uid;
+ group = cfg.gid;
+ permissions = "0750";
+ };
+ };
+ nginxProxy = {
+ enable = true;
+ subdomains = {
+ "${cfg.subdomain}" = {
+ proxyPass = "http://127.0.0.1:6060";
+ proxyWebsockets = true;
+ };
+ };
+ };
+
+ virtualisation.oci-containers.containers.booklore = {
+ image = "booklore/booklore:latest";
+ environment = {
+ USER_ID = cfg.uid;
+ GROUP_ID = cfg.gid;
+ TZ = cfg.settings.timezone;
+
+ DATABASE_URL = "jdbc:mariadb://mariadb-booklore:3306/${cfg.database.name}";
+ DATABASE_USERNAME = cfg.database.user;
+ DATABASE_PASSWORD = cfg.database.password;
+ };
+ ports = [
+ "127.0.0.1:6060:6060"
+ ];
+ volumes = [
+ "${cfg.settings.dataDir}:/app/data"
+ "${cfg.settings.booksDir}:/books"
+ "${cfg.settings.bookdropDir}:/bookdrop"
+ ];
+ dependsOn = [
+ "mariadb-booklore"
+ ];
+ networks = [
+ "booklore_default"
+ ];
+ };
+ systemd.services."podman-booklore" = {
+ serviceConfig = {
+ Restart = lib.mkOverride 90 "always";
+ };
+ after = [
+ "podman-network-booklore_default.service"
+ ];
+ requires = [
+ "podman-network-booklore_default.service"
+ ];
+ };
+
+ virtualisation.oci-containers.containers.mariadb-booklore = {
+ image = "lscr.io/linuxserver/mariadb:11.4.5";
+ environment = {
+ PUID = "1000";
+ PGID = "1000";
+ TZ = cfg.settings.timezone;
+ MYSQL_DATABASE = cfg.database.name;
+ MYSQL_USER = cfg.database.user;
+ MYSQL_PASSWORD = cfg.database.password;
+ };
+ networks = [
+ "booklore_default"
+ ];
+ };
+
+ systemd.services."podman-mariadb-booklore" = {
+ serviceConfig = {
+ Restart = lib.mkOverride 90 "always";
+ };
+ after = [
+ "podman-network-booklore_default.service"
+ ];
+ requires = [
+ "podman-network-booklore_default.service"
+ ];
+ };
+
+ systemd.services."podman-network-booklore_default" = {
+ path = [pkgs.podman];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStop = "podman network rm -f booklore_default";
+ };
+ script = ''
+ podman network inspect booklore_default || podman network create booklore_default
+ '';
+ };
+ };
+ };
+}
diff --git a/modules/nixosModules/directories.nix b/modules/nixosModules/directories.nix
new file mode 100644
index 0000000..6a1426f
--- /dev/null
+++ b/modules/nixosModules/directories.nix
@@ -0,0 +1,90 @@
+{inputs, ...}: {
+ flake.nixosModules.directories = {
+ config,
+ lib,
+ ...
+ }:
+ with lib; let
+ cfg = config.createPaths;
+ pathAttrsToListRec = pathsAttrSet: parentPath: parentConfig:
+ lib.flatten (lib.mapAttrsToList (path: config: let
+ filteredConfig = lib.filterAttrs (n: v: v != null) (builtins.removeAttrs config ["subPaths"]);
+ out =
+ {
+ path =
+ if parentPath == ""
+ then path
+ else parentPath + "/" + path;
+ }
+ // parentConfig // filteredConfig;
+ in
+ if config ? subPaths
+ then [out] ++ (pathAttrsToListRec config.subPaths path filteredConfig)
+ else [out])
+ pathsAttrSet);
+ pathConfig = {
+ options = {
+ group = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ owner = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ permissions = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ subPaths = mkOption {
+ type = types.attrsOf (types.submodule pathConfig);
+ default = {};
+ };
+ };
+ };
+ pathList = pathAttrsToListRec cfg "" {};
+ in rec {
+ options = {
+ createPaths = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ group = mkOption {
+ type = types.str;
+ };
+ owner = mkOption {
+ type = types.str;
+ };
+ permissions = mkOption {
+ type = types.str;
+ default = "0740";
+ };
+ subPaths = mkOption {
+ type = types.attrsOf (types.submodule pathConfig);
+ default = {};
+ description = ''
+ SubPaths to create using systemd tmpfiles.
+ '';
+ };
+ };
+ });
+ default = {};
+ description = ''
+ Paths to create using systemd tmpfiles.
+ '';
+ };
+ };
+
+ config = mkIf (cfg != {}) {
+ systemd.tmpfiles.rules =
+ map
+ (dir: "d ${dir.path} ${dir.permissions} ${dir.owner} ${dir.group}")
+ pathList;
+
+ users = let
+ extraGroups = map (path: path.group) pathList;
+ in {
+ groups = genAttrs extraGroups (group: {});
+ };
+ };
+ };
+}
diff --git a/modules/nixosModules/gonic.nix b/modules/nixosModules/gonic.nix
new file mode 100644
index 0000000..0f1907a
--- /dev/null
+++ b/modules/nixosModules/gonic.nix
@@ -0,0 +1,114 @@
+{inputs, ...}: {
+ flake.nixosModules.gonic = {
+ config,
+ lib,
+ pkgs,
+ ...
+ }:
+ with lib; let
+ cfg = config.gonic;
+ in {
+ options = {
+ gonic = {
+ enable = mkEnableOption "enable gonic configuration";
+
+ listenAddr = mkOption {
+ type = types.str;
+ default = "127.0.0.1:4747";
+ description = ''
+ Address that gonic will listen on.
+ '';
+ };
+
+ extraGroups = mkOption {
+ type = types.listOf (types.str);
+ default = [];
+ description = ''
+ Additional groups for gonic.
+ '';
+ };
+
+ musicPaths = mkOption {
+ type = types.listOf (types.str);
+ description = ''
+ Directories with music in it.
+ '';
+ };
+
+ podcastsPath = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/podcasts";
+ description = ''
+ Directory for podcasts.
+ '';
+ };
+
+ playlistsPath = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/playlists";
+ description = ''
+ Directory for playlists.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "/var/lib/gonic";
+ description = ''
+ A directory where gonic will keep their files.
+ '';
+ };
+
+ settings = mkOption {
+ default = {};
+ description = ''
+ Additional gonic settings
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.gonic.serviceConfig = {
+ DynamicUser = lib.mkForce false;
+ User = "gonic";
+ Group = "gonic";
+ SupplementaryGroups = cfg.extraGroups;
+ ReadWritePaths = [
+ cfg.podcastsPath
+ cfg.playlistsPath
+ ];
+ };
+
+ users = {
+ groups = {
+ gonic = {};
+ };
+
+ users.gonic = {
+ isSystemUser = true;
+ group = "gonic";
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d ${cfg.stateDir} 0755 gonic gonic"
+ "d ${cfg.podcastsPath} 0755 gonic gonic"
+ "d ${cfg.playlistsPath} 0755 gonic gonic"
+ ];
+
+ services.gonic = {
+ enable = true;
+ settings =
+ {
+ listen-addr = cfg.listenAddr;
+ music-path = cfg.musicPaths;
+ playlists-path = [cfg.playlistsPath];
+ podcast-path = [cfg.podcastsPath];
+ db-path = ["${cfg.stateDir}/gonic.db"];
+ }
+ // cfg.settings;
+ };
+ };
+ };
+}
diff --git a/modules/nixosModules/nfs.nix b/modules/nixosModules/nfs.nix
new file mode 100644
index 0000000..3f53cc6
--- /dev/null
+++ b/modules/nixosModules/nfs.nix
@@ -0,0 +1,118 @@
+{inputs, ...}: {
+ flake.nixosModules.nfs = {
+ config,
+ lib,
+ ...
+ }:
+ with lib; let
+ cfg = config.nfs;
+ in {
+ options = {
+ nfs.server = mkOption {
+ description = ''
+ NFS server configuration.
+ '';
+ default = {enable = false;};
+ type = types.submodule {
+ options = {
+ enable = mkEnableOption "Enable nfs server";
+ exportsPath = mkOption {
+ type = types.str;
+ default = "/export";
+ description = ''
+ A path to the dir, where exports will be binded.
+ '';
+ };
+
+ defaultExportIps = mkOption {
+ type = types.listOf (types.str);
+ description = ''
+ A list of ip addresses, that will be used as default in exportDirs
+ '';
+ };
+
+ defaultExportParams = mkOption {
+ type = types.str;
+ default = "rw,nohide,insecure,no_subtree_check";
+ description = ''
+ Params, that will be used as default in exportDirs
+ '';
+ };
+
+ exportDirs = mkOption {
+ description = ''
+ A list of directories to export.
+ '';
+ type = types.listOf (types.submodule {
+ options = {
+ path = mkOption {
+ type = types.str;
+ description = ''
+ A path to the directory to export.
+ '';
+ };
+ exportPath = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ A path that will be binded to the export directory in the exportsPath.
+ '';
+ };
+ ips = mkOption {
+ type = types.listOf (types.str);
+ default = cfg.server.defaultExportIps;
+ description = ''
+ A list of ip addresses to export the dir to.
+ '';
+ };
+ params = mkOption {
+ type = types.str;
+ default = cfg.server.defaultExportParams;
+ description = ''
+ Params for the ip addresses.
+ '';
+ };
+ };
+ });
+ };
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.server.enable {
+ services.nfs.server = {
+ enable = true;
+ exports =
+ "${cfg.server.exportsPath} ${concatMapStrings (ip: "${ip}(rw,fsid=0,no_subtree_check) ") cfg.server.defaultExportIps}\n"
+ + concatMapStrings
+ (dir: let
+ ips = concatMapStrings (ip: "${ip}(${dir.params}) ") dir.ips;
+ exportPath =
+ if dir.exportPath != ""
+ then dir.exportPath
+ else baseNameOf dir.path;
+ in "${cfg.server.exportsPath}/${exportPath} ${ips}\n")
+ cfg.server.exportDirs;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d ${cfg.server.exportsPath} 0744 nobody nogroup"
+ ];
+
+ fileSystems = listToAttrs (map (exportDir: let
+ exportPath =
+ if exportDir.exportPath != ""
+ then exportDir.exportPath
+ else baseNameOf exportDir.path;
+ fullExportPath = "${cfg.server.exportsPath}/${exportPath}";
+ in {
+ name = fullExportPath;
+ value = {
+ device = exportDir.path;
+ options = ["bind"];
+ };
+ }) cfg.server.exportDirs);
+ };
+ };
+}
diff --git a/modules/nixosModules/nginxProxy.nix b/modules/nixosModules/nginxProxy.nix
new file mode 100644
index 0000000..36fdc59
--- /dev/null
+++ b/modules/nixosModules/nginxProxy.nix
@@ -0,0 +1,217 @@
+{inputs, ...}: {
+ flake.nixosModules.nginxProxy = {
+ pkgs,
+ config,
+ lib,
+ ...
+ }:
+ with lib; let
+ vhostOptions = import (pkgs.path + "/nixos/modules/services/web-servers/nginx/vhost-options.nix");
+ locationOptions = import (pkgs.path + "/nixos/modules/services/web-servers/nginx/location-options.nix");
+ nginxOptions = import (pkgs.path + "/nixos/modules/services/web-servers/nginx/default.nix");
+
+ autheliaAuth = url: ''
+ auth_request /internal/authelia/authz;
+ auth_request_set $redirection_url $upstream_http_location;
+ error_page 401 =302 $redirection_url;
+
+ auth_request_set $user $upstream_http_remote_user;
+ auth_request_set $groups $upstream_http_remote_groups;
+ auth_request_set $email $upstream_http_remote_email;
+ auth_request_set $name $upstream_http_remote_name;
+
+ proxy_set_header Remote-User $user;
+ proxy_set_header Remote-Groups $groups;
+ proxy_set_header Remote-Email $email;
+ proxy_set_header Remote-Name $name;
+ '';
+
+ autheliaLocation = url: ''
+ internal;
+ set $upstream_authelia ${url}/api/authz/auth-request;
+ proxy_pass $upstream_authelia;
+
+ ## Headers
+ ## The headers starting with X-* are required.
+ proxy_set_header X-Original-Method $request_method;
+ proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header Content-Length "";
+ proxy_set_header Connection "";
+
+ ## Basic Proxy Configuration
+ proxy_pass_request_body off;
+ proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; # Timeout if the real server is dead
+ proxy_redirect http:// $scheme://;
+ proxy_http_version 1.1;
+ proxy_cache_bypass $cookie_session;
+ proxy_no_cache $cookie_session;
+ proxy_buffers 4 32k;
+ client_body_buffer_size 128k;
+
+ ## Advanced Proxy Configuration
+ send_timeout 5m;
+ proxy_read_timeout 240;
+ proxy_send_timeout 240;
+ proxy_connect_timeout 240;
+ '';
+
+ cfg = config.nginxProxy;
+ in {
+ options.nginxProxy = {
+ enable = mkEnableOption "Enable nginxProxy";
+
+ domain = mkOption {
+ type = types.str;
+ description = ''
+ Domain to use with subdomains
+ '';
+ };
+
+ recommendedProxySettings = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enables global recommended proxy settings
+ '';
+ };
+
+ subdomains = mkOption {
+ type = types.attrsOf (types.submodule (locationOptions {inherit config lib;}));
+ description = ''
+ Subdomains with nginx virtualHosts configuration
+ '';
+ };
+
+ extraVirtualHosts = mkOption {
+ type = types.attrsOf (types.submodule (vhostOptions {inherit config lib;}));
+ default = {};
+ };
+
+ home = {
+ virtualHosts = mkOption {
+ type = types.attrsOf (types.submodule (vhostOptions {inherit config lib;}));
+ default = {};
+ description = ''
+ Virtual hosts from another nginx configuration, that will be used to decrypt ssl and forward traffic to another server.
+ Make sure that the connection between the two is secure.
+ '';
+ };
+
+ subdomains = mkOption {
+ type = types.attrsOf (types.submodule (locationOptions {inherit config lib;}));
+ default = {};
+ description = ''
+ Subdomains from another nginx configuration, that will be used to decrypt ssl and forward traffic to another server.
+ Make sure that the connection between the two is secure.
+ '';
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = cfg.domain;
+ description = ''
+ Home domain, if no domain provided, the current will be used;
+ '';
+ };
+
+ url = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Url that requests would be passed to;
+ '';
+ };
+
+ authelia = mkOption {
+ type = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ };
+ publicUrl = mkOption {
+ type = types.str;
+ default = "https://auth.${cfg.domain}/";
+ };
+ localUrl = mkOption {
+ type = types.str;
+ default = "http://127.0.0.1:9091";
+ };
+ };
+ };
+ default = {};
+ };
+ };
+
+ acme = {
+ enable = mkEnableOption "enable acme certs";
+ email = mkOption {
+ type = types.str;
+ default = "notspl3g+acme@duck.com";
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.attrsOf (types.submodule nginxOptions);
+ default = {};
+ description = ''
+ Extra nginx config.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ security.acme = mkIf cfg.acme.enable {
+ acceptTerms = true;
+ defaults.email = cfg.acme.email;
+ };
+
+ users.groups.nginx = mkIf cfg.acme.enable {};
+ users.users.nginx = mkIf cfg.acme.enable {
+ group = "nginx";
+ extraGroups = ["acme"];
+ isSystemUser = true;
+ };
+ services.nginx = let
+ ssl = {
+ forceSSL = cfg.acme.enable;
+ enableACME = cfg.acme.enable;
+ };
+
+ makeVhosts = domain: subdomains:
+ lib.concatMapAttrs
+ (name: value: {${name + "." + domain} = {locations."/" = value;} // ssl;})
+ subdomains;
+
+ homeRoutes = homeVirtualHosts: homeUrl:
+ builtins.mapAttrs
+ (name: value:
+ {
+ locations."/" =
+ value.locations."/"
+ // {
+ proxyPass = homeUrl;
+ recommendedProxySettings = true;
+ extraConfig = value.locations."/".extraConfig + (autheliaAuth cfg.home.authelia.publicUrl);
+ };
+ locations."/internal/authelia/authz" = mkIf cfg.home.authelia.enable {
+ extraConfig = autheliaLocation cfg.home.authelia.localUrl;
+ };
+ }
+ // ssl)
+ homeVirtualHosts;
+
+ vhosts = makeVhosts cfg.domain cfg.subdomains;
+ homeVhosts = homeRoutes ((makeVhosts (cfg.home.domain) cfg.home.subdomains) // cfg.home.virtualHosts) cfg.home.url;
+ in
+ {
+ enable = true;
+ recommendedProxySettings = cfg.recommendedProxySettings;
+
+ virtualHosts = vhosts // homeVhosts // cfg.extraVirtualHosts;
+ }
+ // cfg.extraConfig;
+ };
+ };
+}
diff --git a/modules/nixosModules/watcharr.nix b/modules/nixosModules/watcharr.nix
new file mode 100644
index 0000000..2263e4f
--- /dev/null
+++ b/modules/nixosModules/watcharr.nix
@@ -0,0 +1,74 @@
+{
+ inputs,
+ self,
+ ...
+}: {
+ flake.nixosModules.watcharr = {
+ config,
+ lib,
+ pkgs,
+ ...
+ }:
+ with lib; let
+ cfg = config.services.watcharr;
+ port = builtins.toString cfg.settings.port;
+ in {
+ options = {
+ services.watcharr = {
+ enable = mkEnableOption "Enable watcharr service";
+ subdomain = mkOption {
+ type = types.str;
+ description = ''
+ Subdomain to use for nginx.
+ '';
+ };
+ settings = {
+ dataDir = mkOption {
+ type = types.path;
+ description = ''
+ Watcharr data directory.
+ '';
+ default = "/var/lib/watcharr";
+ };
+ port = mkOption {
+ type = types.port;
+ default = 3080;
+ description = ''
+ Port to use.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ createPaths = {
+ "${cfg.settings.dataDir}" = {
+ owner = "root";
+ group = "root";
+ permissions = "0750";
+ };
+ };
+
+ nginxProxy = {
+ enable = true;
+ subdomains = {
+ "${cfg.subdomain}" = {
+ proxyPass = "http://127.0.0.1:${port}";
+ proxyWebsockets = true;
+ };
+ };
+ };
+
+ virtualisation.oci-containers.containers.watcharr = {
+ image = "ghcr.io/sbondco/watcharr:latest";
+ ports = [
+ "127.0.0.1${port}:3080"
+ ];
+ volumes = [
+ "${cfg.settings.dataDir}:/data"
+ ];
+ };
+ };
+ };
+}