feat: your_spotify module
This commit is contained in:
parent
d885e47cec
commit
d36fa802d3
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
21
modules/services/your_spotify.nix
Normal file
21
modules/services/your_spotify.nix
Normal 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;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
220
modules/services/your_spotify_m.nix
Normal file
220
modules/services/your_spotify_m.nix
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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
1
result
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/7izsg14mhdm1a75an92gdgmpjd0pq1ww-client_ts-1.7.3
|
Binary file not shown.
15
secrets/your_spotify.age
Normal file
15
secrets/your_spotify.age
Normal 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Ù’
|
Loading…
Reference in a new issue