nix-config/modules/services/your_spotify_m.nix

230 lines
7.1 KiB
Nix

{
pkgs,
config,
lib,
...
}: let
inherit
(lib)
boolToString
concatMapAttrs
concatStrings
isBool
mapAttrsToList
mkEnableOption
mkIf
mkOption
mkPackageOption
optionalAttrs
types
;
cfg = config.services.your_spotify;
configEnv = concatMapAttrs (name: value:
optionalAttrs (value != null) {
${name} =
if isBool value
then boolToString value
else toString value;
})
cfg.settings;
configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
in {
options.services.your_spotify = let
inherit (types) nullOr port str path bool package;
in {
enable = mkEnableOption "your_spotify";
enableLocalDB = mkEnableOption "a local mongodb instance";
nginxVirtualHost = mkOption {
type = nullOr str;
default = null;
description = ''
If set creates an nginx virtual host for the client.
In most cases this should be the CLIENT_ENDPOINT without
protocol prefix.
'';
};
package = mkPackageOption pkgs "your_spotify" {};
clientPackage = mkOption {
type = package;
default = cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;};
description = "Client package to use.";
};
spotifyPublicFile = mkOption {
type = path;
description = ''
The public key of your Spotify application
[Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
'';
};
spotifySecretFile = mkOption {
type = path;
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.
'';
};
settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf types.str;
options = {
CLIENT_ENDPOINT = mkOption {
type = str;
description = ''
The endpoint of your web application
Has to include a protocol Prefix (e.g. `http://`)
'';
example = "https://your_spotify.example.org";
};
API_ENDPOINT = 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.
Has to include a protocol Prefix (e.g. `http://`)
'';
default = "https://localhost:3000";
};
CORS = mkOption {
type = nullOr str;
description = ''
List of comma-separated origin allowed, or nothing to allow any origin
'';
default = null;
};
MAX_IMPORT_CACHESIZE = 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";
};
MONGO_ENDPOINT = 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 = 3000;
};
TIMEZONE = mkOption {
type = str;
description = ''
The timezone of your stats, only affects read requests since data is saved with UTC time
'';
default = "Europe/Paris";
};
LOG_LEVEL = mkOption {
type = str;
description = ''
The log level, debug is useful if you encouter any bugs
'';
default = "info";
};
COOKIE_VALIDITY_MS = mkOption {
type = str;
description = ''
Validity time of the authentication cookie
'';
default = "1h";
};
MONGO_NO_ADMIN_RIGHTS = mkOption {
type = bool;
description = ''
Do not ask for admin right on the Mongo database
'';
default = false;
};
};
};
};
};
config = mkIf cfg.enable {
systemd.services.your_spotify = {
after = ["network.target"];
script = ''
export SPOTIFY_PUBLIC=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_PUBLIC")
export SPOTIFY_SECRET=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_SECRET")
${pkgs.your_spotify}/bin/your_spotify_migrate
exec ${pkgs.your_spotify}/bin/your_spotify_server
'';
serviceConfig = {
User = "your_spotify";
Group = "your_spotify";
DynamicUser = true;
EnvironmentFile = [configFile];
StateDirectory = "your_spotify";
LimitNOFILE = "1048576";
PrivateTmp = true;
PrivateDevices = true;
StateDirectoryMode = "0700";
Restart = "always";
LoadCredential = ["SPOTIFY_PUBLIC:${cfg.spotifyPublicFile}" "SPOTIFY_SECRET:${cfg.spotifySecretFile}"];
# Hardening
CapabilityBoundingSet = "";
LockPersonality = true;
#MemoryDenyWriteExecute = true;
#NoNewPrivileges = true; # Implied by DynamicUser
PrivateUsers = true;
#PrivateTmp = true; # Implied by DynamicUser
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = false; # breaks bwrap
ProtectKernelLogs = false; # breaks bwrap
ProtectKernelModules = true;
ProtectKernelTunables = false; # breaks bwrap
ProtectProc = "invisible";
ProcSubset = "all"; # Using "pid" breaks bwrap
ProtectSystem = "strict";
#RemoveIPC = true; # Implied by DynamicUser
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
#RestrictSUIDSGID = true; # Implied by DynamicUser
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"@pkey"
];
UMask = "0077";
};
wantedBy = ["multi-user.target"];
};
services.nginx = mkIf (cfg.nginxVirtualHost != null) {
enable = true;
virtualHosts.${cfg.nginxVirtualHost} = {
root = cfg.clientPackage;
locations."/".extraConfig = ''
try_files = $uri $uri/ /index.html ;
'';
};
};
services.mongodb = mkIf cfg.enableLocalDB {
enable = true;
};
};
}