{ lib, pkgs, ... }: let inherit (builtins) readFile; inherit (lib) mkOption types; in { _class = "clan.service"; manifest = { name = "amarth-services/zitadel"; description = "Zitadel service module"; categories = [ "System" "Identity" "IAM" ]; readme = readFile ./README.md; }; #============================================================================================================== # Controller configuration #============================================================================================================== roles.controller = { description = "A controller that manages the instance"; interface = { options = { hostName = mkOption { type = types.str; example = "auth.example.com"; description = '' The domain at which zitadel will be hosted ''; }; displayName = mkOption { type = types.str; example = "My awesome org"; description = '' The Name of the zitadel organisation ''; }; emergencyAccessPublicKey = mkOption { type = types.str; example = "ssh-ed25519 ..."; description = '' The public key with which you want access to ''; }; }; }; perInstance = { instanceName, settings, machine, roles, ... }: { nixosModule = { config, pkgs, inputs, system, ... }: let terraform = inputs.terranix.lib.terranixConfiguration { inherit pkgs system; modules = [ ({ config, ... }: { config = { terraform.required_providers.zitadel = { source = "zitadel/zitadel"; version = "2.2.0"; }; provider.zitadel = { domain = "localhost"; insecure = "true"; port = "8080"; jwt_profile_file = "local-token"; }; resource.zitadel_human_user.default = { org_id = ""; }; }; }) ]; }; valuesYamlKey = "amarth/service/zitadel-${instanceName}-values.yml"; in { assertions = let inherit (lib) attrValues any; in [ { assertion = config.services.k3s.enable == true; # config.clan.inventory.instances # |> attrValues # |> any ({ module, ... }: module.name == "k3s"); message = "This module requires k3s in order to run"; } ]; clan.core.vars.generators.zitadel = { share = false; files.masterKey = { deploy = true; secret = true; owner = "zitadel"; group = "zitadel"; mode = "0400"; }; files.initialAdminPassword = { deploy = false; secret = false; }; files.emailPassword = { deploy = false; secret = true; }; runtimeInputs = with pkgs; [ pwgen ]; script = '' pwgen 50 1 > "$out/initialAdminPassword" # https://zitadel.com/docs/self-hosting/manage/configure#masterkey # The master key has to be 32 bytes head -c 32 /dev/urandom > "$out/masterKey" touch $out/emailPassword ''; }; services.k3s.autoDeployCharts.zitadel = { name = "zitadel"; repo = "https://charts.zitadel.com"; version = "9.12.3"; hash = "sha256-eSpqy2vK1tJp0Ci20R7+zGLfUDKPuKOz1iWyvawqEpc="; targetNamespace = "zitadel-system"; createNamespace = true; values = config.sops.templates.${valuesYamlKey}.path; }; sops = { templates.${valuesYamlKey}.content = '' zitadel: masterKey: ${config.sops.placeholder."vars/zitadel/masterKey"} ''; }; # services.zitadel = { # enable = true; # openFirewall = true; # masterKeyFile = config.clan.core.vars.generators.zitadel.files.masterKey.path; # tlsMode = "external"; # settings = { # Port = 9092; # ExternalDomain = settings.hostName; # ExternalPort = 443; # ExternalSecure = true; # Metrics.Type = "otel"; # Tracing.Type = "otel"; # Telemetry.Enabled = true; # SystemDefaults = { # PasswordHasher.Hasher.Algorithm = "argon2id"; # SecretHasher.Hasher.Algorithm = "argon2id"; # }; # DefaultInstance = { # PasswordComplexityPolicy = { # MinLength = 20; # HasLowercase = false; # HasUppercase = false; # HasNumber = false; # HasSymbol = false; # }; # LoginPolicy = { # AllowRegister = false; # ForceMFA = true; # }; # LockoutPolicy = { # MaxPasswordAttempts = 5; # MaxOTPAttempts = 10; # }; # SMTPConfiguration = { # SMTP = { # Host = "black-mail.nl:587"; # User = "info@amarth.cloud"; # Password = ""; #config.clan.core.vars.generators.zitadel.files.emailPassword.value; # }; # FromName = "Amarth Zitadel"; # }; # }; # Database.postgres = { # Host = "localhost"; # # Zitadel will report error if port is not set # Port = 5432; # Database = "zitadel"; # User = { # Username = "zitadel"; # SSL.Mode = "disable"; # }; # Admin = { # Username = "postgres"; # SSL.Mode = "disable"; # }; # }; # Machine.Identification = { # PrivateIp.Enabled = true; # # In the docs this uses a google service. I want a self hosted one # # TODO :: Figure out how to self-host webhooks, if I want them at all # Webhook.Enabled = false; # }; # SystemAPIUsers = { # emergencyAccess = { # # Path = settings.emergencyAccessPublicKey; # KeyData = settings.emergencyAccessPublicKey; # # This is the default value # # Memberships = [ # # { MemberType = "System"; Roles = [ "SYSTEM_OWNER" ]; } # # ]; # }; # }; # }; # steps.FirstInstance = { # InstanceName = settings.hostName; # Org = { # Name = settings.displayName; # Human = { # UserName = "chris"; # FirstName = "Chris"; # LastName = "Kruining"; # Email = { # Address = "chris@kruining.eu"; # Verified = true; # }; # Password = config.clan.core.vars.generators.zitadel.files.initialAdminPassword.value; # }; # }; # }; # }; # services.postgresql = { # enable = true; # authentication = '' # # Generated file, do not edit! # # TYPE DATABASE USER ADDRESS METHOD # local all all trust # host all all 127.0.0.1/32 trust # host all all ::1/128 trust # ''; # ensureDatabases = [ "zitadel" ]; # ensureUsers = [ # { # name = "zitadel"; # ensureDBOwnership = true; # } # ]; # }; }; }; }; #============================================================================================================== # Peer configuration #============================================================================================================== roles.peer = { description = "A peer"; interface = { options = {}; }; perInstance = { instanceName, settings, machine, roles, ... }: { nixosModule = {}; }; }; }