Files
appstore/apps/elizabeth/2.0.2/scripts/init.sh
T
okxlin 468f4321ec Update elizabeth app
Rebuilt-from-PR: #6992
Source-PR: https://github.com/okxlin/appstore/pull/6992
Maintainer-workflow: appstore-pr-maintainer
2026-09-20 18:15:30 +08:00

192 lines
6.2 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 trusted_owner
[[ "$(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"
trusted_owner="$(stat -c '%u' -- "$ROOT_DIR")"
[[ "$trusted_owner" =~ ^[0-9]+$ ]] || { echo "unsafe version root owner: $ROOT_DIR" >&2; return 1; }
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")
[[ "$owner" == "0" || "$owner" == "$trusted_owner" ]] || { echo "unsafe version root chain owner: $current" >&2; return 1; }
[[ "$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"
}
migrate_owned_tree() {
local key="$1"
local uid="$2"
local gid="$3"
local path="$4"
local entry
[[ -d "$path" && ! -L "$path" ]] || { echo "unsafe ${key} path" >&2; return 1; }
command -v find >/dev/null 2>&1 || { echo "find is required" >&2; return 1; }
find -P "$path" -xdev -depth -print0 |
while IFS= read -r -d '' entry; do
[[ -L "$entry" ]] && { echo "unsafe ${key} path: symlink in persistent tree" >&2; return 1; }
chown --no-dereference "$uid:$gid" -- "$entry"
done
}
register_configured_owned_dir "APP_DATA_DIR" "./data"
register_configured_owned_dir "APP_STORAGE_DIR" "./storage"
ensure_owned_dir "APP_DATA_DIR" "./data" "65532" "65532" "0750"
ensure_owned_dir "APP_STORAGE_DIR" "./storage" "65532" "65532" "0750"
migrate_owned_tree "APP_DATA_DIR" "65532" "65532" "${OWNED_PATHS[APP_DATA_DIR]}"
migrate_owned_tree "APP_STORAGE_DIR" "65532" "65532" "${OWNED_PATHS[APP_STORAGE_DIR]}"