#!/usr/bin/env bash # Загрузка логов в S3 — v2: берём готовые .gz от remnanode-ротатора, без нашей ротации. # Запускается ежедневно после 00:00 UTC (когда remnanode уже сделала свою ротацию). set -uo pipefail CONF=/etc/log-upload/secrets.env [ -f "$CONF" ] || { echo "no config" >&2; exit 1; } # shellcheck disable=SC1090 . "$CONF" LOG=/var/log/log-upload.log SPOOL=/var/spool/log-upload mkdir -p "$SPOOL" log() { echo "[$(date -uIs)] $*" >> "$LOG"; } NODE_NAME="${LOG_NODE_NAME:?LOG_NODE_NAME not set}" NODE_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}') DATE=$(date -u -d yesterday +%F) DATE_PATH=${DATE//-/\/} S3_PREFIX="${DATE_PATH}/${NODE_NAME}__${NODE_IP}" log "═══ START upload v2 for ${NODE_NAME}@${NODE_IP} → ${S3_PREFIX} ═══" # === собираем кандидаты: готовые .log.1.gz от remnanode === # Если .gz уже есть — берём как есть. Если только .log.1 — жмём gzip (быстро). # Если только активный .log — не берём! (растущий файл). declare -a FILES # формат: "путь:S3-имя:уже-сжато(0/1)" pick_xray() { local base=$1 target=$2 if [ -f "/var/log/remnanode/${base}.1.gz" ] && [ -s "/var/log/remnanode/${base}.1.gz" ]; then FILES+=("/var/log/remnanode/${base}.1.gz:${target}.gz:1") elif [ -f "/var/log/remnanode/${base}.1" ] && [ -s "/var/log/remnanode/${base}.1" ]; then FILES+=("/var/log/remnanode/${base}.1:${target}.gz:0") fi # если есть только активный .log — не берём (растущий) } pick_xray access.log xray-access.log pick_xray error.log xray-error.log # системные логи (наша зона) — logrotate базы ОС ротирует их сам pick_sys() { local base=$1 target=$2 if [ -f "/var/log/${base}.1.gz" ] && [ -s "/var/log/${base}.1.gz" ]; then FILES+=("/var/log/${base}.1.gz:${target}.gz:1") elif [ -f "/var/log/${base}.1" ] && [ -s "/var/log/${base}.1" ]; then FILES+=("/var/log/${base}.1:${target}.gz:0") elif [ -f "/var/log/${base}" ] && [ -s "/var/log/${base}" ]; then FILES+=("/var/log/${base}:${target}.gz:0") fi } pick_sys auth.log auth.log pick_sys fail2ban.log fail2ban.log if [ ${#FILES[@]} -eq 0 ]; then log " WARN: no log files found" fi log " found ${#FILES[@]} log files" # === сжатие (только тех что не .gz) === META_FILES="[" for ENTRY in "${FILES[@]}"; do SRC="${ENTRY%%:*}" rest="${ENTRY#*:}" S3_NAME="${rest%%:*}" PRE_GZ="${rest##*:}" DST="$SPOOL/${S3_NAME}" if [ "$PRE_GZ" = "1" ]; then # уже .gz — просто копируем (linked) в spool cp --reflink=auto "$SRC" "$DST" 2>/dev/null || cp "$SRC" "$DST" log " picked ${SRC} → ${S3_NAME} (already gz, no recompress)" else # .log → gzip (быстро, 1 ядро, gzip-default) gzip -c "$SRC" > "$DST" log " gzipped ${SRC} → ${S3_NAME}" fi SIZE=$(stat -c%s "$DST") SRC_SIZE=$(stat -c%s "$SRC") LINES=$(zcat -f "$SRC" 2>/dev/null | wc -l) SHA=$(sha256sum "$DST" | awk '{print $1}') META_FILES+="{\"name\":\"${S3_NAME}\",\"src_size\":${SRC_SIZE},\"size\":${SIZE},\"sha256\":\"${SHA}\",\"lines\":${LINES}}," done META_FILES="${META_FILES%,}]" cat > "$SPOOL/_meta.json" </dev/null | grep -q '^tw$'; then $MC alias set tw "$S3_ENDPOINT" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" --api S3v4 >>"$LOG" 2>&1 fi upload_with_retry() { local file=$1 key=$2 for attempt in 1 2 3 4 5; do if $MC cp --quiet "$file" "tw/${S3_BUCKET}/${key}" >>"$LOG" 2>&1; then log " uploaded → ${key}" return 0 fi log " upload FAIL attempt=$attempt for ${key}, retry in 30s" sleep 30 done log " ERROR: failed after 5 attempts: ${key}" return 1 } OK=0 FAIL=0 for f in "$SPOOL"/*.gz "$SPOOL"/_meta.json; do [ -f "$f" ] || continue BASE=$(basename "$f") if upload_with_retry "$f" "${S3_PREFIX}/${BASE}"; then OK=$((OK+1)) rm -f "$f" else FAIL=$((FAIL+1)) fi done log "═══ DONE ok=$OK fail=$FAIL ═══" log "" echo "uploaded for ${DATE}: ok=${OK} fail=${FAIL}" exit "$FAIL"