feat: your_spotify module

This commit is contained in:
Patrick 2024-03-06 13:04:44 +01:00
parent 0ebe35e701
commit a5da0d991f
Signed by: patrick
GPG key ID: 451F95EFB8BECD0F
11 changed files with 167 additions and 121 deletions

View file

@ -15,8 +15,8 @@
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}"; spotifydomain = "sptfy.${config.secrets.secrets.global.domains.web}";
apispotifydomain = "api.spotify.${config.secrets.secrets.global.domains.web}"; apispotifydomain = "apisptfy.${config.secrets.secrets.global.domains.web}";
kanidmdomain = "auth.${config.secrets.secrets.global.domains.web}"; kanidmdomain = "auth.${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 {
@ -182,7 +182,7 @@ in {
''; '';
}; };
upstreams.apispotify = { upstreams.apispotify = {
servers."${ipOf "yourspotify"}:8080" = {}; servers."${ipOf "yourspotify"}:3000" = {};
extraConfig = '' extraConfig = ''
zone spotify 64k ; zone spotify 64k ;

Binary file not shown.

View file

@ -0,0 +1,16 @@
age-encryption.org/v1
-> X25519 NZi/SK85OEWu7w/yvCOn3/l+obNYPsoTZJfBwSSL8gI
7dJPCeMxo1TKGrTH0wWxMB6Y46sph5xGv4JzNeTDtfI
-> piv-p256 XTQkUA AkuxoMo1sCU9PHemSyv5b0xBIK+n5x6OI1Q/VmflaK/o
71KGxc8CXfBhbIBp5NqItWDRvTAyrsJMFSz+O5xRms4
-> piv-p256 ZFgiIw AtA2xJwEhw2jX85m/9JsNyOHdmv2u7tfAMvQdfKkZ6N+
7MbtW54kju2yaIKwn2enFlO6t6othyMP05GPurOB7YM
-> piv-p256 5vmPtQ Ah/kiptBGGyYTGSpjvXoFW7yV33gNE7DuXzSIcupOm1I
cVhABpZEkaPkXEbtk4PPq4z0BTH9kazY2n6jFlaa9YQ
-> piv-p256 ZFgiIw A3GbyGhthzf+oAeMKiI/39MiHvEykf7EkiiW81000Wq5
vMTauia+psU/AtxhriecJci6uONm8BR3db1qPbTANzk
-> *-grease
6agZ0e3ziYasFWtZqR76cVifklgY8kmv531Z79Fr
--- j9TlSPpi3L50WqQw3YD8P0zI0cboA18D6LqzJ1TAYKQ
¿áؘß5©<35>[—b-_C¶BX®J†¾t\BêÙE¦ö¥õ)ŸmŒù´É;¼º{Ž¨º†2a÷Ã
êÖúÚ$é½Ç

View file

@ -1,15 +0,0 @@
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Ù

View file

@ -15,7 +15,6 @@
mkIf mkIf
mkOption mkOption
mkPackageOption mkPackageOption
optional
optionalAttrs optionalAttrs
types types
; ;
@ -23,7 +22,7 @@
configEnv = concatMapAttrs (name: value: configEnv = concatMapAttrs (name: value:
optionalAttrs (value != null) { optionalAttrs (value != null) {
name = ${name} =
if isBool value if isBool value
then boolToString value then boolToString value
else toString value; else toString value;
@ -33,7 +32,7 @@
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 port str bool package; inherit (types) nullOr port str path bool package;
in { in {
enable = mkEnableOption "your_spotify"; enable = mkEnableOption "your_spotify";
@ -47,6 +46,23 @@ in {
default = cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;}; default = cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;};
description = "Client package to use."; 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 { settings = mkOption {
type = types.submodule { type = types.submodule {
@ -65,34 +81,16 @@ in {
This means that for example you may need two nginx virtual hosts if you want to expose this on the This means that for example you may need two nginx virtual hosts if you want to expose this on the
internet. internet.
''; '';
default = "http://localhost:8080"; default = "https://localhost:3000";
}; };
spotifyPublic = mkOption { CORS = mkOption {
type = nullOr str;
description = ''
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 = ''
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; type = nullOr str;
description = '' description = ''
List of comma-separated origin allowed, or nothing to allow any origin List of comma-separated origin allowed, or nothing to allow any origin
''; '';
default = null; default = null;
}; };
maxImportCacheSize = mkOption { MAX_IMPORT_CACHESIZE = mkOption {
type = str; type = str;
description = '' description = ''
The maximum element in the cache when importing data from an outside source, The maximum element in the cache when importing data from an outside source,
@ -100,40 +98,40 @@ in {
''; '';
default = "Infinite"; default = "Infinite";
}; };
mongoEndpoint = mkOption { MONGO_ENDPOINT = mkOption {
type = str; type = str;
description = '' description = ''
The endpoint of the Mongo database. The endpoint of the Mongo database.
''; '';
default = "mongodb://localhost:27017/your_spotify"; default = "mongodb://localhost:27017/your_spotify";
}; };
port = mkOption { PORT = mkOption {
type = port; type = port;
description = "The port of the api server"; description = "The port of the api server";
default = 8080; default = 3000;
}; };
timezone = mkOption { TIMEZONE = mkOption {
type = str; type = str;
description = '' description = ''
The timezone of your stats, only affects read requests since data is saved with UTC time The timezone of your stats, only affects read requests since data is saved with UTC time
''; '';
default = "Europe/Paris"; default = "Europe/Paris";
}; };
logLevel = mkOption { LOG_LEVEL = mkOption {
type = str; type = str;
description = '' description = ''
The log level, debug is useful if you encouter any bugs The log level, debug is useful if you encouter any bugs
''; '';
default = "info"; default = "info";
}; };
cookieValidityMs = mkOption { COOKIE_VALIDITY_MS = mkOption {
type = str; type = str;
description = '' description = ''
Validity time of the authentication cookie Validity time of the authentication cookie
''; '';
default = "1h"; default = "1h";
}; };
mongoNoAdminRights = mkOption { MONGO_NO_ADMIN_RIGHTS = mkOption {
type = bool; type = bool;
description = '' description = ''
Do not ask for admin right on the Mongo database Do not ask for admin right on the Mongo database
@ -143,33 +141,22 @@ in {
}; };
}; };
}; };
environmentFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/var/lib/your_spotify.env";
description = ''
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 { config = mkIf cfg.enable {
systemd.services.your_spotify = { systemd.services.your_spotify = {
after = ["network.target"]; after = ["network.target"];
script = ''
export SPOTIFY_PUBLIC=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_PUBLIC")
export SPOTIFY_SECRET=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_SECRET")
exec ${pkgs.your_spotify}/bin/your_spotify_server
'';
serviceConfig = { serviceConfig = {
User = "your_spotify"; User = "your_spotify";
Group = "your_spotify"; Group = "your_spotify";
DynamicUser = true; DynamicUser = true;
EnvironmentFile = [configFile] ++ optional (cfg.environmentFile != null) cfg.environmentFile; EnvironmentFile = [configFile];
ExecStartPre = "${pkgs.your_spotify}/bin/your_spotify_migrate"; ExecStartPre = "${pkgs.your_spotify}/bin/your_spotify_migrate";
ExecStart = "${pkgs.your_spotify}/bin/your_spotify_server";
StateDirectory = "your_spotify"; StateDirectory = "your_spotify";
LimitNOFILE = "1048576"; LimitNOFILE = "1048576";
PrivateTmp = true; PrivateTmp = true;
@ -177,49 +164,50 @@ in {
StateDirectoryMode = "0700"; StateDirectoryMode = "0700";
Restart = "always"; Restart = "always";
# Hardening LoadCredential = ["SPOTIFY_PUBLIC:${cfg.spotifyPublicFile}" "SPOTIFY_SECRET:${cfg.spotifySecretFile}"];
CapabilityBoundingSet = "";
LockPersonality = true; ## Hardening
MemoryDenyWriteExecute = true; #CapabilityBoundingSet = "";
DevicePolicy = "closed"; #LockPersonality = true;
SupplementaryGroups = ["dialout"]; ##MemoryDenyWriteExecute = true;
#NoNewPrivileges = true; # Implied by DynamicUser ##NoNewPrivileges = true; # Implied by DynamicUser
PrivateUsers = true; #PrivateUsers = true;
#PrivateTmp = true; # Implied by DynamicUser ##PrivateTmp = true; # Implied by DynamicUser
ProtectClock = true; #ProtectClock = true;
ProtectControlGroups = true; #ProtectControlGroups = true;
ProtectHome = true; #ProtectHome = true;
ProtectHostname = false; # breaks bwrap #ProtectHostname = false; # breaks bwrap
ProtectKernelLogs = false; # breaks bwrap #ProtectKernelLogs = false; # breaks bwrap
ProtectKernelModules = true; #ProtectKernelModules = true;
ProtectKernelTunables = false; # breaks bwrap #ProtectKernelTunables = false; # breaks bwrap
ProtectProc = "invisible"; #ProtectProc = "invisible";
ProcSubset = "all"; # Using "pid" breaks bwrap #ProcSubset = "all"; # Using "pid" breaks bwrap
ProtectSystem = "strict"; #ProtectSystem = "strict";
#RemoveIPC = true; # Implied by DynamicUser ##RemoveIPC = true; # Implied by DynamicUser
RestrictAddressFamilies = [ #RestrictAddressFamilies = [
"AF_INET" # "AF_INET"
"AF_INET6" # "AF_INET6"
"AF_NETLINK" # "AF_NETLINK"
"AF_UNIX" # "AF_UNIX"
]; #];
RestrictNamespaces = true; #RestrictNamespaces = true;
RestrictRealtime = true; #RestrictRealtime = true;
#RestrictSUIDSGID = true; # Implied by DynamicUser ##RestrictSUIDSGID = true; # Implied by DynamicUser
SystemCallArchitectures = "native"; #SystemCallArchitectures = "native";
SystemCallFilter = [ #SystemCallFilter = [
"@system-service" # "@system-service"
"@mount" # Required by platformio for chroot # "@mount" # Required by platformio for chroot
]; #];
UMask = "0077"; #UMask = "0077";
}; };
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
}; };
services.nginx = mkIf cfg.enableNginxVirtualHost { services.nginx = mkIf cfg.enableNginxVirtualHost {
enable = true; enable = true;
virtualHosts.${cfg.settings.CLIENT_ENDPOINT} = { virtualHosts.${cfg.settings.CLIENT_ENDPOINT} = {
root = cfg.clientPackage;
locations."/".extraConfig = '' locations."/".extraConfig = ''
try_files = "$uri $uri/ /index.html; try_files = $uri $uri/ /index.html ;
''; '';
}; };
}; };
@ -228,6 +216,3 @@ in {
}; };
}; };
} }
# nginx gaten
# systemd hardening(e.g. esphome)

View file

@ -1,19 +1,26 @@
{config, ...}: { {config, ...}: {
networking.firewall.allowedTCPPorts = [3000 80];
imports = [./your_spotify_m.nix]; imports = [./your_spotify_m.nix];
age.secrets.spotify = { age.secrets.spotifySecret = {
owner = "your_spotify"; owner = "root";
mode = "440"; mode = "440";
rekeyFile = config.node.secretsDir + "/yourspotify.age"; rekeyFile = config.node.secretsDir + "/spotifySecret.age";
};
age.secrets.spotifyPublic = {
owner = "root";
mode = "440";
rekeyFile = config.node.secretsDir + "/spotifyPublic.age";
}; };
services.your_spotify = { services.your_spotify = {
#enable = true; enable = true;
spotifySecretFile = config.age.secrets.spotifySecret.path;
spotifyPublicFile = config.age.secrets.spotifyPublic.path;
settings = { settings = {
CLIENT_ENDPOINT = "https://spotify.${config.secrets.secrets.global.domains.web}"; CLIENT_ENDPOINT = "https://sptfy.${config.secrets.secrets.global.domains.web}";
API_ENDPOINT = "https://api.spotify.${config.secrets.secrets.global.domains.web}"; API_ENDPOINT = "https://apisptfy.${config.secrets.secrets.global.domains.web}";
}; };
enableLocalDB = true; enableLocalDB = true;
enableNginxVirtualHost = true; enableNginxVirtualHost = true;
environmentFile = config.age.secrets.spotify.path;
}; };
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [
{ {

View file

@ -6,6 +6,7 @@
your_spotify = super.callPackage ./your_spotify.nix {}; your_spotify = super.callPackage ./your_spotify.nix {};
deploy = super.callPackage ./deploy.nix {}; deploy = super.callPackage ./deploy.nix {};
minify = super.callPackage ./minify {}; minify = super.callPackage ./minify {};
mongodb-bin = super.callPackage ./mongodb-bin.nix {};
awakened-poe-trade = super.callPackage ./awakened-poe-trade.nix {}; awakened-poe-trade = super.callPackage ./awakened-poe-trade.nix {};
neovim-clean = super.neovim-unwrapped.overrideAttrs (_neovimFinal: neovimPrev: { neovim-clean = super.neovim-unwrapped.overrideAttrs (_neovimFinal: neovimPrev: {
nativeBuildInputs = (neovimPrev.nativeBuildInputs or []) ++ [super.makeWrapper]; nativeBuildInputs = (neovimPrev.nativeBuildInputs or []) ++ [super.makeWrapper];

22
pkgs/mongodb-bin.nix Normal file
View file

@ -0,0 +1,22 @@
{
stdenv,
fetchurl,
}:
stdenv.mkDerivation {
pname = "mongodb-bin";
version = "1.0.0";
srcs = [
(
fetchurl {
url = "https://fastdl.mongodb.org/linux/mongodb-linux-aarch64-ubuntu2204-6.0.14.tgz";
#hash = "";
}
)
(
fetchurl {
url = "https://downloads.mongodb.com/compass/mongosh-2.1.5-linux-x64.tgz";
#hash = "";
}
)
];
}

View file

@ -4,6 +4,8 @@
fetchYarnDeps, fetchYarnDeps,
makeWrapper, makeWrapper,
nodejs, nodejs,
yarn,
prefetch-yarn-deps,
lib, lib,
callPackage, callPackage,
}: let }: let
@ -23,23 +25,37 @@ in
yarnLock = src + "/yarn.lock"; yarnLock = src + "/yarn.lock";
hash = "sha256-pj6owoEPx9gdtFvXF8E89A+Thhe/7m0+OJU6Ttc6ooA="; hash = "sha256-pj6owoEPx9gdtFvXF8E89A+Thhe/7m0+OJU6Ttc6ooA=";
}; };
configurePhase = ''
runHook preConfigure
export HOME=$(mktemp -d)
yarn config --offline set yarn-offline-mirror $offlineCache
fixup-yarn-lock yarn.lock
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
patchShebangs node_modules/
runHook postConfigure
'';
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild
pushd ./deps/@your_spotify/root/apps/server/ ls -lah
yarn --offline --production pushd ./apps/server/
yarn --offline run build
popd popd
runHook postBuild runHook postBuild
''; '';
nativeBuildInputs = [makeWrapper]; nativeBuildInputs = [makeWrapper yarn prefetch-yarn-deps];
installPhase = '' installPhase = ''
mkdir -p $out mkdir -p $out
cp -r $node_modules $out/node_modules cp -r node_modules $out/node_modules
cp -r ./deps/your_spotify/apps/server/{lib,package.json} $out cp -r ./apps/server/{lib,package.json} $out
mkdir -p $out/bin mkdir -p $out/bin
makeWrapper ${lib.escapeShellArg (lib.getExe nodejs)} "$out/bin/your_spotify_migrate" \ makeWrapper ${lib.escapeShellArg (lib.getExe nodejs)} "$out/bin/your_spotify_migrate" \
--add-flags "$out/lib/migrations.js" --add-flags "$out/lib/migrations.js"
makeWrapper ${lib.escapeShellArg (lib.getExe nodejs)} "$out/bin/your_spotify_server" \ makeWrapper ${lib.escapeShellArg (lib.getExe nodejs)} "$out/bin/your_spotify_server" \
--add-flags "$out/lib/bin/www.js" --add-flags "$out/lib/index.js"
''; '';
doDist = false; doDist = false;
passthru = { passthru = {

View file

@ -5,6 +5,8 @@
apiEndpoint ? "localhost:8080", apiEndpoint ? "localhost:8080",
src, src,
version, version,
yarn,
prefetch-yarn-deps,
}: }:
mkYarnPackage rec { mkYarnPackage rec {
inherit version src; inherit version src;
@ -13,18 +15,30 @@ mkYarnPackage rec {
yarnLock = src + "/yarn.lock"; yarnLock = src + "/yarn.lock";
hash = "sha256-pj6owoEPx9gdtFvXF8E89A+Thhe/7m0+OJU6Ttc6ooA="; hash = "sha256-pj6owoEPx9gdtFvXF8E89A+Thhe/7m0+OJU6Ttc6ooA=";
}; };
configurePhase = ''
runHook preConfigure
export HOME=$(mktemp -d)
yarn config --offline set yarn-offline-mirror $offlineCache
fixup-yarn-lock yarn.lock
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
patchShebangs node_modules/
runHook postConfigure
'';
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild
pushd ./deps/@your_spotify/root/apps/client/ pushd ./apps/client/
pwd pwd
yarn --offline run build yarn --offline run build
popd popd
runHook postBuild runHook postBuild
''; '';
nativeBuildInputs = [makeWrapper]; nativeBuildInputs = [makeWrapper yarn prefetch-yarn-deps];
installPhase = '' installPhase = ''
mkdir -p $out mkdir -p $out
cp -r ./deps/your_spotify/apps/client/build/* $out cp -r ./apps/client/build/* $out
substituteInPlace $out/variables-template.js --replace-quiet '__API_ENDPOINT__' "${apiEndpoint}" substituteInPlace $out/variables-template.js --replace-quiet '__API_ENDPOINT__' "${apiEndpoint}"
mv $out/variables-template.js $out/variables.js mv $out/variables-template.js $out/variables.js
''; '';

Binary file not shown.