#!/bin/bash # AdGuard Home install hooks — drive the first-boot setup wizard via its # HTTP API so the admin doesn't have to click through five pages, then # pin the admin bind back to 0.0.0.0:3000 (matches the compose mapping) # and health-check the result. adguard_install_post_start() { local app_name="$1" ((menu_number++)) echo "" echo "---- $menu_number. Completing AdGuardHome initial setup automatically" echo "" # The legacy `$usedport1` variable isn't populated by the current # install pipeline; the resolved host port is stored in the PORTS_TAG_1 # docker-compose tag (format `external:internal`). Pull it from there # so the curl + URL printout actually point somewhere real. local adguard_compose_file="$containers_dir$app_name/docker-compose.yml" local adguard_port_pair adguard_port_pair=$(tagsManagerGetTagContent "$adguard_compose_file" "PORTS_TAG_1") local adguard_admin_port="${adguard_port_pair%%:*}" if [[ -n "$public_ip_v4" && -n "$adguard_admin_port" ]]; then echo " External : http://$public_ip_v4:$adguard_admin_port/" fi if [[ -n "$host_setup" ]]; then echo " Hostname : http://$host_setup/" fi echo "" # AdGuardHome ships a setup wizard that normally needs five clicks in # a browser before the daemon writes its config file. Same wizard is # exposed as an HTTP API (POST /control/install/configure), so drive # it from here and skip the manual interaction. Pre-poll the admin # endpoint until the container is up, then send the form, then let # the post-install sed edits run against the freshly written # AdGuardHome.yaml. local adguard_setup_url="http://127.0.0.1:${adguard_admin_port}" local adguard_attempts=0 local adguard_max_attempts=60 while ((adguard_attempts < adguard_max_attempts)); do if curl -fsS -o /dev/null --max-time 2 "${adguard_setup_url}/control/status" 2>/dev/null \ || curl -fsS -o /dev/null --max-time 2 "${adguard_setup_url}/control/install/get_addresses" 2>/dev/null; then break fi sleep 2 ((adguard_attempts++)) done if ((adguard_attempts >= adguard_max_attempts)); then isError "AdGuardHome admin endpoint did not respond on $adguard_setup_url within $((adguard_max_attempts * 2))s — open the URL and complete setup manually, then re-run the installer to apply the post-setup tweaks." else local adguard_user="${CFG_ADGUARD_USER:-admin}" local adguard_pass="${CFG_ADGUARD_PASSWORD:-}" if [[ -z "$adguard_pass" ]]; then adguard_pass=$(generateRandomPassword) updateConfigOption "CFG_ADGUARD_PASSWORD" "$adguard_pass" >/dev/null 2>&1 || true isNotice "Generated a random AdGuardHome admin password and saved it to CFG_ADGUARD_PASSWORD." fi # Internal container ports are fixed (3000 admin, 53 DNS); host # mapping is what `usedport1` etc. handle. local adguard_payload adguard_payload=$(cat </dev/null 2>&1; then isSuccessful "AdGuardHome admin setup completed automatically (user: $adguard_user)." else # 422/403 here typically means setup was already done on a # previous install; the post-setup tweaks below are still # safe to run against the existing yaml. isNotice "AdGuardHome /control/install/configure rejected the request — assuming it's already configured. If this is a fresh install, complete setup manually at $adguard_setup_url." fi fi local result if [[ "$public" == "true" ]]; then result=$(runFileOp sed -i "s|allow_unencrypted_doh: false|allow_unencrypted_doh: true|g" "$containers_dir$app_name/conf/AdGuardHome.yaml") checkSuccess "Setting allow_unencrypted_doh to false for Traefik" fi result=$(runFileOp sed -i "s|anonymize_client_ip: false: false|anonymize_client_ip: true|g" "$containers_dir$app_name/conf/AdGuardHome.yaml") checkSuccess "Setting anonymize_client_ip to true for privacy reasons" # Force the admin web bind back to 0.0.0.0:3000 inside the container. # The docker-compose mapping is `:3000`, so the container # MUST listen on 3000 internally for the host port to reach it. After # the install API call AdGuardHome sometimes ends up bound to # 0.0.0.0:80 (its build-time default) — exactly what causes "unable # to connect" on the host port. local adguard_yaml="$containers_dir$app_name/conf/AdGuardHome.yaml" if [[ -f "$adguard_yaml" ]]; then runFileOp sed -i 's|^\(\s*address:\s*\)0\.0\.0\.0:[0-9]\+|\10.0.0.0:3000|' "$adguard_yaml" runFileOp sed -i 's|^\(\s*bind_host:\s*\).*|\10.0.0.0|' "$adguard_yaml" runFileOp sed -i 's|^\(\s*bind_port:\s*\)[0-9]\+|\13000|' "$adguard_yaml" checkSuccess "Pinned AdGuardHome admin bind to 0.0.0.0:3000 (matches the compose port mapping)." fi dockerComposeRestart "$app_name" # Drop `-f` and accept any HTTP status code: now that the admin # account is configured, /control/status returns 401 to an # unauthenticated request — which is fine, it means the server is up # and answering. We only care whether the connection succeeded at # all, not what the response body says. local adguard_health_attempts=0 local adguard_health_code while ((adguard_health_attempts < 20)); do adguard_health_code=$(curl -sS -o /dev/null --max-time 2 \ -w '%{http_code}' "${adguard_setup_url}/control/status" 2>/dev/null) if [[ "$adguard_health_code" =~ ^[1-5][0-9][0-9]$ ]]; then isSuccessful "AdGuardHome admin UI is reachable on $adguard_setup_url (HTTP $adguard_health_code)" break fi sleep 1 ((adguard_health_attempts++)) done if ((adguard_health_attempts >= 20)); then isError "AdGuardHome admin UI did not respond after restart on $adguard_setup_url. Check the container logs (\`docker logs adguard-service\`) and the conf/AdGuardHome.yaml bind address." fi } adguard_install_message_data() { # Echo the admin user + password as space-separated tokens so they # become $username $password positional args to menuShowFinalMessages. echo "${CFG_ADGUARD_USER:-admin} $CFG_ADGUARD_PASSWORD" }