{ config, pkgs, lib, ... }: let cfg = config.services.windmill; in { options.services.windmill = { enable = lib.mkEnableOption "windmill service"; serverPort = lib.mkOption { type = lib.types.port; default = 8001; description = "Port the windmill server listens on."; }; lspPort = lib.mkOption { type = lib.types.port; default = 3001; description = "Port the windmill lsp listens on."; }; database = { name = lib.mkOption { type = lib.types.str; # the simplest database setup is to have the database named like the user. default = "windmill"; description = "Database name."; }; user = lib.mkOption { type = lib.types.str; # the simplest database setup is to have the database user like the name. default = "windmill"; description = "Database user."; }; url = lib.mkOption { type = lib.types.str; default = "postgres://${config.services.windmill.database.name}?host=/var/run/postgresql"; defaultText = lib.literalExpression '' "postgres://\$\{config.services.windmill.database.name}?host=/var/run/postgresql"; ''; description = "Database url. Note that any secret here would be world-readable. Use `services.windmill.database.urlPath` unstead to include secrets in the url."; }; urlPath = lib.mkOption { type = lib.types.nullOr lib.types.path; description = '' Path to the file containing the database url windmill should connect to. This is not deducted from database user and name as it might contain a secret ''; default = null; example = "config.age.secrets.DATABASE_URL_FILE.path"; }; createLocally = lib.mkOption { type = lib.types.bool; default = true; description = "Whether to create a local database automatically."; }; }; baseUrl = lib.mkOption { type = lib.types.str; default = "https://localhost:${toString config.services.windmill.serverPort}"; defaultText = lib.literalExpression '' "https://localhost:\$\{toString config.services.windmill.serverPort}"; ''; description = '' The base url that windmill will be served on. ''; example = "https://windmill.example.com"; }; logLevel = lib.mkOption { type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ]; default = "info"; description = "Log level"; }; }; config = lib.mkIf cfg.enable { services.postgresql = lib.optionalAttrs (cfg.database.createLocally) { enable = lib.mkDefault true; ensureDatabases = [ cfg.database.name ]; ensureUsers = [ { name = cfg.database.user; ensureDBOwnership = true; } ]; }; systemd.services = let useUrlPath = (cfg.database.urlPath != null); serviceConfig = { DynamicUser = true; # using the same user to simplify db connection User = cfg.database.user; ExecStart = "${pkgs.windmill}/bin/windmill"; Restart = "always"; } // lib.optionalAttrs useUrlPath { LoadCredential = [ "DATABASE_URL_FILE:${cfg.database.urlPath}" ]; }; db_url_envs = lib.optionalAttrs useUrlPath { DATABASE_URL_FILE = "%d/DATABASE_URL_FILE"; } // lib.optionalAttrs (!useUrlPath) { DATABASE_URL = cfg.database.url; }; in { # coming from https://github.com/windmill-labs/windmill/blob/main/init-db-as-superuser.sql # modified to not grant priviledges on all tables # create role windmill_user and windmill_admin only if they don't exist postgresql.postStart = lib.mkIf cfg.database.createLocally (lib.mkAfter '' $PSQL -tA <<"EOF" DO $$ BEGIN IF NOT EXISTS ( SELECT FROM pg_catalog.pg_roles WHERE rolname = 'windmill_user' ) THEN CREATE ROLE windmill_user; GRANT ALL PRIVILEGES ON DATABASE ${cfg.database.name} TO windmill_user; ELSE RAISE NOTICE 'Role "windmill_user" already exists. Skipping.'; END IF; IF NOT EXISTS ( SELECT FROM pg_catalog.pg_roles WHERE rolname = 'windmill_admin' ) THEN CREATE ROLE windmill_admin WITH BYPASSRLS; GRANT windmill_user TO windmill_admin; ELSE RAISE NOTICE 'Role "windmill_admin" already exists. Skipping.'; END IF; GRANT windmill_admin TO windmill; END $$; EOF ''); windmill-server = { description = "Windmill server"; after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; wantedBy = [ "multi-user.target" ]; serviceConfig = serviceConfig // { StateDirectory = "windmill";}; environment = { PORT = builtins.toString cfg.serverPort; WM_BASE_URL = cfg.baseUrl; RUST_LOG = cfg.logLevel; MODE = "server"; } // db_url_envs; }; windmill-worker = { description = "Windmill worker"; after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; wantedBy = [ "multi-user.target" ]; serviceConfig = serviceConfig // { StateDirectory = "windmill-worker";}; environment = { WM_BASE_URL = cfg.baseUrl; RUST_LOG = cfg.logLevel; MODE = "worker"; WORKER_GROUP = "default"; KEEP_JOB_DIR = "false"; } // db_url_envs; }; windmill-worker-native = { description = "Windmill worker native"; after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; wantedBy = [ "multi-user.target" ]; serviceConfig = serviceConfig // { StateDirectory = "windmill-worker-native";}; environment = { WM_BASE_URL = cfg.baseUrl; RUST_LOG = cfg.logLevel; MODE = "worker"; WORKER_GROUP = "native"; } // db_url_envs; }; }; }; }