Grant Tailnet access and harden Zulip bootstrap

This commit is contained in:
Conrad Kramer 2026-04-19 00:52:16 -07:00
parent 801e0fb419
commit b8cad4c028
6 changed files with 90 additions and 9 deletions

View file

@ -294,8 +294,8 @@ existing_application="$(
)" )"
if [[ -n "$existing_application" ]]; then if [[ -n "$existing_application" ]]; then
application_pk="existing" 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 else
create_application_result="$( create_application_result="$(
api_with_status POST "/api/v3/core/applications/" "$application_payload" api_with_status POST "/api/v3/core/applications/" "$application_payload"

View file

@ -278,7 +278,12 @@ application_payload="$(
policy_engine_mode: .policy_engine_mode 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]')" 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="$( user_pks_json="$(

View file

@ -308,7 +308,7 @@ existing_application="$(
if [[ -n "$existing_application" ]]; then if [[ -n "$existing_application" ]]; then
application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')" 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 else
create_application_result="$( create_application_result="$(
api_with_status POST "/api/v3/core/applications/" "$application_payload" api_with_status POST "/api/v3/core/applications/" "$application_payload"

View file

@ -344,8 +344,8 @@ existing_application="$(
)" )"
if [[ -n "$existing_application" ]]; then if [[ -n "$existing_application" ]]; then
application_pk="existing" 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 else
create_application_result="$( create_application_result="$(
api_with_status POST "/api/v3/core/applications/" "$application_payload" api_with_status POST "/api/v3/core/applications/" "$application_payload"

View file

@ -251,6 +251,7 @@ in
googleLoginMode = "redirect"; googleLoginMode = "redirect";
userGroupName = contributors.groups.users; userGroupName = contributors.groups.users;
adminGroupName = contributors.groups.admins; adminGroupName = contributors.groups.admins;
tailscaleAccessGroupName = contributors.groups.users;
bootstrapUsers = bootstrapUsers; bootstrapUsers = bootstrapUsers;
linearAcsUrl = "https://api.linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de/acs"; linearAcsUrl = "https://api.linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de/acs";
linearAudience = "https://auth.linear.app/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de"; linearAudience = "https://auth.linear.app/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de";

View file

@ -128,6 +128,18 @@ in
description = "Operational Zulip administrator email."; 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 { authentikDomain = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = config.services.burrow.authentik.domain; default = config.services.burrow.authentik.domain;
@ -227,6 +239,7 @@ in
chmod 0600 ${lib.escapeShellArg "${cfg.dataDir}/secrets/email-password"} chmod 0600 ${lib.escapeShellArg "${cfg.dataDir}/secrets/email-password"}
install -m 0444 ${lib.escapeShellArg cfg.memcachedPasswordFile} ${lib.escapeShellArg "${cfg.dataDir}/secrets/memcached-password"} install -m 0444 ${lib.escapeShellArg cfg.memcachedPasswordFile} ${lib.escapeShellArg "${cfg.dataDir}/secrets/memcached-password"}
cat > ${lib.escapeShellArg "${cfg.dataDir}/rabbitmq.conf"} <<EOF cat > ${lib.escapeShellArg "${cfg.dataDir}/rabbitmq.conf"} <<EOF
listeners.tcp.default = 0.0.0.0:5672
default_user = zulip default_user = zulip
default_pass = "$(tr -d '\r\n' < ${lib.escapeShellArg cfg.rabbitmqPasswordFile})" default_pass = "$(tr -d '\r\n' < ${lib.escapeShellArg cfg.rabbitmqPasswordFile})"
EOF EOF
@ -311,6 +324,9 @@ EOF
path = [ path = [
pkgs.bash pkgs.bash
pkgs.coreutils pkgs.coreutils
pkgs.gawk
pkgs.gnugrep
pkgs.openssl
pkgs.podman pkgs.podman
pkgs.podman-compose pkgs.podman-compose
]; ];
@ -334,13 +350,72 @@ EOF
set -euo pipefail set -euo pipefail
cd ${lib.escapeShellArg cfg.dataDir} cd ${lib.escapeShellArg cfg.dataDir}
compose() {
${pkgs.podman-compose}/bin/podman-compose -p burrow-zulip "$@"
}
wait_for_rabbitmq() {
local attempts=0
while ! podman exec burrow-zulip_rabbitmq_1 rabbitmq-diagnostics -q ping >/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 if [ ! -e .initialized ]; then
${pkgs.podman-compose}/bin/podman-compose -p burrow-zulip pull compose pull
${pkgs.podman-compose}/bin/podman-compose -p burrow-zulip run --rm zulip app:init compose up -d database memcached rabbitmq redis
wait_for_rabbitmq
compose run --rm zulip app:init
touch .initialized touch .initialized
fi 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
''; '';
}; };
}; };