Files
appstore/apps/serverkit/latest/scripts/init.sh
T
okxlin c69d1fee40 Use app-local bind mount for ServerKit data
Store the SQLite data directory under the 1Panel application install path so application backups include it. Initialize the bind directory with the image runtime UID/GID and keep both packaged versions upgrade-safe.
2026-09-17 19:40:46 +08:00

176 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
read_env_value() {
local key="$1"
[[ -f "$ENV_FILE" ]] || return 0
local value
value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
case "$value" in
\"*\") value="${value#\"}"; value="${value%\"}" ;;
\'*\') value="${value#\'}"; value="${value%\'}" ;;
esac
printf '%s\n' "$value"
}
configured_value() {
local key="$1"
local default_value="$2"
local value
value="${!key:-}"
if [[ -z "$value" ]]; then
value="$(read_env_value "$key")"
fi
printf '%s\n' "${value:-$default_value}"
}
resolve_app_path() {
local key="$1"
local raw="$2"
local clean candidate resolved current part
local -a parts=()
case "$raw" in
""|/*|.|..|../*|*/../*|*/..) echo "unsafe ${key} path" >&2; return 1 ;;
esac
if [[ "$raw" =~ [[:cntrl:]] ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
clean="${raw#./}"
[[ -n "$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
command -v realpath >/dev/null 2>&1 || { echo "realpath is required" >&2; return 1; }
candidate="$ROOT_DIR/$clean"
resolved="$(realpath -m -- "$candidate")" || { echo "unsafe ${key} path" >&2; return 1; }
case "$resolved" in
"$ROOT_DIR"/*) ;;
*) echo "unsafe ${key} path" >&2; return 1 ;;
esac
current="$ROOT_DIR"
IFS='/' read -r -a parts <<< "$clean"
for part in "${parts[@]}"; do
[[ -z "$part" || "$part" == "." ]] && continue
current="$current/$part"
if [[ -L "$current" ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
done
printf '%s\n' "$resolved"
}
ensure_dir() {
local key="$1"
local raw
local path
raw="$(configured_value "$key" "$2")"
path="$(resolve_app_path "$key" "$raw")"
mkdir -p -- "$path"
[[ "$(resolve_app_path "$key" "$raw")" == "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
}
resolve_direct_child() {
local key="$1"
local raw="$2"
local clean path
clean="${raw#./}"
if [[ -z "$clean" || "$clean" == */* ]]; then
echo "unsafe ${key} path: lifecycle directories must be direct children of the version root" >&2
return 1
fi
path="$(resolve_app_path "$key" "$raw")"
[[ "$path" == "$ROOT_DIR/$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
printf '%s\n' "$path"
}
verify_trusted_root_chain() {
local current owner mode
[[ "$(id -u)" == "0" ]] || { echo "directory ownership initialization must run as root" >&2; return 1; }
command -v stat >/dev/null 2>&1 || { echo "stat is required" >&2; return 1; }
current="$ROOT_DIR"
while [[ "$current" != "/" ]]; do
[[ -d "$current" && ! -L "$current" ]] || { echo "unsafe version root chain: $current" >&2; return 1; }
IFS=':' read -r owner mode < <(stat -c '%u:%a' -- "$current")
if [[ "$current" == "$ROOT_DIR" ]]; then
# 1Panel local-app copies may be owned by the panel agent UID 1001.
[[ "$owner" == "0" || "$owner" == "1001" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
else
[[ "$owner" == "0" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
fi
[[ "$mode" =~ ^[0-7]{3,4}$ ]] || { echo "unsafe version root chain mode: $current" >&2; return 1; }
(( (8#$mode & 0022) == 0 )) || { echo "unsafe version root chain permissions: $current" >&2; return 1; }
current="$(dirname -- "$current")"
done
}
declare -A OWNED_PATHS=()
declare -A OWNED_KEYS_BY_PATH=()
register_owned_dir() {
local key="$1"
local raw="$2"
local path previous_key
path="$(resolve_direct_child "$key" "$raw")"
if [[ -n "${OWNED_KEYS_BY_PATH[$path]+x}" ]]; then
previous_key="${OWNED_KEYS_BY_PATH[$path]}"
echo "duplicate directory ownership target: $path ($previous_key and $key)" >&2
return 1
fi
OWNED_KEYS_BY_PATH["$path"]="$key"
OWNED_PATHS["$key"]="$path"
}
register_configured_owned_dir() {
local key="$1"
local default_value="$2"
local raw
raw="$(configured_value "$key" "$default_value")"
register_owned_dir "$key" "$raw"
}
register_fixed_owned_dir() {
local source="$1"
register_owned_dir "fixed directory $source" "$source"
}
apply_owned_dir() {
local key="$1"
local uid="$2"
local gid="$3"
local mode="$4"
local path actual expected_mode
[[ -n "${OWNED_PATHS[$key]+x}" ]] || { echo "missing directory ownership preflight: $key" >&2; return 1; }
path="${OWNED_PATHS[$key]}"
verify_trusted_root_chain
if [[ -e "$path" || -L "$path" ]]; then
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
else
mkdir -- "$path"
fi
chmod "$mode" -- "$path"
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
chown --no-dereference "$uid:$gid" -- "$path"
expected_mode="${mode#0}"
actual="$(stat -c '%u:%g:%a' -- "$path")"
[[ "$actual" == "$uid:$gid:$expected_mode" ]] || { echo "${key} ownership/mode mismatch: expected ${uid}:${gid}:${expected_mode}, got ${actual}" >&2; return 1; }
}
ensure_owned_dir() {
local key="$1"
local default_value="$2"
[[ -n "$default_value" ]] || { echo "missing directory ownership default: $key" >&2; return 1; }
apply_owned_dir "$key" "$3" "$4" "$5"
}
ensure_fixed_owned_dir() {
local source="$1"
apply_owned_dir "fixed directory $source" "$2" "$3" "$4"
}
register_fixed_owned_dir "./data"
ensure_fixed_owned_dir "./data" "999" "999" "0750"