at 25.11-pre 6.7 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.services.windmill; 10in 11{ 12 options.services.windmill = { 13 enable = lib.mkEnableOption "windmill service"; 14 15 serverPort = lib.mkOption { 16 type = lib.types.port; 17 default = 8001; 18 description = "Port the windmill server listens on."; 19 }; 20 21 lspPort = lib.mkOption { 22 type = lib.types.port; 23 default = 3001; 24 description = "Port the windmill lsp listens on."; 25 }; 26 27 database = { 28 name = lib.mkOption { 29 type = lib.types.str; 30 # the simplest database setup is to have the database named like the user. 31 default = "windmill"; 32 description = "Database name."; 33 }; 34 35 user = lib.mkOption { 36 type = lib.types.str; 37 # the simplest database setup is to have the database user like the name. 38 default = "windmill"; 39 description = "Database user."; 40 }; 41 42 url = lib.mkOption { 43 type = lib.types.str; 44 default = "postgres://${config.services.windmill.database.name}?host=/var/run/postgresql"; 45 defaultText = lib.literalExpression '' 46 "postgres://\$\{config.services.windmill.database.name}?host=/var/run/postgresql"; 47 ''; 48 description = "Database url. Note that any secret here would be world-readable. Use `services.windmill.database.urlPath` unstead to include secrets in the url."; 49 }; 50 51 urlPath = lib.mkOption { 52 type = lib.types.nullOr lib.types.path; 53 description = '' 54 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 55 ''; 56 default = null; 57 example = "config.age.secrets.DATABASE_URL_FILE.path"; 58 }; 59 60 createLocally = lib.mkOption { 61 type = lib.types.bool; 62 default = true; 63 description = "Whether to create a local database automatically."; 64 }; 65 }; 66 67 baseUrl = lib.mkOption { 68 type = lib.types.str; 69 default = "https://localhost:${toString config.services.windmill.serverPort}"; 70 defaultText = lib.literalExpression '' 71 "https://localhost:\$\{toString config.services.windmill.serverPort}"; 72 ''; 73 description = '' 74 The base url that windmill will be served on. 75 ''; 76 example = "https://windmill.example.com"; 77 }; 78 79 logLevel = lib.mkOption { 80 type = lib.types.enum [ 81 "error" 82 "warn" 83 "info" 84 "debug" 85 "trace" 86 ]; 87 default = "info"; 88 description = "Log level"; 89 }; 90 }; 91 92 config = lib.mkIf cfg.enable { 93 94 services.postgresql = lib.optionalAttrs (cfg.database.createLocally) { 95 enable = lib.mkDefault true; 96 97 ensureDatabases = [ cfg.database.name ]; 98 ensureUsers = [ 99 { 100 name = cfg.database.user; 101 ensureDBOwnership = true; 102 } 103 ]; 104 105 }; 106 107 systemd.services = 108 let 109 useUrlPath = (cfg.database.urlPath != null); 110 serviceConfig = 111 { 112 DynamicUser = true; 113 # using the same user to simplify db connection 114 User = cfg.database.user; 115 ExecStart = "${pkgs.windmill}/bin/windmill"; 116 117 Restart = "always"; 118 } 119 // lib.optionalAttrs useUrlPath { 120 LoadCredential = [ 121 "DATABASE_URL_FILE:${cfg.database.urlPath}" 122 ]; 123 }; 124 db_url_envs = 125 lib.optionalAttrs useUrlPath { 126 DATABASE_URL_FILE = "%d/DATABASE_URL_FILE"; 127 } 128 // lib.optionalAttrs (!useUrlPath) { 129 DATABASE_URL = cfg.database.url; 130 }; 131 in 132 { 133 134 # coming from https://github.com/windmill-labs/windmill/blob/main/init-db-as-superuser.sql 135 # modified to not grant priviledges on all tables 136 # create role windmill_user and windmill_admin only if they don't exist 137 postgresql.postStart = lib.mkIf cfg.database.createLocally ( 138 lib.mkAfter '' 139 $PSQL -tA <<"EOF" 140 DO $$ 141 BEGIN 142 IF NOT EXISTS ( 143 SELECT FROM pg_catalog.pg_roles 144 WHERE rolname = 'windmill_user' 145 ) THEN 146 CREATE ROLE windmill_user; 147 GRANT ALL PRIVILEGES ON DATABASE ${cfg.database.name} TO windmill_user; 148 ELSE 149 RAISE NOTICE 'Role "windmill_user" already exists. Skipping.'; 150 END IF; 151 IF NOT EXISTS ( 152 SELECT FROM pg_catalog.pg_roles 153 WHERE rolname = 'windmill_admin' 154 ) THEN 155 CREATE ROLE windmill_admin WITH BYPASSRLS; 156 GRANT windmill_user TO windmill_admin; 157 ELSE 158 RAISE NOTICE 'Role "windmill_admin" already exists. Skipping.'; 159 END IF; 160 GRANT windmill_admin TO windmill; 161 END 162 $$; 163 EOF 164 '' 165 ); 166 167 windmill-server = { 168 description = "Windmill server"; 169 after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; 170 wantedBy = [ "multi-user.target" ]; 171 172 serviceConfig = serviceConfig // { 173 StateDirectory = "windmill"; 174 }; 175 176 environment = { 177 PORT = builtins.toString cfg.serverPort; 178 WM_BASE_URL = cfg.baseUrl; 179 RUST_LOG = cfg.logLevel; 180 MODE = "server"; 181 } // db_url_envs; 182 }; 183 184 windmill-worker = { 185 description = "Windmill worker"; 186 after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; 187 wantedBy = [ "multi-user.target" ]; 188 189 serviceConfig = serviceConfig // { 190 StateDirectory = "windmill-worker"; 191 }; 192 193 environment = { 194 WM_BASE_URL = cfg.baseUrl; 195 RUST_LOG = cfg.logLevel; 196 MODE = "worker"; 197 WORKER_GROUP = "default"; 198 KEEP_JOB_DIR = "false"; 199 } // db_url_envs; 200 }; 201 202 windmill-worker-native = { 203 description = "Windmill worker native"; 204 after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; 205 wantedBy = [ "multi-user.target" ]; 206 207 serviceConfig = serviceConfig // { 208 StateDirectory = "windmill-worker-native"; 209 }; 210 211 environment = { 212 WM_BASE_URL = cfg.baseUrl; 213 RUST_LOG = cfg.logLevel; 214 MODE = "worker"; 215 WORKER_GROUP = "native"; 216 } // db_url_envs; 217 }; 218 }; 219 }; 220}