diff --git a/.gitignore b/.gitignore index 6ba9cff..c732f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .pre-commit-config.yaml .direnv +result diff --git a/modules/services/your_spotify.nix b/modules/services/your_spotify.nix index bd1a530..7ac4b79 100644 --- a/modules/services/your_spotify.nix +++ b/modules/services/your_spotify.nix @@ -1,21 +1,21 @@ {config, ...}: { imports = [./your_spotify_m.nix]; age.secrets.spotify = { - owner = config.services.your_spotify.user; - group = config.services.your_spotify.group; + inherit (config.services.your_spotify) user group; rekeyFile = ../../secrets/your_spotify.age; }; services.your_spotify = { enable = true; config = { clientEndpoint = "https://spotify.${config.secrets.secrets.global.domains.web}"; + apiEndpoint = "https://api.spotify.${config.secrets.secrets.global.domains.web}"; }; environmentFile = config.age.secrets.spotify.path; }; environment.persistence."/persist".directories = [ { + inherit (config.services.mongodb) user; directory = config.services.mongodb.dbpath; - user = config.services.mongodb.user; } ]; } diff --git a/modules/services/your_spotify_m.nix b/modules/services/your_spotify_m.nix index d3ccc18..90d6cd1 100644 --- a/modules/services/your_spotify_m.nix +++ b/modules/services/your_spotify_m.nix @@ -9,52 +9,21 @@ boolToString concatMapAttrs concatStrings - elem - foldl' - head isBool - isList - lowerChars mapAttrsToList - mdDoc mkEnableOption mkIf mkOption mkPackageOption optional optionalAttrs - optionalString - stringLength - substring - toUpper types ; cfg = config.services.your_spotify; - # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER). - nameToEnvVar = name: let - parts = builtins.split "([A-Z0-9]+)" name; - partsToEnvVar = parts: - foldl' (key: x: let - last = stringLength key - 1; - in - if isList x - then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x - else if key != "" && elem (substring 0 1 x) lowerChars - then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ] - substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x - else key + toUpper x) "" - parts; - in - if builtins.match "[A-Z0-9_]+" name != null - then name - else partsToEnvVar parts; - - # Due to the different naming schemes allowed for config keys, - # we can only check for values consistently after converting them to their corresponding environment variable name. configEnv = concatMapAttrs (name: value: optionalAttrs (value != null) { - ${nameToEnvVar name} = + name = if isBool value then boolToString value else toString value; @@ -64,112 +33,114 @@ configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv)); in { options.services.your_spotify = let - inherit (types) nullOr int str bool package; + inherit (types) nullOr port str bool package; in { - enable = mkEnableOption (lib.mdDoc "your_spotify"); - user = mkOption { - type = types.str; - default = "your_spotify"; - description = lib.mdDoc "User account under which your_spotify runs."; - }; + enable = mkEnableOption "your_spotify"; - group = mkOption { - type = types.str; - default = "your_spotify"; - description = lib.mdDoc "Group account under which your_spotify runs."; - }; + enableLocalDB = mkEnableOption "a local mongodb instance"; + enableNginxVirtualHost = mkEnableOption "a ngnix virtual Host for your client"; package = mkPackageOption pkgs "your_spotify" {}; clientPackage = mkOption { type = package; - default = cfg.package.client.override {apiEndpoint = cfg.config.apiEndpoint;}; - description = lib.mdDoc "Client package to use."; + default = cfg.package.client.override {inherit (cfg.config) apiEndpoint;}; + description = "Client package to use."; }; - config = { - clientEndpoint = mkOption { - type = str; - description = "The endpoint of your web application"; - example = "https://your_spotify.example.org"; - }; - apiEndpoint = mkOption { - type = str; - description = "The endpoint of your server"; - default = "http://localhost:8080"; - }; - spotifyPublic = mkOption { - type = nullOr str; - description = mdDoc '' - The public key of your Spotify application - [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application) - ''; - default = null; - }; - spotifySecret = mkOption { - type = nullOr str; - description = mdDoc '' - The secret key of your Spotify application - [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application) - Note that you may want to set this using the `environmentFile` config option to prevent - your secret from being world-readable in the nix store. - ''; - default = null; - }; - cors = mkOption { - type = nullOr str; - description = mdDoc '' - List of comma-separated origin allowed, or nothing to allow any origin - ''; - default = null; - }; - maxImportCacheSize = mkOption { - type = str; - description = mdDoc '' - The maximum element in the cache when importing data from an outside source, - more cache means less requests to Spotify, resulting in faster imports - ''; - default = "Infinite"; - }; - mongoEndpoint = mkOption { - type = str; - description = mdDoc '' - The endpoint of the Mongo database. - ''; - default = "mongodb://localhost:27017/your_spotify"; - }; - port = mkOption { - type = int; - description = "The port of the server"; - default = 8080; - }; - timezone = mkOption { - type = str; - description = mdDoc '' - The timezone of your stats, only affects read requests since data is saved with UTC time - ''; - default = "Europe/Paris"; - }; - logLevel = mkOption { - type = str; - description = mdDoc '' - The log level, debug is useful if you encouter any bugs - ''; - default = "info"; - }; - cookieValidityMs = mkOption { - type = str; - description = mdDoc '' - Validity time of the authentication cookie - ''; - default = "1h"; - }; - mongoNoAdminRights = mkOption { - type = bool; - description = mdDoc '' - Do not ask for admin right on the Mongo database - ''; - default = true; + settings = mkOption { + type = types.submodule { + freeformType = types.attrOf types.str; + options = { + clientEndpoint = mkOption { + type = str; + description = "The endpoint of your web application"; + example = "https://your_spotify.example.org"; + }; + apiEndpoint = mkOption { + type = str; + description = '' + The endpoint of your server + This api has to be reachable from the device you use the website from not from the server. + This means that for example you may need two nginx virtual hosts if you want to expose this on the + internet. + ''; + default = "http://localhost:8080"; + }; + spotifyPublic = mkOption { + type = nullOr str; + description = '' + The public key of your Spotify application + [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application) + ''; + default = null; + }; + spotifySecret = mkOption { + type = nullOr str; + description = '' + The secret key of your Spotify application + [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application) + Note that you may want to set this using the `environmentFile` config option to prevent + your secret from being world-readable in the nix store. + ''; + default = null; + }; + cors = mkOption { + type = nullOr str; + description = '' + List of comma-separated origin allowed, or nothing to allow any origin + ''; + default = null; + }; + maxImportCacheSize = mkOption { + type = str; + description = '' + The maximum element in the cache when importing data from an outside source, + more cache means less requests to Spotify, resulting in faster imports + ''; + default = "Infinite"; + }; + mongoEndpoint = mkOption { + type = str; + description = '' + The endpoint of the Mongo database. + ''; + default = "mongodb://localhost:27017/your_spotify"; + }; + port = mkOption { + type = port; + description = "The port of the api server"; + default = 8080; + }; + timezone = mkOption { + type = str; + description = '' + The timezone of your stats, only affects read requests since data is saved with UTC time + ''; + default = "Europe/Paris"; + }; + logLevel = mkOption { + type = str; + description = '' + The log level, debug is useful if you encouter any bugs + ''; + default = "info"; + }; + cookieValidityMs = mkOption { + type = str; + description = '' + Validity time of the authentication cookie + ''; + default = "1h"; + }; + mongoNoAdminRights = mkOption { + type = bool; + description = '' + Do not ask for admin right on the Mongo database + ''; + default = false; + }; + }; }; }; @@ -177,7 +148,7 @@ in { type = with types; nullOr path; default = null; example = "/var/lib/your_spotify.env"; - description = lib.mdDoc '' + description = '' Additional environment file as defined in {manpage}`systemd.exec(5)`. Secrets like {env}`SPOTIFY_SECRET` @@ -190,24 +161,19 @@ in { }; config = mkIf cfg.enable { - users.users.${cfg.user} = { - inherit (cfg) group; - isSystemUser = true; - }; - users.groups.${cfg.group} = {}; - systemd.services.your_spotify = { after = ["network.target"]; serviceConfig = { - User = cfg.user; - Group = cfg.group; + User = "your_spotify"; + Group = "your_spotify"; + DynamicUser = true; EnvironmentFile = [configFile] ++ optional (cfg.environmentFile != null) cfg.environmentFile; ExecStartPre = "${pkgs.your_spotify}/bin/your_spotify_migrate"; ExecStart = "${pkgs.your_spotify}/bin/your_spotify_server"; LimitNOFILE = "1048576"; - PrivateTmp = "true"; - PrivateDevices = "true"; - ProtectHome = "true"; + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = true; ProtectSystem = "strict"; StateDirectory = "your_spotify"; StateDirectoryMode = "0700"; @@ -215,6 +181,15 @@ in { }; wantedBy = ["multi-user.target"]; }; - services.mongodb.enable = true; + services.nginx = { + enable = true; + virtualHosts.${cfg.clientEndpoint}.root = cfg.clientPackage; + }; + services.mongodb = mkIf cfg.enableLocalDB { + enable = true; + }; }; } +# nginx gaten +# systemd hardening(e.g. esphome) +