This repository has no description
3.8 kB
130 lines
1let
2 domain = "id.lassul.us";
3 port = 1411;
4in
5{
6 config,
7 lib,
8 pkgs,
9 ...
10}:
11let
12 cfg = config.services.zitadel;
13 vars = config.clan.core.vars.generators.zitadel.files;
14 yaml = pkgs.formats.yaml { };
15 configFile = yaml.generate "config.yaml" cfg.settings;
16 args = lib.cli.toCommandLineShellGNU { } {
17 config = [ configFile ];
18 steps = [
19 (yaml.generate "steps.yaml" cfg.steps)
20 vars."steps.yaml".path
21 ];
22 masterkeyFile = cfg.masterKeyFile;
23 inherit (cfg) tlsMode;
24 };
25in
26{
27 # Zitadel: identity provider for lassul.us services (OIDC). Anyone can
28 # register; users sign in with a passkey or an external login (GitHub,
29 # Google, other OIDC) linked to their account. The external IdPs and OIDC
30 # apps are managed in the console at https://id.lassul.us/ui/console.
31
32 clan.core.vars.generators.zitadel = {
33 files."masterkey".owner = "zitadel";
34 # initial password of the admin user lass, changed at first login
35 files."admin-password".deploy = false;
36 files."steps.yaml".owner = "zitadel";
37 runtimeInputs = [
38 pkgs.jq
39 pkgs.openssl
40 pkgs.pwgen
41 ];
42 script = ''
43 openssl rand -hex 16 | tr -d '\n' > "$out/masterkey"
44 pwgen -s -c -n -y 32 1 | tr -d '\n' > "$out/admin-password"
45 jq -n --rawfile pw "$out/admin-password" \
46 '{FirstInstance: {Org: {Human: {Password: $pw}}}}' > "$out/steps.yaml"
47 '';
48 };
49
50 services.zitadel = {
51 enable = true;
52 masterKeyFile = vars."masterkey".path;
53 settings = {
54 Port = port;
55 ExternalDomain = domain;
56 ExternalPort = 443;
57 ExternalSecure = true;
58 Machine.Identification = {
59 PrivateIp.Enabled = false;
60 Hostname.Enabled = true;
61 Webhook.Enabled = false;
62 };
63 Database.postgres = {
64 Host = "/run/postgresql";
65 Port = 5432;
66 Database = "zitadel";
67 User.Username = "zitadel";
68 Admin.Username = "zitadel";
69 Admin.ExistingDatabase = "zitadel";
70 };
71 # Applied once, when the first instance is created.
72 DefaultInstance.LoginPolicy = {
73 AllowRegister = true;
74 AllowExternalIDP = true;
75 AllowUsernamePassword = true;
76 PasswordlessType = 1; # passkeys allowed
77 };
78 };
79 steps.FirstInstance = {
80 InstanceName = "lassul.us";
81 Org = {
82 Name = "lassul.us";
83 Human = {
84 UserName = "lass";
85 FirstName = "lass";
86 LastName = "lassulus";
87 Email = {
88 Address = "lass@lassul.us";
89 Verified = true;
90 };
91 PasswordChangeRequired = true;
92 };
93 };
94 };
95 };
96
97 # The database and its owner come from ensureDatabases/ensureUsers, so the
98 # zitadel role needs no CREATEROLE/CREATEDB. `zitadel init zitadel` creates
99 # only the schemas inside it; start-from-setup then skips the role and
100 # database creation that start-from-init would attempt.
101 systemd.services.zitadel = {
102 after = [ "postgresql.target" ];
103 requires = [ "postgresql.target" ];
104 serviceConfig = {
105 ExecStartPre = "${lib.getExe' cfg.package "zitadel"} init zitadel --config ${configFile}";
106 ExecStart = lib.mkForce "${lib.getExe' cfg.package "zitadel"} start-from-setup ${args}";
107 };
108 };
109
110 services.postgresql = {
111 enable = true;
112 ensureDatabases = [ "zitadel" ];
113 ensureUsers = [
114 {
115 name = "zitadel";
116 ensureDBOwnership = true;
117 }
118 ];
119 };
120
121 # Zitadel serves the console, OIDC and its gRPC APIs on one port over h2c.
122 services.nginx.virtualHosts.${domain} = {
123 enableACME = true;
124 forceSSL = true;
125 locations."/".extraConfig = ''
126 grpc_pass grpc://127.0.0.1:${toString port};
127 grpc_set_header Host $host;
128 '';
129 };
130}