stoat: add the ex-Revolt stack as the closest Discord equivalent
Sixteen containers: MongoDB, Valkey, RabbitMQ, MinIO and eleven Stoat services. Servers, channels, roles and voice/video through LiveKit — the nearest thing in the catalogue to Discord itself, at the price of being much the heaviest app in it. Does not federate. The compose service keys are deliberately kept identical to upstream's (database, redis, api, autumn, ...) while container_name is prefixed stoat-. Compose registers both on the network, so upstream's internal defaults keep resolving and LibrePortal still gets the prefixed names its port, firewall and backup layers key on. Upstream's Caddy is kept as the internal path router and Traefik simply proxies to it, which is upstream's own supported behind-a-reverse-proxy mode — reimplementing eight path routes as Traefik labels would be a second copy to keep in sync for nothing. The install hook is a non-interactive port of generate_config.sh, and it never rewrites an existing secrets.env: REVOLT__FILES__ENCRYPTION_KEY decrypts every file ever uploaded, so regenerating it would orphan the whole media store. LiveKit's UDP media range is published literally rather than through the port table, because the firewall rebuild emits /tcp rules only and a range declared there would produce a wrong rule rather than no rule. Voice falls back to TCP 7881 until the range is opened by hand; the post-install notice says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
50059ea1b8
commit
9919eea138
369
containers/stoat/docker-compose.yml
Normal file
369
containers/stoat/docker-compose.yml
Normal file
@ -0,0 +1,369 @@
|
||||
# Stoat (formerly Revolt) — the closest open-source equivalent to Discord's
|
||||
# model of servers, channels, roles and voice.
|
||||
#
|
||||
# Layout note, because it looks inconsistent at first glance: the compose
|
||||
# *service keys* below (database, redis, api, autumn, ...) are deliberately kept
|
||||
# identical to upstream's compose.yml, while container_name is prefixed with
|
||||
# stoat- so nothing collides with other LibrePortal apps. Compose registers both
|
||||
# the service key and the container name on the network, so upstream's internal
|
||||
# defaults — the S3 endpoint baked into the file server, MINIO_DOMAIN, the
|
||||
# service names in Revolt.toml — keep resolving, and LibrePortal still gets the
|
||||
# prefixed container names its port, firewall and backup layers key on.
|
||||
#
|
||||
# Upstream fronts the whole stack with Caddy doing path routing (/api, /ws,
|
||||
# /autumn, ...). That is kept as-is and Traefik simply proxies to it, which is
|
||||
# upstream's own supported "behind another reverse proxy" mode — reimplementing
|
||||
# eight path routes as Traefik labels would be a second copy to keep in sync for
|
||||
# no benefit. Caddy is given HOSTNAME=:80 by the install hook so it serves plain
|
||||
# HTTP and never tries to obtain its own certificate.
|
||||
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
external: true
|
||||
|
||||
services:
|
||||
# MongoDB — primary datastore.
|
||||
#
|
||||
# Upstream's healthcheck is kept rather than the LibrePortal HEALTHCHECK_TAG:
|
||||
# half the stack uses `depends_on: condition: service_healthy` against it, so
|
||||
# disabling the healthcheck would deadlock the boot order. Same for rabbit.
|
||||
database:
|
||||
container_name: stoat-database
|
||||
image: mongo:8.0 #LIBREPORTAL|STOAT_MONGO_VERSION_TAG|8.0
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data/db:/data/db
|
||||
environment:
|
||||
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
||||
healthcheck:
|
||||
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_1 #LIBREPORTAL|IP_TAG_1|IP_DATA_1
|
||||
|
||||
# Valkey — event message broker and KV store.
|
||||
redis:
|
||||
container_name: stoat-redis
|
||||
image: valkey/valkey:9-alpine #LIBREPORTAL|STOAT_VALKEY_VERSION_TAG|9-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_2 #LIBREPORTAL|IP_TAG_2|IP_DATA_2
|
||||
|
||||
# RabbitMQ — internal message broker (push notifications, voice events).
|
||||
rabbit:
|
||||
container_name: stoat-rabbit
|
||||
image: rabbitmq:4-alpine #LIBREPORTAL|STOAT_RABBITMQ_VERSION_TAG|4-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
||||
- RABBITMQ_DEFAULT_USER=stoat
|
||||
- RABBITMQ_DEFAULT_PASS=PASSWORD_DATA_1 #LIBREPORTAL|PASSWORD_TAG_1|PASSWORD_DATA_1
|
||||
volumes:
|
||||
- ./data/rabbit:/var/lib/rabbitmq
|
||||
healthcheck:
|
||||
test: rabbitmq-diagnostics -q ping
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_3 #LIBREPORTAL|IP_TAG_3|IP_DATA_3
|
||||
|
||||
# MinIO — S3-compatible object storage for uploads and avatars.
|
||||
#
|
||||
# The bucket-name aliases are load-bearing: the file server addresses
|
||||
# buckets virtual-host style (<bucket>.minio), so without these the DNS
|
||||
# lookup fails and every upload errors.
|
||||
minio:
|
||||
container_name: stoat-minio
|
||||
image: minio/minio:latest #LIBREPORTAL|STOAT_MINIO_VERSION_TAG|latest
|
||||
restart: unless-stopped
|
||||
command: server /data
|
||||
volumes:
|
||||
- ./data/minio:/data
|
||||
environment:
|
||||
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
||||
- MINIO_ROOT_USER=stoatminio
|
||||
- MINIO_ROOT_PASSWORD=PASSWORD_DATA_2 #LIBREPORTAL|PASSWORD_TAG_2|PASSWORD_DATA_2
|
||||
- MINIO_DOMAIN=minio
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_4 #LIBREPORTAL|IP_TAG_4|IP_DATA_4
|
||||
aliases:
|
||||
- minio
|
||||
- revolt-uploads.minio
|
||||
# Legacy bucket names, kept because instances created before
|
||||
# the consolidation still address them.
|
||||
- attachments.minio
|
||||
- avatars.minio
|
||||
- backgrounds.minio
|
||||
- icons.minio
|
||||
- banners.minio
|
||||
- emojis.minio
|
||||
|
||||
# One-shot: creates the uploads bucket, then exits. Not a failure when you
|
||||
# see it stopped.
|
||||
createbuckets:
|
||||
container_name: stoat-createbuckets
|
||||
image: minio/mc:latest #LIBREPORTAL|STOAT_MINIO_MC_VERSION_TAG|latest
|
||||
depends_on:
|
||||
- minio
|
||||
# Credentials come in through the environment rather than being written
|
||||
# into the entrypoint: a #LIBREPORTAL annotation only substitutes on the
|
||||
# line it sits on, and inside a folded block scalar it would end up as
|
||||
# literal text in the command anyway. $$ escapes the dollar so compose
|
||||
# leaves it for the container's shell instead of interpolating it here.
|
||||
environment:
|
||||
- MC_USER=stoatminio
|
||||
- MC_PASS=PASSWORD_DATA_2 #LIBREPORTAL|PASSWORD_TAG_2|PASSWORD_DATA_2
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
while ! /usr/bin/mc ready minio; do
|
||||
/usr/bin/mc alias set minio http://minio:9000 $$MC_USER $$MC_PASS;
|
||||
echo 'Waiting minio...' && sleep 1;
|
||||
done;
|
||||
/usr/bin/mc mb --ignore-existing minio/revolt-uploads;
|
||||
exit 0;
|
||||
"
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_5 #LIBREPORTAL|IP_TAG_5|IP_DATA_5
|
||||
|
||||
# Caddy — internal path router for the whole stack. This is the only service
|
||||
# Traefik talks to, and the only one carrying a Traefik router.
|
||||
caddy: #LIBREPORTAL|SERVICE_TAG_1|caddy
|
||||
container_name: stoat-caddy
|
||||
image: caddy:2-alpine #LIBREPORTAL|STOAT_CADDY_VERSION_TAG|2-alpine
|
||||
restart: unless-stopped
|
||||
env_file: .env.web
|
||||
# GLUETUN_OFF_BEGIN
|
||||
ports:
|
||||
- "PORTS_DATA_1" #LIBREPORTAL|PORTS_TAG_1|PORTS_DATA_1
|
||||
# GLUETUN_OFF_END
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./stoat.json:/stoat.json:ro
|
||||
- ./data/caddy-data:/data
|
||||
- ./data/caddy-config:/config
|
||||
labels:
|
||||
libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA
|
||||
libreportal.title: "TITLE_DATA" #LIBREPORTAL|TITLE_TAG|TITLE_DATA
|
||||
libreportal.backup.db: "mongo:stoat-database:data/db:"
|
||||
# Uploads live in MinIO, not on a filesystem the file server owns,
|
||||
# so the object store's own data dir is what has to be captured.
|
||||
libreportal.backup.files: "stoat-minio:/data:data/minio"
|
||||
traefik.enable: TRAEFIK_ENABLE_DATA #LIBREPORTAL|TRAEFIK_ENABLE_TAG|TRAEFIK_ENABLE_DATA
|
||||
# TRAEFIK_PORT_1_BEGIN
|
||||
traefik.http.routers.stoat-caddy.entrypoints: web,websecure
|
||||
traefik.http.routers.stoat-caddy.rule: Host(`DOMAINSUBNAME_DATA_1`) #LIBREPORTAL|DOMAINSUBNAME_TAG_1|DOMAINSUBNAME_DATA_1
|
||||
traefik.http.routers.stoat-caddy.tls: true
|
||||
traefik.http.routers.stoat-caddy.tls.certresolver: production
|
||||
traefik.http.services.stoat-caddy.loadbalancer.server.port: PORT_INTERNAL_DATA_1 #LIBREPORTAL|PORT_INTERNAL_TAG_1|PORT_INTERNAL_DATA_1
|
||||
traefik.http.routers.stoat-caddy.middlewares: MIDDLEWARE_DATA_1 #LIBREPORTAL|MIDDLEWARE_TAG_1|MIDDLEWARE_DATA_1
|
||||
# TRAEFIK_PORT_1_END
|
||||
traefik.docker.network: DOCKER_NETWORK_DATA #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
healthcheck:
|
||||
disable: HEALTHCHECK_DATA #LIBREPORTAL|HEALTHCHECK_TAG|HEALTHCHECK_DATA
|
||||
# GLUETUN_OFF_BEGIN
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_6 #LIBREPORTAL|IP_TAG_6|IP_DATA_6
|
||||
aliases:
|
||||
- caddy
|
||||
# GLUETUN_OFF_END
|
||||
# GLUETUN_ON_BEGIN
|
||||
# network_mode: "container:gluetun-service"
|
||||
# GLUETUN_ON_END
|
||||
|
||||
# API server.
|
||||
api:
|
||||
container_name: stoat-api
|
||||
image: ghcr.io/stoatchat/api:v0.15.1 #LIBREPORTAL|STOAT_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
rabbit:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_7 #LIBREPORTAL|IP_TAG_7|IP_DATA_7
|
||||
aliases:
|
||||
- api
|
||||
|
||||
# Websocket / events service.
|
||||
events:
|
||||
container_name: stoat-events
|
||||
image: ghcr.io/stoatchat/events:v0.15.1 #LIBREPORTAL|STOAT_EVENTS_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_8 #LIBREPORTAL|IP_TAG_8|IP_DATA_8
|
||||
aliases:
|
||||
- events
|
||||
|
||||
# Autumn — file server.
|
||||
autumn:
|
||||
container_name: stoat-autumn
|
||||
image: ghcr.io/stoatchat/file-server:v0.15.1 #LIBREPORTAL|STOAT_AUTUMN_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
createbuckets:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_9 #LIBREPORTAL|IP_TAG_9|IP_DATA_9
|
||||
aliases:
|
||||
- autumn
|
||||
|
||||
# January — link metadata and image proxy.
|
||||
january:
|
||||
container_name: stoat-january
|
||||
image: ghcr.io/stoatchat/proxy:v0.15.1 #LIBREPORTAL|STOAT_JANUARY_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_10 #LIBREPORTAL|IP_TAG_10|IP_DATA_10
|
||||
aliases:
|
||||
- january
|
||||
|
||||
# Gifbox — Tenor proxy for the GIF picker. Inert until a Tenor API key is
|
||||
# added to secrets.env; see the upstream Guides.md.
|
||||
gifbox:
|
||||
container_name: stoat-gifbox
|
||||
image: ghcr.io/stoatchat/gifbox:v0.15.1 #LIBREPORTAL|STOAT_GIFBOX_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_11 #LIBREPORTAL|IP_TAG_11|IP_DATA_11
|
||||
aliases:
|
||||
- gifbox
|
||||
|
||||
# Scheduled task daemon.
|
||||
crond:
|
||||
container_name: stoat-crond
|
||||
image: ghcr.io/stoatchat/crond:v0.15.1 #LIBREPORTAL|STOAT_CROND_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_12 #LIBREPORTAL|IP_TAG_12|IP_DATA_12
|
||||
|
||||
# Push notification daemon.
|
||||
pushd:
|
||||
container_name: stoat-pushd
|
||||
image: ghcr.io/stoatchat/pushd:v0.15.1 #LIBREPORTAL|STOAT_PUSHD_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
rabbit:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_13 #LIBREPORTAL|IP_TAG_13|IP_DATA_13
|
||||
|
||||
# Voice ingress daemon — receives LiveKit's webhooks.
|
||||
voice-ingress:
|
||||
container_name: stoat-voice-ingress
|
||||
image: ghcr.io/stoatchat/voice-ingress:v0.15.1 #LIBREPORTAL|STOAT_VOICE_INGRESS_VERSION_TAG|v0.15.1
|
||||
restart: unless-stopped
|
||||
env_file: secrets.env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
rabbit:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./Revolt.toml:/Revolt.toml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_14 #LIBREPORTAL|IP_TAG_14|IP_DATA_14
|
||||
aliases:
|
||||
- voice-ingress
|
||||
|
||||
# LiveKit — the WebRTC SFU behind voice and video.
|
||||
#
|
||||
# Its media ports cannot go through Traefik: WebRTC is UDP, and Traefik is
|
||||
# an HTTP proxy. The TCP fallback port is declared in stoat.config so the
|
||||
# port and firewall layers manage it. The UDP range is published literally
|
||||
# below because LibrePortal's port table stores one port per row and its
|
||||
# firewall rebuild emits /tcp rules only — a range declared there would
|
||||
# produce a wrong rule rather than no rule. Open it yourself if voice needs
|
||||
# to work from outside the LAN:
|
||||
# sudo ufw allow 50000:50100/udp
|
||||
# Voice still falls back to TCP 7881 without it, at the cost of latency.
|
||||
livekit:
|
||||
container_name: stoat-livekit
|
||||
image: ghcr.io/stoatchat/livekit-server:v1.9.13 #LIBREPORTAL|STOAT_LIVEKIT_VERSION_TAG|v1.9.13
|
||||
restart: unless-stopped
|
||||
command: --config /etc/livekit.yml
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
ports:
|
||||
- "PORTS_DATA_2" #LIBREPORTAL|PORTS_TAG_2|PORTS_DATA_2
|
||||
- "50000-50100:50000-50100/udp"
|
||||
volumes:
|
||||
- ./livekit.yml:/etc/livekit.yml:ro
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_15 #LIBREPORTAL|IP_TAG_15|IP_DATA_15
|
||||
aliases:
|
||||
- livekit
|
||||
|
||||
# The web client itself. Served by Caddy at /.
|
||||
web:
|
||||
container_name: stoat-web
|
||||
image: ghcr.io/stoatchat/for-web:0c31cf0 #LIBREPORTAL|STOAT_WEB_VERSION_TAG|0c31cf0
|
||||
restart: unless-stopped
|
||||
env_file: .env.web
|
||||
networks:
|
||||
DOCKER_NETWORK_DATA: #LIBREPORTAL|DOCKER_NETWORK_TAG|DOCKER_NETWORK_DATA
|
||||
ipv4_address: IP_DATA_16 #LIBREPORTAL|IP_TAG_16|IP_DATA_16
|
||||
aliases:
|
||||
- web
|
||||
70
containers/stoat/resources/Caddyfile
Normal file
70
containers/stoat/resources/Caddyfile
Normal file
@ -0,0 +1,70 @@
|
||||
# Internal path router for the Stoat stack, taken from upstream's self-hosted
|
||||
# repository. Traefik terminates TLS and proxies here, so HOSTNAME is set to
|
||||
# ":80" in .env.web and Caddy neither requests nor serves certificates.
|
||||
#
|
||||
# The path prefixes below are not arbitrary — the web client is built with
|
||||
# VITE_API_URL=https://<domain>/api and friends, so these routes and the URLs in
|
||||
# .env.web / Revolt.toml have to agree.
|
||||
|
||||
{$HOSTNAME} {
|
||||
route /.well-known/stoat {
|
||||
uri strip_prefix /.well-known/stoat
|
||||
header {
|
||||
Access-Control-Allow-Origin *
|
||||
}
|
||||
file_server {
|
||||
root /stoat.json
|
||||
}
|
||||
}
|
||||
|
||||
route /api* {
|
||||
uri strip_prefix /api
|
||||
reverse_proxy http://api:14702 {
|
||||
header_down Location "^/" "/api/"
|
||||
}
|
||||
}
|
||||
|
||||
route /ws {
|
||||
uri strip_prefix /ws
|
||||
reverse_proxy http://events:14703 {
|
||||
header_down Location "^/" "/ws/"
|
||||
}
|
||||
}
|
||||
|
||||
route /autumn* {
|
||||
uri strip_prefix /autumn
|
||||
reverse_proxy http://autumn:14704 {
|
||||
header_down Location "^/" "/autumn/"
|
||||
}
|
||||
}
|
||||
|
||||
route /january* {
|
||||
uri strip_prefix /january
|
||||
reverse_proxy http://january:14705 {
|
||||
header_down Location "^/" "/january/"
|
||||
}
|
||||
}
|
||||
|
||||
route /gifbox* {
|
||||
uri strip_prefix /gifbox
|
||||
reverse_proxy http://gifbox:14706 {
|
||||
header_down Location "^/" "/gifbox/"
|
||||
}
|
||||
}
|
||||
|
||||
route /livekit* {
|
||||
uri strip_prefix /livekit
|
||||
reverse_proxy http://livekit:7880 {
|
||||
header_down Location "^/" "/livekit/"
|
||||
}
|
||||
}
|
||||
|
||||
route /ingress* {
|
||||
uri strip_prefix /ingress
|
||||
reverse_proxy http://voice-ingress:8500 {
|
||||
header_down Location "^/" "/ingress/"
|
||||
}
|
||||
}
|
||||
|
||||
reverse_proxy http://web:5000
|
||||
}
|
||||
226
containers/stoat/scripts/stoat_install_hooks.sh
Normal file
226
containers/stoat/scripts/stoat_install_hooks.sh
Normal file
@ -0,0 +1,226 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stoat install hooks.
|
||||
#
|
||||
# Upstream configures an instance with an interactive generate_config.sh that
|
||||
# asks for a domain and writes five files. This is the non-interactive
|
||||
# equivalent, driven by the domain LibrePortal already knows and writing into
|
||||
# the app's install directory.
|
||||
#
|
||||
# The one rule that matters here: secrets.env is generated ONCE and never
|
||||
# rewritten. REVOLT__FILES__ENCRYPTION_KEY decrypts every file ever uploaded to
|
||||
# the instance, so regenerating it on a reinstall would permanently orphan the
|
||||
# entire media store — which is exactly the failure upstream's script warns
|
||||
# about at length.
|
||||
|
||||
stoat_install_pre()
|
||||
{
|
||||
local app_name="$1"
|
||||
if ! appInstallCheckRequirements "$app_name" "$CFG_STOAT_REQUIRES"; then
|
||||
stoat=n
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# The public host, read back from the deployed compose once tag substitution has
|
||||
# filled it in. Everything else in this file is derived from it.
|
||||
_stoatDomain()
|
||||
{
|
||||
local app_name="$1"
|
||||
tagsManagerGetTagContent "$containers_dir$app_name/docker-compose.yml" "DOMAINSUBNAME_TAG_1"
|
||||
}
|
||||
|
||||
# Generate secrets.env if it does not already exist. Returns without touching an
|
||||
# existing file — see the warning at the top.
|
||||
_stoatWriteSecrets()
|
||||
{
|
||||
local secrets_file="$1"
|
||||
|
||||
if [[ -s "$secrets_file" ]]; then
|
||||
isNotice "Existing secrets.env found — keeping it (regenerating would orphan every uploaded file)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# VAPID keypair for web push. The public key is the uncompressed EC point,
|
||||
# which is the last 65 bytes of the DER encoding, base64url-encoded without
|
||||
# padding — that is what the browser Push API expects.
|
||||
local vapid_pem vapid_private vapid_public
|
||||
vapid_pem=$(mktemp)
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out "$vapid_pem" 2>/dev/null
|
||||
vapid_private=$(base64 < "$vapid_pem" | tr -d '\n' | tr -d '=')
|
||||
vapid_public=$(openssl ec -in "$vapid_pem" -outform DER 2>/dev/null | tail -c 65 | base64 | tr '/+' '_-' | tr -d '\n' | tr -d '=')
|
||||
rm -f "$vapid_pem"
|
||||
|
||||
local files_key livekit_key livekit_secret
|
||||
files_key=$(openssl rand -base64 32)
|
||||
livekit_key=$(openssl rand -hex 6)
|
||||
livekit_secret=$(openssl rand -hex 24)
|
||||
|
||||
runFileWrite "$secrets_file" <<EOF
|
||||
# Generated by LibrePortal at install time. Treat this file as you would a
|
||||
# private key: REVOLT__FILES__ENCRYPTION_KEY is the only thing that can decrypt
|
||||
# the media store, and it is never regenerated once written.
|
||||
REVOLT__PUSHD__VAPID__PRIVATE_KEY='${vapid_private}'
|
||||
REVOLT__PUSHD__VAPID__PUBLIC_KEY='${vapid_public}'
|
||||
|
||||
REVOLT__FILES__ENCRYPTION_KEY='${files_key}'
|
||||
|
||||
REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY='${livekit_key}'
|
||||
REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET='${livekit_secret}'
|
||||
EOF
|
||||
runFileOp chmod 600 "$secrets_file"
|
||||
isSuccessful "Generated secrets.env"
|
||||
}
|
||||
|
||||
stoat_install_post_compose()
|
||||
{
|
||||
local app_name="$1"
|
||||
local app_dir="$containers_dir$app_name"
|
||||
|
||||
((menu_number++))
|
||||
echo ""
|
||||
echo "---- $menu_number. Generating the Stoat instance configuration"
|
||||
echo ""
|
||||
|
||||
local domain
|
||||
domain=$(_stoatDomain "$app_name")
|
||||
if [[ -z "$domain" ]]; then
|
||||
isError "Could not determine the public host from the compose file — aborting Stoat configuration."
|
||||
isNotice "Check that CFG_STOAT_PORT_1 is public and Traefik-managed, then reinstall."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local result
|
||||
result=$(createFolders "loud" "$docker_install_user" \
|
||||
"$app_dir/data/db" "$app_dir/data/rabbit" "$app_dir/data/minio" \
|
||||
"$app_dir/data/caddy-data" "$app_dir/data/caddy-config")
|
||||
checkSuccess "Creating $app_name data folders"
|
||||
|
||||
_stoatWriteSecrets "$app_dir/secrets.env"
|
||||
|
||||
# Read the LiveKit credentials back out — either the ones just generated or
|
||||
# the ones preserved from a previous install — because livekit.yml has to
|
||||
# carry the same pair the API is configured with.
|
||||
local livekit_key livekit_secret
|
||||
livekit_key=$(grep -oP "REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY='\K[^']*" "$app_dir/secrets.env" 2>/dev/null)
|
||||
livekit_secret=$(grep -oP "REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET='\K[^']*" "$app_dir/secrets.env" 2>/dev/null)
|
||||
if [[ -z "$livekit_key" || -z "$livekit_secret" ]]; then
|
||||
isError "Could not read the LiveKit credentials from secrets.env — voice will not work."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# HOSTNAME=:80 is what puts Caddy in plain-HTTP mode behind Traefik. The
|
||||
# VITE_* values are compiled into the browser bundle, so they must be the
|
||||
# public https:// URLs, not internal container addresses.
|
||||
local video_enabled=""
|
||||
[[ "$CFG_STOAT_ENABLE_VIDEO" != "false" ]] && video_enabled="true"
|
||||
|
||||
runFileWrite "$app_dir/.env.web" <<EOF
|
||||
HOSTNAME=:80
|
||||
REVOLT_PUBLIC_URL=https://${domain}/api
|
||||
VITE_API_URL=https://${domain}/api
|
||||
VITE_WS_URL=wss://${domain}/ws
|
||||
VITE_MEDIA_URL=https://${domain}/autumn
|
||||
VITE_PROXY_URL=https://${domain}/january
|
||||
VITE_GIFBOX_URL=https://${domain}/gifbox
|
||||
VITE_CFG_ENABLE_VIDEO=${video_enabled}
|
||||
EOF
|
||||
checkSuccess "Writing .env.web for https://$domain"
|
||||
|
||||
# Client discovery document, served at /.well-known/stoat.
|
||||
printf '{"api":"https://%s/api"}' "$domain" | runFileWrite "$app_dir/stoat.json"
|
||||
checkSuccess "Writing stoat.json"
|
||||
|
||||
runFileWrite "$app_dir/Revolt.toml" <<EOF
|
||||
# Generated by LibrePortal at install time. Secrets live in secrets.env, not
|
||||
# here. Reinstalling the app rewrites this file — put custom configuration in a
|
||||
# copy and merge it back if you change anything.
|
||||
[hosts]
|
||||
app = "https://${domain}"
|
||||
api = "https://${domain}/api"
|
||||
events = "wss://${domain}/ws"
|
||||
autumn = "https://${domain}/autumn"
|
||||
january = "https://${domain}/january"
|
||||
gifbox = "https://${domain}/gifbox"
|
||||
|
||||
[hosts.livekit]
|
||||
worldwide = "wss://${domain}/livekit"
|
||||
|
||||
[api.livekit.nodes.worldwide]
|
||||
url = "http://livekit:7880"
|
||||
lat = 0.0
|
||||
lon = 0.0
|
||||
EOF
|
||||
|
||||
if [[ -n "$video_enabled" ]]; then
|
||||
runFileWrite -a "$app_dir/Revolt.toml" <<'EOF'
|
||||
|
||||
[features.limits.new_user]
|
||||
video_resolution = [1920, 1080]
|
||||
video_aspect_ratio = [0.3, 10]
|
||||
|
||||
[features.limits.default]
|
||||
video_resolution = [1920, 1080]
|
||||
video_aspect_ratio = [0.3, 10]
|
||||
EOF
|
||||
fi
|
||||
checkSuccess "Writing Revolt.toml (video=${video_enabled:-false})"
|
||||
|
||||
# use_external_ip lets LiveKit discover the address to advertise for WebRTC.
|
||||
# The port range matches the literal UDP mapping in the compose file; change
|
||||
# one and you must change the other.
|
||||
runFileWrite "$app_dir/livekit.yml" <<EOF
|
||||
rtc:
|
||||
use_external_ip: true
|
||||
port_range_start: 50000
|
||||
port_range_end: 50100
|
||||
tcp_port: 7881
|
||||
|
||||
redis:
|
||||
address: redis:6379
|
||||
|
||||
turn:
|
||||
enabled: false
|
||||
|
||||
keys:
|
||||
${livekit_key}: ${livekit_secret}
|
||||
|
||||
webhook:
|
||||
api_key: ${livekit_key}
|
||||
urls:
|
||||
- "http://voice-ingress:8500/worldwide"
|
||||
EOF
|
||||
checkSuccess "Writing livekit.yml"
|
||||
|
||||
result=$(copyResource "$app_name" "Caddyfile" "" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||
checkSuccess "Copying Caddyfile to $app_dir"
|
||||
|
||||
runFileOp chown -R "$docker_install_user":"$docker_install_user" "$app_dir"
|
||||
checkSuccess "Setting ownership on the $app_name install directory"
|
||||
}
|
||||
|
||||
stoat_install_post()
|
||||
{
|
||||
local app_name="$1"
|
||||
local domain
|
||||
domain=$(_stoatDomain "$app_name")
|
||||
|
||||
echo ""
|
||||
isNotice "Stoat first run:"
|
||||
echo ""
|
||||
echo " Open https://${domain} and create an account — the first account"
|
||||
echo " registered on a fresh instance becomes the instance owner."
|
||||
echo ""
|
||||
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"
|
||||
echo " shows where it has got to."
|
||||
echo ""
|
||||
echo " Voice falls back to TCP 7881, which is already open. For proper"
|
||||
echo " low-latency WebRTC from outside your LAN, also allow the UDP"
|
||||
echo " media range — LibrePortal's firewall layer only emits TCP rules,"
|
||||
echo " so this one is manual:"
|
||||
echo ""
|
||||
echo " sudo ufw allow 50000:50100/udp"
|
||||
echo ""
|
||||
}
|
||||
87
containers/stoat/stoat.config
Normal file
87
containers/stoat/stoat.config
Normal file
@ -0,0 +1,87 @@
|
||||
#
|
||||
# =============================================================================
|
||||
# GENERAL CONFIGURATION
|
||||
# =============================================================================
|
||||
# APP_NAME = name of application for use in scripts
|
||||
# REQUIRES = comma-separated install prerequisites (see scripts/checks/requirements/check_app_install.sh)
|
||||
# COMPOSE_FILE = default for no app_name in docker-compose file name, app if there is
|
||||
# BACKUP = if true, include this application in backup operations
|
||||
# UPDATE_TYPE = auto: new image builds are applied automatically (a recovery snapshot is taken first), manual: only when you press Update
|
||||
# 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)
|
||||
# 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"
|
||||
CFG_STOAT_BACKUP=true
|
||||
CFG_STOAT_BACKUP_STRATEGY=auto
|
||||
# Manual, deliberately. This is a sixteen-service stack whose components are
|
||||
# released together and expect matching versions; letting them roll forward
|
||||
# unattended and independently is how you end up with an API talking to an
|
||||
# incompatible events service.
|
||||
CFG_STOAT_UPDATE_TYPE=manual
|
||||
CFG_STOAT_COMPOSE_FILE=default
|
||||
CFG_STOAT_HEALTHCHECK=true
|
||||
# Stoat's own accounts back its clients, and /api must stay reachable without a
|
||||
# forward-auth redirect in the way.
|
||||
CFG_STOAT_AUTHELIA=false
|
||||
CFG_STOAT_HEADSCALE=false
|
||||
CFG_STOAT_ENABLE_VIDEO=true
|
||||
CFG_STOAT_MONITORING=false
|
||||
#
|
||||
# =============================================================================
|
||||
# METADATA
|
||||
# =============================================================================
|
||||
# CATEGORY = application category for grouping
|
||||
# TITLE = display name for the application
|
||||
# DESCRIPTION = short description of the application
|
||||
# LONG_DESCRIPTION = detailed description of the application
|
||||
# URL = source repository or documentation URL
|
||||
# ACTIONS = available actions for this application
|
||||
# REQUIRES_SERVICE = name of another LibrePortal app that must be installed before this one can be configured
|
||||
#
|
||||
CFG_STOAT_CATEGORY="communication"
|
||||
CFG_STOAT_TITLE="Stoat"
|
||||
CFG_STOAT_DESCRIPTION="Discord-style Chat"
|
||||
CFG_STOAT_LONG_DESCRIPTION="Stoat, formerly Revolt, is the open-source project that most closely reproduces Discord itself — servers, channels, roles, reactions, and voice and video through LiveKit. It is the heaviest app in this catalog by some distance: sixteen containers including MongoDB, Valkey, RabbitMQ and MinIO, so budget a couple of gigabytes of memory. Unlike Matrix it does not federate, so each instance is its own island"
|
||||
CFG_STOAT_URL="https://github.com/stoatchat/self-hosted"
|
||||
CFG_STOAT_ACTIONS="configure|install|restart|shutdown|uninstall"
|
||||
CFG_STOAT_REQUIRES_SERVICE=traefik
|
||||
#
|
||||
# =============================================================================
|
||||
# NETWORK CONFIGURATION
|
||||
# =============================================================================
|
||||
# DOMAIN = number of domain from the general config, useful when using multiple domains
|
||||
# WHITELIST = if true only allow whitelisted ips (see general config), if false allow all
|
||||
#
|
||||
CFG_STOAT_DOMAIN=1
|
||||
CFG_STOAT_WHITELIST=false
|
||||
CFG_STOAT_NETWORK=default
|
||||
#
|
||||
# =============================================================================
|
||||
# PORT CONFIGURATION
|
||||
# =============================================================================
|
||||
# PORT_ = port configuration: app|name|external:internal|access|protocol|login|traefik|webui|description
|
||||
# - app: application name
|
||||
# - name: service identifier (webui, dns, ssh, etc.)
|
||||
# - external:internal: port mapping (external can be 'random' for auto-allocation)
|
||||
# - access: 'public' (internet accessible), 'private' (local network only), 'disabled' (not running)
|
||||
# - protocol: 'tcp' or 'udp'
|
||||
# - login: if true, this port requires basic-auth via Traefik (only meaningful when traefik=true)
|
||||
# - traefik: if true, Traefik handles this port (reverse proxy)
|
||||
# - webui: if true, this port serves the main web interface
|
||||
# - description: human-readable description of the service
|
||||
#
|
||||
# Only one HTTP port: Caddy fronts the entire stack internally, so /api, /ws,
|
||||
# /autumn and the rest all arrive on this single host.
|
||||
CFG_STOAT_PORT_1="stoat-caddy|webui|random:80|public|tcp|false|true|true|Web Interface||stoat"
|
||||
# LiveKit's TCP fallback. Pinned rather than random on purpose: LiveKit
|
||||
# advertises this exact port number to clients from livekit.yml, so a randomised
|
||||
# external port would be advertised wrongly and voice would fail to connect.
|
||||
# Not Traefik-managed — WebRTC is not HTTP.
|
||||
CFG_STOAT_PORT_2="stoat-livekit|voice-tcp|7881:7881|public|tcp|false|false|false|LiveKit voice/video (TCP fallback)|"
|
||||
1
containers/stoat/stoat.svg
Normal file
1
containers/stoat/stoat.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256" role="img" aria-label="Stoat"><rect width="256" height="256" rx="28" fill="#ff4655" /><path fill="#fff" d="M56 64h56a52 52 0 0 1 0 104H92v34a6 6 0 0 1-6 6H62a6 6 0 0 1-6-6zm36 28v48h20a24 24 0 0 0 0-48z" /><circle fill="#fff" cx="176" cy="96" r="16" /><circle fill="#fff" cx="176" cy="152" r="16" /><circle fill="#fff" cx="176" cy="208" r="16" /></svg>
|
||||
|
After Width: | Height: | Size: 440 B |
@ -646,6 +646,11 @@ declare -gA LP_FN_MAP=(
|
||||
[manifestRemove]="backup/manifest/manifest_write.sh"
|
||||
[manifestWrite]="backup/manifest/manifest_write.sh"
|
||||
[mastodon_upgrade_verify]="mastodon/scripts/mastodon_upgrade_hooks.sh"
|
||||
[matrix_install_post]="matrix/scripts/matrix_install_hooks.sh"
|
||||
[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"
|
||||
[_matrixServerName]="matrix/scripts/matrix_install_hooks.sh"
|
||||
[mattermostToolsMenu]="menu/tools/manage_mattermost.sh"
|
||||
[maybeRegenPoll]="task/crontab_task_processor.sh"
|
||||
[menuContinue]="menu/message/continue.sh"
|
||||
@ -815,6 +820,8 @@ declare -gA LP_FN_MAP=(
|
||||
[restoreFirstRunBulk]="restore/restore_first_run.sh"
|
||||
[restoreFirstRunDiscover]="restore/restore_first_run.sh"
|
||||
[restorePickSnapshot]="restore/restore_app_pick.sh"
|
||||
[rocketchat_install_post]="rocketchat/scripts/rocketchat_install_hooks.sh"
|
||||
[rocketchat_install_post_start]="rocketchat/scripts/rocketchat_install_hooks.sh"
|
||||
[runAppCfg]="docker/command/run_privileged.sh"
|
||||
[runAsManager]="docker/command/run_privileged.sh"
|
||||
[runBackupOp]="docker/command/run_privileged.sh"
|
||||
@ -868,15 +875,20 @@ declare -gA LP_FN_MAP=(
|
||||
[stalwart_install_message_data]="stalwart/scripts/stalwart_install_hooks.sh"
|
||||
[stalwart_install_post_start]="stalwart/scripts/stalwart_install_hooks.sh"
|
||||
[stalwart_install_provision]="stalwart/scripts/stalwart_install_hooks.sh"
|
||||
[stalwart_wait_http]="stalwart/scripts/stalwart_install_hooks.sh"
|
||||
[stalwart_upgrade_admin_ui_code]="stalwart/scripts/stalwart_upgrade_hooks.sh"
|
||||
[stalwart_upgrade_check_admin_ui]="stalwart/scripts/stalwart_upgrade_hooks.sh"
|
||||
[stalwart_upgrade_verify]="stalwart/scripts/stalwart_upgrade_hooks.sh"
|
||||
[stalwart_wait_http]="stalwart/scripts/stalwart_install_hooks.sh"
|
||||
[startInstall]="start/start_install.sh"
|
||||
[startLoad]="start/start_load.sh"
|
||||
[startOther]="start/start_other.sh"
|
||||
[startPreInstall]="start/start_preinstall.sh"
|
||||
[startScan]="start/start_scan.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_pre]="stoat/scripts/stoat_install_hooks.sh"
|
||||
[_stoatWriteSecrets]="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"
|
||||
@ -1689,6 +1701,11 @@ declare -gA LP_FN_ROOT=(
|
||||
[manifestRemove]="scripts"
|
||||
[manifestWrite]="scripts"
|
||||
[mastodon_upgrade_verify]="containers"
|
||||
[matrix_install_post]="containers"
|
||||
[matrix_install_post_compose]="containers"
|
||||
[matrix_install_post_start]="containers"
|
||||
[matrix_install_pre]="containers"
|
||||
[_matrixServerName]="containers"
|
||||
[mattermostToolsMenu]="scripts"
|
||||
[maybeRegenPoll]="scripts"
|
||||
[menuContinue]="scripts"
|
||||
@ -1858,6 +1875,8 @@ declare -gA LP_FN_ROOT=(
|
||||
[restoreFirstRunBulk]="scripts"
|
||||
[restoreFirstRunDiscover]="scripts"
|
||||
[restorePickSnapshot]="scripts"
|
||||
[rocketchat_install_post]="containers"
|
||||
[rocketchat_install_post_start]="containers"
|
||||
[runAppCfg]="scripts"
|
||||
[runAsManager]="scripts"
|
||||
[runBackupOp]="scripts"
|
||||
@ -1911,15 +1930,20 @@ declare -gA LP_FN_ROOT=(
|
||||
[stalwart_install_message_data]="containers"
|
||||
[stalwart_install_post_start]="containers"
|
||||
[stalwart_install_provision]="containers"
|
||||
[stalwart_wait_http]="containers"
|
||||
[stalwart_upgrade_admin_ui_code]="containers"
|
||||
[stalwart_upgrade_check_admin_ui]="containers"
|
||||
[stalwart_upgrade_verify]="containers"
|
||||
[stalwart_wait_http]="containers"
|
||||
[startInstall]="scripts"
|
||||
[startLoad]="scripts"
|
||||
[startOther]="scripts"
|
||||
[startPreInstall]="scripts"
|
||||
[startScan]="scripts"
|
||||
[_stoatDomain]="containers"
|
||||
[stoat_install_post]="containers"
|
||||
[stoat_install_post_compose]="containers"
|
||||
[stoat_install_pre]="containers"
|
||||
[_stoatWriteSecrets]="containers"
|
||||
[stopCrowdsec]="containers"
|
||||
[switchMigrateBackupApps]="scripts"
|
||||
[switchMigrateRestoreApps]="scripts"
|
||||
@ -2766,6 +2790,11 @@ manifestReadFromSnapshot() { unset -f manifestReadFromSnapshot; __lpAutoload "${
|
||||
manifestRemove() { unset -f manifestRemove; __lpAutoload "${install_scripts_dir}backup/manifest/manifest_write.sh"; manifestRemove "$@"; }
|
||||
manifestWrite() { unset -f manifestWrite; __lpAutoload "${install_scripts_dir}backup/manifest/manifest_write.sh"; manifestWrite "$@"; }
|
||||
mastodon_upgrade_verify() { unset -f mastodon_upgrade_verify; __lpAutoload "${install_containers_dir}mastodon/scripts/mastodon_upgrade_hooks.sh"; mastodon_upgrade_verify "$@"; }
|
||||
matrix_install_post() { unset -f matrix_install_post; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_post "$@"; }
|
||||
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 "$@"; }
|
||||
_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 "$@"; }
|
||||
menuContinue() { unset -f menuContinue; __lpAutoload "${install_scripts_dir}menu/message/continue.sh"; menuContinue "$@"; }
|
||||
@ -2935,6 +2964,8 @@ restoreFilesRehydratePreStart() { unset -f restoreFilesRehydratePreStart; __lpAu
|
||||
restoreFirstRunBulk() { unset -f restoreFirstRunBulk; __lpAutoload "${install_scripts_dir}restore/restore_first_run.sh"; restoreFirstRunBulk "$@"; }
|
||||
restoreFirstRunDiscover() { unset -f restoreFirstRunDiscover; __lpAutoload "${install_scripts_dir}restore/restore_first_run.sh"; restoreFirstRunDiscover "$@"; }
|
||||
restorePickSnapshot() { unset -f restorePickSnapshot; __lpAutoload "${install_scripts_dir}restore/restore_app_pick.sh"; restorePickSnapshot "$@"; }
|
||||
rocketchat_install_post() { unset -f rocketchat_install_post; __lpAutoload "${install_containers_dir}rocketchat/scripts/rocketchat_install_hooks.sh"; rocketchat_install_post "$@"; }
|
||||
rocketchat_install_post_start() { unset -f rocketchat_install_post_start; __lpAutoload "${install_containers_dir}rocketchat/scripts/rocketchat_install_hooks.sh"; rocketchat_install_post_start "$@"; }
|
||||
runAppCfg() { unset -f runAppCfg; __lpAutoload "${install_scripts_dir}docker/command/run_privileged.sh"; runAppCfg "$@"; }
|
||||
runAsManager() { unset -f runAsManager; __lpAutoload "${install_scripts_dir}docker/command/run_privileged.sh"; runAsManager "$@"; }
|
||||
runBackupOp() { unset -f runBackupOp; __lpAutoload "${install_scripts_dir}docker/command/run_privileged.sh"; runBackupOp "$@"; }
|
||||
@ -2982,16 +3013,26 @@ setupWizardTerminal() { unset -f setupWizardTerminal; __lpAutoload "${install_sc
|
||||
showInstructions() { unset -f showInstructions; __lpAutoload "${install_scripts_dir}menu/message/instructions.sh"; showInstructions "$@"; }
|
||||
sourceBackupLocations() { unset -f sourceBackupLocations; __lpAutoload "${install_scripts_dir}backup/locations/location_loader.sh"; sourceBackupLocations "$@"; }
|
||||
sshRemote() { unset -f sshRemote; __lpAutoload "${install_scripts_dir}network/ssh/ssh.sh"; sshRemote "$@"; }
|
||||
stalwart_cli() { unset -f stalwart_cli; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_cli "$@"; }
|
||||
stalwart_install_dns_provider() { unset -f stalwart_install_dns_provider; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_install_dns_provider "$@"; }
|
||||
stalwart_install_first_mailbox() { unset -f stalwart_install_first_mailbox; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_install_first_mailbox "$@"; }
|
||||
stalwart_install_message_data() { unset -f stalwart_install_message_data; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_install_message_data "$@"; }
|
||||
stalwart_install_post_start() { unset -f stalwart_install_post_start; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_install_post_start "$@"; }
|
||||
stalwart_install_provision() { unset -f stalwart_install_provision; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_install_provision "$@"; }
|
||||
stalwart_upgrade_admin_ui_code() { unset -f stalwart_upgrade_admin_ui_code; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_upgrade_hooks.sh"; stalwart_upgrade_admin_ui_code "$@"; }
|
||||
stalwart_upgrade_check_admin_ui() { unset -f stalwart_upgrade_check_admin_ui; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_upgrade_hooks.sh"; stalwart_upgrade_check_admin_ui "$@"; }
|
||||
stalwart_upgrade_verify() { unset -f stalwart_upgrade_verify; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_upgrade_hooks.sh"; stalwart_upgrade_verify "$@"; }
|
||||
stalwart_wait_http() { unset -f stalwart_wait_http; __lpAutoload "${install_containers_dir}stalwart/scripts/stalwart_install_hooks.sh"; stalwart_wait_http "$@"; }
|
||||
startInstall() { unset -f startInstall; __lpAutoload "${install_scripts_dir}start/start_install.sh"; startInstall "$@"; }
|
||||
startLoad() { unset -f startLoad; __lpAutoload "${install_scripts_dir}start/start_load.sh"; startLoad "$@"; }
|
||||
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 "$@"; }
|
||||
_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_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 "$@"; }
|
||||
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 "$@"; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user