diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdb9524 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +exchange.md +matrix/ +others/ diff --git a/config/example.env b/config/example.env new file mode 100644 index 0000000..a23f87a --- /dev/null +++ b/config/example.env @@ -0,0 +1,3 @@ +# Example env file + +STOAT_DOMAIN=discord.nagato.com \ No newline at end of file diff --git a/config/example.env.web b/config/example.env.web new file mode 100644 index 0000000..db9e4b7 --- /dev/null +++ b/config/example.env.web @@ -0,0 +1,7 @@ +HOSTNAME=:80 +REVOLT_PUBLIC_URL=https://discord.nagato.com/api +VITE_API_URL=https://discord.nagato.com/api +VITE_WS_URL=wss://discord.nagato.com/ws +VITE_MEDIA_URL=https://discord.nagato.com/autumn +VITE_PROXY_URL=https://discord.nagato.com/january +VITE_CFG_ENABLE_VIDEO=true \ No newline at end of file diff --git a/config/livekit.yml b/config/livekit.yml new file mode 100644 index 0000000..59dbfb9 --- /dev/null +++ b/config/livekit.yml @@ -0,0 +1,20 @@ +rtc: + use_external_ip: true + port_range_start: 50000 + port_range_end: 50100 + tcp_port: 7881 + +redis: + address: redis:6379 + +turn: + enabled: false + +keys: + # 這裡要換成你 secret.env 裡面的 LIVEKIT_NODES_WORLDWIDE_KEY 和 SECRET + "YOUR_LIVEKIT_KEY": "YOUR_LIVEKIT_SECRET" + +webhook: + api_key: "YOUR_LIVEKIT_KEY" + urls: + - "http://voice-ingress:8500/worldwide" \ No newline at end of file diff --git a/config/secret.env b/config/secret.env new file mode 100644 index 0000000..fc9f3eb --- /dev/null +++ b/config/secret.env @@ -0,0 +1,7 @@ +REVOLT__PUSHD__VAPID__PRIVATE_KEY='[生成的base64編碼密鑰]' +REVOLT__PUSHD__VAPID__PUBLIC_KEY='[生成的base64編碼密鑰]' + +REVOLT__FILES__ENCRYPTION_KEY='[生成的base64 32字元]' + +REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET='[生成的hex 48字元]' +REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY='[生成的hex 12字元]' \ No newline at end of file diff --git a/config/stoat.toml b/config/stoat.toml new file mode 100644 index 0000000..28ca916 --- /dev/null +++ b/config/stoat.toml @@ -0,0 +1,26 @@ +# stoat version of nagato server +# Sources: https://github.com/stoatchat/self-hosted + +[hosts] +app = "https://discord.nagato.com" +api = "https://discord.nagato.com/api" +events = "wss://discord.nagato.com/ws" +autumn = "https://discord.nagato.com/autumn" +january = "https://discord.nagato.com/january" + +[hosts.livekit] +worldwide = "wss://discord.nagato.com/livekit" + +[api.livekit.nodes.worldwide] +url = "http://livekit:7880" +lat = 0.0 +lon = 0.0 + +# (如果啟用視頻功能) +[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] \ No newline at end of file diff --git a/others/generate_config.sh b/others/generate_config.sh new file mode 100644 index 0000000..06fe898 --- /dev/null +++ b/others/generate_config.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash + +SECRETS_FOUND=0 +IS_OVERWRITING=0 +DOMAIN= +VIDEO_ENABLED= + +usage() { + echo "Usage: ./generate_config.sh [--overwrite] DOMAIN" + exit 1 +} + +loadSecrets() { + SECRETS_FOUND=1 + set -a && source secrets.env && set +a +} + +# Check args to ensure correct usage +# No args is not valid +if [[ $# -eq 1 ]]; then + if [[ $1 = --* ]]; then + usage + fi + DOMAIN=$1 +elif [[ $# -eq 2 ]]; then + if [[ $1 != --overwrite ]]; then + usage + fi + if [[ $2 = --* ]]; then + usage + fi + DOMAIN=$2 + IS_OVERWRITING=1 +else + usage +fi + +if test -f "secrets.env"; then + loadSecrets +fi + +if test -f "Revolt.toml"; then + if [[ $IS_OVERWRITING -eq 1 ]]; then + if [ "$SECRETS_FOUND" -eq "0" ]; then + echo "Overwrite flag passed, but secrets.env not found. This script will refuse to execute an overwrite without secrets.env." + echo "If you are absolutely sure you want to overwrite your secrets with new secrets, copy the secrets.env.example file without modifying it's contents using command 'cp secrets.env.example secrets.env'." + echo "If you do not copy your existing secrets into secrets.env you WILL lose access to ALL of your files store in your Stoat instance." + exit 1 + fi + echo "Overwriting existing config." + echo "Renaming Revolt.toml to Revolt.toml.old" + mv Revolt.toml Revolt.toml.old + echo "Renaming livekit.yml to livekit.yml.old" + mv livekit.yml livekit.yml.old + echo "Renaming compose.override.yml to compose.override.yml.old" + mv compose.override.yml compose.override.yml.old + else + echo "Existing config found, in caution, this script will refuse to execute if you have existing config." + if [ "$SECRETS_FOUND" -eq "0" ]; then + echo "Please configure secrets.env with your existing secrets to prevent losing access to your saved files in your Stoat instance. You can see instructions on how to configure it by reading the file secrets.env.example. You can do this by running the command 'cat secrets.env.example'." + echo "Overwriting your existing config will result in you losing access to all current files stored on your Stoat instance unless you copy your old secrets into secrets.env." + else + echo "secrets.env found, please ensure it matches what is currently in your Revolt.toml." + fi + echo "This script will back up your old config if you choose to overwrite." + echo "To overwrite the existing config, run the script again with the --overwrite flag" + usage + fi +fi + +if [ "$SECRETS_FOUND" -eq "0" ]; then + cp secrets.env.example secrets.env + loadSecrets +else + echo "Checking if secrets file needs to be updated..." + if [ "$PUSHD_VAPID_PRIVATEKEY" != "" ] || [ "$PUSHD_VAPID_PUBLICKEY" != "" ] || [ "$FILES_ENCRYPTION_KEY" != "" ] || [ "$LIVEKIT_WORLDWIDE_SECRET" != "" ] || [ "$LIVEKIT_WORLDWIDE_KEY" != "" ]; then + echo "Old secrets found. Your secrets will be rewritten in the new format. If you have any custom secrets not managed by this file, you will need to convert them to the new format." + echo "See https://github.com/stoatchat/stoatchat/pull/576" + echo "Renaming secrets.env to secrets.env.old" + mv secrets.env secrets.env.old + echo "Copying old secrets to new format..." + cp secrets.env.example secrets.env + printf "REVOLT__PUSHD__VAPID__PRIVATE_KEY='%s'\n" $PUSHD_VAPID_PRIVATEKEY >> secrets.env + printf "REVOLT__PUSHD__VAPID__PUBLIC_KEY='%s'\n" $PUSHD_VAPID_PUBLICKEY >> secrets.env + echo "" >> secrets.env + printf "REVOLT__FILES__ENCRYPTION_KEY='%s'\n" $FILES_ENCRYPTION_KEY >> secrets.env + echo "" >> secrets.env + printf "REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET='%s'\n" $LIVEKIT_WORLDWIDE_SECRET >> secrets.env + printf "REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY='%s'\n" $LIVEKIT_WORLDWIDE_KEY >> secrets.env + loadSecrets + fi +fi + +echo "Configuring Stoat with hostname $DOMAIN" + +STOAT_HOSTNAME="https://$DOMAIN" + +read -rp "Would you like to place Stoat behind another reverse proxy? [y/N]: " +if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then + echo "Yes received. Configuring for reverse proxy." + STOAT_HOSTNAME=':80' + echo "Writing compose.override.yml..." + echo "services:" > compose.override.yml + echo " caddy:" >> compose.override.yml + echo " ports: !override" >> compose.override.yml + echo " - \"8880:80\"" >> compose.override.yml + echo "caddy is configured to host on :8880. If you need a different port, modify the compose.override.yml." + echo "STOAT_DOMAIN=" > .env +else + echo "No received. Configuring with built in caddy as primary reverse proxy." + echo "STOAT_DOMAIN=$DOMAIN" > .env +fi + +read -rp "Would you like to enable camera and screen sharing? [Y/n]: " +if [ "$REPLY" = "n" ] || [ "$REPLY" = "N" ]; then + echo "No received. Not configuring video." +else + echo "Yes received. Configuring video." + VIDEO_ENABLED=true +fi + +# Generate secrets +echo "Generating secrets..." +if [ "$REVOLT__PUSHD__VAPID__PRIVATE_KEY" = "" ]; then + if [ "$REVOLT__PUSHD__VAPID__PUBLIC_KEY" != "" ]; then + echo "VAPID public key is defined when private key isn't?" + echo "Did you forget to copy the REVOLT__PUSHD__VAPID__PRIVATE_KEY secret?" + echo "Try removing REVOLT__PUSHD__VAPID__PUBLIC_KEY if you do not have a private key." + exit 1 + fi + echo "Generating Pushd VAPID secrets..." + openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem + REVOLT__PUSHD__VAPID__PRIVATE_KEY=$(base64 -i vapid_private.pem | tr -d '\n' | tr -d '=') + REVOLT__PUSHD__VAPID__PUBLIC_KEY=$(openssl ec -in vapid_private.pem -outform DER|tail --bytes 65|base64|tr '/+' '_-'|tr -d '\n'|tr -d '=') + rm vapid_private.pem + echo "" >> secrets.env + printf "REVOLT__PUSHD__VAPID__PRIVATE_KEY='%s'\n" $REVOLT__PUSHD__VAPID__PRIVATE_KEY >> secrets.env + printf "REVOLT__PUSHD__VAPID__PUBLIC_KEY='%s'\n" $REVOLT__PUSHD__VAPID__PUBLIC_KEY >> secrets.env +elif [ "$REVOLT__PUSHD__VAPID__PUBLIC_KEY" = "" ]; then + echo "VAPID private key is defined when public key isn't?" + echo "Did you forget to copy the REVOLT__PUSHD__VAPID__PUBLIC_KEY secret?" + echo "Try removing REVOLT__PUSHD__VAPID__PRIVATE_KEY if you do not have a public key." + exit 1 +else + echo "Using old Pushd VAPID secrets..." +fi + +if [ "$REVOLT__FILES__ENCRYPTION_KEY" = "" ]; then + echo "Generating files encryption secret..." + REVOLT__FILES__ENCRYPTION_KEY=$(openssl rand -base64 32) + echo "" >> secrets.env + printf "REVOLT__FILES__ENCRYPTION_KEY='%s'\n" $REVOLT__FILES__ENCRYPTION_KEY >> secrets.env +else + echo "Using old files encryption secret..." +fi + +if [ "$REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET" = "" ]; then + if [ "$REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY" != "" ]; then + echo "Livekit public key is defined when secret isn't?" + echo "Did you forget to copy the REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET secret?" + echo "Try removing REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY if you do not have a secret." + exit 1 + fi + echo "Generating Livekit secrets..." + REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET=$(openssl rand -hex 24) + REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY=$(openssl rand -hex 6) + echo "" >> secrets.env + printf "REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET='%s'\n" $REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET >> secrets.env + printf "REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY='%s'\n" $REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY >> secrets.env +elif [ "$REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY" = "" ]; then + echo "Livekit secret is defined when public key isn't?" + echo "Did you forget to copy the REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY secret?" + echo "Try removing REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET if you do not have a public key." + exit 1 +else + echo "Using old Livekit secrets..." +fi + +# set hostname for Caddy and vite variables +echo "HOSTNAME=$STOAT_HOSTNAME" > .env.web +echo "REVOLT_PUBLIC_URL=https://$DOMAIN/api" >> .env.web +echo "VITE_API_URL=https://$DOMAIN/api" >> .env.web +echo "VITE_WS_URL=wss://$DOMAIN/ws" >> .env.web +echo "VITE_MEDIA_URL=https://$DOMAIN/autumn" >> .env.web +echo "VITE_PROXY_URL=https://$DOMAIN/january" >> .env.web +echo "VITE_CFG_ENABLE_VIDEO=$VIDEO_ENABLED" >> .env.web + +# hostnames +echo "# All secrets are stored in secrets.env" > Revolt.toml +echo "# Any configuration added to this file will be overwritten by generate_config on run; however," >> Revolt.toml +echo "# the script will back up your old configuration so you can copy over your old configuration" >> Revolt.toml +echo "# values if needed." >> Revolt.toml +echo "[hosts]" >> Revolt.toml +echo "app = \"https://$DOMAIN\"" >> Revolt.toml +echo "api = \"https://$DOMAIN/api\"" >> Revolt.toml +echo "events = \"wss://$DOMAIN/ws\"" >> Revolt.toml +echo "autumn = \"https://$DOMAIN/autumn\"" >> Revolt.toml +echo "january = \"https://$DOMAIN/january\"" >> Revolt.toml + +# livekit hostname +echo "" >> Revolt.toml +echo "[hosts.livekit]" >> Revolt.toml +echo "worldwide = \"wss://$DOMAIN/livekit\"" >> Revolt.toml + +# livekit yml +echo "rtc:" > livekit.yml +echo " use_external_ip: true" >> livekit.yml +echo " port_range_start: 50000" >> livekit.yml +echo " port_range_end: 50100" >> livekit.yml +echo " tcp_port: 7881" >> livekit.yml +echo "" >> livekit.yml +echo "redis:" >> livekit.yml +echo " address: redis:6379" >> livekit.yml +echo "" >> livekit.yml +echo "turn:" >> livekit.yml +echo " enabled: false" >> livekit.yml +echo "" >> livekit.yml +echo "keys:" >> livekit.yml +echo " $REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY: $REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET" >> livekit.yml +echo "" >> livekit.yml +echo "webhook:" >> livekit.yml +echo " api_key: $REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY" >> livekit.yml +echo " urls:" >> livekit.yml +echo " - \"http://voice-ingress:8500/worldwide\"" >> livekit.yml + +# livekit config +echo "" >> Revolt.toml +echo "[api.livekit.nodes.worldwide]" >> Revolt.toml +echo "url = \"http://livekit:7880\"" >> Revolt.toml +echo "lat = 0.0" >> Revolt.toml +echo "lon = 0.0" >> Revolt.toml + +# Video config +# We'll enable 1080p video by default, that should be high enough for most users. +if [[ -n "$VIDEO_ENABLED" ]]; then + echo "" >> Revolt.toml + echo "[features.limits.new_user]" >> Revolt.toml + echo "video_resolution = [1920, 1080]" >> Revolt.toml + echo "video_aspect_ratio = [0.3, 10]" >> Revolt.toml + echo "" >> Revolt.toml + echo "[features.limits.default]" >> Revolt.toml + echo "video_resolution = [1920, 1080]" >> Revolt.toml + echo "video_aspect_ratio = [0.3, 10]" >> Revolt.toml +fi + +if [[ $IS_OVERWRITING -eq 1 ]]; then + echo "Overwrote existing config. If any custom configuration was present in old Revolt.toml, you may now copy it over from Revolt.toml.old." +fi \ No newline at end of file diff --git a/others/secrets.env.example b/others/secrets.env.example new file mode 100644 index 0000000..38dc51b --- /dev/null +++ b/others/secrets.env.example @@ -0,0 +1,45 @@ +# DO NOT EDIT secrets.env.example - copy it to secrets.env then edit it as +# instructed. +# +# This file is for storing secrets for your Stoat instance safely. To use the +# secrets file, copy secrets.env.example to secrets.env with the command: +# cp secrets.env.example secrets.env +# +# Secrets must be stored in .env format, and will never be overwritten by the +# generate_config.sh script. The generate_config.sh script will first check +# secrets.env for secrets, and if a secret exists it will use that secret. If +# it does not exist, it will generate a new one and write it to secrets.env. If +# generate_config.sh creates new secrets, they will be added to the end of the +# file. +# +# You should only need to modify secrets.env manually once, then after that +# generate_config.sh will manage it. If you need to add a secret, uncomment the +# line the secret is on by removing the # and the space before the name of the +# secret and paste the secret after the equals sign, with single quote marks +# surrounding the secret. Example secrets are provided to demonstrate. +# +# This is an example secret +VALID_SECRET_EXAMPLE='example_secret' +# +# This is also an example secret +VALID_SECRET_EXAMPLE_2='This is an example secret' +# +# Pushd VAPID private key is the value stored in the [pushd.vapid] section of +# Revolt.toml for the private_key line. +# REVOLT__PUSHD__VAPID__PRIVATE_KEY= +# +# Pushd VAPID public key is the value stored in the [pushd.vapid] section of +# Revolt.toml for the public_key line. +# REVOLT__PUSHD__VAPID__PUBLIC_KEY= +# +# Files encryption key is the value stored in the [files] section of +# Revolt.toml for the encryption_key line. +# REVOLT__FILES__ENCRYPTION_KEY= +# +# Livekit worldwide key is the value stored in the +# [api.livekit.nodes.worldwide] section of Revolt.toml for the key line. +# REVOLT__API__LIVEKIT__NODES__WORLDWIDE__KEY= +# +# Livekit worldwide secret is the value stored in the +# [api.livekit.nodes.worldwide] section of Revolt.toml for the secret line. +# REVOLT__API__LIVEKIT__NODES__WORLDWIDE__SECRET= diff --git a/proxy.yml b/proxy.yml new file mode 100644 index 0000000..931c869 --- /dev/null +++ b/proxy.yml @@ -0,0 +1,26 @@ +version: "3.8" + +services: + # Nginx Proxy Manager: 用於替代 Caddy,提供具備圖形化介面 (Web UI) 的反向代理與自動 SSL 憑證管理 + npm: + image: jc21/nginx-proxy-manager:latest + container_name: nginx-proxy-manager + restart: unless-stopped + ports: + - "80:80" # HTTP 對外連線埠 + - "443:443" # HTTPS 對外連線埠 + - "81:81" # NPM 的網頁管理後台埠 (建議正式上線後可用防火牆鎖定,或不要對外部網路開放) + # environment: + # DB_SQLITE_FILE: "/data/database.sqlite" # 使用預設的 SQLite 資料庫(輕量化,適合單節點) + volumes: + - ./data/npm/data:/data # 儲存設定檔與資料庫 + - ./data/npm/letsencrypt:/etc/letsencrypt # 儲存 Let's Encrypt 自動申請的 SSL 憑證 + networks: + - npm_network + +networks: + npm_network: + name: npm_network + driver: bridge + + diff --git a/stoat-init.yml b/stoat-init.yml new file mode 100644 index 0000000..6cd72bf --- /dev/null +++ b/stoat-init.yml @@ -0,0 +1,33 @@ +version: "3.8" + +services: + # 臨時性 MinIO:用來在主伺服器啟動前,事先初始化 Bucket 對應的實體資料夾 + # 它的 volumes 掛載路徑必須與主設定檔 (stoat.yml) 完全相同 + minio: + image: docker.io/minio/minio + container_name: revolt-minio-init + command: server /data + volumes: + - ./data/minio:/data + environment: + MINIO_ROOT_USER: minioautumn + MINIO_ROOT_PASSWORD: minioautumn + + # CreateBuckets: 開機一次性初始化腳本 + # 執行完畢後,這個容器會自動停止 (restart: "no") + createbuckets: + image: docker.io/minio/mc + container_name: revolt-createbuckets + restart: "no" + depends_on: + - minio + entrypoint: > + /bin/sh -c " + while ! /usr/bin/mc ready minio; do + /usr/bin/mc alias set minio http://minio:9000 minioautumn minioautumn; + echo 'Waiting minio...' && sleep 1; + done; + /usr/bin/mc mb minio/revolt-uploads; + echo 'Bucket revolt-uploads created successfully. Exiting.'; + exit 0; + " \ No newline at end of file diff --git a/stoat.yml b/stoat.yml new file mode 100644 index 0000000..eb26c2d --- /dev/null +++ b/stoat.yml @@ -0,0 +1,261 @@ +version: "3.8" + +services: + # MongoDB: 資料庫 (負責儲存使用者帳號、頻道結構、訊息歷史等核心資料) + database: + image: docker.io/mongo:latest + container_name: revolt-database + restart: always + volumes: + - ./data/db:/data/db + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + interval: 10s + timeout: 10s + retries: 5 + start_period: 10s + networks: + - stoat_network + + # Redis: 高速快取與狀態暫存 (負責存放連線會話、上線狀態等即時性資料) + redis: + image: docker.io/eqalpha/keydb + container_name: revolt-redis + restart: always + networks: + - stoat_network + + # RabbitMQ: 內部事件通訊與任務佇列 (新版效能核心,負責跨微服務的訊息排隊與分發) + rabbit: + image: docker.io/rabbitmq:4 + container_name: revolt-rabbit + restart: always + environment: + RABBITMQ_DEFAULT_USER: rabbituser + RABBITMQ_DEFAULT_PASS: rabbitpass + volumes: + - ./data/rabbit:/var/lib/rabbitmq + healthcheck: + test: rabbitmq-diagnostics -q ping + interval: 10s + timeout: 10s + retries: 3 + start_period: 20s + networks: + - stoat_network + + # MinIO: 私有物件儲存伺服器 (取代 AWS S3,用來存放圖片、附件、頭像等實體檔案) + minio: + image: docker.io/minio/minio + container_name: revolt-minio + command: server /data + volumes: + - ./data/minio:/data + environment: + MINIO_ROOT_USER: minioautumn + MINIO_ROOT_PASSWORD: minioautumn + MINIO_DOMAIN: minio + networks: + stoat_network: + aliases: + - revolt-uploads.minio + - attachments.minio + - avatars.minio + - backgrounds.minio + - icons.minio + - banners.minio + - emojis.minio + restart: always + + # -- 以下為 Stoat (Revolt) 官方新版微服務群 -- + + # API: 核心後端 API 伺服器 (處理註冊登入、建立頻道、發送訊息等一般 HTTP 請求) + api: + image: ghcr.io/stoatchat/api:v0.12.0 + container_name: revolt-api + env_file: .env + # 測試用:(API 預設為 8000) 若 NPM 無法內網連通,可取消註解對外曝露 + # ports: + # - "10081:8000" + networks: + - stoat_network + - npm_network + depends_on: + database: + condition: service_healthy + redis: + condition: service_started + rabbit: + condition: service_healthy + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # Events: WebSocket 即時通訊伺服器 (負責保持客戶端長連線,推送無延遲的即時新訊息) + events: + image: ghcr.io/stoatchat/events:v0.12.0 + container_name: revolt-events + env_file: .env + # 測試用:(WebSocket Events 預設為 9000) + # ports: + # - "10082:9000" + networks: + - stoat_network + - npm_network + depends_on: + database: + condition: service_healthy + redis: + condition: service_started + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # Autumn: 檔案處理伺服器 (作為前端與 MinIO 之間的橋樑,負責處理圖片上傳與下載) + autumn: + image: ghcr.io/stoatchat/file-server:v0.12.0 + container_name: revolt-autumn + env_file: .env + # 測試用:(Autumn 儲存服務預設為 3000) + # ports: + # - "10083:3000" + networks: + - stoat_network + - npm_network + depends_on: + database: + condition: service_healthy + # 注意:因安全與架構考量,createbuckets 已被抽離至 stoat-init.yml + # 請在佈署 Stoat主從集之前,先到 `stoat-init` stack 執行一次初始化,否則上傳檔案會失敗 + minio: + condition: service_started + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # January: 嵌入中繼資料代理爬蟲 (抓取 YouTube/Twitter 等連結的縮圖和標題顯示在聊天室) + january: + image: ghcr.io/stoatchat/proxy:v0.12.0 + container_name: revolt-january + env_file: .env + networks: + - stoat_network + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # Gifbox: Tenor GIF 代理伺服器 (讓使用者能在聊天室中搜尋並發送 GIF 動圖) + gifbox: + image: ghcr.io/stoatchat/gifbox:v0.12.0 + container_name: revolt-gifbox + env_file: .env + networks: + - stoat_network + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # Crond: 定期排程背景任務 (自動清理過期資料或進行系統維護作業) + crond: + image: ghcr.io/stoatchat/crond:v0.12.0 + container_name: revolt-crond + env_file: .env + networks: + - stoat_network + depends_on: + database: + condition: service_healthy + minio: + condition: service_started + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # Pushd: 系統推播通知伺服器 (負責向手機 App 或瀏覽器發送離線 Push Notification) + pushd: + image: ghcr.io/stoatchat/pushd:v0.12.0 + container_name: revolt-pushd + env_file: .env + networks: + - stoat_network + depends_on: + database: + condition: service_healthy + redis: + condition: service_started + rabbit: + condition: service_healthy + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + restart: always + + # Voice Ingress: 語音通訊邏輯入口 (與前端溝通並協調 LiveKit 開啟語音頻道的控制服務) + voice-ingress: + image: ghcr.io/stoatchat/voice-ingress:v0.12.0 + container_name: revolt-voice-ingress + env_file: .env + restart: always + networks: + - stoat_network + depends_on: + database: + condition: service_healthy + rabbit: + condition: service_healthy + volumes: + - type: bind + source: ./Revolt.toml + target: /Revolt.toml + + # LiveKit: 開源 WebRTC 串流伺服器 (負責真正處理多人語音通話與螢幕畫面分享的封包傳遞) + livekit: + image: ghcr.io/stoatchat/livekit-server:v1.9.13 + container_name: revolt-livekit + depends_on: + redis: + condition: service_started + networks: + - stoat_network + command: --config /etc/livekit.yml + ports: # 語音 UDP 穿透必備 + - "7881:7881" + - "50000-50100:50000-50100/udp" + restart: always + volumes: + - type: bind + source: ./livekit.yml + target: /etc/livekit.yml + + # Web: Stoat 前端網頁用戶端 (使用者在瀏覽器看到並操作的 Discord-like 介面) + web: + image: ghcr.io/stoatchat/for-web:3a83b8c + container_name: revolt-web + restart: always + env_file: .env + # 測試用:(Web 前端介面預設為 5000) + # ports: + # - "10080:5000" + networks: + - stoat_network + - npm_network + +networks: + stoat_network: + driver: bridge + npm_network: + external: true + name: npm_network # 修改為你 NPM 實際使用的 Docker 網路名稱 \ No newline at end of file