This repository has no description
0

Configure Feed

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

machines.neoprism: replace pocket-id with zitadel on id.lassul.us

Zitadel 2.71 from nixpkgs as a federated IdP with open self-registration:
registration, external IdPs and passkeys allowed in the default login
policy, first instance "lassul.us" with admin user lass (lass@lassul.us).
The masterkey and the admin's initial password come from the zitadel clan
vars generator, the password via an extra steps file so it never hits the
store.

Postgres via peer auth over the local socket. ensureDatabases/ensureUsers
create the database and owner role, so the zitadel role gets no
CREATEROLE/CREATEDB (on PG15 CREATEROLE is close to superuser). That needs
`init zitadel` + start-from-setup instead of the module's start-from-init,
whose init always tries CREATE USER/CREATE DATABASE and fails on the
permission check before the "already exists" check.

nginx terminates TLS and talks h2c to Zitadel via grpc_pass, as Zitadel's
reverse-proxy docs require for its gRPC APIs. Machine identification by
hostname, since the default private-IP/GCP-webhook probes don't fit
neoprism.

The database is pg_dumped by the borg preHook; gatus checks
/debug/healthz. Zitadel 2.71 does not work with PostgreSQL 18 (migration
34_add_cache_schema creates unlogged partitioned tables), neoprism runs 15.

+139 -47
-45
configs/pocket-id.nix
··· 1 - let 2 - domain = "id.lassul.us"; 3 - port = 1411; 4 - in 5 - { 6 - config, 7 - pkgs, 8 - ... 9 - }: 10 - { 11 - # Pocket ID: passkey-based OIDC provider. Serves as the SSO IdP for other 12 - # services on this host (e.g. HedgeDoc login at pad.lassul.us). First-run 13 - # bootstrap (admin user + OIDC client registration) happens through its web 14 - # UI at https://id.lassul.us. 15 - 16 - # v2 requires ENCRYPTION_KEY (>=16 bytes) to encrypt keys at rest. 17 - clan.core.vars.generators.pocket-id = { 18 - files."encryption_key" = { }; 19 - runtimeInputs = [ pkgs.openssl ]; 20 - script = '' 21 - openssl rand -hex 32 | tr -d '\n' > "$out/encryption_key" 22 - ''; 23 - }; 24 - 25 - services.pocket-id = { 26 - enable = true; 27 - credentials.ENCRYPTION_KEY = config.clan.core.vars.generators.pocket-id.files."encryption_key".path; 28 - settings = { 29 - APP_URL = "https://${domain}"; 30 - TRUST_PROXY = true; 31 - ANALYTICS_DISABLED = true; 32 - HOST = "127.0.0.1"; 33 - PORT = port; 34 - }; 35 - }; 36 - 37 - services.nginx.virtualHosts.${domain} = { 38 - enableACME = true; 39 - forceSSL = true; 40 - locations."/" = { 41 - proxyPass = "http://127.0.0.1:${toString port}"; 42 - proxyWebsockets = true; 43 - }; 44 - }; 45 - }
+130
configs/zitadel.nix
··· 1 + let 2 + domain = "id.lassul.us"; 3 + port = 1411; 4 + in 5 + { 6 + config, 7 + lib, 8 + pkgs, 9 + ... 10 + }: 11 + let 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 + }; 25 + in 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 + }
+1
configs/monitoring/checks.nix
··· 49 49 (https "lassul.us" "https://lassul.us/") 50 50 (https "matrix" "https://matrix.lassul.us/_matrix/client/versions") 51 51 (https "pad" "https://pad.lassul.us/") 52 + (https "id" "https://id.lassul.us/debug/healthz") 52 53 (https "paint" "https://paint.lassul.us/") 53 54 (https "ntfy" "https://ntfy.lassul.us/v1/health") 54 55 (https "cache" "https://cache.lassul.us/nix-cache-info")
+6
machines/neoprism/backup.nix
··· 80 80 "$db" | ${pkgs.gzip}/bin/gzip > "/var/backup/postgresql/$db.sql.gz" 81 81 done 82 82 83 + # Dump zitadel (id.lassul.us) PostgreSQL database: users, passkeys, 84 + # IdP links, OIDC apps. Its secrets are encrypted with the masterkey 85 + # from clan vars. 86 + ${pkgs.sudo}/bin/sudo -u postgres ${config.services.postgresql.package}/bin/pg_dump \ 87 + zitadel | ${pkgs.gzip}/bin/gzip > /var/backup/postgresql/zitadel.sql.gz 88 + 83 89 # Dump hedgedoc SQLite database safely. It uses a rollback journal and 84 90 # writes often during the day, so wait for its locks instead of failing. 85 91 ${pkgs.sqlite}/bin/sqlite3 -cmd ".timeout 60000" /var/lib/hedgedoc/db.sqlite ".backup /var/backup/hedgedoc.sqlite"
+2 -2
machines/neoprism/config.nix
··· 101 101 # n2n supernode (n2n.lassul.us) 102 102 ../../configs/n2n-supernode.nix 103 103 104 - # pocket-id SSO IdP (id.lassul.us) 105 - ../../configs/pocket-id.nix 104 + # zitadel SSO IdP (id.lassul.us) 105 + ../../configs/zitadel.nix 106 106 107 107 # radicle public seed (radicle.lassul.us) 108 108 ../../configs/radicle.lassul.us.nix