#!/usr/bin/env sh set -eu WARN_PERCENT="${WARN_PERCENT:-85}" CLEANUP_PERCENT="${CLEANUP_PERCENT:-85}" CRITICAL_PERCENT="${CRITICAL_PERCENT:-95}" MIN_TMP_AGE_MINUTES="${MIN_TMP_AGE_MINUTES:-360}" MOUNT_PATH="${MOUNT_PATH:-/}" STATUS_DIR="${STATUS_DIR:-/tmp/rap-web-admin/html/downloads/ops}" LOG_DIR="${LOG_DIR:-/tmp/rap-ops}" WEBHOOK_URL="${WEBHOOK_URL:-}" mkdir -p "$STATUS_DIR" "$LOG_DIR" started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" log_file="$LOG_DIR/test-docker-disk-guard.log" status_file="$STATUS_DIR/test-docker-disk-guard-status.json" log() { printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >>"$log_file" } json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' } disk_used_percent() { df -P "$MOUNT_PATH" | awk 'NR==2 { gsub("%", "", $5); print $5 }' } disk_avail_human() { df -hP "$MOUNT_PATH" | awk 'NR==2 { print $4 }' } disk_used_human() { df -hP "$MOUNT_PATH" | awk 'NR==2 { print $3 }' } disk_size_human() { df -hP "$MOUNT_PATH" | awk 'NR==2 { print $2 }' } docker_df_summary() { if command -v docker >/dev/null 2>&1; then docker system df 2>&1 | tr '\n' '; ' | sed 's/"/\\"/g' else printf 'docker cli not found' fi } cleanup_safe() { log "cleanup started: docker build cache and old RAP tmp artifacts" if command -v docker >/dev/null 2>&1; then docker builder prune -af >>"$log_file" 2>&1 || true docker image prune -f >>"$log_file" 2>&1 || true fi find /tmp -maxdepth 1 \( \ -name 'rap-android-build-*' -o \ -name 'rap-agent-build*' -o \ -name 'rap-backend-build*' -o \ -name 'rap-c*-build*' -o \ -name 'rap-node-agent-*' -o \ -name 'rap-*vpnfarm*' -o \ -name 'rap-build-*' \ \) -mmin +"$MIN_TMP_AGE_MINUTES" -exec rm -rf {} + >>"$log_file" 2>&1 || true sync || true log "cleanup finished" } expansion_hint() { root_source="$(df -P "$MOUNT_PATH" | awk 'NR==2 { print $1 }')" vg_free="" root_lv_bytes="" parent_part_bytes="" if command -v vgs >/dev/null 2>&1; then vg_free="$(vgs --noheadings --units g -o vg_free 2>/dev/null | awk '{print $1}' | tr '\n' ' ' | sed 's/^ *//;s/ *$//')" fi if command -v lsblk >/dev/null 2>&1; then root_lv_bytes="$(lsblk -b -n -o TYPE,SIZE,MOUNTPOINTS 2>/dev/null | awk '$1 == "lvm" && $3 == "/" { print $2; exit }')" parent_part_bytes="$(lsblk -b -n -o TYPE,SIZE 2>/dev/null | awk '$1 == "part" { last=$2 } $1 == "lvm" { print last; exit }')" fi if printf '%s' "$root_source" | grep -q '^/dev/mapper/'; then if [ -n "$root_lv_bytes" ] && [ -n "$parent_part_bytes" ] && [ "$parent_part_bytes" -gt "$((root_lv_bytes + 1073741824))" ]; then printf 'LVM root detected on %s. Backing partition is larger than root LV, so expansion is likely available. Run with sudo: sudo lvextend -r -l +100%%FREE %s' "$root_source" "$root_source" return fi if [ -n "$vg_free" ] && ! printf '%s' "$vg_free" | grep -Eq '(^| )0(\.00)?g( |$)'; then printf 'LVM root detected on %s. If approved, extend inside existing VG: sudo lvextend -r -l +100%%FREE %s' "$root_source" "$root_source" else printf 'LVM root detected on %s. No obvious free VG space. Expand VM disk, then run pvresize on the PV and lvextend -r for root LV.' "$root_source" fi else printf 'Root filesystem is %s. Expand underlying disk/volume, then grow filesystem according to host partition layout.' "$root_source" fi } notify() { level="$1" message="$2" if [ -n "$WEBHOOK_URL" ] && command -v curl >/dev/null 2>&1; then payload="$(printf '{"level":"%s","message":"%s","host":"%s","observed_at":"%s"}' \ "$(json_escape "$level")" \ "$(json_escape "$message")" \ "$(json_escape "$(hostname)")" \ "$(date -u +%Y-%m-%dT%H:%M:%SZ)")" curl -fsS -m 5 -H 'Content-Type: application/json' -d "$payload" "$WEBHOOK_URL" >>"$log_file" 2>&1 || true fi } before_percent="$(disk_used_percent)" action="none" level="ok" if [ "$before_percent" -ge "$CLEANUP_PERCENT" ]; then action="cleanup_safe" cleanup_safe fi after_percent="$(disk_used_percent)" if [ "$after_percent" -ge "$CRITICAL_PERCENT" ]; then level="critical" elif [ "$after_percent" -ge "$WARN_PERCENT" ]; then level="warning" fi hint="$(expansion_hint)" summary="$(docker_df_summary)" finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" cat >"$status_file.tmp" <