From d0973b63013d23d2800bd318a7f7e29cdd7f33b3 Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sun, 18 Oct 2026 06:22:14 +0800 Subject: [PATCH] fix(libredb-studio): preserve custom data paths in 0.18.3 --- apps/libredb-studio/0.18.3/.env.sample | 4 + apps/libredb-studio/0.18.3/data.yml | 51 +++++ apps/libredb-studio/0.18.3/data/.gitkeep | 0 apps/libredb-studio/0.18.3/docker-compose.yml | 16 ++ apps/libredb-studio/0.18.3/scripts/.gitkeep | 0 apps/libredb-studio/0.18.3/scripts/init.sh | 184 ++++++++++++++++++ .../0.18.3/scripts/uninstall.sh | 2 + apps/libredb-studio/0.18.3/scripts/upgrade.sh | 5 + 8 files changed, 262 insertions(+) create mode 100644 apps/libredb-studio/0.18.3/.env.sample create mode 100644 apps/libredb-studio/0.18.3/data.yml create mode 100644 apps/libredb-studio/0.18.3/data/.gitkeep create mode 100644 apps/libredb-studio/0.18.3/docker-compose.yml create mode 100644 apps/libredb-studio/0.18.3/scripts/.gitkeep create mode 100755 apps/libredb-studio/0.18.3/scripts/init.sh create mode 100755 apps/libredb-studio/0.18.3/scripts/uninstall.sh create mode 100755 apps/libredb-studio/0.18.3/scripts/upgrade.sh diff --git a/apps/libredb-studio/0.18.3/.env.sample b/apps/libredb-studio/0.18.3/.env.sample new file mode 100644 index 000000000..4021823c1 --- /dev/null +++ b/apps/libredb-studio/0.18.3/.env.sample @@ -0,0 +1,4 @@ +CONTAINER_NAME=libredb-studio-compose-check +PANEL_APP_PORT_HTTP=3000 +TZ=Asia/Shanghai +APP_DATA_DIR_1=./data diff --git a/apps/libredb-studio/0.18.3/data.yml b/apps/libredb-studio/0.18.3/data.yml new file mode 100644 index 000000000..a0de60952 --- /dev/null +++ b/apps/libredb-studio/0.18.3/data.yml @@ -0,0 +1,51 @@ +additionalProperties: + formFields: + - default: 3000 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text + - default: ./data + edit: true + envKey: APP_DATA_DIR_1 + labelEn: Data Directory 1 + labelZh: 数据目录 1 + label: + en: Data Directory 1 + zh: 数据目录 1 + zh-Hant: 資料目錄 1 + ja: データディレクトリ 1 + ko: 데이터 디렉터리 1 + ru: Каталог данных 1 + ms: Direktori data 1 + pt-br: Diretório de dados 1 + required: true + type: text diff --git a/apps/libredb-studio/0.18.3/data/.gitkeep b/apps/libredb-studio/0.18.3/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/libredb-studio/0.18.3/docker-compose.yml b/apps/libredb-studio/0.18.3/docker-compose.yml new file mode 100644 index 000000000..7a39b9e5c --- /dev/null +++ b/apps/libredb-studio/0.18.3/docker-compose.yml @@ -0,0 +1,16 @@ +services: + libredb-studio: + image: "libredb/libredb-studio:0.18.3" + container_name: ${CONTAINER_NAME} + ports: + - "${PANEL_APP_PORT_HTTP}:3000" + environment: + - TZ=${TZ} + - STORAGE_PROVIDER=sqlite + - STORAGE_SQLITE_PATH=/app/data/libredb-storage.db + labels: + createdBy: "Apps" + restart: on-failure:3 + volumes: + - "${APP_DATA_DIR_1}:/app/data" +networks: {} diff --git a/apps/libredb-studio/0.18.3/scripts/.gitkeep b/apps/libredb-studio/0.18.3/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/libredb-studio/0.18.3/scripts/init.sh b/apps/libredb-studio/0.18.3/scripts/init.sh new file mode 100755 index 000000000..7d76e9388 --- /dev/null +++ b/apps/libredb-studio/0.18.3/scripts/init.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +trim() { + local value="$1" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s\n' "$value" +} + +read_env_value() { + local key="$1" + [[ -f "$ENV_FILE" ]] || return 0 + local value + # Read as the compose file reads it, trimmed before quotes come off. + value="$(sed -n \ + -e "s/^[[:space:]]*export[[:space:]]\{1,\}${key}[[:space:]]*=//p;t" \ + -e "s/^[[:space:]]*${key}[[:space:]]*=//p" \ + "$ENV_FILE" | tail -n 1)" + value="$(trim "$value")" + case "$value" in + \"*\") value="${value#\"}"; value="${value%\"}" ;; + \'*\') value="${value#\'}"; value="${value%\'}" ;; + esac + printf '%s\n' "$value" +} + +env_has_key() { + local key="$1" + [[ -f "$ENV_FILE" ]] || return 1 + grep -qE "^[[:space:]]*(export[[:space:]]+)?${key}[[:space:]]*=" "$ENV_FILE" +} + +# A value that is set but empty is not defaulted. Compose refuses the project on +# one, so saying why here beats letting it fail with an opaque mount spec. +configured_value() { + local key="$1" + local default_value="$2" + local value + if [[ -n "${!key+x}" ]]; then + value="$(trim "${!key}")" + if [[ -z "$value" ]]; then + printf '%s\n' "unsafe ${key} path: ${key} is set to an empty value" >&2 + return 1 + fi + printf '%s\n' "$value" + return 0 + fi + value="$(read_env_value "$key")" + if [[ -z "$value" ]] && env_has_key "$key"; then + printf '%s\n' "unsafe ${key} path: ${key} in .env has an empty value" >&2 + return 1 + fi + printf '%s\n' "${value:-$default_value}" +} + +# Resolves . and .. textually, as the daemon cleans a bind source. realpath +# resolves symlinks first and lands elsewhere when one precedes a .. . +lexclean() { + local path="$1" + local part out="" absolute=0 n=0 i + local -a parts=() stack=() + [[ "$path" == /* ]] && absolute=1 + IFS='/' read -r -a parts <<< "$path" + for part in ${parts[@]+"${parts[@]}"}; do + case "$part" in + ''|.) continue ;; + ..) + if (( n > 0 )) && [[ "${stack[n-1]}" != ".." ]]; then + n=$(( n - 1 )) + elif (( absolute == 0 )); then + stack[n]=".."; n=$(( n + 1 )) + fi + ;; + *) stack[n]="$part"; n=$(( n + 1 )) ;; + esac + done + for (( i = 0; i < n; i++ )); do + out="$out/${stack[i]}" + done + if (( absolute )); then + printf '%s\n' "${out:-/}" + else + printf '%s\n' "${out#/}" + fi +} + +# An absolute value is kept, so an upgrade finds the data where the installation +# keeps it. A relative value stays under the app directory. +resolve_app_path() { + local key="$1" + local raw="$2" + local rest clean candidate resolved current part + local -a parts=() + + [[ -n "$raw" ]] || { printf '%s\n' "unsafe ${key} path" >&2; return 1; } + if [[ "$raw" =~ [[:cntrl:]] ]]; then + printf '%s\n' "unsafe ${key} path" >&2 + return 1 + fi + # The compose file parses the same value and reads these differently. + case "$raw" in + *'$'*|*:*|'#'*|*[[:space:]]'#'*|*\\*|*\"*|*\'*) + printf '%s\n' "unsafe ${key} path: remove the \$ : \\ \" ' or word-opening # from ${key} in .env, because the compose file reads those and would mount a different directory than this script creates" >&2 + return 1 ;; + esac + + if [[ "$raw" == '~'* ]]; then + if [[ -z "${HOME:-}" ]]; then + printf '%s\n' "unsafe ${key} path: ~ cannot be resolved because HOME is not set" >&2 + return 1 + fi + rest="${raw#\~}"; rest="${rest#/}" + raw="${HOME%/}${rest:+/$rest}" + fi + + if [[ "$raw" == /* ]]; then + resolved="$(lexclean "$raw")" + if [[ "$resolved" == "/" ]]; then + printf '%s\n' "unsafe ${key} path: the root directory cannot hold the app data" >&2 + return 1 + fi + current="" + IFS='/' read -r -a parts <<< "$resolved" + for part in ${parts[@]+"${parts[@]}"}; do + [[ -z "$part" ]] && continue + current="$current/$part" + if [[ -L "$current" && ! -e "$current" ]]; then + printf '%s\n' "unsafe ${key} path: ${current} is a link that does not resolve to an existing path" >&2 + return 1 + fi + done + if [[ -e "$resolved" && ! -d "$resolved" ]]; then + printf '%s\n' "unsafe ${key} path: ${resolved} exists and is not a directory" >&2 + return 1 + fi + printf '%s\n' "$resolved" + return 0 + fi + + # Compose reads a bare name as a named volume and refuses the whole project. + if [[ "$raw" != .* ]]; then + printf '%s\n' "unsafe ${key} path: a relative value has to begin with a dot, as ./data does" >&2 + return 1 + fi + case "$raw" in + .|..|../*|*/../*|*/..) printf '%s\n' "unsafe ${key} path" >&2; return 1 ;; + esac + clean="$(lexclean "$raw")" + [[ -n "$clean" ]] || { printf '%s\n' "unsafe ${key} path" >&2; return 1; } + candidate="$ROOT_DIR/$clean" + case "$candidate" in + "$ROOT_DIR"/*) ;; + *) printf '%s\n' "unsafe ${key} path" >&2; return 1 ;; + esac + current="$ROOT_DIR" + IFS='/' read -r -a parts <<< "$clean" + for part in ${parts[@]+"${parts[@]}"}; do + [[ -z "$part" || "$part" == "." ]] && continue + current="$current/$part" + if [[ -L "$current" ]]; then + printf '%s\n' "unsafe ${key} path" >&2 + return 1 + fi + done + if [[ -e "$candidate" && ! -d "$candidate" ]]; then + printf '%s\n' "unsafe ${key} path: ${candidate} exists and is not a directory" >&2 + return 1 + fi + printf '%s\n' "$candidate" +} + +ensure_dir() { + local key="$1" + local raw path + raw="$(configured_value "$key" "$2")" + path="$(resolve_app_path "$key" "$raw")" + mkdir -p -- "$path" +} + +ensure_dir "APP_DATA_DIR_1" "./data" diff --git a/apps/libredb-studio/0.18.3/scripts/uninstall.sh b/apps/libredb-studio/0.18.3/scripts/uninstall.sh new file mode 100755 index 000000000..bf82cc8bf --- /dev/null +++ b/apps/libredb-studio/0.18.3/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +true diff --git a/apps/libredb-studio/0.18.3/scripts/upgrade.sh b/apps/libredb-studio/0.18.3/scripts/upgrade.sh new file mode 100755 index 000000000..980ffd27c --- /dev/null +++ b/apps/libredb-studio/0.18.3/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +exec "${SCRIPT_DIR}/init.sh"