From 7dd92cd30bf48c6023e6e7fd7d9879e5c4349e40 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 22 Aug 2026 19:41:55 +0200 Subject: [PATCH] Fix Node-RED container net: extra_hosts, CODE HTTPS, force-recreate Detect host LAN IP and CODE URL so OCS/Collabora work unattended on each VM. Avoid 127.0.1.1 Nextcloud DNS and HTTP-to-TLS CODE. --- .env.example | 12 ++- docker-compose.yml | 3 +- install.sh | 8 ++ scripts/integrate-into-nodered.sh | 173 ++++++++++++++++++++++++++---- 4 files changed, 172 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index 5d5f463..95fc599 100644 --- a/.env.example +++ b/.env.example @@ -5,10 +5,16 @@ NEXTCLOUD_URL=https://cloud.hannesahlin.se NEXTCLOUD_PUBLIC_URL=https://cloud.hannesahlin.se -# Collabora CODE — bridge IP auto-detected if COLLABORA_IP is empty +# Collabora CODE +# COLLABORA_URL: https://code:9980 när CODE delar Docker-nätverk med Node-RED +# (integrate-into-nodered.sh connectar CODE till nodernätet automatiskt). +# Fallback om 'code' inte kan resolvas: https://:9980. +# ALDRIG http://127.0.0.1:9980 eller http://host.docker.internal:9980 — +# CODE lyssnar bara på HTTPS internt och kör inte på Node-RED-värden. +# verifyTls=false sätts i nc-collab-config-noden (self-signed cert internt). CODE_CONTAINER=code COLLABORA_IP= -COLLABORA_URL= +COLLABORA_URL=https://code:9980 # Existing Node-RED install (integrate mode) NODERED_DIR= @@ -18,6 +24,8 @@ INSTALL_USER= # Sidecar mode (not default) NC_HOST=cloud.hannesahlin.se +# Host-LAN-IP för extra_hosts. Tomt = auto-detekteras av integrate-into- +# nodered.sh (hostname -I, 192.168.x/10.x), fallback 192.168.1.26. NC_HOST_IP= TZ=Europe/Stockholm NODERED_CREDENTIAL_SECRET=change-this-secret-in-production diff --git a/docker-compose.yml b/docker-compose.yml index 68a13f5..7aa021b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,9 @@ services: PORT: "1881" TZ: ${TZ:-Europe/Stockholm} NEXTCLOUD_URL: ${NEXTCLOUD_URL:-https://cloud.hannesahlin.se} - COLLABORA_URL: ${COLLABORA_URL:-http://172.17.0.2:9980} + COLLABORA_URL: ${COLLABORA_URL:-https://code:9980} extra_hosts: + - "host.docker.internal:host-gateway" - "cloud.hannesahlin.se:${NC_HOST_IP:-192.168.1.26}" volumes: - nodered_collab_data:/data diff --git a/install.sh b/install.sh index 447515a..6eb2eea 100755 --- a/install.sh +++ b/install.sh @@ -35,6 +35,13 @@ NODERED_CONTAINER="${NODERED_CONTAINER:-nextcloud-node-red}" NODERED_PORT="${NODERED_PORT:-1880}" CODE_CONTAINER="${CODE_CONTAINER:-code}" COLLABORA_IP="${COLLABORA_IP:-}" +# Aldrig http://127.0.0.1:9980 eller http://host.docker.internal:9980 +case "${COLLABORA_URL:-https://code:9980}" in + http://127.0.0.1:*|http://localhost:*|http://host.docker.internal:*|\ + https://127.0.0.1:*|https://localhost:*|https://host.docker.internal:*|"") + COLLABORA_URL="https://code:9980" ;; + *) COLLABORA_URL="${COLLABORA_URL}" ;; +esac NEXTCLOUD_URL="${NEXTCLOUD_URL:-https://cloud.hannesahlin.se}" [ -n "${NEXTCLOUD_URL}" ] || NEXTCLOUD_URL="https://cloud.hannesahlin.se" @@ -69,6 +76,7 @@ PKG_SRC="${INSTALL_DIR}" \ NODERED_PORT="${NODERED_PORT}" \ CODE_CONTAINER="${CODE_CONTAINER}" \ COLLABORA_IP="${COLLABORA_IP}" \ + COLLABORA_URL="${COLLABORA_URL}" \ NEXTCLOUD_URL="${NEXTCLOUD_URL}" \ INSTALL_USER="${INSTALL_USER:-}" \ bash "${SCRIPT_DIR}/scripts/integrate-into-nodered.sh" diff --git a/scripts/integrate-into-nodered.sh b/scripts/integrate-into-nodered.sh index 75ebddf..4eadffc 100755 --- a/scripts/integrate-into-nodered.sh +++ b/scripts/integrate-into-nodered.sh @@ -146,6 +146,45 @@ code_bridge_ip() { docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{if eq $k "bridge"}}{{$v.IPAddress}}{{end}}{{end}}' "${CODE_CONTAINER}" 2>/dev/null } +# Host-LAN-IP för extra_hosts (detektera; fallback 192.168.1.26) +detect_host_lan_ip() { + local ip="" + if command -v hostname >/dev/null 2>&1; then + ip="$(hostname -I 2>/dev/null | tr ' ' '\n' \ + | grep -E '^(192\.168|10)\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true)" + fi + if [ -z "${ip}" ] && command -v ip >/dev/null 2>&1; then + ip="$(ip -4 addr show scope global 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1 \ + | grep -E '^(192\.168|10)\.' | head -n1 || true)" + fi + if [ -z "${ip}" ]; then + warn "Kunde inte detektera host-LAN-IP — fallback 192.168.1.26" + ip="192.168.1.26" + fi + HOST_LAN_IP="${ip}" + log "Host-LAN-IP: ${HOST_LAN_IP}" +} + +# Skriv NC_HOST_IP i nextcloud-node-red/.env så compose-substitutionen plockar upp den +write_nodered_env_ip() { + local envfile="${NODERED_DIR}/.env" + local existing="" + if [ -f "${envfile}" ]; then + existing="$(grep -E '^NC_HOST_IP=[[:alnum:].:]+' "${envfile}" 2>/dev/null | tail -n1 | cut -d= -f2 || true)" + fi + if [ -n "${existing}" ]; then + log "${envfile} har redan NC_HOST_IP=${existing} — behåller" + HOST_LAN_IP="${existing}" + return 0 + fi + if [ -f "${envfile}" ] && grep -q '^NC_HOST_IP=' "${envfile}" 2>/dev/null; then + run_as_root sed -i "s|^NC_HOST_IP=.*|NC_HOST_IP=${HOST_LAN_IP}|" "${envfile}" + else + run_as_root bash -c "echo 'NC_HOST_IP=${HOST_LAN_IP}' >> '${envfile}'" + fi + log "NC_HOST_IP=${HOST_LAN_IP} → ${envfile}" +} + detect_code_ip() { local ip ip="$(code_bridge_ip)" @@ -202,6 +241,8 @@ resolve_paths() { detect_nodered_dir detect_code_ip + detect_host_lan_ip + write_nodered_env_ip } stop_sidecar_if_any() { @@ -316,13 +357,85 @@ EOF fi } +# Krav: docker network connect code — ignorerar already connected connect_code_network() { - log "Connecting ${NODERED_CONTAINER} to Docker bridge (CODE)" - if docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | grep -q "${NODERED_CONTAINER}"; then - log "Already connected to bridge" - else - docker network connect bridge "${NODERED_CONTAINER}" 2>/dev/null || warn "Could not connect bridge — check manually" + local net + net="$(docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' "${NODERED_CONTAINER}" 2>/dev/null \ + | grep -v '^bridge$' | head -n1)" + [ -n "${net}" ] || die "No Node-RED compose network on ${NODERED_CONTAINER}" + + if docker network inspect "${net}" --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | grep -qw "${CODE_CONTAINER}"; then + log "Already connected: ${CODE_CONTAINER} on ${net}" + return 0 fi + + log "Connecting ${CODE_CONTAINER} → ${net}" + if ! docker network connect "${net}" "${CODE_CONTAINER}" 2>/dev/null; then + if docker network inspect "${net}" --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | grep -qw "${CODE_CONTAINER}"; then + log "Already connected (race): ${CODE_CONTAINER} on ${net}" + else + warn "Could not connect ${CODE_CONTAINER} → ${net} — COLLABORA_URL fallar tillbaka till CODE-IP" + fi + fi +} + +compose_service_name() { + local svc + svc="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.service"}}' "${NODERED_CONTAINER}" 2>/dev/null || true)" + echo "${svc:-node-red}" +} + +# Recreate utan 'docker compose down' — volymer (node-red-data) bevaras. +# ExtraHosts måste vara ifyllda efteråt. +recreate_nodered_service() { + local service extra + service="$(compose_service_name)" + + log "Recreating ${service} (--no-deps --force-recreate, volymer bevaras)..." + if ! compose_run up -d --no-deps --force-recreate "${service}"; then + warn "force-recreate misslyckades — rm + up --no-deps (volymen node-red-data ligger kvar)" + run_as_root docker rm -f "${NODERED_CONTAINER}" 2>/dev/null || true + compose_run up -d --no-deps "${service}" + fi + + for _ in $(seq 1 10); do + extra="$(docker inspect -f '{{len .HostConfig.ExtraHosts}}' "${NODERED_CONTAINER}" 2>/dev/null || echo 0)" + [ "${extra}" != "0" ] && break + sleep 1 + done + if [ -z "${extra:-0}" ] || [ "${extra}" = "0" ]; then + die "ExtraHosts är [] på ${NODERED_CONTAINER} efter recreate — kontrollera extra_hosts i ${NODERED_DIR}/docker-compose.yml" + fi + log "ExtraHosts (${extra}): $(docker inspect -f '{{json .HostConfig.ExtraHosts}}' "${NODERED_CONTAINER}")" +} + +# COLLABORA_URL: https://code:9980 primärt, fallback https://:9980. +# Aldrig http://127.0.0.1:9980 eller http://host.docker.internal:9980. +resolve_collabora_url() { + local url="${COLLABORA_URL:-https://code:9980}" + case "${url}" in + http://127.0.0.1:*|http://localhost:*|http://host.docker.internal:*|\ + https://127.0.0.1:*|https://localhost:*|https://host.docker.internal:*) + warn "Otillåten COLLABORA_URL '${url}' — tvingar https://code:9980" + url="https://code:9980" + ;; + esac + + # Fallback till CODE-IP om 'code' inte kan resolvas från nod-containern + if ! docker exec "${NODERED_CONTAINER}" getent hosts code >/dev/null 2>&1; then + local ip + ip="$(docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$v.IPAddress}} {{end}}' "${CODE_CONTAINER}" 2>/dev/null | awk '{print $1}')" + [ -n "${ip}" ] || ip="${COLLABORA_IP:-}" + if [ -n "${ip}" ]; then + warn "'code' inte resolvbart från ${NODERED_CONTAINER} — fallback https://${ip}:9980" + url="https://${ip}:9980" + else + die "Varken 'code'-DNS eller CODE-IP tillgänglig — kan inte sätta COLLABORA_URL" + fi + fi + + COLLABORA_URL="${url}" + log "COLLABORA_URL=${COLLABORA_URL}" } rebuild_nodered() { @@ -334,7 +447,7 @@ rebuild_nodered() { else compose_run build fi - compose_run up -d + recreate_nodered_service } wait_nodered() { @@ -350,18 +463,35 @@ wait_nodered() { } verify_code_from_nodered() { - local code_ip - code_ip="$(code_bridge_ip)" - [ -n "${code_ip}" ] || code_ip="${COLLABORA_IP}" - # CODE listens on HTTPS internally (HTTP returns empty reply) - local url="https://${code_ip}:9980/hosting/capabilities" - log "Testing CODE from ${NODERED_CONTAINER}: ${url}" - if docker exec "${NODERED_CONTAINER}" curl -sfk "${url}" >/dev/null 2>&1; then - log "OK: CODE reachable from Node-RED container" + # Krav 7: getent i containern != 127.0.0.1/127.0.1.1 + curl mot CODE och Nextcloud + local fail=0 h ip_out + for h in cloud.hannesahlin.se docs.hannesahlin.se host.docker.internal code; do + ip_out="$(docker exec "${NODERED_CONTAINER}" getent hosts "${h}" 2>/dev/null | awk '{print $1}' | head -n1 || true)" + if [ -z "${ip_out}" ]; then + warn "getent ${h}: INGET SVAR"; fail=1 + elif [ "${ip_out#127.}" != "${ip_out}" ] || [ "${ip_out}" = "::1" ]; then + warn "getent ${h}: loopback (${ip_out}) — extra_hosts/translering trasig"; fail=1 + else + log "getent ${h}: ${ip_out}" + fi + done + + if docker exec "${NODERED_CONTAINER}" curl -sfk "${COLLABORA_URL}/hosting/capabilities" >/dev/null 2>&1; then + log "OK: CODE nås från Node-RED: ${COLLABORA_URL}/hosting/capabilities" else - warn "CODE not reachable — run: docker network connect bridge ${NODERED_CONTAINER}" - warn "nc-collab-config collaboraUrl=https://${code_ip}:9980 (verifyTls=false)" + warn "CODE inte nåbar: ${COLLABORA_URL}/hosting/capabilities" + warn "Kör: docker network connect \$(docker inspect -f '{{range \$k,\$_ := .NetworkSettings.Networks}}{{printf \"%s\\n\" \$k}}{{end}}' ${NODERED_CONTAINER} | head -n1) ${CODE_CONTAINER}" + fail=1 fi + + if docker exec "${NODERED_CONTAINER}" curl -sfk https://cloud.hannesahlin.se/status.php >/dev/null 2>&1; then + log "OK: Nextcloud nås från Node-RED: https://cloud.hannesahlin.se/status.php" + else + warn "Nextcloud inte nåbar från Node-RED via https://cloud.hannesahlin.se/status.php" + fail=1 + fi + + return "${fail}" } main() { @@ -377,13 +507,14 @@ main() { patch_entrypoint rebuild_nodered connect_code_network + resolve_collabora_url wait_nodered - verify_code_from_nodered + if ! verify_code_from_nodered; then + warn "Verifiering hade fel — se varningarna ovan" + fi - local host_ip code_ip nc_url + local host_ip nc_url host_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || echo "127.0.0.1")" - code_ip="$(code_bridge_ip)" - [ -n "${code_ip}" ] || code_ip="${COLLABORA_IP}" nc_url="${NEXTCLOUD_URL:-https://cloud.hannesahlin.se}" cat <