#!/bin/bash set -e 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 } 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 } 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 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 } 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 } wait_crdb() { set +e while true; do if $DOCKER_COMPOSE_COMMAND exec -T crdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then break fi echo -n " ." sleep 5 done echo " done" set -e } init_crdb() { echo -e "\nInitializing Zitadel's CockroachDB\n\n" $DOCKER_COMPOSE_COMMAND up -d crdb echo "" # shellcheck disable=SC2028 echo -n "Waiting cockroachDB to become ready " wait_crdb $DOCKER_COMPOSE_COMMAND exec -T crdb /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" "" } 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 while true; do curl -s --fail -o /dev/null "$INSTANCE_URL/auth/v1/users/me" -H "Authorization: Bearer $PAT" if [[ $? -eq 0 ]]; then break fi echo -n " ." sleep 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" } 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" # 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" TURN_USER="self" TURN_PASSWORD=$(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" 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" 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... renderDockerCompose > docker-compose.yml renderCaddyfile > Caddyfile renderZitadelEnv > zitadel.env echo "" > dashboard.env echo "" > turnserver.conf echo "" > management.json mkdir -p machinekey chmod 777 machinekey init_crdb echo -e "\nStarting Zidatel 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 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:" echo "Username: $ZITADEL_ADMIN_USERNAME" | tee .env echo "Password: $ZITADEL_ADMIN_PASSWORD" | tee -a .env } renderCaddyfile() { cat <<EOF { debug servers :80,:443 { protocols h1 h2c } } (security_headers) { header * { # enable HSTS # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#strict-transport-security-hsts # NOTE: Read carefully how this header works before using it. # If the HSTS header is misconfigured or if there is a problem with # the SSL/TLS certificate being used, legitimate users might be unable # to access the website. For example, if the HSTS header is set to a # very long duration and the SSL/TLS certificate expires or is revoked, # legitimate users might be unable to access the website until # the HSTS header duration has expired. # The recommended value for the max-age is 2 year (63072000 seconds). # But we are using 1 hour (3600 seconds) for testing purposes # and ensure that the website is working properly before setting # to two years. Strict-Transport-Security "max-age=3600; includeSubDomains; preload" # disable clients from sniffing the media type # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options X-Content-Type-Options "nosniff" # clickjacking protection # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options X-Frame-Options "DENY" # xss protection # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-xss-protection X-XSS-Protection "1; mode=block" # Remove -Server header, which is an information leak # Remove Caddy from Headers -Server # keep referrer data off of HTTP connections # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy Referrer-Policy strict-origin-when-cross-origin } } :80${CADDY_SECURE_DOMAIN} { import security_headers # Signal reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000 # Management reverse_proxy /api/* management:80 reverse_proxy /management.ManagementService/* h2c://management:80 # Zitadel reverse_proxy /zitadel.admin.v1.AdminService/* h2c://zitadel:8080 reverse_proxy /admin/v1/* h2c://zitadel:8080 reverse_proxy /zitadel.auth.v1.AuthService/* h2c://zitadel:8080 reverse_proxy /auth/v1/* h2c://zitadel:8080 reverse_proxy /zitadel.management.v1.ManagementService/* h2c://zitadel:8080 reverse_proxy /management/v1/* h2c://zitadel:8080 reverse_proxy /zitadel.system.v1.SystemService/* h2c://zitadel:8080 reverse_proxy /system/v1/* h2c://zitadel:8080 reverse_proxy /assets/v1/* h2c://zitadel:8080 reverse_proxy /ui/* h2c://zitadel:8080 reverse_proxy /oidc/v1/* h2c://zitadel:8080 reverse_proxy /saml/v2/* h2c://zitadel:8080 reverse_proxy /oauth/v2/* h2c://zitadel:8080 reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080 reverse_proxy /openapi/* h2c://zitadel:8080 reverse_proxy /debug/* h2c://zitadel:8080 reverse_proxy /device/* h2c://zitadel:8080 reverse_proxy /device h2c://zitadel:8080 # Dashboard reverse_proxy /* dashboard:80 } EOF } renderTurnServerConf() { cat <<EOF listening-port=3478 $TURN_EXTERNAL_IP_CONFIG tls-listening-port=5349 min-port=$TURN_MIN_PORT max-port=$TURN_MAX_PORT fingerprint lt-cred-mech user=$TURN_USER:$TURN_PASSWORD realm=wiretrustee.com cert=/etc/coturn/certs/cert.pem pkey=/etc/coturn/private/privkey.pem log-file=stdout no-software-attribute pidfile="/var/tmp/turnserver.pid" no-cli EOF } renderManagementJson() { cat <<EOF { "Stuns": [ { "Proto": "udp", "URI": "stun:$NETBIRD_DOMAIN:3478" } ], "TURNConfig": { "Turns": [ { "Proto": "udp", "URI": "turn:$NETBIRD_DOMAIN:3478", "Username": "$TURN_USER", "Password": "$TURN_PASSWORD" } ], "TimeBasedCredentials": false }, "Signal": { "Proto": "$NETBIRD_HTTP_PROTOCOL", "URI": "$NETBIRD_DOMAIN:$NETBIRD_PORT" }, "HttpConfig": { "AuthIssuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN", "AuthAudience": "$NETBIRD_AUTH_CLIENT_ID", "OIDCConfigEndpoint":"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/.well-known/openid-configuration" }, "IdpManagerConfig": { "ManagerType": "zitadel", "ClientConfig": { "Issuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN", "TokenEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth/v2/token", "ClientID": "$NETBIRD_IDP_MGMT_CLIENT_ID", "ClientSecret": "$NETBIRD_IDP_MGMT_CLIENT_SECRET", "GrantType": "client_credentials" }, "ExtraConfig": { "ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1" } }, "DeviceAuthorizationFlow": { "Provider": "hosted", "ProviderConfig": { "Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI", "ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI", "Scope": "openid" } }, "PKCEAuthorizationFlow": { "ProviderConfig": { "Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI", "ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI", "Scope": "openid profile email offline_access", "RedirectURLs": ["http://localhost:53000/","http://localhost:54000/"] } } } EOF } renderDashboardEnv() { cat <<EOF # Endpoints NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN # OIDC AUTH_AUDIENCE=$NETBIRD_AUTH_CLIENT_ID AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID AUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN USE_AUTH0=false AUTH_SUPPORTED_SCOPES="openid profile email offline_access" AUTH_REDIRECT_URI=/nb-auth AUTH_SILENT_REDIRECT_URI=/nb-silent-auth # SSL NGINX_SSL_PORT=443 # Letsencrypt LETSENCRYPT_DOMAIN=none EOF } renderZitadelEnv() { cat <<EOF ZITADEL_LOG_LEVEL=debug ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY ZITADEL_DATABASE_COCKROACH_HOST=crdb ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/crdb-certs/ca.crt" ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/crdb-certs/client.zitadel_user.crt" ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/crdb-certs/client.zitadel_user.key" ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/crdb-certs/ca.crt" ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/crdb-certs/client.root.crt" ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/crdb-certs/client.root.key" ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE ZITADEL_TLS_ENABLED="false" ZITADEL_EXTERNALPORT=$NETBIRD_PORT ZITADEL_EXTERNALDOMAIN=$NETBIRD_DOMAIN ZITADEL_FIRSTINSTANCE_PATPATH=/machinekey/zitadel-admin-sa.token ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_SCOPES=openid ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE=$ZIDATE_TOKEN_EXPIRATION_DATE EOF } renderDockerCompose() { cat <<EOF version: "3.4" services: # Caddy reverse proxy caddy: image: caddy restart: unless-stopped networks: [ netbird ] ports: - '443:443' - '80:80' - '8080:8080' volumes: - netbird_caddy_data:/data - ./Caddyfile:/etc/caddy/Caddyfile #UI dashboard dashboard: image: netbirdio/dashboard:latest restart: unless-stopped networks: [netbird] env_file: - ./dashboard.env # Signal signal: image: netbirdio/signal:latest restart: unless-stopped networks: [netbird] # Management management: image: netbirdio/management:latest restart: unless-stopped networks: [netbird] volumes: - netbird_management:/var/lib/netbird - ./management.json:/etc/netbird/management.json command: [ "--port", "80", "--log-file", "console", "--log-level", "info", "--disable-anonymous-metrics=false", "--single-account-mode-domain=netbird.selfhosted", "--dns-domain=netbird.selfhosted", "--idp-sign-key-refresh-enabled", ] # Coturn, AKA relay server coturn: image: coturn/coturn restart: unless-stopped domainname: netbird.relay.selfhosted volumes: - ./turnserver.conf:/etc/turnserver.conf:ro network_mode: host command: - -c /etc/turnserver.conf # Zitadel - identity provider zitadel: restart: 'always' networks: [netbird] image: 'ghcr.io/zitadel/zitadel:v2.31.3' command: 'start-from-init --masterkeyFromEnv --tlsMode $ZITADEL_TLS_MODE' env_file: - ./zitadel.env depends_on: crdb: condition: 'service_healthy' volumes: - ./machinekey:/machinekey - netbird_zitadel_certs:/crdb-certs:ro # CockroachDB for zitadel crdb: restart: 'always' networks: [netbird] image: 'cockroachdb/cockroach:v22.2.2' command: 'start-single-node --advertise-addr crdb' volumes: - netbird_crdb_data:/cockroach/cockroach-data - netbird_crdb_certs:/cockroach/certs - netbird_zitadel_certs:/zitadel-certs healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ] interval: '10s' timeout: '30s' retries: 5 start_period: '20s' volumes: netbird_management: netbird_caddy_data: netbird_crdb_data: netbird_crdb_certs: netbird_zitadel_certs: networks: netbird: EOF } initEnvironment