release: sync public installer asset install.sh

This commit is contained in:
2026-09-02 03:09:43 +00:00
parent c5281c1b6b
commit 529ebbd4c8
+24 -24
View File
@@ -41,8 +41,8 @@ MAINTENANCE_ACTION=
FUSE_PATH_INPUT=
confirm_fuse_action() {
local confirmation
confirmation=$(prompt_tty '该操作将停止并重新启动 app;输入 FUSE_CONFIRM 继续 ')
[[ $confirmation == FUSE_CONFIRM ]] || die '未确认 FUSE 生命周期操作'
confirmation=$(prompt_tty '该操作将停止并重新启动应用,输入 1 继续,其他内容取消 ')
[[ $confirmation == 1 ]] || die '已取消挂载操作'
}
require_public_app_installation() {
local missing=()
@@ -80,14 +80,14 @@ print_fuse_status() {
if [[ -n $mount_id && $source == xymediavault ]] && { [[ $fstype == fuse.xymediavault ]] || [[ $fstype == fuse ]]; }; then
printf '挂载身份:owned xymediavault/fuse.xymediavaultID=%s\n' "$mount_id" >>$TTY_OUT
elif [[ -n $mount_id ]]; then
printf '挂载身份:foreign/unknownID=%s source=%s fstype=%s\n' "$mount_id" "$source" "$fstype" >>$TTY_OUT
printf '挂载身份:外部或未知(ID=%s 来源=%s 类型=%s\n' "$mount_id" "$source" "$fstype" >>$TTY_OUT
else
printf '挂载身份:无法确认\n' >>$TTY_OUT
fi
fi
health=$(curl --silent --show-error --max-time 5 -o /dev/null -w '%{http_code}' "http://127.0.0.1:${API_PORT}/api/health" 2>/dev/null || true)
[[ $health =~ ^[0-9]{3}$ ]] || health='无法连接'
printf 'app health%s\n' "$health" >>$TTY_OUT
printf '应用健康状态%s\n' "$health" >>$TTY_OUT
}
while (($#)); do
case "$1" in
@@ -100,7 +100,7 @@ while (($#)); do
--skip-components) SKIP_COMPONENTS=1; shift;;
--existing-db) EXISTING_DB=1; shift;;
-h|--help) usage; exit 0;;
*) die "unknown option: $1";;
*) die "未知选项:$1";;
esac
done
die() { printf '\n\033[31mxymediavault installer: %s\033[0m\n' "$*" >&2; [[ -n $INSTALL_LOG ]] && printf '详情日志:%s\n' "$INSTALL_LOG" >&2; exit 1; }
@@ -109,11 +109,11 @@ cleanup_installer_tmp() { [[ -z ${INSTALL_TMP_DIR:-} ]] || rm -rf -- "$INSTALL_T
trap cleanup_installer_tmp EXIT
init_installer_log() {
local tmp_parent=${TMPDIR:-/tmp} mode
[[ -d $tmp_parent && ! -L $tmp_parent ]] || die 'TMPDIR must be an existing non-symlink directory'
[[ -d $tmp_parent && ! -L $tmp_parent ]] || die '临时目录必须存在且不能是符号链接目录'
if (( EUID == 0 )); then
mode=$(stat -c '%a' "$tmp_parent")
[[ $(stat -c '%u' "$tmp_parent") -eq 0 ]] || die 'TMPDIR must be owned by root when running as root'
[[ $((mode & 022)) -eq 0 || $((mode & 1000)) -ne 0 ]] || die 'TMPDIR must not be group/world writable unless sticky'
[[ $(stat -c '%u' "$tmp_parent") -eq 0 ]] || die '以管理员权限运行时,临时目录必须由 root 所有'
[[ $((mode & 022)) -eq 0 || $((mode & 1000)) -ne 0 ]] || die '临时目录不能允许组用户或其他用户写入,除非启用了粘滞位'
fi
umask 077
INSTALL_TMP_DIR=$(mktemp -d "$tmp_parent/xymediavault-install.XXXXXX") || die '无法创建私有安装临时目录'
@@ -154,8 +154,8 @@ run_visible_logged() {
}
usage() { sed -n '1,20p' "$0"; }
read_tty() { if ((TTY_TEST_MODE)); then [[ $TTY_TEST_INDEX -lt ${#TTY_TEST_LINES[@]} ]] || return 1; printf -v "$1" '%s' "${TTY_TEST_LINES[$TTY_TEST_INDEX]}"; TTY_TEST_INDEX=$((TTY_TEST_INDEX + 1)); else IFS= read -r "$1" <"$TTY_IN"; fi; }
prompt_tty() { [[ -r $TTY_IN && -w $TTY_OUT ]] || die 'interactive menu requires a TTY; use menu option 1 from a terminal'; printf '%s' "$1" >>$TTY_OUT; read_tty value || die 'unable to read terminal input'; printf '%s' "$value"; }
prompt_tty_into() { [[ -r $TTY_IN && -w $TTY_OUT ]] || die 'interactive menu requires a TTY; use menu option 1 from a terminal'; printf '%s' "$2" >>$TTY_OUT; read_tty "$1" || die 'unable to read terminal input'; }
prompt_tty() { [[ -r $TTY_IN && -w $TTY_OUT ]] || die '交互菜单需要终端,请在终端中运行安装器'; printf '%s' "$1" >>$TTY_OUT; read_tty value || die '无法读取终端输入'; printf '%s' "$value"; }
prompt_tty_into() { [[ -r $TTY_IN && -w $TTY_OUT ]] || die '交互菜单需要终端,请在终端中运行安装器'; printf '%s' "$2" >>$TTY_OUT; read_tty "$1" || die '无法读取终端输入'; }
controller_token_file() { printf '%s/secrets/xiaoya-controller-token' "$INSTALL_DIR"; }
ensure_xiaoya_dir() {
local required=${1:-0}
@@ -165,10 +165,10 @@ ensure_xiaoya_dir() {
[[ $XIAOYA_DIR = /* && $XIAOYA_DIR != / ]] || die '小雅目录必须是绝对路径且不能是根目录'
if [[ -d $XIAOYA_DIR ]] && [[ -n $(find "$XIAOYA_DIR" -mindepth 1 -maxdepth 1 -print -quit) ]]; then
printf '检测到已有非空小雅目录:%s\n' "$XIAOYA_DIR" >>$TTY_OUT
printf '请选择 attach / install-controller / separate / return' >>$TTY_OUT
read_tty choice || die 'unable to read terminal input'
[[ $choice == attach || $choice == install-controller || $choice == separate || $choice == return ]] || die '无效选择,已停止以保护已有数据'
[[ $choice != return ]] || exit 0
printf '请选择1) 继续使用此目录 0) 返回' >>$TTY_OUT
read_tty choice || die '无法读取终端输入'
[[ $choice == 1 || $choice == 0 ]] || die '无效选择,已停止以保护已有数据'
[[ $choice != 0 ]] || exit 0
fi
mkdir -p "$XIAOYA_DIR" "$INSTALL_DIR/secrets"
}
@@ -178,10 +178,10 @@ write_controller_token() {
}
menu() {
[[ $# -eq 0 ]] || return 0
[[ -r $TTY_IN && -w $TTY_OUT ]] || die 'no TTY: interactive installer menu cannot run from a non-interactive pipe'
[[ -r $TTY_IN && -w $TTY_OUT ]] || die '没有可用终端:安装菜单不能通过非交互管道运行'
while :; do
printf '\nXyMediaVault 安装器\n1) 仅安装管理平台\n2) 安装管理平台和本机小雅\n3) 安装管理平台并连接远程小雅控制器\n4) 仅安装小雅控制器\n5) 状态/诊断/维护\n0) 退出\n选择:' >>$TTY_OUT
read_tty choice || die 'unable to read terminal input'
read_tty choice || die '无法读取终端输入'
case "$choice" in
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;;
@@ -189,7 +189,7 @@ menu() {
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
printf '\n维护菜单\n1) 查看状态\n2) 一键挂载本地媒体库\n3) 启动/重启已配置挂载\n4) 停用本地媒体库挂载\n0) 返回\n选择:' >>$TTY_OUT
read_tty maintenance_choice || die 'unable to read terminal input'
read_tty maintenance_choice || die '无法读取终端输入'
case "$maintenance_choice" in
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;;
@@ -228,15 +228,15 @@ validate_container_name() {
[[ $XIAOYA_CONTAINER =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}$ ]] || die '小雅容器名称不合法,不能包含换行或 Compose 特殊字符'
}
progress '检查安装环境'
[[ $INSTALL_DIR = /* && $INSTALL_DIR != / ]] || die 'install directory must be an absolute non-root path'
[[ $INSTALL_DIR = /* && $INSTALL_DIR != / ]] || die '安装目录必须是绝对路径且不能是根目录'
guard_existing_install_tree
init_installer_log
command -v docker >/dev/null || die 'Docker is required'
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 is required'; fi
docker info >/dev/null 2>&1 || die 'cannot connect to Docker'
command -v curl >/dev/null || die 'curl is required'
command -v zstd >/dev/null || die 'zstd is required for release archives'
command -v python3 >/dev/null || die 'python3 is required for safe archive extraction'
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
docker info >/dev/null 2>&1 || die '无法连接 Docker,请确认 Docker 已启动'
command -v curl >/dev/null || die '需要安装 curl'
command -v zstd >/dev/null || die '需要安装 zstd,用于解压发布包'
command -v python3 >/dev/null || die '需要安装 python3,用于安全解压发布包'
if [[ -n $MAINTENANCE_ACTION ]]; then
require_public_app_installation || die '维护操作仅支持已有完整 app 安装'
case "$MAINTENANCE_ACTION" in