mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-29 19:11:28 +02:00
[infrastructure] Improve domain, Docker Compose, and license validation in self-hosted scripts (#7339)
This commit is contained in:
@@ -15,16 +15,25 @@ NETBIRD_EULA_URL="https://netbird.io/self-hosted-EULA"
|
||||
# server trusts X-Forwarded-* headers from this address only.
|
||||
TRAEFIK_IP="172.30.0.10"
|
||||
|
||||
LICENSE_VERDICT="unknown"
|
||||
LICENSE_LOG_LINES=""
|
||||
|
||||
check_docker_compose() {
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
echo "docker-compose"
|
||||
return
|
||||
if ! command -v docker &> /dev/null && ! command -v docker-compose &> /dev/null; then
|
||||
echo "Docker is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
if docker compose --help &> /dev/null; then
|
||||
|
||||
if docker compose version &> /dev/null; then
|
||||
echo "docker compose"
|
||||
return
|
||||
fi
|
||||
echo "docker-compose is not installed or not in PATH. See https://docs.docker.com/engine/install/" > /dev/stderr
|
||||
if command -v docker-compose &> /dev/null && docker-compose version &> /dev/null; then
|
||||
echo "docker-compose"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Docker Compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/compose/install/" > /dev/stderr
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -221,6 +230,90 @@ wait_postgres() {
|
||||
set -e
|
||||
}
|
||||
|
||||
wait_for_license_verdict() {
|
||||
local counter=0
|
||||
local logs=""
|
||||
|
||||
echo -n "Waiting for the server to validate the license"
|
||||
while [[ $counter -lt 60 ]]; do
|
||||
logs=$($DOCKER_COMPOSE_COMMAND logs --no-color --tail=all netbird-server 2>/dev/null || true)
|
||||
|
||||
if grep -qi "license invalidated" <<< "$logs"; then
|
||||
echo " rejected"
|
||||
LICENSE_VERDICT="rejected"
|
||||
LICENSE_LOG_LINES=$(grep -i "license" <<< "$logs" | tail -n 5 || true)
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -qi "license validated" <<< "$logs"; then
|
||||
echo " ok"
|
||||
LICENSE_VERDICT="ok"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -n " ."
|
||||
sleep 2
|
||||
counter=$((counter + 1))
|
||||
done
|
||||
|
||||
echo " no verdict in 120s"
|
||||
LICENSE_VERDICT="unknown"
|
||||
LICENSE_LOG_LINES=$(grep -iE "failed to validate license|error validating license" <<< "$logs" | tail -n 3 || true)
|
||||
return 0
|
||||
}
|
||||
|
||||
report_license_verdict() {
|
||||
if [[ "$LICENSE_VERDICT" == "ok" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$LICENSE_VERDICT" == "unknown" ]]; then
|
||||
echo ""
|
||||
echo " ⚠ The server logged no license verdict within 120s."
|
||||
if [[ -n "$LICENSE_LOG_LINES" ]]; then
|
||||
echo " It was still reporting validation errors:"
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
done <<< "$LICENSE_LOG_LINES"
|
||||
fi
|
||||
echo ""
|
||||
echo " Check the verdict with:"
|
||||
echo ""
|
||||
echo " $DOCKER_COMPOSE_COMMAND logs netbird-server | grep -i license"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local unreachable="false"
|
||||
if grep -qi "couldn't be validated with the license server" <<< "$LICENSE_LOG_LINES"; then
|
||||
unreachable="true"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "$unreachable" == "true" ]]; then
|
||||
echo " ⚠ The server could not validate the license:"
|
||||
else
|
||||
echo " ⚠ The server rejected the license key:"
|
||||
fi
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
done <<< "$LICENSE_LOG_LINES"
|
||||
echo ""
|
||||
echo " The stack is up, and only the license check did not pass."
|
||||
echo ""
|
||||
if [[ "$unreachable" == "true" ]]; then
|
||||
echo " The license server could not be reached, so the key itself was"
|
||||
echo " never checked. Confirm this host has outbound access to the"
|
||||
echo " license server, then restart:"
|
||||
else
|
||||
echo " Check the reason the server gave above, verify that"
|
||||
echo " NETBIRD_LICENSE_KEY in .env matches the key you were issued,"
|
||||
echo " then restart:"
|
||||
fi
|
||||
echo ""
|
||||
echo " $DOCKER_COMPOSE_COMMAND up -d"
|
||||
return 0
|
||||
}
|
||||
|
||||
init_environment() {
|
||||
check_openssl
|
||||
DOCKER_COMPOSE_COMMAND=$(check_docker_compose)
|
||||
@@ -299,6 +392,9 @@ init_environment() {
|
||||
echo "Starting remaining services ..."
|
||||
$DOCKER_COMPOSE_COMMAND up -d
|
||||
|
||||
echo ""
|
||||
wait_for_license_verdict
|
||||
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo ""
|
||||
@@ -309,6 +405,12 @@ init_environment() {
|
||||
echo ""
|
||||
echo "Tail logs:"
|
||||
echo " cd $(pwd) && $DOCKER_COMPOSE_COMMAND logs -f netbird-server traefik"
|
||||
|
||||
report_license_verdict
|
||||
|
||||
if [[ "$LICENSE_VERDICT" == "rejected" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -60,18 +60,21 @@ check_docker_sock_perms() {
|
||||
}
|
||||
|
||||
check_docker_compose() {
|
||||
if command -v docker-compose &> /dev/null
|
||||
then
|
||||
echo "docker-compose"
|
||||
return
|
||||
fi
|
||||
if docker compose --help &> /dev/null
|
||||
then
|
||||
echo "docker compose"
|
||||
return
|
||||
if ! command -v docker &> /dev/null && ! command -v docker-compose &> /dev/null; then
|
||||
echo "Docker is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "docker-compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
|
||||
if docker compose version &> /dev/null; then
|
||||
echo "docker compose"
|
||||
return
|
||||
fi
|
||||
if command -v docker-compose &> /dev/null && docker-compose version &> /dev/null; then
|
||||
echo "docker-compose"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Docker Compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/compose/install/" > /dev/stderr
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -98,19 +101,39 @@ get_main_ip_address() {
|
||||
}
|
||||
|
||||
check_nb_domain() {
|
||||
DOMAIN=$1
|
||||
if [[ "$DOMAIN-x" == "-x" ]]; then
|
||||
local domain="$1"
|
||||
|
||||
if [[ -z "$domain" ]]; then
|
||||
echo "The NETBIRD_DOMAIN variable cannot be empty." > /dev/stderr
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$DOMAIN" == "netbird.example.com" ]]; then
|
||||
if [[ "$domain" == "use-ip" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$domain" == "netbird.example.com" ]]; then
|
||||
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
|
||||
return 1
|
||||
fi
|
||||
if [[ "$domain" =~ ^[0-9.]+$ ]]; then
|
||||
echo "'$domain' is an IP address. Use 'use-ip' to install on this host's IP over HTTP, or an FQDN to get a TLS certificate." > /dev/stderr
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "$domain" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$ ]]; then
|
||||
echo "'$domain' is not a valid FQDN. It needs at least one dot (e.g. netbird.my-domain.com), with no scheme, port or trailing dot." > /dev/stderr
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_domain_resolves() {
|
||||
local domain="$1"
|
||||
if command -v getent &> /dev/null && getent hosts "$domain" &> /dev/null; then return 0; fi
|
||||
if command -v host &> /dev/null && host "$domain" &> /dev/null; then return 0; fi
|
||||
if command -v dig &> /dev/null && [[ -n "$(dig +short "$domain" 2>/dev/null)" ]]; then return 0; fi
|
||||
if command -v nslookup &> /dev/null && nslookup "$domain" &> /dev/null; then return 0; fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Non-interactive configuration
|
||||
# ------------------------------
|
||||
# Every prompt below can be pre-answered with an environment variable, so the
|
||||
@@ -170,7 +193,22 @@ read_nb_domain() {
|
||||
read -r READ_NETBIRD_DOMAIN < /dev/tty
|
||||
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
|
||||
read_nb_domain
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$READ_NETBIRD_DOMAIN" != "use-ip" ]] && ! check_domain_resolves "$READ_NETBIRD_DOMAIN"; then
|
||||
local confirm=""
|
||||
echo "" > /dev/stderr
|
||||
echo "Warning: '$READ_NETBIRD_DOMAIN' does not resolve via DNS from this host." > /dev/stderr
|
||||
echo "TLS certificate issuance and client connections will fail until it does." > /dev/stderr
|
||||
echo -n "Continue anyway? [y/N]: " > /dev/stderr
|
||||
read -r confirm < /dev/tty
|
||||
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||
read_nb_domain
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$READ_NETBIRD_DOMAIN"
|
||||
return 0
|
||||
}
|
||||
@@ -439,12 +477,23 @@ configure_domain() {
|
||||
# Domain is validated (not a free-form value), so it keeps its own guard
|
||||
# rather than going through resolve(): a valid NETBIRD_DOMAIN is used as-is,
|
||||
# otherwise we prompt, or abort when there is no terminal to prompt on.
|
||||
local prompted="false"
|
||||
if ! check_nb_domain "$NETBIRD_DOMAIN"; then
|
||||
if ! tty_available; then
|
||||
echo "NETBIRD_DOMAIN is required for a non-interactive install." > /dev/stderr
|
||||
if [[ -n "$NETBIRD_DOMAIN" ]]; then
|
||||
echo "NETBIRD_DOMAIN='$NETBIRD_DOMAIN' cannot be used for a non-interactive install." > /dev/stderr
|
||||
else
|
||||
echo "NETBIRD_DOMAIN is required for a non-interactive install." > /dev/stderr
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
NETBIRD_DOMAIN=$(read_nb_domain)
|
||||
prompted="true"
|
||||
fi
|
||||
|
||||
if [[ "$prompted" == "false" && "$NETBIRD_DOMAIN" != "use-ip" ]] && ! check_domain_resolves "$NETBIRD_DOMAIN"; then
|
||||
echo "Warning: '$NETBIRD_DOMAIN' does not resolve via DNS from this host." > /dev/stderr
|
||||
echo "TLS certificate issuance and client connections will fail until it does." > /dev/stderr
|
||||
fi
|
||||
|
||||
if [[ "$NETBIRD_DOMAIN" == "use-ip" ]]; then
|
||||
|
||||
@@ -40,6 +40,10 @@ ENTERPRISE_CONFIG_FILE="config.yaml.enterprise"
|
||||
# completed successfully.
|
||||
ROLLBACK_STATE="disarmed"
|
||||
ENV_EXISTED="unknown"
|
||||
# Verdict the server logs about the license key on startup: ok, rejected, or
|
||||
# unknown when neither line appeared before the timeout.
|
||||
LICENSE_VERDICT="unknown"
|
||||
LICENSE_LOG_LINES=""
|
||||
ENV_BACKUP=""
|
||||
PG_VOLUME_NAME=""
|
||||
BACKUP_DIR=""
|
||||
@@ -59,15 +63,21 @@ ENTERPRISE_CONFIG="no"
|
||||
NETBIRD_EULA_URL="https://netbird.io/self-hosted-EULA"
|
||||
|
||||
check_docker_compose() {
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
echo "docker-compose"
|
||||
return
|
||||
if ! command -v docker &> /dev/null && ! command -v docker-compose &> /dev/null; then
|
||||
echo "Docker is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
if docker compose --help &> /dev/null; then
|
||||
|
||||
if docker compose version &> /dev/null; then
|
||||
echo "docker compose"
|
||||
return
|
||||
fi
|
||||
echo "docker-compose is not installed or not in PATH." > /dev/stderr
|
||||
if command -v docker-compose &> /dev/null && docker-compose version &> /dev/null; then
|
||||
echo "docker-compose"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Docker Compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/compose/install/" > /dev/stderr
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -1000,6 +1010,39 @@ init_migration() {
|
||||
check_stale_postgres_volume
|
||||
}
|
||||
|
||||
wait_for_license_verdict() {
|
||||
local counter=0
|
||||
local logs=""
|
||||
|
||||
echo -n "Waiting for the server to validate the license"
|
||||
while [[ $counter -lt 60 ]]; do
|
||||
|
||||
logs=$($DOCKER_COMPOSE_COMMAND logs --no-color --tail=all "$COMBINED_SERVICE" 2>/dev/null || true)
|
||||
|
||||
if grep -qi "license invalidated" <<< "$logs"; then
|
||||
echo " rejected"
|
||||
LICENSE_VERDICT="rejected"
|
||||
LICENSE_LOG_LINES=$(grep -i "license" <<< "$logs" | tail -n 5 || true)
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -qi "license validated" <<< "$logs"; then
|
||||
echo " ok"
|
||||
LICENSE_VERDICT="ok"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -n " ."
|
||||
sleep 2
|
||||
counter=$((counter + 1))
|
||||
done
|
||||
|
||||
echo " no verdict in 120s"
|
||||
LICENSE_VERDICT="unknown"
|
||||
LICENSE_LOG_LINES=$(grep -iE "failed to validate license|error validating license" <<< "$logs" | tail -n 3 || true)
|
||||
return 0
|
||||
}
|
||||
|
||||
apply_changes() {
|
||||
# From here on a failure must roll the deployment back.
|
||||
ROLLBACK_STATE="armed"
|
||||
@@ -1100,9 +1143,57 @@ apply_changes() {
|
||||
echo "Bringing up all services ..."
|
||||
$DOCKER_COMPOSE_COMMAND up -d
|
||||
|
||||
echo ""
|
||||
wait_for_license_verdict
|
||||
|
||||
echo ""
|
||||
echo "Migration complete."
|
||||
|
||||
if [[ "$LICENSE_VERDICT" == "rejected" ]]; then
|
||||
local unreachable="false"
|
||||
if grep -qi "couldn't be validated with the license server" <<< "$LICENSE_LOG_LINES"; then
|
||||
unreachable="true"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "$unreachable" == "true" ]]; then
|
||||
echo " ⚠ The server could not validate the license:"
|
||||
else
|
||||
echo " ⚠ The server rejected the license key:"
|
||||
fi
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
done <<< "$LICENSE_LOG_LINES"
|
||||
echo ""
|
||||
echo " The migration itself completed: the images and any migrated data"
|
||||
echo " are in place, and only the license check did not pass."
|
||||
echo ""
|
||||
if [[ "$unreachable" == "true" ]]; then
|
||||
echo " The license server could not be reached, so the key itself was"
|
||||
echo " never checked. Confirm this host has outbound access to the"
|
||||
echo " license server, then restart:"
|
||||
else
|
||||
echo " Check the reason the server gave above, verify that"
|
||||
echo " NB_LICENSE_KEY in .env matches the key you were issued, then"
|
||||
echo " restart:"
|
||||
fi
|
||||
echo ""
|
||||
echo " $DOCKER_COMPOSE_COMMAND up -d"
|
||||
elif [[ "$LICENSE_VERDICT" == "unknown" ]]; then
|
||||
echo ""
|
||||
echo " ⚠ The server logged no license verdict within 120s."
|
||||
if [[ -n "$LICENSE_LOG_LINES" ]]; then
|
||||
echo " It was still reporting validation errors:"
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
done <<< "$LICENSE_LOG_LINES"
|
||||
fi
|
||||
echo ""
|
||||
echo " Check the verdict with:"
|
||||
echo ""
|
||||
echo " $DOCKER_COMPOSE_COMMAND logs $COMBINED_SERVICE | grep -i license"
|
||||
fi
|
||||
|
||||
# Nothing left to undo.
|
||||
ROLLBACK_STATE="disarmed"
|
||||
}
|
||||
@@ -1122,6 +1213,11 @@ print_summary() {
|
||||
fi
|
||||
[[ "$ENABLE_FLOW" == "yes" ]] && echo " Traffic flow: enabled"
|
||||
[[ "$ENABLE_FLOW" != "yes" ]] && echo " Traffic flow: disabled"
|
||||
case "$LICENSE_VERDICT" in
|
||||
ok) echo " License: validated by the server" ;;
|
||||
rejected) echo " License: REJECTED - see above, the install is not usable yet" ;;
|
||||
*) echo " License: not confirmed (no verdict in the logs yet)" ;;
|
||||
esac
|
||||
echo ""
|
||||
echo " Generated files (next to your docker-compose.yml):"
|
||||
echo " $OVERRIDE_FILE"
|
||||
@@ -1176,3 +1272,10 @@ trap 'exit 130' INT TERM
|
||||
init_migration
|
||||
apply_changes
|
||||
print_summary
|
||||
|
||||
# A rejected license leaves a migrated but unusable install. Say so in the exit
|
||||
# code too, or a wrapper script reads this run as a clean success.
|
||||
if [[ "$LICENSE_VERDICT" == "rejected" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user