Implement Docker healthcheck functionality and improve progress message handling
All checks were successful
Publish Twitch Archive Container / publish (push) Successful in 1m30s

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
MaddoScientisto 2026-04-25 16:46:20 +02:00
commit 9d5c707646
5 changed files with 96 additions and 7 deletions

View file

@ -60,6 +60,33 @@ from modules.downloader import ContentDownloader
from modules.file_manager import FileManager
HEALTHCHECK_HEARTBEAT_PATH = os.getenv('TWITCH_ARCHIVE_HEARTBEAT_PATH', '/tmp/twitch-archive-heartbeat')
HEALTHCHECK_MAX_AGE_SECONDS = int(os.getenv('TWITCH_ARCHIVE_HEALTHCHECK_MAX_AGE', '180'))
def write_healthcheck_heartbeat() -> None:
"""Record a recent application heartbeat for Docker health checks."""
pathlib.Path(HEALTHCHECK_HEARTBEAT_PATH).touch()
def has_fresh_healthcheck_heartbeat(max_age_seconds: int = HEALTHCHECK_MAX_AGE_SECONDS) -> bool:
"""Return whether the application heartbeat file exists and is recent."""
try:
heartbeat_age = time.time() - os.path.getmtime(HEALTHCHECK_HEARTBEAT_PATH)
except OSError:
return False
return heartbeat_age <= max_age_seconds
def print_progress_line(message: str) -> None:
"""Use carriage returns only in an interactive terminal so Docker logs keep full lines."""
if sys.stdout.isatty():
print(message, end='\r', flush=True)
else:
print(message, flush=True)
class TwitchArchive:
"""
Main class for the Twitch Archive system.
@ -345,16 +372,19 @@ class TwitchArchive:
signal.signal(signal.SIGINT, self._signal_handler)
if hasattr(signal, 'SIGTERM'):
signal.signal(signal.SIGTERM, self._signal_handler)
write_healthcheck_heartbeat()
while not self.shutdown_requested:
try:
write_healthcheck_heartbeat()
# Check stream status using StreamMonitor
response = self.stream_monitor.check_stream_status()
is_live = response['data']['user']['stream']
# Stream is offline
if is_live is None:
print(f'{Fore.CYAN}{self.username} is offline. Checking again in {self.refresh}s...{Style.RESET_ALL}', end='\r')
print_progress_line(f'{Fore.CYAN}{self.username} is offline. Checking again in {self.refresh}s...{Style.RESET_ALL}')
if self.shutdown_requested:
break
self._interruptible_sleep(self.refresh)
@ -522,7 +552,7 @@ class TwitchArchive:
# Wait before checking again
if not vod_found:
print(f'{Fore.CYAN}VOD not found yet, waiting...{Style.RESET_ALL}', end='\r')
print_progress_line(f'{Fore.CYAN}VOD not found yet, waiting...{Style.RESET_ALL}')
if not self._interruptible_sleep(min(10, self.vodTimeout - (time.time() - vod_wait_start))):
break
@ -941,6 +971,8 @@ class TwitchArchiveManager:
# Print configuration summary for each streamer
for username, archiver in self.archivers.items():
archiver._print_configuration_summary()
write_healthcheck_heartbeat()
print(f'\n{Fore.GREEN}🚀 Starting monitoring loop...{Style.RESET_ALL}\n')
@ -958,6 +990,7 @@ class TwitchArchiveManager:
while not self.shutdown_requested:
current_time = time.time()
write_healthcheck_heartbeat()
# Print periodic status every 60 seconds
if current_time - last_status_print >= 60:
@ -1037,7 +1070,7 @@ class TwitchArchiveManager:
else:
# Not live
if self.verbose:
print(f'{Fore.CYAN}[{username}] Offline - checking again in {archiver.refresh}s{Style.RESET_ALL}', end='\r')
print_progress_line(f'{Fore.CYAN}[{username}] Offline - checking again in {archiver.refresh}s{Style.RESET_ALL}')
except Exception as e:
print(f'{Fore.RED}[{username}] Error checking stream: {e}{Style.RESET_ALL}')
@ -1340,7 +1373,7 @@ class TwitchArchiveManager:
# Wait before checking again
if not vod_found:
print(f'{Fore.CYAN}VOD not found yet, waiting...{Style.RESET_ALL}', end='\r')
print_progress_line(f'{Fore.CYAN}VOD not found yet, waiting...{Style.RESET_ALL}')
time.sleep(min(10, archiver.vodTimeout - (time.time() - vod_wait_start)))
if not vod_found:
@ -1518,6 +1551,10 @@ def run_healthcheck(specific_streamer: Optional[str] = None) -> int:
if not checks_ok:
return 1
if not has_fresh_healthcheck_heartbeat():
print(f'{Fore.RED}✗ ERROR: Application heartbeat is missing or stale at {HEALTHCHECK_HEARTBEAT_PATH}{Style.RESET_ALL}')
return 1
print(f'{Fore.GREEN}✓ Healthcheck OK for {username}{Style.RESET_ALL}')
return 0