From ccb3ef79e06bb01b12bfffed886d97a8fe300514 Mon Sep 17 00:00:00 2001 From: Jacob Schlecht Date: Sun, 1 Mar 2026 11:09:41 -0700 Subject: [PATCH] feat: Smarter configuration script, add secrets env file, and improve docs (#248) * feat: Add a smarter configurator with secrets env file This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht * fix: rename compose.override.yml on overwrite This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht * docs: Update readme to instruct on secrets.env, and add more bookmarks This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht * docs: Update readme to be a bit more brief, remove some notices. This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht --------- Signed-off-by: Jacob Schlecht --- .gitignore | 2 + README.md | 85 ++++++++++++------------- generate_config.sh | 151 ++++++++++++++++++++++++++++++++++++++------ secrets.env.example | 45 +++++++++++++ 4 files changed, 217 insertions(+), 66 deletions(-) create mode 100644 secrets.env.example diff --git a/.gitignore b/.gitignore index 876289e..34a3aed 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,7 @@ Revolt.toml livekit.yml Revolt.toml.old livekit.yml.old +secrets.env compose.override.yml +compose.override.yml.old diff --git a/README.md b/README.md index 3c8e0e0..4752d96 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Self-hosting Stoat using Docker This repository contains configurations and instructions that can be used for deploying a full instance of Stoat, including the back-end, web front-end, file server, and metadata and image proxy. > [!WARNING] -> If you are updating an instance from before February 20, 2026, please consult the [notices section](#notices) at the bottom. +> If you are updating an instance from before February 28, 2026, please consult the [notices section](#notices) at the bottom. > [!IMPORTANT] > A list of security advisories is [provided at the bottom](#security-advisories). @@ -27,6 +27,10 @@ This repository contains configurations and instructions that can be used for de ## Table of Contents - [Deployment](#deployment) + - [Secure your server](#secure-your-server) + - [Configure your domain](#configure-your-domain) + - [Install Required Dependencies](#install-required-dependencies) +- [Configuring](#configuring) - [Updating](#updating) - [Additional Notes](#additional-notes) - [Placing Behind Another Reverse-Proxy or Another Port](#placing-behind-another-reverse-proxy-or-another-port) @@ -85,7 +89,7 @@ ssh root@ ssh root@ -i path/to/id_rsa ``` -And now we can proceed with some basic configuration and securing the system: +### Securing your server ```bash # update the system @@ -109,7 +113,9 @@ reboot ``` > [!NOTE] -> If you are using another cloud provider, or you are doing this on a physical machine, you will need to forwards ports 80, 443, 7881 and 50000-50100/udp. +> If you are using another cloud provider, or you are doing this on a physical machine, you will need to forward ports 80, 443, 7881 and 50000-50100/udp. + +### Configuring your domain Your system is now ready to proceed with installation, but before we continue, you should configure your domain. @@ -117,7 +123,7 @@ Your system is now ready to proceed with installation, but before we continue, y Your domain (or a subdomain) should point to the server's IP (A and AAAA records) or CNAME to the hostname provided. -Next, we must install the required dependencies: +### Install required dependencies ```bash # ensure Git and Docker are installed @@ -136,6 +142,8 @@ apt-get update apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` +## Configuration + Now, we can pull in the configuration for Stoat: ```bash @@ -150,6 +158,8 @@ chmod +x ./generate_config.sh ./generate_config.sh your.domain ``` +The generate_config.sh script will create the neccessary secrets required to create a Stoat instance, and the secrets will be inserted into a file named `secrets.env`. You should back up this file, as losing it may result in you losing access to all files on your Stoat instance. + You can find [more options here](https://github.com/stoatchat/stoatchat/blob/main/crates/core/config/Revolt.toml), some noteworthy configuration options: - Email verification @@ -185,7 +195,13 @@ Pull the latest version of this repository: git pull ``` -Check if your configuration file is correct by opening [the reference config file](https://github.com/stoatchat/stoatchat/blob/main/crates/core/config/Revolt.toml) and your `Revolt.toml` to compare changes. +Ensure that your secrets in `Revolt.toml` and `secrets.env` match. If your secrets don't match, copy the secrets from `Revolt.toml` to `secrets.env`. The following step will **overwrite** your existing configuration. If you have custom configuration settings you will need to copy them over afterwards. Alternatively, you can forgo running the configurator, but you may miss out on new features. + +Run the configuration script with your domain and pass the overwrite flag: + +```bash +./generate_config.sh your.domain --overwrite +``` Then pull all the latest images: @@ -203,31 +219,7 @@ docker compose up -d ### Placing Behind Another Reverse-Proxy or Another Port -If you'd like to place Stoat behind another reverse proxy or on a non-standard port, you'll need to edit `compose.yml`. - -Override the port definitions on `caddy`: - -```yml -# compose.yml -services: - caddy: - ports: - - "1234:80" - # - "443:443" -``` - -> [!WARNING] -> This file is not included in `.gitignore`. It may be sufficient to use an override file, but that will not remove port `80` / `443` allocations. - -Update the hostname used by the web server: - -```diff -# .env.web -- HOSTNAME=http://example.com -+ HOSTNAME=:80 -``` - -You can now reverse proxy to . +During configuration using `generate_config.sh` you will be asked if you'd like to place Stoat behind another reverse proxy. Enter `y` to configure for reverse proxy. This will expose your caddy on port 8880, and you can reverse proxy to > [!NOTE] > If you are using nginx as your reverse proxy, you will need to add the upgrade header configuration to allow websockets on /ws and /livekit, which are required for Stoat. @@ -304,13 +296,13 @@ services: ### KeyDB Compatibility -Some systems may not support the latest KeyDB version; you may pin to KeyDB 6.3.3 as such: +Some systems (including ARM systems) may not support the latest KeyDB version; you may use redis or valkey instead as such: ```yml # compose.override.yml services: redis: - image: docker.io/eqalpha/keydb:v6.3.3 + image: valkey/valkey:8 ``` ### Making Your Instance Invite-only @@ -319,7 +311,7 @@ Add the following section to your `Revolt.toml` file: ```toml [api.registration] # Whether an invite should be required for registration -# See https://github.com/revoltchat/self-hosted#making-your-instance-invite-only +# See https://github.com/stoatchat/self-hosted#making-your-instance-invite-only invite_only = true ``` @@ -336,6 +328,9 @@ db.invites.insertOne({ _id: "enter_an_invite_code_here" }) ## Notices +
+If you deployed Stoat before October 5, 2025... + > [!IMPORTANT] > If you deployed Stoat before [2022-10-29](https://github.com/minio/docs/issues/624#issuecomment-1296608406), you may have to tag the `minio` image release if it's configured in "fs" mode. > @@ -407,28 +402,26 @@ db.invites.insertOne({ _id: "enter_an_invite_code_here" }) > These will NOT automatically be applied to your environment. > > You must run the environment with the old revolt name to apply the update. After you run `docker compose pull` during the upgrade procedure, you must run `docker compose -p revolt down`. You may then continue with the upgrade procedure. +
+ +
> [!IMPORTANT] -> As of February 18, 2026, livekit support and the new web app was added to the self host repo. In order to utilize the new voice features and the new web app, you must add a valid livekit configuration. +> As of February 28, 2026, the configuration script will load secrets into `secrets.env`. You must copy your existing secrets into secrets.env to prevent `generate_config.sh` from overwriting your secrets. If your secrets are overwritten you will lose access to all files on your Stoat instance. > -> Before beginning the upgrade process, please do the following: +> Copy secrets.env.example to secrets.env > > ```bash -> git pull -> chmod +x migrations/20260218-voice-config.sh -> ./migrations/20260218-voice-config.sh your.domain +> cp secrets.env.example secrets.env > ``` > -> This should append the new configurations to your existing configuration. Only run this migration once, as if you run it more than once your instance will fail to start. You may then continue with the upgrade procedure. - -> [!IMPORTANT] -> As of February 20, 2026, there was an error in the `generate_config.sh` script. Please apply the following changes to your configuration: +> Begin the process of copying your secrets to secrets.env. You can view where each secret is located by reading the `secrets.env` file. Open the file with micro and read the instructions. > -> In `Revolt.toml`, under the section `[api.livekit.nodes.worldwide]`, change the url value to `http://livekit:7880` +> ```bash +> micro secrets.env +> ``` > -> In `livekit.yml`, under the section `webhook`, change the first line under `url` to `http://voice-ingress:8500/worldwide` -> -> Please note that these say `http` and not `https`. That is intentional. +> All of your secrets can be found in `Revolt.toml` and should be copied to your `secrets.env` file. After all 5 secrets are copied over, you are safe to run `generate_config.sh` to get new configuration options. ## Security Advisories diff --git a/generate_config.sh b/generate_config.sh index ce27c66..b3dcedd 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -1,17 +1,128 @@ #!/usr/bin/env bash +SECRETS_FOUND=0 +IS_OVERWRITING=0 + +loadSecrets() { + SECRETS_FOUND=1 + set -a && source secrets.env && set +a +} + +if test -f "secrets.env"; then + loadSecrets +fi + if test -f "Revolt.toml"; then - echo "Existing config found, running this script will overwrite your existing config and make your previously uploaded files innaccesible. Are you sure you'd like to reconfigure?" - select yn in "Yes" "No"; do - case $yn in - No ) exit;; - Yes ) mv Revolt.toml Revolt.toml.old && mv livekit.yml livekit.yml.old && break;; - esac - done + if [[ \ $*\ = *\ --overwrite\ * ]]; then + IS_OVERWRITING=1 + 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" + echo "$0 $* --overwrite" + exit 1 + fi +fi + +if [ "$SECRETS_FOUND" -eq "0" ]; then + cp secrets.env.example secrets.env + loadSecrets +fi + +STOAT_HOSTNAME="https://$1" + +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." +else + echo "No received. Configuring with built in caddy as primary reverse proxy." +fi + +# Generate secrets +echo "Generating secrets..." +if [ "$PUSHD_VAPID_PRIVATEKEY" = "" ]; then + if [ "$PUSHD_VAPID_PUBLICKEY" != "" ]; then + echo "VAPID public key is defined when private key isn't?" + echo "Did you forget to copy the PUSHD_VAPID_PRIVATEKEY secret?" + echo "Try removing PUSHD_VAPID_PUBLICKEY 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 + PUSHD_VAPID_PRIVATEKEY=$(base64 -i vapid_private.pem | tr -d '\n' | tr -d '=') + PUSHD_VAPID_PUBLICKEY=$(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 "PUSHD_VAPID_PRIVATEKEY='%s'\n" $PUSHD_VAPID_PRIVATEKEY >> secrets.env + printf "PUSHD_VAPID_PUBLICKEY='%s'\n" $PUSHD_VAPID_PUBLICKEY >> secrets.env +elif [ "$PUSHD_VAPID_PUBLICKEY" = "" ]; then + echo "VAPID private key is defined when public key isn't?" + echo "Did you forget to copy the PUSHD_VAPID_PUBLICKEY secret?" + echo "Try removing PUSHD_VAPID_PRIVATEKEY if you do not have a public key." + exit 1 +else + echo "Using old Pushd VAPID secrets..." +fi + +if [ "$FILES_ENCRYPTION_KEY" = "" ]; then + echo "Generating files encryption secret..." + FILES_ENCRYPTION_KEY=$(openssl rand -base64 32) + echo "" >> secrets.env + printf "FILES_ENCRYPTION_KEY='%s'\n" $FILES_ENCRYPTION_KEY >> secrets.env +else + echo "Using old files encryption secret..." +fi + +if [ "$LIVEKIT_WORLDWIDE_SECRET" = "" ]; then + if [ "$LIVEKIT_WORLDWIDE_KEY" != "" ]; then + echo "Livekit public key is defined when secret isn't?" + echo "Did you forget to copy the LIVEKIT_WORLDWIDE_SECRET secret?" + echo "Try removing LIVEKIT_WORLDWIDE_KEY if you do not have a secret." + exit 1 + fi + echo "Generating Livekit secrets..." + LIVEKIT_WORLDWIDE_SECRET=$(openssl rand -hex 24) + LIVEKIT_WORLDWIDE_KEY=$(openssl rand -hex 6) + echo "" >> secrets.env + printf "LIVEKIT_WORLDWIDE_SECRET='%s'\n" $LIVEKIT_WORLDWIDE_SECRET >> secrets.env + printf "LIVEKIT_WORLDWIDE_KEY='%s'\n" $LIVEKIT_WORLDWIDE_KEY >> secrets.env +elif [ "$LIVEKIT_WORLDWIDE_KEY" = "" ]; then + echo "Livekit secret is defined when public key isn't?" + echo "Did you forget to copy the LIVEKIT_WORLDWIDE_KEY secret?" + echo "Try removing LIVEKIT_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=https://$1" > .env.web +echo "HOSTNAME=$STOAT_HOSTNAME" > .env.web echo "REVOLT_PUBLIC_URL=https://$1/api" >> .env.web echo "VITE_API_URL=https://$1/api" >> .env.web echo "VITE_WS_URL=wss://$1/ws" >> .env.web @@ -34,18 +145,14 @@ echo "worldwide = \"wss://$1/livekit\"" >> Revolt.toml # VAPID keys echo "" >> Revolt.toml echo "[pushd.vapid]" >> Revolt.toml -openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem -echo "private_key = \"$(base64 -i vapid_private.pem | tr -d '\n' | tr -d '=')\"" >> Revolt.toml -echo "public_key = \"$(openssl ec -in vapid_private.pem -outform DER|tail --bytes 65|base64|tr '/+' '_-'|tr -d '\n'|tr -d '=')\"" >> Revolt.toml -rm vapid_private.pem + +echo "private_key = \"$PUSHD_VAPID_PRIVATEKEY\"" >> Revolt.toml +echo "public_key = \"$PUSHD_VAPID_PUBLICKEY\"" >> Revolt.toml # encryption key for files echo "" >> Revolt.toml echo "[files]" >> Revolt.toml -echo "encryption_key = \"$(openssl rand -base64 32)\"" >> Revolt.toml - -livekit_key=$(openssl rand -hex 6) -livekit_secret=$(openssl rand -hex 24) +echo "encryption_key = \"$FILES_ENCRYPTION_KEY\"" >> Revolt.toml # livekit yml echo "rtc:" > livekit.yml @@ -61,10 +168,10 @@ echo "turn:" >> livekit.yml echo " enabled: false" >> livekit.yml echo "" >> livekit.yml echo "keys:" >> livekit.yml -echo " $livekit_key: $livekit_secret" >> livekit.yml +echo " $LIVEKIT_WORLDWIDE_KEY: $LIVEKIT_WORLDWIDE_SECRET" >> livekit.yml echo "" >> livekit.yml echo "webhook:" >> livekit.yml -echo " api_key: $livekit_key" >> livekit.yml +echo " api_key: $LIVEKIT_WORLDWIDE_KEY" >> livekit.yml echo " urls:" >> livekit.yml echo " - \"http://voice-ingress:8500/worldwide\"" >> livekit.yml @@ -74,5 +181,9 @@ 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 -echo "key = \"$livekit_key\"" >> Revolt.toml -echo "secret = \"$livekit_secret\"" >> Revolt.toml \ No newline at end of file +echo "key = \"$LIVEKIT_WORLDWIDE_KEY\"" >> Revolt.toml +echo "secret = \"$LIVEKIT_WORLDWIDE_SECRET\"" >> Revolt.toml + +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/secrets.env.example b/secrets.env.example new file mode 100644 index 0000000..675bb2a --- /dev/null +++ b/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. +# PUSHD_VAPID_PRIVATEKEY= +# +# Pushd VAPID public key is the value stored in the [pushd.vapid] section of +# Revolt.toml for the public_key line. +# PUSHD_VAPID_PUBLICKEY= +# +# Files encryption key is the value stored in the [files] section of +# Revolt.toml for the encryption_key line. +# FILES_ENCRYPTION_KEY= +# +# Livekit worldwide key is the value stored in the +# [api.livekit.nodes.worldwide] section of Revolt.toml for the key line. +# LIVEKIT_WORLDWIDE_KEY= +# +# Livekit worldwide secret is the value stored in the +# [api.livekit.nodes.worldwide] section of Revolt.toml for the secret line. +# LIVEKIT_WORLDWIDE_SECRET=