diff --git a/apps/mastodon-linuxserver/4.7.3/.env.sample b/apps/mastodon-linuxserver/4.7.3/.env.sample new file mode 100644 index 000000000..1ad21ac97 --- /dev/null +++ b/apps/mastodon-linuxserver/4.7.3/.env.sample @@ -0,0 +1,31 @@ +CONTAINER_NAME= +PANEL_APP_PORT_HTTP=80 +PANEL_APP_PORT_HTTPS=443 +CONFIG_PATH=./data/config +LOCAL_DOMAIN=example.com +WEB_DOMAIN= +REDIS_HOST=mastodon-redis +REDIS_PORT=6379 +DB_HOST=mastodon-db +DB_PORT=5432 +DB_USER=mastodon +DB_NAME=mastodon +DB_PASSWORD=replace-with-db-password +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=replace-with-generated-active-record-primary-key +ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=replace-with-generated-active-record-deterministic-key +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=replace-with-generated-active-record-salt +SECRET_KEY_BASE=replace-with-generated-secret-key-base +OTP_SECRET=replace-with-generated-otp-secret +VAPID_PRIVATE_KEY=replace-with-generated-vapid-private-key +VAPID_PUBLIC_KEY=replace-with-generated-vapid-public-key +SMTP_SERVER=mail.example.com +SMTP_PORT=25 +SMTP_LOGIN= +SMTP_PASSWORD= +SMTP_FROM_ADDRESS=notifications@example.com +MASTODON_PROMETHEUS_EXPORTER_ENABLED=false +SIDEKIQ_THREADS=5 +DB_POOL=5 +DB_DATA_PATH=./data/db +REDIS_DATA_PATH=./data/redis +TIME_ZONE=Asia/Shanghai diff --git a/apps/mastodon-linuxserver/4.7.3/data.yml b/apps/mastodon-linuxserver/4.7.3/data.yml new file mode 100644 index 000000000..a533c1819 --- /dev/null +++ b/apps/mastodon-linuxserver/4.7.3/data.yml @@ -0,0 +1,505 @@ +additionalProperties: + formFields: + - default: 80 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + ja: HTTP ポート + ko: HTTP 포트 + ru: HTTP-порт + ms: Port HTTP + pt-br: Porta HTTP + required: true + type: number + rule: paramPort + - default: 443 + edit: true + envKey: PANEL_APP_PORT_HTTPS + labelEn: HTTPS Port + labelZh: HTTPS 端口 + label: + en: HTTPS Port + zh: HTTPS 端口 + zh-Hant: HTTPS 連接埠 + ja: HTTPS ポート + ko: HTTPS 포트 + ru: HTTPS-порт + ms: Port HTTPS + pt-br: Porta HTTPS + required: true + type: number + rule: paramPort + - default: ./data/config + edit: true + envKey: CONFIG_PATH + labelEn: Config folder path + labelZh: 配置文件路径 + label: + en: Config folder path + zh: 配置文件路径 + zh-Hant: 設定檔資料夾路徑 + ja: 設定フォルダーのパス + ko: 설정 폴더 경로 + ru: Путь к папке конфигурации + ms: Laluan folder konfigurasi + pt-br: Caminho da pasta de configuração + required: true + type: text + - default: '' + edit: true + envKey: LOCAL_DOMAIN + labelEn: Local domain + labelZh: 实例域名 + label: + en: Local domain + zh: 实例域名 + zh-Hant: 實例網域 + ja: ローカルドメイン + ko: 로컬 도메인 + ru: Локальный домен + ms: Domain setempat + pt-br: Dominio local + required: true + type: text + - default: '' + edit: true + envKey: WEB_DOMAIN + labelEn: Web domain + labelZh: Web 域名 + label: + en: Web domain + zh: Web 域名 + zh-Hant: Web 網域 + ja: Web ドメイン + ko: Web 도메인 + ru: Web-домен + ms: Domain web + pt-br: Dominio web + required: false + type: text + - default: "false" + edit: true + envKey: MASTODON_PROMETHEUS_EXPORTER_ENABLED + labelEn: Prometheus exporter + labelZh: Prometheus 导出器 + label: + en: Prometheus exporter + zh: Prometheus 导出器 + zh-Hant: Prometheus 匯出器 + ja: Prometheus エクスポーター + ko: Prometheus exporter + ru: Экспортер Prometheus + ms: Pengeksport Prometheus + pt-br: Exportador Prometheus + required: false + type: select + values: + - label: "false" + value: "false" + - label: "true" + value: "true" + - default: mastodon-db + edit: true + envKey: DB_HOST + labelEn: Database host + labelZh: 数据库主机 + label: + en: Database host + zh: 数据库主机 + zh-Hant: 資料庫主機 + ja: データベースホスト + ko: 데이터베이스 호스트 + ru: Хост базы данных + ms: Hos pangkalan data + pt-br: Host do banco + required: true + type: text + - default: 5432 + edit: true + envKey: DB_PORT + labelEn: Database port + labelZh: 数据库端口 + label: + en: Database port + zh: 数据库端口 + zh-Hant: 資料庫連接埠 + ja: データベースポート + ko: 데이터베이스 포트 + ru: Порт базы данных + ms: Port pangkalan data + pt-br: Porta do banco + required: true + type: number + - default: mastodon + edit: true + envKey: DB_USER + labelEn: Database username + labelZh: 数据库用户名 + label: + en: Database username + zh: 数据库用户名 + zh-Hant: 資料庫使用者名稱 + ja: データベースユーザー名 + ko: 데이터베이스 사용자 이름 + ru: Имя пользователя базы + ms: Nama pengguna pangkalan data + pt-br: Usuario do banco + required: true + type: text + - default: mastodon + edit: true + envKey: DB_NAME + labelEn: Database name + labelZh: 数据库名称 + label: + en: Database name + zh: 数据库名称 + zh-Hant: 資料庫名稱 + ja: データベース名 + ko: 데이터베이스 이름 + ru: Имя базы данных + ms: Nama pangkalan data + pt-br: Nome do banco + required: true + type: text + - default: mastodon-redis + edit: true + envKey: REDIS_HOST + labelEn: Redis host + labelZh: Redis 主机 + label: + en: Redis host + zh: Redis 主机 + zh-Hant: Redis 主機 + ja: Redis ホスト + ko: Redis 호스트 + ru: Хост Redis + ms: Hos Redis + pt-br: Host do Redis + required: true + type: text + - default: 6379 + edit: true + envKey: REDIS_PORT + labelEn: Redis port + labelZh: Redis 端口 + label: + en: Redis port + zh: Redis 端口 + zh-Hant: Redis 連接埠 + ja: Redis ポート + ko: Redis 포트 + ru: Порт Redis + ms: Port Redis + pt-br: Porta do Redis + required: true + type: number + - default: '' + edit: true + envKey: DB_PASSWORD + labelEn: Database password + labelZh: 数据库密码 + label: + en: Database password + zh: 数据库密码 + zh-Hant: 資料庫密碼 + ja: データベースパスワード + ko: 데이터베이스 비밀번호 + ru: Пароль базы данных + ms: Kata laluan pangkalan data + pt-br: Senha do banco de dados + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: SECRET_KEY_BASE + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: OTP_SECRET + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: VAPID_PRIVATE_KEY + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: VAPID_PUBLIC_KEY + labelEn: Secret value + labelZh: 密钥值 + label: + en: Secret value + zh: 密钥值 + zh-Hant: 金鑰值 + ja: シークレット値 + ko: 시크릿 값 + ru: Секретное значение + ms: Nilai rahsia + pt-br: Valor secreto + required: true + random: true + type: password + rule: paramComplexity + - default: '' + edit: true + envKey: SMTP_SERVER + labelEn: SMTP server + labelZh: SMTP 服务器 + label: + en: SMTP server + zh: SMTP 服务器 + zh-Hant: SMTP 伺服器 + ja: SMTP サーバー + ko: SMTP 서버 + ru: SMTP-сервер + ms: Pelayan SMTP + pt-br: Servidor SMTP + required: true + type: text + - default: 25 + edit: true + envKey: SMTP_PORT + labelEn: SMTP port + labelZh: SMTP 端口 + label: + en: SMTP port + zh: SMTP 端口 + zh-Hant: SMTP 連接埠 + ja: SMTP ポート + ko: SMTP 포트 + ru: Порт SMTP + ms: Port SMTP + pt-br: Porta SMTP + required: true + type: number + - default: '' + edit: true + envKey: SMTP_LOGIN + labelEn: SMTP login + labelZh: SMTP 登录名 + label: + en: SMTP login + zh: SMTP 登录名 + zh-Hant: SMTP 登入名 + ja: SMTP ログイン + ko: SMTP 로그인 + ru: Логин SMTP + ms: Log masuk SMTP + pt-br: Login SMTP + required: false + type: text + - default: '' + edit: true + envKey: SMTP_PASSWORD + labelEn: SMTP password + labelZh: SMTP 密码 + label: + en: SMTP password + zh: SMTP 密码 + zh-Hant: SMTP 密碼 + ja: SMTP パスワード + ko: SMTP 비밀번호 + ru: Пароль SMTP + ms: Kata laluan SMTP + pt-br: Senha SMTP + required: false + type: password + - default: '' + edit: true + envKey: SMTP_FROM_ADDRESS + labelEn: SMTP from address + labelZh: SMTP 发件地址 + label: + en: SMTP from address + zh: SMTP 发件地址 + zh-Hant: SMTP 寄件地址 + ja: SMTP 送信元アドレス + ko: SMTP 발신 주소 + ru: SMTP адрес отправителя + ms: Alamat pengirim SMTP + pt-br: Endereco remetente SMTP + required: true + type: text + - default: 5 + edit: true + envKey: SIDEKIQ_THREADS + labelEn: Sidekiq threads + labelZh: Sidekiq 线程数 + label: + en: Sidekiq threads + zh: Sidekiq 线程数 + zh-Hant: Sidekiq 執行緒數 + ja: Sidekiq スレッド数 + ko: Sidekiq 스레드 수 + ru: Потоки Sidekiq + ms: Benang Sidekiq + pt-br: Threads Sidekiq + required: true + type: number + - default: 5 + edit: true + envKey: DB_POOL + labelEn: Database pool size + labelZh: 数据库连接池大小 + label: + en: Database pool size + zh: 数据库连接池大小 + zh-Hant: 資料庫連線池大小 + ja: データベースプールサイズ + ko: 데이터베이스 풀 크기 + ru: Размер пула базы данных + ms: Saiz kolam pangkalan data + pt-br: Tamanho do pool do banco + required: true + type: number + - default: ./data/db + edit: true + envKey: DB_DATA_PATH + labelEn: Database data path + labelZh: 数据库数据目录 + label: + en: Database data path + zh: 数据库数据目录 + zh-Hant: 資料庫資料目錄 + ja: データベースデータディレクトリ + ko: 데이터베이스 데이터 디렉터リ + ru: Каталог данных базы + ms: Laluan data pangkalan data + pt-br: Caminho de dados do banco + required: true + type: text + - default: ./data/redis + edit: true + envKey: REDIS_DATA_PATH + labelEn: Redis data path + labelZh: Redis 数据目录 + label: + en: Redis data path + zh: Redis 数据目录 + zh-Hant: Redis 資料目錄 + ja: Redis データディレクトリ + ko: Redis 데이터 디렉터리 + ru: Каталог данных Redis + ms: Laluan data Redis + pt-br: Caminho de dados Redis + required: true + type: text + - default: Asia/Shanghai + edit: true + envKey: TIME_ZONE + labelEn: Time zone + labelZh: 时区 + label: + en: Time zone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text diff --git a/apps/mastodon-linuxserver/4.7.3/data/.gitkeep b/apps/mastodon-linuxserver/4.7.3/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/mastodon-linuxserver/4.7.3/docker-compose.yml b/apps/mastodon-linuxserver/4.7.3/docker-compose.yml new file mode 100644 index 000000000..22b6e6ef1 --- /dev/null +++ b/apps/mastodon-linuxserver/4.7.3/docker-compose.yml @@ -0,0 +1,72 @@ +networks: + 1panel-network: + external: true +services: + mastodon: + image: "linuxserver/mastodon:4.7.3" + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:80" + - "${PANEL_APP_PORT_HTTPS}:443" + volumes: + - "${CONFIG_PATH}:/config" + environment: + - PUID=1000 + - PGID=1000 + - TZ=${TIME_ZONE} + - LOCAL_DOMAIN=${LOCAL_DOMAIN} + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} + - DB_HOST=${DB_HOST} + - DB_USER=${DB_USER} + - DB_NAME=${DB_NAME} + - DB_PASS=${DB_PASSWORD} + - DB_PORT=${DB_PORT} + - ES_ENABLED=false + - ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=${ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY} + - ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=${ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY} + - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=${ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT} + - SECRET_KEY_BASE=${SECRET_KEY_BASE} + - OTP_SECRET=${OTP_SECRET} + - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY} + - VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY} + - SMTP_SERVER=${SMTP_SERVER} + - SMTP_PORT=${SMTP_PORT} + - SMTP_LOGIN=${SMTP_LOGIN:-} + - SMTP_PASSWORD=${SMTP_PASSWORD:-} + - SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS} + - S3_ENABLED=false + - WEB_DOMAIN=${WEB_DOMAIN:-} + - MASTODON_PROMETHEUS_EXPORTER_ENABLED=${MASTODON_PROMETHEUS_EXPORTER_ENABLED} + - SIDEKIQ_THREADS=${SIDEKIQ_THREADS} + - DB_POOL=${DB_POOL} + depends_on: + - mastodon-db + - mastodon-redis + labels: + createdBy: "Apps" + mastodon-db: + image: "postgres:16-alpine" + restart: always + networks: + - 1panel-network + volumes: + - "${DB_DATA_PATH}:/var/lib/postgresql/data" + environment: + - POSTGRES_DB=${DB_NAME} + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + labels: + createdBy: "Apps" + mastodon-redis: + image: "redis:7-alpine" + restart: always + networks: + - 1panel-network + volumes: + - "${REDIS_DATA_PATH}:/data" + labels: + createdBy: "Apps" diff --git a/apps/mastodon-linuxserver/4.7.3/scripts/init.sh b/apps/mastodon-linuxserver/4.7.3/scripts/init.sh new file mode 100755 index 000000000..bee255c50 --- /dev/null +++ b/apps/mastodon-linuxserver/4.7.3/scripts/init.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +set -euo pipefail + +ENV_FILE="${ENV_FILE:-./.env}" +MASTODON_IMAGE="lscr.io/linuxserver/mastodon:4.6.4" +LEGACY_DB_PASSWORD="mastodon-change-me" +LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="fGWkJDBwyRYhILyO7akZGLSSz0gAjPpo" +LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="Z8BE3tc3XnmUr0MbRexRiPN7vcP52VX0" +LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="3hRwOaLRCikUCsxd19cUcO5EgXCN6ig1" +LEGACY_SECRET_KEY_BASE="ae3ab77b9a041a4d760126dba8c4e24a33bdfcec7fdbadf3d973baf92017ee0d0b988d62c040aca882ee8030ea22adb04d9c26a9541c918a434029840c8719b0" +LEGACY_OTP_SECRET="b3d6069b87df718c733a1b17a9adcbd7705b76ba0b48dd3f3ce58870dd52bed0d32d947a43a4eadac4230d508e11df0cee6f59ed85e40670134a52545f1131ac" +LEGACY_VAPID_PRIVATE_KEY="1eF670mQsgs_-W_eb06ZZ46apD4qVDYNvFWu9eGWc7E=" +LEGACY_VAPID_PUBLIC_KEY="BOOqXKvCVA9tb8Bas05sdez6fGavnA5SrDJe88s7FMeIu7txiQ5sOqOZqKIpTSS_xmm5Wkd_MKHqj2UCQIwl9_8=" + +read_env_value() { + local key="$1" + + if [[ ! -f "$ENV_FILE" ]] || ! grep -qE "^${key}=" "$ENV_FILE"; then + return + fi + + local current + current="$(sed -n -E "s/^${key}=//p" "$ENV_FILE" | tail -n 1)" + current="${current%\"}" + current="${current#\"}" + current="${current%\'}" + current="${current#\'}" + printf '%s\n' "$current" +} + +read_cache_value() { + local cache_file="$1" + if [[ ! -s "$cache_file" ]]; then + return + fi + sed -n '1p' "$cache_file" | tr -d '\r\n' +} + +write_cache_value() { + local cache_file="$1" + local value="$2" + + mkdir -p "$(dirname "$cache_file")" + printf '%s\n' "$value" > "$cache_file" + chmod 600 "$cache_file" 2>/dev/null || true +} + +escape_sed_replacement() { + printf '%s' "$1" | sed -e 's/[\/&|]/\\&/g' +} + +set_env_value() { + local key="$1" + local value="$2" + + if [[ ! -f "$ENV_FILE" ]]; then + echo "$ENV_FILE not found; skipped $key generation" + return + fi + + local replacement + replacement="$(escape_sed_replacement "$value")" + if grep -qE "^${key}=" "$ENV_FILE"; then + sed -i -E "s|^${key}=.*|${key}=${replacement}|" "$ENV_FILE" + echo "Updated $key" + return + fi + + printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE" + echo "Added $key" +} + +generate_alnum_secret() { + local secret="" + local chunk="" + while [[ ${#secret} -lt 32 ]]; do + chunk="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c $((32 - ${#secret})) || true)" + secret="${secret}${chunk}" + done + printf '%s\n' "$secret" +} + +run_mastodon_generator() { + docker run --rm --entrypoint /bin/bash "$MASTODON_IMAGE" "$1" +} + +extract_generator_value() { + local output="$1" + local key="$2" + printf '%s\n' "$output" | sed -n -E "s/^${key}=//p" | tail -n 1 +} + +is_active_record_secret_valid() { + local value="$1" + [[ "$value" =~ ^[A-Za-z0-9]{32}$ ]] +} + +is_hex_secret_valid() { + local value="$1" + [[ "$value" =~ ^[a-f0-9]{128}$ ]] +} + +is_vapid_key_valid() { + local value="$1" + [[ ${#value} -ge 40 && "$value" =~ ^[A-Za-z0-9_-]+=*$ ]] +} + +CONFIG_PATH_VALUE="${CONFIG_PATH:-$(read_env_value CONFIG_PATH || true)}" +CONFIG_PATH_VALUE="${CONFIG_PATH_VALUE:-./data/config}" +SECRET_CACHE_DIR="${SECRET_CACHE_DIR:-${CONFIG_PATH_VALUE}/.mastodon_secret_cache}" +DB_PASSWORD_CACHE="${DB_PASSWORD_CACHE:-${SECRET_CACHE_DIR}/db_password}" +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE="${ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_primary_key}" +ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE="${ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_deterministic_key}" +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE="${ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_key_derivation_salt}" +SECRET_KEY_BASE_CACHE="${SECRET_KEY_BASE_CACHE:-${SECRET_CACHE_DIR}/secret_key_base}" +OTP_SECRET_CACHE="${OTP_SECRET_CACHE:-${SECRET_CACHE_DIR}/otp_secret}" +VAPID_PRIVATE_KEY_CACHE="${VAPID_PRIVATE_KEY_CACHE:-${SECRET_CACHE_DIR}/vapid_private_key}" +VAPID_PUBLIC_KEY_CACHE="${VAPID_PUBLIC_KEY_CACHE:-${SECRET_CACHE_DIR}/vapid_public_key}" + +if [[ -f "$ENV_FILE" ]]; then + db_password="$(read_env_value DB_PASSWORD || true)" + cached_db_password="$(read_cache_value "$DB_PASSWORD_CACHE" || true)" + if [[ -n "$db_password" && "$db_password" != "$LEGACY_DB_PASSWORD" ]]; then + write_cache_value "$DB_PASSWORD_CACHE" "$db_password" + elif [[ -n "$cached_db_password" ]]; then + set_env_value "DB_PASSWORD" "$cached_db_password" + else + db_password="$(generate_alnum_secret)" + set_env_value "DB_PASSWORD" "$db_password" + write_cache_value "$DB_PASSWORD_CACHE" "$db_password" + fi + + active_primary="$(read_env_value ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY || true)" + active_deterministic="$(read_env_value ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY || true)" + active_salt="$(read_env_value ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT || true)" + cached_active_primary="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" || true)" + cached_active_deterministic="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" || true)" + cached_active_salt="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" || true)" + if is_active_record_secret_valid "$active_primary" \ + && is_active_record_secret_valid "$active_deterministic" \ + && is_active_record_secret_valid "$active_salt" \ + && [[ "$active_primary" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" ]] \ + && [[ "$active_deterministic" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" ]] \ + && [[ "$active_salt" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" ]]; then + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" "$active_primary" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" "$active_deterministic" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" "$active_salt" + elif is_active_record_secret_valid "$cached_active_primary" \ + && is_active_record_secret_valid "$cached_active_deterministic" \ + && is_active_record_secret_valid "$cached_active_salt"; then + set_env_value "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" "$cached_active_primary" + set_env_value "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" "$cached_active_deterministic" + set_env_value "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" "$cached_active_salt" + elif [[ "$active_primary" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" ]] \ + || [[ "$active_deterministic" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" ]] \ + || [[ "$active_salt" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" ]] \ + || ! is_active_record_secret_valid "$active_primary" \ + || ! is_active_record_secret_valid "$active_deterministic" \ + || ! is_active_record_secret_valid "$active_salt"; then + active_record_output="$(run_mastodon_generator generate-active-record)" + active_primary="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY")" + active_deterministic="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY")" + active_salt="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT")" + set_env_value "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" "$active_primary" + set_env_value "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" "$active_deterministic" + set_env_value "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" "$active_salt" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" "$active_primary" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" "$active_deterministic" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" "$active_salt" + fi + + secret_key_base="$(read_env_value SECRET_KEY_BASE || true)" + if [[ "$secret_key_base" == "$LEGACY_SECRET_KEY_BASE" ]] || ! is_hex_secret_valid "$secret_key_base"; then + cached_secret_key_base="$(read_cache_value "$SECRET_KEY_BASE_CACHE" || true)" + if is_hex_secret_valid "$cached_secret_key_base"; then + set_env_value "SECRET_KEY_BASE" "$cached_secret_key_base" + else + secret_key_base="$(run_mastodon_generator generate-secret | tail -n 1 | tr -d '\r\n')" + set_env_value "SECRET_KEY_BASE" "$secret_key_base" + write_cache_value "$SECRET_KEY_BASE_CACHE" "$secret_key_base" + fi + else + write_cache_value "$SECRET_KEY_BASE_CACHE" "$secret_key_base" + fi + + otp_secret="$(read_env_value OTP_SECRET || true)" + if [[ "$otp_secret" == "$LEGACY_OTP_SECRET" ]] || ! is_hex_secret_valid "$otp_secret"; then + cached_otp_secret="$(read_cache_value "$OTP_SECRET_CACHE" || true)" + if is_hex_secret_valid "$cached_otp_secret"; then + set_env_value "OTP_SECRET" "$cached_otp_secret" + else + otp_secret="$(run_mastodon_generator generate-secret | tail -n 1 | tr -d '\r\n')" + set_env_value "OTP_SECRET" "$otp_secret" + write_cache_value "$OTP_SECRET_CACHE" "$otp_secret" + fi + else + write_cache_value "$OTP_SECRET_CACHE" "$otp_secret" + fi + + vapid_private_key="$(read_env_value VAPID_PRIVATE_KEY || true)" + vapid_public_key="$(read_env_value VAPID_PUBLIC_KEY || true)" + cached_vapid_private_key="$(read_cache_value "$VAPID_PRIVATE_KEY_CACHE" || true)" + cached_vapid_public_key="$(read_cache_value "$VAPID_PUBLIC_KEY_CACHE" || true)" + if is_vapid_key_valid "$vapid_private_key" \ + && is_vapid_key_valid "$vapid_public_key" \ + && [[ "$vapid_private_key" != "$LEGACY_VAPID_PRIVATE_KEY" ]] \ + && [[ "$vapid_public_key" != "$LEGACY_VAPID_PUBLIC_KEY" ]]; then + write_cache_value "$VAPID_PRIVATE_KEY_CACHE" "$vapid_private_key" + write_cache_value "$VAPID_PUBLIC_KEY_CACHE" "$vapid_public_key" + elif is_vapid_key_valid "$cached_vapid_private_key" && is_vapid_key_valid "$cached_vapid_public_key"; then + set_env_value "VAPID_PRIVATE_KEY" "$cached_vapid_private_key" + set_env_value "VAPID_PUBLIC_KEY" "$cached_vapid_public_key" + elif [[ "$vapid_private_key" == "$LEGACY_VAPID_PRIVATE_KEY" ]] \ + || [[ "$vapid_public_key" == "$LEGACY_VAPID_PUBLIC_KEY" ]] \ + || ! is_vapid_key_valid "$vapid_private_key" \ + || ! is_vapid_key_valid "$vapid_public_key"; then + vapid_output="$(run_mastodon_generator generate-vapid)" + set_env_value "VAPID_PRIVATE_KEY" "$(extract_generator_value "$vapid_output" "VAPID_PRIVATE_KEY")" + set_env_value "VAPID_PUBLIC_KEY" "$(extract_generator_value "$vapid_output" "VAPID_PUBLIC_KEY")" + write_cache_value "$VAPID_PRIVATE_KEY_CACHE" "$(extract_generator_value "$vapid_output" "VAPID_PRIVATE_KEY")" + write_cache_value "$VAPID_PUBLIC_KEY_CACHE" "$(extract_generator_value "$vapid_output" "VAPID_PUBLIC_KEY")" + fi +fi + +paths=( + "${CONFIG_PATH:-./data/config}" + "${DB_DATA_PATH:-./data/db}" + "${REDIS_DATA_PATH:-./data/redis}" +) + +mkdir -p "${paths[@]}" +for path in "${paths[@]}"; do + case "$path" in + ./*|../*) chown -R 1000:1000 "$path" 2>/dev/null || true ;; + esac +done diff --git a/apps/mastodon-linuxserver/4.7.3/scripts/uninstall.sh b/apps/mastodon-linuxserver/4.7.3/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/mastodon-linuxserver/4.7.3/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/mastodon-linuxserver/4.7.3/scripts/upgrade.sh b/apps/mastodon-linuxserver/4.7.3/scripts/upgrade.sh new file mode 100755 index 000000000..8e4e6de98 --- /dev/null +++ b/apps/mastodon-linuxserver/4.7.3/scripts/upgrade.sh @@ -0,0 +1,251 @@ +#!/usr/bin/env bash +set -euo pipefail + +ENV_FILE="${ENV_FILE:-./.env}" +MASTODON_IMAGE="lscr.io/linuxserver/mastodon:4.6.4" +LEGACY_DB_PASSWORD="mastodon-change-me" +LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="fGWkJDBwyRYhILyO7akZGLSSz0gAjPpo" +LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="Z8BE3tc3XnmUr0MbRexRiPN7vcP52VX0" +LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="3hRwOaLRCikUCsxd19cUcO5EgXCN6ig1" +LEGACY_SECRET_KEY_BASE="ae3ab77b9a041a4d760126dba8c4e24a33bdfcec7fdbadf3d973baf92017ee0d0b988d62c040aca882ee8030ea22adb04d9c26a9541c918a434029840c8719b0" +LEGACY_OTP_SECRET="b3d6069b87df718c733a1b17a9adcbd7705b76ba0b48dd3f3ce58870dd52bed0d32d947a43a4eadac4230d508e11df0cee6f59ed85e40670134a52545f1131ac" +LEGACY_VAPID_PRIVATE_KEY="1eF670mQsgs_-W_eb06ZZ46apD4qVDYNvFWu9eGWc7E=" +LEGACY_VAPID_PUBLIC_KEY="BOOqXKvCVA9tb8Bas05sdez6fGavnA5SrDJe88s7FMeIu7txiQ5sOqOZqKIpTSS_xmm5Wkd_MKHqj2UCQIwl9_8=" + +ensure_env_default() { + local key="$1" + local value="$2" + + if [[ ! -f "$ENV_FILE" ]]; then + echo "$ENV_FILE not found; skipped $key migration" + return + fi + + if grep -qE "^${key}=" "$ENV_FILE"; then + echo "$key already exists" + return + fi + + printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE" + echo "Added $key" +} + +read_env_value() { + local key="$1" + + if [[ ! -f "$ENV_FILE" ]] || ! grep -qE "^${key}=" "$ENV_FILE"; then + return + fi + + local current + current="$(sed -n -E "s/^${key}=//p" "$ENV_FILE" | tail -n 1)" + current="${current%\"}" + current="${current#\"}" + current="${current%\'}" + current="${current#\'}" + printf '%s\n' "$current" +} + +read_cache_value() { + local cache_file="$1" + if [[ ! -s "$cache_file" ]]; then + return + fi + sed -n '1p' "$cache_file" | tr -d '\r\n' +} + +write_cache_value() { + local cache_file="$1" + local value="$2" + + mkdir -p "$(dirname "$cache_file")" + printf '%s\n' "$value" > "$cache_file" + chmod 600 "$cache_file" 2>/dev/null || true +} + +escape_sed_replacement() { + printf '%s' "$1" | sed -e 's/[\/&|]/\\&/g' +} + +set_env_value() { + local key="$1" + local value="$2" + + if [[ ! -f "$ENV_FILE" ]]; then + echo "$ENV_FILE not found; skipped $key generation" + return + fi + + local replacement + replacement="$(escape_sed_replacement "$value")" + if grep -qE "^${key}=" "$ENV_FILE"; then + sed -i -E "s|^${key}=.*|${key}=${replacement}|" "$ENV_FILE" + echo "Updated $key" + return + fi + + printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE" + echo "Added $key" +} + +generate_alnum_secret() { + local secret="" + local chunk="" + while [[ ${#secret} -lt 32 ]]; do + chunk="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c $((32 - ${#secret})) || true)" + secret="${secret}${chunk}" + done + printf '%s\n' "$secret" +} + +run_mastodon_generator() { + docker run --rm --entrypoint /bin/bash "$MASTODON_IMAGE" "$1" +} + +extract_generator_value() { + local output="$1" + local key="$2" + printf '%s\n' "$output" | sed -n -E "s/^${key}=//p" | tail -n 1 +} + +is_active_record_secret_valid() { + local value="$1" + [[ "$value" =~ ^[A-Za-z0-9]{32}$ ]] +} + +is_hex_secret_valid() { + local value="$1" + [[ "$value" =~ ^[a-f0-9]{128}$ ]] +} + +is_vapid_key_valid() { + local value="$1" + [[ ${#value} -ge 40 && "$value" =~ ^[A-Za-z0-9_-]+=*$ ]] +} + +CONFIG_PATH_VALUE="${CONFIG_PATH:-$(read_env_value CONFIG_PATH || true)}" +CONFIG_PATH_VALUE="${CONFIG_PATH_VALUE:-./data/config}" +SECRET_CACHE_DIR="${SECRET_CACHE_DIR:-${CONFIG_PATH_VALUE}/.mastodon_secret_cache}" +DB_PASSWORD_CACHE="${DB_PASSWORD_CACHE:-${SECRET_CACHE_DIR}/db_password}" +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE="${ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_primary_key}" +ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE="${ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_deterministic_key}" +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE="${ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_key_derivation_salt}" +SECRET_KEY_BASE_CACHE="${SECRET_KEY_BASE_CACHE:-${SECRET_CACHE_DIR}/secret_key_base}" +OTP_SECRET_CACHE="${OTP_SECRET_CACHE:-${SECRET_CACHE_DIR}/otp_secret}" +VAPID_PRIVATE_KEY_CACHE="${VAPID_PRIVATE_KEY_CACHE:-${SECRET_CACHE_DIR}/vapid_private_key}" +VAPID_PUBLIC_KEY_CACHE="${VAPID_PUBLIC_KEY_CACHE:-${SECRET_CACHE_DIR}/vapid_public_key}" + +if [[ -f "$ENV_FILE" ]]; then + db_password="$(read_env_value DB_PASSWORD || true)" + cached_db_password="$(read_cache_value "$DB_PASSWORD_CACHE" || true)" + if [[ -n "$db_password" && "$db_password" != "$LEGACY_DB_PASSWORD" ]]; then + write_cache_value "$DB_PASSWORD_CACHE" "$db_password" + elif [[ -n "$cached_db_password" ]]; then + set_env_value "DB_PASSWORD" "$cached_db_password" + else + db_password="$(generate_alnum_secret)" + set_env_value "DB_PASSWORD" "$db_password" + write_cache_value "$DB_PASSWORD_CACHE" "$db_password" + fi + + active_primary="$(read_env_value ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY || true)" + active_deterministic="$(read_env_value ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY || true)" + active_salt="$(read_env_value ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT || true)" + cached_active_primary="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" || true)" + cached_active_deterministic="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" || true)" + cached_active_salt="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" || true)" + if is_active_record_secret_valid "$active_primary" \ + && is_active_record_secret_valid "$active_deterministic" \ + && is_active_record_secret_valid "$active_salt" \ + && [[ "$active_primary" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" ]] \ + && [[ "$active_deterministic" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" ]] \ + && [[ "$active_salt" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" ]]; then + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" "$active_primary" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" "$active_deterministic" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" "$active_salt" + elif is_active_record_secret_valid "$cached_active_primary" \ + && is_active_record_secret_valid "$cached_active_deterministic" \ + && is_active_record_secret_valid "$cached_active_salt"; then + set_env_value "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" "$cached_active_primary" + set_env_value "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" "$cached_active_deterministic" + set_env_value "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" "$cached_active_salt" + elif [[ "$active_primary" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" ]] \ + || [[ "$active_deterministic" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" ]] \ + || [[ "$active_salt" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" ]] \ + || ! is_active_record_secret_valid "$active_primary" \ + || ! is_active_record_secret_valid "$active_deterministic" \ + || ! is_active_record_secret_valid "$active_salt"; then + active_record_output="$(run_mastodon_generator generate-active-record)" + active_primary="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY")" + active_deterministic="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY")" + active_salt="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT")" + set_env_value "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" "$active_primary" + set_env_value "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" "$active_deterministic" + set_env_value "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" "$active_salt" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" "$active_primary" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" "$active_deterministic" + write_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" "$active_salt" + fi + + secret_key_base="$(read_env_value SECRET_KEY_BASE || true)" + if [[ "$secret_key_base" == "$LEGACY_SECRET_KEY_BASE" ]] || ! is_hex_secret_valid "$secret_key_base"; then + cached_secret_key_base="$(read_cache_value "$SECRET_KEY_BASE_CACHE" || true)" + if is_hex_secret_valid "$cached_secret_key_base"; then + set_env_value "SECRET_KEY_BASE" "$cached_secret_key_base" + else + secret_key_base="$(run_mastodon_generator generate-secret | tail -n 1 | tr -d '\r\n')" + set_env_value "SECRET_KEY_BASE" "$secret_key_base" + write_cache_value "$SECRET_KEY_BASE_CACHE" "$secret_key_base" + fi + else + write_cache_value "$SECRET_KEY_BASE_CACHE" "$secret_key_base" + fi + + otp_secret="$(read_env_value OTP_SECRET || true)" + if [[ "$otp_secret" == "$LEGACY_OTP_SECRET" ]] || ! is_hex_secret_valid "$otp_secret"; then + cached_otp_secret="$(read_cache_value "$OTP_SECRET_CACHE" || true)" + if is_hex_secret_valid "$cached_otp_secret"; then + set_env_value "OTP_SECRET" "$cached_otp_secret" + else + otp_secret="$(run_mastodon_generator generate-secret | tail -n 1 | tr -d '\r\n')" + set_env_value "OTP_SECRET" "$otp_secret" + write_cache_value "$OTP_SECRET_CACHE" "$otp_secret" + fi + else + write_cache_value "$OTP_SECRET_CACHE" "$otp_secret" + fi + + vapid_private_key="$(read_env_value VAPID_PRIVATE_KEY || true)" + vapid_public_key="$(read_env_value VAPID_PUBLIC_KEY || true)" + cached_vapid_private_key="$(read_cache_value "$VAPID_PRIVATE_KEY_CACHE" || true)" + cached_vapid_public_key="$(read_cache_value "$VAPID_PUBLIC_KEY_CACHE" || true)" + if is_vapid_key_valid "$vapid_private_key" \ + && is_vapid_key_valid "$vapid_public_key" \ + && [[ "$vapid_private_key" != "$LEGACY_VAPID_PRIVATE_KEY" ]] \ + && [[ "$vapid_public_key" != "$LEGACY_VAPID_PUBLIC_KEY" ]]; then + write_cache_value "$VAPID_PRIVATE_KEY_CACHE" "$vapid_private_key" + write_cache_value "$VAPID_PUBLIC_KEY_CACHE" "$vapid_public_key" + elif is_vapid_key_valid "$cached_vapid_private_key" && is_vapid_key_valid "$cached_vapid_public_key"; then + set_env_value "VAPID_PRIVATE_KEY" "$cached_vapid_private_key" + set_env_value "VAPID_PUBLIC_KEY" "$cached_vapid_public_key" + elif [[ "$vapid_private_key" == "$LEGACY_VAPID_PRIVATE_KEY" ]] \ + || [[ "$vapid_public_key" == "$LEGACY_VAPID_PUBLIC_KEY" ]] \ + || ! is_vapid_key_valid "$vapid_private_key" \ + || ! is_vapid_key_valid "$vapid_public_key"; then + vapid_output="$(run_mastodon_generator generate-vapid)" + set_env_value "VAPID_PRIVATE_KEY" "$(extract_generator_value "$vapid_output" "VAPID_PRIVATE_KEY")" + set_env_value "VAPID_PUBLIC_KEY" "$(extract_generator_value "$vapid_output" "VAPID_PUBLIC_KEY")" + write_cache_value "$VAPID_PRIVATE_KEY_CACHE" "$(extract_generator_value "$vapid_output" "VAPID_PRIVATE_KEY")" + write_cache_value "$VAPID_PUBLIC_KEY_CACHE" "$(extract_generator_value "$vapid_output" "VAPID_PUBLIC_KEY")" + fi + + ensure_env_default "REDIS_HOST" "mastodon-redis" + ensure_env_default "REDIS_PORT" "6379" + ensure_env_default "DB_HOST" "mastodon-db" + ensure_env_default "DB_PORT" "5432" + ensure_env_default "DB_USER" "mastodon" + ensure_env_default "DB_NAME" "mastodon" + ensure_env_default "MASTODON_PROMETHEUS_EXPORTER_ENABLED" "false" +else + echo "$ENV_FILE not found; skipped LinuxServer environment migration" +fi