feat: your spotify modules

This commit is contained in:
Patrick 2024-03-02 10:22:02 +01:00
parent dfc62da346
commit 6fe5d90427
Signed by: patrick
GPG key ID: 451F95EFB8BECD0F
3 changed files with 121 additions and 145 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
.pre-commit-config.yaml .pre-commit-config.yaml
.direnv .direnv
result

View file

@ -1,21 +1,21 @@
{config, ...}: { {config, ...}: {
imports = [./your_spotify_m.nix]; imports = [./your_spotify_m.nix];
age.secrets.spotify = { age.secrets.spotify = {
owner = config.services.your_spotify.user; inherit (config.services.your_spotify) user group;
group = config.services.your_spotify.group;
rekeyFile = ../../secrets/your_spotify.age; rekeyFile = ../../secrets/your_spotify.age;
}; };
services.your_spotify = { services.your_spotify = {
enable = true; enable = true;
config = { config = {
clientEndpoint = "https://spotify.${config.secrets.secrets.global.domains.web}"; 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; environmentFile = config.age.secrets.spotify.path;
}; };
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [
{ {
inherit (config.services.mongodb) user;
directory = config.services.mongodb.dbpath; directory = config.services.mongodb.dbpath;
user = config.services.mongodb.user;
} }
]; ];
} }

View file

@ -9,52 +9,21 @@
boolToString boolToString
concatMapAttrs concatMapAttrs
concatStrings concatStrings
elem
foldl'
head
isBool isBool
isList
lowerChars
mapAttrsToList mapAttrsToList
mdDoc
mkEnableOption mkEnableOption
mkIf mkIf
mkOption mkOption
mkPackageOption mkPackageOption
optional optional
optionalAttrs optionalAttrs
optionalString
stringLength
substring
toUpper
types types
; ;
cfg = config.services.your_spotify; 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: configEnv = concatMapAttrs (name: value:
optionalAttrs (value != null) { optionalAttrs (value != null) {
${nameToEnvVar name} = name =
if isBool value if isBool value
then boolToString value then boolToString value
else toString value; else toString value;
@ -64,112 +33,114 @@
configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv)); configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
in { in {
options.services.your_spotify = let options.services.your_spotify = let
inherit (types) nullOr int str bool package; inherit (types) nullOr port str bool package;
in { in {
enable = mkEnableOption (lib.mdDoc "your_spotify"); enable = mkEnableOption "your_spotify";
user = mkOption {
type = types.str;
default = "your_spotify";
description = lib.mdDoc "User account under which your_spotify runs.";
};
group = mkOption { enableLocalDB = mkEnableOption "a local mongodb instance";
type = types.str; enableNginxVirtualHost = mkEnableOption "a ngnix virtual Host for your client";
default = "your_spotify";
description = lib.mdDoc "Group account under which your_spotify runs.";
};
package = mkPackageOption pkgs "your_spotify" {}; package = mkPackageOption pkgs "your_spotify" {};
clientPackage = mkOption { clientPackage = mkOption {
type = package; type = package;
default = cfg.package.client.override {apiEndpoint = cfg.config.apiEndpoint;}; default = cfg.package.client.override {inherit (cfg.config) apiEndpoint;};
description = lib.mdDoc "Client package to use."; description = "Client package to use.";
}; };
config = { settings = mkOption {
clientEndpoint = mkOption { type = types.submodule {
type = str; freeformType = types.attrOf types.str;
description = "The endpoint of your web application"; options = {
example = "https://your_spotify.example.org"; clientEndpoint = mkOption {
}; type = str;
apiEndpoint = mkOption { description = "The endpoint of your web application";
type = str; example = "https://your_spotify.example.org";
description = "The endpoint of your server"; };
default = "http://localhost:8080"; apiEndpoint = mkOption {
}; type = str;
spotifyPublic = mkOption { description = ''
type = nullOr str; The endpoint of your server
description = mdDoc '' This api has to be reachable from the device you use the website from not from the server.
The public key of your Spotify application This means that for example you may need two nginx virtual hosts if you want to expose this on the
[Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application) internet.
''; '';
default = null; default = "http://localhost:8080";
}; };
spotifySecret = mkOption { spotifyPublic = mkOption {
type = nullOr str; type = nullOr str;
description = mdDoc '' description = ''
The secret key of your Spotify application The public key of your Spotify application
[Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-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;
''; };
default = null; spotifySecret = mkOption {
}; type = nullOr str;
cors = mkOption { description = ''
type = nullOr str; The secret key of your Spotify application
description = mdDoc '' [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
List of comma-separated origin allowed, or nothing to allow any origin 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; '';
}; default = null;
maxImportCacheSize = mkOption { };
type = str; cors = mkOption {
description = mdDoc '' type = nullOr str;
The maximum element in the cache when importing data from an outside source, description = ''
more cache means less requests to Spotify, resulting in faster imports List of comma-separated origin allowed, or nothing to allow any origin
''; '';
default = "Infinite"; default = null;
}; };
mongoEndpoint = mkOption { maxImportCacheSize = mkOption {
type = str; type = str;
description = mdDoc '' description = ''
The endpoint of the Mongo database. 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 = "mongodb://localhost:27017/your_spotify"; '';
}; default = "Infinite";
port = mkOption { };
type = int; mongoEndpoint = mkOption {
description = "The port of the server"; type = str;
default = 8080; description = ''
}; The endpoint of the Mongo database.
timezone = mkOption { '';
type = str; default = "mongodb://localhost:27017/your_spotify";
description = mdDoc '' };
The timezone of your stats, only affects read requests since data is saved with UTC time port = mkOption {
''; type = port;
default = "Europe/Paris"; description = "The port of the api server";
}; default = 8080;
logLevel = mkOption { };
type = str; timezone = mkOption {
description = mdDoc '' type = str;
The log level, debug is useful if you encouter any bugs description = ''
''; The timezone of your stats, only affects read requests since data is saved with UTC time
default = "info"; '';
}; default = "Europe/Paris";
cookieValidityMs = mkOption { };
type = str; logLevel = mkOption {
description = mdDoc '' type = str;
Validity time of the authentication cookie description = ''
''; The log level, debug is useful if you encouter any bugs
default = "1h"; '';
}; default = "info";
mongoNoAdminRights = mkOption { };
type = bool; cookieValidityMs = mkOption {
description = mdDoc '' type = str;
Do not ask for admin right on the Mongo database description = ''
''; Validity time of the authentication cookie
default = true; '';
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; type = with types; nullOr path;
default = null; default = null;
example = "/var/lib/your_spotify.env"; example = "/var/lib/your_spotify.env";
description = lib.mdDoc '' description = ''
Additional environment file as defined in {manpage}`systemd.exec(5)`. Additional environment file as defined in {manpage}`systemd.exec(5)`.
Secrets like {env}`SPOTIFY_SECRET` Secrets like {env}`SPOTIFY_SECRET`
@ -190,24 +161,19 @@ in {
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
users.users.${cfg.user} = {
inherit (cfg) group;
isSystemUser = true;
};
users.groups.${cfg.group} = {};
systemd.services.your_spotify = { systemd.services.your_spotify = {
after = ["network.target"]; after = ["network.target"];
serviceConfig = { serviceConfig = {
User = cfg.user; User = "your_spotify";
Group = cfg.group; Group = "your_spotify";
DynamicUser = true;
EnvironmentFile = [configFile] ++ optional (cfg.environmentFile != null) cfg.environmentFile; EnvironmentFile = [configFile] ++ optional (cfg.environmentFile != null) cfg.environmentFile;
ExecStartPre = "${pkgs.your_spotify}/bin/your_spotify_migrate"; ExecStartPre = "${pkgs.your_spotify}/bin/your_spotify_migrate";
ExecStart = "${pkgs.your_spotify}/bin/your_spotify_server"; ExecStart = "${pkgs.your_spotify}/bin/your_spotify_server";
LimitNOFILE = "1048576"; LimitNOFILE = "1048576";
PrivateTmp = "true"; PrivateTmp = true;
PrivateDevices = "true"; PrivateDevices = true;
ProtectHome = "true"; ProtectHome = true;
ProtectSystem = "strict"; ProtectSystem = "strict";
StateDirectory = "your_spotify"; StateDirectory = "your_spotify";
StateDirectoryMode = "0700"; StateDirectoryMode = "0700";
@ -215,6 +181,15 @@ in {
}; };
wantedBy = ["multi-user.target"]; 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)