Installing rocketchat failed with
invalid IPv4 address: ParseAddr("IP_DATA_2"): unable to parse IP
ipUpdateComposeTags allocates one IP per SERVICE_TAG_N annotation and fills
IP_TAG_i only where SERVICE_TAG_i exists. The four new apps tagged only their
primary service, so every sidecar — matrix's postgres, mattermost's postgres,
rocketchat's mongo, and fifteen of stoat's sixteen — kept a literal IP_DATA_n
in the deployed compose and docker refused to create the container.
Tag every service that carries an ipv4_address, index-aligned with its IP_TAG.
For stoat that also meant moving caddy from SERVICE_TAG_1 to _6 so the indices
line up with the IPs rather than the reading order.
mastodon had the same latent break (IP_TAG_2 and _3 untagged) and is fixed the
same way — it would have failed on first install for the same reason.
SERVICE_TAG carries the compose *key*, not container_name: 'libreportal app
restart <app> <service>' passes it to 'docker compose restart', which only
understands keys.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
143 lines
6.7 KiB
Bash
143 lines
6.7 KiB
Bash
#!/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_1:-}"
|
|
if [[ -z "$adguard_pass" ]]; then
|
|
adguard_pass=$(generateRandomPassword)
|
|
updateConfigOption "CFG_ADGUARD_PASSWORD_1" "$adguard_pass" >/dev/null 2>&1 || true
|
|
isNotice "Generated a random AdGuardHome admin password and saved it to CFG_ADGUARD_PASSWORD_1."
|
|
fi
|
|
|
|
# Internal container ports are fixed (3000 admin, 53 DNS); host
|
|
# mapping is what `usedport1` etc. handle.
|
|
local adguard_payload
|
|
adguard_payload=$(cat <<JSON
|
|
{
|
|
"web": { "ip": "0.0.0.0", "port": 3000, "autofix": false },
|
|
"dns": { "ip": "0.0.0.0", "port": 53, "autofix": false },
|
|
"username": "${adguard_user}",
|
|
"password": "${adguard_pass}"
|
|
}
|
|
JSON
|
|
)
|
|
if curl -fsS -X POST \
|
|
-H 'Content-Type: application/json' \
|
|
--data "$adguard_payload" \
|
|
--max-time 15 \
|
|
"${adguard_setup_url}/control/install/configure" >/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 `<host_port>: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_1"
|
|
}
|