Rende persistenti i log di produzione e protegge il deploy.

Monta /app/log sul disco video, sposta i cron log sullo stesso path, esclude log/ dal sync rsync e aggiunge pulizia volumi Docker obsoleti.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-13 09:23:42 +02:00
parent b802cf3309
commit 208664848e
9 changed files with 82 additions and 7 deletions

View File

@@ -114,7 +114,8 @@ Watchdog esterno se Rails è completamente giù:
5. Rebuild: `docker compose -f docker-compose.prod.yml up -d --build rails sidekiq` 5. Rebuild: `docker compose -f docker-compose.prod.yml up -d --build rails sidekiq`
6. Migrate: `docker compose exec -T rails bundle exec rails db:migrate` 6. Migrate: `docker compose exec -T rails bundle exec rails db:migrate`
7. Cron: `bash scripts/install_production_cron.sh` 7. Cron: `bash scripts/install_production_cron.sh`
8. Smoke test: 8. (Opzionale) `bash scripts/cleanup_obsolete_volumes.sh` — rimuove volumi Docker migrati
9. Smoke test:
- `curl -sf https://www.matchlivetv.it/up` - `curl -sf https://www.matchlivetv.it/up`
- `bundle exec rails ops:health_check` (nel container) - `bundle exec rails ops:health_check` (nel container)
- Apri `/admin/ops` - Apri `/admin/ops`
@@ -130,4 +131,4 @@ Imposta `SENTRY_DSN` dopo `bundle install`. Gli errori creano anche incidenti op
| Nessuna push | Verifica `OPS_NTFY_URL`, test con `ops:test_notify` | | Nessuna push | Verifica `OPS_NTFY_URL`, test con `ops:test_notify` |
| Troppi alert | Aumenta `OPS_NOTIFY_COOLDOWN_MINUTES`, usa Mute 24h in admin | | Troppi alert | Aumenta `OPS_NOTIFY_COOLDOWN_MINUTES`, usa Mute 24h in admin |
| Sidekiq stale | Verifica container `sidekiq` e log Sidekiq | | Sidekiq stale | Verifica container `sidekiq` e log Sidekiq |
| Log scanner vuoto | Controlla `/opt/matchlivetv/log/cron-ops.log` | | Log scanner vuoto | Controlla `${MATCHLIVETV_VIDEOS_ROOT}/log/cron-ops.log` (es. `/media/videos/matchlivetv/log/cron-ops.log`) |

View File

@@ -109,7 +109,7 @@ Job attivi (utente `eminux`):
| 03:00 | `recordings:purge_expired` — elimina replay scaduti (DB + Garage) | | 03:00 | `recordings:purge_expired` — elimina replay scaduti (DB + Garage) |
| 08:00 | `recordings:expiry_warnings` — email avviso scadenza (7 gg) | | 08:00 | `recordings:expiry_warnings` — email avviso scadenza (7 gg) |
Log: `/opt/matchlivetv/log/cron-replay.log` Log: `${MATCHLIVETV_VIDEOS_ROOT}/log/cron-replay.log` (es. `/media/videos/matchlivetv/log/cron-replay.log`)
### Capacità Garage ### Capacità Garage

View File

@@ -53,8 +53,10 @@ SMTP_SSL=true
SMTP_STARTTLS=false SMTP_STARTTLS=false
PASSWORD_RESET_EXPIRY_HOURS=2 PASSWORD_RESET_EXPIRY_HOURS=2
# Disco video dedicato (recordings MediaMTX + dati Garage). Montare es. 800GB su /media/videos # Disco video dedicato (recordings MediaMTX + dati Garage + log + Active Storage). Montare es. 800GB su /media/videos
MATCHLIVETV_VIDEOS_ROOT=/media/videos/matchlivetv MATCHLIVETV_VIDEOS_ROOT=/media/videos/matchlivetv
# Log cron e YouTube relay ffmpeg (default: ${MATCHLIVETV_VIDEOS_ROOT}/log)
# MATCHLIVETV_LOG_DIR=/media/videos/matchlivetv/log
# Replay storage — Garage (obbligatorio in produzione per Premium replay) # Replay storage — Garage (obbligatorio in produzione per Premium replay)
# Dopo il primo deploy: bash scripts/setup_garage_production.sh (crea garage.prod.toml + chiavi) # Dopo il primo deploy: bash scripts/setup_garage_production.sh (crea garage.prod.toml + chiavi)

View File

@@ -131,6 +131,7 @@ services:
volumes: volumes:
- ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/recordings:/recordings - ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/recordings:/recordings
- ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/active_storage:/app/storage - ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/active_storage:/app/storage
- ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/log:/app/log
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/up"] test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/up"]
interval: 15s interval: 15s
@@ -203,6 +204,7 @@ services:
volumes: volumes:
- ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/recordings:/recordings - ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/recordings:/recordings
- ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/active_storage:/app/storage - ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/active_storage:/app/storage
- ${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}/log:/app/log
garage: garage:
image: dxflrs/garage:v1.0.1 image: dxflrs/garage:v1.0.1

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Rimuove volumi Docker obsoleti dopo migrazione storage su disco video.
set -euo pipefail
PROJECT="${COMPOSE_PROJECT_NAME:-infra}"
remove_if_empty() {
local name="${PROJECT}_$1"
if ! docker volume inspect "$name" >/dev/null 2>&1; then
echo "Volume ${name}: assente, salto"
return 0
fi
local mount
mount="$(docker volume inspect "$name" --format '{{ .Mountpoint }}')"
if [ -n "$(sudo ls -A "$mount" 2>/dev/null || true)" ]; then
echo "Volume ${name}: non vuoto (${mount}), salto"
return 0
fi
docker volume rm "$name"
echo "Volume ${name}: rimosso"
}
echo "Pulizia volumi Docker obsoleti (progetto ${PROJECT})..."
remove_if_empty rails_storage
remove_if_empty recordings
remove_if_empty garage_data
remove_if_empty garage_meta

View File

@@ -4,13 +4,30 @@ set -euo pipefail
ROOT="${MATCHLIVETV_INFRA:-/opt/matchlivetv/infra}" ROOT="${MATCHLIVETV_INFRA:-/opt/matchlivetv/infra}"
SINCE="${OPS_LOG_SCAN_SINCE:-10m}" SINCE="${OPS_LOG_SCAN_SINCE:-10m}"
ENV_FILE="${ROOT}/.env"
VIDEOS_ROOT="/media/videos/matchlivetv"
TMP="$(mktemp)" TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT trap 'rm -f "$TMP"' EXIT
if [ -f "$ENV_FILE" ]; then
line="$(grep -E '^MATCHLIVETV_VIDEOS_ROOT=' "$ENV_FILE" 2>/dev/null | tail -1 || true)"
if [ -n "$line" ]; then
VIDEOS_ROOT="${line#MATCHLIVETV_VIDEOS_ROOT=}"
VIDEOS_ROOT="${VIDEOS_ROOT%\"}"
VIDEOS_ROOT="${VIDEOS_ROOT#\"}"
fi
fi
LOG_DIR="${MATCHLIVETV_LOG_DIR:-${VIDEOS_ROOT}/log}"
cd "$ROOT" cd "$ROOT"
docker compose -f docker-compose.prod.yml logs --since "$SINCE" rails sidekiq mediamtx 2>/dev/null > "$TMP" || true docker compose -f docker-compose.prod.yml logs --since "$SINCE" rails sidekiq mediamtx 2>/dev/null > "$TMP" || true
if compgen -G "${LOG_DIR}/youtube_relay_*.log" > /dev/null 2>&1; then
cat "${LOG_DIR}"/youtube_relay_*.log >> "$TMP" 2>/dev/null || true
fi
if [[ ! -s "$TMP" ]]; then if [[ ! -s "$TMP" ]]; then
exit 0 exit 0
fi fi

View File

@@ -5,7 +5,19 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)" ROOT="$(cd "$(dirname "$0")/.." && pwd)"
RUNNER="${ROOT}/scripts/cron/run_rails_task.sh" RUNNER="${ROOT}/scripts/cron/run_rails_task.sh"
SCAN_LOGS="${ROOT}/scripts/cron/scan_ops_logs.sh" SCAN_LOGS="${ROOT}/scripts/cron/scan_ops_logs.sh"
LOG_DIR="${MATCHLIVETV_LOG_DIR:-/opt/matchlivetv/log}" ENV_FILE="${ROOT}/.env"
VIDEOS_ROOT="/media/videos/matchlivetv"
if [ -f "$ENV_FILE" ]; then
line="$(grep -E '^MATCHLIVETV_VIDEOS_ROOT=' "$ENV_FILE" 2>/dev/null | tail -1 || true)"
if [ -n "$line" ]; then
VIDEOS_ROOT="${line#MATCHLIVETV_VIDEOS_ROOT=}"
VIDEOS_ROOT="${VIDEOS_ROOT%\"}"
VIDEOS_ROOT="${VIDEOS_ROOT#\"}"
fi
fi
LOG_DIR="${MATCHLIVETV_LOG_DIR:-${VIDEOS_ROOT}/log}"
LOG_FILE="${LOG_DIR}/cron-replay.log" LOG_FILE="${LOG_DIR}/cron-replay.log"
OPS_LOG_FILE="${LOG_DIR}/cron-ops.log" OPS_LOG_FILE="${LOG_DIR}/cron-ops.log"
@@ -30,5 +42,6 @@ fi
echo "$CRON_BLOCK" echo "$CRON_BLOCK"
} | sed '/^$/d' | crontab - } | sed '/^$/d' | crontab -
echo "Log persistenti: ${LOG_DIR}"
echo "Crontab installato:" echo "Crontab installato:"
crontab -l | grep -A3 "$MARKER" crontab -l | grep -A4 "$MARKER"

View File

@@ -35,7 +35,17 @@ fs_rsync() {
sudo rsync -aH "$@" sudo rsync -aH "$@"
} }
fs_mkdir "${VIDEOS_ROOT}/recordings" "${VIDEOS_ROOT}/garage/meta" "${VIDEOS_ROOT}/garage/data" "${VIDEOS_ROOT}/active_storage" fs_mkdir "${VIDEOS_ROOT}/recordings" "${VIDEOS_ROOT}/garage/meta" "${VIDEOS_ROOT}/garage/data" "${VIDEOS_ROOT}/active_storage" "${VIDEOS_ROOT}/log"
legacy_log_dir="/opt/matchlivetv/log"
if [ -d "$legacy_log_dir" ] && [ -n "$(ls -A "$legacy_log_dir" 2>/dev/null || true)" ]; then
if [ -z "$(ls -A "${VIDEOS_ROOT}/log" 2>/dev/null || true)" ]; then
echo "Migro log legacy ${legacy_log_dir}${VIDEOS_ROOT}/log..."
fs_rsync "${legacy_log_dir}/" "${VIDEOS_ROOT}/log/"
else
echo "Log già presenti in ${VIDEOS_ROOT}/log, salto migrazione da ${legacy_log_dir}"
fi
fi
if [ ! -w "$VIDEOS_ROOT" ]; then if [ ! -w "$VIDEOS_ROOT" ]; then
sudo chown -R "$(id -un)":"$(id -gn)" "$VIDEOS_ROOT" sudo chown -R "$(id -un)":"$(id -gn)" "$VIDEOS_ROOT"
fi fi

View File

@@ -17,6 +17,7 @@ if ssh "$SERVER" 'command -v rsync >/dev/null 2>&1'; then
--exclude 'backend/tmp' \ --exclude 'backend/tmp' \
--exclude 'backend/.bundle' \ --exclude 'backend/.bundle' \
--exclude 'infra/.env' \ --exclude 'infra/.env' \
--exclude 'log/' \
--exclude 'node_modules' \ --exclude 'node_modules' \
"$SRC/" "$SERVER:$DEST/" "$SRC/" "$SERVER:$DEST/"
else else