Merge pull request #293817 from PatrickDaG/your_spotify

nixos/your_spotify: init at 1.10.1

Changed files
+390
maintainers
nixos
doc
manual
release-notes
modules
services
web-apps
tests
pkgs
by-name
+9
maintainers/maintainer-list.nix
···
githubId = 69802930;
name = "patka";
};
patricksjackson = {
email = "patrick@jackson.dev";
github = "patricksjackson";
···
githubId = 69802930;
name = "patka";
};
+
patrickdag = {
+
email = "patrick-nixos@failmail.dev";
+
github = "PatrickDaG";
+
githubId = 58092422;
+
name = "Patrick";
+
keys = [{
+
fingerprint = "5E4C 3D74 80C2 35FE 2F0B D23F 7DD6 A72E C899 617D";
+
}];
+
};
patricksjackson = {
email = "patrick@jackson.dev";
github = "patricksjackson";
+2
nixos/doc/manual/release-notes/rl-2405.section.md
···
- [xdg-terminal-exec](https://github.com/Vladimir-csp/xdg-terminal-exec), the proposed Default Terminal Execution Specification.
- [RustDesk](https://rustdesk.com), a full-featured open source remote control alternative for self-hosting and security with minimal configuration. Alternative to TeamViewer. Available as [services.rustdesk-server](#opt-services.rustdesk-server.enable).
- [Scrutiny](https://github.com/AnalogJ/scrutiny), a S.M.A.R.T monitoring tool for hard disks with a web frontend. Available as [services.scrutiny](#opt-services.scrutiny.enable).
···
- [xdg-terminal-exec](https://github.com/Vladimir-csp/xdg-terminal-exec), the proposed Default Terminal Execution Specification.
+
- [your_spotify](https://github.com/Yooooomi/your_spotify), a self hosted Spotify tracking dashboard. Available as [services.your_spotify](#opt-services.your_spotify.enable)
+
- [RustDesk](https://rustdesk.com), a full-featured open source remote control alternative for self-hosting and security with minimal configuration. Alternative to TeamViewer. Available as [services.rustdesk-server](#opt-services.rustdesk-server.enable).
- [Scrutiny](https://github.com/AnalogJ/scrutiny), a S.M.A.R.T monitoring tool for hard disks with a web frontend. Available as [services.scrutiny](#opt-services.scrutiny.enable).
+1
nixos/modules/module-list.nix
···
./services/web-apps/windmill.nix
./services/web-apps/wordpress.nix
./services/web-apps/writefreely.nix
./services/web-apps/youtrack.nix
./services/web-apps/zabbix.nix
./services/web-apps/zitadel.nix
···
./services/web-apps/windmill.nix
./services/web-apps/wordpress.nix
./services/web-apps/writefreely.nix
+
./services/web-apps/your_spotify.nix
./services/web-apps/youtrack.nix
./services/web-apps/zabbix.nix
./services/web-apps/zitadel.nix
+191
nixos/modules/services/web-apps/your_spotify.nix
···
···
+
{
+
pkgs,
+
config,
+
lib,
+
...
+
}: let
+
inherit
+
(lib)
+
boolToString
+
concatMapAttrs
+
concatStrings
+
isBool
+
mapAttrsToList
+
mkEnableOption
+
mkIf
+
mkOption
+
mkPackageOption
+
optionalAttrs
+
types
+
mkDefault
+
;
+
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 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;
+
description = "Client package to use.";
+
};
+
+
spotifySecretFile = mkOption {
+
type = path;
+
description = ''
+
A file containing the secret key of your Spotify application.
+
Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application).
+
'';
+
};
+
+
settings = mkOption {
+
description = ''
+
Your Spotify Configuration. Refer to [Your Spotify](https://github.com/Yooooomi/your_spotify) for definitions and values.
+
'';
+
example = lib.literalExpression ''
+
{
+
CLIENT_ENDPOINT = "https://example.com";
+
API_ENDPOINT = "https://api.example.com";
+
SPOTIFY_PUBLIC = "spotify_client_id";
+
}
+
'';
+
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://`)
+
'';
+
example = "https://localhost:3000";
+
};
+
SPOTIFY_PUBLIC = mkOption {
+
type = str;
+
description = ''
+
The public client ID of your Spotify application.
+
Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
+
'';
+
};
+
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;
+
};
+
};
+
};
+
};
+
};
+
+
config = mkIf cfg.enable {
+
services.your_spotify.clientPackage = mkDefault (cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;});
+
systemd.services.your_spotify = {
+
after = ["network.target"];
+
script = ''
+
export SPOTIFY_SECRET=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_SECRET")
+
${lib.getExe' cfg.package "your_spotify_migrate"}
+
exec ${lib.getExe cfg.package}
+
'';
+
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_SECRET:${cfg.spotifySecretFile}"];
+
+
# Hardening
+
CapabilityBoundingSet = "";
+
LockPersonality = true;
+
#MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT
+
PrivateUsers = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectProc = "invisible";
+
ProcSubset = "pid";
+
ProtectSystem = "strict";
+
RestrictAddressFamilies = [
+
"AF_INET"
+
"AF_INET6"
+
"AF_NETLINK"
+
];
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
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 = ''
+
add_header Content-Security-Policy "frame-ancestors 'none';" ;
+
add_header X-Content-Type-Options "nosniff" ;
+
try_files = $uri $uri/ /index.html ;
+
'';
+
};
+
};
+
services.mongodb = mkIf cfg.enableLocalDB {
+
enable = true;
+
};
+
};
+
meta.maintainers = with lib.maintainers; [patrickdag];
+
}
+1
nixos/tests/all-tests.nix
···
yabar = handleTest ./yabar.nix {};
ydotool = handleTest ./ydotool.nix {};
yggdrasil = handleTest ./yggdrasil.nix {};
zammad = handleTest ./zammad.nix {};
zeronet-conservancy = handleTest ./zeronet-conservancy.nix {};
zfs = handleTest ./zfs.nix {};
···
yabar = handleTest ./yabar.nix {};
ydotool = handleTest ./ydotool.nix {};
yggdrasil = handleTest ./yggdrasil.nix {};
+
your_spotify = handleTest ./your_spotify.nix {};
zammad = handleTest ./zammad.nix {};
zeronet-conservancy = handleTest ./zeronet-conservancy.nix {};
zfs = handleTest ./zfs.nix {};
+33
nixos/tests/your_spotify.nix
···
···
+
import ./make-test-python.nix ({pkgs, ...}: {
+
name = "your_spotify";
+
meta = with pkgs.lib.maintainers; {
+
maintainers = [patrickdag];
+
};
+
+
nodes.machine = {
+
services.your_spotify = {
+
enable = true;
+
spotifySecretFile = pkgs.writeText "spotifySecretFile" "deadbeef";
+
settings = {
+
CLIENT_ENDPOINT = "http://localhost";
+
API_ENDPOINT = "http://localhost:3000";
+
SPOTIFY_PUBLIC = "beefdead";
+
};
+
enableLocalDB = true;
+
nginxVirtualHost = "localhost";
+
};
+
};
+
+
testScript = ''
+
machine.wait_for_unit("your_spotify.service")
+
+
machine.wait_for_open_port(3000)
+
machine.wait_for_open_port(80)
+
+
out = machine.succeed("curl --fail -X GET 'http://localhost:3000/'")
+
assert "Hello !" in out
+
+
out = machine.succeed("curl --fail -X GET 'http://localhost:80/'")
+
assert "<title>Your Spotify</title>" in out
+
'';
+
})
+58
pkgs/by-name/yo/your_spotify/client.nix
···
···
+
{
+
apiEndpoint ? "http://localhost:3000",
+
fetchYarnDeps,
+
your_spotify,
+
mkYarnPackage,
+
fixup-yarn-lock,
+
src,
+
version,
+
yarn,
+
}:
+
mkYarnPackage rec {
+
inherit version src;
+
pname = "your_spotify_client";
+
name = "your_spotify_client-${version}";
+
packageJSON = ./package.json;
+
offlineCache = fetchYarnDeps {
+
yarnLock = src + "/yarn.lock";
+
hash = "sha256-5SgknaRVzgO2Dzc8MhAaM8UERWMv+PrItzevoWHbWnA=";
+
};
+
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 = ''
+
runHook preBuild
+
pushd ./apps/client/
+
yarn --offline run build
+
export API_ENDPOINT="${apiEndpoint}"
+
substituteInPlace scripts/run/variables.sh --replace-quiet '/app/apps/client/' "./"
+
+
chmod +x ./scripts/run/variables.sh
+
patchShebangs --build ./scripts/run/variables.sh
+
+
./scripts/run/variables.sh
+
+
popd
+
runHook postBuild
+
'';
+
nativeBuildInputs = [yarn fixup-yarn-lock];
+
+
installPhase = ''
+
runHook preInstall
+
mkdir -p $out
+
cp -r ./apps/client/build/* $out
+
runHook postInstall
+
'';
+
doDist = false;
+
meta = {
+
inherit (your_spotify.meta) homepage changelog description license maintainers;
+
};
+
}
+10
pkgs/by-name/yo/your_spotify/package.json
···
···
+
{
+
"name": "@your_spotify/root",
+
"version": "1.10.1",
+
"repository": "git@github.com:Yooooomi/your_spotify.git",
+
"author": "Timothee <timothee.boussus@gmail.com>",
+
"private": true,
+
"workspaces": [
+
"apps/*"
+
]
+
}
+85
pkgs/by-name/yo/your_spotify/package.nix
···
···
+
{
+
callPackage,
+
fetchFromGitHub,
+
fetchYarnDeps,
+
lib,
+
makeWrapper,
+
mkYarnPackage,
+
nodejs,
+
fixup-yarn-lock,
+
yarn,
+
}: let
+
version = "1.10.1";
+
src = fetchFromGitHub {
+
owner = "Yooooomi";
+
repo = "your_spotify";
+
rev = "refs/tags/${version}";
+
hash = "sha256-e82j2blGxQLWAlBNuAnFvlD9vwMk4/mRI0Vf7vuaPA0=";
+
};
+
client = callPackage ./client.nix {inherit src version;};
+
in
+
mkYarnPackage rec {
+
inherit version src;
+
pname = "your_spotify_server";
+
name = "your_spotify_server-${version}";
+
packageJSON = ./package.json;
+
offlineCache = fetchYarnDeps {
+
yarnLock = src + "/yarn.lock";
+
hash = "sha256-5SgknaRVzgO2Dzc8MhAaM8UERWMv+PrItzevoWHbWnA=";
+
};
+
+
configurePhase = ''
+
runHook preConfigure
+
+
export HOME=$(mktemp -d)
+
yarn config --offline set yarn-offline-mirror $offlineCache
+
fixup-yarn-lock yarn.lock
+
+
runHook postConfigure
+
'';
+
+
buildPhase = ''
+
runHook preBuild
+
+
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
+
patchShebangs node_modules/
+
+
pushd ./apps/server/
+
yarn --offline run build
+
popd
+
+
rm -r node_modules
+
export NODE_ENV="production"
+
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
+
patchShebangs node_modules/
+
+
runHook postBuild
+
'';
+
nativeBuildInputs = [makeWrapper yarn fixup-yarn-lock];
+
installPhase = ''
+
runHook preInstall
+
+
mkdir -p $out/share/your_spotify
+
cp -r node_modules $out/share/your_spotify/node_modules
+
cp -r ./apps/server/{lib,package.json} $out
+
mkdir -p $out/bin
+
makeWrapper ${lib.escapeShellArg (lib.getExe nodejs)} "$out/bin/your_spotify_migrate" \
+
--add-flags "$out/lib/migrations.js" --set NODE_PATH "$out/share/your_spotify/node_modules"
+
makeWrapper ${lib.escapeShellArg (lib.getExe nodejs)} "$out/bin/your_spotify_server" \
+
--add-flags "$out/lib/index.js" --set NODE_PATH "$out/share/your_spotify/node_modules"
+
+
runHook postInstall
+
'';
+
doDist = false;
+
passthru = {
+
inherit client;
+
};
+
meta = with lib; {
+
homepage = "https://github.com/Yooooomi/your_spotify";
+
changelog = "https://github.com/Yooooomi/your_spotify/releases/tag/${version}";
+
description = "Self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it";
+
license = licenses.gpl3Only;
+
maintainers = with maintainers; [patrickdag];
+
mainProgram = "your_spotify_server";
+
};
+
}