[WIP] music platform user data scraper
teal-fm atproto
Something went wrong. Try again.
piper module.nix
7.7 kB · 244 lines
Nix
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245{ self ? null }:{ config, lib, pkgs, ... }:
let inherit (lib) mkEnableOption mkIf mkOption types literalExpression;
cfg = config.services.tealfm-piper;
settingsFormat = pkgs.formats.keyValue { };
derivedSettings = lib.optionalAttrs (cfg.settings.SERVER_ROOT_URL != null) { ATPROTO_CLIENT_ID = cfg.settings.ATPROTO_CLIENT_ID or "${cfg.settings.SERVER_ROOT_URL}/oauth-client-metadata.json"; ATPROTO_METADATA_URL = cfg.settings.ATPROTO_METADATA_URL or "${cfg.settings.SERVER_ROOT_URL}/oauth-client-metadata.json"; ATPROTO_CALLBACK_URL = cfg.settings.ATPROTO_CALLBACK_URL or "${cfg.settings.SERVER_ROOT_URL}/callback/atproto"; CALLBACK_SPOTIFY = cfg.settings.CALLBACK_SPOTIFY or "${cfg.settings.SERVER_ROOT_URL}/callback/spotify"; };
dbPathDefault = lib.optionalAttrs (cfg.settings.DB_PATH == null) { DB_PATH = "${cfg.dataDir}/piper.db"; };
allowedDidsString = lib.optionalAttrs (cfg.settings.ALLOWED_DIDS != null) { ALLOWED_DIDS = lib.concatStringsSep " " cfg.settings.ALLOWED_DIDS; };
finalSettings = lib.filterAttrs (_: v: v != null) (cfg.settings // derivedSettings // dbPathDefault // allowedDidsString); settingsFile = settingsFormat.generate "tealfm-piper.env" finalSettings;
in { meta = { maintainers = with lib.maintainers; [ ptdewey ]; };
options.services.tealfm-piper = { enable = mkEnableOption "Piper - teal.fm scrobbler service";
package = mkOption { type = types.package; default = if self != null then self.packages.${pkgs.stdenv.hostPlatform.system}.tealfm-piper else pkgs.tealfm-piper; defaultText = literalExpression "pkgs.tealfm-piper"; description = "The piper package to use."; };
user = mkOption { type = types.str; default = "tealfm-piper"; description = "User account under which piper runs."; };
group = mkOption { type = types.str; default = "tealfm-piper"; description = "Group under which piper runs."; };
dataDir = mkOption { type = types.path; default = "/var/lib/tealfm-piper"; description = "Directory where piper stores its database and data."; };
settings = mkOption { type = types.submodule { freeformType = types.attrsOf (types.oneOf [ (types.nullOr types.str) types.int types.port ]);
options = { SERVER_PORT = mkOption { type = types.port; default = 8080; description = "Port to listen on."; };
SERVER_HOST = mkOption { type = types.str; default = "localhost"; description = "Host to bind to."; };
SERVER_ROOT_URL = mkOption { type = types.nullOr types.str; default = null; example = "https://piper.teal.fm"; description = '' Public URL for OAuth callbacks. Required for OAuth flows.
Auto-derives the following URLs if not explicitly set: - ATPROTO_CLIENT_ID - ATPROTO_METADATA_URL - ATPROTO_CALLBACK_URL - CALLBACK_SPOTIFY ''; };
DB_PATH = mkOption { type = types.nullOr types.str; default = null; description = '' Path to SQLite database file. Defaults to {dataDir}/piper.db if not set. ''; };
TRACKER_INTERVAL = mkOption { type = types.int; default = 30; description = "Seconds between music playback checks."; };
SPOTIFY_AUTH_URL = mkOption { type = types.str; default = "https://accounts.spotify.com/authorize"; description = "Spotify authorization endpoint."; };
SPOTIFY_TOKEN_URL = mkOption { type = types.str; default = "https://accounts.spotify.com/api/token"; description = "Spotify token endpoint."; };
SPOTIFY_SCOPES = mkOption { type = types.str; default = "user-read-currently-playing user-read-email"; description = "Spotify OAuth scopes to request."; };
ALLOWED_DIDS = mkOption { type = types.nullOr (types.listOf types.str); default = null; example = literalExpression ''[ "did:plc:abcdefg" "did:web:example.com" ]''; description = '' List of ATProto DIDs allowed to sign in. When set, restricts instance access to only these accounts. Leave null to allow any ATProto account to sign in. ''; }; }; };
default = { };
example = literalExpression '' { SERVER_PORT = 8080; SERVER_HOST = "localhost"; SERVER_ROOT_URL = "https://piper.teal.fm"; TRACKER_INTERVAL = 30; } '';
description = '' Configuration for piper. These will be converted to environment variables. ''; };
environmentFiles = mkOption { type = types.listOf types.path; default = [ ]; example = literalExpression '' [ "/run/secrets/tealfm-piper.env" "/run/secrets/tealfm-piper-apple-music.env" ] ''; description = '' List of files containing environment variables for secrets. Files are loaded in order, with later files overriding earlier ones. ''; }; };
config = mkIf cfg.enable { users.users.${cfg.user} = { isSystemUser = true; group = cfg.group; home = cfg.dataDir; description = "Piper service user"; };
users.groups.${cfg.group} = { };
systemd.services.tealfm-piper = { description = "Piper - teal.fm scrobbler service"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; NoNewPrivileges = true; PrivateTmp = true; PrivateDevices = true; ProtectSystem = "strict"; ProtectHome = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; LockPersonality = true; ReadWritePaths = [ cfg.dataDir ]; StateDirectory = "tealfm-piper"; StateDirectoryMode = "0700"; WorkingDirectory = cfg.dataDir; EnvironmentFile = [ settingsFile ] ++ cfg.environmentFiles; ExecStart = "${cfg.package}/bin/piper"; Restart = "on-failure"; RestartSec = "10s"; }; };
assertions = [ { assertion = (cfg.environmentFiles != [ ]) || (cfg.settings ? ATPROTO_CLIENT_SECRET_KEY); message = "services.tealfm-piper: ATPROTO_CLIENT_SECRET_KEY must be set via settings or environmentFiles"; } { assertion = (cfg.environmentFiles != [ ]) || (cfg.settings ? ATPROTO_CLIENT_SECRET_KEY_ID); message = "services.tealfm-piper: ATPROTO_CLIENT_SECRET_KEY_ID must be set via settings or environmentFiles"; } { assertion = cfg.settings.SERVER_ROOT_URL != null; message = "services.tealfm-piper: SERVER_ROOT_URL must be set in settings (e.g., https://piper.teal.fm)"; } ]; };}