diff --git a/containers/matrix/docker-compose.yml b/containers/matrix/docker-compose.yml index 2da64a5..688d840 100644 --- a/containers/matrix/docker-compose.yml +++ b/containers/matrix/docker-compose.yml @@ -24,6 +24,15 @@ services: environment: - TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml + # These are not redundant with `user:` above. The image's entrypoint + # checks whether it is root and, if so, su-execs down to UID:GID — + # which default to 991:991. So a container started as 0:0 still ends + # up running as 991, and 991 maps to a host sub-UID that does not own + # the bind mount: Synapse then dies reading its own chmod-600 + # homeserver.yaml. Pinning them to the same identity as USER_TAG + # stops the entrypoint dropping to a user that cannot read anything. + - UID=MATRIX_RUN_UID_DATA #LIBREPORTAL|MATRIX_RUN_UID_TAG|MATRIX_RUN_UID_DATA + - GID=MATRIX_RUN_GID_DATA #LIBREPORTAL|MATRIX_RUN_GID_TAG|MATRIX_RUN_GID_DATA depends_on: - matrix-postgres labels: diff --git a/containers/matrix/matrix.config b/containers/matrix/matrix.config index db20afc..2880b82 100644 --- a/containers/matrix/matrix.config +++ b/containers/matrix/matrix.config @@ -13,14 +13,15 @@ # ENABLE_REGISTRATION = if true, anyone who can reach the homeserver can create an account on it # ADMIN_USERNAME = localpart of the first admin account created at install (the full ID becomes @:) # ADMIN_PASSWORD = password for that first admin account +# SERVER_NAME = permanent identity of this homeserver — the half of a user ID after the colon # MONITORING = if true, export this app's metrics to Prometheus + Grafana (needs both apps installed) # CFG_MATRIX_APP_NAME=matrix -# A domain and Traefik are hard requirements, not conveniences. Synapse bakes -# server_name into every event and user ID it has ever signed, and it CANNOT be -# changed afterwards without throwing the database away — so the homeserver must -# know its real public name at install time, over real TLS. -CFG_MATRIX_REQUIRES="domain,traefik" +# No prerequisites. Synapse runs perfectly well on a plain HTTP listener reached +# by IP over the LAN or a WireGuard tunnel — that is the same setup as sitting +# behind a reverse proxy, minus the proxy. Only *federation* needs a real domain +# with public DNS and TLS, and federation is optional. +CFG_MATRIX_REQUIRES="" CFG_MATRIX_BACKUP=true CFG_MATRIX_BACKUP_STRATEGY=auto CFG_MATRIX_UPDATE_TYPE=auto @@ -37,6 +38,21 @@ CFG_MATRIX_HEADSCALE=false CFG_MATRIX_ENABLE_REGISTRATION=false CFG_MATRIX_ADMIN_USERNAME=admin CFG_MATRIX_ADMIN_PASSWORD_1=RANDOMIZEDPASSWORD1 +# Set this if you ever intend to federate — it is the single most consequential +# value here and the ONLY one that cannot be changed later. It is signed into +# every event this server sends and forms the half of a user ID after the colon +# (@alice:example.com), so changing it orphans the database. +# +# Crucially it is independent of how clients reach the server: you can set it to +# a domain you own that has no DNS pointing anywhere yet, run today on +# http://:, and switch federation on later by adding DNS, Traefik +# and a certificate — with no rebuild and no lost history. +# +# Left empty it is derived: . when a domain is configured, +# otherwise this machine's LAN address. The LAN address works fine for local and +# WireGuard use but can never federate, and every user ID breaks if the IP +# changes — so if in doubt, put a domain here even if you do not use it yet. +CFG_MATRIX_SERVER_NAME= CFG_MATRIX_MONITORING=false # Postgres password for the `synapse` role, fed to the compose via # #LIBREPORTAL|MATRIX_DB_PASSWORD_1_TAG| and written into homeserver.yaml by the @@ -62,7 +78,6 @@ CFG_MATRIX_DESCRIPTION="Federated Chat" CFG_MATRIX_LONG_DESCRIPTION="The open federated chat protocol — rooms, end-to-end encryption and bridges to Discord and Slack. Installs Synapse plus the Element web client" CFG_MATRIX_URL="https://github.com/element-hq/synapse" CFG_MATRIX_ACTIONS="configure|install|restart|shutdown|uninstall" -CFG_MATRIX_REQUIRES_SERVICE=traefik # # ============================================================================= # NETWORK CONFIGURATION diff --git a/containers/matrix/resources/element-config.json b/containers/matrix/resources/element-config.json index efe129b..68715ba 100644 --- a/containers/matrix/resources/element-config.json +++ b/containers/matrix/resources/element-config.json @@ -1,8 +1,8 @@ { "default_server_config": { "m.homeserver": { - "base_url": "https://ELEMENT_HOMESERVER_PLACEHOLDER", - "server_name": "ELEMENT_HOMESERVER_PLACEHOLDER" + "base_url": "ELEMENT_BASEURL_PLACEHOLDER", + "server_name": "ELEMENT_SERVERNAME_PLACEHOLDER" } }, "brand": "Element", @@ -13,7 +13,7 @@ "default_country_code": "GB", "show_labs_settings": true, "room_directory": { - "servers": ["ELEMENT_HOMESERVER_PLACEHOLDER", "matrix.org"] + "servers": ["ELEMENT_SERVERNAME_PLACEHOLDER"] }, "setting_defaults": { "breadcrumbs": true diff --git a/containers/matrix/resources/homeserver.yaml b/containers/matrix/resources/homeserver.yaml index b707752..63d7022 100644 --- a/containers/matrix/resources/homeserver.yaml +++ b/containers/matrix/resources/homeserver.yaml @@ -10,15 +10,20 @@ # sent and forms the second half of every user ID (@alice:). # Changing it later does not migrate anything — it orphans the whole database. server_name: "SYNAPSE_SERVER_NAME_PLACEHOLDER" -public_baseurl: "https://SYNAPSE_SERVER_NAME_PLACEHOLDER/" + +# Where clients actually reach this server. Independent of server_name: on a +# LAN or WireGuard-only install this is http://: while server_name +# stays whatever permanent identity you chose. Adding TLS later means changing +# only this line. +public_baseurl: "SYNAPSE_PUBLIC_BASEURL_PLACEHOLDER" pid_file: /data/homeserver.pid # Serve /.well-known/matrix/server ourselves, advertising port 443. Federation # otherwise defaults to port 8448 on server_name, which Traefik is not -# listening on. Because server_name is the same host Traefik already terminates -# TLS for, this is all the delegation that is needed — nothing has to be -# published at the apex domain. -serve_server_wellknown: true +# listening on. Only switched on for a real HTTPS deployment — advertising +# :443 from an install that is only reachable at http://: +# would just invite other servers to connect somewhere that cannot answer. +serve_server_wellknown: SYNAPSE_SERVE_WELLKNOWN_PLACEHOLDER listeners: # Port 8008 is the container-internal port and is deliberately hardcoded: it diff --git a/containers/matrix/scripts/matrix_compose_tags.sh b/containers/matrix/scripts/matrix_compose_tags.sh new file mode 100644 index 0000000..f21db0c --- /dev/null +++ b/containers/matrix/scripts/matrix_compose_tags.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# App-specific compose tags for Matrix. +# +# The Synapse image drops privileges from root to its UID/GID environment +# variables (991:991 by default) before starting. That has to land on the same +# identity USER_TAG resolves to, or the process ends up as a host sub-UID that +# owns none of the bind-mounted data and cannot even read homeserver.yaml. +# +# The rule is deliberately the same one docker_config_setup_data.sh applies to +# USER_TAG: container-root under rootless (which IS the install user on the +# host), the real uid:gid under rooted. Kept here rather than parsing USER_DATA +# back out of the compose because the ordering between the two substitutions is +# not guaranteed. +appSetupComposeTags_matrix() { + local full_file_path="$1" + local run_uid=0 run_gid=0 + + if [[ "$CFG_DOCKER_INSTALL_TYPE" != "rootless" ]]; then + local container_user="${docker_install_user:-$sudo_user_name}" + run_uid=$(id -u "$container_user" 2>/dev/null) || run_uid=0 + run_gid=$(id -g "$container_user" 2>/dev/null) || run_gid=0 + fi + + tagsManagerUpdateUniversalTag "$full_file_path" "MATRIX_RUN_UID_TAG" "$run_uid" + tagsManagerUpdateUniversalTag "$full_file_path" "MATRIX_RUN_GID_TAG" "$run_gid" +} diff --git a/containers/matrix/scripts/matrix_install_hooks.sh b/containers/matrix/scripts/matrix_install_hooks.sh index 2196f0b..c441d0c 100644 --- a/containers/matrix/scripts/matrix_install_hooks.sh +++ b/containers/matrix/scripts/matrix_install_hooks.sh @@ -15,6 +15,14 @@ matrix_install_pre() matrix=n return 1 fi + # Not a prerequisite, just the one warning worth making loudly. Everything + # else about this install can be changed later; server_name cannot. + if [[ -z "$CFG_MATRIX_SERVER_NAME" && -z "$domain_full" ]]; then + isNotice "No domain configured — this homeserver will identify itself as '${public_ip_v4:-localhost}'." + isNotice "That works for LAN and WireGuard clients but can never federate, and every" + isNotice "user ID breaks if this machine's IP changes. To keep the door open, set" + isNotice "CFG_MATRIX_SERVER_NAME to a domain you own — it needs no DNS today." + fi } # The homeserver's public host — the value that becomes server_name. @@ -30,15 +38,57 @@ matrix_install_pre() # here and the Traefik rule generated later cannot drift apart. _matrixServerName() { - local sub="${port_subdomains[0]}" - [[ -z "$domain_full" ]] && return 1 - if [[ "$sub" == "@" || "$sub" == "root" ]]; then - echo "$domain_full" - elif [[ -n "$sub" ]]; then - echo "${sub}.${domain_full}" - else - echo "matrix.${domain_full}" + # An explicit choice always wins — this is the escape hatch that lets a + # LAN-only install still be given a federatable identity up front. + if [[ -n "$CFG_MATRIX_SERVER_NAME" ]]; then + echo "$CFG_MATRIX_SERVER_NAME" + return 0 fi + if [[ -n "$domain_full" ]]; then + local sub="${port_subdomains[0]}" + if [[ "$sub" == "@" || "$sub" == "root" ]]; then + echo "$domain_full" + elif [[ -n "$sub" ]]; then + echo "${sub}.${domain_full}" + else + echo "matrix.${domain_full}" + fi + return 0 + fi + # LAN / WireGuard only. Usable, never federatable. + echo "${public_ip_v4:-localhost}" +} + +# How clients reach the homeserver, as opposed to what it calls itself. +# +# Depends on the allocated external port, so it is only correct once ports have +# been assigned — i.e. from install_post_start onward, not install_post_compose. +_matrixPublicBaseUrl() +{ + local app_name="$1" + local compose="$containers_dir$app_name/docker-compose.yml" + + # Real HTTPS only when Traefik is actually installed AND a domain exists; + # otherwise the router in the compose is decorative. + if [[ -d "${containers_dir}traefik" && -n "$domain_full" ]]; then + local host + host=$(tagsManagerGetTagContent "$compose" "DOMAINSUBNAME_TAG_1") + if [[ -n "$host" && "$host" != DOMAINSUBNAME_DATA* ]]; then + echo "https://${host}/" + return 0 + fi + fi + + local ports external + ports=$(tagsManagerGetTagContent "$compose" "PORTS_TAG_1") + external="${ports%%:*}" + if [[ -n "$external" && "$external" != PORTS_DATA* ]]; then + echo "http://${public_ip_v4:-localhost}:${external}/" + return 0 + fi + + # Ports not settled yet — the post-start pass will correct this. + echo "http://${public_ip_v4:-localhost}/" } matrix_install_post_compose() @@ -55,8 +105,8 @@ matrix_install_post_compose() local server_name server_name=$(_matrixServerName) if [[ -z "$server_name" ]]; then - isError "No domain is configured — cannot determine the Matrix server_name." - isNotice "Set CFG_DOMAIN_${CFG_MATRIX_DOMAIN:-1} (General → Network), then reinstall." + isError "Could not determine a Matrix server_name and could not fall back to this host's address." + isNotice "Set CFG_MATRIX_SERVER_NAME in matrix.config, then reinstall." return 1 fi @@ -75,6 +125,37 @@ matrix_install_post_compose() result=$(createFolders "loud" "$docker_install_user" "$data_dir" "$app_dir/element") checkSuccess "Creating $app_name data folders" + # Best guess now; the post-start pass recomputes it once the port is known + # and rewrites + restarts if it turns out different. + local base_url serve_wellknown="false" + base_url=$(_matrixPublicBaseUrl "$app_name") + [[ "$base_url" == https://* ]] && serve_wellknown="true" + + # Element is a static bundle; config.json is the only thing that makes it + # point at this homeserver rather than matrix.org. + # + # Written before the signing key on purpose. config.json is bind-mounted as + # a FILE, and docker silently creates a directory at a missing mount source + # — so if an earlier step fails and returns, compose-up leaves a directory + # here that every later run then trips over. Doing it first means the file + # always exists; the rm handles a landmine left by an install that failed + # before this reordering. + [[ -d "$app_dir/element/config.json" ]] && runFileOp rm -rf "$app_dir/element/config.json" + result=$(copyResource "$app_name" "element-config.json" "element" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1) + checkSuccess "Copying Element configuration to $app_dir/element" + + result=$(runFileOp mv "$app_dir/element/element-config.json" "$app_dir/element/config.json") + checkSuccess "Renaming Element configuration to config.json" + + # base_url is where the browser sends requests; server_name is only the + # label. On a LAN install these genuinely differ — http://10.0.0.5:8008 vs + # whatever permanent name the server answers to. + runFileOp sed -i "s|ELEMENT_BASEURL_PLACEHOLDER|${base_url%/}|g" "$app_dir/element/config.json" + runFileOp sed -i "s|ELEMENT_SERVERNAME_PLACEHOLDER|$server_name|g" "$app_dir/element/config.json" + runFileOp chown -R "$docker_install_user":"$docker_install_user" "$app_dir/element" + checkSuccess "Pointing Element at ${base_url%/}" + + # Synapse signs every federated event with this key, and a peer that has # seen one key will reject events signed by a different one. So: generate it # exactly once, and never regenerate it over an existing install. @@ -88,7 +169,20 @@ matrix_install_post_compose() synapse_image=$(tagsManagerGetTagContent "$app_dir/docker-compose.yml" "MATRIX_VERSION_TAG") synapse_image="matrixdotorg/synapse:${synapse_image:-latest}" + # UID/GID are the image's own knobs for who it writes /data as; left at + # their default of 991 the generated key lands owned by a host sub-UID + # that the install user does not own, and the mv/rm below fail with + # EACCES on files we just created. Pin them to the same identity the + # compose runs the container as (USER_TAG): container-root under + # rootless, the real uid:gid under rooted. + local gen_uid=0 gen_gid=0 + if [[ "$CFG_DOCKER_INSTALL_TYPE" != "rootless" ]]; then + gen_uid=$(id -u "${docker_install_user:-$sudo_user_name}" 2>/dev/null || echo 0) + gen_gid=$(id -g "${docker_install_user:-$sudo_user_name}" 2>/dev/null || echo 0) + fi + result=$(runFileOp docker run --rm \ + -e UID="$gen_uid" -e GID="$gen_gid" \ -e SYNAPSE_SERVER_NAME="$server_name" \ -e SYNAPSE_REPORT_STATS=no \ -v "$data_dir":/data \ @@ -133,6 +227,8 @@ matrix_install_post_compose() local enable_registration="false" [[ "$CFG_MATRIX_ENABLE_REGISTRATION" == "true" ]] && enable_registration="true" + runFileOp sed -i "s|SYNAPSE_PUBLIC_BASEURL_PLACEHOLDER|$base_url|g" "$homeserver_file" + runFileOp sed -i "s|SYNAPSE_SERVE_WELLKNOWN_PLACEHOLDER|$serve_wellknown|g" "$homeserver_file" runFileOp sed -i "s|SYNAPSE_SERVER_NAME_PLACEHOLDER|$server_name|g" "$homeserver_file" runFileOp sed -i "s|SYNAPSE_DB_PASSWORD_PLACEHOLDER|$db_password|g" "$homeserver_file" runFileOp sed -i "s|SYNAPSE_REGISTRATION_SECRET_PLACEHOLDER|$registration_secret|g" "$homeserver_file" @@ -145,28 +241,32 @@ matrix_install_post_compose() runFileOp chown -R "$docker_install_user":"$docker_install_user" "$data_dir" checkSuccess "Restricting permissions on the Synapse secrets" - # Element is a static bundle; config.json is the only thing that makes it - # point at this homeserver rather than matrix.org. - result=$(copyResource "$app_name" "element-config.json" "element" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1) - checkSuccess "Copying Element configuration to $app_dir/element" - - result=$(runFileOp mv "$app_dir/element/element-config.json" "$app_dir/element/config.json") - checkSuccess "Renaming Element configuration to config.json" - - runFileOp sed -i "s|ELEMENT_HOMESERVER_PLACEHOLDER|$server_name|g" "$app_dir/element/config.json" - runFileOp chown -R "$docker_install_user":"$docker_install_user" "$app_dir/element" - checkSuccess "Pointing Element at https://$server_name" } matrix_install_post_start() { local app_name="$1" + local app_dir="$containers_dir$app_name" ((menu_number++)) echo "" echo "---- $menu_number. Creating the first Matrix admin account" echo "" + # Ports are only allocated during compose-up, so the base URL written by + # install_post_compose was a guess whenever there is no domain. Now that the + # real external port is known, correct it — and only restart if it actually + # changed, so a domain-backed install pays nothing for this. + local base_url current_base + base_url=$(_matrixPublicBaseUrl "$app_name") + current_base=$(runFileOp grep -oP '^public_baseurl:\s*"\K[^"]*' "$app_dir/data/homeserver.yaml" 2>/dev/null) + if [[ -n "$base_url" && "$base_url" != "$current_base" ]]; then + runFileOp sed -i "s|^public_baseurl: .*|public_baseurl: \"$base_url\"|" "$app_dir/data/homeserver.yaml" + runFileOp sed -i "s|\"base_url\": \".*\"|\"base_url\": \"${base_url%/}\"|" "$app_dir/element/config.json" + isSuccessful "Homeserver URL settled as ${base_url%/} (was ${current_base:-unset})" + dockerComposeRestart "$app_name" + fi + # Synapse runs its database migrations on first boot, which on an empty # Postgres takes appreciably longer than the container takes to start. # /health answers only once it is actually serving. @@ -201,14 +301,20 @@ matrix_install_post_start() # Idempotent in practice: on a reinstall over existing data the account # already exists and register_new_matrix_user fails with "User ID already # taken", which is not worth failing the install over. - local result + # rc is captured on its own line because the [[ ]] test below overwrites $? + # — checkSuccess would then report the *test's* result, which is how a + # perfectly successful registration got logged as an error. + local result rc result=$(runFileOp docker exec matrix-synapse register_new_matrix_user \ -u "$admin_user" -p "$admin_pass" -a \ -c /data/homeserver.yaml http://localhost:8008 2>&1) + rc=$? if [[ "$result" == *"already taken"* ]]; then isNotice "Matrix admin '$admin_user' already exists — leaving the existing account alone." + elif (( rc == 0 )); then + isSuccessful "Created Matrix admin account '$admin_user'" else - checkSuccess "Creating Matrix admin account '$admin_user'" + isError "Creating Matrix admin account '$admin_user' failed: $result" fi } @@ -226,10 +332,18 @@ matrix_install_post() echo " Your user ID : @${admin_user}:${server_name}" echo " Password : ${CFG_MATRIX_ADMIN_PASSWORD_1}" echo "" - echo " Sign in through the Element web interface, or any Matrix client" - echo " (Element mobile/desktop, FluffyChat, Nheko) using the server name" - echo " above." + echo " Sign in through the Element web interface, or point any Matrix" + echo " client (FluffyChat, Nheko, Element desktop) at the homeserver URL" + echo " shown above." echo "" + if [[ "$(_matrixPublicBaseUrl "$app_name")" != https://* ]]; then + echo " This install serves plain HTTP, which is fine over the LAN or a" + echo " WireGuard tunnel. Two consequences worth knowing:" + echo " • It cannot federate — other Matrix servers need public DNS + TLS." + echo " • Element's mobile apps expect an HTTPS homeserver; desktop and" + echo " web clients are unaffected." + echo "" + fi echo " Registration is ${CFG_MATRIX_ENABLE_REGISTRATION:-false}. To invite" echo " others while it stays closed, create their accounts with:" echo " docker exec -it matrix-synapse register_new_matrix_user \\" diff --git a/containers/stoat/scripts/stoat_install_hooks.sh b/containers/stoat/scripts/stoat_install_hooks.sh index b17b0b3..156f11d 100644 --- a/containers/stoat/scripts/stoat_install_hooks.sh +++ b/containers/stoat/scripts/stoat_install_hooks.sh @@ -43,6 +43,97 @@ _stoatDomain() fi } +# Scheme + host the client bundle is built against, with no trailing slash. +# +# https:// when Traefik is installed and a domain is configured; +# otherwise http://:, which is a perfectly good Stoat +# instance for LAN or WireGuard use — it just cannot do camera or microphone, +# because browsers only grant those to a secure context. +# +# The port is only assigned during compose-up, so a call from +# install_post_compose returns a best guess and install_post_start corrects it. +_stoatBaseUrl() +{ + local app_name="$1" + local compose="$containers_dir$app_name/docker-compose.yml" + + if [[ -d "${containers_dir}traefik" && -n "$domain_full" ]]; then + local host + host=$(_stoatDomain) + [[ -n "$host" ]] && { echo "https://${host}"; return 0; } + fi + + local ports external + ports=$(tagsManagerGetTagContent "$compose" "PORTS_TAG_1") + external="${ports%%:*}" + if [[ -n "$external" && "$external" != PORTS_DATA* ]]; then + echo "http://${public_ip_v4:-localhost}:${external}" + else + echo "http://${public_ip_v4:-localhost}" + fi +} + +# Write the three files that carry the public URL. Called once with a guess +# before the stack starts (they are bind-mounted, so they must exist or docker +# would create directories in their place) and again once the port is known. +_stoatWriteUrlFiles() +{ + local app_dir="$1" base="$2" video_enabled="$3" + + # ws:// for http, wss:// for https — a wss:// URL on a plain-HTTP origin + # fails to connect and the client hangs on "connecting". + local ws_scheme="wss" + [[ "$base" == http://* ]] && ws_scheme="ws" + local hostport="${base#*://}" + + runFileWrite "$app_dir/.env.web" </dev/null) + current="${current%/api}" + [[ "$base" == "$current" ]] && return 0 + + ((menu_number++)) + echo "" + echo "---- $menu_number. Settling the Stoat public URL" + echo "" + + local video_enabled="" + [[ "$CFG_STOAT_ENABLE_VIDEO" != "false" ]] && video_enabled="true" + _stoatWriteUrlFiles "$app_dir" "$base" "$video_enabled" + runFileOp chown -R "$docker_install_user":"$docker_install_user" "$app_dir" + isSuccessful "Public URL settled as $base (was ${current:-unset})" + + # The web client compiles VITE_* at container start, so it has to come back + # up before the corrected URL reaches a browser. + dockerComposeRestart "$app_name" +} + stoat_install_post() { local app_name="$1" - local domain - domain=$(_stoatDomain) + local base + base=$(_stoatBaseUrl "$app_name") echo "" isNotice "Stoat first run:" echo "" - echo " Open https://${domain} and create an account — the first account" + echo " Open ${base} and create an account — the first account" echo " registered on a fresh instance becomes the instance owner." echo "" + if [[ "$base" == http://* ]]; then + echo " This install serves plain HTTP. Text chat, channels, roles and" + echo " uploads all work, but browsers refuse camera and microphone" + echo " access outside a secure context — so voice and video will not" + echo " work until it is served over HTTPS. A WireGuard tunnel does not" + echo " change that: the check is on the URL scheme, not the transport." + echo "" + fi echo " Give it a few minutes on first boot: sixteen containers start in" echo " dependency order, and the API restarts until MongoDB and RabbitMQ" echo " both report healthy. 'docker compose ps' in the app directory" diff --git a/containers/stoat/stoat.config b/containers/stoat/stoat.config index b7f8042..09f1575 100644 --- a/containers/stoat/stoat.config +++ b/containers/stoat/stoat.config @@ -10,14 +10,17 @@ # HEALTHCHECK = if true, default docker health checks for that container will be enabled # AUTHELIA = if true, use Authelia authentication, if false turned off. # HEADSCALE = options : false, local, remote (see general config). e.g false or local,remote -# ENABLE_VIDEO = if true, allow camera and screen sharing (voice always works) +# ENABLE_VIDEO = if true, allow camera and screen sharing (needs an HTTPS origin to work in a browser) # MONITORING = if true, export this app's metrics to Prometheus + Grafana (needs both apps installed) # CFG_STOAT_APP_NAME=stoat -# Stoat bakes its public URL into the client bundle and into Revolt.toml at -# install time, and voice needs real TLS, so a domain behind Traefik is a -# prerequisite rather than a nicety. -CFG_STOAT_REQUIRES="domain,traefik" +# No prerequisites. Stoat bakes its public URL into the client bundle, but that +# URL can just as well be http://: — text chat, channels, roles +# and uploads all work that way over a LAN or WireGuard tunnel. Only camera and +# microphone need an HTTPS origin, because browsers refuse getUserMedia outside +# a secure context; a VPN does not change that, since the check is on the URL +# scheme. Any HTTPS satisfies it, including a self-signed Traefik certificate. +CFG_STOAT_REQUIRES="" CFG_STOAT_BACKUP=true CFG_STOAT_BACKUP_STRATEGY=auto # Manual, deliberately. This is a sixteen-service stack whose components are @@ -58,7 +61,6 @@ CFG_STOAT_DESCRIPTION="Discord-style Chat" CFG_STOAT_LONG_DESCRIPTION="The closest open-source match to Discord — servers, channels, roles, and LiveKit voice and video. Heavy (16 containers) and does not federate" CFG_STOAT_URL="https://github.com/stoatchat/self-hosted" CFG_STOAT_ACTIONS="configure|install|restart|shutdown|uninstall" -CFG_STOAT_REQUIRES_SERVICE=traefik # # ============================================================================= # NETWORK CONFIGURATION diff --git a/scripts/source/files/arrays/function_manifest.sh b/scripts/source/files/arrays/function_manifest.sh index 8e6c2c0..94960dd 100644 --- a/scripts/source/files/arrays/function_manifest.sh +++ b/scripts/source/files/arrays/function_manifest.sh @@ -77,6 +77,7 @@ declare -gA LP_FN_MAP=( [_appReqServiceMsg]="checks/requirements/check_app_install.sh" [appScanAvailable]="app/app_scan_available.sh" [appSetupComposeTags_gluetun]="gluetun/scripts/gluetun_compose_tags.sh" + [appSetupComposeTags_matrix]="matrix/scripts/matrix_compose_tags.sh" [appSetupComposeTags_nextcloud]="nextcloud/scripts/nextcloud_compose_tags.sh" [appSetupComposeTags_pihole]="pihole/scripts/pihole_compose_tags.sh" [appSetupComposeTags_searxng]="searxng/scripts/searxng_compose_tags.sh" @@ -651,6 +652,7 @@ declare -gA LP_FN_MAP=( [matrix_install_post_compose]="matrix/scripts/matrix_install_hooks.sh" [matrix_install_post_start]="matrix/scripts/matrix_install_hooks.sh" [matrix_install_pre]="matrix/scripts/matrix_install_hooks.sh" + [_matrixPublicBaseUrl]="matrix/scripts/matrix_install_hooks.sh" [_matrixServerName]="matrix/scripts/matrix_install_hooks.sh" [mattermostToolsMenu]="menu/tools/manage_mattermost.sh" [maybeRegenPoll]="task/crontab_task_processor.sh" @@ -885,11 +887,14 @@ declare -gA LP_FN_MAP=( [startOther]="start/start_other.sh" [startPreInstall]="start/start_preinstall.sh" [startScan]="start/start_scan.sh" + [_stoatBaseUrl]="stoat/scripts/stoat_install_hooks.sh" [_stoatDomain]="stoat/scripts/stoat_install_hooks.sh" [stoat_install_post]="stoat/scripts/stoat_install_hooks.sh" [stoat_install_post_compose]="stoat/scripts/stoat_install_hooks.sh" + [stoat_install_post_start]="stoat/scripts/stoat_install_hooks.sh" [stoat_install_pre]="stoat/scripts/stoat_install_hooks.sh" [_stoatWriteSecrets]="stoat/scripts/stoat_install_hooks.sh" + [_stoatWriteUrlFiles]="stoat/scripts/stoat_install_hooks.sh" [stopCrowdsec]="crowdsec/crowdsec.sh" [switchMigrateBackupApps]="docker/type_switcher/swap_docker_type.sh" [switchMigrateRestoreApps]="docker/type_switcher/swap_docker_type.sh" @@ -1133,6 +1138,7 @@ declare -gA LP_FN_ROOT=( [_appReqServiceMsg]="scripts" [appScanAvailable]="scripts" [appSetupComposeTags_gluetun]="containers" + [appSetupComposeTags_matrix]="containers" [appSetupComposeTags_nextcloud]="containers" [appSetupComposeTags_pihole]="containers" [appSetupComposeTags_searxng]="containers" @@ -1707,6 +1713,7 @@ declare -gA LP_FN_ROOT=( [matrix_install_post_compose]="containers" [matrix_install_post_start]="containers" [matrix_install_pre]="containers" + [_matrixPublicBaseUrl]="containers" [_matrixServerName]="containers" [mattermostToolsMenu]="scripts" [maybeRegenPoll]="scripts" @@ -1941,11 +1948,14 @@ declare -gA LP_FN_ROOT=( [startOther]="scripts" [startPreInstall]="scripts" [startScan]="scripts" + [_stoatBaseUrl]="containers" [_stoatDomain]="containers" [stoat_install_post]="containers" [stoat_install_post_compose]="containers" + [stoat_install_post_start]="containers" [stoat_install_pre]="containers" [_stoatWriteSecrets]="containers" + [_stoatWriteUrlFiles]="containers" [stopCrowdsec]="containers" [switchMigrateBackupApps]="scripts" [switchMigrateRestoreApps]="scripts" @@ -2223,6 +2233,7 @@ _appReqServiceInstalled() { unset -f _appReqServiceInstalled; __lpAutoload "${in _appReqServiceMsg() { unset -f _appReqServiceMsg; __lpAutoload "${install_scripts_dir}checks/requirements/check_app_install.sh"; _appReqServiceMsg "$@"; } appScanAvailable() { unset -f appScanAvailable; __lpAutoload "${install_scripts_dir}app/app_scan_available.sh"; appScanAvailable "$@"; } appSetupComposeTags_gluetun() { unset -f appSetupComposeTags_gluetun; __lpAutoload "${install_containers_dir}gluetun/scripts/gluetun_compose_tags.sh"; appSetupComposeTags_gluetun "$@"; } +appSetupComposeTags_matrix() { unset -f appSetupComposeTags_matrix; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_compose_tags.sh"; appSetupComposeTags_matrix "$@"; } appSetupComposeTags_nextcloud() { unset -f appSetupComposeTags_nextcloud; __lpAutoload "${install_containers_dir}nextcloud/scripts/nextcloud_compose_tags.sh"; appSetupComposeTags_nextcloud "$@"; } appSetupComposeTags_pihole() { unset -f appSetupComposeTags_pihole; __lpAutoload "${install_containers_dir}pihole/scripts/pihole_compose_tags.sh"; appSetupComposeTags_pihole "$@"; } appSetupComposeTags_searxng() { unset -f appSetupComposeTags_searxng; __lpAutoload "${install_containers_dir}searxng/scripts/searxng_compose_tags.sh"; appSetupComposeTags_searxng "$@"; } @@ -2797,6 +2808,7 @@ matrix_install_post() { unset -f matrix_install_post; __lpAutoload "${install_co matrix_install_post_compose() { unset -f matrix_install_post_compose; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_post_compose "$@"; } matrix_install_post_start() { unset -f matrix_install_post_start; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_post_start "$@"; } matrix_install_pre() { unset -f matrix_install_pre; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_pre "$@"; } +_matrixPublicBaseUrl() { unset -f _matrixPublicBaseUrl; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; _matrixPublicBaseUrl "$@"; } _matrixServerName() { unset -f _matrixServerName; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; _matrixServerName "$@"; } mattermostToolsMenu() { unset -f mattermostToolsMenu; __lpAutoload "${install_scripts_dir}menu/tools/manage_mattermost.sh"; mattermostToolsMenu "$@"; } maybeRegenPoll() { unset -f maybeRegenPoll; __lpAutoload "${install_scripts_dir}task/crontab_task_processor.sh"; maybeRegenPoll "$@"; } @@ -3031,11 +3043,14 @@ startLoad() { unset -f startLoad; __lpAutoload "${install_scripts_dir}start/star startOther() { unset -f startOther; __lpAutoload "${install_scripts_dir}start/start_other.sh"; startOther "$@"; } startPreInstall() { unset -f startPreInstall; __lpAutoload "${install_scripts_dir}start/start_preinstall.sh"; startPreInstall "$@"; } startScan() { unset -f startScan; __lpAutoload "${install_scripts_dir}start/start_scan.sh"; startScan "$@"; } +_stoatBaseUrl() { unset -f _stoatBaseUrl; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; _stoatBaseUrl "$@"; } _stoatDomain() { unset -f _stoatDomain; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; _stoatDomain "$@"; } stoat_install_post() { unset -f stoat_install_post; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; stoat_install_post "$@"; } stoat_install_post_compose() { unset -f stoat_install_post_compose; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; stoat_install_post_compose "$@"; } +stoat_install_post_start() { unset -f stoat_install_post_start; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; stoat_install_post_start "$@"; } stoat_install_pre() { unset -f stoat_install_pre; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; stoat_install_pre "$@"; } _stoatWriteSecrets() { unset -f _stoatWriteSecrets; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; _stoatWriteSecrets "$@"; } +_stoatWriteUrlFiles() { unset -f _stoatWriteUrlFiles; __lpAutoload "${install_containers_dir}stoat/scripts/stoat_install_hooks.sh"; _stoatWriteUrlFiles "$@"; } stopCrowdsec() { unset -f stopCrowdsec; __lpAutoload "${install_containers_dir}crowdsec/crowdsec.sh"; stopCrowdsec "$@"; } switchMigrateBackupApps() { unset -f switchMigrateBackupApps; __lpAutoload "${install_scripts_dir}docker/type_switcher/swap_docker_type.sh"; switchMigrateBackupApps "$@"; } switchMigrateRestoreApps() { unset -f switchMigrateRestoreApps; __lpAutoload "${install_scripts_dir}docker/type_switcher/swap_docker_type.sh"; switchMigrateRestoreApps "$@"; }