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
.direnv
result

View file

@ -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;
}
];
}

View file

@ -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,30 +33,25 @@
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 = {
settings = mkOption {
type = types.submodule {
freeformType = types.attrOf types.str;
options = {
clientEndpoint = mkOption {
type = str;
description = "The endpoint of your web application";
@ -95,12 +59,17 @@ in {
};
apiEndpoint = mkOption {
type = str;
description = "The endpoint of your server";
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 = mdDoc ''
description = ''
The public key of your Spotify application
[Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
'';
@ -108,7 +77,7 @@ in {
};
spotifySecret = mkOption {
type = nullOr str;
description = mdDoc ''
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
@ -118,14 +87,14 @@ in {
};
cors = mkOption {
type = nullOr str;
description = mdDoc ''
description = ''
List of comma-separated origin allowed, or nothing to allow any origin
'';
default = null;
};
maxImportCacheSize = mkOption {
type = str;
description = mdDoc ''
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
'';
@ -133,43 +102,45 @@ in {
};
mongoEndpoint = mkOption {
type = str;
description = mdDoc ''
description = ''
The endpoint of the Mongo database.
'';
default = "mongodb://localhost:27017/your_spotify";
};
port = mkOption {
type = int;
description = "The port of the server";
type = port;
description = "The port of the api server";
default = 8080;
};
timezone = mkOption {
type = str;
description = mdDoc ''
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 = mdDoc ''
description = ''
The log level, debug is useful if you encouter any bugs
'';
default = "info";
};
cookieValidityMs = mkOption {
type = str;
description = mdDoc ''
description = ''
Validity time of the authentication cookie
'';
default = "1h";
};
mongoNoAdminRights = mkOption {
type = bool;
description = mdDoc ''
description = ''
Do not ask for admin right on the Mongo database
'';
default = true;
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)