Sposta storage video su disco dedicato e completa ops/SMTP in produzione.

Recordings e Garage usano bind mount su /media/videos; aggiunge ntfy self-hosted,
notifiche critical+warning, SMTP Aruba SSL/465 e fix cron replay.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-12 23:13:08 +02:00
parent 52cfffb83f
commit da75582dae
14 changed files with 278 additions and 44 deletions

View File

@@ -6,6 +6,12 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DEST="${ROOT}/garage/garage.prod.toml"
TEMPLATE="${ROOT}/garage/garage.prod.toml.example"
if [ -d "$DEST" ]; then
echo "ERRORE: $DEST è una directory (bind mount Docker senza file sorgente)."
echo "Rimuovi la directory e rilancia questo script."
exit 1
fi
if [ -f "$DEST" ]; then
echo "Config Garage produzione già presente: $DEST"
exit 0

View File

@@ -8,9 +8,14 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.prod.yml}"
ENV_FILE="${ENV_FILE:-${ROOT}/.env}"
GARAGE_CONTAINER="${GARAGE_CONTAINER:-}"
VIDEOS_ROOT="${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}"
if [ -z "$CAPACITY" ]; then
avail="$(df -BG "${ROOT}" 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' || echo 0)"
capacity_df="${VIDEOS_ROOT}"
if [ ! -d "$capacity_df" ]; then
capacity_df="${ROOT}"
fi
avail="$(df -BG "${capacity_df}" 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' || echo 0)"
if [ "$avail" -lt 1 ]; then
avail="$(df -BG / 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G')"
fi

View File

@@ -14,14 +14,14 @@ mkdir -p "$LOG_DIR"
MARKER="# matchlivetv-replay-cron"
CRON_BLOCK="${MARKER}
30 * * * * ${RUNNER} recordings:cleanup_local >> ${LOG_FILE} 2>&1
0 3 * * * ${RUNNER} recordings:purge_expired >> ${LOG_FILE} 2>&1
0 8 * * * ${RUNNER} recordings:expiry_warnings >> ${LOG_FILE} 2>&1
*/10 * * * * ${SCAN_LOGS} >> ${OPS_LOG_FILE} 2>&1
30 * * * * mkdir -p ${LOG_DIR} && ${RUNNER} recordings:cleanup_local >> ${LOG_FILE} 2>&1
0 3 * * * mkdir -p ${LOG_DIR} && ${RUNNER} recordings:purge_expired >> ${LOG_FILE} 2>&1
0 8 * * * mkdir -p ${LOG_DIR} && ${RUNNER} recordings:expiry_warnings >> ${LOG_FILE} 2>&1
*/10 * * * * mkdir -p ${LOG_DIR} && ${SCAN_LOGS} >> ${OPS_LOG_FILE} 2>&1
"
existing="$(crontab -l 2>/dev/null || true)"
filtered="$(echo "$existing" | grep -v "$MARKER" | grep -v 'run_rails_task\.sh' || true)"
filtered="$(echo "$existing" | grep -v "$MARKER" | grep -v 'run_rails_task\.sh' | grep -v 'scan_ops_logs\.sh' || true)"
if echo "$existing" | grep -q "$MARKER"; then
echo "Crontab replay già presente, aggiorno..."
fi

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Migra recordings + Garage da volumi Docker al disco video (es. /media/videos/matchlivetv).
# Uso: sudo bash scripts/migrate_videos_storage.sh
# Richiede: docker compose prod fermo o in fase di stop; disco montato su MATCHLIVETV_VIDEOS_ROOT.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
VIDEOS_ROOT="${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.prod.yml}"
ENV_FILE="${ENV_FILE:-${ROOT}/.env}"
PROJECT="${COMPOSE_PROJECT_NAME:-infra}"
VIDEOS_MOUNT="$(dirname "$VIDEOS_ROOT")"
if ! mountpoint -q "$VIDEOS_MOUNT" 2>/dev/null && [ ! -d "$VIDEOS_ROOT" ]; then
echo "ERRORE: disco video non montato (atteso ${VIDEOS_MOUNT})"
exit 1
fi
echo "Destinazione: ${VIDEOS_ROOT}"
df -h "$VIDEOS_ROOT" 2>/dev/null || df -h "$VIDEOS_MOUNT"
fs_mkdir() {
if mkdir -p "$@" 2>/dev/null; then
return 0
fi
sudo mkdir -p "$@"
}
fs_rsync() {
if rsync -aH "$@" 2>/dev/null; then
return 0
fi
sudo rsync -aH "$@"
}
fs_mkdir "${VIDEOS_ROOT}/recordings" "${VIDEOS_ROOT}/garage/meta" "${VIDEOS_ROOT}/garage/data"
if [ ! -w "$VIDEOS_ROOT" ]; then
sudo chown -R "$(id -un)":"$(id -gn)" "$VIDEOS_ROOT"
fi
volume_path() {
local mp
mp="$(docker volume inspect "${PROJECT}_$1" --format '{{ .Mountpoint }}' 2>/dev/null || true)"
[ -n "$mp" ] && [ -d "$mp" ] && echo "$mp" && return 0
mp="$(sudo docker volume inspect "${PROJECT}_$1" --format '{{ .Mountpoint }}' 2>/dev/null || true)"
[ -n "$mp" ] && echo "$mp"
}
copy_volume() {
local vol="$1" dest="$2"
local src
src="$(volume_path "$vol")"
if [ -z "$src" ] || [ ! -d "$src" ]; then
echo "Volume ${PROJECT}_${vol} assente, salto copia verso ${dest}"
return 0
fi
if [ -n "$(sudo ls -A "$dest" 2>/dev/null || true)" ]; then
echo "Già presente in ${dest}, salto copia da ${src}"
return 0
fi
echo "Copia ${src}${dest}..."
fs_rsync "${src}/" "${dest}/"
}
echo "Arresto servizi che usano recordings/Garage..."
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" stop mediamtx sidekiq rails garage
copy_volume recordings "${VIDEOS_ROOT}/recordings"
copy_volume garage_meta "${VIDEOS_ROOT}/garage/meta"
copy_volume garage_data "${VIDEOS_ROOT}/garage/data"
if ! grep -q '^MATCHLIVETV_VIDEOS_ROOT=' "$ENV_FILE" 2>/dev/null; then
echo "MATCHLIVETV_VIDEOS_ROOT=${VIDEOS_ROOT}" >> "$ENV_FILE"
echo "Aggiunto MATCHLIVETV_VIDEOS_ROOT a ${ENV_FILE}"
fi
if [ -d "${ROOT}/garage/garage.prod.toml" ]; then
echo "ERRORE: ${ROOT}/garage/garage.prod.toml è una directory (bind mount Docker corrotto)."
echo "Rimuovi la directory e ripristina il file TOML prima di continuare."
exit 1
fi
echo "Riavvio stack..."
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --force-recreate mediamtx garage rails sidekiq
echo "Ridimensiono capacità Garage sul disco video..."
export MATCHLIVETV_VIDEOS_ROOT="$VIDEOS_ROOT"
bash "${ROOT}/scripts/garage_set_capacity.sh"
echo "Verifica mount:"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T rails \
bundle exec rails runner \
'puts({recordings: `du -sh /recordings 2>/dev/null`.strip, storage: Recordings::Storage.new.backend_name}.inspect)'
echo "Fatto. Volumi Docker obsoleti (rimuovi dopo verifica):"
for v in recordings garage_data garage_meta; do
docker volume inspect "${PROJECT}_${v}" --format '{{ .Name }} ({{ .Mountpoint }})' 2>/dev/null || true
done
echo " docker volume rm ${PROJECT}_recordings ${PROJECT}_garage_data ${PROJECT}_garage_meta"

View File

@@ -14,7 +14,10 @@ bash "${ROOT}/scripts/ensure_garage_prod_config.sh"
echo "Avvio Garage..."
docker compose -f docker-compose.prod.yml --env-file .env up -d garage
avail_gb="$(df -BG / 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G')"
videos_root="${MATCHLIVETV_VIDEOS_ROOT:-/media/videos/matchlivetv}"
capacity_df="${videos_root}"
[ -d "$capacity_df" ] || capacity_df="/"
avail_gb="$(df -BG "${capacity_df}" 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G')"
reserve="${GARAGE_RESERVE_GB:-20}"
cap_gb=$((avail_gb - reserve))
[ "$cap_gb" -lt 10 ] && cap_gb=10