908c37f73f
Module manages PostgreSQL (both databases + schema init), qBittorrent (VPN-confined via VPN-Confinement), Jackett, metadata-agregator, musicfs, and the main orchestrator. All services are independently enableable with auto-wired inter-service configuration. Includes NixOS VM test validating PostgreSQL setup, schema initialization, service startup, and directory creation. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
238 lines
6.6 KiB
Nix
238 lines
6.6 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.agregators;
|
|
pgCfg = cfg.postgres;
|
|
|
|
# For local: run as the db user via peer auth (service runs as that user)
|
|
# For remote: run as anyone, use password auth via PGPASSWORD
|
|
mkPsql =
|
|
db:
|
|
lib.concatStringsSep " " (
|
|
[
|
|
"${pkgs.postgresql}/bin/psql"
|
|
]
|
|
++ lib.optionals (!isLocal) [
|
|
"-h ${lib.escapeShellArg pgCfg.host}"
|
|
"-p ${toString pgCfg.port}"
|
|
"-U ${lib.escapeShellArg db.user}"
|
|
]
|
|
++ [
|
|
"-d ${lib.escapeShellArg db.name}"
|
|
]
|
|
);
|
|
|
|
mkPasswordExport =
|
|
db:
|
|
if db.passwordFile != null then
|
|
''export PGPASSWORD="$(cat ${lib.escapeShellArg db.passwordFile})"''
|
|
else
|
|
"";
|
|
|
|
isLocal = pgCfg.host == "/run/postgresql" || pgCfg.host == "localhost" || pgCfg.host == "127.0.0.1";
|
|
in
|
|
{
|
|
options.services.agregators.postgres = {
|
|
host = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/run/postgresql";
|
|
description = ''
|
|
PostgreSQL host. Use a path for unix socket (e.g. /run/postgresql)
|
|
or a hostname for TCP.
|
|
'';
|
|
};
|
|
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 5432;
|
|
description = "PostgreSQL port (ignored for unix socket connections).";
|
|
};
|
|
|
|
musicDatabase = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "music_agregator";
|
|
description = "Database name for music-agregator.";
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "music_agregator";
|
|
description = "Database user for music-agregator.";
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Password file for music-agregator database. Null for peer auth.";
|
|
};
|
|
};
|
|
|
|
metadataDatabase = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "metadata_agregator";
|
|
description = "Database name for metadata-agregator.";
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "metadata_agregator";
|
|
description = "Database user for metadata-agregator.";
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Password file for metadata-agregator database. Null for peer auth.";
|
|
};
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.str;
|
|
default = { };
|
|
description = "Extra PostgreSQL settings merged into services.postgresql.settings.";
|
|
example = {
|
|
shared_buffers = "256MB";
|
|
effective_cache_size = "768MB";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
# Create system users for peer auth (local postgres only)
|
|
users.users = lib.mkIf isLocal {
|
|
${pgCfg.musicDatabase.user} = {
|
|
isSystemUser = true;
|
|
group = cfg.group;
|
|
};
|
|
${pgCfg.metadataDatabase.user} = {
|
|
isSystemUser = true;
|
|
group = cfg.group;
|
|
};
|
|
};
|
|
|
|
# Enable and configure local PostgreSQL
|
|
services.postgresql = lib.mkIf isLocal {
|
|
enable = lib.mkDefault true;
|
|
settings = {
|
|
shared_preload_libraries = "pg_prewarm";
|
|
}
|
|
// pgCfg.settings;
|
|
|
|
ensureDatabases = [
|
|
pgCfg.musicDatabase.name
|
|
pgCfg.metadataDatabase.name
|
|
];
|
|
|
|
ensureUsers = [
|
|
{
|
|
name = pgCfg.musicDatabase.user;
|
|
ensureDBOwnership = true;
|
|
}
|
|
{
|
|
name = pgCfg.metadataDatabase.user;
|
|
ensureDBOwnership = true;
|
|
}
|
|
];
|
|
};
|
|
|
|
# Schema init for metadata_agregator
|
|
systemd.services.agregators-schema-metadata = {
|
|
description = "Initialize metadata_agregator database schema";
|
|
after = [ "network.target" ] ++ lib.optionals isLocal [ "postgresql.service" ];
|
|
requires = lib.optionals isLocal [ "postgresql.service" ];
|
|
before = [ "metadata-agregator.service" ];
|
|
requiredBy = [ "metadata-agregator.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
# Run as the db user for peer auth on local postgres
|
|
User = if isLocal then pgCfg.metadataDatabase.user else cfg.user;
|
|
};
|
|
|
|
script =
|
|
let
|
|
psql = mkPsql pgCfg.metadataDatabase;
|
|
passwordExport = mkPasswordExport pgCfg.metadataDatabase;
|
|
in
|
|
''
|
|
${passwordExport}
|
|
MARKER="${cfg.stateDir}/postgres/.schema-metadata-initialized"
|
|
|
|
# Wait for database
|
|
for i in $(seq 1 30); do
|
|
if ${psql} -c "SELECT 1" >/dev/null 2>&1; then break; fi
|
|
echo "Waiting for metadata database... ($i/30)"
|
|
sleep 1
|
|
done
|
|
|
|
if [ -f "$MARKER" ]; then
|
|
echo "metadata_agregator schema already initialized"
|
|
exit 0
|
|
fi
|
|
|
|
echo "Applying metadata_agregator schema..."
|
|
${psql} -f ${./sql/metadata/001_schema.sql}
|
|
|
|
touch "$MARKER"
|
|
echo "metadata_agregator schema initialized"
|
|
'';
|
|
};
|
|
|
|
# Schema init for music_agregator
|
|
systemd.services.agregators-schema-music = {
|
|
description = "Initialize music_agregator database schema";
|
|
after = [ "network.target" ] ++ lib.optionals isLocal [ "postgresql.service" ];
|
|
requires = lib.optionals isLocal [ "postgresql.service" ];
|
|
before = [ "music-agregator.service" ];
|
|
requiredBy = [ "music-agregator.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
# Run as the db user for peer auth on local postgres
|
|
User = if isLocal then pgCfg.musicDatabase.user else cfg.user;
|
|
};
|
|
|
|
script =
|
|
let
|
|
psql = mkPsql pgCfg.musicDatabase;
|
|
passwordExport = mkPasswordExport pgCfg.musicDatabase;
|
|
in
|
|
''
|
|
${passwordExport}
|
|
MARKER="${cfg.stateDir}/postgres/.schema-music-initialized"
|
|
|
|
# Wait for database
|
|
for i in $(seq 1 30); do
|
|
if ${psql} -c "SELECT 1" >/dev/null 2>&1; then break; fi
|
|
echo "Waiting for music database... ($i/30)"
|
|
sleep 1
|
|
done
|
|
|
|
if [ -f "$MARKER" ]; then
|
|
echo "music_agregator schema already initialized"
|
|
exit 0
|
|
fi
|
|
|
|
echo "Applying music_agregator schema..."
|
|
${psql} -f ${./sql/music/001_river.sql}
|
|
${psql} -f ${./sql/music/002_schema.sql}
|
|
${psql} -f ${./sql/music/003_event_bus.sql}
|
|
|
|
touch "$MARKER"
|
|
echo "music_agregator schema initialized"
|
|
'';
|
|
};
|
|
};
|
|
}
|