An instance's isolation never needed a domain — its own slug, dir, secrets, IP and randomly-allocated host port already make two copies independent. But the routing layer assumed one, so a LAN-only box got a broken instance rather than a port-served one. Four fixes: - instanceCreate now rewrites the parent-service column of the cloned config's PORT_ rows to match the service names it stamps into the compose. That value is stored as network_resources.parent_service and joined against the compose-derived service names, so an instance left carrying the TYPE's service name matched nothing: it rendered in the WebUI with no port, no URL and no login row despite being up and reachable. - `instance create --local` (plus a LAN-only toggle in the modal) forces every port to access=private, traefik=false, for a second copy that should stay off the domain even when one is configured. - initializeAppVariables forces the traefik column false when no CFG_DOMAIN_n is set. Previously a traefik=true port with an empty domain stamped Host(`app.`) — a trailing-dot host matching nothing — and dragged APP_URL to https://app. with it, breaking every app that builds its links from APP_URL. host_setup is blanked for the same reason. The published host port is untouched; access type, not the traefik flag, gates allocation. - APP_URL's direct host-port branch now prefers a new $local_ip_v4 (the source IP for the default route) over $public_ip_v4, which is the WAN address from an external resolver. LibrePortal never forwards ports, so the WAN address was unreachable for exactly the LAN/VPN clients that branch serves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
201 lines
8.5 KiB
Bash
Executable File
201 lines
8.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Default app variable setups
|
|
initializeAppVariables()
|
|
{
|
|
app_name="$1"
|
|
|
|
if [[ "$app_name" == "" ]]; then
|
|
isError "Something went wrong...No app name provided..."
|
|
if [[ "$initial_command2" == "terminal" ]]; then
|
|
resetToMenu;
|
|
fi
|
|
fi
|
|
|
|
# Build variable names based on app_name
|
|
compose_setup_var="CFG_${app_name^^}_COMPOSE_FILE"
|
|
domain_var="CFG_${app_name^^}_DOMAIN"
|
|
whitelist_var="CFG_${app_name^^}_WHITELIST"
|
|
healthcheck_var="CFG_${app_name^^}_HEALTHCHECK"
|
|
authelia_var="CFG_${app_name^^}_AUTHELIA"
|
|
headscale_var="CFG_${app_name^^}_HEADSCALE"
|
|
app_category_var="CFG_${app_name^^}_CATEGORY"
|
|
app_title_var="CFG_${app_name^^}_TITLE"
|
|
|
|
# Access the variables using variable indirection
|
|
compose_setup="${!compose_setup_var}"
|
|
domain="${!domain_var}"
|
|
whitelist="${!whitelist_var}"
|
|
healthcheck="${!healthcheck_var}"
|
|
authelia_setup="${!authelia_var}"
|
|
headscale_setup="${!headscale_var}"
|
|
app_category="${!app_category_var}"
|
|
app_title="${!app_title_var}"
|
|
domain_var_name="CFG_DOMAIN_${domain}"
|
|
domain_full="${!domain_var_name}"
|
|
ssl_key=${domain_full}.key
|
|
ssl_crt=${domain_full}.crt
|
|
# host_setup (the app's primary/canonical FQDN) is derived from the primary
|
|
# Traefik port's subdomain, computed after the port arrays are parsed below.
|
|
|
|
# Port configuration variables
|
|
port_config_vars=()
|
|
port_config_data=()
|
|
# Arrays to hold parsed port configuration data
|
|
port_service_names=()
|
|
port_parent_services=()
|
|
port_data_tags=()
|
|
port_external_ports=()
|
|
port_internal_ports=()
|
|
port_access_types=()
|
|
port_protocols=()
|
|
port_traefik_managed=()
|
|
port_url_accessibles=()
|
|
port_login_requireds=()
|
|
port_labels=()
|
|
port_url_paths=()
|
|
port_subdomains=()
|
|
port_recommendeds=()
|
|
|
|
for i in {1..20}; do
|
|
port_config_vars+=("CFG_${app_name^^}_PORT_$i")
|
|
# Store actual config data for port allocation
|
|
local port_config_var="CFG_${app_name^^}_PORT_$i"
|
|
local port_config_value="${!port_config_var}"
|
|
if [[ -n "$port_config_value" ]]; then
|
|
port_config_data+=("$port_config_value")
|
|
|
|
# 12-col: parent|name|ext:int|access|proto|login|traefik|webui|label|url_path|subdomain|recommended
|
|
# 10-col: parent|name|ext:int|access|proto|login|traefik|webui|label|url_path
|
|
# 9-col: parent|name|ext:int|access|proto|login|traefik|webui|label
|
|
# Legacy 8-col: parent|name|ext:int|access|proto|traefik|webui|label (login defaults to false)
|
|
# Legacy 7-col: name|ext:int|access|proto|traefik|webui|label (no parent, login defaults to false)
|
|
local parts=(${port_config_value//|/ })
|
|
if [[ ${#parts[@]} -ge 9 ]]; then
|
|
local external_port="${parts[2]%%:*}"
|
|
local internal_port="${parts[2]##*:}"
|
|
port_parent_services+=("${parts[0]}")
|
|
port_service_names+=("${parts[1]}")
|
|
port_data_tags+=("PORTS_TAG_$i")
|
|
port_external_ports+=("$external_port")
|
|
port_internal_ports+=("$internal_port")
|
|
port_access_types+=("${parts[3]}")
|
|
port_protocols+=("${parts[4]}")
|
|
port_login_requireds+=("${parts[5]}")
|
|
port_traefik_managed+=("${parts[6]}")
|
|
port_url_accessibles+=("${parts[7]}")
|
|
port_labels+=("${parts[8]}")
|
|
port_url_paths+=("${parts[9]:-}")
|
|
port_subdomains+=("${parts[10]:-}")
|
|
# Recommended defaults to the webui flag when the column isn't present —
|
|
# matches the panel's expectation that webui ports are primary by default.
|
|
if [[ ${#parts[@]} -ge 12 ]]; then
|
|
port_recommendeds+=("${parts[11]}")
|
|
else
|
|
port_recommendeds+=("${parts[7]}")
|
|
fi
|
|
elif [[ ${#parts[@]} -ge 8 ]]; then
|
|
local external_port="${parts[2]%%:*}"
|
|
local internal_port="${parts[2]##*:}"
|
|
port_parent_services+=("${parts[0]}")
|
|
port_service_names+=("${parts[1]}")
|
|
port_data_tags+=("PORTS_TAG_$i")
|
|
port_external_ports+=("$external_port")
|
|
port_internal_ports+=("$internal_port")
|
|
port_access_types+=("${parts[3]}")
|
|
port_protocols+=("${parts[4]}")
|
|
port_traefik_managed+=("${parts[5]}")
|
|
port_url_accessibles+=("${parts[6]}")
|
|
port_login_requireds+=("false")
|
|
port_labels+=("${parts[7]}")
|
|
port_url_paths+=("")
|
|
port_subdomains+=("")
|
|
port_recommendeds+=("${parts[6]}")
|
|
elif [[ ${#parts[@]} -ge 7 ]]; then
|
|
local external_port="${parts[1]%%:*}"
|
|
local internal_port="${parts[1]##*:}"
|
|
port_parent_services+=("")
|
|
port_service_names+=("${parts[0]}")
|
|
port_data_tags+=("PORTS_TAG_$i")
|
|
port_external_ports+=("$external_port")
|
|
port_internal_ports+=("$internal_port")
|
|
port_access_types+=("${parts[2]}")
|
|
port_protocols+=("${parts[3]}")
|
|
port_traefik_managed+=("${parts[4]}")
|
|
port_url_accessibles+=("${parts[5]}")
|
|
port_login_requireds+=("false")
|
|
port_labels+=("${parts[6]}")
|
|
port_url_paths+=("")
|
|
port_subdomains+=("")
|
|
port_recommendeds+=("${parts[5]}")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Default Empty config options
|
|
if [ "$authelia_setup" == "" ]; then
|
|
authelia_setup=false
|
|
fi
|
|
if [ "$headscale_setup" == "" ]; then
|
|
headscale_setup=false
|
|
fi
|
|
if [ "$whitelist" == "" ]; then
|
|
whitelist=false
|
|
fi
|
|
if [ "$healthcheck" == "" ]; then
|
|
healthcheck=true
|
|
fi
|
|
|
|
# No domain configured -> no Traefik, whatever the per-port column says.
|
|
# LibrePortal is LAN/VPN-first: a box with CFG_DOMAIN_<n> unset must still
|
|
# serve every app on http://<ip>:<port>. Left alone, a traefik=true port with
|
|
# an empty $domain_full stamps Host(`<app>.`) — a trailing-dot host that
|
|
# matches nothing — and drags APP_URL to https://<app>. with it, breaking any
|
|
# app that builds its links from APP_URL (Bookstack, Nextcloud, Mastodon).
|
|
# Forcing the column false makes tagsProcessorPortRouterBlocks comment the
|
|
# router out entirely and lets tagsProcessorAppUrl fall through to the
|
|
# http://<ip>:<port> branch. The published host port is unaffected — access
|
|
# type, not the traefik flag, decides whether a port is allocated.
|
|
if [[ -z "$domain_full" ]]; then
|
|
local _nd
|
|
for _nd in "${!port_traefik_managed[@]}"; do
|
|
port_traefik_managed[$_nd]="false"
|
|
done
|
|
fi
|
|
|
|
# $public is derived from the port config — true iff any of this app's
|
|
# PORT_<n> rows have field 7 (traefik) == "true". Replaces the legacy
|
|
# CFG_<APP>_PUBLIC field, which was redundant with the per-port flag.
|
|
public="false"
|
|
local _t
|
|
for _t in "${port_traefik_managed[@]}"; do
|
|
[[ "$_t" == "true" ]] && { public="true"; break; }
|
|
done
|
|
|
|
# Primary/canonical host for this app: its first recommended Traefik port,
|
|
# else its first Traefik port. Feeds the legacy single DOMAINSUBNAME_DATA
|
|
# (app env vars), the app URL, and trusted-domains. Mirrors the per-port
|
|
# host rule (@/root -> apex, set -> sub.domain, empty -> app-name).
|
|
host_setup="${app_name}.${domain_full}"
|
|
local _i _primary=-1
|
|
for ((_i = 0; _i < ${#port_service_names[@]}; _i++)); do
|
|
[[ "${port_traefik_managed[$_i]}" == "true" ]] || continue
|
|
if [[ "${port_recommendeds[$_i]}" == "true" ]]; then _primary=$_i; break; fi
|
|
[[ $_primary -lt 0 ]] && _primary=$_i
|
|
done
|
|
if [[ $_primary -ge 0 ]]; then
|
|
local _sub="${port_subdomains[$_primary]}"
|
|
if [[ "$_sub" == "@" || "$_sub" == "root" ]]; then
|
|
host_setup="${domain_full}"
|
|
elif [[ -n "$_sub" ]]; then
|
|
host_setup="${_sub}.${domain_full}"
|
|
fi
|
|
fi
|
|
# Every branch above suffixes $domain_full, so with no domain configured they
|
|
# all yield a bare trailing dot ("bookstack."). That string is not a host, and
|
|
# it reaches DOMAINSUBNAME_TAG and the trusted-domains list regardless of the
|
|
# Traefik flag — so blank it rather than let a non-resolving name ship. Both
|
|
# consumers already treat empty as "no canonical host".
|
|
[[ -z "$domain_full" ]] && host_setup=""
|
|
}
|