diff --git a/bin/cli.sh b/bin/cli.sh index d9f0fd6..b38a5a4 100644 --- a/bin/cli.sh +++ b/bin/cli.sh @@ -244,7 +244,7 @@ wait_for_services() { fi if [ "$total" -gt 0 ] && [ "$ready" -eq "$total" ]; then - echo >&2 + printf '\r\033[K' >&2 log_success "All $total services are ready." return 0 fi @@ -258,7 +258,7 @@ wait_for_services() { wait_time=$((wait_time + 1)) done - echo >&2 + printf '\r\033[K' >&2 log_error "Not all services became ready within ${timeout}s ($ready/$total ready${waiting[*]:+: ${waiting[*]}})" } @@ -542,10 +542,12 @@ start_app() { fi if [ "$#" -eq 0 ]; then - "${DC[@]}" up -d --remove-orphans || log_error "Failed to start services" + compose_up_stack "${MEDIA_SERVICE:-jellyfin}" || log_error "Failed to start services" wait_for_services + echo + check_status || true else - "${DC[@]}" up -d --remove-orphans "$@" || log_error "Failed to start services" + compose_up_stack "${MEDIA_SERVICE:-jellyfin}" "$@" || log_error "Failed to start services" fi } @@ -554,12 +556,14 @@ restart_app() { prepare_install_dirs "$INSTALL_DIR" "$_HOST_UID" "$_HOST_GID" if [ "$#" -eq 0 ]; then - "${DC[@]}" stop || log_warning "Some services failed to stop" - "${DC[@]}" up -d --remove-orphans || log_error "Failed to start services" + wait_while "Stopping services" "${DC[@]}" stop || log_warning "Some services failed to stop" + compose_up_stack "${MEDIA_SERVICE:-jellyfin}" || log_error "Failed to start services" wait_for_services + echo + check_status || true else "${DC[@]}" stop "$@" || log_warning "Some services failed to stop" - "${DC[@]}" up -d --remove-orphans "$@" || log_error "Failed to start services" + compose_up_stack "${MEDIA_SERVICE:-jellyfin}" "$@" || log_error "Failed to start services" fi } diff --git a/compose/base.yaml b/compose/base.yaml index 82f8c58..b689aeb 100644 --- a/compose/base.yaml +++ b/compose/base.yaml @@ -16,11 +16,12 @@ services: ports: - 8096:8096 healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8096/health"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8096/health"] + interval: 10s + timeout: 5s retries: 3 - start_period: 60s + start_period: 30s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: @@ -43,11 +44,12 @@ services: ports: - 8096:8096 healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8096/emby/System/Ping"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8096/emby/System/Ping"] + interval: 10s + timeout: 5s retries: 3 - start_period: 60s + start_period: 30s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: @@ -70,11 +72,12 @@ services: - ${INSTALL_DIRECTORY}/config/plex:/config restart: unless-stopped healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:32400/identity"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:32400/identity"] + interval: 10s + timeout: 5s retries: 3 - start_period: 90s + start_period: 30s + start_interval: 2s # qBittorrent — torrent client (ports added by overlay) qbittorrent: @@ -91,11 +94,12 @@ services: - ${INSTALL_DIRECTORY}/config/qbittorrent:/config restart: unless-stopped healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8081"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8081"] + interval: 5s + timeout: 3s retries: 3 - start_period: 30s + start_period: 20s + start_interval: 2s # Sonarr — TV show management and automation sonarr: @@ -114,11 +118,12 @@ services: ports: - 8989:8989 healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8989/ping"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8989/ping"] + interval: 10s + timeout: 5s retries: 3 - start_period: 30s + start_period: 60s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: @@ -141,11 +146,12 @@ services: ports: - 7878:7878 healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:7878/ping"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:7878/ping"] + interval: 10s + timeout: 5s retries: 3 - start_period: 30s + start_period: 60s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: @@ -165,11 +171,12 @@ services: ports: - 9696:9696 healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:9696/ping"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9696/ping"] + interval: 10s + timeout: 5s retries: 3 - start_period: 30s + start_period: 60s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: @@ -191,11 +198,12 @@ services: expose: - "5055" healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5055/api/v1/settings/public"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5055/api/v1/settings/public"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 60s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: @@ -236,10 +244,11 @@ services: "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:5055/_assemblrr/ready', timeout=5)", ] - interval: 30s - timeout: 10s + interval: 10s + timeout: 5s retries: 3 - start_period: 20s + start_period: 30s + start_interval: 2s command: ["python3", "/scripts/seerr-gateway.py"] restart: unless-stopped networks: @@ -307,11 +316,12 @@ services: ports: - 6767:6767 healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:6767/api/system/ping"] - interval: 30s - timeout: 10s + test: ["CMD", "curl", "-sf", "http://127.0.0.1:6767/api/system/ping"] + interval: 10s + timeout: 5s retries: 3 - start_period: 60s + start_period: 30s + start_interval: 2s restart: unless-stopped networks: assemblrr_network: diff --git a/compose/vpn.yaml b/compose/vpn.yaml index 58dcf54..8239e75 100644 --- a/compose/vpn.yaml +++ b/compose/vpn.yaml @@ -36,10 +36,11 @@ services: "CMD-SHELL", "wget -qO /dev/null http://127.0.0.1:9999/v1/healthcheck || exit 1", ] - interval: 15s + interval: 10s timeout: 5s retries: 3 - start_period: 15s + start_period: 10s + start_interval: 2s volumes: - ${INSTALL_DIRECTORY}/config/gluetun:/gluetun restart: unless-stopped @@ -60,6 +61,7 @@ services: timeout: 3s retries: 2 start_period: 10s + start_interval: 2s restart: unless-stopped deunhealth: diff --git a/lib/core.sh b/lib/core.sh index dfb2b41..1325078 100644 --- a/lib/core.sh +++ b/lib/core.sh @@ -34,7 +34,7 @@ log_debug() { _log_to_file "DEBUG: $1"; return 0; } wait_inline() { local label="$1" local secs="${2:-0}" - printf '\r%s (%ss) ' "$label" "$secs" >&2 + printf '\r\033[K%s (%ss)' "$label" "$secs" >&2 } # Run a command while updating wait_inline every second. @@ -62,7 +62,7 @@ wait_while() { [ "$had_errexit" = 1 ] && set -e kill "$ticker" 2>/dev/null || true wait "$ticker" 2>/dev/null || true - echo >&2 + printf '\r\033[K' >&2 WAIT_WHILE_OUTPUT=$(cat "$out" 2>/dev/null || true) rm -f "$out" return "$rc" diff --git a/lib/vpn.sh b/lib/vpn.sh index 7dfab69..5297b0a 100644 --- a/lib/vpn.sh +++ b/lib/vpn.sh @@ -16,7 +16,7 @@ test_vpn_connection_shared() { log_info "Testing VPN connection by starting Gluetun first..." # Force recreate so updated secrets/.env from a credential retry always apply. - if ! "${docker_cmd[@]}" up -d --force-recreate gluetun 2>/dev/null; then + if ! "${docker_cmd[@]}" up -d --force-recreate gluetun >/dev/null 2>&1; then log_warning "Failed to start Gluetun container. VPN configuration may be incorrect." log_info "Check secrets/ (credentials) and .env (VPN_SERVICE, VPN_TYPE, WIREGUARD_ADDRESSES)." return 1 @@ -29,16 +29,16 @@ test_vpn_connection_shared() { health=$(docker inspect --format='{{.State.Health.Status}}' gluetun 2>/dev/null || echo "unknown") if [ "$health" = "healthy" ]; then - echo >&2 + printf '\r\033[K' >&2 log_success "VPN connection established successfully!" return 0 elif [ "$health" = "unhealthy" ]; then - echo >&2 + printf '\r\033[K' >&2 log_warning "VPN connection failed. Gluetun logs:" "${docker_cmd[@]}" logs gluetun --tail=20 2>/dev/null echo log_info "Fix VPN credentials in secrets/ (and WIREGUARD_ADDRESSES in .env if using WireGuard), then try again." - "${docker_cmd[@]}" stop gluetun 2>/dev/null + "${docker_cmd[@]}" stop gluetun >/dev/null 2>&1 || true return 1 fi @@ -47,12 +47,12 @@ test_vpn_connection_shared() { wait_time=$((wait_time + 3)) done - echo >&2 + printf '\r\033[K' >&2 log_warning "VPN connection timed out after ${max_wait}s. Gluetun logs:" "${docker_cmd[@]}" logs gluetun --tail=20 2>/dev/null echo log_info "The VPN may need more time, or credentials may be incorrect (see secrets/ and .env)." - "${docker_cmd[@]}" stop gluetun 2>/dev/null + "${docker_cmd[@]}" stop gluetun >/dev/null 2>&1 || true return 1 } diff --git a/migrations/003_standardize_quality_profiles.sh b/migrations/003_standardize_quality_profiles.sh index d58e00f..f919d90 100644 --- a/migrations/003_standardize_quality_profiles.sh +++ b/migrations/003_standardize_quality_profiles.sh @@ -4,7 +4,7 @@ migration_id="003_standardize_quality_profiles" migration_description="Remove superseded Recyclarr include packs from prior versions" -readonly LEGACY_RECYCLARR_INCLUDES=( +LEGACY_RECYCLARR_INCLUDES=( "radarr-hd.yml" "radarr-uhd.yml" "radarr-web-1080.yml" diff --git a/tests/unit/test_compose.sh b/tests/unit/test_compose.sh index 9831d3a..0b1d59c 100755 --- a/tests/unit/test_compose.sh +++ b/tests/unit/test_compose.sh @@ -74,7 +74,12 @@ assert_contains "$up" "Starting services" "friendly wait label" assert_contains "$up" "tail -n 40" "prints a log tail on failure" assert_true "setup uses compose_up_stack" "grep -q 'compose_up_stack' \"$REPO_ROOT/bin/setup.sh\"" assert_true "upgrade uses compose_up_stack" "grep -q 'compose_up_stack' \"$REPO_ROOT/lib/upgrade.sh\"" +assert_true "cli uses compose_up_stack" "grep -q 'compose_up_stack' \"$REPO_ROOT/bin/cli.sh\"" assert_not_contains "$(sed -n '/compose_up_stack/,/exit 1/p' "$REPO_ROOT/bin/setup.sh")" \ "run_docker compose" "setup no longer streams compose up" +test_suite "healthcheck fast-start intervals" +assert_contains "$(grep start_interval "$REPO_ROOT/compose/base.yaml")" "start_interval: 2s" "base.yaml has fast healthcheck start_interval" +assert_contains "$(grep start_interval "$REPO_ROOT/compose/vpn.yaml")" "start_interval: 2s" "vpn.yaml has fast healthcheck start_interval" + test_summary