diff --git a/fluxer_docs/src/content/docs/operator/configuration.mdx b/fluxer_docs/src/content/docs/operator/configuration.mdx index e7662cec0..9914b711d 100644 --- a/fluxer_docs/src/content/docs/operator/configuration.mdx +++ b/fluxer_docs/src/content/docs/operator/configuration.mdx @@ -72,6 +72,8 @@ Outside Compose these fall back to an empty base domain, `http`, and port `8088` `FLUXER_DOMAIN` also feeds the default passkey relying party identifier, the default VAPID contact address, and the edge listener address. +`FLUXER_PUBLIC_ORIGIN` states the same public address as one string, and [The public origin](#the-public-origin) has it. + #### `FLUXER_INTERNAL_SCHEME` Default `http`. The scheme for internal service URLs. Must be `http` or `https`, and anything else fails startup. Nothing outside the config loader reads it. @@ -167,11 +169,15 @@ Nothing in the stack reads `X-Forwarded-Proto` or `X-Forwarded-Host`. Every abso ## The public origin -`FLUXER_PUBLIC_ORIGIN` is the public origin browsers use, with no trailing slash. Only `docker-compose.yml` reads it, and it substitutes the value into fourteen values that need a full origin, including `FLUXER_MEDIA_ENDPOINT`, `FLUXER_MARKETING_ENDPOINT`, `FLUXER_ADMIN_ENDPOINT`, `FLUXER_ADMIN_OAUTH_REDIRECT_URI`, `FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT`, `PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT`, and the Gateway's media and static endpoints. +`FLUXER_PUBLIC_ORIGIN` states the public address as one string, with no trailing slash: the scheme, the host, and the port when that port is not the default for the scheme. `FLUXER_PUBLIC_SCHEME`, `FLUXER_DOMAIN` and `FLUXER_PUBLIC_PORT` state the same address between them, so the two spellings have to agree. -It ships commented out. When it is unset, Compose falls back to `FLUXER_PUBLIC_SCHEME` and `FLUXER_DOMAIN` with no port, which is correct for the usual https-on-443 case. +`docker-compose.yml` substitutes it into every name that needs a full origin, among them `FLUXER_APP_ENDPOINT`, `FLUXER_ADMIN_ENDPOINT`, `FLUXER_ADMIN_OAUTH_REDIRECT_URI`, `FLUXER_MEDIA_ENDPOINT`, `FLUXER_MARKETING_ENDPOINT`, `FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT`, `FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT`, `FLUXER_STATIC_CDN_ENDPOINT`, `FLUXER_LIVEKIT_URL`, `FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS`, `PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT`, and the Gateway's media and static endpoints. `grep FLUXER_PUBLIC_ORIGIN docker-compose.yml` is the whole list. Compose also puts the name itself into the shared `x-fluxer-env` block, and a service that reads it takes its base domain, scheme and port from it. -Serving the instance on any other port means setting `FLUXER_PUBLIC_ORIGIN` by hand, with that port in it, because the fallback has no port and the media and admin links are built from it. +It ships commented out. When it is unset, Compose builds those names from `FLUXER_PUBLIC_SCHEME` and `FLUXER_DOMAIN`, and each service puts `FLUXER_PUBLIC_PORT` back into the endpoints it derives. + +A non-default port therefore needs nothing here. `FLUXER_PUBLIC_PORT` is where the port of the public address goes, and the rest of the stack follows it. The comment block under the first three lines of `.env.example` says what each layout needs. + +Set `FLUXER_PUBLIC_ORIGIN` only to write the address out in one place, and then repeat the port `FLUXER_PUBLIC_PORT` names and the host `FLUXER_DOMAIN` names inside it. An `.env` whose two spellings disagree names two addresses. Half the instance answers on one and half on the other, and the web app sends no `Authorization` header to an API that is not on its own origin. ## Endpoint overrides @@ -265,7 +271,7 @@ Falls back to `FLUXER_STATIC_CDN_ENDPOINT`. The static origin used by `unfurl`. The `edge` container is the only HTTP entry point. Both layouts below work with the edge left alone. -Bundled TLS is the default. `docker compose up -d` binds `80/tcp`, `443/tcp`, and `443/udp` and obtains its own certificate for `FLUXER_DOMAIN`. Point DNS at the host and set nothing else. +Bundled TLS is the default. `docker compose up -d` binds `80/tcp`, `443/tcp`, and `443/udp` and obtains its own certificate for `FLUXER_DOMAIN`. Point DNS at the host and set nothing else. The two `443` publishes follow `FLUXER_PUBLIC_PORT`, so a non-default public port moves them with it. Put your own reverse proxy in front by adding `docker-compose.proxy.yml`, a second Compose file that overrides parts of the first. Load it with `-f` twice, or set `COMPOSE_FILE=docker-compose.yml:docker-compose.proxy.yml` in `.env` once. The edge then serves plain HTTP on one port, and the proxy in front terminates TLS. [Behind your own reverse proxy](/operator/reverse-proxy/) owns the switch, the requirements, and the per-proxy configuration. @@ -273,7 +279,7 @@ All are optional. #### `FLUXER_EDGE_SITE_ADDRESS` -Default `FLUXER_DOMAIN`. What the edge listens on. Honoured in the bundled layout only, because `docker-compose.proxy.yml` sets the literal `:8080` and discards any `.env` value. Several hostnames or a non-default TLS port therefore needs the bundled layout. It is independent of `FLUXER_PUBLIC_SCHEME` and `FLUXER_PUBLIC_PORT`, so keep the listener and the advertised origin in step by hand. +Defaults to the address Compose builds from `FLUXER_PUBLIC_SCHEME`, `FLUXER_DOMAIN` and `FLUXER_PUBLIC_PORT`. What the edge listens on, and the default keeps the listener on the address the instance advertises. Honoured in the bundled layout only, because `docker-compose.proxy.yml` sets the literal `:8080` and `tunnel.compose.yml` the literal `:80`, and either discards any `.env` value. Several hostnames therefore need the bundled layout. Write the scheme into any value you set here, because a bare hostname means automatic HTTPS on `443` whatever `FLUXER_PUBLIC_SCHEME` says. #### `FLUXER_EDGE_TRUSTED_PROXIES` @@ -285,11 +291,11 @@ Default `127.0.0.1:8080`. Where the plain-HTTP port binds. Read only under the o #### `FLUXER_HTTP_PORT` -Default `80`. The host side of the edge's HTTP publish. The container still listens on `80` inside. It takes an optional bind address in front of the port, so `127.0.0.1:80` keeps the publish off every public interface. Not read under the overlay, which publishes `FLUXER_EDGE_BIND` instead. +Default `80`. The host side of the edge's HTTP publish, which serves the redirect to HTTPS and the ACME HTTP challenge under an `https` scheme and nothing at all under an `http` one. The container still listens on `80` inside. It takes an optional bind address in front of the port, so `127.0.0.1:80` keeps the publish off every public interface. Do not set it to the port `FLUXER_PUBLIC_PORT` already names, because that publishes one host port twice and the edge refuses to start. Not read under the overlay, which publishes `FLUXER_EDGE_BIND` instead. #### `FLUXER_HTTPS_PORT` -Default `443`. The host side of the edge's HTTPS publish. It moves the TCP and the UDP publish together, because HTTP/3 needs both on the same port. Same optional bind address, and it is not read under the overlay either. +Defaults to `FLUXER_PUBLIC_PORT`. The host side of the edge's HTTPS publish, and the only thing it moves is that host side. Set it when the host already has something on the port the instance advertises. It moves the TCP and the UDP publish together, because HTTP/3 needs both on the same port. Same optional bind address, and it is not read under the overlay either. #### `COMPOSE_FILE` @@ -649,7 +655,7 @@ Default empty. The LiveKit secret. Compose fills it from `LIVEKIT_API_SECRET`. #### `FLUXER_LIVEKIT_URL` -Default empty. The client-facing LiveKit URL. When empty the API derives it from the public API origin as `wss://host/livekit`, or `ws://` under `http`, and keeps a non-default port. Set it only when LiveKit is served from another host. Compose forwards the `.env` value, empty by default. +Default empty. The client-facing LiveKit URL. Set it only when LiveKit is served from another host. `docker-compose.yml` never passes it empty: it builds the value from `FLUXER_PUBLIC_ORIGIN`, or from `FLUXER_PUBLIC_SCHEME`, `FLUXER_DOMAIN` and `FLUXER_PUBLIC_PORT` when the origin is unset, followed by `/livekit`. An empty value outside Compose has the API derive `wss://host/livekit` from the public API origin, or `ws://` under `http`, with a non-default port kept. #### `FLUXER_LIVEKIT_USE_EXTERNAL_IP` @@ -729,6 +735,8 @@ Default `60000`. Grace before culling a LiveKit-only participant. Milliseconds. Email is off by default, and two conditions turn it on. The switch must be on, and the provider must be `smtp` with a complete SMTP configuration, meaning `FLUXER_EMAIL_FROM_EMAIL`, `FLUXER_EMAIL_SMTP_HOST`, `FLUXER_EMAIL_SMTP_PORT`, `FLUXER_EMAIL_SMTP_USERNAME`, and `FLUXER_EMAIL_SMTP_PASSWORD` are all non-empty. All are optional. +The same two conditions turn on a DNS check at registration. The check runs when email is on by the rule above, so the dashboard switch and the SMTP values decide it along with the variable. The address domain has to publish an `MX` record, or an `A` or `AAAA` record as a fallback, and an address at a domain that publishes neither is answered `That email domain cannot receive mail.` however well formed it is. The first admin account is no exception, so an owner address at a `.lan`, `.internal` or `home.arpa` name needs email left off. + #### `FLUXER_EMAIL_ENABLED` `.env.example` `false`. The delivery switch. The admin dashboard value wins over this. @@ -1600,10 +1608,6 @@ Docker Compose or the edge container consumes most of the names below, and each Interpolated into `FLUXER_BASE_DOMAIN` and into the derived URL strings. -#### `FLUXER_PUBLIC_ORIGIN` - -Interpolated into fourteen values that need a full origin. - #### `COMPOSE_FILE` Read by Docker Compose to select the proxy overlay. diff --git a/fluxer_docs/src/content/docs/operator/get-started.mdx b/fluxer_docs/src/content/docs/operator/get-started.mdx index b30331ebc..64aad8f61 100644 --- a/fluxer_docs/src/content/docs/operator/get-started.mdx +++ b/fluxer_docs/src/content/docs/operator/get-started.mdx @@ -79,6 +79,8 @@ Open these ports on the host: A machine behind a home router needs 80, 443 and both LiveKit ports forwarded to it. The certificate is issued from the public internet, and voice media arrives on the LiveKit ports directly. Running your own reverse proxy changes only the first two, which the proxy holds while the stack binds a plain HTTP port on the loopback. 7881 and 7882 have to reach the host either way, because voice media never goes through a proxy. +On another port, open that one in place of 443 and read [Serving on another port](#serving-on-another-port). + On a cloud VM, open them in the provider's firewall or security group. On a Linux host running firewalld: ```bash @@ -136,6 +138,25 @@ The run prints one line per phase and ends with the URL to open. `~/fluxer` then holds `docker-compose.yml`, `docker-compose.proxy.yml`, `tunnel.compose.yml`, `Caddyfile` and `.env.example`, plus a `.env` readable only by you. Every value that ships as `CHANGE_ME` in `.env.example` is a fresh random value. Every command from here on runs in that directory. +### Serving on another port + +The installer writes an `.env` for `https` on `443` and has no flag for another port. Two lines change it, the port of the public address and the publish that answers on it: + +```ini +FLUXER_PUBLIC_PORT=8443 +FLUXER_HTTPS_PORT=8443 +``` + +```bash +docker compose up -d +``` + +`FLUXER_PUBLIC_PORT` is where the port of the public address goes, and every endpoint the services advertise follows it. It does not move what the host publishes, so `FLUXER_HTTPS_PORT` has to name the same port. Move `FLUXER_PUBLIC_SCHEME` with them when the new port serves plain HTTP, and set `FLUXER_HTTP_PORT` in place of `FLUXER_HTTPS_PORT`. `.env.example` ships beside `.env` and writes out both complete recipes. + +Host 80 stays published on the `https` recipe and still answers the ACME challenge. Let's Encrypt only ever connects to the public 80 or 443, so the certificate is issued if a router in front forwards public 80 to this host. Serve your own certificate from the `Caddyfile` when it cannot. + +Leave `FLUXER_PUBLIC_ORIGIN` commented out. It states that same address as one string, so an `.env` that sets it without the port `FLUXER_PUBLIC_PORT` names holds two addresses. Part of the instance then answers on one and part on the other, the web app sends no `Authorization` header to an API that is not on its own origin, the API answers 401, and the setup wizard reads that 401 as an expired session and returns to the account form. [The public origin](/operator/configuration/#the-public-origin) has the rest. + ### Installer flags | Flag | Meaning | @@ -173,7 +194,7 @@ docker compose logs -f api Every service reads `running` or `healthy` except `seaweedfs-init`, which creates the upload buckets and then reads `exited (0)`. -Then the public probes. Replace the hostname with your own: +Then the public probes. Replace the origin with your own, port included when the instance answers on one: ```bash base=https://chat.example.com @@ -203,6 +224,8 @@ The wizard runs in two halves. The first is a welcome, a theme choice, an admin Create the owner account with an email address at a domain you control. The first registration that supplies one receives the wildcard admin ACL, which is full access to the admin dashboard, unless registration is set to hold new accounts for approval. Finishing the wizard grants that same ACL to whichever account completes it, when that account holds none. +Once email is on, the address goes through a DNS check before the account exists. Email counts as on when the switch is set and the provider is `smtp` with a complete SMTP configuration, so setting `FLUXER_EMAIL_ENABLED=true` on its own does not turn the check on. Its domain has to publish an `MX` record, or an `A` or `AAAA` record as a fallback. A name that resolves on your own network alone, such as a `.lan`, `.internal` or `home.arpa` name, publishes neither, and the account form answers `That email domain cannot receive mail.` whatever the address looks like. Use a domain with public records, or leave email off. + `.env.example` ships `FLUXER_EMAIL_ENABLED=false`, which marks every address verified at creation and sends no mail at all. A forgotten owner password therefore has no email reset. Record it, and register a passkey or a second admin account before you open registration. Then sign in to the admin dashboard at `https://chat.example.com/admin` with the account holding the wildcard ACL. The **Instance Config** page has everything the wizard asked, plus registration mode, approvals and integration keys. **Limit Config** holds the instance limits published to clients. **Voice Regions** and **Voice Servers** come seeded, so voice needs no setup there. diff --git a/fluxer_docs/src/content/docs/operator/reverse-proxy.mdx b/fluxer_docs/src/content/docs/operator/reverse-proxy.mdx index eed8e2cc2..beb989405 100644 --- a/fluxer_docs/src/content/docs/operator/reverse-proxy.mdx +++ b/fluxer_docs/src/content/docs/operator/reverse-proxy.mdx @@ -107,12 +107,16 @@ FLUXER_PUBLIC_PORT=443 They stay `https` on `443` even though the instance itself speaks plain HTTP on `8080`. Clients read every base URL from the discovery document the API builds out of these values. -Serving on a port other than `443` also needs `FLUXER_PUBLIC_ORIGIN`. Several endpoints are built from the scheme and the domain alone, and the port is lost without it: +Serving on a port other than `443` means changing one of those three: ```ini -FLUXER_PUBLIC_ORIGIN=https://chat.example.com:8443 +FLUXER_PUBLIC_PORT=8443 ``` +Your proxy holds the public port, and the overlay pins the edge to plain HTTP on `8080` whatever that port is, so nothing else in `.env` moves with it. + +Leave `FLUXER_PUBLIC_ORIGIN` commented out. It states the same address as one string, and an `.env` that sets it without the port `FLUXER_PUBLIC_PORT` names puts part of the instance on `chat.example.com:8443` and part on `chat.example.com`, where the web app sends no `Authorization` header across the gap. + ## nginx The `map` block makes `Connection` follow `Upgrade`. The snippet raises the body limit and the read timeout above nginx's own 1 MB and 60 seconds. diff --git a/fluxer_docs/src/installer/install.ps1 b/fluxer_docs/src/installer/install.ps1 index 6cd976c50..4cc0ecbdf 100644 --- a/fluxer_docs/src/installer/install.ps1 +++ b/fluxer_docs/src/installer/install.ps1 @@ -775,15 +775,64 @@ function Wait-FluxerStack([string]$Lead) { Stop-Fluxer "The stack did not report healthy within $FluxerReadyTimeoutSeconds seconds. Read docker compose ps and docker compose logs." $FluxerExitUnhealthy } +# The origin browsers use. .env states it outright when FLUXER_PUBLIC_ORIGIN is set, and Compose +# otherwise builds the same string from the scheme, the domain and the port, dropping a port that +# is the default for its scheme. +# +# Compose expands a ${...} reference inside an .env value and this script does not, so a +# FLUXER_PUBLIC_ORIGIN written that way is skipped rather than printed back with the braces still +# in it. The three names below say the same address, so the derived string is the right one to +# fall back to. +# +# By hand: +# Select-String -Path .env -Pattern '^FLUXER_(PUBLIC_ORIGIN|PUBLIC_SCHEME|DOMAIN|PUBLIC_PORT)=' +function Get-FluxerPublicOrigin([string]$EnvPath) { + $origin = Get-FluxerEnvValue $EnvPath 'FLUXER_PUBLIC_ORIGIN' + if ($origin.Contains('${')) { + Write-FluxerProblem 'FLUXER_PUBLIC_ORIGIN in .env holds a ${...} reference. This script does not expand those, so the address below comes from FLUXER_PUBLIC_SCHEME, FLUXER_DOMAIN and FLUXER_PUBLIC_PORT instead.' + $origin = '' + } + if ($origin.Length -gt 0) { + return $origin.TrimEnd('/') + } + $originHost = Get-FluxerEnvValue $EnvPath 'FLUXER_DOMAIN' + if ($originHost.Length -eq 0) { + return '' + } + $scheme = Get-FluxerEnvValue $EnvPath 'FLUXER_PUBLIC_SCHEME' + if ($scheme.Length -eq 0) { + $scheme = 'https' + } + $port = Get-FluxerEnvValue $EnvPath 'FLUXER_PUBLIC_PORT' + $suffix = '' + if ($port.Length -gt 0 -and -not (($scheme -eq 'http' -and $port -eq '80') -or ($scheme -eq 'https' -and $port -eq '443'))) { + $suffix = ":$port" + } + return "${scheme}://${originHost}${suffix}" +} + # The public probe is informational. A host behind hairpin NAT cannot always reach its own -# hostname, and a false failure there would be worse than no probe. -function Test-FluxerPublicHealth([string]$DomainValue) { - $url = "https://$DomainValue$FluxerHealthPath" +# hostname, and a false failure there would be worse than no probe. It asks the origin .env +# advertises, so an instance on a non-default port is probed where it actually answers. +function Test-FluxerPublicHealth([string]$Origin) { + $url = "$Origin$FluxerHealthPath" + $authority = $Origin + $separator = $Origin.IndexOf('://') + if ($separator -ge 0) { + $authority = $Origin.Substring($separator + 3) + } + $probeHost = $authority + $probePort = if ($Origin.StartsWith('http://')) { '80' } else { '443' } + $colon = $authority.IndexOf(':') + if ($colon -ge 0) { + $probeHost = $authority.Substring(0, $colon) + $probePort = $authority.Substring($colon + 1) + } try { $response = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 20 Write-FluxerLine "$url returned $([int]$response.StatusCode)." } catch { - Write-FluxerLine "$url did not answer with 200. Confirm the DNS record for $DomainValue and that ports 80 and 443 reach this host." + Write-FluxerLine "$url did not answer with 200. Confirm the DNS record for $probeHost and that inbound port $probePort reaches this host." } } @@ -1558,9 +1607,9 @@ function Invoke-FluxerUpgrade([string]$TargetDir, [string]$EnvPath, [string]$Bac } Restart-FluxerMounts $changedMounts $TargetDir Wait-FluxerStack 'Waiting for every service to report ready.' - $domainValue = Get-FluxerEnvValue $EnvPath 'FLUXER_DOMAIN' - if ($domainValue.Length -gt 0) { - Test-FluxerPublicHealth $domainValue + $originValue = Get-FluxerPublicOrigin $EnvPath + if ($originValue.Length -gt 0) { + Test-FluxerPublicHealth $originValue } Write-FluxerLine "Instance upgraded in $TargetDir." Write-FluxerLine "The record of what it ran before is in $record." @@ -1647,9 +1696,9 @@ function Invoke-FluxerRollback([string]$TargetDir, [string]$EnvPath, [string]$Ba # would save one restart and cost the reader a reason. Restart-FluxerMounts $FluxerMountedFiles $TargetDir Wait-FluxerStack 'Waiting for every service to report ready.' - $domainValue = Get-FluxerEnvValue $EnvPath 'FLUXER_DOMAIN' - if ($domainValue.Length -gt 0) { - Test-FluxerPublicHealth $domainValue + $originValue = Get-FluxerPublicOrigin $EnvPath + if ($originValue.Length -gt 0) { + Test-FluxerPublicHealth $originValue } Write-FluxerLine "Instance rolled back in $TargetDir." $dumpPath = Join-Path $record $FluxerDumpFile @@ -1938,8 +1987,12 @@ function Invoke-FluxerInstall { Stop-Fluxer 'docker compose up -d failed.' $FluxerExitUnhealthy } Wait-FluxerStack 'Waiting for the stack to report healthy. The first start pulls images and takes several minutes.' - Test-FluxerPublicHealth $domainValue - Write-FluxerLine "Instance ready at https://$domainValue" + $readyOrigin = Get-FluxerPublicOrigin $envPath + if ($readyOrigin.Length -eq 0) { + $readyOrigin = "https://$domainValue" + } + Test-FluxerPublicHealth $readyOrigin + Write-FluxerLine "Instance ready at $readyOrigin" Write-FluxerLine 'Open it and create the first admin account. Finish the setup wizard in the same sitting.' Write-FluxerLine "Secrets live in $envPath. Back that file up." } finally { diff --git a/fluxer_docs/src/installer/install.sh b/fluxer_docs/src/installer/install.sh index 1532ffaf1..b1bb4602a 100644 --- a/fluxer_docs/src/installer/install.sh +++ b/fluxer_docs/src/installer/install.sh @@ -936,22 +936,77 @@ fluxer_wait_ready() { return 1 } +fluxer_env_value() { + sed -n "s/^$1=\\(.*\\)\$/\\1/p" "$opt_dir/.env" | head -n 1 +} + +# The origin browsers use. .env states it outright when FLUXER_PUBLIC_ORIGIN is +# set, and Compose otherwise builds the same string from the scheme, the domain +# and the port, dropping a port that is the default for its scheme. +# +# Compose expands a ${...} reference inside an .env value and this script does +# not, so a FLUXER_PUBLIC_ORIGIN written that way is skipped rather than printed +# back with the braces still in it. The three names below say the same address, +# so the derived string is the right one to fall back to. +# +# By hand: +# grep -E '^FLUXER_(PUBLIC_ORIGIN|PUBLIC_SCHEME|DOMAIN|PUBLIC_PORT)=' .env +fluxer_public_origin() { + fluxer_origin=$(fluxer_env_value FLUXER_PUBLIC_ORIGIN) + case $fluxer_origin in + *'${'*) + printf '%s\n' 'FLUXER_PUBLIC_ORIGIN in .env holds a ${...} reference. This script does not expand those, so the address below comes from FLUXER_PUBLIC_SCHEME, FLUXER_DOMAIN and FLUXER_PUBLIC_PORT instead.' >&2 + fluxer_origin='' + ;; + esac + if [ -n "$fluxer_origin" ]; then + printf '%s' "${fluxer_origin%/}" + return 0 + fi + fluxer_origin_host=$(fluxer_env_value FLUXER_DOMAIN) + if [ -z "$fluxer_origin_host" ]; then + return 0 + fi + fluxer_origin_scheme=$(fluxer_env_value FLUXER_PUBLIC_SCHEME) + if [ -z "$fluxer_origin_scheme" ]; then + fluxer_origin_scheme='https' + fi + fluxer_origin_port=$(fluxer_env_value FLUXER_PUBLIC_PORT) + if [ -z "$fluxer_origin_port" ]; then + fluxer_origin_suffix='' + else + case $fluxer_origin_scheme:$fluxer_origin_port in + http:80|https:443) fluxer_origin_suffix='' ;; + *) fluxer_origin_suffix=":$fluxer_origin_port" ;; + esac + fi + printf '%s://%s%s' "$fluxer_origin_scheme" "$fluxer_origin_host" "$fluxer_origin_suffix" +} + # The public probe is informational. A host behind hairpin NAT cannot always # reach its own hostname, and a false failure there would be worse than no probe. +# It asks the origin .env advertises, so an instance on a non-default port is +# probed where it actually answers. fluxer_probe() { - fluxer_probe_code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 15 "https://$1/_health" 2>/dev/null || true) + fluxer_probe_origin=$1 + fluxer_probe_authority=${fluxer_probe_origin#*://} + fluxer_probe_host=${fluxer_probe_authority%%:*} + case $fluxer_probe_origin in + http://*) fluxer_probe_port='80' ;; + *) fluxer_probe_port='443' ;; + esac + case $fluxer_probe_authority in + *:*) fluxer_probe_port=${fluxer_probe_authority##*:} ;; + esac + fluxer_probe_code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 15 "$fluxer_probe_origin/_health" 2>/dev/null || true) if [ -z "$fluxer_probe_code" ]; then fluxer_probe_code='000' fi if [ "$fluxer_probe_code" = '200' ]; then - fluxer_say "https://$1/_health answers 200." + fluxer_say "$fluxer_probe_origin/_health answers 200." return 0 fi - fluxer_say "https://$1/_health answers $fluxer_probe_code from this host. Check the DNS record for $1 and inbound ports 80 and 443." -} - -fluxer_env_value() { - sed -n "s/^$1=\\(.*\\)\$/\\1/p" "$opt_dir/.env" | head -n 1 + fluxer_say "$fluxer_probe_origin/_health answers $fluxer_probe_code from this host. Check the DNS record for $fluxer_probe_host and inbound port $fluxer_probe_port." } # The keys a refreshed stack requires that an .env written by an older installer @@ -1510,9 +1565,9 @@ fluxer_verify_stack() { if ! fluxer_wait_ready; then fluxer_fail 6 "The stack is not ready after $FLUXER_READY_TIMEOUT seconds. Read $fluxer_engine compose logs in $opt_dir." fi - fluxer_domain_value=$(fluxer_env_value FLUXER_DOMAIN) - if [ -n "$fluxer_domain_value" ]; then - fluxer_probe "$fluxer_domain_value" + fluxer_origin_value=$(fluxer_public_origin) + if [ -n "$fluxer_origin_value" ]; then + fluxer_probe "$fluxer_origin_value" fi } @@ -1862,6 +1917,7 @@ if ! fluxer_generate_vapid; then fi fluxer_write_env fluxer_say "Wrote $opt_dir/.env, readable by you alone." +fluxer_say 'That .env serves https on 443, which is the only layout this script writes. The .env.example beside it says what to change for any other one.' if [ "$opt_no_start" -eq 1 ]; then fluxer_say "Start the instance with $fluxer_engine compose up -d in $opt_dir." @@ -1878,8 +1934,12 @@ fluxer_say 'Waiting for every service to report ready. This takes several minute if ! fluxer_wait_ready; then fluxer_fail 6 "The stack is not ready after $FLUXER_READY_TIMEOUT seconds. Read $fluxer_engine compose logs in $opt_dir." fi -fluxer_probe "$opt_domain" +fluxer_ready_origin=$(fluxer_public_origin) +if [ -z "$fluxer_ready_origin" ]; then + fluxer_ready_origin="https://$opt_domain" +fi +fluxer_probe "$fluxer_ready_origin" -fluxer_say "Instance ready at https://$opt_domain" +fluxer_say "Instance ready at $fluxer_ready_origin" fluxer_say 'Open it and create the first admin account. Finish the setup wizard in the same sitting.' fluxer_say "Secrets live in $opt_dir/.env. Back that file up."