Files
netbird/7d/03435efeeb/0/full.jsonl
braginini 5e3dd25b24 Checkpoint: 7d03435efeeb
Entire-Session: 263c810b-0eda-4afa-805b-a24c681d4fa5
Entire-Strategy: manual-commit
Entire-Agent: Claude Code
Ephemeral-branch: entire/d97fe84-e3b0c4
2026-04-01 19:13:00 +02:00

75 lines
534 KiB
JSON

{"type":"file-history-snapshot","messageId":"366b460e-5430-443d-89dc-514a1f34eb03","snapshot":{"messageId":"366b460e-5430-443d-89dc-514a1f34eb03","trackedFileBackups":{},"timestamp":"2026-04-01T16:53:52.738Z"},"isSnapshotUpdate":false}
{"parentUuid":null,"isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":"in the infrastructure_files/getting-started.sh in the NETBIRD SETUP COMPLETE output message add a note that this setup is great for homelab and poc in teh business environment but if they want to scale in teh enterprise environment the should look at onprem license or scaling the open source deployment: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment"},"uuid":"366b460e-5430-443d-89dc-514a1f34eb03","timestamp":"2026-04-01T16:53:51.734Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main"}
{"parentUuid":"366b460e-5430-443d-89dc-514a1f34eb03","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01PBBcrNpdokvfayDnz3hSxT","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01RWEeYajZX87xKvxJmAoEr1","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":4850,"cache_read_input_tokens":11270,"output_tokens":81,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":4850,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQAp4aDvj7RYkQDWFhK","type":"assistant","uuid":"f57bc95e-88d8-4e4a-b880-c3ac85ba4a19","timestamp":"2026-04-01T16:53:55.424Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"f57bc95e-88d8-4e4a-b880-c3ac85ba4a19","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"File content (15320 tokens) exceeds maximum allowed tokens (10000). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.","is_error":true,"tool_use_id":"toolu_01RWEeYajZX87xKvxJmAoEr1"}]},"uuid":"bd4a0a86-3fc8-48df-8078-378b2d97742d","timestamp":"2026-04-01T16:53:55.697Z","toolUseResult":"Error: File content (15320 tokens) exceeds maximum allowed tokens (10000). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.","sourceToolAssistantUUID":"f57bc95e-88d8-4e4a-b880-c3ac85ba4a19","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"bd4a0a86-3fc8-48df-8078-378b2d97742d","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_016jhh6yeojJ7KtEQot8BiFq","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_013XpBfJKh87LFeszfQiWPie","name":"Grep","input":{"pattern":"NETBIRD SETUP COMPLETE","path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","output_mode":"content","-n":true},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":4994,"cache_read_input_tokens":11270,"output_tokens":139,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":4994,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQB33BWRD79ukenumWf","type":"assistant","uuid":"e1a2bb38-d7f7-4a6b-9d47-b730a6235b60","timestamp":"2026-04-01T16:53:58.258Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"e1a2bb38-d7f7-4a6b-9d47-b730a6235b60","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_013XpBfJKh87LFeszfQiWPie","type":"tool_result","content":"1140: echo \" NETBIRD SETUP COMPLETE\""}]},"uuid":"059f04fa-4f1d-4cc2-84d1-8a70eccf90c6","timestamp":"2026-04-01T16:53:58.287Z","toolUseResult":{"mode":"content","numFiles":0,"filenames":[],"content":"1140: echo \" NETBIRD SETUP COMPLETE\"","numLines":1},"sourceToolAssistantUUID":"e1a2bb38-d7f7-4a6b-9d47-b730a6235b60","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"059f04fa-4f1d-4cc2-84d1-8a70eccf90c6","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_015S4XQqGD1inFN8sVXabPCT","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01JLiJ8R397RrzRzRk9kp3e6","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","offset":1130},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":166,"cache_read_input_tokens":16264,"output_tokens":98,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":166,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQBEBtywh49BjSS2R85","type":"assistant","uuid":"28b12c75-7156-45fe-b83c-1708ec090199","timestamp":"2026-04-01T16:54:01.092Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"28b12c75-7156-45fe-b83c-1708ec090199","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01JLiJ8R397RrzRzRk9kp3e6","type":"tool_result","content":"1130\t return 0\n1131\t}\n1132\t\n1133\t############################################\n1134\t# Post-Setup Instructions per Proxy Type\n1135\t############################################\n1136\t\n1137\tprint_builtin_traefik_instructions() {\n1138\t echo \"\"\n1139\t echo \"$MSG_SEPARATOR\"\n1140\t echo \" NETBIRD SETUP COMPLETE\"\n1141\t echo \"$MSG_SEPARATOR\"\n1142\t echo \"\"\n1143\t echo \"You can access the NetBird dashboard at:\"\n1144\t echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n1145\t echo \"\"\n1146\t echo \"Follow the onboarding steps to set up your NetBird instance.\"\n1147\t echo \"\"\n1148\t echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n1149\t echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n1150\t echo \"\"\n1151\t echo \"Open ports:\"\n1152\t echo \" - 443/tcp (HTTPS - all NetBird services)\"\n1153\t echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n1154\t echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n1155\t if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n1156\t echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n1157\t echo \"\"\n1158\t echo \"NetBird Proxy:\"\n1159\t echo \" The proxy service is enabled and running.\"\n1160\t echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n1161\t echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n1162\t echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n1163\t echo \"\"\n1164\t echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n1165\t echo \"\"\n1166\t fi\n1167\t return 0\n1168\t}\n1169\t\n1170\tprint_traefik_instructions() {\n1171\t echo \"\"\n1172\t echo \"$MSG_SEPARATOR\"\n1173\t echo \" TRAEFIK SETUP\"\n1174\t echo \"$MSG_SEPARATOR\"\n1175\t echo \"\"\n1176\t echo \"NetBird containers are configured with Traefik labels.\"\n1177\t echo \"\"\n1178\t echo \"Configuration:\"\n1179\t echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n1180\t if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n1181\t echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n1182\t fi\n1183\t if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n1184\t echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n1185\t else\n1186\t echo \" Network: netbird\"\n1187\t fi\n1188\t echo \"\"\n1189\t echo \"$MSG_NEXT_STEPS\"\n1190\t echo \" - Ensure Traefik is running and configured\"\n1191\t if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n1192\t echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n1193\t fi\n1194\t echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n1195\t if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n1196\t echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n1197\t fi\n1198\t echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n1199\t echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n1200\t echo \" - HTTP to HTTPS redirect (recommended)\"\n1201\t return 0\n1202\t}\n1203\t\n1204\tprint_nginx_instructions() {\n1205\t local bind_addr=$(get_bind_address)\n1206\t echo \"\"\n1207\t echo \"$MSG_SEPARATOR\"\n1208\t echo \" NGINX SETUP\"\n1209\t echo \"$MSG_SEPARATOR\"\n1210\t echo \"\"\n1211\t echo \"Generated: nginx-netbird.conf\"\n1212\t echo \"\"\n1213\t echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n1214\t echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n1215\t echo \"generated config file. The config includes examples for common certificate sources.\"\n1216\t echo \"\"\n1217\t if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n1218\t echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n1219\t echo \"The config uses container names for upstream servers.\"\n1220\t echo \"\"\n1221\t echo \"$MSG_NEXT_STEPS\"\n1222\t echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n1223\t echo \" (mount certificate directory as volume if needed)\"\n1224\t echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n1225\t echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n1226\t echo \" 3. Include the config in your Nginx container's configuration\"\n1227\t echo \" 4. Reload Nginx\"\n1228\t else\n1229\t echo \"$MSG_NEXT_STEPS\"\n1230\t echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n1231\t echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n1232\t echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n1233\t echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n1234\t echo \"\"\n1235\t echo \"For detailed TLS setup instructions, see:\"\n1236\t echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n1237\t echo \"\"\n1238\t echo \"Container ports (bound to ${bind_addr}):\"\n1239\t echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n1240\t echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n1241\t fi\n1242\t return 0\n1243\t}\n1244\t\n1245\tprint_npm_instructions() {\n1246\t local bind_addr=$(get_bind_address)\n1247\t local upstream_host=$(get_upstream_host)\n1248\t echo \"\"\n1249\t echo \"$MSG_SEPARATOR\"\n1250\t echo \" NGINX PROXY MANAGER SETUP\"\n1251\t echo \"$MSG_SEPARATOR\"\n1252\t echo \"\"\n1253\t echo \"Generated: npm-advanced-config.txt\"\n1254\t echo \"\"\n1255\t if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n1256\t echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n1257\t echo \"\"\n1258\t echo \"In NPM, create a Proxy Host:\"\n1259\t echo \" Domain: $NETBIRD_DOMAIN\"\n1260\t echo \" Forward Hostname: netbird-dashboard\"\n1261\t echo \" Forward Port: 80\"\n1262\t echo \" Block Common Exploits: enabled\"\n1263\t echo \"\"\n1264\t echo \" SSL tab:\"\n1265\t echo \" - Request or select existing certificate\"\n1266\t echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n1267\t echo \"\"\n1268\t echo \" Advanced tab:\"\n1269\t echo \" - Paste contents of npm-advanced-config.txt\"\n1270\t else\n1271\t echo \"Container ports (bound to ${bind_addr}):\"\n1272\t echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n1273\t echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n1274\t echo \"\"\n1275\t echo \"In NPM, create a Proxy Host:\"\n1276\t echo \" Domain: $NETBIRD_DOMAIN\"\n1277\t echo \" Forward Hostname/IP: ${upstream_host}\"\n1278\t echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n1279\t echo \" Block Common Exploits: enabled\"\n1280\t echo \"\"\n1281\t echo \" SSL tab:\"\n1282\t echo \" - Request or select existing certificate\"\n1283\t echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n1284\t echo \"\"\n1285\t echo \" Advanced tab:\"\n1286\t echo \" - Paste contents of npm-advanced-config.txt\"\n1287\t fi\n1288\t return 0\n1289\t}\n1290\t\n1291\tprint_external_caddy_instructions() {\n1292\t local bind_addr=$(get_bind_address)\n1293\t echo \"\"\n1294\t echo \"$MSG_SEPARATOR\"\n1295\t echo \" EXTERNAL CADDY SETUP\"\n1296\t echo \"$MSG_SEPARATOR\"\n1297\t echo \"\"\n1298\t echo \"Generated: caddyfile-netbird.txt\"\n1299\t echo \"\"\n1300\t if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n1301\t echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n1302\t echo \"The config uses container names for upstream servers.\"\n1303\t echo \"\"\n1304\t echo \"$MSG_NEXT_STEPS\"\n1305\t echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n1306\t echo \" 2. Reload Caddy\"\n1307\t else\n1308\t echo \"$MSG_NEXT_STEPS\"\n1309\t echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n1310\t echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n1311\t echo \"\"\n1312\t echo \"Container ports (bound to ${bind_addr}):\"\n1313\t echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n1314\t echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n1315\t fi\n1316\t return 0\n1317\t}\n1318\t\n1319\tprint_manual_instructions() {\n1320\t local bind_addr=$(get_bind_address)\n1321\t local upstream_host=$(get_upstream_host)\n1322\t echo \"\"\n1323\t echo \"$MSG_SEPARATOR\"\n1324\t echo \" MANUAL REVERSE PROXY SETUP\"\n1325\t echo \"$MSG_SEPARATOR\"\n1326\t echo \"\"\n1327\t echo \"Container ports (bound to ${bind_addr}):\"\n1328\t echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n1329\t echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n1330\t echo \"\"\n1331\t echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n1332\t echo \"\"\n1333\t echo \" WebSocket (relay, signal, management WS proxy):\"\n1334\t echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n1335\t echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n1336\t echo \"\"\n1337\t echo \" Native gRPC (signal + management):\"\n1338\t echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n1339\t echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n1340\t echo \" (gRPC/h2c - plaintext HTTP/2)\"\n1341\t echo \"\"\n1342\t echo \" HTTP (API + embedded IdP):\"\n1343\t echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n1344\t echo \"\"\n1345\t echo \" Dashboard (catch-all):\"\n1346\t echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n1347\t echo \"\"\n1348\t echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n1349\t echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n1350\t return 0\n1351\t}\n1352\t\n1353\tprint_post_setup_instructions() {\n1354\t case \"$REVERSE_PROXY_TYPE\" in\n1355\t 0)\n1356\t print_builtin_traefik_instructions\n1357\t ;;\n1358\t 1)\n1359\t print_traefik_instructions\n1360\t ;;\n1361\t 2)\n1362\t print_nginx_instructions\n1363\t ;;\n1364\t 3)\n1365\t print_npm_instructions\n1366\t ;;\n1367\t 4)\n1368\t print_external_caddy_instructions\n1369\t ;;\n1370\t 5)\n1371\t print_manual_instructions\n1372\t ;;\n1373\t *)\n1374\t echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n1375\t ;;\n1376\t esac\n1377\t return 0\n1378\t}\n1379\t\n1380\tinit_environment\n1381\t"}]},"uuid":"04fe91f9-dd32-4e78-86f5-c2105feda0e0","timestamp":"2026-04-01T16:54:01.100Z","toolUseResult":{"type":"text","file":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","content":" return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment\n","numLines":252,"startLine":1130,"totalLines":1381}},"sourceToolAssistantUUID":"28b12c75-7156-45fe-b83c-1708ec090199","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"04fe91f9-dd32-4e78-86f5-c2105feda0e0","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01RvQxJoz3qa5587SkWMRhTK","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"Ep8MClkIDBgCKkAD5M+brSIv8cA8uh6gq2OgQvMHa1gMhbX55SBV3iDKrdtiExWg9c8QyHSOV2SfABadbCsxtdsFuNmFA3T+554FMg9jbGF1ZGUtb3B1cy00LTY4ABIMjUn49vnlKCHvfEN6GgxRCDQeVQTZ7uX1p6siMCfG+ZilRKodH1vy8u9mf20P4SntWiqhPjx/0nm/syXaUeda/JANr+QCvRofgIdCXSrzChWqiG5BYRGQtRg2eBs6A7tbYZ0qRx33V1ermn6DMF/cyl2JI244NtyIFF8IiAz4kba4Rn0+chjHUfD7NE6KVUc5RjeTCRrMq2FWvUlPnlnieOtKy41y5RhTvbh+uHAzttFhMQDSuSqAhY6274RzKyKKAMfRdXiX9sju0rLDIATn6xqdAIDpTNw3fT22irX712AgL0UjDUzn62MkpTra+eXM1cNK2wgOvEIkQDLjkYVZ8AtajiJPcPaFHpaa74F0/2kJpi82pw//XxZqdf53eSGZQUz155b2/XC7RDd0+3cJD8XEI5360a5kwR21Sjz0EwmDeQgPmjGLR4/PWf6YDpFtIsLreD8TQ74biO/UpyeFcFg0M98sxOChP1hpm05WfebQ9IvjARqsyi/ppqep8lqwu5+xCUSQGqVwvptRctj42CEawqdQiE+I3x3angMmhMQ90+yuycxbhdhFHWtYr4hvWEr+ENSD1YI27OPLwwHeOVA38/dK0Yye8oUW9bIG8rk8MSUd6FachTcKw3imdYqJe2TPkb3GGtKOqOLApH6bw7YHytA97f4G/EuxtM98VPQvpAIyPJHUYA7xkJ7LyVoTqNcVEZ3Gfe47x/59VffYcMZJeswQA8sSI83WuTst4xkDXoX57PHnoUOsiozDdJoOP1KxVI+XpILfXiT8n6Zo4UbFHOG/u3ijEI12ulWLB9VAUDSU/ho85C36JNwLuXC4EH7sIR6ti1lXwb3ueBY9WRSIAz3xMoXAQhDZz9WSvPKD17sowrRwNPOhGSPqL5vgB1fXu2O192FrHk1kPdgR06cfgk1kxNm3dlmyNNubcOJ7X34FDmPYAmqpyYPJPP7Npx1wntgIFlLeD26NQiDf6oojWuGGCIyfe3rxRRmQR9NdGhBeNKjGijqdrC2GFcddJOJLd3+9oOlzzivQC7Q3gNQlASC8Gt+FIF79n0zoQG/K18ejl5pRCqtCSQs6PAHpcGQ7QGklOFGfKljA3zzcvdKX0pOccAaEnaU/cFI0Xtj0VSMmv3dCiRAlubKj/jVpaZUNlnIaq1JVVT+JzrhhWfrMeT01P7KL22te4ebcJ9P93gw7Q9pzSr7UUcUaZ8yXILApKitOMjfNKcM/Ie/LTb5Wlos7M0okjsfgjF27fneGwsgl6L9xnwhNT+ilZlluf5+i9HZQuj9y6RLf1ndN9Nxw/5wwZxH8IiXZ8qBrnO6sklixLYJgeqILqCydeaXnBEg9MlVS4h12PTvlv53TFbug1zMjlzL/3vEgi5vylshyNfhHwfpSmqLIwZ5vjy/Bl9FpQYhptw2I10H5QtfIq2+tQS06qDjF9OByrld5WKISlicedk8wt8Q59ZOd6FYCYXNQQrX37bmNs/aZDHWtbqjra2hOe2Zr9M0IKABb/AnxetNBy8ocrL3CGvMfwFFktEweiF+MxVyAad6lTCk5vSwXZAVHfNrvFygXZbChhGYyT4v8QLvp8cXdLd37zHbTN7GLS3ngoLfLaf2TVkG8mEvzxkiTSYBkJHVWgK6q8K75g48Xb/YFTyduGKSOwRwPlvyCOdJW2Ld+xK7ef+HG8kM6bndRAaxyC9PNy1NNaDKJQqJ5pLCW8WuGDKzR7tQOVYOZjKO7+BaoPn0lGV1ZlW4Cpxa2ioRiQNcCW6Kwly7N3Ozd5XYa4niYaOCxS8Uky7S8swXXyKeIyzRwFtizqaE3oNvhs2EdcSwxqzRIMClHhFjpn3xO82RHVCI4MiDeE9IwjO2kg7z9ivas9Ly54vyOWbUbhwk6wkTf53J5Kke/oS15Wt1h+5TKLlo+RPwH6bXFzv79RFWssznc54YmFHAc+x2b291125nAOcZICzh+QxgB"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":3773,"cache_read_input_tokens":16430,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":3773},"output_tokens":30,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdQBRx5RDUengifPRfRe","type":"assistant","uuid":"5622550a-7b37-42e6-aba3-e766850ebe9d","timestamp":"2026-04-01T16:54:10.534Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"5622550a-7b37-42e6-aba3-e766850ebe9d","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01RvQxJoz3qa5587SkWMRhTK","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_012WEPsn6qHNJsZo9Pmtp469","name":"Grep","input":{"pattern":"print_post_setup_instructions","path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","output_mode":"content","-n":true},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":3773,"cache_read_input_tokens":16430,"output_tokens":493,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":3773,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQBRx5RDUengifPRfRe","type":"assistant","uuid":"3414691c-aebe-4a0b-94d3-0e437da3c31c","timestamp":"2026-04-01T16:54:12.102Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"3414691c-aebe-4a0b-94d3-0e437da3c31c","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_012WEPsn6qHNJsZo9Pmtp469","type":"tool_result","content":"456: print_post_setup_instructions\n467: print_post_setup_instructions\n481: print_post_setup_instructions\n487: print_post_setup_instructions\n1353:print_post_setup_instructions() {"}]},"uuid":"4da432cf-3d7f-4966-9e2e-f89facd95dba","timestamp":"2026-04-01T16:54:12.118Z","toolUseResult":{"mode":"content","numFiles":0,"filenames":[],"content":"456: print_post_setup_instructions\n467: print_post_setup_instructions\n481: print_post_setup_instructions\n487: print_post_setup_instructions\n1353:print_post_setup_instructions() {","numLines":5},"sourceToolAssistantUUID":"3414691c-aebe-4a0b-94d3-0e437da3c31c","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"4da432cf-3d7f-4966-9e2e-f89facd95dba","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01PPCojSPxr8A2sN3hSgu7m8","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01HeSg3T4YsqwDzzVhxA7Vs3","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","offset":450,"limit":50},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":561,"cache_read_input_tokens":20203,"output_tokens":114,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":561,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQCEuqETE7AkjxcYn3G","type":"assistant","uuid":"df0fd795-9ed1-4f5f-be65-28c302d726ac","timestamp":"2026-04-01T16:54:14.426Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"df0fd795-9ed1-4f5f-be65-28c302d726ac","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01HeSg3T4YsqwDzzVhxA7Vs3","type":"tool_result","content":"450\t\n451\t sleep 3\n452\t wait_management_proxy traefik\n453\t fi\n454\t\n455\t echo -e \"$MSG_DONE\"\n456\t print_post_setup_instructions\n457\t elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n458\t # External Traefik - start containers, then show instructions\n459\t # Traefik discovers services via Docker labels, so containers must be running\n460\t echo -e \"$MSG_STARTING_SERVICES\"\n461\t $DOCKER_COMPOSE_COMMAND up -d\n462\t\n463\t sleep 3\n464\t wait_management_direct\n465\t\n466\t echo -e \"$MSG_DONE\"\n467\t print_post_setup_instructions\n468\t echo \"\"\n469\t echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n470\t echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n471\t elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n472\t # NPM - start containers first, then show instructions\n473\t # NPM requires backend services to be running before creating proxy hosts\n474\t echo -e \"$MSG_STARTING_SERVICES\"\n475\t $DOCKER_COMPOSE_COMMAND up -d\n476\t\n477\t sleep 3\n478\t wait_management_direct\n479\t\n480\t echo -e \"$MSG_DONE\"\n481\t print_post_setup_instructions\n482\t echo \"\"\n483\t echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n484\t echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n485\t else\n486\t # External proxies (nginx, external Caddy, other) - need manual config first\n487\t print_post_setup_instructions\n488\t\n489\t echo \"\"\n490\t echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n491\t read -r < /dev/tty\n492\t\n493\t echo -e \"$MSG_STARTING_SERVICES\"\n494\t $DOCKER_COMPOSE_COMMAND up -d\n495\t\n496\t sleep 3\n497\t wait_management_direct\n498\t\n499\t echo -e \"$MSG_DONE\""}]},"uuid":"15c29bab-a741-407e-aff2-e695ad2b7543","timestamp":"2026-04-01T16:54:14.434Z","toolUseResult":{"type":"text","file":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","content":"\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"","numLines":50,"startLine":450,"totalLines":1381}},"sourceToolAssistantUUID":"df0fd795-9ed1-4f5f-be65-28c302d726ac","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"15c29bab-a741-407e-aff2-e695ad2b7543","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_015FHEmn2jNvxb6Ao99UN4cF","type":"message","role":"assistant","content":[{"type":"text","text":"I see the note should go in `print_builtin_traefik_instructions` since that's the one with \"NETBIRD SETUP COMPLETE\". I'll add it before the `return 0`."}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":719,"cache_read_input_tokens":20764,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":719},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdQCQqdP6hM2Rm5gqSGR","type":"assistant","uuid":"665a4183-1bfe-4703-ab6d-f2d3647fcf6a","timestamp":"2026-04-01T16:54:17.231Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"665a4183-1bfe-4703-ab6d-f2d3647fcf6a","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_015FHEmn2jNvxb6Ao99UN4cF","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_018Nfrahnqcw6eK7ijnYSU26","name":"Edit","input":{"replace_all":false,"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","old_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}","new_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and proof-of-concept deployments.\"\n echo \"For scaling in enterprise environments, consider an on-prem license or\"\n echo \"scaling your open-source deployment:\"\n echo \" https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":719,"cache_read_input_tokens":20764,"output_tokens":431,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":719,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQCQqdP6hM2Rm5gqSGR","type":"assistant","uuid":"191edb22-86af-4e3a-9c28-2094c1e53672","timestamp":"2026-04-01T16:54:22.421Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"191edb22-86af-4e3a-9c28-2094c1e53672","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.\n\nNote: The user's next message may contain a correction or preference. Pay close attention — if they explain what went wrong or how they'd prefer you to work, consider saving that to memory for future sessions.","is_error":true,"tool_use_id":"toolu_018Nfrahnqcw6eK7ijnYSU26"}]},"uuid":"480590ed-4318-4a4c-8863-ca7cea003e12","timestamp":"2026-04-01T16:54:40.049Z","toolUseResult":"User rejected tool use","sourceToolAssistantUUID":"191edb22-86af-4e3a-9c28-2094c1e53672","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"480590ed-4318-4a4c-8863-ca7cea003e12","isSidechain":false,"promptId":"6252f876-83e8-4683-b13e-45be61aa7880","type":"user","message":{"role":"user","content":[{"type":"text","text":"[Request interrupted by user for tool use]"}]},"uuid":"cbc5bfea-c9a8-4b2e-9f91-8236dc0ffc01","timestamp":"2026-04-01T16:54:40.052Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"e7f2744a-e160-4b8d-b3df-da93fb5ada9e","snapshot":{"messageId":"e7f2744a-e160-4b8d-b3df-da93fb5ada9e","trackedFileBackups":{},"timestamp":"2026-04-01T16:54:57.425Z"},"isSnapshotUpdate":false}
{"parentUuid":"cbc5bfea-c9a8-4b2e-9f91-8236dc0ffc01","isSidechain":false,"promptId":"7ffcff69-5148-4804-b616-2ae59c9742e5","type":"user","message":{"role":"user","content":"thats good but maybe instead of poc smaller org deployments"},"uuid":"e7f2744a-e160-4b8d-b3df-da93fb5ada9e","timestamp":"2026-04-01T16:54:56.688Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"e7f2744a-e160-4b8d-b3df-da93fb5ada9e","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01EXa5yk7i3Nowxq8QQmVi3P","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_011QxsvBzGFQWkUnmKrSg2my","name":"Edit","input":{"replace_all":false,"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","old_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}","new_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For scaling in enterprise environments, consider an on-prem license or\"\n echo \"scaling your open-source deployment:\"\n echo \" https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":671,"cache_read_input_tokens":21483,"output_tokens":402,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":671,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQFaTQeRgfuXeMTArFs","type":"assistant","uuid":"f0bb2036-bdea-4593-9606-68d061502cb2","timestamp":"2026-04-01T16:55:01.666Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"f0bb2036-bdea-4593-9606-68d061502cb2","isSidechain":false,"promptId":"7ffcff69-5148-4804-b616-2ae59c9742e5","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.\n\nNote: The user's next message may contain a correction or preference. Pay close attention — if they explain what went wrong or how they'd prefer you to work, consider saving that to memory for future sessions.","is_error":true,"tool_use_id":"toolu_011QxsvBzGFQWkUnmKrSg2my"}]},"uuid":"ee1e1f07-0709-435d-8a9f-00d0c59f768a","timestamp":"2026-04-01T16:55:38.156Z","toolUseResult":"User rejected tool use","sourceToolAssistantUUID":"f0bb2036-bdea-4593-9606-68d061502cb2","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"ee1e1f07-0709-435d-8a9f-00d0c59f768a","isSidechain":false,"promptId":"7ffcff69-5148-4804-b616-2ae59c9742e5","type":"user","message":{"role":"user","content":[{"type":"text","text":"[Request interrupted by user for tool use]"}]},"uuid":"f3a559b6-94f1-4949-8eef-19bd1dee88ca","timestamp":"2026-04-01T16:55:38.160Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"7d835c2a-33a8-44a3-95b6-23cbdb945d79","snapshot":{"messageId":"7d835c2a-33a8-44a3-95b6-23cbdb945d79","trackedFileBackups":{},"timestamp":"2026-04-01T16:55:51.424Z"},"isSnapshotUpdate":false}
{"parentUuid":"f3a559b6-94f1-4949-8eef-19bd1dee88ca","isSidechain":false,"promptId":"2dfb5489-6cda-43e0-8c40-6f7d6f4578ab","type":"user","message":{"role":"user","content":"god but for commercial license and somethign around: for high availability and integrations"},"uuid":"7d835c2a-33a8-44a3-95b6-23cbdb945d79","timestamp":"2026-04-01T16:55:50.718Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"7d835c2a-33a8-44a3-95b6-23cbdb945d79","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01Ef8h8BuZJyyCY5kgd6zMji","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01QcN5xXVevg5YhwVkz4JsoT","name":"Edit","input":{"replace_all":false,"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","old_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}","new_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial license or scaling your open-source deployment:\"\n echo \" https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":630,"cache_read_input_tokens":22154,"output_tokens":402,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":630,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQKZGLUre3pZxcvoiDw","type":"assistant","uuid":"310149a8-d716-40af-83a5-cb7275432a19","timestamp":"2026-04-01T16:55:55.940Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"310149a8-d716-40af-83a5-cb7275432a19","isSidechain":false,"promptId":"2dfb5489-6cda-43e0-8c40-6f7d6f4578ab","type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.\n\nNote: The user's next message may contain a correction or preference. Pay close attention — if they explain what went wrong or how they'd prefer you to work, consider saving that to memory for future sessions.","is_error":true,"tool_use_id":"toolu_01QcN5xXVevg5YhwVkz4JsoT"}]},"uuid":"0a8eef26-5317-4800-8c05-c6dc6a5d521b","timestamp":"2026-04-01T16:56:09.641Z","toolUseResult":"User rejected tool use","sourceToolAssistantUUID":"310149a8-d716-40af-83a5-cb7275432a19","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"0a8eef26-5317-4800-8c05-c6dc6a5d521b","isSidechain":false,"promptId":"2dfb5489-6cda-43e0-8c40-6f7d6f4578ab","type":"user","message":{"role":"user","content":[{"type":"text","text":"[Request interrupted by user for tool use]"}]},"uuid":"0398594c-42ab-405f-a6be-60ed94adc8fe","timestamp":"2026-04-01T16:56:09.642Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"fbeb97af-0ef2-4d6f-bfd7-94cab7bbcfef","snapshot":{"messageId":"fbeb97af-0ef2-4d6f-bfd7-94cab7bbcfef","trackedFileBackups":{},"timestamp":"2026-04-01T16:56:29.843Z"},"isSnapshotUpdate":false}
{"parentUuid":"0398594c-42ab-405f-a6be-60ed94adc8fe","isSidechain":false,"promptId":"3f349554-91ee-4b1c-bb22-296a4f4f6c70","type":"user","message":{"role":"user","content":"add link to on prem: https://netbird.io/pricing#on-prem"},"uuid":"fbeb97af-0ef2-4d6f-bfd7-94cab7bbcfef","timestamp":"2026-04-01T16:56:29.117Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"fbeb97af-0ef2-4d6f-bfd7-94cab7bbcfef","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01737AfCc2pqdDgdewWRwTK5","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01XTpWrFEBeKHmH3PDQqUtf5","name":"Edit","input":{"replace_all":false,"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","old_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}","new_string":" echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial license or scaling your open-source deployment:\"\n echo \" On-prem license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":633,"cache_read_input_tokens":22784,"output_tokens":432,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":633,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQNQGvZ8mBJTWuwh1yT","type":"assistant","uuid":"533d9ee2-9e38-44c5-884a-5a5a69c5a38e","timestamp":"2026-04-01T16:56:37.881Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"533d9ee2-9e38-44c5-884a-5a5a69c5a38e","snapshot":{"messageId":"fbeb97af-0ef2-4d6f-bfd7-94cab7bbcfef","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v1","version":1,"backupTime":"2026-04-01T16:57:13.963Z"}},"timestamp":"2026-04-01T16:56:29.843Z"},"isSnapshotUpdate":true}
{"parentUuid":"533d9ee2-9e38-44c5-884a-5a5a69c5a38e","isSidechain":false,"promptId":"3f349554-91ee-4b1c-bb22-296a4f4f6c70","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01XTpWrFEBeKHmH3PDQqUtf5","type":"tool_result","content":"The file /Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh has been updated successfully. The user modified your proposed changes before accepting them. ."}]},"uuid":"735485c6-fdb7-4e64-b7a9-575020f2ebdc","timestamp":"2026-04-01T16:57:13.967Z","toolUseResult":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","oldString":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment","newString":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment","originalFile":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment\n","structuredPatch":[{"oldStart":1164,"oldLines":6,"newStart":1164,"newLines":12,"lines":[" echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\""," echo \"\""," fi","+ echo \"This setup is ideal for homelabs and smaller organization deployments.\"","+ echo \"For enterprise environments requiring high availability and integrations,\"","+ echo \"consider a commercial on-prem license or scaling your open source deployment:\"","+ echo \" Commercial license: https://netbird.io/pricing#on-prem\"","+ echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"","+ echo \"\""," return 0"," }"," "]}],"userModified":true,"replaceAll":false},"sourceToolAssistantUUID":"533d9ee2-9e38-44c5-884a-5a5a69c5a38e","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"735485c6-fdb7-4e64-b7a9-575020f2ebdc","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_018R3noKuizkxYMBCqaGoyQW","type":"message","role":"assistant","content":[{"type":"text","text":"Done. The note has been added to the setup complete message with both the on-prem license and scaling guide links. Let me verify what was actually written since the user modified the changes."}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":603,"cache_read_input_tokens":23417,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":603},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdQRf3GTYFcDruY6xFv5","type":"assistant","uuid":"a40fee39-9854-4aeb-8be5-4c6124e1f65f","timestamp":"2026-04-01T16:57:17.739Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"a40fee39-9854-4aeb-8be5-4c6124e1f65f","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_018R3noKuizkxYMBCqaGoyQW","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01Sv53XK7Vv3PkgqCc1dBPyx","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","offset":1165,"limit":15},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":603,"cache_read_input_tokens":23417,"output_tokens":155,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":603,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQRf3GTYFcDruY6xFv5","type":"assistant","uuid":"1d147702-084b-48fc-972a-711cc1dd786d","timestamp":"2026-04-01T16:57:18.160Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"1d147702-084b-48fc-972a-711cc1dd786d","isSidechain":false,"promptId":"3f349554-91ee-4b1c-bb22-296a4f4f6c70","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01Sv53XK7Vv3PkgqCc1dBPyx","type":"tool_result","content":"1165\t echo \"\"\n1166\t fi\n1167\t echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n1168\t echo \"For enterprise environments requiring high availability and integrations,\"\n1169\t echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n1170\t echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n1171\t echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n1172\t echo \"\"\n1173\t return 0\n1174\t}\n1175\t\n1176\tprint_traefik_instructions() {\n1177\t echo \"\"\n1178\t echo \"$MSG_SEPARATOR\"\n1179\t echo \" TRAEFIK SETUP\""}]},"uuid":"15c30c8d-46f8-4946-9d50-616ef09f4a5a","timestamp":"2026-04-01T16:57:18.190Z","toolUseResult":{"type":"text","file":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","content":" echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"","numLines":15,"startLine":1165,"totalLines":1387}},"sourceToolAssistantUUID":"1d147702-084b-48fc-972a-711cc1dd786d","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"15c30c8d-46f8-4946-9d50-616ef09f4a5a","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01FpEAZYu8RrfGsRYUp5VqMu","type":"message","role":"assistant","content":[{"type":"text","text":"Looks good. The user tweaked \"on-prem license\" to \"commercial on-prem license\" and \"On-prem license\" to \"Commercial license\" in the final version."}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":799,"cache_read_input_tokens":24020,"output_tokens":45,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":799,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQRxV17fDWUpmugcpN9","type":"assistant","uuid":"949869f9-a0d7-41c8-beee-4cda317e27d5","timestamp":"2026-04-01T16:57:20.466Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"949869f9-a0d7-41c8-beee-4cda317e27d5","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":5025}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T16:57:25.514Z","uuid":"fa8d85b1-d5fd-4156-a71b-31fec213db5e","toolUseID":"345e9453-9293-4387-82a2-f643a5b9581b","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"fa8d85b1-d5fd-4156-a71b-31fec213db5e","isSidechain":false,"promptId":"7d377b44-ddd1-4ff9-996d-07a1c0aebada","type":"user","message":{"role":"user","content":"output it just like if it is output by a script of give me a bash script that I can run manually just to see teh output with dummy domains: demo.netbird.io"},"uuid":"124e2c0f-cc12-497c-a342-a6faa54f6a52","timestamp":"2026-04-01T16:57:58.323Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"124e2c0f-cc12-497c-a342-a6faa54f6a52","snapshot":{"messageId":"124e2c0f-cc12-497c-a342-a6faa54f6a52","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v2","version":2,"backupTime":"2026-04-01T16:57:59.553Z"}},"timestamp":"2026-04-01T16:57:59.553Z"},"isSnapshotUpdate":false}
{"parentUuid":"124e2c0f-cc12-497c-a342-a6faa54f6a52","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_014FsBunF6hfeiKTSwaYJiF6","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"Ep4CClkIDBgCKkCRlGYnKlG9MB1WmbWIQStl+vxbhcRUmdewPNX0xtbdkM2ICT3u5Utmt7C+yqmwsGIvKmwEl49QsQBtswg5uRXIMg9jbGF1ZGUtb3B1cy00LTY4ABIMrvd3PNeQN/HsTaBUGgzQM+TxxsHzIJ4umCkiMLS0KsSLS9hNCxj8XUkBqqtOch2FS1b0fBq837wBh6ompKkmY8z4oZNX7B8VxWexgSpzphR9LiKocg2uK+t+6guMPMVbjHfDGay7e4vACgs6D3FAzuk07FyXUzv+SWNwi3EZaaZhOSNQdZhOxxdgDV7QcWrrKm1fOgmKPZMr+K2zY/V2Nb2Zgl+h9GAHy8br0008/IcB70BYNQaoRdhr9Ny10rCEnBgB"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":162,"cache_read_input_tokens":24819,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":162},"output_tokens":25,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdQV2aefCBiwTmrVC4Tv","type":"assistant","uuid":"cb57a1a3-e97f-45b1-bf04-d6714a044103","timestamp":"2026-04-01T16:58:02.208Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"cb57a1a3-e97f-45b1-bf04-d6714a044103","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_014FsBunF6hfeiKTSwaYJiF6","type":"message","role":"assistant","content":[{"type":"text","text":"```bash\n#!/bin/bash\nMSG_SEPARATOR=\"============================================\"\nNETBIRD_DOMAIN=\"demo.netbird.io\"\nNETBIRD_HTTP_PROTOCOL=\"https\"\nNETBIRD_STUN_PORT=\"3478\"\nENABLE_PROXY=\"false\"\n\necho \"\"\necho \"$MSG_SEPARATOR\"\necho \" NETBIRD SETUP COMPLETE\"\necho \"$MSG_SEPARATOR\"\necho \"\"\necho \"You can access the NetBird dashboard at:\"\necho \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\necho \"\"\necho \"Follow the onboarding steps to set up your NetBird instance.\"\necho \"\"\necho \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\necho \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\necho \"\"\necho \"Open ports:\"\necho \" - 443/tcp (HTTPS - all NetBird services)\"\necho \" - 80/tcp (HTTP - redirects to HTTPS)\"\necho \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\necho \"\"\necho \"This setup is ideal for homelabs and smaller organization deployments.\"\necho \"For enterprise environments requiring high availability and integrations,\"\necho \"consider a commercial on-prem license or scaling your open source deployment:\"\necho \" Commercial license: https://netbird.io/pricing#on-prem\"\necho \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\necho \"\"\n```\n\nYou can run it with `! bash` or just paste it in your terminal."}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":162,"cache_read_input_tokens":24819,"output_tokens":449,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":162,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQV2aefCBiwTmrVC4Tv","type":"assistant","uuid":"1b5c7149-c0ce-4bcd-b0ae-aa304a683428","timestamp":"2026-04-01T16:58:07.756Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"1b5c7149-c0ce-4bcd-b0ae-aa304a683428","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":3540}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T16:58:11.317Z","uuid":"8cca576d-5cb1-433d-93c2-619cf356de38","toolUseID":"c4c1df11-e5ab-4e6c-ac5e-8ce14bf799d8","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"29769e31-99d1-4178-b60b-5c6bb600ddcb","snapshot":{"messageId":"29769e31-99d1-4178-b60b-5c6bb600ddcb","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v2","version":2,"backupTime":"2026-04-01T16:57:59.553Z"}},"timestamp":"2026-04-01T16:58:52.697Z"},"isSnapshotUpdate":false}
{"parentUuid":"8cca576d-5cb1-433d-93c2-619cf356de38","isSidechain":false,"promptId":"a2e4b5fa-ef3e-4710-8e17-1fff024df91d","type":"user","message":{"role":"user","content":"proxy has to be output too"},"uuid":"29769e31-99d1-4178-b60b-5c6bb600ddcb","timestamp":"2026-04-01T16:58:51.362Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"29769e31-99d1-4178-b60b-5c6bb600ddcb","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01YBdS6YTHVHRG1QgDdR938R","type":"message","role":"assistant","content":[{"type":"text","text":"\n\n```bash\n#!/bin/bash\nMSG_SEPARATOR=\"============================================\"\nNETBIRD_DOMAIN=\"demo.netbird.io\"\nNETBIRD_HTTP_PROTOCOL=\"https\"\nNETBIRD_STUN_PORT=\"3478\"\nENABLE_PROXY=\"true\"\n\necho \"\"\necho \"$MSG_SEPARATOR\"\necho \" NETBIRD SETUP COMPLETE\"\necho \"$MSG_SEPARATOR\"\necho \"\"\necho \"You can access the NetBird dashboard at:\"\necho \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\necho \"\"\necho \"Follow the onboarding steps to set up your NetBird instance.\"\necho \"\"\necho \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\necho \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\necho \"\"\necho \"Open ports:\"\necho \" - 443/tcp (HTTPS - all NetBird services)\"\necho \" - 80/tcp (HTTP - redirects to HTTPS)\"\necho \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\necho \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\necho \"\"\necho \"NetBird Proxy:\"\necho \" The proxy service is enabled and running.\"\necho \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\necho \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\necho \" Point your proxy domain to this server's domain address like in the examples below:\"\necho \"\"\necho \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\necho \"\"\necho \"This setup is ideal for homelabs and smaller organization deployments.\"\necho \"For enterprise environments requiring high availability and integrations,\"\necho \"consider a commercial on-prem license or scaling your open source deployment:\"\necho \" Commercial license: https://netbird.io/pricing#on-prem\"\necho \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\necho \"\"\n```"}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":457,"cache_read_input_tokens":24981,"output_tokens":543,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":457,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQYvFkYeRwbxFoD3mNo","type":"assistant","uuid":"74c44976-ec3b-49b3-b776-556fd8257668","timestamp":"2026-04-01T16:58:59.984Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"74c44976-ec3b-49b3-b776-556fd8257668","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":3526}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T16:59:03.515Z","uuid":"05d95176-0a1b-4066-80f8-859a716e7b3d","toolUseID":"997a7db2-6001-4988-988c-6036f79f61f4","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"7f98be80-3875-4083-a91c-3bc5fee9bdca","snapshot":{"messageId":"7f98be80-3875-4083-a91c-3bc5fee9bdca","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v2","version":2,"backupTime":"2026-04-01T16:57:59.553Z"}},"timestamp":"2026-04-01T17:00:09.958Z"},"isSnapshotUpdate":false}
{"parentUuid":"05d95176-0a1b-4066-80f8-859a716e7b3d","isSidechain":false,"promptId":"fd5a112e-5203-436a-9e96-66bd2a0c09f9","type":"user","message":{"role":"user","content":"I thibk that in teh script we should put this text before if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then"},"uuid":"7f98be80-3875-4083-a91c-3bc5fee9bdca","timestamp":"2026-04-01T17:00:08.622Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"7f98be80-3875-4083-a91c-3bc5fee9bdca","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01Tpu5uECg6fFbv5RiMvKzeU","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"EtYCClkIDBgCKkAIJLDj0Tk5OwFYb78tfMn7Gu+yMm41D1xtT3yuOmTMOzpBNARy7ubqM38YbmoGEzdbABZMJPYtQkgET0f6i7VbMg9jbGF1ZGUtb3B1cy00LTY4ABIMpYk0QOK2hZuwoUeAGgy6csDVIghmM6h9LdIiMA13OEv2IVx8VKuARY2kSxTsXqXO7CDZbOOAvAb7am+Z5d6R7vt/YW20agwBIVgYlCqqAadTQjYgCHT+7Utd80hmxan0X6JL0ghCkdaHzniCPZy7hFZwevUNMh1zOKSSk+nBsEA/l30lnrhxmrZWs47ITwRdVIq65tPPUS7SaCZ+VNdWsJnzQShYnn4E7SU1UgdubanVST9Waed15V6PUQoGIAdeK7Ifyx7LOVWfngGoaBPbeysvh6Hxk6SvNzwUUKuUMMkw2ZRZZ+jbJW2VjqL6XCcoJkyANQPPNAnQGAE="}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":667,"cache_read_input_tokens":25438,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":667},"output_tokens":33,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdQedA2GokE1tRS6dnBa","type":"assistant","uuid":"835d1618-7944-4bab-b983-a8c1b54f1a6f","timestamp":"2026-04-01T17:00:13.415Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"835d1618-7944-4bab-b983-a8c1b54f1a6f","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01Tpu5uECg6fFbv5RiMvKzeU","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_012ZM3LmQ18HDpVT8ymGXndV","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","offset":1148,"limit":30},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":667,"cache_read_input_tokens":25438,"output_tokens":162,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":667,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQedA2GokE1tRS6dnBa","type":"assistant","uuid":"53821e5f-2c97-48fe-9e7c-137fb73e15e2","timestamp":"2026-04-01T17:00:13.940Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"53821e5f-2c97-48fe-9e7c-137fb73e15e2","isSidechain":false,"promptId":"fd5a112e-5203-436a-9e96-66bd2a0c09f9","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_012ZM3LmQ18HDpVT8ymGXndV","type":"tool_result","content":"1148\t echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n1149\t echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n1150\t echo \"\"\n1151\t echo \"Open ports:\"\n1152\t echo \" - 443/tcp (HTTPS - all NetBird services)\"\n1153\t echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n1154\t echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n1155\t if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n1156\t echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n1157\t echo \"\"\n1158\t echo \"NetBird Proxy:\"\n1159\t echo \" The proxy service is enabled and running.\"\n1160\t echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n1161\t echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n1162\t echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n1163\t echo \"\"\n1164\t echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n1165\t echo \"\"\n1166\t fi\n1167\t echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n1168\t echo \"For enterprise environments requiring high availability and integrations,\"\n1169\t echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n1170\t echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n1171\t echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n1172\t echo \"\"\n1173\t return 0\n1174\t}\n1175\t\n1176\tprint_traefik_instructions() {\n1177\t echo \"\""}]},"uuid":"dc5e00a3-ab01-4ccb-8ccc-d4aa6afe5192","timestamp":"2026-04-01T17:00:13.955Z","toolUseResult":{"type":"text","file":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","content":" echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"","numLines":30,"startLine":1148,"totalLines":1387}},"sourceToolAssistantUUID":"53821e5f-2c97-48fe-9e7c-137fb73e15e2","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"dc5e00a3-ab01-4ccb-8ccc-d4aa6afe5192","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01WzH54FWaeaicYdbW7uEwFw","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01F32a6mUTaSkUVNAjEEjGHX","name":"Edit","input":{"replace_all":false,"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","old_string":" echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then","new_string":" echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":709,"cache_read_input_tokens":26105,"output_tokens":334,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":709,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQev5WD25TUbegaEbVi","type":"assistant","uuid":"80981069-cc1f-4d0a-b58c-9dcf231a4578","timestamp":"2026-04-01T17:00:18.229Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"80981069-cc1f-4d0a-b58c-9dcf231a4578","isSidechain":false,"promptId":"fd5a112e-5203-436a-9e96-66bd2a0c09f9","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01F32a6mUTaSkUVNAjEEjGHX","type":"tool_result","content":"The file /Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh has been updated successfully."}]},"uuid":"c25dc655-e0e7-40d2-b6d8-143dbde2f2f3","timestamp":"2026-04-01T17:00:20.344Z","toolUseResult":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","oldString":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment","newString":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment","originalFile":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment\n","structuredPatch":[{"oldStart":1152,"oldLines":6,"newStart":1152,"newLines":13,"lines":[" echo \" - 443/tcp (HTTPS - all NetBird services)\""," echo \" - 80/tcp (HTTP - redirects to HTTPS)\""," echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"","+ echo \"\"","+ echo \"This setup is ideal for homelabs and smaller organization deployments.\"","+ echo \"For enterprise environments requiring high availability and integrations,\"","+ echo \"consider a commercial on-prem license or scaling your open source deployment:\"","+ echo \" Commercial license: https://netbird.io/pricing#on-prem\"","+ echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"","+ echo \"\""," if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then"," echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\""," echo \"\""]}],"userModified":false,"replaceAll":false},"sourceToolAssistantUUID":"80981069-cc1f-4d0a-b58c-9dcf231a4578","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"c25dc655-e0e7-40d2-b6d8-143dbde2f2f3","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_011SnQ5YgVhwSKeb6dji3MCY","type":"message","role":"assistant","content":[{"type":"text","text":"Now remove the old copy that was after the `fi`:"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":399,"cache_read_input_tokens":26814,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":399},"output_tokens":2,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdQfPRobTkCdXvF6MRqg","type":"assistant","uuid":"b3c841a3-83a2-4ca4-bccd-d002bf1a4a8d","timestamp":"2026-04-01T17:00:22.617Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"b3c841a3-83a2-4ca4-bccd-d002bf1a4a8d","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_011SnQ5YgVhwSKeb6dji3MCY","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01Tuh1YqqGsEA9TvZ8PRQhhm","name":"Edit","input":{"replace_all":false,"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","old_string":" fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0","new_string":" fi\n return 0"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":399,"cache_read_input_tokens":26814,"output_tokens":276,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":399,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQfPRobTkCdXvF6MRqg","type":"assistant","uuid":"fe3f2ecf-2f78-4ba1-9925-b5ca87d5aa28","timestamp":"2026-04-01T17:00:24.083Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"fe3f2ecf-2f78-4ba1-9925-b5ca87d5aa28","isSidechain":false,"promptId":"fd5a112e-5203-436a-9e96-66bd2a0c09f9","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01Tuh1YqqGsEA9TvZ8PRQhhm","type":"tool_result","content":"The file /Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh has been updated successfully."}]},"uuid":"2e745d66-3063-41e9-b51a-7a48772b9a73","timestamp":"2026-04-01T17:00:25.497Z","toolUseResult":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","oldString":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment","newString":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment","originalFile":"#!/bin/bash\n\nset -e\n\n# NetBird Getting Started with Embedded IdP (Dex)\n# This script sets up NetBird with the embedded Dex identity provider\n# No separate Dex container or reverse proxy needed - IdP is built into management server\n\n# Sed pattern to strip base64 padding characters\nSED_STRIP_PADDING='s/=//g'\n\n# Constants for repeated string literals\nreadonly MSG_STARTING_SERVICES=\"\\nStarting NetBird services\\n\"\nreadonly MSG_DONE=\"\\nDone!\\n\"\nreadonly MSG_NEXT_STEPS=\"Next steps:\"\nreadonly MSG_SEPARATOR=\"==========================================\"\n\n############################################\n# Utility Functions\n############################################\n\ncheck_docker_compose() {\n if command -v docker-compose &> /dev/null\n then\n echo \"docker-compose\"\n return\n fi\n if docker compose --help &> /dev/null\n then\n echo \"docker compose\"\n return\n fi\n\n 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\n exit 1\n}\n\ncheck_jq() {\n if ! command -v jq &> /dev/null\n then\n echo \"jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq\" > /dev/stderr\n exit 1\n fi\n return 0\n}\n\nget_main_ip_address() {\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n interface=$(route -n get default | grep 'interface:' | awk '{print $2}')\n ip_address=$(ifconfig \"$interface\" | grep 'inet ' | awk '{print $2}')\n else\n interface=$(ip route | grep default | awk '{print $5}' | head -n 1)\n ip_address=$(ip addr show \"$interface\" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)\n fi\n\n echo \"$ip_address\"\n return 0\n}\n\ncheck_nb_domain() {\n DOMAIN=$1\n if [[ \"$DOMAIN-x\" == \"-x\" ]]; then\n echo \"The NETBIRD_DOMAIN variable cannot be empty.\" > /dev/stderr\n return 1\n fi\n\n if [[ \"$DOMAIN\" == \"netbird.example.com\" ]]; then\n echo \"The NETBIRD_DOMAIN cannot be netbird.example.com\" > /dev/stderr\n return 1\n fi\n return 0\n}\n\nread_nb_domain() {\n READ_NETBIRD_DOMAIN=\"\"\n echo -n \"Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): \" > /dev/stderr\n read -r READ_NETBIRD_DOMAIN < /dev/tty\n if ! check_nb_domain \"$READ_NETBIRD_DOMAIN\"; then\n read_nb_domain\n fi\n echo \"$READ_NETBIRD_DOMAIN\"\n return 0\n}\n\nread_reverse_proxy_type() {\n echo \"\" > /dev/stderr\n echo \"Which reverse proxy will you use?\" > /dev/stderr\n echo \" [0] Traefik (recommended - automatic TLS, included in Docker Compose)\" > /dev/stderr\n echo \" [1] Existing Traefik (labels for external Traefik instance)\" > /dev/stderr\n echo \" [2] Nginx (generates config template)\" > /dev/stderr\n echo \" [3] Nginx Proxy Manager (generates config + instructions)\" > /dev/stderr\n echo \" [4] External Caddy (generates Caddyfile snippet)\" > /dev/stderr\n echo \" [5] Other/Manual (displays setup documentation)\" > /dev/stderr\n echo \"\" > /dev/stderr\n echo -n \"Enter choice [0-5] (default: 0): \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ -z \"$CHOICE\" ]]; then\n CHOICE=\"0\"\n fi\n\n if [[ ! \"$CHOICE\" =~ ^[0-5]$ ]]; then\n echo \"Invalid choice. Please enter a number between 0 and 5.\" > /dev/stderr\n read_reverse_proxy_type\n return\n fi\n\n echo \"$CHOICE\"\n return 0\n}\n\nread_traefik_network() {\n echo \"\" > /dev/stderr\n echo \"If you have an existing Traefik instance, enter its external network name.\" > /dev/stderr\n echo -n \"External network (leave empty to create 'netbird' network): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_traefik_entrypoint() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik HTTPS entrypoint.\" > /dev/stderr\n echo -n \"HTTPS entrypoint name (default: websecure): \" > /dev/stderr\n read -r ENTRYPOINT < /dev/tty\n if [[ -z \"$ENTRYPOINT\" ]]; then\n ENTRYPOINT=\"websecure\"\n fi\n echo \"$ENTRYPOINT\"\n return 0\n}\n\nread_traefik_certresolver() {\n echo \"\" > /dev/stderr\n echo \"Enter the name of your Traefik certificate resolver (for automatic TLS).\" > /dev/stderr\n echo \"Leave empty if you handle TLS termination elsewhere or use a wildcard cert.\" > /dev/stderr\n echo -n \"Certificate resolver name (e.g., letsencrypt): \" > /dev/stderr\n read -r RESOLVER < /dev/tty\n echo \"$RESOLVER\"\n return 0\n}\n\nread_port_binding_preference() {\n echo \"\" > /dev/stderr\n echo \"Should container ports be bound to localhost only (127.0.0.1)?\" > /dev/stderr\n echo \"Choose 'yes' if your reverse proxy runs on the same host (more secure).\" > /dev/stderr\n echo -n \"Bind to localhost only? [Y/n]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Nn]$ ]]; then\n echo \"false\"\n else\n echo \"true\"\n fi\n return 0\n}\n\nread_proxy_docker_network() {\n local proxy_name=\"$1\"\n echo \"\" > /dev/stderr\n echo \"Is ${proxy_name} running in Docker?\" > /dev/stderr\n echo \"If yes, enter the Docker network ${proxy_name} is on (NetBird will join it).\" > /dev/stderr\n echo -n \"Docker network (leave empty if not in Docker): \" > /dev/stderr\n read -r NETWORK < /dev/tty\n echo \"$NETWORK\"\n return 0\n}\n\nread_enable_proxy() {\n echo \"\" > /dev/stderr\n echo \"Do you want to enable the NetBird Proxy service?\" > /dev/stderr\n echo \"The proxy allows you to selectively expose internal NetBird network resources\" > /dev/stderr\n echo \"to the internet. You control which resources are exposed through the dashboard.\" > /dev/stderr\n echo -n \"Enable proxy? [y/N]: \" > /dev/stderr\n read -r CHOICE < /dev/tty\n\n if [[ \"$CHOICE\" =~ ^[Yy]$ ]]; then\n echo \"true\"\n else\n echo \"false\"\n fi\n return 0\n}\n\nread_traefik_acme_email() {\n echo \"\" > /dev/stderr\n echo \"Enter your email for Let's Encrypt certificate notifications.\" > /dev/stderr\n echo -n \"Email address: \" > /dev/stderr\n read -r EMAIL < /dev/tty\n if [[ -z \"$EMAIL\" ]]; then\n echo \"Email is required for Let's Encrypt.\" > /dev/stderr\n read_traefik_acme_email\n return\n fi\n echo \"$EMAIL\"\n return 0\n}\n\nget_bind_address() {\n if [[ \"$BIND_LOCALHOST_ONLY\" == \"true\" ]]; then\n echo \"127.0.0.1\"\n else\n echo \"0.0.0.0\"\n fi\n return 0\n}\n\nget_upstream_host() {\n # Always return 127.0.0.1 for health checks and upstream targets\n # Cannot use 0.0.0.0 as a connection target\n echo \"127.0.0.1\"\n return 0\n}\n\nwait_management_proxy() {\n local proxy_container=\"${1:-traefik}\"\n set +e\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint through the reverse proxy\n if curl -sk -f -o /dev/null \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 \"$proxy_container\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\nwait_management_direct() {\n set +e\n local upstream_host=$(get_upstream_host)\n echo -n \"Waiting for NetBird server to become ready\"\n counter=1\n while true; do\n # Check the embedded IdP endpoint directly (no reverse proxy)\n if curl -sk -f -o /dev/null \"http://${upstream_host}:${MANAGEMENT_HOST_PORT}/oauth2/.well-known/openid-configuration\" 2>/dev/null; then\n break\n fi\n if [[ $counter -eq 60 ]]; then\n echo \"\"\n echo \"Taking too long. Checking logs...\"\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n fi\n echo -n \" .\"\n sleep 2\n counter=$((counter + 1))\n done\n echo \" done\"\n set -e\n return 0\n}\n\n############################################\n# Initialization and Configuration\n############################################\n\ninitialize_default_values() {\n NETBIRD_PORT=80\n NETBIRD_HTTP_PROTOCOL=\"http\"\n NETBIRD_RELAY_PROTO=\"rel\"\n NETBIRD_RELAY_AUTH_SECRET=$(openssl rand -base64 32 | sed \"$SED_STRIP_PADDING\")\n # Note: DataStoreEncryptionKey must keep base64 padding (=) for Go's base64.StdEncoding\n DATASTORE_ENCRYPTION_KEY=$(openssl rand -base64 32)\n NETBIRD_STUN_PORT=3478\n\n # Docker images\n DASHBOARD_IMAGE=\"netbirdio/dashboard:latest\"\n # Combined server replaces separate signal, relay, and management containers\n NETBIRD_SERVER_IMAGE=\"netbirdio/netbird-server:latest\"\n NETBIRD_PROXY_IMAGE=\"netbirdio/reverse-proxy:latest\"\n\n # Reverse proxy configuration\n REVERSE_PROXY_TYPE=\"0\"\n TRAEFIK_EXTERNAL_NETWORK=\"\"\n TRAEFIK_ENTRYPOINT=\"websecure\"\n TRAEFIK_CERTRESOLVER=\"\"\n TRAEFIK_ACME_EMAIL=\"\"\n DASHBOARD_HOST_PORT=\"8080\"\n MANAGEMENT_HOST_PORT=\"8081\" # Combined server port (management + signal + relay)\n BIND_LOCALHOST_ONLY=\"true\"\n EXTERNAL_PROXY_NETWORK=\"\"\n\n # Traefik static IP within the internal bridge network\n TRAEFIK_IP=\"172.30.0.10\"\n\n # NetBird Proxy configuration\n ENABLE_PROXY=\"false\"\n PROXY_TOKEN=\"\"\n return 0\n}\n\nconfigure_domain() {\n if ! check_nb_domain \"$NETBIRD_DOMAIN\"; then\n NETBIRD_DOMAIN=$(read_nb_domain)\n fi\n\n if [[ \"$NETBIRD_DOMAIN\" == \"use-ip\" ]]; then\n NETBIRD_DOMAIN=$(get_main_ip_address)\n BASE_DOMAIN=$NETBIRD_DOMAIN\n else\n NETBIRD_PORT=443\n NETBIRD_HTTP_PROTOCOL=\"https\"\n NETBIRD_RELAY_PROTO=\"rels\"\n BASE_DOMAIN=$(echo $NETBIRD_DOMAIN | sed -E 's/^[^.]+\\.//')\n fi\n return 0\n}\n\nconfigure_reverse_proxy() {\n # Prompt for reverse proxy type\n REVERSE_PROXY_TYPE=$(read_reverse_proxy_type)\n\n # Handle built-in Traefik prompts (option 0)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)\n ENABLE_PROXY=$(read_enable_proxy)\n fi\n\n # Handle external Traefik-specific prompts (option 1)\n if [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n TRAEFIK_EXTERNAL_NETWORK=$(read_traefik_network)\n TRAEFIK_ENTRYPOINT=$(read_traefik_entrypoint)\n TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)\n fi\n\n # Handle port binding for external proxy options (2-5)\n if [[ \"$REVERSE_PROXY_TYPE\" -ge 2 ]]; then\n BIND_LOCALHOST_ONLY=$(read_port_binding_preference)\n fi\n\n # Handle Docker network prompts for external proxies (options 2-4)\n case \"$REVERSE_PROXY_TYPE\" in\n 2) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx\") ;;\n 3) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Nginx Proxy Manager\") ;;\n 4) EXTERNAL_PROXY_NETWORK=$(read_proxy_docker_network \"Caddy\") ;;\n *) ;; # No network prompt for other options\n esac\n return 0\n}\n\ncheck_existing_installation() {\n if [[ -f config.yaml ]]; then\n echo \"Generated files already exist, if you want to reinitialize the environment, please remove them first.\"\n echo \"You can use the following commands:\"\n echo \" $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes\"\n echo \" rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt\"\n echo \"Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard.\"\n exit 1\n fi\n return 0\n}\n\ngenerate_configuration_files() {\n echo Rendering initial files...\n\n # Render docker-compose and proxy config based on selection\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n render_docker_compose_traefik_builtin > docker-compose.yml\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Create placeholder proxy.env so docker-compose can validate\n # This will be overwritten with the actual token after netbird-server starts\n echo \"# Placeholder - will be updated with token after netbird-server starts\" > proxy.env\n echo \"NB_PROXY_TOKEN=placeholder\" >> proxy.env\n # TCP ServersTransport for PROXY protocol v2 to the proxy backend\n render_traefik_dynamic > traefik-dynamic.yaml\n fi\n ;;\n 1)\n render_docker_compose_traefik > docker-compose.yml\n ;;\n 2)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_nginx_conf > nginx-netbird.conf\n ;;\n 3)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_npm_advanced_config > npm-advanced-config.txt\n ;;\n 4)\n render_docker_compose_exposed_ports > docker-compose.yml\n render_external_caddyfile > caddyfile-netbird.txt\n ;;\n 5)\n render_docker_compose_exposed_ports > docker-compose.yml\n ;;\n *)\n echo \"Invalid reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n exit 1\n ;;\n esac\n\n # Common files for all configurations\n render_dashboard_env > dashboard.env\n render_combined_yaml > config.yaml\n return 0\n}\n\nstart_services_and_show_instructions() {\n # For built-in Traefik, start containers immediately\n # For NPM, start containers first (NPM needs services running to create proxy)\n # For other external proxies, show instructions first and wait for user confirmation\n if [[ \"$REVERSE_PROXY_TYPE\" == \"0\" ]]; then\n # Built-in Traefik - two-phase startup if proxy is enabled\n echo -e \"$MSG_STARTING_SERVICES\"\n\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n # Phase 1: Start core services (without proxy)\n echo \"Starting core services...\"\n $DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server\n\n sleep 3\n wait_management_proxy traefik\n\n # Phase 2: Create proxy token and start proxy\n echo \"\"\n echo \"Creating proxy access token...\"\n # Use docker exec with bash to run the token command directly\n PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T netbird-server \\\n /go/bin/netbird-server token create --name \"default-proxy\" --config /etc/netbird/config.yaml 2>/dev/null | grep \"^Token:\" | awk '{print $2}')\n\n if [[ -z \"$PROXY_TOKEN\" ]]; then\n echo \"ERROR: Failed to create proxy token. Check netbird-server logs.\" > /dev/stderr\n $DOCKER_COMPOSE_COMMAND logs --tail=20 netbird-server\n exit 1\n fi\n\n echo \"Proxy token created successfully.\"\n\n # Generate proxy.env with the token\n render_proxy_env > proxy.env\n\n # Start proxy service\n echo \"Starting proxy service...\"\n $DOCKER_COMPOSE_COMMAND up -d proxy\n else\n # No proxy - start all services at once\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_proxy traefik\n fi\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"1\" ]]; then\n # External Traefik - start containers, then show instructions\n # Traefik discovers services via Docker labels, so containers must be running\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Once Traefik is connected, access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n elif [[ \"$REVERSE_PROXY_TYPE\" == \"3\" ]]; then\n # NPM - start containers first, then show instructions\n # NPM requires backend services to be running before creating proxy hosts\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n print_post_setup_instructions\n echo \"\"\n echo \"NetBird containers are running. Configure NPM as shown above, then access:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n else\n # External proxies (nginx, external Caddy, other) - need manual config first\n print_post_setup_instructions\n\n echo \"\"\n echo -n \"Press Enter when your reverse proxy is configured (or Ctrl+C to exit)... \"\n read -r < /dev/tty\n\n echo -e \"$MSG_STARTING_SERVICES\"\n $DOCKER_COMPOSE_COMMAND up -d\n\n sleep 3\n wait_management_direct\n\n echo -e \"$MSG_DONE\"\n echo \"NetBird is now running. Access the dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n fi\n return 0\n}\n\ninit_environment() {\n initialize_default_values\n configure_domain\n configure_reverse_proxy\n\n check_jq\n DOCKER_COMPOSE_COMMAND=$(check_docker_compose)\n\n check_existing_installation\n generate_configuration_files\n start_services_and_show_instructions\n return 0\n}\n\n############################################\n# Configuration File Renderers\n############################################\n\nrender_docker_compose_traefik_builtin() {\n # Generate proxy service section and Traefik dynamic config if enabled\n local proxy_service=\"\"\n local proxy_volumes=\"\"\n local traefik_file_provider=\"\"\n local traefik_dynamic_volume=\"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n traefik_file_provider=' - \"--providers.file.filename=/etc/traefik/dynamic.yaml\"'\n traefik_dynamic_volume=\" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro\"\n proxy_service=\"\n # NetBird Proxy - exposes internal resources to the internet\n proxy:\n image: $NETBIRD_PROXY_IMAGE\n container_name: netbird-proxy\n ports:\n - 51820:51820/udp\n restart: unless-stopped\n networks: [netbird]\n depends_on:\n - netbird-server\n env_file:\n - ./proxy.env\n volumes:\n - netbird_proxy_certs:/certs\n labels:\n # TCP passthrough for any unmatched domain (proxy handles its own TLS)\n - traefik.enable=true\n - traefik.tcp.routers.proxy-passthrough.entrypoints=websecure\n - traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\\`*\\`)\n - traefik.tcp.routers.proxy-passthrough.tls.passthrough=true\n - traefik.tcp.routers.proxy-passthrough.service=proxy-tls\n - traefik.tcp.routers.proxy-passthrough.priority=1\n - traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443\n - traefik.tcp.services.proxy-tls.loadbalancer.serverstransport=pp-v2@file\n logging:\n driver: \\\"json-file\\\"\n options:\n max-size: \\\"500m\\\"\n max-file: \\\"2\\\"\n\"\n proxy_volumes=\"\n netbird_proxy_certs:\"\n fi\n\n cat <<EOF\nservices:\n # Traefik reverse proxy (automatic TLS via Let's Encrypt)\n traefik:\n image: traefik:v3.6\n container_name: netbird-traefik\n restart: unless-stopped\n networks:\n netbird:\n ipv4_address: $TRAEFIK_IP\n command:\n # Logging\n - \"--log.level=INFO\"\n - \"--accesslog=true\"\n # Docker provider\n - \"--providers.docker=true\"\n - \"--providers.docker.exposedbydefault=false\"\n - \"--providers.docker.network=netbird\"\n # Entrypoints\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--entrypoints.websecure.allowACMEByPass=true\"\n # Disable timeouts for long-lived gRPC streams\n - \"--entrypoints.websecure.transport.respondingTimeouts.readTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=0\"\n - \"--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0\"\n # HTTP to HTTPS redirect\n - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n # Let's Encrypt ACME\n - \"--certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_ACME_EMAIL\"\n - \"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json\"\n - \"--certificatesresolvers.letsencrypt.acme.tlschallenge=true\"\n # gRPC transport settings\n - \"--serverstransport.forwardingtimeouts.responseheadertimeout=0s\"\n - \"--serverstransport.forwardingtimeouts.idleconntimeout=0s\"\n$traefik_file_provider\n ports:\n - '443:443'\n - '80:80'\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock:ro\n - netbird_traefik_letsencrypt:/letsencrypt\n$traefik_dynamic_volume\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [netbird]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=websecure\n - traefik.http.routers.netbird-dashboard.tls=true\n - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-dashboard.service=dashboard\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [netbird]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=websecure\n - traefik.http.routers.netbird-grpc.tls=true\n - traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n - traefik.http.routers.netbird-grpc.priority=100\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=websecure\n - traefik.http.routers.netbird-backend.tls=true\n - traefik.http.routers.netbird-backend.tls.certresolver=letsencrypt\n - traefik.http.routers.netbird-backend.service=netbird-server\n - traefik.http.routers.netbird-backend.priority=100\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n${proxy_service}\nvolumes:\n netbird_data:\n netbird_traefik_letsencrypt:${proxy_volumes}\n\nnetworks:\n netbird:\n driver: bridge\n ipam:\n config:\n - subnet: 172.30.0.0/24\n gateway: 172.30.0.1\nEOF\n return 0\n}\n\nrender_combined_yaml() {\n cat <<EOF\n# Combined NetBird Server Configuration (Simplified)\n# Generated by getting-started.sh\n\nserver:\n listenAddress: \":80\"\n exposedAddress: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT\"\n stunPorts:\n - $NETBIRD_STUN_PORT\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n\n authSecret: \"$NETBIRD_RELAY_AUTH_SECRET\"\n dataDir: \"/var/lib/netbird\"\n\n auth:\n issuer: \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\"\n signKeyRefreshEnabled: true\n dashboardRedirectURIs:\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-auth\"\n - \"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/nb-silent-auth\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n\n reverseProxy:\n trustedHTTPProxies:\n - \"$TRAEFIK_IP/32\"\n\n store:\n engine: \"sqlite\"\n encryptionKey: \"$DATASTORE_ENCRYPTION_KEY\"\nEOF\n return 0\n}\n\nrender_dashboard_env() {\n cat <<EOF\n# Endpoints\nNETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\nNETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\n# OIDC - using embedded IdP\nAUTH_AUDIENCE=netbird-dashboard\nAUTH_CLIENT_ID=netbird-dashboard\nAUTH_CLIENT_SECRET=\nAUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2\nUSE_AUTH0=false\nAUTH_SUPPORTED_SCOPES=openid profile email groups\nAUTH_REDIRECT_URI=/nb-auth\nREDACTED\n# SSL\nNGINX_SSL_PORT=443\n# Letsencrypt\nLETSENCRYPT_DOMAIN=none\nEOF\n return 0\n}\n\nrender_traefik_dynamic() {\n cat <<'EOF'\ntcp:\n serversTransports:\n pp-v2:\n proxyProtocol:\n version: 2\nEOF\n return 0\n}\n\nrender_proxy_env() {\n cat <<EOF\n# NetBird Proxy Configuration\nNB_PROXY_DEBUG_LOGS=false\n# Use internal Docker network to connect to management (avoids hairpin NAT issues)\nNB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80\n# Allow insecure gRPC connection to management (required for internal Docker network)\nNB_PROXY_ALLOW_INSECURE=true\n# Public URL where this proxy is reachable (used for cluster registration)\nNB_PROXY_DOMAIN=$NETBIRD_DOMAIN\nNB_PROXY_ADDRESS=:8443\nNB_PROXY_TOKEN=$PROXY_TOKEN\nNB_PROXY_CERTIFICATE_DIRECTORY=/certs\nNB_PROXY_ACME_CERTIFICATES=true\nREDACTED\nNB_PROXY_FORWARDED_PROTO=https\n# Enable PROXY protocol to preserve client IPs through L4 proxies (Traefik TCP passthrough)\nNB_PROXY_PROXY_PROTOCOL=true\n# Trust Traefik's IP for PROXY protocol headers\nNB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP\nEOF\n return 0\n}\n\nrender_docker_compose_traefik() {\n local network_name=\"${TRAEFIK_EXTERNAL_NETWORK:-netbird}\"\n local network_config=\"\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n network_config=\" external: true\"\n fi\n\n # Build TLS labels - certresolver is optional\n local tls_labels=\"\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n tls_labels=\"tls.certresolver=${TRAEFIK_CERTRESOLVER}\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: [$network_name]\n env_file:\n - ./dashboard.env\n labels:\n - traefik.enable=true\n - traefik.http.routers.netbird-dashboard.rule=Host(\\`$NETBIRD_DOMAIN\\`)\n - traefik.http.routers.netbird-dashboard.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-dashboard.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-dashboard.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-dashboard.priority=1\n - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: [$network_name]\n ports:\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n labels:\n - traefik.enable=true\n # gRPC router (needs h2c backend for HTTP/2 cleartext)\n - traefik.http.routers.netbird-grpc.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/signalexchange.SignalExchange/\\`) || PathPrefix(\\`/management.ManagementService/\\`))\n - traefik.http.routers.netbird-grpc.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-grpc.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-grpc.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-grpc.service=netbird-server-h2c\n # Backend router (relay, WebSocket, API, OAuth2)\n - traefik.http.routers.netbird-backend.rule=Host(\\`$NETBIRD_DOMAIN\\`) && (PathPrefix(\\`/relay\\`) || PathPrefix(\\`/ws-proxy/\\`) || PathPrefix(\\`/api\\`) || PathPrefix(\\`/oauth2\\`))\n - traefik.http.routers.netbird-backend.entrypoints=$TRAEFIK_ENTRYPOINT\n - traefik.http.routers.netbird-backend.tls=true\n$(if [[ -n \"$tls_labels\" ]]; then echo \" - traefik.http.routers.netbird-backend.${tls_labels}\"; fi)\n - traefik.http.routers.netbird-backend.service=netbird-server\n # Services\n - traefik.http.services.netbird-server.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.port=80\n - traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme=h2c\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\nnetworks:\n $network_name:\n$network_config\nEOF\n return 0\n}\n\nrender_docker_compose_exposed_ports() {\n local bind_addr=$(get_bind_address)\n local networks=\"[netbird]\"\n local networks_config=\"networks:\n netbird:\"\n\n # If an external network is specified, add it and include in service networks\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n networks=\"[netbird, $EXTERNAL_PROXY_NETWORK]\"\n networks_config=\"networks:\n netbird:\n $EXTERNAL_PROXY_NETWORK:\n external: true\"\n fi\n\n cat <<EOF\nservices:\n # UI dashboard\n dashboard:\n image: $DASHBOARD_IMAGE\n container_name: netbird-dashboard\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${DASHBOARD_HOST_PORT}:80'\n env_file:\n - ./dashboard.env\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\n # Combined server (Management + Signal + Relay + STUN)\n netbird-server:\n image: $NETBIRD_SERVER_IMAGE\n container_name: netbird-server\n restart: unless-stopped\n networks: ${networks}\n ports:\n - '${bind_addr}:${MANAGEMENT_HOST_PORT}:80'\n - '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'\n volumes:\n - netbird_data:/var/lib/netbird\n - ./config.yaml:/etc/netbird/config.yaml\n command: [\"--config\", \"/etc/netbird/config.yaml\"]\n logging:\n driver: \"json-file\"\n options:\n max-size: \"500m\"\n max-file: \"2\"\n\nvolumes:\n netbird_data:\n\n${networks_config}\nEOF\n return 0\n}\n\nrender_nginx_conf() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# 1. Update SSL certificate paths below\n# 2. Copy to your nginx config directory:\n# Debian/Ubuntu: /etc/nginx/sites-available/netbird (then symlink to sites-enabled)\n# RHEL/CentOS: /etc/nginx/conf.d/netbird.conf\n# 3. Test and reload: nginx -t && systemctl reload nginx\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Nginx is on the same Docker network.\n# Add this to your nginx.conf or include it from a separate file.\"\n fi\n\n cat <<EOF\n# NetBird Nginx Configuration\n# Generated by getting-started.sh\n#\n${install_note}\n\nupstream netbird_dashboard {\n server ${dashboard_addr};\n keepalive 10;\n}\nupstream netbird_server {\n server ${server_addr};\n}\n\nserver {\n listen 80;\n server_name $NETBIRD_DOMAIN;\n\n location / {\n return 301 https://\\$host\\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n server_name $NETBIRD_DOMAIN;\n\n # SSL/TLS Configuration\n # Update these paths based on your certificate source:\n #\n # Let's Encrypt (certbot):\n # ssl_certificate /etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem;\n # ssl_certificate_key /etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem;\n #\n # Let's Encrypt (acme.sh):\n # ssl_certificate /root/.acme.sh/$NETBIRD_DOMAIN/fullchain.cer;\n # ssl_certificate_key /root/.acme.sh/$NETBIRD_DOMAIN/$NETBIRD_DOMAIN.key;\n #\n # Custom certificates:\n # ssl_certificate /etc/ssl/certs/$NETBIRD_DOMAIN.crt;\n # ssl_certificate_key /etc/ssl/private/$NETBIRD_DOMAIN.key;\n #\n ssl_certificate /path/to/your/fullchain.pem;\n ssl_certificate_key /path/to/your/privkey.pem;\n\n # Recommended SSL settings\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_prefer_server_ciphers off;\n ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;\n\n # Required for long-lived gRPC connections\n client_header_timeout 1d;\n client_body_timeout 1d;\n\n # Common proxy headers\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Scheme \\$scheme;\n proxy_set_header X-Forwarded-Proto https;\n proxy_set_header X-Forwarded-Host \\$host;\n grpc_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n\n # WebSocket connections (relay, signal, management)\n location ~ ^/(relay|ws-proxy/) {\n proxy_pass http://netbird_server;\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n proxy_set_header Host \\$host;\n proxy_read_timeout 1d;\n }\n\n # Native gRPC (signal + management)\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://netbird_server;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n }\n\n # HTTP routes (API + OAuth2)\n location ~ ^/(api|oauth2)/ {\n proxy_pass http://netbird_server;\n proxy_set_header Host \\$host;\n }\n\n # Dashboard (catch-all)\n location / {\n proxy_pass http://netbird_dashboard;\n }\n}\nEOF\n return 0\n}\n\nrender_external_caddyfile() {\n local upstream_host=$(get_upstream_host)\n local dashboard_addr=\"${upstream_host}:${DASHBOARD_HOST_PORT}\"\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n local install_note=\"# Add this block to your existing Caddyfile and reload Caddy\"\n\n # If running in Docker network, use container names\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n dashboard_addr=\"netbird-dashboard:80\"\n server_addr=\"netbird-server:80\"\n install_note=\"# This config uses container names since Caddy is on the same Docker network.\n# Add this block to your Caddyfile and reload Caddy.\"\n fi\n\n cat <<EOF\n# NetBird Caddyfile Snippet\n# Generated by getting-started.sh\n#\n${install_note}\n\n$NETBIRD_DOMAIN {\n # Native gRPC (needs HTTP/2 cleartext to backend)\n @grpc header Content-Type application/grpc*\n reverse_proxy @grpc h2c://${server_addr}\n\n # Combined server paths (relay, signal, management, OAuth2)\n @backend path /relay* /ws-proxy/* /api/* /oauth2/*\n reverse_proxy @backend ${server_addr}\n\n # Dashboard (everything else)\n reverse_proxy /* ${dashboard_addr}\n}\nEOF\n return 0\n}\n\nrender_npm_advanced_config() {\n local upstream_host=$(get_upstream_host)\n local server_addr=\"${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n\n # If external network is specified, use container names instead of host addresses\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n server_addr=\"netbird-server:80\"\n fi\n\n cat <<EOF\n# Advanced Configuration for Nginx Proxy Manager\n# Paste this into the \"Advanced\" tab of your Proxy Host configuration\n#\n# IMPORTANT: Enable \"HTTP/2 Support\" in the SSL tab for gRPC to work!\n\n# Required for long-lived connections (gRPC and WebSocket)\nclient_header_timeout 1d;\nclient_body_timeout 1d;\n\n# WebSocket connections (relay, signal, management)\nlocation ~ ^/(relay|ws-proxy/) {\n proxy_pass http://${server_addr};\n proxy_http_version 1.1;\n proxy_set_header Upgrade \\$http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_read_timeout 1d;\n}\n\n# Native gRPC (signal + management)\nlocation ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService)/ {\n grpc_pass grpc://${server_addr};\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n grpc_socket_keepalive on;\n}\n\n# HTTP routes (API + OAuth2)\nlocation ~ ^/(api|oauth2)/ {\n proxy_pass http://${server_addr};\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n}\nEOF\n return 0\n}\n\n############################################\n# Post-Setup Instructions per Proxy Type\n############################################\n\nprint_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" TRAEFIK SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"NetBird containers are configured with Traefik labels.\"\n echo \"\"\n echo \"Configuration:\"\n echo \" Entrypoint: $TRAEFIK_ENTRYPOINT\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" Certificate resolver: $TRAEFIK_CERTRESOLVER\"\n fi\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" Network: $TRAEFIK_EXTERNAL_NETWORK (external)\"\n else\n echo \" Network: netbird\"\n fi\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" - Ensure Traefik is running and configured\"\n if [[ -n \"$TRAEFIK_EXTERNAL_NETWORK\" ]]; then\n echo \" - Traefik must be on the '$TRAEFIK_EXTERNAL_NETWORK' network\"\n fi\n echo \" - Entrypoint '$TRAEFIK_ENTRYPOINT' must be defined\"\n if [[ -n \"$TRAEFIK_CERTRESOLVER\" ]]; then\n echo \" - Certificate resolver '$TRAEFIK_CERTRESOLVER' must be configured\"\n fi\n echo \" - Disable read timeout on the entrypoint for gRPC streams:\"\n echo \" --entrypoints.$TRAEFIK_ENTRYPOINT.transport.respondingTimeouts.readTimeout=0\"\n echo \" - HTTP to HTTPS redirect (recommended)\"\n return 0\n}\n\nprint_nginx_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: nginx-netbird.conf\"\n echo \"\"\n echo \"IMPORTANT: Nginx requires manual TLS certificate setup.\"\n echo \"You'll need to obtain SSL/TLS certificates and configure the paths in the\"\n echo \"generated config file. The config includes examples for common certificate sources.\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Ensure your Nginx container has access to SSL certificates\"\n echo \" (mount certificate directory as volume if needed)\"\n echo \" 2. Edit nginx-netbird.conf and update SSL certificate paths\"\n echo \" The config includes examples for certbot, acme.sh, and custom certs\"\n echo \" 3. Include the config in your Nginx container's configuration\"\n echo \" 4. Reload Nginx\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Obtain SSL/TLS certificates (Let's Encrypt recommended)\"\n echo \" 2. Edit nginx-netbird.conf and update certificate paths\"\n echo \" 3. Install to /etc/nginx/sites-available/ (Debian) or /etc/nginx/conf.d/ (RHEL)\"\n echo \" 4. Test and reload: nginx -t && systemctl reload nginx\"\n echo \"\"\n echo \"For detailed TLS setup instructions, see:\"\n echo \"https://docs.netbird.io/selfhosted/reverse-proxy#tls-certificate-setup-for-nginx\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_npm_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NGINX PROXY MANAGER SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: npm-advanced-config.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname: netbird-dashboard\"\n echo \" Forward Port: 80\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n else\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n echo \"\"\n echo \"In NPM, create a Proxy Host:\"\n echo \" Domain: $NETBIRD_DOMAIN\"\n echo \" Forward Hostname/IP: ${upstream_host}\"\n echo \" Forward Port: ${DASHBOARD_HOST_PORT}\"\n echo \" Block Common Exploits: enabled\"\n echo \"\"\n echo \" SSL tab:\"\n echo \" - Request or select existing certificate\"\n echo \" - Enable 'HTTP/2 Support' (REQUIRED for gRPC)\"\n echo \"\"\n echo \" Advanced tab:\"\n echo \" - Paste contents of npm-advanced-config.txt\"\n fi\n return 0\n}\n\nprint_external_caddy_instructions() {\n local bind_addr=$(get_bind_address)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" EXTERNAL CADDY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Generated: caddyfile-netbird.txt\"\n echo \"\"\n if [[ -n \"$EXTERNAL_PROXY_NETWORK\" ]]; then\n echo \"NetBird containers have joined the '$EXTERNAL_PROXY_NETWORK' Docker network.\"\n echo \"The config uses container names for upstream servers.\"\n echo \"\"\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy\"\n else\n echo \"$MSG_NEXT_STEPS\"\n echo \" 1. Add the contents of caddyfile-netbird.txt to your Caddyfile\"\n echo \" 2. Reload Caddy: caddy reload --config /path/to/Caddyfile\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services)\"\n fi\n return 0\n}\n\nprint_manual_instructions() {\n local bind_addr=$(get_bind_address)\n local upstream_host=$(get_upstream_host)\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" MANUAL REVERSE PROXY SETUP\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"Container ports (bound to ${bind_addr}):\"\n echo \" Dashboard: ${DASHBOARD_HOST_PORT}\"\n echo \" NetBird Server: ${MANAGEMENT_HOST_PORT} (all services: management, signal, relay)\"\n echo \"\"\n echo \"Configure your reverse proxy with these routes (all go to the same backend):\"\n echo \"\"\n echo \" WebSocket (relay, signal, management WS proxy):\"\n echo \" /relay*, /ws-proxy/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (HTTP with WebSocket upgrade, extended timeout)\"\n echo \"\"\n echo \" Native gRPC (signal + management):\"\n echo \" /signalexchange.SignalExchange/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" /management.ManagementService/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \" (gRPC/h2c - plaintext HTTP/2)\"\n echo \"\"\n echo \" HTTP (API + embedded IdP):\"\n echo \" /api/*, /oauth2/* -> ${upstream_host}:${MANAGEMENT_HOST_PORT}\"\n echo \"\"\n echo \" Dashboard (catch-all):\"\n echo \" /* -> ${upstream_host}:${DASHBOARD_HOST_PORT}\"\n echo \"\"\n echo \"IMPORTANT: gRPC routes require HTTP/2 (h2c) upstream support.\"\n echo \"WebSocket and gRPC connections need extended timeouts (recommend 1 day).\"\n return 0\n}\n\nprint_post_setup_instructions() {\n case \"$REVERSE_PROXY_TYPE\" in\n 0)\n print_builtin_traefik_instructions\n ;;\n 1)\n print_traefik_instructions\n ;;\n 2)\n print_nginx_instructions\n ;;\n 3)\n print_npm_instructions\n ;;\n 4)\n print_external_caddy_instructions\n ;;\n 5)\n print_manual_instructions\n ;;\n *)\n echo \"Unknown reverse proxy type: $REVERSE_PROXY_TYPE\" > /dev/stderr\n ;;\n esac\n return 0\n}\n\ninit_environment\n","structuredPatch":[{"oldStart":1171,"oldLines":12,"newStart":1171,"newLines":6,"lines":[" echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\""," echo \"\""," fi","- echo \"This setup is ideal for homelabs and smaller organization deployments.\"","- echo \"For enterprise environments requiring high availability and integrations,\"","- echo \"consider a commercial on-prem license or scaling your open source deployment:\"","- echo \" Commercial license: https://netbird.io/pricing#on-prem\"","- echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"","- echo \"\""," return 0"," }"," "]}],"userModified":false,"replaceAll":false},"sourceToolAssistantUUID":"fe3f2ecf-2f78-4ba1-9925-b5ca87d5aa28","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"2e745d66-3063-41e9-b51a-7a48772b9a73","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01FL395w5LEimoYWCYrLRfuv","type":"message","role":"assistant","content":[{"type":"text","text":"Done. The scaling/enterprise note now appears right after the open ports list and before the proxy section."}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":322,"cache_read_input_tokens":27213,"output_tokens":24,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":322,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQfmWM2u2QcAhPJUT63","type":"assistant","uuid":"ff2565b3-d5c4-488f-a418-2cc9bbd188c3","timestamp":"2026-04-01T17:00:27.571Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"ff2565b3-d5c4-488f-a418-2cc9bbd188c3","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":3798}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T17:00:31.404Z","uuid":"0cbfc65e-8313-4953-9d6a-fb63241eeae5","toolUseID":"7ddee3e4-9681-468d-9e36-c5acebfc8c92","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"0cbfc65e-8313-4953-9d6a-fb63241eeae5","isSidechain":false,"promptId":"31c33d24-fc70-4d3c-a9bc-bf4818fd73f7","type":"user","message":{"role":"user","content":"output the script here for me to try"},"uuid":"e72a61e8-abc6-4729-a6bb-a6d9d9d1e1e5","timestamp":"2026-04-01T17:00:53.421Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"e72a61e8-abc6-4729-a6bb-a6d9d9d1e1e5","snapshot":{"messageId":"e72a61e8-abc6-4729-a6bb-a6d9d9d1e1e5","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v3","version":3,"backupTime":"2026-04-01T17:00:54.681Z"}},"timestamp":"2026-04-01T17:00:54.681Z"},"isSnapshotUpdate":false}
{"parentUuid":"e72a61e8-abc6-4729-a6bb-a6d9d9d1e1e5","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01SpJgdFoTfDVevC6YNWHxLV","type":"message","role":"assistant","content":[{"type":"text","text":"\n\n```bash\n#!/bin/bash\nMSG_SEPARATOR=\"============================================\"\nNETBIRD_DOMAIN=\"demo.netbird.io\"\nNETBIRD_HTTP_PROTOCOL=\"https\"\nNETBIRD_STUN_PORT=\"3478\"\nENABLE_PROXY=\"true\"\n\necho \"\"\necho \"$MSG_SEPARATOR\"\necho \" NETBIRD SETUP COMPLETE\"\necho \"$MSG_SEPARATOR\"\necho \"\"\necho \"You can access the NetBird dashboard at:\"\necho \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\necho \"\"\necho \"Follow the onboarding steps to set up your NetBird instance.\"\necho \"\"\necho \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\necho \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\necho \"\"\necho \"Open ports:\"\necho \" - 443/tcp (HTTPS - all NetBird services)\"\necho \" - 80/tcp (HTTP - redirects to HTTPS)\"\necho \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\necho \"\"\necho \"This setup is ideal for homelabs and smaller organization deployments.\"\necho \"For enterprise environments requiring high availability and integrations,\"\necho \"consider a commercial on-prem license or scaling your open source deployment:\"\necho \" Commercial license: https://netbird.io/pricing#on-prem\"\necho \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\necho \"\"\nif [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\nfi\n```"}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":771,"cache_read_input_tokens":27535,"output_tokens":576,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":771,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQhvNhZhtGtRgR4AQSW","type":"assistant","uuid":"0ddeadaf-7627-4491-9532-d5d2bcc6c39d","timestamp":"2026-04-01T17:01:05.324Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"0ddeadaf-7627-4491-9532-d5d2bcc6c39d","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":3525}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T17:01:08.880Z","uuid":"d0edb179-0e2c-4b94-8217-37d08333c338","toolUseID":"85e54be3-44c7-4310-8562-a9608e6cc5fe","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"d0edb179-0e2c-4b94-8217-37d08333c338","isSidechain":false,"promptId":"cf6da2dc-0efc-4db5-8bbe-baf79721cddd","type":"user","message":{"role":"user","content":"i did some changes, plz generate again the trial output"},"uuid":"5f89d9e4-503c-4104-bbd0-5d5c60784372","timestamp":"2026-04-01T17:03:21.226Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"5f89d9e4-503c-4104-bbd0-5d5c60784372","snapshot":{"messageId":"5f89d9e4-503c-4104-bbd0-5d5c60784372","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v4","version":4,"backupTime":"2026-04-01T17:03:22.458Z"}},"timestamp":"2026-04-01T17:03:22.461Z"},"isSnapshotUpdate":false}
{"parentUuid":"5f89d9e4-503c-4104-bbd0-5d5c60784372","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01BVa2eJ6wFNQQEXiG639bH7","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_017otYaU7DD6ijPUdRNoJsFs","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","offset":1137,"limit":40},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":654,"cache_read_input_tokens":28306,"output_tokens":116,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":654,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQtojQVvYmU42eAZuSL","type":"assistant","uuid":"e8a9b856-1520-4fec-9b62-9c6553b95e59","timestamp":"2026-04-01T17:03:24.888Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"e8a9b856-1520-4fec-9b62-9c6553b95e59","isSidechain":false,"promptId":"cf6da2dc-0efc-4db5-8bbe-baf79721cddd","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_017otYaU7DD6ijPUdRNoJsFs","type":"tool_result","content":"1137\tprint_builtin_traefik_instructions() {\n1138\t echo \"\"\n1139\t echo \"$MSG_SEPARATOR\"\n1140\t echo \" NETBIRD SETUP COMPLETE\"\n1141\t echo \"$MSG_SEPARATOR\"\n1142\t echo \"\"\n1143\t echo \"You can access the NetBird dashboard at:\"\n1144\t echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n1145\t echo \"\"\n1146\t echo \"Follow the onboarding steps to set up your NetBird instance.\"\n1147\t echo \"\"\n1148\t echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n1149\t echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n1150\t echo \"\"\n1151\t echo \"Open ports:\"\n1152\t echo \" - 443/tcp (HTTPS - all NetBird services)\"\n1153\t echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n1154\t echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n1155\t if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n1156\t echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n1157\t fi\n1158\t echo \"\"\n1159\t echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n1160\t echo \"For enterprise environments requiring high availability and advanced integrations,\"\n1161\t echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n1162\t echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n1163\t echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n1164\t echo \"\"\n1165\t if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n1166\t echo \"\"\n1167\t echo \"NetBird Proxy:\"\n1168\t echo \" The proxy service is enabled and running.\"\n1169\t echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n1170\t echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n1171\t echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n1172\t echo \"\"\n1173\t echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n1174\t echo \"\"\n1175\t fi\n1176\t return 0"}]},"uuid":"165b0731-744a-4624-a94b-c1892597e398","timestamp":"2026-04-01T17:03:24.907Z","toolUseResult":{"type":"text","file":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","content":"print_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n fi\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and advanced integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0","numLines":40,"startLine":1137,"totalLines":1390}},"sourceToolAssistantUUID":"e8a9b856-1520-4fec-9b62-9c6553b95e59","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"165b0731-744a-4624-a94b-c1892597e398","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01AsreHkjdyfijUPjnPCRNhZ","type":"message","role":"assistant","content":[{"type":"text","text":"```bash\n#!/bin/bash\nMSG_SEPARATOR=\"============================================\"\nNETBIRD_DOMAIN=\"demo.netbird.io\"\nNETBIRD_HTTP_PROTOCOL=\"https\"\nNETBIRD_STUN_PORT=\"3478\"\nENABLE_PROXY=\"true\"\n\necho \"\"\necho \"$MSG_SEPARATOR\"\necho \" NETBIRD SETUP COMPLETE\"\necho \"$MSG_SEPARATOR\"\necho \"\"\necho \"You can access the NetBird dashboard at:\"\necho \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\necho \"\"\necho \"Follow the onboarding steps to set up your NetBird instance.\"\necho \"\"\necho \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\necho \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\necho \"\"\necho \"Open ports:\"\necho \" - 443/tcp (HTTPS - all NetBird services)\"\necho \" - 80/tcp (HTTP - redirects to HTTPS)\"\necho \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\nif [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\nfi\necho \"\"\necho \"This setup is ideal for homelabs and smaller organization deployments.\"\necho \"For enterprise environments requiring high availability and advanced integrations,\"\necho \"consider a commercial on-prem license or scaling your open source deployment:\"\necho \" Commercial license: https://netbird.io/pricing#on-prem\"\necho \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\necho \"\"\nif [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \"\"\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\nfi\n```"}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":820,"cache_read_input_tokens":28960,"output_tokens":596,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":820,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdQtzkgRwaNmSMUKrunV","type":"assistant","uuid":"e8096ff4-64a5-429a-af89-5e7c038840dc","timestamp":"2026-04-01T17:03:31.761Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"e8096ff4-64a5-429a-af89-5e7c038840dc","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":3535}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T17:03:35.312Z","uuid":"50c95494-6af7-4042-a47f-264c8b094e2c","toolUseID":"16428182-702d-4190-840b-46e7b7d5749c","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"50c95494-6af7-4042-a47f-264c8b094e2c","isSidechain":false,"promptId":"b117ec7c-5e52-468f-a3ad-c4c4ab6670e9","type":"user","message":{"role":"user","content":"output again according to changes"},"uuid":"bca155b0-9fa2-4708-abef-1955d0e49f42","timestamp":"2026-04-01T17:09:12.190Z","permissionMode":"default","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"type":"file-history-snapshot","messageId":"bca155b0-9fa2-4708-abef-1955d0e49f42","snapshot":{"messageId":"bca155b0-9fa2-4708-abef-1955d0e49f42","trackedFileBackups":{"infrastructure_files/getting-started.sh":{"backupFileName":"e24cec2a4b0e60b1@v5","version":5,"backupTime":"2026-04-01T17:09:13.490Z"}},"timestamp":"2026-04-01T17:09:13.490Z"},"isSnapshotUpdate":false}
{"parentUuid":"bca155b0-9fa2-4708-abef-1955d0e49f42","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_012qmYSAyxEn8nyBv198iFcf","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01AhzH4NRUZrpMJeQqd1ohUR","name":"Read","input":{"file_path":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","offset":1137,"limit":45},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":664,"cache_read_input_tokens":29780,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":664},"output_tokens":52,"service_tier":"standard","inference_geo":"not_available"}},"requestId":"req_011CZdRLgWYW8247A9Udub8J","type":"assistant","uuid":"c14945b2-1dc2-45e8-956e-4272b639cacc","timestamp":"2026-04-01T17:09:16.775Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"c14945b2-1dc2-45e8-956e-4272b639cacc","isSidechain":false,"promptId":"b117ec7c-5e52-468f-a3ad-c4c4ab6670e9","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01AhzH4NRUZrpMJeQqd1ohUR","type":"tool_result","content":"1137\tprint_builtin_traefik_instructions() {\n1138\t echo \"\"\n1139\t echo \"$MSG_SEPARATOR\"\n1140\t echo \" NETBIRD SETUP COMPLETE\"\n1141\t echo \"$MSG_SEPARATOR\"\n1142\t echo \"\"\n1143\t echo \"You can access the NetBird dashboard at:\"\n1144\t echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n1145\t echo \"\"\n1146\t echo \"Follow the onboarding steps to set up your NetBird instance.\"\n1147\t echo \"\"\n1148\t echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n1149\t echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n1150\t echo \"\"\n1151\t echo \"Open ports:\"\n1152\t echo \" - 443/tcp (HTTPS - all NetBird services)\"\n1153\t echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n1154\t echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n1155\t if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n1156\t echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n1157\t fi\n1158\t echo \"\"\n1159\t echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n1160\t echo \"For enterprise environments requiring high availability and advanced integrations,\"\n1161\t echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n1162\t echo \"\"\n1163\t echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n1164\t echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n1165\t echo \"\"\n1166\t if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n1167\t echo \"NetBird Proxy:\"\n1168\t echo \" The proxy service is enabled and running.\"\n1169\t echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n1170\t echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n1171\t echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n1172\t echo \"\"\n1173\t echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n1174\t echo \"\"\n1175\t fi\n1176\t return 0\n1177\t}\n1178\t\n1179\tprint_traefik_instructions() {\n1180\t echo \"\"\n1181\t echo \"$MSG_SEPARATOR\""}]},"uuid":"92adb81e-f511-4166-9d79-b7a1334ad9c9","timestamp":"2026-04-01T17:09:16.782Z","toolUseResult":{"type":"text","file":{"filePath":"/Users/misha/Documents/GolandProjects/netbird/netbird/infrastructure_files/getting-started.sh","content":"print_builtin_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"\n echo \" NETBIRD SETUP COMPLETE\"\n echo \"$MSG_SEPARATOR\"\n echo \"\"\n echo \"You can access the NetBird dashboard at:\"\n echo \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\n echo \"\"\n echo \"Follow the onboarding steps to set up your NetBird instance.\"\n echo \"\"\n echo \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\n echo \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\n echo \"\"\n echo \"Open ports:\"\n echo \" - 443/tcp (HTTPS - all NetBird services)\"\n echo \" - 80/tcp (HTTP - redirects to HTTPS)\"\n echo \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\n fi\n echo \"\"\n echo \"This setup is ideal for homelabs and smaller organization deployments.\"\n echo \"For enterprise environments requiring high availability and advanced integrations,\"\n echo \"consider a commercial on-prem license or scaling your open source deployment:\"\n echo \"\"\n echo \" Commercial license: https://netbird.io/pricing#on-prem\"\n echo \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\n echo \"\"\n if [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\n fi\n return 0\n}\n\nprint_traefik_instructions() {\n echo \"\"\n echo \"$MSG_SEPARATOR\"","numLines":45,"startLine":1137,"totalLines":1390}},"sourceToolAssistantUUID":"c14945b2-1dc2-45e8-956e-4272b639cacc","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"92adb81e-f511-4166-9d79-b7a1334ad9c9","isSidechain":false,"message":{"model":"claude-opus-4-6","id":"msg_01NfUi4VhjCKEDpozbNbdGCH","type":"message","role":"assistant","content":[{"type":"text","text":"```bash\n#!/bin/bash\nMSG_SEPARATOR=\"============================================\"\nNETBIRD_DOMAIN=\"demo.netbird.io\"\nNETBIRD_HTTP_PROTOCOL=\"https\"\nNETBIRD_STUN_PORT=\"3478\"\nENABLE_PROXY=\"true\"\n\necho \"\"\necho \"$MSG_SEPARATOR\"\necho \" NETBIRD SETUP COMPLETE\"\necho \"$MSG_SEPARATOR\"\necho \"\"\necho \"You can access the NetBird dashboard at:\"\necho \" $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN\"\necho \"\"\necho \"Follow the onboarding steps to set up your NetBird instance.\"\necho \"\"\necho \"Traefik is handling TLS certificates automatically via Let's Encrypt.\"\necho \"If you see certificate warnings, wait a moment for certificate issuance to complete.\"\necho \"\"\necho \"Open ports:\"\necho \" - 443/tcp (HTTPS - all NetBird services)\"\necho \" - 80/tcp (HTTP - redirects to HTTPS)\"\necho \" - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)\"\nif [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \" - 51820/udp (WIREGUARD - (optional) for P2P proxy connections)\"\nfi\necho \"\"\necho \"This setup is ideal for homelabs and smaller organization deployments.\"\necho \"For enterprise environments requiring high availability and advanced integrations,\"\necho \"consider a commercial on-prem license or scaling your open source deployment:\"\necho \"\"\necho \" Commercial license: https://netbird.io/pricing#on-prem\"\necho \" Scaling guide: https://docs.netbird.io/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment\"\necho \"\"\nif [[ \"$ENABLE_PROXY\" == \"true\" ]]; then\n echo \"NetBird Proxy:\"\n echo \" The proxy service is enabled and running.\"\n echo \" Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy.\"\n echo \" The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge.\"\n echo \" Point your proxy domain to this server's domain address like in the examples below:\"\n echo \"\"\n echo \" *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN\"\n echo \"\"\nfi\n```"}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":861,"cache_read_input_tokens":30444,"output_tokens":595,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":861,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"}},"requestId":"req_011CZdRMLSLRkXQsnTrak33m","type":"assistant","uuid":"6ca5a7b3-1ea5-44a7-a452-f6c8535b52c9","timestamp":"2026-04-01T17:09:28.383Z","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}
{"parentUuid":"6ca5a7b3-1ea5-44a7-a452-f6c8535b52c9","isSidechain":false,"type":"system","subtype":"stop_hook_summary","hookCount":1,"hookInfos":[{"command":"entire hooks claude-code stop","durationMs":3643}],"hookErrors":[],"preventedContinuation":false,"stopReason":"","hasOutput":true,"level":"suggestion","timestamp":"2026-04-01T17:09:32.030Z","uuid":"860c6477-8401-4a84-b903-d5ff9c9fb093","toolUseID":"f4bc93ed-8542-43e8-bef9-5fe29940a0bb","userType":"external","entrypoint":"cli","cwd":"/Users/misha/Documents/GolandProjects/netbird/netbird","sessionId":"263c810b-0eda-4afa-805b-a24c681d4fa5","version":"2.1.89","gitBranch":"main","slug":"dapper-churning-reddy"}