This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / configs / zitadel.nix
8.1 kB 239 lines
1let 2 domain = "id.lassul.us"; 3 port = 1411; 4in 5{ 6 self, 7 config, 8 lib, 9 pkgs, 10 ... 11}: 12let 13 cfg = config.services.zitadel; 14 vars = config.clan.core.vars.generators.zitadel.files; 15 yaml = pkgs.formats.yaml { }; 16 configFile = yaml.generate "config.yaml" cfg.settings; 17 args = lib.cli.toCommandLineShellGNU { } { 18 config = [ configFile ]; 19 steps = [ 20 (yaml.generate "steps.yaml" cfg.steps) 21 vars."steps.yaml".path 22 ]; 23 masterkeyFile = cfg.masterKeyFile; 24 inherit (cfg) tlsMode; 25 }; 26 idpCredentials = name: url: { 27 prompts.client_id = { 28 description = '' 29 ${name} login on ${domain}: client id of an OAuth app (${url}) 30 with the callback URL 31 https://${domain}/ui/login/login/externalidp/callback 32 Leave empty to not offer ${name} logins. 33 ''; 34 persist = true; 35 }; 36 prompts.client_secret = { 37 description = "${name} login on ${domain}: client secret of that OAuth app"; 38 type = "hidden"; 39 persist = true; 40 }; 41 }; 42in 43{ 44 # Zitadel: identity provider for lassul.us services (OIDC). Anyone can 45 # register; users sign in with a passkey or an external login (GitHub, 46 # Google, their own OpenID provider via byoidc) linked to their account. The 47 # login policy, these logins and the services' projects and OIDC apps come from 48 # lass.zitadel (modules/zitadel); the zitadel-reconcile service applies 49 # them. Further IdPs and the grants of "managed" projects are done in the 50 # console at https://id.lassul.us/ui/console. 51 # 52 # Example projects for services on other hosts, once they speak OIDC (the 53 # client secret ends up in /var/lib/zitadel-apps/<project>-<app>.env here): 54 # lass.zitadel.projects.assist = { 55 # access = "declarative"; 56 # users = [ "lass@lassul.us" ]; 57 # apps.web.redirectUris = [ "https://assist.lassul.us/<callback>" ]; 58 # }; 59 # lass.zitadel.projects.flix = { 60 # access = "managed"; 61 # # jellyfin-plugin-sso with a provider named id.lassul.us 62 # apps.jellyfin.redirectUris = [ "https://flix.lassul.us/sso/OID/redirect/id.lassul.us" ]; 63 # }; 64 65 clan.core.vars.generators.zitadel = { 66 files."masterkey".owner = "zitadel"; 67 # initial password of the admin user lass, changed at first login 68 files."admin-password".deploy = false; 69 files."steps.yaml".owner = "zitadel"; 70 runtimeInputs = [ 71 pkgs.jq 72 pkgs.openssl 73 pkgs.pwgen 74 ]; 75 script = '' 76 openssl rand -hex 16 | tr -d '\n' > "$out/masterkey" 77 pwgen -s -c -n -y 32 1 | tr -d '\n' > "$out/admin-password" 78 jq -n --rawfile pw "$out/admin-password" \ 79 '{FirstInstance: {Org: {Human: {Password: $pw}}}}' > "$out/steps.yaml" 80 ''; 81 }; 82 83 services.zitadel = { 84 enable = true; 85 package = self.packages.${pkgs.stdenv.hostPlatform.system}.zitadel; 86 masterKeyFile = vars."masterkey".path; 87 settings = { 88 # daily usage report to zitadel.com, on by default 89 ServicePing.Enabled = false; 90 # a new instance would otherwise send logins to the separate Login V2 app, which isn't deployed 91 DefaultInstance.Features.LoginV2.Required = false; 92 Port = port; 93 ExternalDomain = domain; 94 ExternalPort = 443; 95 ExternalSecure = true; 96 Machine.Identification = { 97 PrivateIp.Enabled = false; 98 Hostname.Enabled = true; 99 Webhook.Enabled = false; 100 }; 101 Database.postgres = { 102 Host = "/run/postgresql"; 103 Port = 5432; 104 Database = "zitadel"; 105 User.Username = "zitadel"; 106 Admin.Username = "zitadel"; 107 Admin.ExistingDatabase = "zitadel"; 108 }; 109 }; 110 steps.FirstInstance = { 111 InstanceName = "lassul.us"; 112 Org = { 113 Name = "lassul.us"; 114 Human = { 115 UserName = "lass"; 116 FirstName = "lass"; 117 LastName = "lassulus"; 118 Email = { 119 Address = "lass@lassul.us"; 120 Verified = true; 121 }; 122 PasswordChangeRequired = true; 123 }; 124 }; 125 }; 126 }; 127 128 # zitadel-invite (tools/zitadel-invite) as root, with the token of 129 # zitadel-reconcile 130 users.users.root.packages = [ 131 (pkgs.writeShellScriptBin "zitadel-invite" '' 132 export ZITADEL_URL=''${ZITADEL_URL:-https://${domain}} 133 export ZITADEL_TOKEN_FILE=''${ZITADEL_TOKEN_FILE:-${cfg.steps.FirstInstance.PatPath}} 134 exec ${lib.getExe self.packages.${pkgs.stdenv.hostPlatform.system}.zitadel-invite} "$@" 135 '') 136 ]; 137 138 lass.zitadel.loginPolicy = { 139 allowRegister = true; 140 allowExternalIdp = true; 141 allowUsernamePassword = true; 142 passwordlessType = "PASSWORDLESS_TYPE_ALLOWED"; 143 }; 144 # Verification codes and password resets. Zitadel asks every user whose 145 # address is unverified (registration, byoidc) to verify it before the 146 # login finishes. Sent as the send-only account id@lassul.us of the 147 # mailserver here (configs/mailserver.nix), which DKIM-signs it. 148 lass.zitadel.smtp = { 149 senderAddress = "id@lassul.us"; 150 senderName = "lassul.us ID"; 151 host = "mail.lassul.us:465"; 152 tls = true; 153 user = "id@lassul.us"; 154 passwordFile = config.clan.core.vars.generators.mailserver-id.files."password".path; 155 }; 156 lass.zitadel.idps = { 157 GitHub = { 158 type = "github"; 159 clientIdFile = config.clan.core.vars.generators.zitadel-github.files.client_id.path; 160 clientSecretFile = config.clan.core.vars.generators.zitadel-github.files.client_secret.path; 161 scopes = [ 162 "read:user" 163 "user:email" 164 ]; 165 }; 166 Google = { 167 type = "google"; 168 clientIdFile = config.clan.core.vars.generators.zitadel-google.files.client_id.path; 169 clientSecretFile = config.clan.core.vars.generators.zitadel-google.files.client_secret.path; 170 scopes = [ 171 "openid" 172 "profile" 173 "email" 174 ]; 175 }; 176 # byoidc (configs/byoidc.nix): sign in with any OpenID provider. Anyone 177 # can run one that claims any email address, so these accounts are never 178 # linked to existing ones. 179 byoidc = { 180 type = "oidc"; 181 name = "Your own provider"; 182 issuer = "https://byoidc.lassul.us"; 183 clientId = "zitadel"; 184 clientSecretFile = config.clan.core.vars.generators.byoidc.files."client-secret".path; 185 providerOptions = { 186 isLinkingAllowed = false; 187 isCreationAllowed = true; 188 isAutoCreation = true; 189 isAutoUpdate = true; 190 }; 191 }; 192 }; 193 clan.core.vars.generators.zitadel-github = 194 idpCredentials "GitHub" "https://github.com/settings/applications/new"; 195 clan.core.vars.generators.zitadel-google = 196 idpCredentials "Google" "https://console.cloud.google.com/apis/credentials"; 197 198 # The database and its owner come from ensureDatabases/ensureUsers, so the 199 # zitadel role needs no CREATEROLE/CREATEDB. `zitadel init zitadel` creates 200 # only the schemas inside it; start-from-setup then skips the role and 201 # database creation that start-from-init would attempt. 202 systemd.services.zitadel = { 203 after = [ "postgresql.target" ]; 204 requires = [ "postgresql.target" ]; 205 serviceConfig = { 206 ExecStartPre = "${lib.getExe' cfg.package "zitadel"} init zitadel --config ${configFile}"; 207 ExecStart = lib.mkForce "${lib.getExe' cfg.package "zitadel"} start-from-setup ${args}"; 208 }; 209 }; 210 211 services.postgresql = { 212 enable = true; 213 ensureDatabases = [ "zitadel" ]; 214 ensureUsers = [ 215 { 216 name = "zitadel"; 217 ensureDBOwnership = true; 218 } 219 ]; 220 }; 221 222 # users, passkeys, IdP links, OIDC apps; its secrets are encrypted with the 223 # masterkey from clan vars 224 lass.backup.state.zitadel.preBackup = '' 225 mkdir -p /var/backup/postgresql 226 ${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dump \ 227 zitadel | ${pkgs.gzip}/bin/gzip > /var/backup/postgresql/zitadel.sql.gz 228 ''; 229 230 # Zitadel serves the console, OIDC and its gRPC APIs on one port over h2c. 231 services.nginx.virtualHosts.${domain} = { 232 enableACME = true; 233 forceSSL = true; 234 locations."/".extraConfig = '' 235 grpc_pass grpc://127.0.0.1:${toString port}; 236 grpc_set_header Host $host; 237 ''; 238 }; 239}