diff --git a/apps/bookstack-linuxserver/26.09.20260924/.env.sample b/apps/bookstack-linuxserver/26.09.20260924/.env.sample new file mode 100644 index 000000000..1c3f1ae34 --- /dev/null +++ b/apps/bookstack-linuxserver/26.09.20260924/.env.sample @@ -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 diff --git a/apps/bookstack-linuxserver/26.09.20260924/data.yml b/apps/bookstack-linuxserver/26.09.20260924/data.yml new file mode 100644 index 000000000..3c9be7783 --- /dev/null +++ b/apps/bookstack-linuxserver/26.09.20260924/data.yml @@ -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 diff --git a/apps/bookstack-linuxserver/26.09.20260924/data/.gitkeep b/apps/bookstack-linuxserver/26.09.20260924/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/bookstack-linuxserver/26.09.20260924/docker-compose.yml b/apps/bookstack-linuxserver/26.09.20260924/docker-compose.yml new file mode 100644 index 000000000..04cd10b57 --- /dev/null +++ b/apps/bookstack-linuxserver/26.09.20260924/docker-compose.yml @@ -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" diff --git a/apps/bookstack-linuxserver/26.09.20260924/scripts/init.sh b/apps/bookstack-linuxserver/26.09.20260924/scripts/init.sh new file mode 100755 index 000000000..b80a36e37 --- /dev/null +++ b/apps/bookstack-linuxserver/26.09.20260924/scripts/init.sh @@ -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 diff --git a/apps/bookstack-linuxserver/26.09.20260924/scripts/uninstall.sh b/apps/bookstack-linuxserver/26.09.20260924/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/bookstack-linuxserver/26.09.20260924/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/bookstack-linuxserver/26.09.20260924/scripts/upgrade.sh b/apps/bookstack-linuxserver/26.09.20260924/scripts/upgrade.sh new file mode 100755 index 000000000..8d29e5f48 --- /dev/null +++ b/apps/bookstack-linuxserver/26.09.20260924/scripts/upgrade.sh @@ -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