release: sync public installer asset remount-fuse.sh
This commit is contained in:
+94
-4
@@ -39,7 +39,6 @@ if [ ! -e "$state_dir" ]; then umask 077; mkdir "$state_dir" || die 'cannot crea
|
|||||||
[ "$(stat -c '%u' "$state_dir")" -eq 0 ] || die 'state directory must be owned by root'
|
[ "$(stat -c '%u' "$state_dir")" -eq 0 ] || die 'state directory must be owned by root'
|
||||||
state_mode=$(stat -c '%a' "$state_dir")
|
state_mode=$(stat -c '%a' "$state_dir")
|
||||||
[ $(( 0$state_mode & 022 )) -eq 0 ] || die 'state directory must not be group/world writable'
|
[ $(( 0$state_mode & 022 )) -eq 0 ] || die 'state directory must not be group/world writable'
|
||||||
[ -e /dev/fuse ] && [ -c /dev/fuse ] || die '/dev/fuse character device is required'
|
|
||||||
command -v docker >/dev/null 2>&1 || die 'docker is required'
|
command -v docker >/dev/null 2>&1 || die 'docker is required'
|
||||||
docker compose version >/dev/null 2>&1 || die 'Docker Compose V2 is required'
|
docker compose version >/dev/null 2>&1 || die 'Docker Compose V2 is required'
|
||||||
command -v findmnt >/dev/null 2>&1 || die 'findmnt is required'
|
command -v findmnt >/dev/null 2>&1 || die 'findmnt is required'
|
||||||
@@ -93,6 +92,20 @@ elif [ "$old_enabled" = true ]; then
|
|||||||
fi
|
fi
|
||||||
if [ "$action" != disable ] && [ -z "$new_path" ]; then die '未配置 FUSE 主机路径'; fi
|
if [ "$action" != disable ] && [ -z "$new_path" ]; then die '未配置 FUSE 主机路径'; fi
|
||||||
|
|
||||||
|
print_preflight() {
|
||||||
|
printf 'xymediavault FUSE: preflight diagnostics\n' >&2
|
||||||
|
printf ' install_dir: %s\n' "$install_dir" >&2
|
||||||
|
printf ' configured host path: %s\n' "${new_path:-${old_path:-<none>}}" >&2
|
||||||
|
if [ -e /dev/fuse ]; then
|
||||||
|
printf ' host /dev/fuse: %s (%s)\n' "$( [ -c /dev/fuse ] && printf present || printf 'wrong type' )" "$(stat -c '%F' /dev/fuse 2>/dev/null || printf 'unreadable')" >&2
|
||||||
|
else
|
||||||
|
printf ' host /dev/fuse: missing\n' >&2
|
||||||
|
fi
|
||||||
|
printf ' compose.fuse.yaml: %s\n' "$( [ -f "$install_dir/compose.fuse.yaml" ] && printf present || printf missing )" >&2
|
||||||
|
}
|
||||||
|
print_preflight
|
||||||
|
[ -e /dev/fuse ] && [ -c /dev/fuse ] || die '/dev/fuse character device is required'
|
||||||
|
|
||||||
compose_check_env=${new_path:-$old_path}
|
compose_check_env=${new_path:-$old_path}
|
||||||
if [ "$action" = disable ] || [ -z "$compose_check_env" ]; then
|
if [ "$action" = disable ] || [ -z "$compose_check_env" ]; then
|
||||||
docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" config -q || die 'compose configuration is invalid'
|
docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" config -q || die 'compose configuration is invalid'
|
||||||
@@ -118,8 +131,20 @@ config_snapshot=$(mktemp "$private_tmp/config.snapshot.XXXXXX") || { rm -rf "$pr
|
|||||||
cp "$install_dir/.env" "$env_snapshot"; cp "$install_dir/config.yaml" "$config_snapshot"
|
cp "$install_dir/.env" "$env_snapshot"; cp "$install_dir/config.yaml" "$config_snapshot"
|
||||||
LOG_FILE=$log_file
|
LOG_FILE=$log_file
|
||||||
env_tmp= config_tmp=
|
env_tmp= config_tmp=
|
||||||
cleanup() { status=$?; rm -f "$env_tmp" "$config_tmp"; rm -rf "$private_tmp"; rmdir "$lock_dir" 2>/dev/null || true; exit "$status"; }
|
log_pid=
|
||||||
trap cleanup EXIT INT TERM
|
cleanup() {
|
||||||
|
status=$?
|
||||||
|
trap - 0 1 2 3 15
|
||||||
|
if [ -n "$log_pid" ]; then
|
||||||
|
kill "$log_pid" 2>/dev/null || true
|
||||||
|
wait "$log_pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -f "$env_tmp" "$config_tmp"
|
||||||
|
rm -rf "$private_tmp"
|
||||||
|
rmdir "$lock_dir" 2>/dev/null || true
|
||||||
|
exit "$status"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 2 3 15
|
||||||
|
|
||||||
compose() { docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" "$@"; }
|
compose() { docker compose --project-directory "$install_dir" --env-file "$install_dir/.env" -f "$install_dir/compose.yaml" "$@"; }
|
||||||
compose_old() { if [ "$old_enabled" = true ]; then compose -f "$install_dir/compose.fuse.yaml" "$@"; else compose "$@"; fi; }
|
compose_old() { if [ "$old_enabled" = true ]; then compose -f "$install_dir/compose.fuse.yaml" "$@"; else compose "$@"; fi; }
|
||||||
@@ -168,15 +193,80 @@ PY
|
|||||||
then
|
then
|
||||||
fail_restore 'failed to update environment or config'
|
fail_restore 'failed to update environment or config'
|
||||||
fi
|
fi
|
||||||
if [ "$enabled" = true ]; then compose -f "$install_dir/compose.fuse.yaml" up -d app >/dev/null; else compose up -d app >/dev/null; fi
|
startup_since=$(date '+%Y-%m-%dT%H:%M:%S%z')
|
||||||
|
if [ "$enabled" = true ]; then
|
||||||
|
if ! compose -f "$install_dir/compose.fuse.yaml" up -d app >/dev/null; then
|
||||||
|
fail_restore 'failed to start app: docker compose up failed'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! compose up -d app >/dev/null; then
|
||||||
|
fail_restore 'failed to start app: docker compose up failed'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
docker logs -f --since "$startup_since" xymedia-app >&2 &
|
||||||
|
log_pid=$!
|
||||||
|
health_result=unavailable
|
||||||
|
container_status=unknown
|
||||||
|
container_fuse=unknown
|
||||||
|
status_mount_source=
|
||||||
|
status_mount_fstype=
|
||||||
|
print_status() {
|
||||||
|
inspect_mount "${new_path:-${old_path:-/nonexistent}}" || true
|
||||||
|
status_mount_source=${mount_source:-absent}
|
||||||
|
status_mount_fstype=${mount_fstype:-absent}
|
||||||
|
container_status=unknown
|
||||||
|
container_fuse=unknown
|
||||||
|
inspected_status=$(docker inspect --format '{{.State.Status}}' xymedia-app 2>/dev/null || true)
|
||||||
|
case "$inspected_status" in
|
||||||
|
created|restarting|running|paused|exited|dead) container_status=$inspected_status;;
|
||||||
|
*) container_status=unavailable;;
|
||||||
|
esac
|
||||||
|
if [ "$container_status" = running ]; then
|
||||||
|
if docker exec xymedia-app test -c /dev/fuse >/dev/null 2>&1; then container_fuse=present; else container_fuse=missing; fi
|
||||||
|
fi
|
||||||
|
printf 'xymediavault FUSE: status attempt %s/60: health=%s, host mount source=%s, fstype=%s, container status=%s, container /dev/fuse=%s\n' \
|
||||||
|
"$attempt" "$health_result" "$status_mount_source" "$status_mount_fstype" "$container_status" "$container_fuse" >&2
|
||||||
|
}
|
||||||
|
print_timeout_diagnostics() {
|
||||||
|
printf 'xymediavault FUSE: timeout diagnostics\n' >&2
|
||||||
|
if [ ! -e /dev/fuse ] || [ ! -c /dev/fuse ]; then
|
||||||
|
printf ' likely cause: host /dev/fuse is missing or is not a character device\n' >&2
|
||||||
|
elif [ "$enabled" = true ] && [ "$container_fuse" = missing ]; then
|
||||||
|
printf ' likely cause: compose/container is missing /dev/fuse\n' >&2
|
||||||
|
elif [ "$enabled" = true ] && [ "$container_fuse" = unknown ]; then
|
||||||
|
printf ' likely cause: app container is unavailable; container /dev/fuse could not be confirmed\n' >&2
|
||||||
|
elif [ "$health_result" = unavailable ]; then
|
||||||
|
printf ' likely cause: app health unavailable\n' >&2
|
||||||
|
elif [ -z "$status_mount_source" ] || [ "$status_mount_source" = absent ]; then
|
||||||
|
printf ' likely cause: app is healthy but host FUSE mount is absent\n' >&2
|
||||||
|
else
|
||||||
|
printf ' likely cause: host mount exists with unexpected source/fstype (source=%s, fstype=%s)\n' "$status_mount_source" "$status_mount_fstype" >&2
|
||||||
|
fi
|
||||||
|
printf ' recent xymedia-app logs (bounded):\n' >&2
|
||||||
|
docker logs --tail 80 xymedia-app 2>&1 | while IFS= read -r line; do printf ' %s\n' "$line"; done >&2 || true
|
||||||
|
printf ' container diagnostics (status and device mapping only):\n' >&2
|
||||||
|
docker inspect --format '{{.State.Status}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}} devices={{range .HostConfig.Devices}}{{.PathOnHost}}:{{.PathInContainer}} {{end}}' xymedia-app 2>&1 >&2 || true
|
||||||
|
printf ' compose configuration validation: ' >&2
|
||||||
|
if [ "$enabled" = true ]; then
|
||||||
|
compose -f "$install_dir/compose.fuse.yaml" config -q >/dev/null 2>&1 && printf 'passed\n' >&2 || printf 'failed\n' >&2
|
||||||
|
else
|
||||||
|
compose config -q >/dev/null 2>&1 && printf 'passed\n' >&2 || printf 'failed\n' >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
attempt=0
|
attempt=0
|
||||||
while [ "$attempt" -lt 60 ]; do
|
while [ "$attempt" -lt 60 ]; do
|
||||||
attempt=$((attempt+1))
|
attempt=$((attempt+1))
|
||||||
if curl -fsS --max-time 5 "http://127.0.0.1:${XYMEDIA_API_PORT:-18080}/api/health" >/dev/null 2>&1; then
|
if curl -fsS --max-time 5 "http://127.0.0.1:${XYMEDIA_API_PORT:-18080}/api/health" >/dev/null 2>&1; then
|
||||||
|
health_result=healthy
|
||||||
if [ "$enabled" = false ]; then log 'FUSE disabled and base app is healthy'; exit 0; fi
|
if [ "$enabled" = false ]; then log 'FUSE disabled and base app is healthy'; exit 0; fi
|
||||||
inspect_mount "$new_path" || fail_restore 'mount identity is unstable after start'
|
inspect_mount "$new_path" || fail_restore 'mount identity is unstable after start'
|
||||||
[ "$mount_source" = xymediavault ] && { [ "$mount_fstype" = fuse.xymediavault ] || [ "$mount_fstype" = fuse ]; } && log "FUSE mounted: $new_path" && exit 0
|
[ "$mount_source" = xymediavault ] && { [ "$mount_fstype" = fuse.xymediavault ] || [ "$mount_fstype" = fuse ]; } && log "FUSE mounted: $new_path" && exit 0
|
||||||
|
else
|
||||||
|
health_result=unavailable
|
||||||
fi
|
fi
|
||||||
|
if [ "$attempt" -eq 1 ] || [ $((attempt % 5)) -eq 0 ]; then print_status; fi
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
print_status
|
||||||
|
print_timeout_diagnostics
|
||||||
fail_restore 'app health or FUSE mount timed out'
|
fail_restore 'app health or FUSE mount timed out'
|
||||||
|
|||||||
Reference in New Issue
Block a user