{ description = "Movie Map - Visualize media origin countries from Radarr/Sonarr/Lidarr"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; outputs = { self, nixpkgs }: let system = "x86_64-linux"; pkgs = import nixpkgs { inherit system; }; # Python dependencies pythonEnv = pkgs.python3.withPackages (ps: with ps; [ fastapi uvicorn psycopg psycopg-pool alembic sqlalchemy httpx pydantic pydantic-settings python-multipart ]); # Node.js for frontend nodejs = pkgs.nodejs_20; # Frontend build frontend = pkgs.buildNpmPackage { name = "moviemap-frontend"; src = ./frontend; npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Update after first build buildPhase = '' npm run build ''; installPhase = '' mkdir -p $out cp -r dist/* $out/ ''; }; # Backend package backend = pkgs.stdenv.mkDerivation { name = "moviemap-backend"; src = ./backend; buildInputs = [ pythonEnv ]; installPhase = '' mkdir -p $out/backend cp -r . $out/backend/ chmod +x $out/backend/run.sh # Make Python scripts executable find $out/backend -name "*.py" -exec chmod +x {} \; ''; }; # Combined package app = pkgs.stdenv.mkDerivation { name = "moviemap"; buildInputs = [ backend frontend ]; buildPhase = "true"; installPhase = '' mkdir -p $out/backend cp -r ${backend}/backend/* $out/backend/ # Copy frontend dist to backend for serving mkdir -p $out/backend/frontend/dist cp -r ${frontend}/* $out/backend/frontend/dist/ ''; }; in { packages.${system} = { default = app; backend = backend; frontend = frontend; }; devShells.${system}.default = pkgs.mkShell { buildInputs = [ pythonEnv nodejs pkgs.nodePackages.npm pkgs.postgresql pkgs.alejandra # Nix formatter ]; shellHook = '' echo "Movie Map development environment" echo "Python: $(python --version)" echo "Node: $(node --version)" ''; }; nixosModules.default = { config, lib, pkgs, ... }: with lib; let cfg = config.services.moviemap; appPackage = self.packages.x86_64-linux.default; # Check if a value is a file path (starts with /) isPath = v: lib.hasPrefix "/" (toString v); # Build environment variables - use file paths directly for secrets # The run.sh script will read from files at runtime envVars = [ "PORT=${toString cfg.port}" "HOST=${cfg.host}" "POSTGRES_SOCKET_PATH=${cfg.postgresSocketPath}" ] ++ [ # API keys - if path, pass as-is; if string, pass directly (if isPath cfg.sonarrApiKey then "SONARR_API_KEY_FILE=${toString cfg.sonarrApiKey}" else "SONARR_API_KEY=${toString cfg.sonarrApiKey}") (if isPath cfg.radarrApiKey then "RADARR_API_KEY_FILE=${toString cfg.radarrApiKey}" else "RADARR_API_KEY=${toString cfg.radarrApiKey}") (if isPath cfg.lidarrApiKey then "LIDARR_API_KEY_FILE=${toString cfg.lidarrApiKey}" else "LIDARR_API_KEY=${toString cfg.lidarrApiKey}") ] ++ lib.optional (cfg.adminToken != null) (if isPath cfg.adminToken then "MOVIEMAP_ADMIN_TOKEN_FILE=${toString cfg.adminToken}" else "MOVIEMAP_ADMIN_TOKEN=${toString cfg.adminToken}"); in { options.services.moviemap = { enable = mkEnableOption "Movie Map service"; port = mkOption { type = types.int; default = 8080; description = "Port to bind the backend server"; }; host = mkOption { type = types.str; default = "0.0.0.0"; description = "Host address to bind the server (0.0.0.0 for all interfaces, 127.0.0.1 for localhost only)"; }; postgresSocketPath = mkOption { type = types.str; default = "/run/postgresql"; description = "PostgreSQL socket directory"; }; sonarrApiKey = mkOption { type = types.either types.str types.path; description = "Sonarr API key (string or path to file, e.g., /run/secrets/sonarr-api-key)"; }; radarrApiKey = mkOption { type = types.either types.str types.path; description = "Radarr API key (string or path to file, e.g., /run/secrets/radarr-api-key)"; }; lidarrApiKey = mkOption { type = types.either types.str types.path; description = "Lidarr API key (string or path to file, e.g., /run/secrets/lidarr-api-key)"; }; adminToken = mkOption { type = types.nullOr (types.either types.str types.path); default = null; description = "Optional admin token for sync endpoint (string or path to file)"; }; }; config = mkIf cfg.enable { systemd.services.moviemap-backend = { description = "Movie Map Backend"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "postgresql.service" ]; serviceConfig = { Type = "simple"; ExecStart = "${appPackage}/backend/run.sh"; Restart = "always"; RestartSec = "10s"; Environment = envVars; User = "moviemap"; Group = "moviemap"; }; }; users.users.moviemap = { isSystemUser = true; group = "moviemap"; description = "Movie Map service user"; }; users.groups.moviemap = {}; }; }; }; }