From 0537291c047f77f25104a8579f8797313f28ea82 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 30 Aug 2026 13:05:40 +0000 Subject: [PATCH] feat: show installer progress and logs --- install.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/install.sh b/install.sh index 27750de..9d255a4 100644 --- a/install.sh +++ b/install.sh @@ -15,7 +15,16 @@ EXISTING_DB=0 API_PORT=${XYMEDIA_API_PORT:-18080} WEBDAV_PORT=${XYMEDIA_WEBDAV_PORT:-18081} TVBOX_PORT=${XYMEDIA_TVBOX_PORT:-18082} -die() { printf 'xymediavault installer: %s\n' "$*" >&2; exit 1; } +TOTAL_STEPS=9 +STEP=0 +INSTALL_LOG= +die() { printf '\n\033[31mxymediavault installer: %s\033[0m\n' "$*" >&2; [[ -n $INSTALL_LOG ]] && printf '详情日志:%s\n' "$INSTALL_LOG" >&2; exit 1; } +progress() { + STEP=$((STEP + 1)) + printf '\033[2K\r[%d/%d] %s\n' "$STEP" "$TOTAL_STEPS" "$1" + [[ -n $INSTALL_LOG ]] && printf '[%d/%d] %s\n' "$STEP" "$TOTAL_STEPS" "$1" >>"$INSTALL_LOG" +} +run_logged() { "$@" >>"$INSTALL_LOG" 2>&1; } usage() { sed -n '1,20p' "$0"; } while (($#)); do case "$1" in @@ -31,6 +40,7 @@ while (($#)); do *) die "unknown option: $1";; esac done +progress '检查安装环境' [[ $INSTALL_DIR = /* && $INSTALL_DIR != / ]] || die 'install directory must be an absolute non-root path' 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 @@ -42,7 +52,11 @@ if [[ $PG_HOST != postgres && $EXISTING_DB != 1 ]]; then die 'an external databa if [[ $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 ! [[ $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 +progress '准备安装目录与数据库凭据' mkdir -p "$INSTALL_DIR" "$INSTALL_DIR"/{data/postgres,data,state,components,releases,secrets,config} +INSTALL_LOG="$INSTALL_DIR/install.log" +: >"$INSTALL_LOG" +chmod 600 "$INSTALL_LOG" chmod 700 "$INSTALL_DIR/secrets" if [[ -n $PG_PASSWORD_FILE ]]; then [[ -r $PG_PASSWORD_FILE ]] || die 'password file is not readable' @@ -74,6 +88,7 @@ COMPOSE_TEMPLATE=$(template_file compose.yaml) DOCKERFILE_TEMPLATE=$(template_file Dockerfile.bootstrap) umask 077 printf 'XYMEDIA_INSTALL_DIR=%s\nXYMEDIA_POSTGRES_HOST=%s\nXYMEDIA_POSTGRES_PORT=%s\nXYMEDIA_POSTGRES_DATABASE=%s\nXYMEDIA_POSTGRES_USER=%s\nXYMEDIA_API_PORT=%s\nXYMEDIA_WEBDAV_PORT=%s\nXYMEDIA_TVBOX_PORT=%s\n' "$INSTALL_DIR" "$PG_HOST" "$PG_PORT" "$PG_DB" "$PG_USER" "$API_PORT" "$WEBDAV_PORT" "$TVBOX_PORT" >"$INSTALL_DIR/.env" +progress '下载公开版本目录' curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 "$CATALOG_URL" -o "$INSTALL_DIR/catalog-v1.json" python3 - "$INSTALL_DIR/catalog-v1.json" <<'PY' || die 'invalid public catalog' import json, sys @@ -122,6 +137,7 @@ PY } IFS=$'\t' read -r APP_VERSION APP_URL < <(artifact app "$PLATFORM") APP_STAGE="$INSTALL_DIR/state/app-$APP_VERSION" +progress "下载应用包 $APP_VERSION ($PLATFORM)" download_extract app "$APP_VERSION" "$APP_URL" "$APP_STAGE" APP_ROOT="$APP_STAGE/xymediavault-$APP_VERSION-$PLATFORM" [[ -x $APP_ROOT/bin/xymediavault && -x $APP_ROOT/bin/xymedia-supervisor && -d $APP_ROOT/web/dist && -f $APP_ROOT/release.json ]] || die "app package $APP_VERSION lacks the required xymedia-supervisor; use a newer public app package" @@ -129,14 +145,18 @@ mkdir -p "$INSTALL_DIR/releases/releases" [[ ! -e "$INSTALL_DIR/releases/releases/$APP_VERSION" ]] && mv "$APP_ROOT" "$INSTALL_DIR/releases/releases/$APP_VERSION" ln -sfn "releases/$APP_VERSION" "$INSTALL_DIR/releases/current" if (( ! SKIP_COMPONENTS )); then + progress '下载 TMM 与 Title 组件包' for component in tmm title; do platform=$([[ $component == tmm ]] && printf linux-any || printf "$PLATFORM") IFS=$'\t' read -r version url < <(artifact "$component" "$platform") download_extract "$component" "$version" "$url" "$INSTALL_DIR/state/$component-$version" archive="$INSTALL_DIR/state/$component-$version.archive" [[ -s $archive ]] || die "$component archive was not downloaded" - component_root=$(find "$INSTALL_DIR/state/$component-$version" -mindepth 1 -maxdepth 1 -type d | head -n 1) - [[ -n $component_root ]] || die "$component archive has no root directory" + component_root="$INSTALL_DIR/state/$component-$version" + if [[ ! -d "$component_root/payload" ]]; then + component_root=$(find "$component_root" -mindepth 1 -maxdepth 1 -type d | head -n 1) + [[ -n $component_root && -d "$component_root/payload" ]] || die "$component archive has no payload directory" + fi if [[ $component == tmm ]]; then [[ -f "$component_root/payload/xymedia-api.jar" ]] || die 'TMM archive is missing payload/xymedia-api.jar' compgen -G "$component_root/payload/lib/*" >/dev/null || die 'TMM archive is missing payload/lib files' @@ -162,11 +182,30 @@ output.write_text(content) PY cp "$COMPOSE_TEMPLATE" "$DOCKERFILE_TEMPLATE" "$INSTALL_DIR/" if [[ ! -f "$INSTALL_DIR/.bootstrap-dockerfile.sha256" || "$(sha256sum "$INSTALL_DIR/Dockerfile.bootstrap" | cut -d' ' -f1)" != "$(<"$INSTALL_DIR/.bootstrap-dockerfile.sha256")" ]]; then - "${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" build app + progress '构建首次 bootstrap runtime' + run_logged "${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" build app || die 'bootstrap runtime 构建失败' sha256sum "$INSTALL_DIR/Dockerfile.bootstrap" | cut -d' ' -f1 >"$INSTALL_DIR/.bootstrap-dockerfile.sha256" fi -if [[ $PG_HOST == postgres ]]; then "${COMPOSE[@]}" --profile local-db --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" up -d postgres; fi -if [[ $PG_HOST == postgres ]]; then for _ in {1..60}; do "${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" exec -T postgres pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null 2>&1 && break; sleep 2; done; fi -if (( EXISTING_DB )); then export XYMEDIA_EXTERNAL_MIGRATION_CONFIRM=YES; "${COMPOSE[@]}" --profile migration --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" run --rm --no-deps app-migrate migrate --config /app/config.yaml; else "${COMPOSE[@]}" --profile migration --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" run --rm --no-deps app-migrate migrate --new --config /app/config.yaml; fi -"${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" up -d app -for _ in {1..60}; do curl --fail --silent "http://127.0.0.1:$API_PORT/api/health" >/dev/null && printf 'XyMediaVault is ready at http://127.0.0.1:%s\n' "$API_PORT" && exit 0; sleep 2; done +progress '启动 PostgreSQL' +if [[ $PG_HOST == postgres ]]; then run_logged "${COMPOSE[@]}" --profile local-db --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" up -d postgres || die 'PostgreSQL 启动失败'; fi +if [[ $PG_HOST == postgres ]]; then + ready=0 + for elapsed in $(seq 0 118 2); do + "${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" exec -T postgres pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null 2>&1 && { ready=1; break; } + printf '\033[2K\r[%d/%d] 等待 PostgreSQL 就绪(%s 秒)' "$STEP" "$TOTAL_STEPS" "$elapsed" + sleep 2 + done + printf '\n' + [[ $ready = 1 ]] || die 'PostgreSQL 未在限定时间内就绪' +fi +progress '初始化数据库结构' +if (( EXISTING_DB )); then export XYMEDIA_EXTERNAL_MIGRATION_CONFIRM=YES; run_logged "${COMPOSE[@]}" --profile migration --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" run --rm --no-deps app-migrate migrate --config /app/config.yaml || die '已有数据库迁移失败'; else run_logged "${COMPOSE[@]}" --profile migration --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" run --rm --no-deps app-migrate migrate --new --config /app/config.yaml || die '数据库初始化失败'; fi +progress '启动 XyMediaVault 服务' +run_logged "${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" up -d app || die 'XyMediaVault 启动失败' +for elapsed in $(seq 0 118 2); do + curl --fail --silent "http://127.0.0.1:$API_PORT/api/health" >/dev/null && { printf '\033[2K\r[%d/%d] 安装完成:XyMediaVault 已就绪\n' "$TOTAL_STEPS" "$TOTAL_STEPS"; printf '管理地址:http://127.0.0.1:%s\n安装日志:%s\n' "$API_PORT" "$INSTALL_LOG"; exit 0; } + printf '\033[2K\r[%d/%d] 等待 XyMediaVault 健康检查(%s 秒)' "$STEP" "$TOTAL_STEPS" "$elapsed" + sleep 2 +done +printf '\n' +die 'XyMediaVault 未在限定时间内通过健康检查'