Untitled

 avatar
unknown
plain_text
a month ago
23 kB
9
No Index
in a year

    #!/bin/bash
    set -euo pipefail
    
    ###############################################################################
    #                          startdocker-all.sh
    # Maintainer: user
    # Date: 2025-09-16
    # Version: 1.18
    ###############################################################################
    
    # --------------------------- config ------------------------------------------
    
    # Show timestamps in logs (true or false)
    SHOW_TIMESTAMPS=false
    
    # Write output to a logfile as well (true or false)
    LOG_TO_FILE=false
    LOGFILE="/var/log/startdocker-all.log"
    
    # Journal retention window
    JOURNAL_RETENTION="2d"
    
    # Max retries for docker operations per directory
    RETRIES=3
    RETRY_DELAY=5
    
    # Minimum free disk space percent at Docker root
    MIN_FREE_PCT=5
    
    # -----------------------------------------------------------------------------
    
    # Colors
    setup_colors() {
      if [[ -n "${NO_COLOR:-}" || ! -t 1 ]]; then
        RED=""; GREEN=""; YELLOW=""; BLUE=""; CYAN=""; MAGENTA=""; BOLD=""; RESET=""
        return
      fi
      RED='\033[0;31m'
      GREEN='\033[0;32m'
      YELLOW='\033[1;33m'
      BLUE='\033[0;34m'
      CYAN='\033[0;36m'
      MAGENTA='\033[0;35m'
      BOLD='\033[1m'
      RESET='\033[0m'
    }
    setup_colors
    
    ts_prefix() {
      if [[ "${SHOW_TIMESTAMPS}" == "true" ]]; then
        printf "%s " "$(date '+%Y-%m-%d %H:%M:%S')"
      fi
    }
    
    log()      { echo -e "${GREEN}$(ts_prefix)$*${RESET}"; }
    log_warn() { echo -e "${YELLOW}$(ts_prefix)WARNING: $*${RESET}"; }
    log_err()  { echo -e "${RED}$(ts_prefix)ERROR: $*${RESET}" >&2; }
    line()     { echo -e 
    "${CYAN}------------------------------------------------${RESET}"; }
    
    # Optional file logging
    if [[ "${LOG_TO_FILE}" == "true" ]]; then
      logdir="$(dirname "$LOGFILE")"
      mkdir -p "$logdir" 2>/dev/null || true
      if touch "$LOGFILE" 2>/dev/null && [[ -w "$LOGFILE" ]]; then
        exec > >(tee -a "$LOGFILE") 2>&1
      else
        log_warn "Cannot write to $LOGFILE, continuing without file logging."
      fi
    fi
    
    # Prevent overlap (per-user lock in /tmp)
    LOCKFILE="/tmp/startdocker-all.${UID}.lock"
    if exec 9>>"$LOCKFILE"; then
      if ! flock -n 9 2>/dev/null; then
        log_err "Another instance is running (lock: ${LOCKFILE}). Exiting."
        exit 1
      fi
    else
      log_warn "Cannot open lock file ${LOCKFILE}; continuing without lock."
    fi
    
    log "Starting docker maintenance on host $(hostname -f)"
    
    HOSTNAME_SHORT=$(hostname -s)
    
    declare -a directories
    case "$HOSTNAME_SHORT" in
      machine1)
        directories=(
          "/opt/audiobookshelf"
          "/opt/overseerr"
          "/opt/openwebui"
          "/opt/portainer_agent"
          "/opt/prowlarr"
          "/opt/sabnzbd"
          "/opt/tinyMediaManager"
          "/opt/Tautulli"
        )
        ;;
      machine2)
        directories=(
          "/opt/booklore"
          "/opt/calibre-web"
          "/opt/digikam"
          "/opt/docker-handbrake"
          "/opt/ebook2audiobook"
          "/opt/freshrss"
          "/opt/handbrake-web"
          "/opt/immich"
          "/opt/karakeep-app"
          "/opt/paperless-ngx"
          "/opt/photoprism"
          "/opt/portainer"
          "/opt/portainer_agent"
          "/opt/speedtest-tracker"
          "/opt/syncthing"
        )
        ;;
      monitor)
        directories=(
          "/opt/docker-handbrake"
          "/opt/dockmon"
          "/opt/handbrake-web"
          "/opt/immich-remote-learning"
          "/opt/portainer_agent"
        )
        ;;
      worker)
        directories=(
          "/opt/docker-handbrake"
          "/opt/handbrake-web"
          "/opt/immich-remote-learning"
        )
        ;;
      blue)
        directories=(
          "/opt/bazarr"
          "/opt/docker-handbrake"
          "/opt/emby"
          "/opt/FlareSolverr"
          "/opt/handbrake-web"
          "/opt/immich-remote-learning"
          "/opt/portainer_agent"
        )
        ;;
      *)
        log_err "Unknown host: $HOSTNAME_SHORT. Exiting."
        exit 1
        ;;
    esac
    
    # Preflight
    if ! command -v docker >/dev/null 2>&1; then
      log_err "Docker is not installed or not in PATH."
      exit 1
    fi
    if ! docker info >/dev/null 2>&1; then
      log_err "Docker daemon is not reachable. Is the service running?"
      exit 1
    fi
    if ! docker compose version >/dev/null 2>&1; then
      log_err "Docker Compose plugin not available."
      exit 1
    fi
    
    # Disk space info
    DOCKER_ROOT=$(docker info --format '{{ .DockerRootDir }}' 2>/dev/null || echo 
    "/var/lib/docker")
    if [[ -d "$DOCKER_ROOT" ]]; then
      USE_PCT=$(df -P "$DOCKER_ROOT" | awk 'NR==2 {gsub("%","",$5); print $5}')
      FREE_PCT=$((100 - USE_PCT))
      if (( FREE_PCT < MIN_FREE_PCT )); then
        log_warn "Free space at $DOCKER_ROOT: ${FREE_PCT}% (below 
    ${MIN_FREE_PCT}%)."
      else
        log "Free space at $DOCKER_ROOT: ${BOLD}${FREE_PCT}%${RESET}"
      fi
    fi
    
    # ---------- size helpers (for summary) ----------
    to_bytes() {
      local s="${1:-0B}"
      local num unit
      num="$(sed -E 's/^([0-9.]+).*/\1/' <<<"$s")"
      unit="$(sed -E 's/^[0-9.]+ *([A-Za-z]+).*/\1/' <<<"$s" | tr '[:upper:]' 
    '[:lower:]')"
      [[ -z "$num" ]] && num=0
      case "$unit" in
        b|"")   awk -v n="$num" 'BEGIN{printf "%.0f", n}' ;;
        kb)     awk -v n="$num" 'BEGIN{printf "%.0f", n*1000}' ;;
        kib)    awk -v n="$num" 'BEGIN{printf "%.0f", n*1024}' ;;
        mb)     awk -v n="$num" 'BEGIN{printf "%.0f", n*1000*1000}' ;;
        mib)    awk -v n="$num" 'BEGIN{printf "%.0f", n*1024*1024}' ;;
        gb)     awk -v n="$num" 'BEGIN{printf "%.0f", n*1000*1000*1000}' ;;
        gib)    awk -v n="$num" 'BEGIN{printf "%.0f", n*1024*1024*1024}' ;;
        tb)     awk -v n="$num" 'BEGIN{printf "%.0f", n*1000*1000*1000*1000}' ;;
        tib)    awk -v n="$num" 'BEGIN{printf "%.0f", n*1024*1024*1024*1024}' ;;
        *)      awk -v n="$num" 'BEGIN{printf "%.0f", n}' ;;
      esac
    }
    
    fmt_bytes() {
      local b="${1:-0}"
      local -a u=(B KiB MiB GiB TiB)
      local i=0
      while (( b >= 1024 && i < ${#u[@]}-1 )); do b=$((b/1024)); ((i++)); done
      printf "%d%s" "$b" "${u[$i]}"
    }
    
    extract_reclaimed_sizes() {
      sed -nE 's/.*Total (reclaimed space|space reclaimed): 
    *([0-9.]+[[:space:]]*[A-Za-z]+).*/\2/ip'
    }
    
    sum_reclaimed_from_text() {
      local out="$1" total=0
      while IFS= read -r sz; do
        (( total += $(to_bytes "$sz") ))
      done < <(extract_reclaimed_sizes <<<"$out")
      echo "$total"
    }
    
    sum_freed_from_journal_output() {
      local out="$1" total=0
      while IFS= read -r sz; do
        (( total += $(to_bytes "$sz") ))
      done < <(echo "$out" | sed -nE 
    's/.*freed[[:space:]]+([0-9.]+[[:space:]]*[A-Za-z]+).*/\1/p')
      echo "$total"
    }
    
    DOCKER_RECLAIMED_BYTES=0
    JOURNAL_FREED_BYTES=0
    
    # ---------- prune (capture outputs for summary) ----------
    log "Running docker prune tasks"
    
    prune_and_report() {
      local name="$1"; shift
      local out; out="$("$@" 2>&1 || true)"
      local bytes; bytes="$(sum_reclaimed_from_text "$out")"
      echo -e "${BLUE}${name}${RESET}: reclaimed $(fmt_bytes "${bytes}")"
      echo "$out" | awk 'NF>0 && !/Total (reclaimed space|space reclaimed):/{print}'
      echo
      DOCKER_RECLAIMED_BYTES=$(( DOCKER_RECLAIMED_BYTES + bytes ))
    }
    
    prune_and_report "Images"     docker image prune -f
    prune_and_report "Containers" docker container prune -f
    prune_and_report "Volumes"    docker volume prune -f
    #prune_and_report "Networks"   docker network prune -f
    #prune_and_report "Builder"    docker builder prune -f --filter 'until=24h'
    
    # Journal cleanup with summary capture
    JOURNAL_SUMMARY_MODE="skipped"
    JOURNAL_SUMMARY_NOTE="journalctl not found"
    
    clean_journal() {
      local retention="${JOURNAL_RETENTION:-2d}"
      if ! command -v journalctl >/dev/null 2>&1; then
        log "journalctl not found. Skipping journal vacuum."
        JOURNAL_SUMMARY_MODE="skipped"; JOURNAL_SUMMARY_NOTE="journalctl not found"
        return 0
      fi
    
      if [[ $EUID -eq 0 ]]; then
        log "Cleaning system journal logs, keeping ${BOLD}${retention}${RESET}"
        jout="$(journalctl --vacuum-time="${retention}" 2>&1 || true)"
        echo "$jout"
        JOURNAL_FREED_BYTES=$(sum_freed_from_journal_output "$jout")
        if [[ -n "$jout" ]]; then
          JOURNAL_SUMMARY_MODE="system"; JOURNAL_SUMMARY_NOTE="via root, retention 
    ${retention}"
        else
          JOURNAL_SUMMARY_MODE="failed"; JOURNAL_SUMMARY_NOTE="system vacuum error"
        fi
        return 0
      fi
    
      if command -v sudo >/dev/null 2>&1; then
        log "Cleaning system journal logs with sudo, keeping 
    ${BOLD}${retention}${RESET}"
        jout="$(sudo -n journalctl --vacuum-time="${retention}" 2>&1 || true)"
        if grep -q . <<<"$jout"; then
          echo "$jout"
          JOURNAL_FREED_BYTES=$(sum_freed_from_journal_output "$jout")
          JOURNAL_SUMMARY_MODE="system"; JOURNAL_SUMMARY_NOTE="via sudo, retention 
    ${retention}"
          return 0
        else
          log "Passwordless sudo not allowed for journalctl (or it failed). Falling 
    back to user journal."
        fi
      else
        log "sudo not available. Falling back to user journal."
      fi
    
      log "Cleaning user journal logs, keeping ${BOLD}${retention}${RESET}"
      jout="$(journalctl --user --vacuum-time="${retention}" 2>&1 || true)"
      echo "$jout"
      JOURNAL_FREED_BYTES=$(sum_freed_from_journal_output "$jout")
      if grep -q . <<<"$jout"; then
        JOURNAL_SUMMARY_MODE="user"; JOURNAL_SUMMARY_NOTE="retention ${retention}"
      else
        JOURNAL_SUMMARY_MODE="failed"; JOURNAL_SUMMARY_NOTE="user vacuum error"
      fi
    }
    clean_journal
    
    # ---------- helpers ----------
    
    declare -A BEFORE_TAG_BY_REPO   # key: "dir|repo" -> tag
    declare -A AFTER_TAG_BY_REPO    # key: "dir|repo" -> tag
    declare -A BEFORE_ID_BY_REPO    # key: "dir|repo" -> image ID
    declare -A AFTER_ID_BY_REPO     # key: "dir|repo" -> image ID
    declare -A BEFORE_VER_BY_REPO   # key: "dir|repo" -> version label
    declare -A AFTER_VER_BY_REPO    # key: "dir|repo" -> version label
    declare -A DIR_STATUS           # processed|skipped|no-compose|failed
    declare -A SERVICE_URLS         # key: "dir|service" -> "http://host:port[, 
    http://host:port2...]"
    
    k() { echo "$1|$2"; }
    
    split_repo_tag() {
      local full="$1"
      local no_digest="${full%%@*}"
      local repo="$no_digest"
      local tag="latest"
      if [[ "$no_digest" == *:* ]]; then
        tag="${no_digest##*:}"
        repo="${no_digest%:*}"
      fi
      echo "$repo|$tag"
    }
    
    list_compose_images() {
      if docker compose config --images >/dev/null 2>&1; then
        docker compose config --images | awk 'NF>0'
      else
        docker compose config 2>/dev/null | awk 
    '/^[[:space:]]*image:[[:space:]]*/{print $2}'
      fi
    }
    
    has_compose_file() {
      [[ -f "docker-compose.yml" || -f "docker-compose.yaml" || -f "compose.yml" || 
    -f "compose.yaml" ]]
    }
    
    compose_retry() {
      local tries=0
      local desc=$1
      shift
      until "$@" ; do
        tries=$((tries + 1))
        if (( tries >= RETRIES )); then
          log_err "Failed: ${desc} after ${RETRIES} attempts"
          return 1
        fi
        log_warn "Retry ${tries}/${RETRIES}: ${desc}. Sleeping ${RETRY_DELAY}s."
        sleep "${RETRY_DELAY}"
      done
      return 0
    }
    
    # Image ID and label helpers
    image_id_of() {
      local img="$1"
      docker image inspect "$img" --format '{{.Id}}' 2>/dev/null || echo ""
    }
    image_label_version_of() {
      local img="$1"
      docker image inspect "$img" --format '{{index .Config.Labels 
    "org.opencontainers.image.version"}}' 2>/dev/null || echo ""
    }
    
    check_stack_health() {
      local dir="$1"
      local stack
      stack="$(basename "$dir")"
    
      # Let containers settle for healthchecks
      sleep 5
    
      # List services for this compose stack
      mapfile -t services < <(docker compose ps --services 2>/dev/null || true)
    
      if ((${#services[@]} == 0)); then
        log_warn "${stack}: no services reported for health check"
        return
      fi
    
      local unhealthy=0
      local svc
      local host
      host="$HOSTNAME_SHORT"
    
      for svc in "${services[@]}"; do
        # Container IDs for this service
        mapfile -t containers < <(docker compose ps -q "$svc" 2>/dev/null || true)
    
        if ((${#containers[@]} == 0)); then
          log_warn "${stack}: service ${svc} has no running containers"
          unhealthy=1
          continue
        fi
    
        local svc_unhealthy=0
        local cid
    
        # Deduplicate ports per service
        local -A svc_ports=()
    
        for cid in "${containers[@]}"; do
          # Inspect status and healthcheck
          local inspect status health
          inspect="$(docker inspect --format '{{.State.Status}} {{if 
    .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' "$cid" 
    2>/dev/null || echo "unknown unknown")"
          read -r status health <<<"$inspect"
    
          # Get published host ports (no jq)
          local hostports
          hostports="$(docker inspect --format '{{range $p, $bindings := 
    .NetworkSettings.Ports}}{{if $bindings}}{{range $b := $bindings}}{{$b.HostPort}} 
    {{end}}{{end}}{{end}}' "$cid" 2>/dev/null || true)"
    
          if [[ -n "$hostports" ]]; then
            for port in $hostports; do
              # Only record a given port once per service
              if [[ -z "${svc_ports[$port]+x}" ]]; then
                svc_ports["$port"]=1
                local url="http://${host}:${port}"
                log "${stack}: service ${svc} -> ${url}"
              fi
            done
          else
            log "${stack}: service ${svc} has no published ports"
          fi
    
          # Health logic
          if [[ "$status" != "running" ]]; then
            log_warn "${stack}: service ${svc} container ${cid:0:12} not running 
    (state '${status}')"
            svc_unhealthy=1
            continue
          fi
    
          # Treat "starting" as informational, not a failure
          if [[ "$health" == "starting" ]]; then
            log "${stack}: service ${svc} container ${cid:0:12} health status 
    'starting' (still initializing)"
          elif [[ "$health" != "healthy" && "$health" != "no-healthcheck" ]]; then
            log_warn "${stack}: service ${svc} container ${cid:0:12} health status 
    '${health}'"
            svc_unhealthy=1
          fi
        done
    
        # Save URLs for summary, if any
        local urls=""
        local port
        for port in "${!svc_ports[@]}"; do
          local url="http://${host}:${port}"
          if [[ -z "$urls" ]]; then
            urls="${url}"
          else
            urls="${urls}, ${url}"
          fi
        done
    
        if [[ -n "$urls" ]]; then
          SERVICE_URLS["$dir|$svc"]="$urls"
        fi
    
        if (( svc_unhealthy != 0 )); then
          unhealthy=1
        fi
      done
    
      if (( unhealthy == 0 )); then
        log "${stack}: all services running and healthy"
      fi
    }
    
    # ---------- main ----------
    
    for dir in "${directories[@]}"; do
      if [[ ! -d "$dir" ]]; then
        log_err "Directory missing: $dir"
        DIR_STATUS["$dir"]="skipped"
        line
        continue
      fi
    
      echo -e "${MAGENTA}${BOLD}Processing:${RESET} ${BOLD}${BLUE}$dir${RESET}"
      pushd "$dir" >/dev/null || { log_err "Cannot enter: $dir"; 
    DIR_STATUS["$dir"]="skipped"; line; continue; }
    
      if ! has_compose_file; then
        log_warn "No compose file. Skipping."
        DIR_STATUS["$dir"]="no-compose"
        popd >/dev/null
        line
        continue
      fi
    
      mapfile -t IMAGES < <(list_compose_images)
      for img in "${IMAGES[@]:-}"; do
        read -r repo tag <<<"$(split_repo_tag "$img" | tr '|' ' ')"
        BEFORE_TAG_BY_REPO["$(k "$dir" "$repo")"]="$tag"
        BEFORE_ID_BY_REPO["$(k "$dir" "$repo")"]="$(image_id_of "$img")"
        BEFORE_VER_BY_REPO["$(k "$dir" "$repo")"]="$(image_label_version_of "$img")"
      done
    
      set +e
      compose_retry "docker compose pull in $dir" docker compose pull
      rc_pull=$?
      compose_retry "docker compose stop in $dir" docker compose stop
      rc_stop=$?
      compose_retry "docker compose up in $dir" docker compose up -d 
    --remove-orphans
      rc_up=$?
      set -e
    
      if (( rc_pull != 0 || rc_stop != 0 || rc_up != 0 )); then
        log_err "Compose step failed in $dir"
        DIR_STATUS["$dir"]="failed"
      else
        DIR_STATUS["$dir"]="processed"
        check_stack_health "$dir"
      fi
    
      mapfile -t IMAGES_AFTER < <(list_compose_images)
      for img in "${IMAGES_AFTER[@]:-}"; do
        read -r repo tag <<<"$(split_repo_tag "$img" | tr '|' ' ')"
        AFTER_TAG_BY_REPO["$(k "$dir" "$repo")"]="$tag"
        AFTER_ID_BY_REPO["$(k "$dir" "$repo")"]="$(image_id_of "$img")"
        AFTER_VER_BY_REPO["$(k "$dir" "$repo")"]="$(image_label_version_of "$img")"
      done
    
      echo -e "${GREEN}Done:${RESET} ${BOLD}${BLUE}$dir${RESET}"
      popd >/dev/null
      line
    done
    
    # ---------- summary ----------
    set +e
    set +u
    
    echo
    echo -e "${BOLD}${CYAN}Run Summary${RESET}"
    
    processed=0; skipped=0; nocompose=0; failed=0; updated=0
    declare -a UPD_LINES
    declare -a NOCHANGE_STACKS
    declare -a SKIPLINES
    
    for dir in "${directories[@]}"; do
      status="skipped"
      if [[ -n "${DIR_STATUS[$dir]+x}" ]]; then
        status="${DIR_STATUS[$dir]}"
      fi
    
      case "$status" in
        processed) ((processed++)) ;;
        skipped)   ((skipped++)) ;;
        no-compose)((nocompose++)) ;;
        failed)    ((failed++)) ;;
      esac
    
      stack="$(basename "$dir")"
    
      if [[ "$status" != "processed" ]]; then
        case "$status" in
          skipped)    SKIPLINES+=("${BOLD}${BLUE}${stack}${RESET} ${YELLOW}- skipped 
    (directory missing)${RESET}") ;;
          no-compose) SKIPLINES+=("${BOLD}${BLUE}${stack}${RESET} ${YELLOW}- skipped 
    (no compose file)${RESET}") ;;
          failed)     SKIPLINES+=("${BOLD}${BLUE}${stack}${RESET} ${RED}- compose 
    failed${RESET}") ;;
        esac
        continue
      fi
    
      declare -A REPOS=()
      for keymap in "${!BEFORE_TAG_BY_REPO[@]}"; do
        d="${keymap%%|*}"; r="${keymap#*|}"
        [[ "$d" == "$dir" ]] && REPOS["$r"]=1
      done
      for keymap in "${!AFTER_TAG_BY_REPO[@]}"; do
        d="${keymap%%|*}"; r="${keymap#*|}"
        [[ "$d" == "$dir" ]] && REPOS["$r"]=1
      done
    
      changed_any=0
      for repo in "${!REPOS[@]}"; do
        key="${dir}|$repo"
        old_tag="${BEFORE_TAG_BY_REPO[$key]:-}"
        new_tag="${AFTER_TAG_BY_REPO[$key]:-}"
    
        svc="$(basename "$repo")"
        svc_fmt="${BOLD}${svc}${RESET}"
    
        if [[ -n "$old_tag" && -n "$new_tag" && "$old_tag" != "$new_tag" ]]; then
          ((updated++)); changed_any=1
          UPD_LINES+=("${GREEN}${BOLD}${stack}${RESET} - ${GREEN}${svc_fmt} 
    ${YELLOW}${old_tag}${RESET} ${GREEN}-> ${new_tag}${RESET}")
        else
          old_id="${BEFORE_ID_BY_REPO[$key]:-}"
          new_id="${AFTER_ID_BY_REPO[$key]:-}"
          if [[ -n "$old_id" && -n "$new_id" && "$old_id" != "$new_id" ]]; then
            ((updated++)); changed_any=1
            old_ver="${BEFORE_VER_BY_REPO[$key]:-}"
            new_ver="${AFTER_VER_BY_REPO[$key]:-}"
            if [[ -n "$old_ver" || -n "$new_ver" ]]; then
              if [[ -n "$old_ver" && -n "$new_ver" && "$old_ver" != "$new_ver" ]]; 
    then
                UPD_LINES+=("${GREEN}${BOLD}${stack}${RESET} - ${GREEN}${svc_fmt} 
    Updated image, tag unchanged${RESET} (${YELLOW}${old_ver}${RESET} ${GREEN}-> 
    ${new_ver}${RESET}, tag '${new_tag}')")
              else
                UPD_LINES+=("${GREEN}${BOLD}${stack}${RESET} - ${GREEN}${svc_fmt} 
    Updated image, tag unchanged${RESET} (version '${new_ver:-${old_ver}}', tag 
    '${new_tag}')")
              fi
            else
              UPD_LINES+=("${GREEN}${BOLD}${stack}${RESET} - ${GREEN}${svc_fmt} 
    Updated image, tag unchanged${RESET} (tag '${new_tag}')")
            fi
          fi
        fi
      done
    
      if (( changed_any == 0 )); then
        NOCHANGE_STACKS+=("${BOLD}${BLUE}${stack}${RESET}")
      fi
    done
    
    if ((${#NOCHANGE_STACKS[@]} > 0)); then
      echo -e "${BOLD}No container version changes${RESET}"
      for s in "${NOCHANGE_STACKS[@]}"; do echo -e "${s}"; done
      echo
    fi
    
    if ((${#UPD_LINES[@]} > 0)); then
      echo -e "${BOLD}Container Version Updates${RESET}"
      for l in "${UPD_LINES[@]}"; do echo -e "$l"; done
      echo
    fi
    
    if ((${#SKIPLINES[@]} > 0)); then
      echo -e "${BOLD}Skipped / Errors${RESET}"
      for x in "${SKIPLINES[@]}"; do echo -e "$x"; done
      echo
    fi
    
    if ((${#SERVICE_URLS[@]} > 0)); then
      echo -e "${BOLD}Service URLs${RESET}"
      for dir in "${directories[@]}"; do
        stack="$(basename "$dir")"
        for key in "${!SERVICE_URLS[@]}"; do
          d="${key%%|*}"
          svc="${key#*|}"
          if [[ "$d" == "$dir" ]]; then
            urls="${SERVICE_URLS[$key]}"
            echo -e "${BOLD}${BLUE}${stack}${RESET} ${BOLD}${svc}${RESET}: ${urls}"
          fi
        done
      done
      echo
    fi
    
    echo -e "Processed: ${BOLD}${processed}${RESET}   Skipped: 
    ${BOLD}${skipped}${RESET}   No compose: ${BOLD}${nocompose}${RESET}   Failed: 
    ${BOLD}${failed}${RESET}"
    echo -e "Containers updated: ${BOLD}${updated}${RESET}"
    echo -e "Docker prune reclaimed: ${BOLD}$(fmt_bytes 
    "${DOCKER_RECLAIMED_BYTES}")${RESET}"
    echo -e "Journal vacuum freed:   ${BOLD}$(fmt_bytes 
    "${JOURNAL_FREED_BYTES}")${RESET}"
    
    case "${JOURNAL_SUMMARY_MODE:-skipped}" in
      system) echo -e "Journal cleanup: ${BOLD}system${RESET} 
    (${JOURNAL_SUMMARY_NOTE:-})" ;;
      user)   echo -e "Journal cleanup: ${BOLD}user${RESET} 
    (${JOURNAL_SUMMARY_NOTE:-})" ;;
      skipped)echo -e "Journal cleanup: skipped (${JOURNAL_SUMMARY_NOTE:-})" ;;
      failed) echo -e "Journal cleanup: ${BOLD}failed${RESET} 
    (${JOURNAL_SUMMARY_NOTE:-})" ;;
      *)      echo -e "Journal cleanup: unknown" ;;
    esac
    
    echo
    log "All done."
    
    # Restore strict mode (no further code expected).
    set -u
    set -e
Editor is loading...
Leave a Comment