#!/bin/bash # ============================================================================= # plugin/sbin/podman-autostart.sh # # "Autostart einrichten" — starts containers listed in # /boot/config/plugins/podman/autostart, in file order, via the running # Podman API service. Run by `rc.podman start` after the service is up. # See docs/ARCHITECTURE.md section 12 (Autostart). # # Design constraints from the architecture doc, both implemented below: # - A single container failing to start must NOT abort the rest of the # chain (one broken container shouldn't take down everything else). # - After repeated consecutive failures, a container is automatically # paused out of the autostart chain ("Safe-Mode pro Container") so a # persistently crash-looping container doesn't waste boot time or # resources forever — with a GUI notification explaining why. # # File formats: # autostart one container name per line; '#' starts a comment; # blank lines ignored. Order = start order. # autostart-delay optional "=" lines — sleep # that many seconds AFTER starting that container # before moving to the next (for startup dependencies, # e.g. a database before the app that needs it). # # Failure tracking: a small per-container counter file under # $PODMAN_BOOT_DIR/autostart-failures/ holds the consecutive-failure # count. Reset to 0 on any successful start. # ============================================================================= set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=./podman-common.sh . "$SCRIPT_DIR/podman-common.sh" podman_load_cfg # After this many consecutive failed autostart attempts, a container is # skipped and flagged rather than retried again on the next boot, until a # human clears it (see clear_failure_flag below). MAX_CONSECUTIVE_FAILURES=3 FAILURES_DIR="$PODMAN_BOOT_DIR/autostart-failures" mkdir -p "$FAILURES_DIR" # ----------------------------------------------------------------------------- # get_delay # # Looks up an optional post-start delay for a container from # autostart-delay ("name=seconds" lines). Returns 0 if not configured. # ----------------------------------------------------------------------------- get_delay() { local name="$1" if [ -f "$PODMAN_AUTOSTART_DELAY_FILE" ]; then awk -F= -v n="$name" '$1 == n { print $2; found=1 } END { if (!found) print 0 }' \ "$PODMAN_AUTOSTART_DELAY_FILE" else echo 0 fi } failure_count() { local name="$1" local f="$FAILURES_DIR/$name" [ -f "$f" ] && cat "$f" || echo 0 } record_failure() { local name="$1" local count count=$(($(failure_count "$name") + 1)) echo "$count" > "$FAILURES_DIR/$name" echo "$count" } clear_failure_flag() { local name="$1" rm -f "$FAILURES_DIR/$name" } # ----------------------------------------------------------------------------- # start_one # # Starts a single container via `podman start`, which talks to the already # running API service over $PODMAN_SOCKET rather than spawning an unrelated # podman process tree — see docs/ARCHITECTURE.md section 11 (Container). # ----------------------------------------------------------------------------- start_one() { local name="$1" local prior_failures prior_failures=$(failure_count "$name") if [ "$prior_failures" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then podman_log_error "autostart: skipping '$name' — $prior_failures consecutive prior failures (Safe-Mode). Remove $FAILURES_DIR/$name to re-enable." return 1 fi podman_log "autostart: starting '$name'" if podman --url "unix://$PODMAN_SOCKET" start "$name" > /tmp/podman-autostart-"$name".log 2>&1; then clear_failure_flag "$name" podman_log "autostart: '$name' started successfully" return 0 fi local new_count new_count=$(record_failure "$name") podman_log_error "autostart: '$name' failed to start (attempt $new_count/$MAX_CONSECUTIVE_FAILURES) — see /tmp/podman-autostart-$name.log" if [ "$new_count" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then podman_notify "Podman container disabled from autostart" \ "'$name' failed to start $new_count times in a row and has been paused from autostart. Fix the underlying issue, then remove $FAILURES_DIR/$name to re-enable." \ "warning" fi return 1 } # ----------------------------------------------------------------------------- # main # ----------------------------------------------------------------------------- if [ ! -f "$PODMAN_AUTOSTART_FILE" ]; then podman_log "autostart: no autostart file at $PODMAN_AUTOSTART_FILE, nothing to do" exit 0 fi if [ ! -S "$PODMAN_SOCKET" ]; then podman_log_error "autostart: podman API socket ($PODMAN_SOCKET) not present — is the service running?" exit 1 fi started=0 skipped=0 failed=0 while IFS= read -r raw_line || [ -n "$raw_line" ]; do # Strip comments and surrounding whitespace; skip blank lines. line="${raw_line%%#*}" line="$(echo "$line" | xargs || true)" [ -z "$line" ] && continue if start_one "$line"; then started=$((started + 1)) else prior=$(failure_count "$line") if [ "$prior" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then skipped=$((skipped + 1)) else failed=$((failed + 1)) fi # A single container's failure does not abort the loop — see file # header. Continue to the next entry. continue fi delay=$(get_delay "$line") if [ "$delay" -gt 0 ] 2> /dev/null; then podman_log "autostart: waiting ${delay}s after '$line' (configured dependency delay)" sleep "$delay" fi done < "$PODMAN_AUTOSTART_FILE" podman_log "autostart: complete (started=$started failed=$failed skipped-safe-mode=$skipped)" # Exit non-zero only if EVERY entry failed outright (as opposed to a mix, # or entries already in Safe-Mode) — rc.podman treats that as worth # flagging loudly, whereas a partial failure is already individually # notified above. if [ "$started" -eq 0 ] && [ "$failed" -gt 0 ]; then exit 1 fi exit 0