From df39c2b254ca7443afbfb8ff30638433466db230 Mon Sep 17 00:00:00 2001
From: Brandon Hopkins <76761586+TechHutTV@users.noreply.github.com>
Date: Wed, 29 Jul 2026 03:20:43 -0700
Subject: [PATCH] [infrastructure] Deprecate legacy Dex and Zitadel
getting-started scripts (#6952)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Describe your changes
Retire the legacy Dex and Zitadel installation scripts by replacing
their implementations with compatibility notices that:
- Exit unsuccessfully before making any system changes.
- Direct new deployments to `getting-started.sh` and the self-hosting
quickstart.
- Explain that the current installer uses NetBird's embedded Dex-based
identity provider.
- Direct users to configure Zitadel through the NetBird Dashboard or
follow the advanced guide for a standalone identity-provider deployment.
- Clarify that Dex support, Zitadel support, and existing deployments
are not deprecated.
- Announce that the compatibility notices will be removed in NetBird
v0.80.
Replace the legacy Zitadel provisioning workflow with CI checks that
verify both scripts fail, write their notices to stderr, and include the
expected documentation links and removal version.
## Issue ticket number and link
No single issue tracks this deprecation.
Related to:
- Legacy installer failures: #4980, #4698, #3566, #3010, #2834, #2094,
#2046, and #1966.
- Legacy installer enhancement requests: #4510 and #1785.
## Stack
Standalone PR based on `main`.
### Checklist
- [ ] Is it a bug fix
- [ ] Is a typo/documentation fix
- [ ] Is a feature enhancement
- [ ] It is a refactor
- [x] Created tests that fail without the change (if possible)
- [x] This change does **not** modify the public API, gRPC protocols,
functionality behavior, CLI / service flags, or introduce a new feature
— **OR** I have discussed it with the NetBird team beforehand (link the
issue / Slack thread in the description). See
[CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first).
> By submitting this pull request, you confirm that you have read and
agree to the terms of the [Contributor License
Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).
## Documentation
Select exactly one:
- [ ] I added/updated documentation for this change
- [x] Documentation is **not needed** for this change because the
compatibility notices link to the existing quickstart,
identity-provider, and advanced deployment documentation.
### Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
N/A
---
Need help on this PR? Tag @codesmith-bot with what you
need. Autofix is disabled.
## Summary by CodeRabbit
- **Breaking Changes**
- Retired the legacy Dex and Zitadel getting-started installation
scripts.
- These scripts now exit with an explanatory deprecation message and
direct users to the current setup flow and documentation.
- Existing workflows now verify that the retired scripts fail as
expected and provide the appropriate migration guidance.
---
.../workflows/test-infrastructure-files.yml | 87 +-
.../getting-started-with-dex.sh | 560 +---------
.../getting-started-with-zitadel.sh | 997 +-----------------
3 files changed, 48 insertions(+), 1596 deletions(-)
diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml
index 0a4f2e371..965d8aa5d 100644
--- a/.github/workflows/test-infrastructure-files.yml
+++ b/.github/workflows/test-infrastructure-files.yml
@@ -249,78 +249,35 @@ jobs:
docker compose exec management ls -l /var/lib/netbird/ | grep -i GeoLite2-City_[0-9]*.mmdb
docker compose exec management ls -l /var/lib/netbird/ | grep -i geonames_[0-9]*.db
- test-getting-started-script:
+ test-legacy-getting-started-scripts:
runs-on: ubuntu-latest
steps:
- - name: Install jq
- run: sudo apt-get install -y jq
-
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- - name: run script with Zitadel PostgreSQL
- run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh
-
- - name: test Caddy file gen postgres
- run: test -f Caddyfile
-
- - name: test docker-compose file gen postgres
- run: test -f docker-compose.yml
-
- - name: test management.json file gen postgres
- run: test -f management.json
-
- - name: test turnserver.conf file gen postgres
+ - name: Verify Dex retirement notice
run: |
- set -x
- test -f turnserver.conf
- grep external-ip turnserver.conf
+ if infrastructure_files/getting-started-with-dex.sh >stdout.txt 2>stderr.txt; then
+ echo "Expected the retired Dex installer to fail"
+ exit 1
+ fi
+ test ! -s stdout.txt
+ grep -Fq "Dex support is not deprecated." stderr.txt
+ grep -Fq "https://docs.netbird.io/selfhosted/selfhosted-quickstart" stderr.txt
+ grep -Fq "https://docs.netbird.io/selfhosted/identity-providers/local" stderr.txt
+ grep -Fq "removed in NetBird v0.80" stderr.txt
- - name: test zitadel.env file gen postgres
- run: test -f zitadel.env
-
- - name: test dashboard.env file gen postgres
- run: test -f dashboard.env
-
- - name: test relay.env file gen postgres
- run: test -f relay.env
-
- - name: test zdb.env file gen postgres
- run: test -f zdb.env
-
- - name: Postgres run cleanup
+ - name: Verify Zitadel retirement notice
run: |
- docker compose down --volumes --rmi all
- rm -rf docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json zdb.env
-
- - name: run script with Zitadel CockroachDB
- run: bash -x infrastructure_files/getting-started-with-zitadel.sh
- env:
- NETBIRD_DOMAIN: use-ip
- ZITADEL_DATABASE: cockroach
-
- - name: test Caddy file gen CockroachDB
- run: test -f Caddyfile
-
- - name: test docker-compose file gen CockroachDB
- run: test -f docker-compose.yml
-
- - name: test management.json file gen CockroachDB
- run: test -f management.json
-
- - name: test turnserver.conf file gen CockroachDB
- run: |
- set -x
- test -f turnserver.conf
- grep external-ip turnserver.conf
-
- - name: test zitadel.env file gen CockroachDB
- run: test -f zitadel.env
-
- - name: test dashboard.env file gen CockroachDB
- run: test -f dashboard.env
-
- - name: test relay.env file gen CockroachDB
- run: test -f relay.env
+ if bash infrastructure_files/getting-started-with-zitadel.sh >stdout.txt 2>stderr.txt; then
+ echo "Expected the retired Zitadel installer to fail"
+ exit 1
+ fi
+ test ! -s stdout.txt
+ grep -Fq "Zitadel support and existing Zitadel deployments are not deprecated." stderr.txt
+ grep -Fq "https://docs.netbird.io/selfhosted/selfhosted-quickstart" stderr.txt
+ grep -Fq "https://docs.netbird.io/selfhosted/identity-providers/zitadel" stderr.txt
+ grep -Fq "https://docs.netbird.io/selfhosted/selfhosted-guide" stderr.txt
+ grep -Fq "removed in NetBird v0.80" stderr.txt
diff --git a/infrastructure_files/getting-started-with-dex.sh b/infrastructure_files/getting-started-with-dex.sh
index 5e605f19c..a9be19eb3 100755
--- a/infrastructure_files/getting-started-with-dex.sh
+++ b/infrastructure_files/getting-started-with-dex.sh
@@ -1,557 +1,19 @@
-#!/bin/bash
+#!/usr/bin/env bash
-set -e
+cat >&2 <<'EOF'
+ERROR: This legacy installation script has been retired and no longer runs.
-# NetBird Getting Started with Dex IDP
-# This script sets up NetBird with Dex as the identity provider
+Dex support is not deprecated. For new deployments, use getting-started.sh:
-# Sed pattern to strip base64 padding characters
-SED_STRIP_PADDING='s/=//g'
+https://docs.netbird.io/selfhosted/selfhosted-quickstart
-check_docker_compose() {
- if command -v docker-compose &> /dev/null
- then
- echo "docker-compose"
- return
- fi
- if docker compose --help &> /dev/null
- then
- echo "docker compose"
- return
- fi
+The current installer includes NetBird's embedded Dex-based identity provider.
+Local users and external identity providers can be managed through the
+NetBird Dashboard:
- echo "docker-compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
- exit 1
-}
+https://docs.netbird.io/selfhosted/identity-providers/local
-check_jq() {
- if ! command -v jq &> /dev/null
- then
- echo "jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq" > /dev/stderr
- exit 1
- fi
- return 0
-}
-
-get_main_ip_address() {
- if [[ "$OSTYPE" == "darwin"* ]]; then
- interface=$(route -n get default | grep 'interface:' | awk '{print $2}')
- ip_address=$(ifconfig "$interface" | grep 'inet ' | awk '{print $2}')
- else
- interface=$(ip route | grep default | awk '{print $5}' | head -n 1)
- ip_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
- fi
-
- echo "$ip_address"
- return 0
-}
-
-check_nb_domain() {
- DOMAIN=$1
- if [[ "$DOMAIN-x" == "-x" ]]; then
- echo "The NETBIRD_DOMAIN variable cannot be empty." > /dev/stderr
- return 1
- fi
-
- if [[ "$DOMAIN" == "netbird.example.com" ]]; then
- echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
- return 1
- fi
- return 0
-}
-
-read_nb_domain() {
- READ_NETBIRD_DOMAIN=""
- echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
- read -r READ_NETBIRD_DOMAIN < /dev/tty
- if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
- read_nb_domain
- fi
- echo "$READ_NETBIRD_DOMAIN"
- return 0
-}
-
-get_turn_external_ip() {
- TURN_EXTERNAL_IP_CONFIG="#external-ip="
- IP=$(curl -s -4 https://jsonip.com | jq -r '.ip')
- if [[ "x-$IP" != "x-" ]]; then
- TURN_EXTERNAL_IP_CONFIG="external-ip=$IP"
- fi
- echo "$TURN_EXTERNAL_IP_CONFIG"
- return 0
-}
-
-wait_dex() {
- set +e
- echo -n "Waiting for Dex to become ready (via $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN)"
- counter=1
- while true; do
- # Check Dex through Caddy proxy (also validates TLS is working)
- if curl -sk -f -o /dev/null "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/dex/.well-known/openid-configuration" 2>/dev/null; then
- break
- fi
- if [[ $counter -eq 60 ]]; then
- echo ""
- echo "Taking too long. Checking logs..."
- $DOCKER_COMPOSE_COMMAND logs --tail=20 caddy
- $DOCKER_COMPOSE_COMMAND logs --tail=20 dex
- fi
- echo -n " ."
- sleep 2
- counter=$((counter + 1))
- done
- echo " done"
- set -e
- return 0
-}
-
-init_environment() {
- CADDY_SECURE_DOMAIN=""
- NETBIRD_PORT=80
- NETBIRD_HTTP_PROTOCOL="http"
- NETBIRD_RELAY_PROTO="rel"
- TURN_USER="self"
- TURN_PASSWORD=$(openssl rand -base64 32 | sed "$SED_STRIP_PADDING")
- NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed "$SED_STRIP_PADDING")
- TURN_MIN_PORT=49152
- TURN_MAX_PORT=65535
- TURN_EXTERNAL_IP_CONFIG=$(get_turn_external_ip)
-
- # Generate secrets for Dex
- DEX_DASHBOARD_CLIENT_SECRET=$(openssl rand -base64 32 | sed "$SED_STRIP_PADDING")
-
- # Generate admin password
- NETBIRD_ADMIN_PASSWORD=$(openssl rand -base64 16 | sed "$SED_STRIP_PADDING")
-
- if ! check_nb_domain "$NETBIRD_DOMAIN"; then
- NETBIRD_DOMAIN=$(read_nb_domain)
- fi
-
- if [[ "$NETBIRD_DOMAIN" == "use-ip" ]]; then
- NETBIRD_DOMAIN=$(get_main_ip_address)
- else
- NETBIRD_PORT=443
- CADDY_SECURE_DOMAIN=", $NETBIRD_DOMAIN:$NETBIRD_PORT"
- NETBIRD_HTTP_PROTOCOL="https"
- NETBIRD_RELAY_PROTO="rels"
- fi
-
- check_jq
-
- DOCKER_COMPOSE_COMMAND=$(check_docker_compose)
-
- if [[ -f dex.yaml ]]; then
- echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
- echo "You can use the following commands:"
- echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
- echo " rm -f docker-compose.yml Caddyfile dex.yaml dashboard.env turnserver.conf management.json relay.env"
- echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
- exit 1
- fi
-
- echo Rendering initial files...
- render_docker_compose > docker-compose.yml
- render_caddyfile > Caddyfile
- render_dex_config > dex.yaml
- render_dashboard_env > dashboard.env
- render_management_json > management.json
- render_turn_server_conf > turnserver.conf
- render_relay_env > relay.env
-
- echo -e "\nStarting Dex IDP\n"
- $DOCKER_COMPOSE_COMMAND up -d caddy dex
-
- # Wait for Dex to be ready (through caddy proxy)
- sleep 3
- wait_dex
-
- echo -e "\nStarting NetBird services\n"
- $DOCKER_COMPOSE_COMMAND up -d
-
- echo -e "\nDone!\n"
- echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
- echo ""
- echo "Login with the following credentials:"
- install -m 600 /dev/null .env
- printf 'Email: admin@%s\nPassword: %s\n' \
- "$NETBIRD_DOMAIN" "$NETBIRD_ADMIN_PASSWORD" >> .env
- echo "Email: admin@$NETBIRD_DOMAIN"
- echo "Password: $NETBIRD_ADMIN_PASSWORD"
- echo ""
- echo "Dex admin UI is not available (Dex has no built-in UI)."
- echo "To add more users, edit dex.yaml and restart: $DOCKER_COMPOSE_COMMAND restart dex"
- return 0
-}
-
-render_caddyfile() {
- cat < /dev/null; then
- ADMIN_PASSWORD_HASH=$(htpasswd -bnBC 10 "" "$NETBIRD_ADMIN_PASSWORD" | tr -d ':\n')
- elif command -v python3 &> /dev/null; then
- ADMIN_PASSWORD_HASH=$(python3 -c "import bcrypt; print(bcrypt.hashpw('$NETBIRD_ADMIN_PASSWORD'.encode(), bcrypt.gensalt(rounds=10)).decode())" 2>/dev/null || echo "")
- fi
-
- # Fallback to a known hash if we can't generate one
- if [[ -z "$ADMIN_PASSWORD_HASH" ]]; then
- # This is hash of "password" - user should change it
- ADMIN_PASSWORD_HASH='$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W'
- NETBIRD_ADMIN_PASSWORD="password"
- echo "Warning: Could not generate password hash. Using default password: password. Please change it in dex.yaml" > /dev/stderr
- fi
-
- cat </dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "admin-user-id-001")"
-
-# Optional: Add external identity provider connectors
-# connectors:
-# - type: github
-# id: github
-# name: GitHub
-# config:
-# clientID: \$GITHUB_CLIENT_ID
-# clientSecret: \$GITHUB_CLIENT_SECRET
-# redirectURI: $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/dex/callback
-#
-# - type: ldap
-# id: ldap
-# name: LDAP
-# config:
-# host: ldap.example.com:636
-# insecureNoSSL: false
-# bindDN: cn=admin,dc=example,dc=com
-# bindPW: admin
-# userSearch:
-# baseDN: ou=users,dc=example,dc=com
-# filter: "(objectClass=person)"
-# username: uid
-# idAttr: uid
-# emailAttr: mail
-# nameAttr: cn
-EOF
- return 0
-}
-
-render_turn_server_conf() {
- cat <&2 <<'EOF'
+ERROR: This legacy installation script has been retired and no longer runs.
-handle_request_command_status() {
- PARSED_RESPONSE=$1
- FUNCTION_NAME=$2
- RESPONSE=$3
- if [[ $PARSED_RESPONSE -ne 0 ]]; then
- echo "ERROR calling $FUNCTION_NAME:" $(echo "$RESPONSE" | jq -r '.message') > /dev/stderr
- exit 1
- fi
-}
+Zitadel support and existing Zitadel deployments are not deprecated.
-handle_zitadel_request_response() {
- PARSED_RESPONSE=$1
- FUNCTION_NAME=$2
- RESPONSE=$3
- if [[ $PARSED_RESPONSE == "null" ]]; then
- echo "ERROR calling $FUNCTION_NAME:" $(echo "$RESPONSE" | jq -r '.message') > /dev/stderr
- exit 1
- fi
- sleep 1
-}
+For new deployments, use getting-started.sh:
-check_docker_compose() {
- if command -v docker-compose &> /dev/null
- then
- echo "docker-compose"
- return
- fi
- if docker compose --help &> /dev/null
- then
- echo "docker compose"
- return
- fi
+https://docs.netbird.io/selfhosted/selfhosted-quickstart
- echo "docker-compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
- exit 1
-}
+The current installer includes NetBird's embedded Dex-based identity provider.
+Zitadel can be added as an external identity provider directly through the
+NetBird Dashboard:
-check_jq() {
- if ! command -v jq &> /dev/null
- then
- echo "jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq" > /dev/stderr
- exit 1
- fi
-}
+https://docs.netbird.io/selfhosted/identity-providers/zitadel
-wait_crdb() {
- set +e
- while true; do
- if $DOCKER_COMPOSE_COMMAND exec -T zdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then
- break
- fi
- echo -n " ."
- sleep 5
- done
- echo " done"
- set -e
-}
+Standalone Zitadel and other custom identity-provider deployments remain
+supported through the advanced guide:
-init_crdb() {
- if [[ $ZITADEL_DATABASE == "cockroach" ]]; then
- echo -e "\nInitializing Zitadel's CockroachDB\n\n"
- $DOCKER_COMPOSE_COMMAND up -d zdb
- echo ""
- # shellcheck disable=SC2028
- echo -n "Waiting CockroachDB to become ready"
- wait_crdb
- $DOCKER_COMPOSE_COMMAND exec -T zdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
- handle_request_command_status $? "init_crdb failed" ""
- fi
-}
+https://docs.netbird.io/selfhosted/selfhosted-guide
-get_main_ip_address() {
- if [[ "$OSTYPE" == "darwin"* ]]; then
- interface=$(route -n get default | grep 'interface:' | awk '{print $2}')
- ip_address=$(ifconfig "$interface" | grep 'inet ' | awk '{print $2}')
- else
- interface=$(ip route | grep default | awk '{print $5}' | head -n 1)
- ip_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
- fi
-
- echo "$ip_address"
-}
-
-wait_pat() {
- PAT_PATH=$1
- set +e
- while true; do
- if [[ -f "$PAT_PATH" ]]; then
- break
- fi
- echo -n " ."
- sleep 1
- done
- echo " done"
- set -e
-}
-
-wait_api() {
- INSTANCE_URL=$1
- PAT=$2
- set +e
- counter=1
- while true; do
- FLAGS="-s"
- if [[ $counter -eq 45 ]]; then
- FLAGS="-v"
- echo ""
- fi
-
- curl $FLAGS --fail --connect-timeout 1 -o /dev/null "$INSTANCE_URL/auth/v1/users/me" -H "Authorization: Bearer $PAT"
- if [[ $? -eq 0 ]]; then
- break
- fi
- if [[ $counter -eq 45 ]]; then
- echo ""
- echo "Unable to connect to Zitadel for more than 45s, please check the output above, your firewall rules and the caddy container logs to confirm if there are any issues provisioning TLS certificates"
- fi
- echo -n " ."
- sleep 1
- counter=$((counter + 1))
- done
- echo " done"
- set -e
-}
-
-create_new_project() {
- INSTANCE_URL=$1
- PAT=$2
- PROJECT_NAME="NETBIRD"
-
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/management/v1/projects" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{"name": "'"$PROJECT_NAME"'"}'
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.id')
- handle_zitadel_request_response "$PARSED_RESPONSE" "create_new_project" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-create_new_application() {
- INSTANCE_URL=$1
- PAT=$2
- APPLICATION_NAME=$3
- BASE_REDIRECT_URL1=$4
- BASE_REDIRECT_URL2=$5
- LOGOUT_URL=$6
- ZITADEL_DEV_MODE=$7
- DEVICE_CODE=$8
-
- if [[ $DEVICE_CODE == "true" ]]; then
- GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_DEVICE_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]'
- else
- GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]'
- fi
-
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "'"$APPLICATION_NAME"'",
- "redirectUris": [
- "'"$BASE_REDIRECT_URL1"'",
- "'"$BASE_REDIRECT_URL2"'"
- ],
- "postLogoutRedirectUris": [
- "'"$LOGOUT_URL"'"
- ],
- "RESPONSETypes": [
- "OIDC_RESPONSE_TYPE_CODE"
- ],
- "grantTypes": '"$GRANT_TYPES"',
- "appType": "OIDC_APP_TYPE_USER_AGENT",
- "authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
- "version": "OIDC_VERSION_1_0",
- "devMode": '"$ZITADEL_DEV_MODE"',
- "accessTokenType": "OIDC_TOKEN_TYPE_JWT",
- "accessTokenRoleAssertion": true,
- "skipNativeAppSuccessPage": true
- }'
- )
-
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.clientId')
- handle_zitadel_request_response "$PARSED_RESPONSE" "create_new_application" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-create_service_user() {
- INSTANCE_URL=$1
- PAT=$2
-
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/management/v1/users/machine" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{
- "userName": "netbird-service-account",
- "name": "Netbird Service Account",
- "description": "Netbird Service Account for IDP management",
- "accessTokenType": "ACCESS_TOKEN_TYPE_JWT"
- }'
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.userId')
- handle_zitadel_request_response "$PARSED_RESPONSE" "create_service_user" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-create_service_user_secret() {
- INSTANCE_URL=$1
- PAT=$2
- USER_ID=$3
-
- RESPONSE=$(
- curl -sS -X PUT "$INSTANCE_URL/management/v1/users/$USER_ID/secret" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{}'
- )
- SERVICE_USER_CLIENT_ID=$(echo "$RESPONSE" | jq -r '.clientId')
- handle_zitadel_request_response "$SERVICE_USER_CLIENT_ID" "create_service_user_secret_id" "$RESPONSE"
- SERVICE_USER_CLIENT_SECRET=$(echo "$RESPONSE" | jq -r '.clientSecret')
- handle_zitadel_request_response "$SERVICE_USER_CLIENT_SECRET" "create_service_user_secret" "$RESPONSE"
-}
-
-add_organization_user_manager() {
- INSTANCE_URL=$1
- PAT=$2
- USER_ID=$3
-
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/management/v1/orgs/me/members" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{
- "userId": "'"$USER_ID"'",
- "roles": [
- "ORG_USER_MANAGER"
- ]
- }'
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.creationDate')
- handle_zitadel_request_response "$PARSED_RESPONSE" "add_organization_user_manager" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-create_admin_user() {
- INSTANCE_URL=$1
- PAT=$2
- USERNAME=$3
- PASSWORD=$4
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/management/v1/users/human/_import" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{
- "userName": "'"$USERNAME"'",
- "profile": {
- "firstName": "Zitadel",
- "lastName": "Admin"
- },
- "email": {
- "email": "'"$USERNAME"'",
- "isEmailVerified": true
- },
- "password": "'"$PASSWORD"'",
- "passwordChangeRequired": true
- }'
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.userId')
- handle_zitadel_request_response "$PARSED_RESPONSE" "create_admin_user" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-add_instance_admin() {
- INSTANCE_URL=$1
- PAT=$2
- USER_ID=$3
-
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/admin/v1/members" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{
- "userId": "'"$USER_ID"'",
- "roles": [
- "IAM_OWNER"
- ]
- }'
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.creationDate')
- handle_zitadel_request_response "$PARSED_RESPONSE" "add_instance_admin" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-delete_auto_service_user() {
- INSTANCE_URL=$1
- PAT=$2
-
- RESPONSE=$(
- curl -sS -X GET "$INSTANCE_URL/auth/v1/users/me" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- )
- USER_ID=$(echo "$RESPONSE" | jq -r '.user.id')
- handle_zitadel_request_response "$USER_ID" "delete_auto_service_user_get_user" "$RESPONSE"
-
- RESPONSE=$(
- curl -sS -X DELETE "$INSTANCE_URL/admin/v1/members/$USER_ID" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate')
- handle_zitadel_request_response "$PARSED_RESPONSE" "delete_auto_service_user_remove_instance_permissions" "$RESPONSE"
-
- RESPONSE=$(
- curl -sS -X DELETE "$INSTANCE_URL/management/v1/orgs/me/members/$USER_ID" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate')
- handle_zitadel_request_response "$PARSED_RESPONSE" "delete_auto_service_user_remove_org_permissions" "$RESPONSE"
- echo "$PARSED_RESPONSE"
-}
-
-delete_default_zitadel_admin() {
- INSTANCE_URL=$1
- PAT=$2
-
- # Search for the default zitadel-admin user
- RESPONSE=$(
- curl -sS -X POST "$INSTANCE_URL/management/v1/users/_search" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- -d '{
- "queries": [
- {
- "userNameQuery": {
- "userName": "zitadel-admin@",
- "method": "TEXT_QUERY_METHOD_STARTS_WITH"
- }
- }
- ]
- }'
- )
-
- DEFAULT_ADMIN_ID=$(echo "$RESPONSE" | jq -r '.result[0].id // empty')
-
- if [ -n "$DEFAULT_ADMIN_ID" ] && [ "$DEFAULT_ADMIN_ID" != "null" ]; then
- echo "Found default zitadel-admin user with ID: $DEFAULT_ADMIN_ID"
-
- RESPONSE=$(
- curl -sS -X DELETE "$INSTANCE_URL/management/v1/users/$DEFAULT_ADMIN_ID" \
- -H "Authorization: Bearer $PAT" \
- -H "Content-Type: application/json" \
- )
- PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate // "deleted"')
- handle_zitadel_request_response "$PARSED_RESPONSE" "delete_default_zitadel_admin" "$RESPONSE"
-
- else
- echo "Default zitadel-admin user not found: $RESPONSE"
- fi
-}
-
-init_zitadel() {
- echo -e "\nInitializing Zitadel with NetBird's applications\n"
- INSTANCE_URL="$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
-
- TOKEN_PATH=./machinekey/zitadel-admin-sa.token
-
- echo -n "Waiting for Zitadel's PAT to be created "
- wait_pat "$TOKEN_PATH"
- echo "Reading Zitadel PAT"
- PAT=$(cat $TOKEN_PATH)
- if [ "$PAT" = "null" ]; then
- echo "Failed requesting getting Zitadel PAT"
- exit 1
- fi
-
- echo -n "Waiting for Zitadel to become ready "
- wait_api "$INSTANCE_URL" "$PAT"
-
- echo "Deleting default zitadel-admin user..."
- delete_default_zitadel_admin "$INSTANCE_URL" "$PAT"
-
- # create the zitadel project
- echo "Creating new zitadel project"
- PROJECT_ID=$(create_new_project "$INSTANCE_URL" "$PAT")
-
- ZITADEL_DEV_MODE=false
- BASE_REDIRECT_URL=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN
- if [[ $NETBIRD_HTTP_PROTOCOL == "http" ]]; then
- ZITADEL_DEV_MODE=true
- fi
-
- # create zitadel spa applications
- echo "Creating new Zitadel SPA Dashboard application"
- DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE" "false")
-
- echo "Creating new Zitadel SPA Cli application"
- CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true" "true")
-
- MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
-
- SERVICE_USER_CLIENT_ID="null"
- SERVICE_USER_CLIENT_SECRET="null"
-
- create_service_user_secret "$INSTANCE_URL" "$PAT" "$MACHINE_USER_ID"
-
- DATE=$(add_organization_user_manager "$INSTANCE_URL" "$PAT" "$MACHINE_USER_ID")
-
- ZITADEL_ADMIN_USERNAME="admin@$NETBIRD_DOMAIN"
- ZITADEL_ADMIN_PASSWORD="$(openssl rand -base64 32 | sed 's/=//g')@"
-
- HUMAN_USER_ID=$(create_admin_user "$INSTANCE_URL" "$PAT" "$ZITADEL_ADMIN_USERNAME" "$ZITADEL_ADMIN_PASSWORD")
-
- DATE="null"
-
- DATE=$(add_instance_admin "$INSTANCE_URL" "$PAT" "$HUMAN_USER_ID")
-
- DATE="null"
- DATE=$(delete_auto_service_user "$INSTANCE_URL" "$PAT")
- if [ "$DATE" = "null" ]; then
- echo "Failed deleting auto service user"
- echo "Please remove it manually"
- fi
-
- export NETBIRD_AUTH_CLIENT_ID=$DASHBOARD_APPLICATION_CLIENT_ID
- export NETBIRD_AUTH_CLIENT_ID_CLI=$CLI_APPLICATION_CLIENT_ID
- export NETBIRD_IDP_MGMT_CLIENT_ID=$SERVICE_USER_CLIENT_ID
- export NETBIRD_IDP_MGMT_CLIENT_SECRET=$SERVICE_USER_CLIENT_SECRET
- export ZITADEL_ADMIN_USERNAME
- export ZITADEL_ADMIN_PASSWORD
-}
-
-check_nb_domain() {
- DOMAIN=$1
- if [ "$DOMAIN-x" == "-x" ]; then
- echo "The NETBIRD_DOMAIN variable cannot be empty." > /dev/stderr
- return 1
- fi
-
- if [ "$DOMAIN" == "netbird.example.com" ]; then
- echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
- return 1
- fi
- return 0
-}
-
-read_nb_domain() {
- READ_NETBIRD_DOMAIN=""
- echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
- read -r READ_NETBIRD_DOMAIN < /dev/tty
- if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
- read_nb_domain
- fi
- echo "$READ_NETBIRD_DOMAIN"
-}
-
-get_turn_external_ip() {
- TURN_EXTERNAL_IP_CONFIG="#external-ip="
- IP=$(curl -s -4 https://jsonip.com | jq -r '.ip')
- if [[ "x-$IP" != "x-" ]]; then
- TURN_EXTERNAL_IP_CONFIG="external-ip=$IP"
- fi
- echo "$TURN_EXTERNAL_IP_CONFIG"
-}
-
-initEnvironment() {
- CADDY_SECURE_DOMAIN=""
- ZITADEL_EXTERNALSECURE="false"
- ZITADEL_TLS_MODE="disabled"
- ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)"
- NETBIRD_PORT=80
- NETBIRD_HTTP_PROTOCOL="http"
- NETBIRD_RELAY_PROTO="rel"
- TURN_USER="self"
- TURN_PASSWORD=$(openssl rand -base64 32 | sed 's/=//g')
- NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed 's/=//g')
- TURN_MIN_PORT=49152
- TURN_MAX_PORT=65535
- TURN_EXTERNAL_IP_CONFIG=$(get_turn_external_ip)
-
- if ! check_nb_domain "$NETBIRD_DOMAIN"; then
- NETBIRD_DOMAIN=$(read_nb_domain)
- fi
-
- if [ "$NETBIRD_DOMAIN" == "use-ip" ]; then
- NETBIRD_DOMAIN=$(get_main_ip_address)
- else
- ZITADEL_EXTERNALSECURE="true"
- ZITADEL_TLS_MODE="external"
- NETBIRD_PORT=443
- CADDY_SECURE_DOMAIN=", $NETBIRD_DOMAIN:$NETBIRD_PORT"
- NETBIRD_HTTP_PROTOCOL="https"
- NETBIRD_RELAY_PROTO="rels"
- fi
-
- if [[ "$OSTYPE" == "darwin"* ]]; then
- ZIDATE_TOKEN_EXPIRATION_DATE=$(date -u -v+30M "+%Y-%m-%dT%H:%M:%SZ")
- else
- ZIDATE_TOKEN_EXPIRATION_DATE=$(date -u -d "+30 minutes" "+%Y-%m-%dT%H:%M:%SZ")
- fi
-
- check_jq
-
- DOCKER_COMPOSE_COMMAND=$(check_docker_compose)
-
- if [ -f zitadel.env ]; then
- echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
- echo "You can use the following commands:"
- echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
- echo " rm -f docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json relay.env"
- echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
- exit 1
- fi
-
- if [[ $ZITADEL_DATABASE == "cockroach" ]]; then
- echo "Use CockroachDB as Zitadel database."
- ZDB=$(renderDockerComposeCockroachDB)
- ZITADEL_DB_ENV=$(renderZitadelCockroachDBEnv)
- else
- echo "Use Postgres as default Zitadel database."
- echo "For using CockroachDB please the environment variable 'export ZITADEL_DATABASE=cockroach'."
- POSTGRES_ROOT_PASSWORD="$(openssl rand -base64 32 | sed 's/=//g')@"
- POSTGRES_ZITADEL_PASSWORD="$(openssl rand -base64 32 | sed 's/=//g')@"
- ZDB=$(renderDockerComposePostgres)
- ZITADEL_DB_ENV=$(renderZitadelPostgresEnv)
- renderPostgresEnv > zdb.env
- fi
-
- echo Rendering initial files...
- renderDockerCompose > docker-compose.yml
- renderCaddyfile > Caddyfile
- renderZitadelEnv > zitadel.env
- echo "" > dashboard.env
- echo "" > turnserver.conf
- echo "" > management.json
- echo "" > relay.env
-
- mkdir -p machinekey
- chmod 777 machinekey
-
- init_crdb
-
- echo -e "\nStarting Zitadel IDP for user management\n\n"
- $DOCKER_COMPOSE_COMMAND up -d caddy zitadel
- init_zitadel
-
- echo -e "\nRendering NetBird files...\n"
- renderTurnServerConf > turnserver.conf
- renderManagementJson > management.json
- renderDashboardEnv > dashboard.env
- renderRelayEnv > relay.env
-
- echo -e "\nStarting NetBird services\n"
- $DOCKER_COMPOSE_COMMAND up -d
- echo -e "\nDone!\n"
- echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
- echo "Login with the following credentials:"
- install -m 600 /dev/null .env
- printf 'Username: %s\nPassword: %s\n' \
- "$ZITADEL_ADMIN_USERNAME" "$ZITADEL_ADMIN_PASSWORD" >> .env
- echo "Username: $ZITADEL_ADMIN_USERNAME"
- echo "Password: $ZITADEL_ADMIN_PASSWORD"
-}
-
-renderCaddyfile() {
- cat <