Tick elapsed seconds on blocking waits, not just API polls

Recyclarr sync, backup/restore archives, git fetches, and Prowlarr
indexer sync now use wait_while/wait_inline. Compose up/pull keep
their own progress instead of a fake overlay.
This commit is contained in:
soulis-1256 2026-08-16 14:40:25 +03:00
parent 1149dfaae4
commit bdd5f263b5
No known key found for this signature in database
7 changed files with 59 additions and 20 deletions

View file

@ -289,7 +289,6 @@ backup_app() {
"${DC[@]}" stop > /dev/null 2>&1 || log_error "Failed to stop services"
echo -e "\nBacking up ${APP_DISPLAY_NAME} to $destination..."
echo "This may take a while depending on the size of your installation."
# Copy current CLI script and lib modules, create backup (skip copy if running from install dir)
local running_cli; running_cli="$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || echo "${_cli_self}/cli.sh")"
@ -304,11 +303,12 @@ backup_app() {
fi
# Run tar inside an alpine container bound to the INSTALL_DIR and the destination (excluding temp/cache and sockets)
run_docker run --rm \
if ! wait_while "Creating backup archive" run_docker run --rm \
-v "$INSTALL_DIR:/source" \
-v "$(dirname "$backup_file"):/backup" \
alpine tar --exclude='./transcoding-temp' --exclude='./config/jellyfin/cache' --exclude='ipc-socket' -czf "/backup/$(basename "$backup_file")" -C /source . ||
alpine tar --exclude='./transcoding-temp' --exclude='./config/jellyfin/cache' --exclude='ipc-socket' -czf "/backup/$(basename "$backup_file")" -C /source .; then
log_error "Failed to create backup archive"
fi
echo -e "\nStarting ${APP_DISPLAY_NAME} services..."
"${DC[@]}" start > /dev/null 2>&1 || log_warning "Failed to restart services"
@ -842,22 +842,22 @@ restore_app() {
local backup_dir="${INSTALL_DIR}_backup_${timestamp}"
echo "Creating safety snapshot of current state at ${backup_dir}..."
# Use alpine container to copy the snapshot safely
run_docker run --rm \
wait_while "Creating safety snapshot" run_docker run --rm \
-v "$(dirname "$INSTALL_DIR"):/target_parent" \
alpine cp -r "/target_parent/$(basename "$INSTALL_DIR")" "/target_parent/$(basename "$backup_dir")" || log_warning "Failed to create safety snapshot"
# Remove files using alpine so we don't hit permission denied issues
run_docker run --rm \
alpine cp -r "/target_parent/$(basename "$INSTALL_DIR")" "/target_parent/$(basename "$backup_dir")" \
|| log_warning "Failed to create safety snapshot"
wait_while "Clearing install directory" run_docker run --rm \
-v "$INSTALL_DIR:/target" \
alpine sh -c 'find /target -mindepth 1 -maxdepth 1 ! -name "media" ! -name "downloads" -exec rm -rf {} +'
# Extract using alpine
local abs_backup_file; abs_backup_file=$(realpath "$backup_file")
run_docker run --rm \
if ! wait_while "Restoring from backup" run_docker run --rm \
-v "$INSTALL_DIR:/target" \
-v "$(dirname "$abs_backup_file"):/backup" \
alpine tar -xzf "/backup/$(basename "$abs_backup_file")" -C /target || log_error "Failed to extract backup"
alpine tar -xzf "/backup/$(basename "$abs_backup_file")" -C /target; then
log_error "Failed to extract backup"
fi
echo "Starting ${APP_DISPLAY_NAME} services..."
"${DC[@]}" up -d --remove-orphans || log_warning "Failed to start services"
@ -921,7 +921,7 @@ update_cli() {
local tmp_dir
tmp_dir=$(mktemp -d)
if ! git clone --depth=1 "${APP_REPO_URL}" "$tmp_dir/assemblrr" 2>/dev/null; then
if ! wait_while "Updating CLI from git" git clone --depth=1 "${APP_REPO_URL}" "$tmp_dir/assemblrr"; then
log_error "Failed to clone ${APP_REPO_URL}. Check your internet connection and repository access."
fi

View file

@ -715,7 +715,7 @@ install_cli
set_permissions
log_success "Install files ready — starting services..."
log_info "Starting ${APP_DISPLAY_NAME} services (this may take a while)..."
log_info "Starting ${APP_DISPLAY_NAME} services..."
# Ensure mounts still exist/owned immediately before the full stack comes up
prepare_install_dirs "$install_directory" "$puid" "$pgid"
build_compose_args "$install_directory" "${setup_vpn,,}"

View file

@ -724,11 +724,14 @@ wait_for_arr_indexer_sync() {
local rc
rc=$(api_get "7878" "/api/v3/indexer" "$radarr_key" | jq 'length' 2>/dev/null || echo 0)
if [ "${rc:-0}" -gt 0 ]; then
[ "$waited" -gt 0 ] && echo >&2
return 0
fi
wait_inline "Waiting for Radarr indexers from Prowlarr" "$waited"
sleep 2
waited=$((waited + 2))
done
echo >&2
return 1
}

View file

@ -37,6 +37,37 @@ wait_inline() {
printf '\r%s (%ss) ' "$label" "$secs" >&2
}
# Run a command while updating wait_inline every second.
# Captures stdout+stderr in WAIT_WHILE_OUTPUT. Returns the command's exit status.
wait_while() {
local label="$1"
shift
local out ticker rc=0 had_errexit=0
[ $# -gt 0 ] || return 1
out=$(mktemp)
[[ $- == *e* ]] && had_errexit=1
(
secs=0
trap 'exit 0' TERM INT
while true; do
wait_inline "$label" "$secs"
sleep 1
secs=$((secs + 1))
done
) &
ticker=$!
set +e
"$@" >"$out" 2>&1
rc=$?
[ "$had_errexit" = 1 ] && set -e
kill "$ticker" 2>/dev/null || true
wait "$ticker" 2>/dev/null || true
echo >&2
WAIT_WHILE_OUTPUT=$(cat "$out" 2>/dev/null || true)
rm -f "$out"
return "$rc"
}
# --- Safe source ---
# Safely source a config file — validates that it contains only KEY=VALUE
# assignments (no command substitution, pipes, etc.) before sourcing

View file

@ -235,11 +235,11 @@ configure_recyclarr() {
pack_count=$(find "$recyclarr_config_dir/includes" -maxdepth 1 -name '*.yml' | wc -l | tr -d ' ')
log_step "Recyclarr: generated config (${pack_count} include packs; Seerr default: ${default_label})"
_cfg_log_info "Recyclarr: syncing TRaSH Guides to Radarr and Sonarr... this might take a moment"
local sync_out
if sync_out=$(docker exec recyclarr recyclarr sync 2>&1); then
if wait_while "Syncing Recyclarr (TRaSH Guides)" docker exec recyclarr recyclarr sync; then
log_step "Recyclarr: synced Custom Formats and Quality Profiles successfully"
else
sync_out="${WAIT_WHILE_OUTPUT:-}"
log_step_fail "Recyclarr: failed to sync to Radarr/Sonarr"
echo "$sync_out" | grep -E '•|Error|error|Invalid|YAML' | head -5 | while IFS= read -r line; do
_cfg_log_info " $line"

View file

@ -63,11 +63,10 @@ resolve_upgrade_source() {
local repo="${APP_REPO_URL:-https://github.com/soulis-1256/assemblrr}"
tmp=$(mktemp -d)
UPGRADE_SOURCE_TEMP="$tmp"
log_info "Fetching ${repo} @ ${ref}..."
if ! git clone --depth=1 --branch "$ref" "$repo" "$tmp/assemblrr" 2>/dev/null; then
if ! wait_while "Fetching assemblrr @ ${ref}" git clone --depth=1 --branch "$ref" "$repo" "$tmp/assemblrr"; then
# branch might be a commit or default branch name failed; try without --branch then checkout
rm -rf "$tmp/assemblrr"
if ! git clone --depth=1 "$repo" "$tmp/assemblrr" 2>/dev/null; then
if ! wait_while "Fetching assemblrr (default branch)" git clone --depth=1 "$repo" "$tmp/assemblrr"; then
[ -n "$UPGRADE_SOURCE_TEMP" ] && rm -rf "$UPGRADE_SOURCE_TEMP"
log_error "Failed to clone ${repo}. Check network and APP_REPO_URL."
fi

View file

@ -16,6 +16,12 @@ trap 'rm -rf "$tmp"' EXIT
test_suite "wait_inline"
assert_contains "$(wait_inline "Waiting for Radarr API" 12 2>&1)" \
"Waiting for Radarr API (12s)" "shows elapsed seconds"
WAIT_WHILE_OUTPUT=""
wait_while "unit-test" bash -c 'printf hi' >/dev/null 2>&1
assert_eq "hi" "$WAIT_WHILE_OUTPUT" "captures command output"
rc=0
wait_while "unit-fail" bash -c 'exit 3' >/dev/null 2>&1 || rc=$?
assert_eq "3" "$rc" "propagates exit status"
# --- expand_path ---
test_suite "expand_path"