feat: your_spotify module

This commit is contained in:
Patrick 2024-03-01 12:58:02 +01:00
parent d885e47cec
commit d36fa802d3
Signed by: patrick
GPG key ID: 451F95EFB8BECD0F
8 changed files with 310 additions and 1 deletions

View file

@ -15,6 +15,7 @@
paperlessdomain = "ppl.${config.secrets.secrets.global.domains.web}"; paperlessdomain = "ppl.${config.secrets.secrets.global.domains.web}";
ttrssdomain = "rss.${config.secrets.secrets.global.domains.web}"; ttrssdomain = "rss.${config.secrets.secrets.global.domains.web}";
vaultwardendomain = "pw.${config.secrets.secrets.global.domains.web}"; vaultwardendomain = "pw.${config.secrets.secrets.global.domains.web}";
spotifydomain = "spotify.${config.secrets.secrets.global.domains.web}";
ipOf = hostName: lib.net.cidr.host config.secrets.secrets.global.net.ips."${config.guests.${hostName}.nodeName}" config.secrets.secrets.global.net.privateSubnetv4; ipOf = hostName: lib.net.cidr.host config.secrets.secrets.global.net.ips."${config.guests.${hostName}.nodeName}" config.secrets.secrets.global.net.privateSubnetv4;
in { in {
services.nginx = { services.nginx = {
@ -96,6 +97,7 @@ in {
proxyPass = "http://ollama"; proxyPass = "http://ollama";
proxyWebsockets = true; proxyWebsockets = true;
}; };
extraConfig = '' extraConfig = ''
allow ${config.secrets.secrets.global.net.privateSubnetv4}; allow ${config.secrets.secrets.global.net.privateSubnetv4};
allow ${config.secrets.secrets.global.net.privateSubnetv6}; allow ${config.secrets.secrets.global.net.privateSubnetv6};
@ -162,6 +164,22 @@ in {
''; '';
}; };
upstreams.spotify = {
servers."${ipOf "your_spotify"}:80" = {};
extraConfig = ''
zone spotify 64k ;
keepalive 5 ;
'';
};
virtualHosts.${spotifydomain} = {
forceSSL = true;
useACMEHost = "web";
locations."/".proxyPass = "http://spotify";
extraConfig = ''
'';
};
upstreams.nextcloud = { upstreams.nextcloud = {
servers."${ipOf "nextcloud"}:80" = {}; servers."${ipOf "nextcloud"}:80" = {};
@ -266,6 +284,7 @@ in {
// mkContainer "ddclient" {} // mkContainer "ddclient" {}
// mkContainer "ollama" {} // mkContainer "ollama" {}
// mkContainer "ttrss" {} // mkContainer "ttrss" {}
// mkContainer "your_spotify" {}
// mkContainer "nextcloud" { // mkContainer "nextcloud" {
enablePanzer = true; enablePanzer = true;
} }

View file

@ -28,6 +28,8 @@
maddy = uidGid 218; maddy = uidGid 218;
tt_rss = uidGid 219; tt_rss = uidGid 219;
freshrss = uidGid 220; freshrss = uidGid 220;
mongodb = uidGid 221;
your_spotify = uidGid 222;
paperless = uidGid 315; paperless = uidGid 315;
systemd-oom = uidGid 300; systemd-oom = uidGid 300;
systemd-coredump = uidGid 301; systemd-coredump = uidGid 301;

View file

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

View file

@ -0,0 +1,220 @@
{
pkgs,
config,
lib,
...
}: let
inherit
(lib)
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} =
if isBool value
then boolToString value
else toString value;
})
cfg.config;
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;
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.";
};
group = mkOption {
type = types.str;
default = "your_spotify";
description = lib.mdDoc "Group account under which your_spotify runs.";
};
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.";
};
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;
};
};
environmentFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/var/lib/your_spotify.env";
description = lib.mdDoc ''
Additional environment file as defined in {manpage}`systemd.exec(5)`.
Secrets like {env}`SPOTIFY_SECRET`
may be passed to the service without adding them to the world-readable Nix store.
Note that this file needs to be available on the host on which
`your_spotify` is running.
'';
};
};
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;
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";
ProtectSystem = "strict";
StateDirectory = "your_spotify";
StateDirectoryMode = "0700";
Restart = "always";
};
wantedBy = ["multi-user.target"];
};
services.mongodb.enable = true;
};
}

View file

@ -5,6 +5,7 @@
makeWrapper, makeWrapper,
nodejs, nodejs,
lib, lib,
apiEndpoint ? "localhost:8080",
}: let }: let
version = "1.7.3"; version = "1.7.3";
src_o = fetchFromGitHub { src_o = fetchFromGitHub {
@ -13,6 +14,33 @@
rev = "refs/tags/${version}"; rev = "refs/tags/${version}";
hash = "sha256-/0xKktywwGcqsuwLytWBJ3O6ADHg1nP6BdMRlkW5ErY="; hash = "sha256-/0xKktywwGcqsuwLytWBJ3O6ADHg1nP6BdMRlkW5ErY=";
}; };
client = mkYarnPackage rec {
inherit version;
pname = "your_spotify_client";
src = "${src_o}/client";
offlineCache = fetchYarnDeps {
yarnLock = src + "/yarn.lock";
hash = "sha256-9UfRVv7M9311lesnr19oThYnzB9cK23XNZejJY/Fd24=";
};
postPatch = ''
substituteInPlace tsconfig.json --replace-quiet '"extends": "../tsconfig.json",' ""
'';
buildPhase = ''
runHook preBuild
pushd ./deps/client_ts
yarn --offline run build
popd
runHook postBuild
'';
nativeBuildInputs = [makeWrapper];
installPhase = ''
mkdir -p $out
cp -r ./deps/client_ts/build/* $out
substituteInPlace $out/variables-template.js --replace-quiet '__API_ENDPOINT__' "${apiEndpoint}"
mv $out/variables-template.js $out/variables.js
'';
doDist = false;
};
in in
mkYarnPackage rec { mkYarnPackage rec {
inherit version; inherit version;
@ -23,7 +51,7 @@ in
hash = "sha256-3ZK+p3RoHHjPu53MLGSho7lEroZ77vUrZ2CjDwIUQTs="; hash = "sha256-3ZK+p3RoHHjPu53MLGSho7lEroZ77vUrZ2CjDwIUQTs=";
}; };
postPatch = '' postPatch = ''
substituteInPlace tsconfig.json --replace '"extends": "../tsconfig.json",' "" substituteInPlace tsconfig.json --replace-quiet '"extends": "../tsconfig.json",' ""
''; '';
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild
@ -44,4 +72,7 @@ in
--add-flags "$out/lib/bin/www.js" --add-flags "$out/lib/bin/www.js"
''; '';
doDist = false; doDist = false;
passthru = {
inherit client;
};
} }

1
result Symbolic link
View file

@ -0,0 +1 @@
/nix/store/7izsg14mhdm1a75an92gdgmpjd0pq1ww-client_ts-1.7.3

Binary file not shown.

15
secrets/your_spotify.age Normal file
View file

@ -0,0 +1,15 @@
age-encryption.org/v1
-> X25519 ggSXy/sQbB5RIx9y+7b9gx+Osn4CDC1llDZpEurSUlQ
eaRyyBSWaPjuY0VQOIKef+jeJrKP/bjn0A3ptY1Yi1c
-> piv-p256 XTQkUA A712bv8pNfgCw6BY8uko50ZT6ctKw0aKGMzw21ntFoH9
Od/YRbbeDhrsjrydRLpbJ29fb7FVVLNdHrqHIqADD90
-> piv-p256 ZFgiIw A7KV41jrxMfKZvJVInfcLH0SX22uRKrGx3Ce1RBK1ba0
o6DUQEhob61zHAj6o4l0wPLudMjsg8w2qyanKWn7ZsQ
-> piv-p256 5vmPtQ AjgfvHuq6ZktpH4hS5aMnT8OJnFLN0D0+ELXNvuaNyi/
ALCCRjJYI6CExt9Di4/p5Gcok5IO/nmuFV5wN7ZJYx0
-> piv-p256 ZFgiIw Amn+6yW9k49wRmdkooDqE185U/oZq69mcP2NbOq4l5Ty
aQbjyUaiBbf34Fg6HXxgcuVy5s69j4nhmZKelxlGx2Y
-> Zb-grease Kj7
L0fTYguif9Le7qsrbF1YsD43CgE
--- lGupwRLVGo0u7OcziXOmEFo6kA7NsvnMuCLWiIRdqA0
#yä^¢uŽf1ʶ¦®í6D²ëAôVIÕnhzÅG&ÄÀeGVðýºg&Mìq‰¬TííÉÜÉ]ð<>95ÑÕë/3µ9–ÐÑýõ<C3BD>.ßè=ŒÎôŸw~4»Û§Ãw~H3\c>ù؆Κš¯ñZÙ