{ description = "St-Pierre - MCP Knowledge Server"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: (flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; st-pierre = pkgs.buildGoModule { pname = "st-pierre"; version = "0.1.0"; src = ./.; vendorHash = "sha256-HfaivCA4oIUL5fIVuxNeuqftmShVhniyFpMaEPkEmOA="; }; in { packages.default = st-pierre; devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ go gopls gotools ]; }; } )) // { nixosModules.st-pierre = { config, pkgs, lib, ... }: let cfg = config.services.st-pierre; st-pierre-pkg = self.packages.${pkgs.stdenv.hostPlatform.system}.default; gitHash = if (self ? rev) then builtins.substring 0 8 self.rev else "dirty"; lastUpdated = if (self ? lastModified) then builtins.toString self.lastModified else "0"; in { options.services.st-pierre = { enable = lib.mkEnableOption "St-Pierre knowledge MCP server"; port = lib.mkOption { type = lib.types.port; default = 8080; description = "HTTP port to listen on."; }; qdrantUrl = lib.mkOption { type = lib.types.str; default = "localhost"; description = "Qdrant server hostname."; }; qdrantPort = lib.mkOption { type = lib.types.port; default = 6334; description = "Qdrant gRPC port."; }; qdrantApiKeyFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Path to a file containing the Qdrant API key."; }; qdrantTls = lib.mkOption { type = lib.types.bool; default = false; description = "Enable TLS for Qdrant connection."; }; openaiApiKeyFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Path to a file containing the API key (OpenAI or DeepSeek)."; }; openaiApiKey = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "API key directly. Prefer openaiApiKeyFile for secrets."; }; embeddingBaseUrl = lib.mkOption { type = lib.types.str; default = "https://api.openai.com"; description = "Base URL for the embeddings API. Do not include /v1 suffix; it is appended automatically."; }; embeddingModel = lib.mkOption { type = lib.types.str; default = "text-embedding-3-small"; description = "Embedding model name."; }; tokenFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Path to a file containing the MCP Bearer token."; }; token = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "MCP Bearer token directly. Prefer tokenFile for secrets."; }; sqlitePath = lib.mkOption { type = lib.types.path; default = "/var/lib/st-pierre/st-pierre.sqlite"; description = "Path to the SQLite database file."; }; }; config = lib.mkIf cfg.enable { users.groups.st-pierre = { }; users.users.st-pierre = { createHome = true; isSystemUser = true; home = "/var/lib/st-pierre"; group = "st-pierre"; }; systemd.services.st-pierre = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { User = "st-pierre"; Group = "st-pierre"; Restart = "always"; WorkingDirectory = "/var/lib/st-pierre"; StateDirectory = "st-pierre"; ExecStart = "${st-pierre-pkg}/bin/st-pierre"; EnvironmentFile = lib.optional (cfg.openaiApiKeyFile != null) cfg.openaiApiKeyFile ++ lib.optional (cfg.tokenFile != null) cfg.tokenFile ++ lib.optional (cfg.qdrantApiKeyFile != null) cfg.qdrantApiKeyFile; }; environment = { PORT = builtins.toString cfg.port; QDRANT_URL = cfg.qdrantUrl; QDRANT_PORT = builtins.toString cfg.qdrantPort; QDRANT_TLS = if cfg.qdrantTls then "true" else "false"; EMBEDDING_BASE_URL = cfg.embeddingBaseUrl; EMBEDDING_MODEL = cfg.embeddingModel; SQLITE_PATH = builtins.toString cfg.sqlitePath; GIT_COMMIT = gitHash; BUILD_DATE = lastUpdated; } // lib.optionalAttrs (cfg.openaiApiKey != null) { OPENAI_API_KEY = cfg.openaiApiKey; } // lib.optionalAttrs (cfg.token != null) { MCP_TOKEN = cfg.token; }; }; }; }; }; }