diff --git a/Scripts/authentik-sync-linear-saml.sh b/Scripts/authentik-sync-linear-saml.sh index 5da64ad..2fd1a90 100755 --- a/Scripts/authentik-sync-linear-saml.sh +++ b/Scripts/authentik-sync-linear-saml.sh @@ -294,8 +294,8 @@ existing_application="$( )" if [[ -n "$existing_application" ]]; then - application_pk="existing" - api PATCH "/api/v3/core/applications/${application_slug}/" "$application_payload" >/dev/null + application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')" + api PATCH "/api/v3/core/applications/${application_pk}/" "$application_payload" >/dev/null else create_application_result="$( api_with_status POST "/api/v3/core/applications/" "$application_payload" diff --git a/Scripts/authentik-sync-linear-scim.sh b/Scripts/authentik-sync-linear-scim.sh index 4ef83e4..5d42cca 100644 --- a/Scripts/authentik-sync-linear-scim.sh +++ b/Scripts/authentik-sync-linear-scim.sh @@ -278,7 +278,12 @@ application_payload="$( policy_engine_mode: .policy_engine_mode }' )" -api PATCH "/api/v3/core/applications/${application_slug}/" "$application_payload" >/dev/null +application_pk="$(printf '%s\n' "$application" | jq -r '.pk // empty')" +if [[ -z "$application_pk" ]]; then + echo "error: could not resolve Authentik application primary key for ${application_slug}" >&2 + exit 1 +fi +api PATCH "/api/v3/core/applications/${application_pk}/" "$application_payload" >/dev/null group_pks_json="$(jq -cn --arg owner "$owner_group_pk" --arg admin "$admin_group_pk" --arg guest "$guest_group_pk" '[$owner, $admin, $guest]')" user_pks_json="$( diff --git a/Scripts/authentik-sync-tailscale-oidc.sh b/Scripts/authentik-sync-tailscale-oidc.sh index 45e654e..fde1a01 100755 --- a/Scripts/authentik-sync-tailscale-oidc.sh +++ b/Scripts/authentik-sync-tailscale-oidc.sh @@ -308,7 +308,7 @@ existing_application="$( if [[ -n "$existing_application" ]]; then application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')" - api PATCH "/api/v3/core/applications/${application_slug}/" "$application_payload" >/dev/null + api PATCH "/api/v3/core/applications/${application_pk}/" "$application_payload" >/dev/null else create_application_result="$( api_with_status POST "/api/v3/core/applications/" "$application_payload" diff --git a/Scripts/authentik-sync-zulip-saml.sh b/Scripts/authentik-sync-zulip-saml.sh index d503ce0..6767991 100644 --- a/Scripts/authentik-sync-zulip-saml.sh +++ b/Scripts/authentik-sync-zulip-saml.sh @@ -344,8 +344,8 @@ existing_application="$( )" if [[ -n "$existing_application" ]]; then - application_pk="existing" - api PATCH "/api/v3/core/applications/${application_slug}/" "$application_payload" >/dev/null + application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')" + api PATCH "/api/v3/core/applications/${application_pk}/" "$application_payload" >/dev/null else create_application_result="$( api_with_status POST "/api/v3/core/applications/" "$application_payload" diff --git a/nixos/hosts/burrow-forge/default.nix b/nixos/hosts/burrow-forge/default.nix index f6d99f9..2464672 100644 --- a/nixos/hosts/burrow-forge/default.nix +++ b/nixos/hosts/burrow-forge/default.nix @@ -251,6 +251,7 @@ in googleLoginMode = "redirect"; userGroupName = contributors.groups.users; adminGroupName = contributors.groups.admins; + tailscaleAccessGroupName = contributors.groups.users; bootstrapUsers = bootstrapUsers; linearAcsUrl = "https://api.linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de/acs"; linearAudience = "https://auth.linear.app/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de"; diff --git a/nixos/modules/burrow-zulip.nix b/nixos/modules/burrow-zulip.nix index 48a5cbf..a408c12 100644 --- a/nixos/modules/burrow-zulip.nix +++ b/nixos/modules/burrow-zulip.nix @@ -128,6 +128,18 @@ in description = "Operational Zulip administrator email."; }; + realmName = lib.mkOption { + type = lib.types.str; + default = "Burrow"; + description = "Initial Zulip organization name for single-tenant bootstrap."; + }; + + realmOwnerName = lib.mkOption { + type = lib.types.str; + default = "Burrow"; + description = "Display name used for the initial Zulip organization owner."; + }; + authentikDomain = lib.mkOption { type = lib.types.str; default = config.services.burrow.authentik.domain; @@ -227,6 +239,7 @@ in chmod 0600 ${lib.escapeShellArg "${cfg.dataDir}/secrets/email-password"} install -m 0444 ${lib.escapeShellArg cfg.memcachedPasswordFile} ${lib.escapeShellArg "${cfg.dataDir}/secrets/memcached-password"} cat > ${lib.escapeShellArg "${cfg.dataDir}/rabbitmq.conf"} </dev/null 2>&1; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 90 ]; then + echo "error: RabbitMQ did not become ready for Zulip bootstrap" >&2 + exit 1 + fi + sleep 2 + done + } + + ensure_zulip_volume_layout() { + local zulip_volume_mount + zulip_volume_mount="$(podman volume inspect burrow-zulip_zulip --format '{{.Mountpoint}}')" + install -d -m 0755 "$zulip_volume_mount/logs" + install -d -m 0755 "$zulip_volume_mount/logs/emails" + install -d -m 0700 "$zulip_volume_mount/secrets" + chown 1000:1000 "$zulip_volume_mount/logs" "$zulip_volume_mount/logs/emails" "$zulip_volume_mount/secrets" + + if [ ! -s "$zulip_volume_mount/secrets/bootstrap-owner-password" ]; then + umask 077 + openssl rand -base64 24 > "$zulip_volume_mount/secrets/bootstrap-owner-password" + fi + chown 1000:1000 "$zulip_volume_mount/secrets/bootstrap-owner-password" + chmod 0600 "$zulip_volume_mount/secrets/bootstrap-owner-password" + } + + bootstrap_realm_if_needed() { + local realm_exists + realm_exists="$( + compose run --rm --entrypoint bash zulip -lc \ + "su zulip -c '/home/zulip/deployments/current/manage.py list_realms'" \ + | awk '$NF == "https://${cfg.domain}" { print "yes" }' + )" + + if [ -n "$realm_exists" ]; then + return 0 + fi + + export ZULIP_REALM_NAME=${lib.escapeShellArg cfg.realmName} + export ZULIP_ADMIN_EMAIL=${lib.escapeShellArg cfg.administratorEmail} + export ZULIP_OWNER_NAME=${lib.escapeShellArg cfg.realmOwnerName} + + compose run --rm --entrypoint bash zulip -lc ' + su zulip -c "/home/zulip/deployments/current/manage.py create_realm --string-id= --password-file /data/secrets/bootstrap-owner-password --automated \"$ZULIP_REALM_NAME\" \"$ZULIP_ADMIN_EMAIL\" \"$ZULIP_OWNER_NAME\"" + ' + } + if [ ! -e .initialized ]; then - ${pkgs.podman-compose}/bin/podman-compose -p burrow-zulip pull - ${pkgs.podman-compose}/bin/podman-compose -p burrow-zulip run --rm zulip app:init + compose pull + compose up -d database memcached rabbitmq redis + wait_for_rabbitmq + compose run --rm zulip app:init touch .initialized fi - ${pkgs.podman-compose}/bin/podman-compose -p burrow-zulip up -d + compose up -d database memcached rabbitmq redis + wait_for_rabbitmq + ensure_zulip_volume_layout + bootstrap_realm_if_needed + compose up -d zulip ''; }; };