nixpkgs/nixos/modules/services/web-apps/windmill.nix

197 lines
5.9 KiB
Nix

{ 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;
};
};
};
}