Compare commits
20
Commits
v1.0.4
...
b4340dcd0f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4340dcd0f | ||
|
|
2307402e34 | ||
|
|
9d911ba7af | ||
|
|
646a518e57 | ||
|
|
06c28677e2 | ||
|
|
0537291c04 | ||
|
|
ef605f8a2d | ||
|
|
252a3320df | ||
|
|
63e28aefa0 | ||
|
|
38821d5331 | ||
|
|
f0f100cc99 | ||
|
|
49d4b7e387 | ||
|
|
741908fdc2 | ||
|
|
bd8fb1203a | ||
|
|
8dde04f70b | ||
|
|
dd1a1679e9 | ||
|
|
cd265a5103 | ||
|
|
2ee5a7a06a | ||
|
|
903607e1bf | ||
|
|
f281332474 |
@@ -0,0 +1,5 @@
|
|||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends tini openjdk-17-jre-headless fuse3 zstd curl ca-certificates bash procps && rm -rf /var/lib/apt/lists/*
|
||||||
|
WORKDIR /app
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "--", "/releases/current/bin/xymedia-supervisor"]
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
# xymediavault-releases
|
# XyMediaVault public installer
|
||||||
|
|
||||||
Public XyMediaVault release catalog and downloadable assets
|
Run `curl -fsSL https://git.keeper.work/admin/xymediavault-releases/raw/branch/main/install.sh | sudo bash` from the directory where you want XyMediaVault installed. The default install directory is the command's current directory; use `--install-dir /absolute/path` to override it. The installer downloads `catalog-v1.json` and the public Generic Package assets over HTTPS, selects `linux-amd64` or `linux-arm64`, safely extracts the app archive, and stores TMM and Title archives in the local component directory.
|
||||||
|
|
||||||
|
The app archive must contain `bin/xymediavault`, `bin/xymedia-supervisor`, `web/dist`, and `release.json`. The installer rejects legacy app packages missing the supervisor and waits for the current public catalog entry containing this complete layout.
|
||||||
|
|
||||||
|
Options include `--install-dir`, PostgreSQL connection options, `--postgres-password-file`, `--skip-components`, and `--existing-db`. An external database requires `--existing-db`; the migration command is explicit and never runs against an unconfirmed external database. No source repository, token, signature, or release hash is used by this v1 installer.
|
||||||
|
|||||||
+43
-1
@@ -1 +1,43 @@
|
|||||||
{"schema_version":1,"generated_at":"2026-08-29T09:50:00Z","components":{}}
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"generated_at": "2026-08-30T10:23:00Z",
|
||||||
|
"components": {
|
||||||
|
"title": {
|
||||||
|
"artifacts": {
|
||||||
|
"linux-amd64": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"platform": "linux-amd64",
|
||||||
|
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-title/1.0.6/xymedia-title-1.0.6-linux-amd64.tar.zst"
|
||||||
|
},
|
||||||
|
"linux-arm64": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"platform": "linux-arm64",
|
||||||
|
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-title/1.0.6/xymedia-title-1.0.6-linux-arm64.tar.zst"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tmm": {
|
||||||
|
"artifacts": {
|
||||||
|
"linux-any": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"platform": "linux-any",
|
||||||
|
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-tmm/1.0.0/xymedia-tmm-1.0.0-linux-any.tar.zst"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"artifacts": {
|
||||||
|
"linux-amd64": {
|
||||||
|
"version": "1.4.15",
|
||||||
|
"platform": "linux-amd64",
|
||||||
|
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-app/1.4.15/xymediavault-1.4.15-linux-amd64.tar.gz"
|
||||||
|
},
|
||||||
|
"linux-arm64": {
|
||||||
|
"version": "1.4.15",
|
||||||
|
"platform": "linux-arm64",
|
||||||
|
"asset_url": "https://git.keeper.work/api/packages/admin/generic/xymediavault-app/1.4.15/xymediavault-1.4.15-linux-arm64.tar.gz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-bookworm
|
||||||
|
profiles: [local-db]
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${XYMEDIA_POSTGRES_DATABASE}
|
||||||
|
POSTGRES_USER: ${XYMEDIA_POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD_FILE: /run/secrets/xymedia-postgres-password
|
||||||
|
secrets: [xymedia-postgres-password]
|
||||||
|
volumes: ["${XYMEDIA_INSTALL_DIR}/data/postgres:/var/lib/postgresql/data"]
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD-SHELL, "pg_isready -U ${XYMEDIA_POSTGRES_USER} -d ${XYMEDIA_POSTGRES_DATABASE}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 24
|
||||||
|
app:
|
||||||
|
image: ${XYMEDIA_BOOTSTRAP_IMAGE:-git.keeper.work/admin/xymediavault-bootstrap:1}
|
||||||
|
environment:
|
||||||
|
XYMEDIA_POSTGRES_MODE: external
|
||||||
|
XYMEDIA_COMPONENT_RUNTIME: local
|
||||||
|
XYMEDIA_WEB_DIR: /releases/current/web/dist
|
||||||
|
ports:
|
||||||
|
- "${XYMEDIA_API_PORT:-18080}:8080"
|
||||||
|
- "${XYMEDIA_WEBDAV_PORT:-18081}:8081"
|
||||||
|
- "${XYMEDIA_TVBOX_PORT:-18082}:8082"
|
||||||
|
volumes:
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/data:/app/data"
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/components:/app/components"
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/releases:/releases"
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/config.yaml:/app/config.yaml:ro"
|
||||||
|
secrets: [xymedia-postgres-password]
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD, /releases/current/bin/xymediavault, healthcheck, --url, http://127.0.0.1:8080/api/health]
|
||||||
|
app-migrate:
|
||||||
|
image: ${XYMEDIA_BOOTSTRAP_IMAGE:-git.keeper.work/admin/xymediavault-bootstrap:1}
|
||||||
|
entrypoint: [/releases/current/bin/xymediavault]
|
||||||
|
command: [migrate, --new, --config, /app/config.yaml]
|
||||||
|
environment:
|
||||||
|
XYMEDIA_POSTGRES_MODE: external
|
||||||
|
XYMEDIA_EXTERNAL_MIGRATION_CONFIRM: ${XYMEDIA_EXTERNAL_MIGRATION_CONFIRM:-YES}
|
||||||
|
volumes:
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/data:/app/data"
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/releases:/releases:ro"
|
||||||
|
- "${XYMEDIA_INSTALL_DIR}/config.yaml:/app/config.yaml:ro"
|
||||||
|
secrets: [xymedia-postgres-password]
|
||||||
|
profiles: [migration]
|
||||||
|
restart: "no"
|
||||||
|
secrets:
|
||||||
|
xymedia-postgres-password:
|
||||||
|
file: ${XYMEDIA_INSTALL_DIR}/secrets/postgres-password
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
server:
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 8080
|
||||||
|
web_dir: /releases/current/web/dist
|
||||||
|
database:
|
||||||
|
mode: external
|
||||||
|
driver: postgres
|
||||||
|
host: ${XYMEDIA_POSTGRES_HOST}
|
||||||
|
port: ${XYMEDIA_POSTGRES_PORT}
|
||||||
|
name: ${XYMEDIA_POSTGRES_DATABASE}
|
||||||
|
user: ${XYMEDIA_POSTGRES_USER}
|
||||||
|
password_file: /run/secrets/xymedia-postgres-password
|
||||||
|
sslmode: disable
|
||||||
|
virtual:
|
||||||
|
enable_remote_file_mapping: false
|
||||||
|
webdav:
|
||||||
|
enabled: true
|
||||||
|
listen_addr: 0.0.0.0
|
||||||
|
port: 8081
|
||||||
|
public_port: 18081
|
||||||
|
base_path: /dav
|
||||||
|
read_only: true
|
||||||
|
tvbox:
|
||||||
|
enabled: true
|
||||||
|
listen_addr: 0.0.0.0
|
||||||
|
port: 8082
|
||||||
|
public_port: 18082
|
||||||
|
token_key_file: /app/data/tvbox-token.key
|
||||||
|
fuse:
|
||||||
|
auto_mount: false
|
||||||
|
mount_path: /mnt/xymediavault
|
||||||
|
cache:
|
||||||
|
directory: /app/data/cache
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CATALOG_URL=${XYMEDIA_CATALOG_URL:-https://git.keeper.work/admin/xymediavault-releases/raw/branch/main/catalog-v1.json}
|
||||||
|
RELEASE_BASE=${XYMEDIA_RELEASE_BASE:-https://git.keeper.work/admin/xymediavault-releases/raw/branch/main}
|
||||||
|
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd -P)
|
||||||
|
INSTALL_DIR=${XYMEDIA_INSTALL_DIR:-$PWD}
|
||||||
|
PG_HOST=postgres
|
||||||
|
PG_PORT=5432
|
||||||
|
PG_DB=xymedia
|
||||||
|
PG_USER=xymedia_app
|
||||||
|
PG_PASSWORD_FILE=
|
||||||
|
SKIP_COMPONENTS=0
|
||||||
|
EXISTING_DB=0
|
||||||
|
API_PORT=${XYMEDIA_API_PORT:-18080}
|
||||||
|
WEBDAV_PORT=${XYMEDIA_WEBDAV_PORT:-18081}
|
||||||
|
TVBOX_PORT=${XYMEDIA_TVBOX_PORT:-18082}
|
||||||
|
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"
|
||||||
|
if [[ -n $INSTALL_LOG ]]; then
|
||||||
|
printf '[%d/%d] %s\n' "$STEP" "$TOTAL_STEPS" "$1" >>"$INSTALL_LOG"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
run_logged() { "$@" >>"$INSTALL_LOG" 2>&1; }
|
||||||
|
usage() { sed -n '1,20p' "$0"; }
|
||||||
|
while (($#)); do
|
||||||
|
case "$1" in
|
||||||
|
--install-dir) INSTALL_DIR=${2:?missing value}; shift 2;;
|
||||||
|
--postgres-host) PG_HOST=${2:?missing value}; shift 2;;
|
||||||
|
--postgres-port) PG_PORT=${2:?missing value}; shift 2;;
|
||||||
|
--postgres-db) PG_DB=${2:?missing value}; shift 2;;
|
||||||
|
--postgres-user) PG_USER=${2:?missing value}; shift 2;;
|
||||||
|
--postgres-password-file) PG_PASSWORD_FILE=${2:?missing value}; shift 2;;
|
||||||
|
--skip-components) SKIP_COMPONENTS=1; shift;;
|
||||||
|
--existing-db) EXISTING_DB=1; shift;;
|
||||||
|
-h|--help) usage; exit 0;;
|
||||||
|
*) 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
|
||||||
|
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'
|
||||||
|
if [[ $PG_HOST != postgres && $EXISTING_DB != 1 ]]; then die 'an external database requires --existing-db confirmation'; fi
|
||||||
|
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}
|
||||||
|
write_probe="$INSTALL_DIR/xymediavault-write-probe-$$"
|
||||||
|
if ! touch "$write_probe" || ! rm -f "$write_probe"; then
|
||||||
|
die "安装目录不可写:$INSTALL_DIR;请使用 --install-dir 指定本机可写目录,例如 /opt/xymediavault"
|
||||||
|
fi
|
||||||
|
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'
|
||||||
|
if [[ "$PG_PASSWORD_FILE" != "$INSTALL_DIR/secrets/postgres-password" ]]; then
|
||||||
|
cp "$PG_PASSWORD_FILE" "$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
|
PG_PASSWORD_FILE="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
PG_PASSWORD_FILE="$INSTALL_DIR/secrets/xymedia-postgres-password"
|
||||||
|
if [[ ! -s $PG_PASSWORD_FILE ]]; then
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
chmod 600 "$PG_PASSWORD_FILE"
|
||||||
|
TEMPLATE_DIR="$INSTALL_DIR/bootstrap"
|
||||||
|
mkdir -p "$TEMPLATE_DIR"
|
||||||
|
template_file() {
|
||||||
|
local name=$1 downloaded="$TEMPLATE_DIR/$1"
|
||||||
|
curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 "$RELEASE_BASE/$name" -o "$downloaded"
|
||||||
|
printf '%s\n' "$downloaded"
|
||||||
|
}
|
||||||
|
CONFIG_TEMPLATE=$(template_file config.yaml)
|
||||||
|
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
|
||||||
|
catalog = json.load(open(sys.argv[1]))
|
||||||
|
assert catalog.get("schema_version") == 1
|
||||||
|
for component in ("app", "tmm", "title"):
|
||||||
|
assert isinstance(catalog.get("components", {}).get(component, {}).get("artifacts"), dict)
|
||||||
|
PY
|
||||||
|
case "$(uname -m)" in x86_64|amd64) PLATFORM=linux-amd64;; aarch64|arm64) PLATFORM=linux-arm64;; *) die "unsupported host architecture: $(uname -m)";; esac
|
||||||
|
artifact() {
|
||||||
|
python3 - "$INSTALL_DIR/catalog-v1.json" "$1" "$2" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
catalog, component, platform = sys.argv[1:]
|
||||||
|
artifacts = json.load(open(catalog))["components"][component]["artifacts"]
|
||||||
|
entry = artifacts.get(platform) or artifacts.get("linux-any")
|
||||||
|
if not isinstance(entry, dict) or not isinstance(entry.get("version"), str) or not isinstance(entry.get("asset_url"), str):
|
||||||
|
raise SystemExit(1)
|
||||||
|
print(entry["version"] + "\t" + entry["asset_url"])
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
download_extract() {
|
||||||
|
local component=$1
|
||||||
|
local version=$2
|
||||||
|
local url=$3
|
||||||
|
local target=$4
|
||||||
|
local archive="$INSTALL_DIR/state/$component-$version.archive"
|
||||||
|
curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 "$url" -o "$archive"
|
||||||
|
local tarball="$archive.tar"
|
||||||
|
if [[ $url == *.tar.gz || $url == *.tgz ]]; then gzip -dc "$archive" >"$tarball"; else zstd -dc "$archive" >"$tarball"; fi
|
||||||
|
python3 - "$tarball" "$target" <<'PY'
|
||||||
|
import pathlib, sys, tarfile
|
||||||
|
archive, target = sys.argv[1:]
|
||||||
|
pathlib.Path(target).mkdir(parents=True, exist_ok=True)
|
||||||
|
with tarfile.open(archive, 'r:*') as tf:
|
||||||
|
members = tf.getmembers()
|
||||||
|
for m in members:
|
||||||
|
name = m.name.lstrip('./')
|
||||||
|
if not name or name.startswith('/') or '..' in pathlib.PurePosixPath(name).parts:
|
||||||
|
raise SystemExit('archive contains traversal path')
|
||||||
|
if m.issym() or m.islnk() or not (m.isfile() or m.isdir()):
|
||||||
|
raise SystemExit('archive contains link or special file')
|
||||||
|
m.name = name
|
||||||
|
tf.extractall(target, members=members)
|
||||||
|
PY
|
||||||
|
rm -f "$tarball"
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
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="$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'
|
||||||
|
else
|
||||||
|
for required in payload/python/bin/python payload/service/entrypoint.py payload/service/app.py payload/service/engine.json payload/NOTICE payload/licenses/guessit-LICENSE.txt payload/licenses/flask-LICENSE.txt payload/licenses/gunicorn-LICENSE.txt; do [[ -f "$component_root/$required" ]] || die "Title archive is missing $required"; done
|
||||||
|
fi
|
||||||
|
cp "$archive" "$INSTALL_DIR/components/$component.tar.zst"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
PG_HOST="$PG_HOST" PG_PORT="$PG_PORT" PG_DB="$PG_DB" PG_USER="$PG_USER" python3 - "$CONFIG_TEMPLATE" "$INSTALL_DIR/config.yaml" <<'PY'
|
||||||
|
import os, pathlib, sys
|
||||||
|
template, output = map(pathlib.Path, sys.argv[1:])
|
||||||
|
values = {
|
||||||
|
"${XYMEDIA_POSTGRES_HOST}": os.environ["PG_HOST"],
|
||||||
|
"${XYMEDIA_POSTGRES_PORT}": os.environ["PG_PORT"],
|
||||||
|
"${XYMEDIA_POSTGRES_DATABASE}": os.environ["PG_DB"],
|
||||||
|
"${XYMEDIA_POSTGRES_USER}": os.environ["PG_USER"],
|
||||||
|
}
|
||||||
|
content = template.read_text()
|
||||||
|
for placeholder, value in values.items():
|
||||||
|
content = content.replace(placeholder, value)
|
||||||
|
output.write_text(content)
|
||||||
|
PY
|
||||||
|
cp "$COMPOSE_TEMPLATE" "$DOCKERFILE_TEMPLATE" "$INSTALL_DIR/"
|
||||||
|
progress '下载 bootstrap runtime'
|
||||||
|
run_logged "${COMPOSE[@]}" --project-directory "$INSTALL_DIR" --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.yaml" pull app || die 'bootstrap runtime 下载失败;请确认能访问 git.keeper.work Container Registry'
|
||||||
|
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 未在限定时间内通过健康检查'
|
||||||
Reference in New Issue
Block a user