release: sync public installer asset install.sh
This commit is contained in:
+272
-22
@@ -6,13 +6,17 @@ RELEASE_BASE=${XYMEDIA_RELEASE_BASE:-https://git.keeper.work/admin/xymediavault-
|
|||||||
INSTALL_NONCE=$(date +%s)
|
INSTALL_NONCE=$(date +%s)
|
||||||
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd -P)
|
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd -P)
|
||||||
INSTALL_DIR=${XYMEDIA_INSTALL_DIR:-$PWD}
|
INSTALL_DIR=${XYMEDIA_INSTALL_DIR:-$PWD}
|
||||||
PG_HOST=postgres
|
PG_HOST=${XYMEDIA_POSTGRES_HOST:-postgres}
|
||||||
PG_PORT=5432
|
PG_PORT=${XYMEDIA_POSTGRES_PUBLIC_PORT:-5432}
|
||||||
PG_DB=xymedia
|
PG_DB=xymedia
|
||||||
PG_USER=xymedia_app
|
PG_USER=${XYMEDIA_POSTGRES_USER:-xymedia_app}
|
||||||
PG_PASSWORD_FILE=
|
PG_PASSWORD_FILE=
|
||||||
|
INSTALL_EXISTING=0
|
||||||
|
XYMEDIA_POSTGRES_PUBLIC_PORT=${XYMEDIA_POSTGRES_PUBLIC_PORT:-}
|
||||||
SKIP_COMPONENTS=0
|
SKIP_COMPONENTS=0
|
||||||
EXISTING_DB=0
|
EXISTING_DB=0
|
||||||
|
LOCAL_DB=0
|
||||||
|
PG_HOST_CLI=0
|
||||||
API_PORT=${XYMEDIA_API_PORT:-18080}
|
API_PORT=${XYMEDIA_API_PORT:-18080}
|
||||||
WEBDAV_PORT=${XYMEDIA_WEBDAV_PORT:-18081}
|
WEBDAV_PORT=${XYMEDIA_WEBDAV_PORT:-18081}
|
||||||
TVBOX_PORT=${XYMEDIA_TVBOX_PORT:-18082}
|
TVBOX_PORT=${XYMEDIA_TVBOX_PORT:-18082}
|
||||||
@@ -39,6 +43,10 @@ CONTROLLER_BIND_EXPLICIT=${XYMEDIA_CONTROLLER_BIND_ADDRESS:+1}
|
|||||||
CONTROLLER_PORT_EXPLICIT=${XYMEDIA_CONTROLLER_PORT:+1}
|
CONTROLLER_PORT_EXPLICIT=${XYMEDIA_CONTROLLER_PORT:+1}
|
||||||
MAINTENANCE_ACTION=
|
MAINTENANCE_ACTION=
|
||||||
FUSE_PATH_INPUT=
|
FUSE_PATH_INPUT=
|
||||||
|
CLEANUP_CHOICE=
|
||||||
|
XYMEDIA_INSTANCE_ID=${XYMEDIA_INSTANCE_ID:-}
|
||||||
|
cleanup_interrupted=0
|
||||||
|
cleanup_journal=
|
||||||
confirm_fuse_action() {
|
confirm_fuse_action() {
|
||||||
local confirmation
|
local confirmation
|
||||||
confirmation=$(prompt_tty '该操作将停止并重新启动应用,输入 1 继续,其他内容取消: ')
|
confirmation=$(prompt_tty '该操作将停止并重新启动应用,输入 1 继续,其他内容取消: ')
|
||||||
@@ -92,19 +100,171 @@ print_fuse_status() {
|
|||||||
print_controller_info() {
|
print_controller_info() {
|
||||||
local env_file="$INSTALL_DIR/.env" token_file address port token
|
local env_file="$INSTALL_DIR/.env" token_file address port token
|
||||||
[[ -f $env_file ]] || { printf '未找到安装配置,请先完成安装。\n' >>$TTY_OUT; return 1; }
|
[[ -f $env_file ]] || { printf '未找到安装配置,请先完成安装。\n' >>$TTY_OUT; return 1; }
|
||||||
address=$(awk -F= '$1 == "XYMEDIA_CONTROLLER_BIND_ADDRESS" {print $2; exit}' "$env_file")
|
address=$(awk -F= '$1 == "XYMEDIA_HOST_ADDRESSES" {print $2; exit}' "$env_file" | cut -d, -f1)
|
||||||
port=$(awk -F= '$1 == "XYMEDIA_CONTROLLER_PORT" {print $2; exit}' "$env_file")
|
port=$(awk -F= '$1 == "XYMEDIA_CONTROLLER_PORT" {print $2; exit}' "$env_file")
|
||||||
token_file="$INSTALL_DIR/secrets/xiaoya-controller-token"
|
token_file="$INSTALL_DIR/secrets/xiaoya-controller-token"
|
||||||
token=$(cat "$token_file" 2>/dev/null || true)
|
token=$(cat "$token_file" 2>/dev/null || true)
|
||||||
[[ -n $address ]] || address='0.0.0.0'
|
[[ -n $address ]] || address=$(awk -F= '$1 == "XYMEDIA_CONTROLLER_BIND_ADDRESS" {print $2; exit}' "$env_file")
|
||||||
|
[[ -n $address && $address != 0.0.0.0 && $address != :: ]] || address='127.0.0.1'
|
||||||
[[ -n $port ]] || port=19090
|
[[ -n $port ]] || port=19090
|
||||||
[[ -n $token ]] || { printf '未找到控制器密钥,请先完成控制器安装。\n' >>$TTY_OUT; return 1; }
|
[[ -n $token ]] || { printf '未找到控制器密钥,请先完成控制器安装。\n' >>$TTY_OUT; return 1; }
|
||||||
printf '小雅控制器地址:http://%s:%s\n小雅控制器密钥:%s\n' "$address" "$port" "$token" >>$TTY_OUT
|
printf '小雅控制器地址:http://%s:%s\n小雅控制器密钥:%s\n' "$address" "$port" "$token" >>$TTY_OUT
|
||||||
}
|
}
|
||||||
|
print_database_info() {
|
||||||
|
local env_file="$INSTALL_DIR/.env" host port db user password_file password local_db
|
||||||
|
[[ -f $env_file ]] || { printf '未找到安装配置,请先完成安装。\n' >>$TTY_OUT; return 1; }
|
||||||
|
host=$(read_install_env XYMEDIA_POSTGRES_HOST)
|
||||||
|
port=$(read_install_env XYMEDIA_POSTGRES_PORT)
|
||||||
|
db=$(read_install_env XYMEDIA_POSTGRES_DATABASE)
|
||||||
|
user=$(read_install_env XYMEDIA_POSTGRES_USER)
|
||||||
|
if [[ $host == *:* && $host != \[*\] ]]; then host="[$host]"; fi
|
||||||
|
printf 'PostgreSQL 地址:%s:%s\nPostgreSQL 数据库:%s\nPostgreSQL 用户:%s\n' "$host" "$port" "$db" "$user" >>$TTY_OUT
|
||||||
|
password_file="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
|
local_db=$(read_install_env XYMEDIA_POSTGRES_LOCAL_DB)
|
||||||
|
if [[ $local_db == 1 || $host == postgres || $LOCAL_DB == 1 ]] && [[ -s $password_file ]]; then
|
||||||
|
password=$(cat "$password_file")
|
||||||
|
printf 'PostgreSQL 密码:%s\n' "$password" >>$TTY_OUT
|
||||||
|
else
|
||||||
|
printf 'PostgreSQL 密码:外部数据库密码由用户自行保管,不在安装器显示。\n' >>$TTY_OUT
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
validate_cleanup_root() {
|
||||||
|
[[ $INSTALL_DIR = /* && $INSTALL_DIR != / && -d $INSTALL_DIR && ! -L $INSTALL_DIR ]] || die '清理要求安装目录是已存在的非符号链接绝对目录'
|
||||||
|
INSTALL_PATH="$INSTALL_DIR" python3 - <<'PY'
|
||||||
|
import os, pathlib
|
||||||
|
p = pathlib.Path(os.environ['INSTALL_PATH'])
|
||||||
|
current = pathlib.Path(p.root)
|
||||||
|
for part in p.parts[1:]:
|
||||||
|
current /= part
|
||||||
|
info = current.lstat()
|
||||||
|
if info.st_uid != 0 or info.st_mode & 0o022 or current.is_symlink(): raise SystemExit(1)
|
||||||
|
if p.resolve(strict=True) == pathlib.Path('/'):
|
||||||
|
raise SystemExit(1)
|
||||||
|
PY
|
||||||
|
[[ -f "$INSTALL_DIR/.env" && ! -L "$INSTALL_DIR/.env" ]] || die '清理要求安装目录存在非符号链接 .env;无法确认实例所有权'
|
||||||
|
local recorded_dir recorded_id
|
||||||
|
recorded_dir=$(cleanup_read_env XYMEDIA_INSTALL_DIR)
|
||||||
|
recorded_id=$(cleanup_read_env XYMEDIA_INSTANCE_ID)
|
||||||
|
[[ $recorded_dir == "$INSTALL_DIR" ]] || die '安装配置中的实例目录与当前目录不一致,拒绝清理'
|
||||||
|
[[ $recorded_id =~ ^[a-z0-9_-]+$ ]] || die '安装配置缺少有效 XYMEDIA_INSTANCE_ID,拒绝清理旧实例'
|
||||||
|
XYMEDIA_INSTANCE_ID=$recorded_id
|
||||||
|
}
|
||||||
|
cleanup_read_env() { awk -F= -v wanted="$1" '$1 == wanted {sub(/^[^=]*=/, ""); print; exit}' "$INSTALL_DIR/.env" 2>/dev/null || true; }
|
||||||
|
cleanup_canonical_child() {
|
||||||
|
[[ -n $1 && -d $1 && ! -L $1 ]] || return 1
|
||||||
|
CLEANUP_PATH_INPUT="$1" CLEANUP_ROOT="$2" python3 - <<'PY'
|
||||||
|
import os, pathlib
|
||||||
|
p = pathlib.Path(os.environ['CLEANUP_PATH_INPUT'])
|
||||||
|
root = pathlib.Path(os.environ['CLEANUP_ROOT']).resolve(strict=True)
|
||||||
|
if not p.is_absolute() or any((p.root.joinpath(*p.parts[1:i+1])).is_symlink() for i in range(len(p.parts)-1)): raise SystemExit(1)
|
||||||
|
canonical = p.resolve(strict=True)
|
||||||
|
try: canonical.relative_to(root)
|
||||||
|
except ValueError: raise SystemExit(1)
|
||||||
|
if canonical == root: raise SystemExit(1)
|
||||||
|
print(canonical)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
cleanup_unmount_owned_fuse() {
|
||||||
|
local path mount_id source fstype
|
||||||
|
path=$(cleanup_read_env XYMEDIA_FUSE_HOST_PATH)
|
||||||
|
[[ -n $path ]] || return 0
|
||||||
|
path=$(cleanup_canonical_child "$path" /) || die 'FUSE 路径无效,已停止清理'
|
||||||
|
command -v mountpoint >/dev/null 2>&1 && command -v findmnt >/dev/null 2>&1 || die '无法验证 FUSE 挂载身份,已停止清理'
|
||||||
|
mountpoint -q "$path" 2>/dev/null || return 0
|
||||||
|
mount_id=$(findmnt -rn -M "$path" -o ID 2>/dev/null || true); source=$(findmnt -rn -M "$path" -o SOURCE 2>/dev/null || true); fstype=$(findmnt -rn -M "$path" -o FSTYPE 2>/dev/null || true)
|
||||||
|
[[ -n $mount_id && $source == xymediavault && ( $fstype == fuse.xymediavault || $fstype == fuse ) ]] || die 'FUSE 路径不是可确认的 XyMediaVault 挂载,已停止清理'
|
||||||
|
if command -v fusermount3 >/dev/null 2>&1; then fusermount3 -u "$path" || die '受管 FUSE 卸载失败,未删除任何数据'; elif command -v fusermount >/dev/null 2>&1; then fusermount -u "$path" || die '受管 FUSE 卸载失败,未删除任何数据'; else umount "$path" || die '缺少安全卸载命令,未删除任何数据'; fi
|
||||||
|
mountpoint -q "$path" 2>/dev/null && die 'FUSE 卸载后仍处于挂载状态,未删除任何数据' || true
|
||||||
|
}
|
||||||
|
cleanup_inspect_ok() {
|
||||||
|
local expected=$1 inspected=$2 require_mount=$3
|
||||||
|
INSPECTED_JSON="$inspected" EXPECTED_NAME="$expected" EXPECTED_ID="$XYMEDIA_INSTANCE_ID" EXPECTED_DIR="$INSTALL_DIR" REQUIRE_MOUNT="$require_mount" python3 - <<'PY'
|
||||||
|
import json, os, pathlib
|
||||||
|
try:
|
||||||
|
item = json.loads(os.environ['INSPECTED_JSON'])[0]
|
||||||
|
labels = (item.get('Config') or {}).get('Labels') or {}
|
||||||
|
if (item.get('Name') or '').lstrip('/') != os.environ['EXPECTED_NAME']: raise SystemExit(1)
|
||||||
|
if labels.get('com.xymediavault.managed') != 'true': raise SystemExit(1)
|
||||||
|
if labels.get('com.xymediavault.instance_id') != os.environ['EXPECTED_ID']: raise SystemExit(1)
|
||||||
|
if labels.get('com.xymediavault.install_dir') != os.environ['EXPECTED_DIR']: raise SystemExit(1)
|
||||||
|
if os.environ['REQUIRE_MOUNT'] == '1':
|
||||||
|
data = pathlib.Path(os.environ['EXPECTED_DATA']).resolve(strict=True)
|
||||||
|
matched = False
|
||||||
|
for mount in item.get('Mounts') or []:
|
||||||
|
if mount.get('Type') == 'bind' and mount.get('Destination') in ('/data', '/www/data'):
|
||||||
|
source = pathlib.Path(mount.get('Source', '')).resolve(strict=True)
|
||||||
|
if source == data: matched = True
|
||||||
|
if not matched: raise SystemExit(1)
|
||||||
|
except (ValueError, IndexError, TypeError, AttributeError, OSError): raise SystemExit(1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
cleanup_container() {
|
||||||
|
local name=$1 require_mount=${2:-0} expected_data=${3:-} inspected
|
||||||
|
inspected=$(docker container inspect "$name" 2>/dev/null) || return 0
|
||||||
|
[[ -n $inspected ]] || return 0
|
||||||
|
EXPECTED_DATA="$expected_data" cleanup_inspect_ok "$name" "$inspected" "$require_mount" || die "容器 $name 缺少当前实例所有权标签或挂载校验失败,拒绝删除"
|
||||||
|
docker rm -f -- "$name" >>"$INSTALL_LOG" 2>&1 || die "删除容器失败:$name(详情见日志)"
|
||||||
|
}
|
||||||
|
cleanup_all() {
|
||||||
|
local include_xiaoya=$1 marker data_path canonical_data name managed confirm inspected
|
||||||
|
validate_cleanup_root
|
||||||
|
if [[ -e "$INSTALL_DIR/.cleanup.journal" ]]; then
|
||||||
|
[[ -f "$INSTALL_DIR/.cleanup.journal" && ! -L "$INSTALL_DIR/.cleanup.journal" ]] || die '清理日志不是安全的普通文件,拒绝继续'
|
||||||
|
[[ $(stat -c '%u' "$INSTALL_DIR/.cleanup.journal") -eq 0 && $(( $(stat -c '%a' "$INSTALL_DIR/.cleanup.journal") & 077 )) -eq 0 ]] || die '清理日志权限不安全,拒绝继续'
|
||||||
|
printf '检测到未完成的清理日志:%s\n' "$INSTALL_DIR/.cleanup.journal" >>$TTY_OUT
|
||||||
|
confirm=$(prompt_tty '继续未完成清理请输入数字 1,输入 0 取消: ')
|
||||||
|
[[ $confirm == 1 ]] || { printf '已取消恢复清理。\n' >>$TTY_OUT; return 0; }
|
||||||
|
fi
|
||||||
|
marker=$(cleanup_read_env XYMEDIA_XIAOYA_MANAGED); data_path=$(cleanup_read_env XYMEDIA_XIAOYA_DATA_DIR); canonical_data=
|
||||||
|
if [[ $include_xiaoya == 1 && $marker != 1 ]]; then
|
||||||
|
printf '当前安装未记录本机小雅由安装器创建,选项 2 不可用;未删除任何内容。\n' >>$TTY_OUT
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ $include_xiaoya == 1 && $marker == 1 ]]; then canonical_data=$(cleanup_canonical_child "$data_path" "$INSTALL_DIR") || die '小雅数据未被标记为安装目录内的安全路径,已停止清理'; fi
|
||||||
|
local -a containers=(xymedia-app xymedia-postgres xymedia-controller xymedia-aliyuntvtoken)
|
||||||
|
if [[ $include_xiaoya == 1 && $marker == 1 ]]; then containers+=(xymedia-xiaoya); fi
|
||||||
|
local -a migration_ids=() migration_names=()
|
||||||
|
while IFS=$'\t' read -r name id; do
|
||||||
|
[[ -n $id ]] || continue
|
||||||
|
inspected=$(docker container inspect "$id" 2>/dev/null) || continue
|
||||||
|
if [[ $name =~ ^xymedia-app-migrate-[0-9]+$ ]] && cleanup_inspect_ok "$name" "$inspected" 0; then migration_names+=("$name"); migration_ids+=("$id"); fi
|
||||||
|
done < <(docker ps -a --filter label=com.xymediavault.managed=true --format '{{.Names}}\t{{.ID}}')
|
||||||
|
containers+=("${migration_names[@]}")
|
||||||
|
for name in "${containers[@]}"; do
|
||||||
|
inspected=$(docker container inspect "$name" 2>/dev/null) || continue
|
||||||
|
cleanup_inspect_ok "$name" "$inspected" "$([[ $name == xymedia-xiaoya ]] && printf 1 || printf 0)" || die "容器 $name 缺少当前实例所有权标签,拒绝清理;未删除容器"
|
||||||
|
done
|
||||||
|
printf '\n将删除以下精确容器(不存在的会跳过):\n' >>$TTY_OUT; printf ' %s\n' "${containers[@]}" >>$TTY_OUT
|
||||||
|
printf '将删除安装器管理数据:%s/data %s/secrets %s/components %s/releases %s/controller %s/state\n' "$INSTALL_DIR" "$INSTALL_DIR" "$INSTALL_DIR" "$INSTALL_DIR" "$INSTALL_DIR" "$INSTALL_DIR" >>$TTY_OUT
|
||||||
|
printf '以及受管配置、Compose、日志和状态文件;其他文件、外部小雅数据和未标记小雅容器保留。\n' >>$TTY_OUT
|
||||||
|
[[ -z $canonical_data ]] || printf '小雅数据(已确认在安装目录内):%s\n' "$canonical_data" >>$TTY_OUT
|
||||||
|
confirm=$(prompt_tty '确认删除以上内容?请输入数字 1,其他内容取消: '); [[ $confirm == 1 ]] || { printf '已取消清理。\n' >>$TTY_OUT; return 0; }
|
||||||
|
[[ ! -L "$INSTALL_DIR/.cleanup.journal" ]] || die '清理日志是符号链接,拒绝清理'
|
||||||
|
mkdir "$INSTALL_DIR/.install.lock" 2>/dev/null || die '已有安装或维护操作正在进行,拒绝清理'
|
||||||
|
cleanup_lock_dir="$INSTALL_DIR/.install.lock"
|
||||||
|
cleanup_journal="$INSTALL_DIR/.cleanup.journal"
|
||||||
|
(umask 077; printf 'stage=confirmed\ninstance_id=%s\n' "$XYMEDIA_INSTANCE_ID" >"$cleanup_journal") || die '无法创建清理日志,未删除任何内容'
|
||||||
|
cleanup_exit() { local status=$?; rmdir "${cleanup_lock_dir:-}" 2>/dev/null || true; if (( cleanup_interrupted )); then (umask 077; printf 'stage=interrupted\ninstance_id=%s\n' "$XYMEDIA_INSTANCE_ID" >"$cleanup_journal"); printf '清理被中断,已保留恢复日志:%s\n' "$cleanup_journal" >>$TTY_OUT; fi; persist_installer_log; cleanup_installer_tmp; exit "$status"; }
|
||||||
|
cleanup_trap() { cleanup_interrupted=1; exit 130; }
|
||||||
|
trap cleanup_exit EXIT
|
||||||
|
trap cleanup_trap INT TERM
|
||||||
|
cleanup_checkpoint() { if (( cleanup_interrupted )); then printf 'stage=%s\n' "$1" >"$cleanup_journal"; return 1; fi; printf 'stage=%s\n' "$1" >"$cleanup_journal"; return 0; }
|
||||||
|
cleanup_interrupted=0
|
||||||
|
cleanup_unmount_owned_fuse
|
||||||
|
for managed in xymedia-app xymedia-postgres xymedia-controller xymedia-aliyuntvtoken xymedia-xiaoya; do [[ " ${containers[*]} " == *" $managed "* ]] || continue; cleanup_checkpoint "before-$managed" || exit 130; cleanup_container "$managed" "$([[ $managed == xymedia-xiaoya ]] && printf 1 || printf 0)" "$canonical_data"; done
|
||||||
|
local index; for index in "${!migration_ids[@]}"; do cleanup_checkpoint "before-migration-${migration_names[$index]}" || exit 130; inspected=$(docker container inspect "${migration_ids[$index]}" 2>/dev/null) || continue; cleanup_inspect_ok "${migration_names[$index]}" "$inspected" 0 || continue; docker rm -f -- "${migration_ids[$index]}" >>"$INSTALL_LOG" 2>&1 || die "删除迁移容器失败(详情见日志)"; done
|
||||||
|
if [[ -n $canonical_data ]]; then
|
||||||
|
[[ ! -L "$canonical_data" ]] || die '小雅数据路径是符号链接,已停止清理'
|
||||||
|
rm -rf -- "$canonical_data"
|
||||||
|
fi
|
||||||
|
for managed in data secrets components releases controller state bootstrap; do [[ ! -L "$INSTALL_DIR/$managed" ]] || die "受管路径是符号链接,已停止清理"; rm -rf -- "$INSTALL_DIR/$managed"; done
|
||||||
|
for managed in .env config.yaml compose.yaml compose.fuse.yaml Dockerfile.bootstrap remount-fuse.sh catalog-v1.json install.log; do [[ ! -L "$INSTALL_DIR/$managed" ]] || die "受管文件是符号链接,已停止清理"; rm -f -- "$INSTALL_DIR/$managed"; done
|
||||||
|
rm -f -- "$cleanup_journal"; cleanup_journal=; printf '清理完成;安装目录中的其他文件和外部小雅数据已保留。\n' >>$TTY_OUT
|
||||||
|
}
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--install-dir) INSTALL_DIR=${2:?missing value}; shift 2;;
|
--install-dir) INSTALL_DIR=${2:?missing value}; shift 2;;
|
||||||
--postgres-host) PG_HOST=${2:?missing value}; shift 2;;
|
--postgres-host) PG_HOST=${2:?missing value}; PG_HOST_CLI=1; shift 2;;
|
||||||
--postgres-port) PG_PORT=${2:?missing value}; shift 2;;
|
--postgres-port) PG_PORT=${2:?missing value}; shift 2;;
|
||||||
--postgres-db) PG_DB=${2:?missing value}; shift 2;;
|
--postgres-db) PG_DB=${2:?missing value}; shift 2;;
|
||||||
--postgres-user) PG_USER=${2:?missing value}; shift 2;;
|
--postgres-user) PG_USER=${2:?missing value}; shift 2;;
|
||||||
@@ -155,6 +315,14 @@ PY
|
|||||||
fi
|
fi
|
||||||
[[ ! -e "$INSTALL_DIR/.env" || ! -L "$INSTALL_DIR/.env" ]] || die '.env must not be a symlink'
|
[[ ! -e "$INSTALL_DIR/.env" || ! -L "$INSTALL_DIR/.env" ]] || die '.env must not be a symlink'
|
||||||
}
|
}
|
||||||
|
ensure_instance_id() {
|
||||||
|
[[ -n "$XYMEDIA_INSTANCE_ID" ]] || XYMEDIA_INSTANCE_ID=$(awk -F= '$1 == "XYMEDIA_INSTANCE_ID" {print $2; exit}' "$INSTALL_DIR/.env" 2>/dev/null || true)
|
||||||
|
if [[ -z "$XYMEDIA_INSTANCE_ID" ]]; then
|
||||||
|
XYMEDIA_INSTANCE_ID=$(od -An -tx1 -N8 /dev/urandom 2>/dev/null | tr -d ' \n')
|
||||||
|
[[ -n "$XYMEDIA_INSTANCE_ID" ]] || XYMEDIA_INSTANCE_ID="install-${INSTALL_NONCE}"
|
||||||
|
fi
|
||||||
|
[[ $XYMEDIA_INSTANCE_ID =~ ^[a-z0-9_-]+$ ]] || die 'XYMEDIA_INSTANCE_ID 格式无效'
|
||||||
|
}
|
||||||
progress() {
|
progress() {
|
||||||
STEP=$((STEP + 1))
|
STEP=$((STEP + 1))
|
||||||
printf '\033[2K\r[%d/%d] %s\n' "$STEP" "$TOTAL_STEPS" "$1"
|
printf '\033[2K\r[%d/%d] %s\n' "$STEP" "$TOTAL_STEPS" "$1"
|
||||||
@@ -204,21 +372,23 @@ menu() {
|
|||||||
read_tty choice || die '无法读取终端输入'
|
read_tty choice || die '无法读取终端输入'
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) return 0;;
|
1) return 0;;
|
||||||
2) ensure_xiaoya_dir; printf '小雅目录已准备:%s;将安装小雅和小雅控制器。\n' "$XIAOYA_DIR" >>$TTY_OUT; write_controller_token; CONTROLLER_URL='http://xymedia-controller:19090'; MENU_PROFILES='xiaoya xiaoya-control'; return 0;;
|
2) ensure_xiaoya_dir; printf '小雅目录已准备:%s;将安装小雅和小雅控制器。\n' "$XIAOYA_DIR" >>$TTY_OUT; write_controller_token; CONTROLLER_URL='http://xymedia-controller:19090'; [[ -f "$INSTALL_DIR/.env" ]] || XYMEDIA_XIAOYA_MANAGED=1; MENU_PROFILES='xiaoya xiaoya-control'; return 0;;
|
||||||
3) local url name key; url=$(prompt_tty '控制器地址:'); key=$(prompt_tty '控制器密钥:'); name=$(prompt_tty '显示名称:'); [[ $url == https://* || ${ALLOW_HTTP:-0} == 1 ]] || die '远程连接默认要求 HTTPS;设置 ALLOW_HTTP=1 后才允许 HTTP'; curl --fail --silent --show-error --location --max-time 10 -H "X-XyMedia-Controller-Token: $key" "${url%/}/health" >/dev/null || die '控制器健康检查失败,未保存连接'; REMOTE_MODE=1; REMOTE_TOKEN=$key; CONTROLLER_URL="${url%/}"; printf '远程连接 %s (%s) 校验成功,配置已保存;控制器密钥不会显示。\n' "$name" "$CONTROLLER_URL" >>$TTY_OUT; return 0;;
|
3) local url name key; url=$(prompt_tty '控制器地址:'); key=$(prompt_tty '控制器密钥:'); name=$(prompt_tty '显示名称:'); [[ $url == https://* || ${ALLOW_HTTP:-0} == 1 ]] || die '远程连接默认要求 HTTPS;设置 ALLOW_HTTP=1 后才允许 HTTP'; curl --fail --silent --show-error --location --max-time 10 -H "X-XyMedia-Controller-Token: $key" "${url%/}/health" >/dev/null || die '控制器健康检查失败,未保存连接'; REMOTE_MODE=1; REMOTE_TOKEN=$key; CONTROLLER_URL="${url%/}"; printf '远程连接 %s (%s) 校验成功,配置已保存;控制器密钥不会显示。\n' "$name" "$CONTROLLER_URL" >>$TTY_OUT; return 0;;
|
||||||
4) ensure_xiaoya_dir 1; if [[ ${XYMEDIA_INSTALL_TEST_DISCOVERY_ONLY:-0} != 1 && -z $CONTROLLER_BIND_EXPLICIT ]]; then prompt_tty_into CONTROLLER_BIND_ADDRESS "控制器监听地址 [${CONTROLLER_BIND_ADDRESS}]: "; CONTROLLER_BIND_ADDRESS=${CONTROLLER_BIND_ADDRESS:-0.0.0.0}; fi; if [[ ${XYMEDIA_INSTALL_TEST_DISCOVERY_ONLY:-0} != 1 && -z $CONTROLLER_PORT_EXPLICIT ]]; then prompt_tty_into CONTROLLER_PORT "控制器端口 [${CONTROLLER_PORT}]: "; CONTROLLER_PORT=${CONTROLLER_PORT:-19090}; fi; write_controller_token; CONTROLLER_ONLY=1; printf '小雅控制器安装目录已准备。\n' >>$TTY_OUT; MENU_PROFILES='xiaoya-control'; return 0;;
|
4) ensure_xiaoya_dir 1; if [[ ${XYMEDIA_INSTALL_TEST_DISCOVERY_ONLY:-0} != 1 && -z $CONTROLLER_BIND_EXPLICIT ]]; then prompt_tty_into CONTROLLER_BIND_ADDRESS "控制器监听地址 [${CONTROLLER_BIND_ADDRESS}]: "; CONTROLLER_BIND_ADDRESS=${CONTROLLER_BIND_ADDRESS:-0.0.0.0}; fi; if [[ ${XYMEDIA_INSTALL_TEST_DISCOVERY_ONLY:-0} != 1 && -z $CONTROLLER_PORT_EXPLICIT ]]; then prompt_tty_into CONTROLLER_PORT "控制器端口 [${CONTROLLER_PORT}]: "; CONTROLLER_PORT=${CONTROLLER_PORT:-19090}; fi; write_controller_token; CONTROLLER_ONLY=1; printf '小雅控制器安装目录已准备。\n' >>$TTY_OUT; MENU_PROFILES='xiaoya-control'; return 0;;
|
||||||
5) while :; do
|
5) while :; do
|
||||||
printf '\n维护菜单\n1) 查看状态\n2) 一键挂载本地媒体库\n3) 启动/重启已配置挂载\n4) 停用本地媒体库挂载\n0) 返回\n选择:' >>$TTY_OUT
|
printf '\n维护菜单\n1) 查看状态\n2) 一键挂载本地媒体库\n3) 启动/重启已配置挂载\n4) 停用本地媒体库挂载\n5) 清理 XyMediaVault 容器及数据\n0) 返回\n选择:' >>$TTY_OUT
|
||||||
read_tty maintenance_choice || die '无法读取终端输入'
|
read_tty maintenance_choice || die '无法读取终端输入'
|
||||||
case "$maintenance_choice" in
|
case "$maintenance_choice" in
|
||||||
1) require_public_app_installation || continue; print_fuse_status;;
|
1) require_public_app_installation || continue; print_fuse_status;;
|
||||||
2) require_public_app_installation || continue; prompt_tty_into FUSE_PATH_INPUT '宿主机媒体库目录(必须为空):'; confirm_fuse_action; MAINTENANCE_ACTION=configure; return 0;;
|
2) require_public_app_installation || continue; prompt_tty_into FUSE_PATH_INPUT '宿主机媒体库目录(必须为空):'; confirm_fuse_action; MAINTENANCE_ACTION=configure; return 0;;
|
||||||
3) require_public_app_installation || continue; confirm_fuse_action; MAINTENANCE_ACTION=restart; return 0;;
|
3) require_public_app_installation || continue; confirm_fuse_action; MAINTENANCE_ACTION=restart; return 0;;
|
||||||
4) require_public_app_installation || continue; confirm_fuse_action; MAINTENANCE_ACTION=disable; return 0;;
|
4) require_public_app_installation || continue; confirm_fuse_action; MAINTENANCE_ACTION=disable; return 0;;
|
||||||
|
5) printf '清理选项:\n1) 只清理 XyMediaVault\n2) 清理 XyMediaVault 和本机安装的小雅\n0) 取消\n选择:' >>$TTY_OUT; read_tty cleanup_choice || die '无法读取终端输入'; case "$cleanup_choice" in 1) CLEANUP_CHOICE=1; return 0;; 2) CLEANUP_CHOICE=2; return 0;; 0) continue;; *) printf '无效选择\n' >>$TTY_OUT;; esac;;
|
||||||
0) continue 2;; *) printf '无效选择\n' >>$TTY_OUT;;
|
0) continue 2;; *) printf '无效选择\n' >>$TTY_OUT;;
|
||||||
esac
|
esac
|
||||||
done;;
|
done;;
|
||||||
6) print_controller_info || true;;
|
6) print_controller_info || true;;
|
||||||
|
7) print_database_info || true;;
|
||||||
0) exit 0;; *) printf '无效选项\n' >>$TTY_OUT;;
|
0) exit 0;; *) printf '无效选项\n' >>$TTY_OUT;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -251,6 +421,7 @@ validate_container_name() {
|
|||||||
progress '检查安装环境'
|
progress '检查安装环境'
|
||||||
[[ $INSTALL_DIR = /* && $INSTALL_DIR != / ]] || die '安装目录必须是绝对路径且不能是根目录'
|
[[ $INSTALL_DIR = /* && $INSTALL_DIR != / ]] || die '安装目录必须是绝对路径且不能是根目录'
|
||||||
guard_existing_install_tree
|
guard_existing_install_tree
|
||||||
|
if [[ -f "$INSTALL_DIR/.env" ]]; then INSTALL_EXISTING=1; fi
|
||||||
init_installer_log
|
init_installer_log
|
||||||
command -v docker >/dev/null || die '需要安装 Docker'
|
command -v docker >/dev/null || die '需要安装 Docker'
|
||||||
if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose); elif command -v docker-compose >/dev/null; then COMPOSE=(docker-compose); else die '需要安装 Docker Compose'; fi
|
if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose); elif command -v docker-compose >/dev/null; then COMPOSE=(docker-compose); else die '需要安装 Docker Compose'; fi
|
||||||
@@ -258,6 +429,24 @@ docker info >/dev/null 2>&1 || die '无法连接 Docker,请确认 Docker 已
|
|||||||
command -v curl >/dev/null || die '需要安装 curl'
|
command -v curl >/dev/null || die '需要安装 curl'
|
||||||
command -v zstd >/dev/null || die '需要安装 zstd,用于解压发布包'
|
command -v zstd >/dev/null || die '需要安装 zstd,用于解压发布包'
|
||||||
command -v python3 >/dev/null || die '需要安装 python3,用于安全解压发布包'
|
command -v python3 >/dev/null || die '需要安装 python3,用于安全解压发布包'
|
||||||
|
if [[ -n $CLEANUP_CHOICE ]]; then
|
||||||
|
(( EUID == 0 )) || die '清理要求 root 权限'
|
||||||
|
cleanup_all "$([[ $CLEANUP_CHOICE == 2 ]] && printf 1 || printf 0)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
ensure_instance_id
|
||||||
|
if (( ! CONTROLLER_ONLY )) && (( INSTALL_EXISTING )); then
|
||||||
|
old_host=$(read_install_env XYMEDIA_POSTGRES_HOST)
|
||||||
|
old_port=$(read_install_env XYMEDIA_POSTGRES_PORT)
|
||||||
|
old_db=$(read_install_env XYMEDIA_POSTGRES_DATABASE)
|
||||||
|
old_user=$(read_install_env XYMEDIA_POSTGRES_USER)
|
||||||
|
[[ -n $old_host ]] && PG_HOST=$old_host
|
||||||
|
[[ -n $old_port ]] && PG_PORT=$old_port
|
||||||
|
[[ -n $old_db ]] && PG_DB=$old_db
|
||||||
|
[[ -n $old_user ]] && PG_USER=$old_user
|
||||||
|
old_local=$(read_install_env XYMEDIA_POSTGRES_LOCAL_DB)
|
||||||
|
if [[ $old_local == 1 || ( $old_host == postgres && $EXISTING_DB != 1 ) ]]; then LOCAL_DB=1; fi
|
||||||
|
fi
|
||||||
if [[ -n $MAINTENANCE_ACTION ]]; then
|
if [[ -n $MAINTENANCE_ACTION ]]; then
|
||||||
require_public_app_installation || die '维护操作仅支持已有完整 app 安装'
|
require_public_app_installation || die '维护操作仅支持已有完整 app 安装'
|
||||||
case "$MAINTENANCE_ACTION" in
|
case "$MAINTENANCE_ACTION" in
|
||||||
@@ -392,10 +581,18 @@ if (( CONTROLLER_ONLY )); then
|
|||||||
[[ ${XYMEDIA_INSTALL_TEST_DISCOVERY_ONLY:-0} == 1 ]] && exit 0
|
[[ ${XYMEDIA_INSTALL_TEST_DISCOVERY_ONLY:-0} == 1 ]] && exit 0
|
||||||
validate_xiaoya_data_dir
|
validate_xiaoya_data_dir
|
||||||
fi
|
fi
|
||||||
if (( ! CONTROLLER_ONLY )) && [[ $PG_HOST != postgres && $EXISTING_DB != 1 ]]; then die 'an external database requires --existing-db confirmation'; fi
|
if (( ! CONTROLLER_ONLY )) && (( PG_HOST_CLI )) && [[ $PG_HOST != postgres && $EXISTING_DB != 1 ]]; then die 'an external database requires --existing-db confirmation'; fi
|
||||||
if (( ! CONTROLLER_ONLY )) && [[ $PG_HOST != postgres && -z $PG_PASSWORD_FILE ]]; then die 'an external database requires --postgres-password-file'; fi
|
if (( ! CONTROLLER_ONLY )) && (( PG_HOST_CLI )) && [[ $PG_HOST != postgres && -z $PG_PASSWORD_FILE ]]; then die 'an external database requires --postgres-password-file'; fi
|
||||||
if ! [[ $API_PORT =~ ^[0-9]+$ && $WEBDAV_PORT =~ ^[0-9]+$ && $TVBOX_PORT =~ ^[0-9]+$ ]]; then die 'ports must be numeric'; fi
|
if ! [[ $API_PORT =~ ^[0-9]+$ && $WEBDAV_PORT =~ ^[0-9]+$ && $TVBOX_PORT =~ ^[0-9]+$ ]]; then die 'ports must be numeric'; fi
|
||||||
if (( ! CONTROLLER_ONLY )) && ! [[ $PG_PORT =~ ^[0-9]+$ && $PG_DB =~ ^[A-Za-z0-9_]+$ && $PG_USER =~ ^[A-Za-z0-9._-]+$ && $PG_HOST =~ ^[A-Za-z0-9._-]+$ ]]; then die 'invalid PostgreSQL connection values'; fi
|
if (( ! CONTROLLER_ONLY )) && ! [[ $PG_PORT =~ ^[0-9]+$ && $PG_PORT -ge 1024 && $PG_PORT -le 65535 && $PG_DB =~ ^[A-Za-z0-9_]+$ && $PG_USER =~ ^[A-Za-z0-9._-]+$ ]]; then die 'invalid PostgreSQL connection values'; fi
|
||||||
|
if (( ! CONTROLLER_ONLY )) && ! python3 - "$PG_HOST" <<'PY'
|
||||||
|
import ipaddress, sys
|
||||||
|
try:
|
||||||
|
ipaddress.ip_address(sys.argv[1])
|
||||||
|
except ValueError:
|
||||||
|
if not __import__('re').fullmatch(r'[A-Za-z0-9.-]+', sys.argv[1]): raise SystemExit(1)
|
||||||
|
PY
|
||||||
|
then die 'PostgreSQL 主机必须是 IPv4、IPv6 或合法主机名'; fi
|
||||||
progress '准备安装目录与数据库凭据'
|
progress '准备安装目录与数据库凭据'
|
||||||
mkdir -p "$INSTALL_DIR" "$INSTALL_DIR"/{state,secrets,config}
|
mkdir -p "$INSTALL_DIR" "$INSTALL_DIR"/{state,secrets,config}
|
||||||
if (( ! CONTROLLER_ONLY )); then
|
if (( ! CONTROLLER_ONLY )); then
|
||||||
@@ -458,6 +655,44 @@ for value in values:
|
|||||||
PY
|
PY
|
||||||
}
|
}
|
||||||
HOST_ADDRESSES_INITIAL="$(discover_host_addresses || true)"
|
HOST_ADDRESSES_INITIAL="$(discover_host_addresses || true)"
|
||||||
|
select_random_postgres_port() {
|
||||||
|
local requested=${XYMEDIA_POSTGRES_PUBLIC_PORT:-}
|
||||||
|
if [[ -n $requested ]]; then
|
||||||
|
[[ $requested =~ ^[0-9]+$ && $requested -ge 1024 && $requested -le 65535 ]] || die 'XYMEDIA_POSTGRES_PUBLIC_PORT 必须是 1024 到 65535 之间的数字'
|
||||||
|
printf '%s\n' "$requested"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
python3 - <<'PY'
|
||||||
|
import random, socket
|
||||||
|
for _ in iter(int, 1):
|
||||||
|
port = random.randint(20000, 40000)
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
sock.bind(('0.0.0.0', port))
|
||||||
|
print(port)
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
random_postgres_user() {
|
||||||
|
python3 - <<'PY'
|
||||||
|
import secrets, string
|
||||||
|
print('x' + ''.join(secrets.choice(string.ascii_lowercase + string.digits + '_') for _ in range(15)))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
if (( ! CONTROLLER_ONLY )) && (( ! INSTALL_EXISTING )) && (( ! PG_HOST_CLI )); then
|
||||||
|
if [[ -z ${XYMEDIA_POSTGRES_HOST:-} ]]; then
|
||||||
|
[[ -n $HOST_ADDRESSES_INITIAL ]] || die '未检测到可用宿主机 IP,请设置 XYMEDIA_POSTGRES_HOST 后重试'
|
||||||
|
PG_HOST=$(printf '%s\n' "$HOST_ADDRESSES_INITIAL" | head -n 1)
|
||||||
|
fi
|
||||||
|
PG_PORT=$(select_random_postgres_port) || die '无法选择 PostgreSQL 对外端口,请稍后重试'
|
||||||
|
XYMEDIA_POSTGRES_PUBLIC_PORT=$PG_PORT
|
||||||
|
PG_USER=$(random_postgres_user) || die '无法生成 PostgreSQL 用户名'
|
||||||
|
LOCAL_DB=1
|
||||||
|
fi
|
||||||
if (( CONTROLLER_ONLY )) || [[ $MENU_PROFILES == *xiaoya-control* ]]; then
|
if (( CONTROLLER_ONLY )) || [[ $MENU_PROFILES == *xiaoya-control* ]]; then
|
||||||
if [[ ! -e "$INSTALL_DIR/config/XYMEDIA_HOST_ADDRESSES" ]]; then
|
if [[ ! -e "$INSTALL_DIR/config/XYMEDIA_HOST_ADDRESSES" ]]; then
|
||||||
umask 077
|
umask 077
|
||||||
@@ -472,7 +707,7 @@ fi
|
|||||||
chmod 700 "$INSTALL_DIR/secrets"
|
chmod 700 "$INSTALL_DIR/secrets"
|
||||||
if (( ! CONTROLLER_ONLY )) && [[ -n $PG_PASSWORD_FILE ]]; then
|
if (( ! CONTROLLER_ONLY )) && [[ -n $PG_PASSWORD_FILE ]]; then
|
||||||
[[ -r $PG_PASSWORD_FILE ]] || die 'password file is not readable'
|
[[ -r $PG_PASSWORD_FILE ]] || die 'password file is not readable'
|
||||||
if [[ "$PG_PASSWORD_FILE" != "$INSTALL_DIR/secrets/postgres-password" ]]; then
|
if [[ "$PG_PASSWORD_FILE" != "$INSTALL_DIR/secrets/xymedia-postgres-password" ]]; then
|
||||||
cp "$PG_PASSWORD_FILE" "$INSTALL_DIR/secrets/xymedia-postgres-password"
|
cp "$PG_PASSWORD_FILE" "$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
PG_PASSWORD_FILE="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
PG_PASSWORD_FILE="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
fi
|
fi
|
||||||
@@ -480,15 +715,14 @@ elif (( ! CONTROLLER_ONLY )); then
|
|||||||
PG_PASSWORD_FILE="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
PG_PASSWORD_FILE="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
if [[ ! -s $PG_PASSWORD_FILE ]]; then
|
if [[ ! -s $PG_PASSWORD_FILE ]]; then
|
||||||
umask 077
|
umask 077
|
||||||
if command -v openssl >/dev/null; then openssl rand -hex 32 >"$PG_PASSWORD_FILE"; else od -An -N32 -tx1 /dev/urandom | tr -d ' \n' >"$PG_PASSWORD_FILE"; fi
|
if (( INSTALL_EXISTING )) && [[ -s "$INSTALL_DIR/secrets/postgres-password" ]]; then cp "$INSTALL_DIR/secrets/postgres-password" "$PG_PASSWORD_FILE"
|
||||||
|
elif command -v openssl >/dev/null; then openssl rand -hex 32 >"$PG_PASSWORD_FILE"
|
||||||
|
else od -An -N32 -tx1 /dev/urandom | tr -d ' \n' >"$PG_PASSWORD_FILE"; fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if (( ! CONTROLLER_ONLY )); then chmod 600 "$PG_PASSWORD_FILE"; fi
|
if (( ! CONTROLLER_ONLY )); then chmod 600 "$PG_PASSWORD_FILE"; fi
|
||||||
if (( ! CONTROLLER_ONLY )); then
|
if (( LOCAL_DB )) && [[ -z ${XYMEDIA_POSTGRES_PUBLIC_PORT:-} ]]; then XYMEDIA_POSTGRES_PUBLIC_PORT=$PG_PORT; fi
|
||||||
if (( ! CONTROLLER_ONLY )); then
|
# Keep the legacy secret for existing administrators and old installations.
|
||||||
rm -f "$INSTALL_DIR/secrets/postgres-password"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
TEMPLATE_DIR="$INSTALL_DIR/bootstrap"
|
TEMPLATE_DIR="$INSTALL_DIR/bootstrap"
|
||||||
mkdir -p "$TEMPLATE_DIR"
|
mkdir -p "$TEMPLATE_DIR"
|
||||||
template_file() {
|
template_file() {
|
||||||
@@ -512,13 +746,15 @@ if [[ "$HOST_ADDRESSES_ENV" == *$'\r'* || "$HOST_ADDRESSES_ENV" == *$'\n'* ]]; t
|
|||||||
env_tmp=$(mktemp "$INSTALL_DIR/.env.tmp.XXXXXX") || die '无法创建安全的 .env 临时文件'
|
env_tmp=$(mktemp "$INSTALL_DIR/.env.tmp.XXXXXX") || die '无法创建安全的 .env 临时文件'
|
||||||
chmod 600 "$env_tmp"
|
chmod 600 "$env_tmp"
|
||||||
if [[ -f "$INSTALL_DIR/.env" ]]; then cp "$INSTALL_DIR/.env" "$env_tmp"; fi
|
if [[ -f "$INSTALL_DIR/.env" ]]; then cp "$INSTALL_DIR/.env" "$env_tmp"; fi
|
||||||
|
XIAOYA_MANAGED_ENV=$(awk -F= '$1 == "XYMEDIA_XIAOYA_MANAGED" {print $2; exit}' "$env_tmp" 2>/dev/null || true)
|
||||||
|
[[ ${XYMEDIA_XIAOYA_MANAGED:-0} == 1 ]] || XYMEDIA_XIAOYA_MANAGED=${XIAOYA_MANAGED_ENV:-0}
|
||||||
FUSE_ENABLED=$(awk -F= '$1 == "XYMEDIA_FUSE_ENABLED" {print $2; exit}' "$env_tmp" 2>/dev/null || true)
|
FUSE_ENABLED=$(awk -F= '$1 == "XYMEDIA_FUSE_ENABLED" {print $2; exit}' "$env_tmp" 2>/dev/null || true)
|
||||||
FUSE_HOST_PATH=$(awk -F= '$1 == "XYMEDIA_FUSE_HOST_PATH" {print $2; exit}' "$env_tmp" 2>/dev/null || true)
|
FUSE_HOST_PATH=$(awk -F= '$1 == "XYMEDIA_FUSE_HOST_PATH" {print $2; exit}' "$env_tmp" 2>/dev/null || true)
|
||||||
[[ $FUSE_ENABLED == true || $FUSE_ENABLED == false ]] || FUSE_ENABLED=false
|
[[ $FUSE_ENABLED == true || $FUSE_ENABLED == false ]] || FUSE_ENABLED=false
|
||||||
python3 - "$env_tmp" "$INSTALL_DIR" "$PG_HOST" "$PG_PORT" "$PG_DB" "$PG_USER" "$API_PORT" "$WEBDAV_PORT" "$TVBOX_PORT" "$CONTROLLER_URL" "${XIAOYA_DIR:-$INSTALL_DIR/xiaoya}" "$XIAOYA_CONTAINER" "$HOST_ADDRESSES_ENV" "$CONTROLLER_BIND_ADDRESS" "$CONTROLLER_PORT" "$FUSE_ENABLED" "$FUSE_HOST_PATH" <<'PY'
|
python3 - "$env_tmp" "$INSTALL_DIR" "$PG_HOST" "$PG_PORT" "$PG_DB" "$PG_USER" "$API_PORT" "$WEBDAV_PORT" "$TVBOX_PORT" "$CONTROLLER_URL" "${XIAOYA_DIR:-$INSTALL_DIR/xiaoya}" "$XIAOYA_CONTAINER" "$HOST_ADDRESSES_ENV" "$CONTROLLER_BIND_ADDRESS" "$CONTROLLER_PORT" "$FUSE_ENABLED" "$FUSE_HOST_PATH" "${XYMEDIA_XIAOYA_MANAGED:-0}" "$XYMEDIA_INSTANCE_ID" "$LOCAL_DB" "$XYMEDIA_POSTGRES_PUBLIC_PORT" <<'PY'
|
||||||
import pathlib, sys
|
import pathlib, sys
|
||||||
p=pathlib.Path(sys.argv[1])
|
p=pathlib.Path(sys.argv[1])
|
||||||
keys=('XYMEDIA_INSTALL_DIR','XYMEDIA_POSTGRES_HOST','XYMEDIA_POSTGRES_PORT','XYMEDIA_POSTGRES_DATABASE','XYMEDIA_POSTGRES_USER','XYMEDIA_API_PORT','XYMEDIA_WEBDAV_PORT','XYMEDIA_TVBOX_PORT','XYMEDIA_XIAOYA_CONTROLLER_URL','XYMEDIA_XIAOYA_DATA_DIR','XYMEDIA_XIAOYA_CONTAINER','XYMEDIA_HOST_ADDRESSES','XYMEDIA_CONTROLLER_BIND_ADDRESS','XYMEDIA_CONTROLLER_PORT','XYMEDIA_FUSE_ENABLED','XYMEDIA_FUSE_HOST_PATH')
|
keys=('XYMEDIA_INSTALL_DIR','XYMEDIA_POSTGRES_HOST','XYMEDIA_POSTGRES_PORT','XYMEDIA_POSTGRES_DATABASE','XYMEDIA_POSTGRES_USER','XYMEDIA_API_PORT','XYMEDIA_WEBDAV_PORT','XYMEDIA_TVBOX_PORT','XYMEDIA_XIAOYA_CONTROLLER_URL','XYMEDIA_XIAOYA_DATA_DIR','XYMEDIA_XIAOYA_CONTAINER','XYMEDIA_HOST_ADDRESSES','XYMEDIA_CONTROLLER_BIND_ADDRESS','XYMEDIA_CONTROLLER_PORT','XYMEDIA_FUSE_ENABLED','XYMEDIA_FUSE_HOST_PATH','XYMEDIA_XIAOYA_MANAGED','XYMEDIA_INSTANCE_ID','XYMEDIA_POSTGRES_LOCAL_DB','XYMEDIA_POSTGRES_PUBLIC_PORT')
|
||||||
values=dict(zip(keys, sys.argv[2:]))
|
values=dict(zip(keys, sys.argv[2:]))
|
||||||
seen=set(); output=[]
|
seen=set(); output=[]
|
||||||
for line in p.read_text().splitlines():
|
for line in p.read_text().splitlines():
|
||||||
@@ -663,6 +899,15 @@ output.write_text(content)
|
|||||||
PY
|
PY
|
||||||
rm -f "$INSTALL_DIR/compose.yaml" "$INSTALL_DIR/Dockerfile.bootstrap"
|
rm -f "$INSTALL_DIR/compose.yaml" "$INSTALL_DIR/Dockerfile.bootstrap"
|
||||||
cp "$COMPOSE_TEMPLATE" "$DOCKERFILE_TEMPLATE" "$INSTALL_DIR/"
|
cp "$COMPOSE_TEMPLATE" "$DOCKERFILE_TEMPLATE" "$INSTALL_DIR/"
|
||||||
|
if (( PG_HOST_CLI )) || [[ -n ${XYMEDIA_POSTGRES_HOST:-} ]]; then
|
||||||
|
python3 - "$INSTALL_DIR/compose.yaml" <<'PY'
|
||||||
|
import pathlib, re, sys
|
||||||
|
p = pathlib.Path(sys.argv[1])
|
||||||
|
text = p.read_text()
|
||||||
|
text = re.sub(r'\n extra_hosts:\n - "host\.docker\.internal:host-gateway"', '', text)
|
||||||
|
p.write_text(text)
|
||||||
|
PY
|
||||||
|
fi
|
||||||
cp "$COMPOSE_FUSE_TEMPLATE" "$REMOUNT_FUSE_TEMPLATE" "$INSTALL_DIR/"
|
cp "$COMPOSE_FUSE_TEMPLATE" "$REMOUNT_FUSE_TEMPLATE" "$INSTALL_DIR/"
|
||||||
chmod 700 "$INSTALL_DIR/remount-fuse.sh"
|
chmod 700 "$INSTALL_DIR/remount-fuse.sh"
|
||||||
FUSE_ENABLED=$(awk -F= '$1 == "XYMEDIA_FUSE_ENABLED" {print $2; exit}' "$INSTALL_DIR/.env")
|
FUSE_ENABLED=$(awk -F= '$1 == "XYMEDIA_FUSE_ENABLED" {print $2; exit}' "$INSTALL_DIR/.env")
|
||||||
@@ -693,12 +938,12 @@ progress '下载 bootstrap runtime'
|
|||||||
die 'bootstrap runtime 下载失败;请确认能访问 git.keeper.work Container Registry'
|
die 'bootstrap runtime 下载失败;请确认能访问 git.keeper.work Container Registry'
|
||||||
fi
|
fi
|
||||||
progress '启动 PostgreSQL'
|
progress '启动 PostgreSQL'
|
||||||
if [[ $PG_HOST == postgres ]]; then
|
if (( LOCAL_DB )); then
|
||||||
if ! run_logged "${COMPOSE[@]}" --profile local-db --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" up -d postgres; then
|
if ! run_logged "${COMPOSE[@]}" --profile local-db --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" up -d postgres; then
|
||||||
die 'PostgreSQL 启动失败'
|
die 'PostgreSQL 启动失败'
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ $PG_HOST == postgres ]]; then
|
if (( LOCAL_DB )); then
|
||||||
wait_for_postgres() {
|
wait_for_postgres() {
|
||||||
local started_at elapsed container_id health
|
local started_at elapsed container_id health
|
||||||
local previous_int previous_term
|
local previous_int previous_term
|
||||||
@@ -818,5 +1063,10 @@ cp "$INSTALL_LOG" "$INSTALL_DIR/install.log" 2>/dev/null || true
|
|||||||
if [[ $MENU_PROFILES == 'xiaoya xiaoya-control' ]]; then
|
if [[ $MENU_PROFILES == 'xiaoya xiaoya-control' ]]; then
|
||||||
print_controller_info
|
print_controller_info
|
||||||
fi
|
fi
|
||||||
|
if (( LOCAL_DB )); then
|
||||||
|
print_database_info
|
||||||
|
else
|
||||||
|
printf 'PostgreSQL 地址:%s:%s\nPostgreSQL 数据库:%s\nPostgreSQL 用户:%s\n' "$PG_HOST" "$PG_PORT" "$PG_DB" "$PG_USER"
|
||||||
|
fi
|
||||||
printf '安装日志:%s/install.log\n' "$INSTALL_DIR"
|
printf '安装日志:%s/install.log\n' "$INSTALL_DIR"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user