Merge remote-tracking branch 'refs/remotes/origin/localApps' into maint-refresh-pr-7681

This commit is contained in:
okxlin
2026-10-17 13:43:53 +08:00
51 changed files with 3605 additions and 23 deletions
+9
View File
@@ -93,5 +93,14 @@
],
"zipline": [
"zipline"
],
"bookstack-linuxserver": [
"bookstack"
],
"mastodon-linuxserver": [
"mastodon"
],
"odoo": [
"odoo"
]
}
@@ -3,7 +3,7 @@ networks:
external: true
services:
adguardhome-sync:
image: "linuxserver/adguardhome-sync:0.9.3"
image: "linuxserver/adguardhome-sync:0.9.4"
container_name: ${CONTAINER_NAME}
restart: always
networks:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
bark:
image: "finab/bark-server:v2.3.7"
image: "finab/bark-server:v2.3.8"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -0,0 +1,13 @@
CONTAINER_NAME=
PANEL_APP_PORT_HTTP=6875
CONFIG_PATH=./data/config
APP_URL=http://bookstack.example.com
APP_KEY=base64:replace-with-generated-app-key
DB_HOST=bookstack-db
DB_PORT=3306
DB_USERNAME=bookstack
DB_DATABASE=bookstack
DB_PASSWORD=replace-with-db-password
QUEUE_CONNECTION=
DB_DATA_PATH=./data/db
TIME_ZONE=Asia/Shanghai
@@ -0,0 +1,204 @@
additionalProperties:
formFields:
- default: 6875
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: ./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: APP_URL
labelEn: Application URL
labelZh: 应用访问 URL
label:
en: Application URL
zh: 应用访问 URL
zh-Hant: 應用程式存取 URL
ja: アプリケーション URL
ko: 애플리케이션 URL
ru: URL приложения
ms: URL aplikasi
pt-br: URL do aplicativo
required: true
type: text
- default: ''
edit: true
envKey: APP_KEY
labelEn: Application key
labelZh: 应用密钥
label:
en: Application key
zh: 应用密钥
zh-Hant: 應用程式密鑰
ja: アプリケーションキー
ko: 애플리케이션 키
ru: Ключ приложения
ms: Kunci aplikasi
pt-br: Chave do aplicativo
required: true
random: true
type: password
rule: paramComplexity
- default: bookstack-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: 3306
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: bookstack
edit: true
envKey: DB_USERNAME
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: bookstack
edit: true
envKey: DB_DATABASE
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: ''
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: QUEUE_CONNECTION
labelEn: Async queue connection
labelZh: 异步队列连接
label:
en: Async queue connection
zh: 异步队列连接
zh-Hant: 非同步佇列連線
ja: 非同期キュー接続
ko: 비동기 큐 연결
ru: Подключение асинхронной очереди
ms: Sambungan baris gilir tak segerak
pt-br: Conexao de fila assincrona
required: false
type: select
values:
- label: disabled
value: ''
- label: database
value: database
- 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: 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
@@ -0,0 +1,44 @@
networks:
1panel-network:
external: true
services:
bookstack:
image: "linuxserver/bookstack:26.09.20260924"
container_name: ${CONTAINER_NAME}
restart: always
networks:
- 1panel-network
ports:
- "${PANEL_APP_PORT_HTTP}:80"
volumes:
- "${CONFIG_PATH}:/config"
environment:
- PUID=1000
- PGID=1000
- TZ=${TIME_ZONE}
- APP_URL=${APP_URL}
- APP_KEY=${APP_KEY}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- DB_DATABASE=${DB_DATABASE}
- QUEUE_CONNECTION=${QUEUE_CONNECTION:-}
depends_on:
- bookstack-db
labels:
createdBy: "Apps"
bookstack-db:
image: "mariadb:11.4"
restart: always
networks:
- 1panel-network
volumes:
- "${DB_DATA_PATH}:/var/lib/mysql"
environment:
- MARIADB_ROOT_PASSWORD=${DB_PASSWORD}
- MARIADB_DATABASE=${DB_DATABASE}
- MARIADB_USER=${DB_USERNAME}
- MARIADB_PASSWORD=${DB_PASSWORD}
labels:
createdBy: "Apps"
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env bash
set -euo pipefail
ENV_FILE="${ENV_FILE:-./.env}"
LEGACY_BOOKSTACK_APP_KEY="base64:j1wQ2YbRrQ3j0tG5h6v8cYpV1gW9mN4uQ2xZ7aE3sT8="
LEGACY_BOOKSTACK_DB_PASSWORD="bookstack-change-me"
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"
}
generate_laravel_app_key() {
if command -v openssl >/dev/null 2>&1; then
printf 'base64:%s\n' "$(openssl rand -base64 32 | tr -d '\r\n')"
return
fi
if command -v base64 >/dev/null 2>&1; then
printf 'base64:%s\n' "$(head -c 32 /dev/urandom | base64 | tr -d '\r\n')"
return
fi
generate_alnum_secret
}
laravel_app_key_is_valid() {
local value="$1"
[[ "$value" =~ ^base64:[A-Za-z0-9+/=]{43,}$ ]] && return 0
[[ "$value" =~ ^[A-Za-z0-9]{32,}$ ]] && return 0
return 1
}
CONFIG_PATH_VALUE="${CONFIG_PATH:-$(read_env_value CONFIG_PATH || true)}"
CONFIG_PATH_VALUE="${CONFIG_PATH_VALUE:-./data/config}"
APP_KEY_CACHE="${APP_KEY_CACHE:-${CONFIG_PATH_VALUE}/.bookstack_app_key}"
DB_PASSWORD_CACHE="${DB_PASSWORD_CACHE:-${CONFIG_PATH_VALUE}/.bookstack_db_password}"
if [[ -f "$ENV_FILE" ]]; then
app_key="$(read_env_value APP_KEY || true)"
cached_app_key="$(read_cache_value "$APP_KEY_CACHE" || true)"
if laravel_app_key_is_valid "$app_key" && [[ "$app_key" != "$LEGACY_BOOKSTACK_APP_KEY" ]]; then
write_cache_value "$APP_KEY_CACHE" "$app_key"
elif laravel_app_key_is_valid "$cached_app_key"; then
set_env_value "APP_KEY" "$cached_app_key"
else
app_key="$(generate_laravel_app_key)"
set_env_value "APP_KEY" "$app_key"
write_cache_value "$APP_KEY_CACHE" "$app_key"
fi
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_BOOKSTACK_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
fi
paths=(
"${CONFIG_PATH:-./data/config}"
"${DB_DATA_PATH:-./data/db}"
)
mkdir -p "${paths[@]}"
for path in "${paths[@]}"; do
case "$path" in
./*|../*) chown -R 1000:1000 "$path" 2>/dev/null || true ;;
esac
done
@@ -0,0 +1,2 @@
#!/bin/bash
docker-compose down --volumes
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail
ENV_FILE="${ENV_FILE:-./.env}"
LEGACY_BOOKSTACK_APP_KEY="base64:j1wQ2YbRrQ3j0tG5h6v8cYpV1gW9mN4uQ2xZ7aE3sT8="
LEGACY_BOOKSTACK_DB_PASSWORD="bookstack-change-me"
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"
}
generate_laravel_app_key() {
if command -v openssl >/dev/null 2>&1; then
printf 'base64:%s\n' "$(openssl rand -base64 32 | tr -d '\r\n')"
return
fi
if command -v base64 >/dev/null 2>&1; then
printf 'base64:%s\n' "$(head -c 32 /dev/urandom | base64 | tr -d '\r\n')"
return
fi
generate_alnum_secret
}
laravel_app_key_is_valid() {
local value="$1"
[[ "$value" =~ ^base64:[A-Za-z0-9+/=]{43,}$ ]] && return 0
[[ "$value" =~ ^[A-Za-z0-9]{32,}$ ]] && return 0
return 1
}
CONFIG_PATH_VALUE="${CONFIG_PATH:-$(read_env_value CONFIG_PATH || true)}"
CONFIG_PATH_VALUE="${CONFIG_PATH_VALUE:-./data/config}"
APP_KEY_CACHE="${APP_KEY_CACHE:-${CONFIG_PATH_VALUE}/.bookstack_app_key}"
DB_PASSWORD_CACHE="${DB_PASSWORD_CACHE:-${CONFIG_PATH_VALUE}/.bookstack_db_password}"
if [[ -f "$ENV_FILE" ]]; then
app_key="$(read_env_value APP_KEY || true)"
cached_app_key="$(read_cache_value "$APP_KEY_CACHE" || true)"
if laravel_app_key_is_valid "$app_key" && [[ "$app_key" != "$LEGACY_BOOKSTACK_APP_KEY" ]]; then
write_cache_value "$APP_KEY_CACHE" "$app_key"
elif laravel_app_key_is_valid "$cached_app_key"; then
set_env_value "APP_KEY" "$cached_app_key"
else
app_key="$(generate_laravel_app_key)"
set_env_value "APP_KEY" "$app_key"
write_cache_value "$APP_KEY_CACHE" "$app_key"
fi
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_BOOKSTACK_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
ensure_env_default "DB_HOST" "bookstack-db"
ensure_env_default "DB_PORT" "3306"
ensure_env_default "DB_USERNAME" "bookstack"
ensure_env_default "DB_DATABASE" "bookstack"
ensure_env_default "QUEUE_CONNECTION" ""
else
echo "$ENV_FILE not found; skipped LinuxServer environment migration"
fi
@@ -3,7 +3,7 @@ networks:
external: true
services:
calibre:
image: "linuxserver/calibre:9.15.0"
image: "linuxserver/calibre:9.16.0"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -1,6 +1,6 @@
services:
cpa-manager-plus:
image: "seakee/cpa-manager-plus:v1.14.4"
image: "seakee/cpa-manager-plus:v1.14.5"
container_name: ${CONTAINER_NAME}
ports:
- "${PANEL_APP_PORT_HTTP}:18317"
+1 -1
View File
@@ -1,6 +1,6 @@
services:
dagu:
image: "ghcr.io/dagucloud/dagu:2.18.2"
image: "ghcr.io/dagucloud/dagu:2.19.0"
container_name: ${CONTAINER_NAME}
restart: always
networks:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
dailytxt:
image: "phitux/dailytxt:2.6.3"
image: "phitux/dailytxt:2.6.4"
container_name: ${CONTAINER_NAME}
restart: unless-stopped
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
darktable:
image: "linuxserver/darktable:25.6.1"
image: "linuxserver/darktable:25.6.2"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
flexget:
image: "linuxserver/flexget:3.22.1"
image: "linuxserver/flexget:3.22.2"
container_name: ${CONTAINER_NAME}
restart: always
networks:
+1 -1
View File
@@ -14,7 +14,7 @@ services:
- PGID=1000
- TZ=${TIME_ZONE}
- FG_WEBUI_PASSWD=${WEB_PASSWORD}
image: wiserain/flexget:3.21.3
image: wiserain/flexget:3.22.2
labels:
createdBy: "Apps"
+1 -1
View File
@@ -1,6 +1,6 @@
services:
gotosocial:
image: "docker.io/superseriousbusiness/gotosocial:0.22.1"
image: "docker.io/superseriousbusiness/gotosocial:0.22.2"
container_name: ${CONTAINER_NAME}
user: "1000:1000"
restart: always
@@ -3,7 +3,7 @@ networks:
external: true
services:
grav:
image: "linuxserver/grav:2.2.5"
image: "linuxserver/grav:2.2.6"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
healthchecks:
image: "linuxserver/healthchecks:4.4.20261005"
image: "linuxserver/healthchecks:4.4.20261012"
container_name: ${CONTAINER_NAME}
restart: always
networks:
+97
View File
@@ -0,0 +1,97 @@
# Core runtime
CONTAINER_NAME=hermes-agent
TZ=Asia/Shanghai
APP_DATA_DIR=./data
HERMES_UID=10000
HERMES_GID=10000
PANEL_APP_PORT_API=8642
GATEWAY_STATIC_IP=172.18.0.240
API_SERVER_KEY=change_me_with_a_random_api_key
API_SERVER_CORS_ORIGINS=*
# LLM providers
OPENAI_API_KEY=
OPENROUTER_API_KEY=
ANTHROPIC_TOKEN=
ANTHROPIC_BASE_URL=
GOOGLE_API_KEY=
GLM_API_KEY=
GLM_BASE_URL=https://api.z.ai/api/paas/v4
KIMI_API_KEY=
KIMI_BASE_URL=https://api.kimi.com/coding/v1
MINIMAX_API_KEY=
MINIMAX_BASE_URL=https://api.minimax.io/v1
GROQ_API_KEY=
MISTRAL_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
TOGETHER_API_KEY=
PERPLEXITY_API_KEY=
FIREWORKS_API_KEY=
XAI_API_KEY=
HF_TOKEN=
# Search / crawling / telemetry
EXA_API_KEY=
PARALLEL_API_KEY=
FIRECRAWL_API_KEY=
FIRECRAWL_API_URL=
HELICONE_API_KEY=
# Gateway access control
GATEWAY_ALLOWED_USERS=
# Telegram
TELEGRAM_BOT_TOKEN=
TELEGRAM_ALLOWED_USERS=
TELEGRAM_HOME_CHANNEL=
TELEGRAM_HOME_CHANNEL_NAME=
# Discord
DISCORD_HOME_CHANNEL=
DISCORD_HOME_CHANNEL_NAME=
DISCORD_REQUIRE_MENTION=false
DISCORD_FREE_RESPONSE_CHANNELS=
DISCORD_AUTO_THREAD=false
# Slack
SLACK_HOME_CHANNEL=
SLACK_HOME_CHANNEL_NAME=
SLACK_ALLOWED_USERS=
# WhatsApp
WHATSAPP_ENABLED=false
WHATSAPP_MODE=personal
WHATSAPP_ALLOWED_USERS=
# Signal
SIGNAL_HTTP_URL=
SIGNAL_ACCOUNT=
SIGNAL_ALLOWED_USERS=
SIGNAL_GROUP_ALLOWED_USERS=
SIGNAL_HOME_CHANNEL=
SIGNAL_HOME_CHANNEL_NAME=
SIGNAL_IGNORE_STORIES=false
# Email
EMAIL_ADDRESS=
EMAIL_PASSWORD=
EMAIL_IMAP_HOST=
EMAIL_SMTP_HOST=
EMAIL_HOME_ADDRESS=
EMAIL_HOME_ADDRESS_NAME=
# GitHub
GH_TOKEN=
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY_PATH=
GITHUB_APP_INSTALLATION_ID=
# Home Assistant
HASS_URL=
HASS_TOKEN=
# Modal / Daytona
MODAL_TOKEN_ID=
MODAL_TOKEN_SECRET=
DAYTONA_API_KEY=
+1215
View File
File diff suppressed because it is too large Load Diff
View File
+92
View File
@@ -0,0 +1,92 @@
services:
hermes-agent:
image: "nousresearch/hermes-agent:v2026.9.24"
container_name: ${CONTAINER_NAME}
command:
- gateway
- run
environment:
- TZ=${TZ}
- HERMES_UID=${HERMES_UID:-10000}
- HERMES_GID=${HERMES_GID:-10000}
- API_SERVER_ENABLED=true
- API_SERVER_HOST=0.0.0.0
- API_SERVER_KEY=${API_SERVER_KEY}
- API_SERVER_CORS_ORIGINS=${API_SERVER_CORS_ORIGINS:-*}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- GOOGLE_API_KEY=${GOOGLE_API_KEY:-}
- GLM_API_KEY=${GLM_API_KEY:-}
- GLM_BASE_URL=${GLM_BASE_URL:-}
- KIMI_API_KEY=${KIMI_API_KEY:-}
- KIMI_BASE_URL=${KIMI_BASE_URL:-}
- MINIMAX_API_KEY=${MINIMAX_API_KEY:-}
- MINIMAX_BASE_URL=${MINIMAX_BASE_URL:-}
- ANTHROPIC_TOKEN=${ANTHROPIC_TOKEN:-}
- GROQ_API_KEY=${GROQ_API_KEY:-}
- MISTRAL_API_KEY=${MISTRAL_API_KEY:-}
- COHERE_API_KEY=${COHERE_API_KEY:-}
- DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY:-}
- TOGETHER_API_KEY=${TOGETHER_API_KEY:-}
- PERPLEXITY_API_KEY=${PERPLEXITY_API_KEY:-}
- FIREWORKS_API_KEY=${FIREWORKS_API_KEY:-}
- XAI_API_KEY=${XAI_API_KEY:-}
- HF_TOKEN=${HF_TOKEN:-}
- EXA_API_KEY=${EXA_API_KEY:-}
- PARALLEL_API_KEY=${PARALLEL_API_KEY:-}
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY:-}
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
- TELEGRAM_ALLOWED_USERS=${TELEGRAM_ALLOWED_USERS:-}
- TELEGRAM_HOME_CHANNEL=${TELEGRAM_HOME_CHANNEL:-}
- ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL:-}
- TELEGRAM_HOME_CHANNEL_NAME=${TELEGRAM_HOME_CHANNEL_NAME:-}
- DISCORD_HOME_CHANNEL=${DISCORD_HOME_CHANNEL:-}
- DISCORD_HOME_CHANNEL_NAME=${DISCORD_HOME_CHANNEL_NAME:-}
- DISCORD_REQUIRE_MENTION=${DISCORD_REQUIRE_MENTION:-}
- DISCORD_FREE_RESPONSE_CHANNELS=${DISCORD_FREE_RESPONSE_CHANNELS:-}
- DISCORD_AUTO_THREAD=${DISCORD_AUTO_THREAD:-}
- SLACK_HOME_CHANNEL=${SLACK_HOME_CHANNEL:-}
- SLACK_HOME_CHANNEL_NAME=${SLACK_HOME_CHANNEL_NAME:-}
- SLACK_ALLOWED_USERS=${SLACK_ALLOWED_USERS:-}
- WHATSAPP_ENABLED=${WHATSAPP_ENABLED:-}
- WHATSAPP_MODE=${WHATSAPP_MODE:-}
- WHATSAPP_ALLOWED_USERS=${WHATSAPP_ALLOWED_USERS:-}
- SIGNAL_HTTP_URL=${SIGNAL_HTTP_URL:-}
- SIGNAL_ACCOUNT=${SIGNAL_ACCOUNT:-}
- SIGNAL_ALLOWED_USERS=${SIGNAL_ALLOWED_USERS:-}
- SIGNAL_GROUP_ALLOWED_USERS=${SIGNAL_GROUP_ALLOWED_USERS:-}
- SIGNAL_HOME_CHANNEL=${SIGNAL_HOME_CHANNEL:-}
- SIGNAL_HOME_CHANNEL_NAME=${SIGNAL_HOME_CHANNEL_NAME:-}
- SIGNAL_IGNORE_STORIES=${SIGNAL_IGNORE_STORIES:-}
- HASS_URL=${HASS_URL:-}
- HASS_TOKEN=${HASS_TOKEN:-}
- HELICONE_API_KEY=${HELICONE_API_KEY:-}
- FIRECRAWL_API_URL=${FIRECRAWL_API_URL:-}
- GH_TOKEN=${GH_TOKEN:-}
- GITHUB_APP_ID=${GITHUB_APP_ID:-}
- GITHUB_APP_PRIVATE_KEY_PATH=${GITHUB_APP_PRIVATE_KEY_PATH:-}
- GITHUB_APP_INSTALLATION_ID=${GITHUB_APP_INSTALLATION_ID:-}
- EMAIL_ADDRESS=${EMAIL_ADDRESS:-}
- EMAIL_PASSWORD=${EMAIL_PASSWORD:-}
- EMAIL_IMAP_HOST=${EMAIL_IMAP_HOST:-}
- EMAIL_SMTP_HOST=${EMAIL_SMTP_HOST:-}
- EMAIL_HOME_ADDRESS=${EMAIL_HOME_ADDRESS:-}
- EMAIL_HOME_ADDRESS_NAME=${EMAIL_HOME_ADDRESS_NAME:-}
- GATEWAY_ALLOWED_USERS=${GATEWAY_ALLOWED_USERS:-}
- MODAL_TOKEN_ID=${MODAL_TOKEN_ID:-}
- MODAL_TOKEN_SECRET=${MODAL_TOKEN_SECRET:-}
- DAYTONA_API_KEY=${DAYTONA_API_KEY:-}
networks:
1panel-network:
ipv4_address: ${GATEWAY_STATIC_IP}
ports:
- "${PANEL_APP_PORT_API}:8642"
volumes:
- "${APP_DATA_DIR}:/opt/data"
restart: unless-stopped
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
APP_DATA_DIR_RAW="${APP_DATA_DIR:-./data}"
DEFAULT_UID="10000"
DEFAULT_GID="10000"
TARGET_UID="${HERMES_UID:-$DEFAULT_UID}"
TARGET_GID="${HERMES_GID:-$DEFAULT_GID}"
if [[ "$APP_DATA_DIR_RAW" = /* ]]; then
APP_DATA_DIR_ABS="$APP_DATA_DIR_RAW"
else
APP_DATA_DIR_ABS="$ROOT_DIR/${APP_DATA_DIR_RAW#./}"
fi
mkdir -p "$APP_DATA_DIR_ABS"
upsert_env_if_missing() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
fi
}
generate_random_value() {
local length="$1"
local value=""
while [[ ${#value} -lt $length ]]; do
value+="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$((length - ${#value}))" || true)"
done
printf '%s' "$value"
}
ensure_env_value() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
elif grep -Eq "^${key}=([[:space:]]*|\"\"|'')$" "$ENV_FILE"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
fi
}
if [[ -f "$ENV_FILE" ]]; then
EXISTING_UID=$(grep '^HERMES_UID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
EXISTING_GID=$(grep '^HERMES_GID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
[[ -n "$EXISTING_UID" ]] && TARGET_UID="$EXISTING_UID"
[[ -n "$EXISTING_GID" ]] && TARGET_GID="$EXISTING_GID"
fi
upsert_env_if_missing "HERMES_UID" "$TARGET_UID"
upsert_env_if_missing "HERMES_GID" "$TARGET_GID"
ensure_env_value "API_SERVER_KEY" "$(generate_random_value 48)"
ensure_env_value "API_SERVER_CORS_ORIGINS" "*"
CURRENT_UID=$(stat -c '%u' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
CURRENT_GID=$(stat -c '%g' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
if [[ "$CURRENT_UID" != "$TARGET_UID" || "$CURRENT_GID" != "$TARGET_GID" ]]; then
chown -R "$TARGET_UID:$TARGET_GID" "$APP_DATA_DIR_ABS" 2>/dev/null || true
fi
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
docker-compose down --volumes
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
APP_DATA_DIR_RAW="${APP_DATA_DIR:-./data}"
DEFAULT_UID="10000"
DEFAULT_GID="10000"
TARGET_UID="${HERMES_UID:-$DEFAULT_UID}"
TARGET_GID="${HERMES_GID:-$DEFAULT_GID}"
if [[ "$APP_DATA_DIR_RAW" = /* ]]; then
APP_DATA_DIR_ABS="$APP_DATA_DIR_RAW"
else
APP_DATA_DIR_ABS="$ROOT_DIR/${APP_DATA_DIR_RAW#./}"
fi
mkdir -p "$APP_DATA_DIR_ABS"
upsert_env_if_missing() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
fi
}
generate_random_value() {
local length="$1"
local value=""
while [[ ${#value} -lt $length ]]; do
value+="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$((length - ${#value}))" || true)"
done
printf '%s' "$value"
}
ensure_env_value() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
elif grep -Eq "^${key}=([[:space:]]*|\"\"|'')$" "$ENV_FILE"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
fi
}
if [[ -f "$ENV_FILE" ]]; then
EXISTING_UID=$(grep '^HERMES_UID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
EXISTING_GID=$(grep '^HERMES_GID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
[[ -n "$EXISTING_UID" ]] && TARGET_UID="$EXISTING_UID"
[[ -n "$EXISTING_GID" ]] && TARGET_GID="$EXISTING_GID"
fi
upsert_env_if_missing "HERMES_UID" "$TARGET_UID"
upsert_env_if_missing "HERMES_GID" "$TARGET_GID"
ensure_env_value "API_SERVER_KEY" "$(generate_random_value 48)"
ensure_env_value "API_SERVER_CORS_ORIGINS" "*"
CURRENT_UID=$(stat -c '%u' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
CURRENT_GID=$(stat -c '%g' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
if [[ "$CURRENT_UID" != "$TARGET_UID" || "$CURRENT_GID" != "$TARGET_GID" ]]; then
chown -R "$TARGET_UID:$TARGET_GID" "$APP_DATA_DIR_ABS" 2>/dev/null || true
fi
+10
View File
@@ -0,0 +1,10 @@
CONTAINER_NAME=hermes-dashboard
TZ=Asia/Shanghai
APP_DATA_DIR=/opt/1panel/apps/local/hermes-agent/hermes-agent/data
HERMES_UID=10000
HERMES_GID=10000
PANEL_APP_PORT_HTTP=9119
GATEWAY_HEALTH_URL=http://172.18.0.240:8642
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=change_me_with_a_strong_password
DASHBOARD_SESSION_SECRET=change_me_with_a_random_session_secret
+169
View File
@@ -0,0 +1,169 @@
additionalProperties:
formFields:
- default: "Hermes Dashboard 是附属应用。请先部署 Hermes gateway,并把 APP_DATA_DIR 指向 Hermes Agent 的同一份数据目录,再把 GATEWAY_HEALTH_URL 填成它的完整地址。公开绑定现在强制启用认证,`--insecure` 已不能绕过认证;请设置 Dashboard 用户名、强密码和会话签名密钥。旧版本升级时,脚本只会为缺失或空白的认证项生成随机值,不会覆盖已有配置;升级完成后请在应用参数中设置自己知道的密码。"
disabled: true
envKey: TIPS
labelEn: Tips
labelZh: 提示
label:
en: Tips
zh: 提示
zh-Hant: 提示
ja: ヒント
ko: 팁
ru: Подсказка
ms: Petua
pt-br: Dicas
edit: true
required: true
type: text
- default: Asia/Shanghai
envKey: TZ
labelEn: Timezone
labelZh: 时区
label:
en: Timezone
zh: 时区
zh-Hant: 時區
ja: タイムゾーン
ko: 시간대
ru: Часовой пояс
ms: Zon waktu
pt-br: Fuso horário
edit: true
required: true
type: text
- default: /opt/1panel/apps/local/hermes-agent/hermes-agent/data
envKey: APP_DATA_DIR
labelEn: Data Directory
labelZh: 数据目录
label:
en: Data Directory
zh: 数据目录
zh-Hant: 資料目錄
ja: データディレクトリ
ko: 데이터 디렉터리
ru: Каталог данных
ms: Direktori data
pt-br: Diretório de dados
edit: true
required: true
type: text
- default: 10000
envKey: HERMES_UID
labelEn: Hermes Runtime UID
labelZh: Hermes 运行用户 UID
label:
en: Hermes Runtime UID
zh: Hermes 运行用户 UID
zh-Hant: Hermes 執行使用者 UID
ja: Hermes 実行ユーザー UID
ko: Hermes 실행 사용자 UID
ru: UID пользователя Hermes
ms: UID pengguna runtime Hermes
pt-br: UID do usuário de execução do Hermes
edit: true
required: true
type: number
- default: 10000
envKey: HERMES_GID
labelEn: Hermes Runtime GID
labelZh: Hermes 运行用户 GID
label:
en: Hermes Runtime GID
zh: Hermes 运行用户 GID
zh-Hant: Hermes 執行使用者 GID
ja: Hermes 実行ユーザー GID
ko: Hermes 실행 사용자 GID
ru: GID пользователя Hermes
ms: GID pengguna runtime Hermes
pt-br: GID do usuário de execução do Hermes
edit: true
required: true
type: number
- default: 9119
envKey: PANEL_APP_PORT_HTTP
labelEn: Dashboard Port
labelZh: Dashboard 端口
label:
en: Dashboard Port
zh: Dashboard 端口
zh-Hant: Dashboard 連接埠
ja: Dashboard ポート
ko: 대시보드 포트
ru: Порт dashboard
ms: Port dashboard
pt-br: Porta do dashboard
edit: true
required: true
rule: paramPort
type: number
- default: http://172.18.0.240:8642
envKey: GATEWAY_HEALTH_URL
labelEn: Gateway Health URL
labelZh: 网关健康检查 URL
label:
en: Gateway Health URL
zh: 网关健康检查 URL
zh-Hant: 閘道健康檢查 URL
ja: Gateway ヘルス URL
ko: 게이트웨이 헬스 URL
ru: URL проверки шлюза
ms: URL kesihatan gateway
pt-br: URL de verificação do gateway
edit: true
required: true
rule: paramExtUrl
type: text
- default: admin
envKey: DASHBOARD_USERNAME
labelEn: Dashboard Username
labelZh: Dashboard 用户名
label:
en: Dashboard Username
zh: Dashboard 用户名
zh-Hant: Dashboard 使用者名稱
ja: Dashboard ユーザー名
ko: Dashboard 사용자 이름
ru: Имя пользователя dashboard
ms: Nama pengguna dashboard
pt-br: Usuário do dashboard
edit: true
required: true
type: text
- default: ""
envKey: DASHBOARD_PASSWORD
labelEn: Dashboard Password
labelZh: Dashboard 密码
label:
en: Dashboard Password
zh: Dashboard 密码
zh-Hant: Dashboard 密碼
ja: Dashboard パスワード
ko: Dashboard 비밀번호
ru: Пароль dashboard
ms: Kata laluan dashboard
pt-br: Senha do dashboard
edit: true
random: true
required: true
rule: paramComplexity
type: password
- default: ""
envKey: DASHBOARD_SESSION_SECRET
labelEn: Dashboard Session Secret
labelZh: Dashboard 会话签名密钥
label:
en: Dashboard Session Secret
zh: Dashboard 会话签名密钥
zh-Hant: Dashboard 工作階段簽章密鑰
ja: Dashboard セッション署名シークレット
ko: Dashboard 세션 서명 비밀값
ru: Секрет подписи сессии dashboard
ms: Rahsia tandatangan sesi dashboard
pt-br: Segredo de assinatura da sessão do dashboard
edit: true
random: true
required: true
rule: paramComplexity
type: password
+30
View File
@@ -0,0 +1,30 @@
services:
hermes-dashboard:
image: "nousresearch/hermes-agent:v2026.9.24"
container_name: ${CONTAINER_NAME}
command:
- dashboard
- --host
- 0.0.0.0
- --no-open
environment:
- TZ=${TZ}
- HERMES_UID=${HERMES_UID:-10000}
- HERMES_GID=${HERMES_GID:-10000}
- GATEWAY_HEALTH_URL=${GATEWAY_HEALTH_URL}
- HERMES_DASHBOARD_BASIC_AUTH_USERNAME=${DASHBOARD_USERNAME}
- HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=${DASHBOARD_PASSWORD}
- HERMES_DASHBOARD_BASIC_AUTH_SECRET=${DASHBOARD_SESSION_SECRET}
ports:
- "${PANEL_APP_PORT_HTTP}:9119"
volumes:
- "${APP_DATA_DIR}:/opt/data"
restart: unless-stopped
networks:
- 1panel-network
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
APP_DATA_DIR_RAW="${APP_DATA_DIR:-./data}"
DEFAULT_UID="10000"
DEFAULT_GID="10000"
TARGET_UID="${HERMES_UID:-$DEFAULT_UID}"
TARGET_GID="${HERMES_GID:-$DEFAULT_GID}"
if [[ "$APP_DATA_DIR_RAW" = /* ]]; then
APP_DATA_DIR_ABS="$APP_DATA_DIR_RAW"
else
APP_DATA_DIR_ABS="$ROOT_DIR/${APP_DATA_DIR_RAW#./}"
fi
mkdir -p "$APP_DATA_DIR_ABS"
upsert_env_if_missing() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
fi
}
generate_random_value() {
local length="$1"
local value=""
while [[ ${#value} -lt $length ]]; do
value+="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$((length - ${#value}))" || true)"
done
printf '%s' "$value"
}
ensure_env_value() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
elif grep -Eq "^${key}=([[:space:]]*|\"\"|'')$" "$ENV_FILE"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
fi
}
if [[ -f "$ENV_FILE" ]]; then
EXISTING_UID=$(grep '^HERMES_UID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
EXISTING_GID=$(grep '^HERMES_GID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
[[ -n "$EXISTING_UID" ]] && TARGET_UID="$EXISTING_UID"
[[ -n "$EXISTING_GID" ]] && TARGET_GID="$EXISTING_GID"
fi
upsert_env_if_missing "HERMES_UID" "$TARGET_UID"
upsert_env_if_missing "HERMES_GID" "$TARGET_GID"
ensure_env_value "DASHBOARD_USERNAME" "admin"
ensure_env_value "DASHBOARD_PASSWORD" "$(generate_random_value 32)"
ensure_env_value "DASHBOARD_SESSION_SECRET" "$(generate_random_value 48)"
CURRENT_UID=$(stat -c '%u' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
CURRENT_GID=$(stat -c '%g' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
if [[ "$CURRENT_UID" != "$TARGET_UID" || "$CURRENT_GID" != "$TARGET_GID" ]]; then
chown -R "$TARGET_UID:$TARGET_GID" "$APP_DATA_DIR_ABS" 2>/dev/null || true
fi
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
docker-compose down --volumes
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
APP_DATA_DIR_RAW="${APP_DATA_DIR:-./data}"
DEFAULT_UID="10000"
DEFAULT_GID="10000"
TARGET_UID="${HERMES_UID:-$DEFAULT_UID}"
TARGET_GID="${HERMES_GID:-$DEFAULT_GID}"
if [[ "$APP_DATA_DIR_RAW" = /* ]]; then
APP_DATA_DIR_ABS="$APP_DATA_DIR_RAW"
else
APP_DATA_DIR_ABS="$ROOT_DIR/${APP_DATA_DIR_RAW#./}"
fi
mkdir -p "$APP_DATA_DIR_ABS"
upsert_env_if_missing() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
fi
}
generate_random_value() {
local length="$1"
local value=""
while [[ ${#value} -lt $length ]]; do
value+="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$((length - ${#value}))" || true)"
done
printf '%s' "$value"
}
ensure_env_value() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
return 0
fi
if ! grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
elif grep -Eq "^${key}=([[:space:]]*|\"\"|'')$" "$ENV_FILE"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
fi
}
if [[ -f "$ENV_FILE" ]]; then
EXISTING_UID=$(grep '^HERMES_UID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
EXISTING_GID=$(grep '^HERMES_GID=' "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
[[ -n "$EXISTING_UID" ]] && TARGET_UID="$EXISTING_UID"
[[ -n "$EXISTING_GID" ]] && TARGET_GID="$EXISTING_GID"
fi
upsert_env_if_missing "HERMES_UID" "$TARGET_UID"
upsert_env_if_missing "HERMES_GID" "$TARGET_GID"
ensure_env_value "DASHBOARD_USERNAME" "admin"
ensure_env_value "DASHBOARD_PASSWORD" "$(generate_random_value 32)"
ensure_env_value "DASHBOARD_SESSION_SECRET" "$(generate_random_value 48)"
CURRENT_UID=$(stat -c '%u' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
CURRENT_GID=$(stat -c '%g' "$APP_DATA_DIR_ABS" 2>/dev/null || echo '')
if [[ "$CURRENT_UID" != "$TARGET_UID" || "$CURRENT_GID" != "$TARGET_GID" ]]; then
chown -R "$TARGET_UID:$TARGET_GID" "$APP_DATA_DIR_ABS" 2>/dev/null || true
fi
+1 -1
View File
@@ -1,6 +1,6 @@
services:
iptv-api:
image: "guovern/iptv-api:3.0.0"
image: "guovern/iptv-api:3.1.0"
container_name: ${CONTAINER_NAME}
restart: always
tty: true
@@ -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
+505
View File
@@ -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
@@ -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"
+236
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
docker-compose down --volumes
+251
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
services:
mt-photos:
image: "mtphotos/mt-photos:1.57.1"
image: "mtphotos/mt-photos:1.57.2"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
nzbget:
image: "linuxserver/nzbget:26.3.20261002"
image: "linuxserver/nzbget:26.3.20261009"
container_name: ${CONTAINER_NAME}
restart: always
networks:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
odoo:
image: "odoo:19.0"
image: "odoo:20.0"
container_name: ${CONTAINER_NAME}
user: root
restart: always
+1 -1
View File
@@ -1,6 +1,6 @@
services:
piclist:
image: "kuingsmile/piclist:v2.4.2"
image: "kuingsmile/piclist:v3.0.0"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
qdirstat:
image: "linuxserver/qdirstat:2.0.20260928"
image: "linuxserver/qdirstat:2.0.20261012"
container_name: ${CONTAINER_NAME}
restart: always
networks:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
rocketchat:
image: "registry.rocket.chat/rocketchat/rocket.chat:8.8.1"
image: "registry.rocket.chat/rocketchat/rocket.chat:8.9.0"
container_name: ${CONTAINER_NAME}
restart: always
depends_on:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
rocketchat:
image: "registry.rocket.chat/rocketchat/rocket.chat:8.8.1"
image: "registry.rocket.chat/rocketchat/rocket.chat:8.9.0"
container_name: ${CONTAINER_NAME}
restart: always
depends_on:
@@ -3,7 +3,7 @@ networks:
external: true
services:
ungoogled-chromium:
image: "linuxserver/ungoogled-chromium:154.0.8037"
image: "linuxserver/ungoogled-chromium:155.0.8059"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
vlc:
image: "linuxserver/vlc:3.0.23"
image: "linuxserver/vlc:3.0.24"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -3,7 +3,7 @@ networks:
external: true
services:
zen:
image: "linuxserver/zen:1.23.1"
image: "linuxserver/zen:1.23.2"
container_name: ${CONTAINER_NAME}
restart: always
networks: