++ Primo Caricamento
This commit is contained in:
603
sync.sh
Normal file
603
sync.sh
Normal file
@@ -0,0 +1,603 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# sync.sh - Script di sincronizzazione S3 → Locale
|
||||
# ============================================================
|
||||
# Configura rclone dinamicamente dalle variabili d'ambiente
|
||||
# e avvia un loop di sincronizzazione periodica.
|
||||
# Scrive stato strutturato in /data/state/ per la dashboard web.
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Colori per il log (solo su terminale, non nel file di log) ---
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# --- Directory di stato condivisa con il web server ---
|
||||
STATE_DIR="${STATE_DIR:-/data/state}"
|
||||
LOG_FILE="${STATE_DIR}/sync.log"
|
||||
STATUS_FILE="${STATE_DIR}/status.json"
|
||||
HISTORY_FILE="${STATE_DIR}/history.json"
|
||||
CHANGES_FILE="${STATE_DIR}/recent_changes.json"
|
||||
|
||||
# Crea la directory di stato
|
||||
mkdir -p "${STATE_DIR}"
|
||||
|
||||
# Inizializza i file JSON se non esistono
|
||||
[ -f "${HISTORY_FILE}" ] || echo '[]' > "${HISTORY_FILE}"
|
||||
[ -f "${CHANGES_FILE}" ] || echo '[]' > "${CHANGES_FILE}"
|
||||
|
||||
# --- Funzioni di logging ---
|
||||
# Scrive sia su stdout (con colori) che sul file di log (senza colori)
|
||||
log_info() {
|
||||
local msg="[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $*"
|
||||
echo -e "${GREEN}${msg}${NC}"
|
||||
echo "${msg}" >> "${LOG_FILE}"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
local msg="[WARN] $(date '+%Y-%m-%d %H:%M:%S') - $*"
|
||||
echo -e "${YELLOW}${msg}${NC}"
|
||||
echo "${msg}" >> "${LOG_FILE}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
local msg="[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $*"
|
||||
echo -e "${RED}${msg}${NC}"
|
||||
echo "${msg}" >> "${LOG_FILE}"
|
||||
}
|
||||
|
||||
# --- Funzioni di stato (scrivono JSON per la dashboard) ---
|
||||
write_status() {
|
||||
# Scrive lo stato corrente in formato JSON
|
||||
local state="$1"
|
||||
local message="${2:-}"
|
||||
local now
|
||||
now="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
cat > "${STATUS_FILE}" <<ENDJSON
|
||||
{
|
||||
"state": "${state}",
|
||||
"message": "${message}",
|
||||
"last_sync": ${LAST_SYNC_TS:-null},
|
||||
"updated_at": "${now}",
|
||||
"sync_count": ${SYNC_COUNT:-0},
|
||||
"error_count": ${ERROR_COUNT:-0}
|
||||
}
|
||||
ENDJSON
|
||||
}
|
||||
|
||||
append_history() {
|
||||
# Aggiunge un record allo storico delle sincronizzazioni (max 50 entries)
|
||||
local success="$1"
|
||||
local duration="$2"
|
||||
local transferred="${3:-0}"
|
||||
local size="${4:-0 B}"
|
||||
local time_str
|
||||
time_str="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
# Leggi storico attuale, aggiungi in testa, limita a 50
|
||||
python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
with open('${HISTORY_FILE}', 'r') as f:
|
||||
history = json.load(f)
|
||||
except:
|
||||
history = []
|
||||
entry = {
|
||||
'time': '${time_str}',
|
||||
'success': ${success},
|
||||
'duration': '${duration}',
|
||||
'transferred': '${transferred}',
|
||||
'size': '${size}'
|
||||
}
|
||||
history.insert(0, entry)
|
||||
history = history[:50]
|
||||
with open('${HISTORY_FILE}', 'w') as f:
|
||||
json.dump(history, f)
|
||||
"
|
||||
}
|
||||
|
||||
update_recent_changes() {
|
||||
# Confronta lo stato prima/dopo la sync per trovare le modifiche
|
||||
local after_snapshot="$1"
|
||||
local before_snapshot="$2"
|
||||
|
||||
python3 -c "
|
||||
import json, os
|
||||
from datetime import datetime
|
||||
|
||||
after_file = '${after_snapshot}'
|
||||
before_file = '${before_snapshot}'
|
||||
|
||||
def read_snapshot(path):
|
||||
result = {}
|
||||
try:
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
parts = line.split('|', 2)
|
||||
if len(parts) == 3:
|
||||
result[parts[2]] = {'size': int(parts[0]), 'mtime': parts[1]}
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
def format_size(b):
|
||||
for u in ['B','KB','MB','GB','TB']:
|
||||
if b < 1024: return f'{b:.1f} {u}'
|
||||
b /= 1024
|
||||
return f'{b:.1f} PB'
|
||||
|
||||
before = read_snapshot(before_file)
|
||||
after = read_snapshot(after_file)
|
||||
|
||||
changes = []
|
||||
now = datetime.now().strftime('%H:%M:%S')
|
||||
|
||||
# File nuovi
|
||||
for path in after:
|
||||
if path not in before:
|
||||
changes.append({
|
||||
'action': 'new',
|
||||
'path': path,
|
||||
'size': format_size(after[path]['size']),
|
||||
'time': now
|
||||
})
|
||||
|
||||
# File modificati
|
||||
for path in after:
|
||||
if path in before and (after[path]['size'] != before[path]['size'] or after[path]['mtime'] != before[path]['mtime']):
|
||||
changes.append({
|
||||
'action': 'modified',
|
||||
'path': path,
|
||||
'size': format_size(after[path]['size']),
|
||||
'time': now
|
||||
})
|
||||
|
||||
# File eliminati (solo in modalità mirror)
|
||||
for path in before:
|
||||
if path not in after:
|
||||
changes.append({
|
||||
'action': 'deleted',
|
||||
'path': path,
|
||||
'size': format_size(before[path]['size']),
|
||||
'time': now
|
||||
})
|
||||
|
||||
# Mantieni le ultime 100 modifiche
|
||||
try:
|
||||
with open('${CHANGES_FILE}', 'r') as f:
|
||||
old_changes = json.load(f)
|
||||
except:
|
||||
old_changes = []
|
||||
|
||||
all_changes = changes + old_changes
|
||||
all_changes = all_changes[:100]
|
||||
|
||||
with open('${CHANGES_FILE}', 'w') as f:
|
||||
json.dump(all_changes, f)
|
||||
"
|
||||
}
|
||||
|
||||
take_snapshot() {
|
||||
# Scatta una "foto" dello stato dei file locali: size|mtime|path
|
||||
local output_file="$1"
|
||||
find "${LOCAL_PATH}" -type f -printf '%s|%T@|%P\n' 2>/dev/null > "${output_file}" || true
|
||||
}
|
||||
|
||||
# Tronca il file di log se troppo grande (>5MB)
|
||||
truncate_log_if_needed() {
|
||||
if [ -f "${LOG_FILE}" ]; then
|
||||
local size
|
||||
size=$(stat -f%z "${LOG_FILE}" 2>/dev/null || stat -c%s "${LOG_FILE}" 2>/dev/null || echo 0)
|
||||
if [ "${size}" -gt 5242880 ]; then
|
||||
# Mantieni solo le ultime 1000 righe
|
||||
tail -n 1000 "${LOG_FILE}" > "${LOG_FILE}.tmp"
|
||||
mv "${LOG_FILE}.tmp" "${LOG_FILE}"
|
||||
log_info "File di log troncato (superato 5MB)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Notifiche Gotify
|
||||
# ============================================================
|
||||
# Invia UNA SOLA notifica per ciclo di sync, al termine del
|
||||
# processo, con riepilogo completo del lavoro eseguito.
|
||||
# ============================================================
|
||||
|
||||
GOTIFY_ENABLED="${GOTIFY_ENABLED:-false}"
|
||||
GOTIFY_URL="${GOTIFY_URL:-}"
|
||||
GOTIFY_TOKEN="${GOTIFY_TOKEN:-}"
|
||||
GOTIFY_PRIORITY="${GOTIFY_PRIORITY:-5}"
|
||||
|
||||
gotify_send() {
|
||||
# Invia una notifica push a Gotify
|
||||
# Parametri: $1 = titolo, $2 = messaggio, $3 = priorità (opzionale)
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local priority="${3:-${GOTIFY_PRIORITY}}"
|
||||
|
||||
# Non inviare se Gotify è disabilitato o non configurato
|
||||
if [ "${GOTIFY_ENABLED}" != "true" ] || [ -z "${GOTIFY_URL}" ] || [ -z "${GOTIFY_TOKEN}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Invia la notifica in background per non bloccare la sync
|
||||
# Usa il formato Markdown per il messaggio
|
||||
wget -q --timeout=10 --tries=2 -O /dev/null \
|
||||
--post-data="$(printf '{"title":"%s","message":"%s","priority":%s,"extras":{"client::display":{"contentType":"text/markdown"}}}' \
|
||||
"${title}" "${message}" "${priority}")" \
|
||||
--header="Content-Type: application/json" \
|
||||
"${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" 2>/dev/null &
|
||||
|
||||
log_info "Notifica Gotify inviata: ${title}"
|
||||
}
|
||||
|
||||
gotify_notify_sync_result() {
|
||||
# Notifica unica a fine processo di sync con riepilogo completo
|
||||
# Parametri: $1 = success (true/false), $2 = durata, $3 = snap_after, $4 = snap_before
|
||||
local success="$1"
|
||||
local duration="$2"
|
||||
local after_snap="${3:-}"
|
||||
local before_snap="${4:-}"
|
||||
|
||||
# Info pianificazione
|
||||
local schedule_info
|
||||
if [ "${SCHEDULE_MODE:-interval}" = "cron" ]; then
|
||||
schedule_info="${SCHEDULE_HUMAN:-cron}"
|
||||
else
|
||||
schedule_info="ogni ${SYNC_INTERVAL}s"
|
||||
fi
|
||||
|
||||
if [ "${success}" = "true" ]; then
|
||||
# --- Sync riuscita: calcola riepilogo modifiche ---
|
||||
local summary
|
||||
summary=$(python3 -c "
|
||||
import sys
|
||||
|
||||
def read_snapshot(path):
|
||||
result = {}
|
||||
try:
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
parts = line.split('|', 2)
|
||||
if len(parts) == 3:
|
||||
result[parts[2]] = int(parts[0])
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
def format_size(b):
|
||||
for u in ['B','KB','MB','GB','TB']:
|
||||
if b < 1024: return f'{b:.1f} {u}'
|
||||
b /= 1024
|
||||
return f'{b:.1f} PB'
|
||||
|
||||
before = read_snapshot('${before_snap}')
|
||||
after = read_snapshot('${after_snap}')
|
||||
|
||||
added = [p for p in after if p not in before]
|
||||
modified = [p for p in after if p in before and after[p] != before[p]]
|
||||
deleted = [p for p in before if p not in after]
|
||||
total_size = sum(after.values())
|
||||
|
||||
lines = []
|
||||
if not added and not modified and not deleted:
|
||||
lines.append('Nessuna modifica rilevata')
|
||||
else:
|
||||
if added:
|
||||
lines.append(f'**+{len(added)}** nuovi')
|
||||
for f in added[:3]:
|
||||
lines.append(f' ↳ {f} ({format_size(after[f])})')
|
||||
if len(added) > 3:
|
||||
lines.append(f' ↳ ...e altri {len(added)-3}')
|
||||
if modified:
|
||||
lines.append(f'**~{len(modified)}** modificati')
|
||||
for f in modified[:3]:
|
||||
lines.append(f' ↳ {f}')
|
||||
if len(modified) > 3:
|
||||
lines.append(f' ↳ ...e altri {len(modified)-3}')
|
||||
if deleted:
|
||||
lines.append(f'**-{len(deleted)}** rimossi')
|
||||
for f in deleted[:3]:
|
||||
lines.append(f' ↳ {f}')
|
||||
if len(deleted) > 3:
|
||||
lines.append(f' ↳ ...e altri {len(deleted)-3}')
|
||||
|
||||
lines.append(f'\\n**Totale locale:** {len(after)} file ({format_size(total_size)})')
|
||||
print('\\\\n'.join(lines))
|
||||
" 2>/dev/null || echo "Riepilogo non disponibile")
|
||||
|
||||
gotify_send \
|
||||
"✅ Sync #${SYNC_COUNT} completata" \
|
||||
"**Bucket:** ${S3_BUCKET}\\n**Modalità:** ${SYNC_MODE}\\n**Pianificazione:** ${schedule_info}\\n**Durata:** ${duration}s\\n\\n---\\n\\n${summary}"
|
||||
|
||||
else
|
||||
# --- Sync fallita: notifica con priorità alta ---
|
||||
gotify_send \
|
||||
"❌ Sync fallita" \
|
||||
"**Bucket:** ${S3_BUCKET}\\n**Modalità:** ${SYNC_MODE}\\n**Pianificazione:** ${schedule_info}\\n**Durata:** ${duration}s\\n**Errori totali:** ${ERROR_COUNT}\\n\\nControllare i log per dettagli." \
|
||||
"8"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Validazione variabili obbligatorie ---
|
||||
log_info "Avvio S3-to-Local Sync..."
|
||||
write_status "starting" "Validazione configurazione..."
|
||||
|
||||
REQUIRED_VARS=("S3_ENDPOINT" "S3_ACCESS_KEY" "S3_SECRET_KEY" "S3_BUCKET")
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [ -z "${!var:-}" ]; then
|
||||
log_error "Variabile d'ambiente obbligatoria non impostata: $var"
|
||||
write_status "error" "Variabile mancante: $var"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# --- Contatori ---
|
||||
SYNC_COUNT=0
|
||||
ERROR_COUNT=0
|
||||
LAST_SYNC_TS="null"
|
||||
|
||||
# --- Impostazione permessi utente (se specificati) ---
|
||||
PUID="${PUID:-1000}"
|
||||
PGID="${PGID:-1000}"
|
||||
log_info "File verranno creati con UID=$PUID, GID=$PGID"
|
||||
|
||||
# --- Configurazione rclone via environment (no file di config) ---
|
||||
# Rclone supporta la configurazione tramite variabili d'ambiente
|
||||
# con il prefisso RCLONE_CONFIG_<REMOTE>_<PARAMETRO>
|
||||
export RCLONE_CONFIG_S3SOURCE_TYPE="s3"
|
||||
export RCLONE_CONFIG_S3SOURCE_PROVIDER="Other"
|
||||
export RCLONE_CONFIG_S3SOURCE_ENDPOINT="${S3_ENDPOINT}"
|
||||
export RCLONE_CONFIG_S3SOURCE_ACCESS_KEY_ID="${S3_ACCESS_KEY}"
|
||||
export RCLONE_CONFIG_S3SOURCE_SECRET_ACCESS_KEY="${S3_SECRET_KEY}"
|
||||
export RCLONE_CONFIG_S3SOURCE_FORCE_PATH_STYLE="${S3_FORCE_PATH_STYLE:-true}"
|
||||
|
||||
# Regione (opzionale)
|
||||
if [ -n "${S3_REGION:-}" ]; then
|
||||
export RCLONE_CONFIG_S3SOURCE_REGION="${S3_REGION}"
|
||||
fi
|
||||
|
||||
# Gestione SSL insicuro (per certificati self-signed in sviluppo)
|
||||
if [ "${S3_INSECURE_SSL:-false}" = "true" ]; then
|
||||
log_warn "SSL verification disabilitata - usare solo in sviluppo!"
|
||||
export RCLONE_CONFIG_S3SOURCE_NO_CHECK_CERTIFICATE="true"
|
||||
fi
|
||||
|
||||
# --- Costruzione path sorgente ---
|
||||
# Se S3_PATH_PREFIX è specificato, sincronizza solo quella sottocartella
|
||||
SOURCE_PATH="S3SOURCE:${S3_BUCKET}"
|
||||
if [ -n "${S3_PATH_PREFIX:-}" ]; then
|
||||
SOURCE_PATH="${SOURCE_PATH}/${S3_PATH_PREFIX}"
|
||||
log_info "Sincronizzazione limitata al prefisso: ${S3_PATH_PREFIX}"
|
||||
fi
|
||||
|
||||
# --- Parametri di sincronizzazione ---
|
||||
SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
|
||||
SYNC_SCHEDULE="${SYNC_SCHEDULE:-}"
|
||||
SYNC_MODE="${SYNC_MODE:-mirror}"
|
||||
SYNC_TRANSFERS="${SYNC_TRANSFERS:-4}"
|
||||
SYNC_BANDWIDTH="${SYNC_BANDWIDTH:-0}"
|
||||
SYNC_LOG_LEVEL="${SYNC_LOG_LEVEL:-INFO}"
|
||||
SYNC_ON_START="${SYNC_ON_START:-true}"
|
||||
|
||||
LOCAL_PATH="/data/local"
|
||||
|
||||
# --- Helper cron (script Python per il parsing delle espressioni) ---
|
||||
CRON_HELPER="/app/web/cron_helper.py"
|
||||
|
||||
# --- Determina la modalità di scheduling ---
|
||||
# Se SYNC_SCHEDULE è impostato, usa la pianificazione cron
|
||||
# Altrimenti, usa SYNC_INTERVAL (comportamento classico)
|
||||
if [ -n "${SYNC_SCHEDULE}" ]; then
|
||||
SCHEDULE_MODE="cron"
|
||||
# Valida l'espressione cron all'avvio
|
||||
if ! python3 "${CRON_HELPER}" "${SYNC_SCHEDULE}" validate >/dev/null 2>&1; then
|
||||
log_error "Espressione cron non valida: '${SYNC_SCHEDULE}'"
|
||||
log_error "Formato: minuto ora giorno_mese mese giorno_settimana"
|
||||
log_error "Esempi: '0 3 * * *' (ogni giorno alle 3:00), '*/30 * * * *' (ogni 30 min)"
|
||||
write_status "error" "Espressione cron non valida: ${SYNC_SCHEDULE}"
|
||||
exit 1
|
||||
fi
|
||||
SCHEDULE_HUMAN=$(python3 "${CRON_HELPER}" "${SYNC_SCHEDULE}" human)
|
||||
log_info "Pianificazione: CRON - ${SCHEDULE_HUMAN} (${SYNC_SCHEDULE})"
|
||||
else
|
||||
SCHEDULE_MODE="interval"
|
||||
log_info "Pianificazione: INTERVALLO - ogni ${SYNC_INTERVAL}s"
|
||||
fi
|
||||
|
||||
# --- Determina il comando rclone in base alla modalità ---
|
||||
case "${SYNC_MODE}" in
|
||||
mirror)
|
||||
# Mirror: replica esatta, cancella file locali non presenti nel bucket
|
||||
RCLONE_CMD="sync"
|
||||
log_info "Modalità: MIRROR (i file rimossi dal bucket verranno rimossi localmente)"
|
||||
;;
|
||||
copy)
|
||||
# Copy: solo copia, non cancella nulla localmente
|
||||
RCLONE_CMD="copy"
|
||||
log_info "Modalità: COPY (i file locali extra non verranno rimossi)"
|
||||
;;
|
||||
*)
|
||||
log_error "Modalità di sync non valida: ${SYNC_MODE} (valori ammessi: mirror, copy)"
|
||||
write_status "error" "Modalità non valida: ${SYNC_MODE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Riepilogo configurazione ---
|
||||
log_info "========================================="
|
||||
log_info "Configurazione:"
|
||||
log_info " Endpoint: ${S3_ENDPOINT}"
|
||||
log_info " Bucket: ${S3_BUCKET}"
|
||||
log_info " Prefisso: ${S3_PATH_PREFIX:-(nessuno, tutto il bucket)}"
|
||||
log_info " Destinazione: ${LOCAL_PATH}"
|
||||
log_info " Modalità: ${SYNC_MODE} (comando: rclone ${RCLONE_CMD})"
|
||||
if [ "${SCHEDULE_MODE}" = "cron" ]; then
|
||||
log_info " Pianificazione: ${SCHEDULE_HUMAN} (${SYNC_SCHEDULE})"
|
||||
else
|
||||
log_info " Intervallo: ${SYNC_INTERVAL}s"
|
||||
fi
|
||||
log_info " Sync all'avvio: ${SYNC_ON_START}"
|
||||
log_info " Trasferimenti paralleli: ${SYNC_TRANSFERS}"
|
||||
log_info " Limite banda: ${SYNC_BANDWIDTH:-nessuno}"
|
||||
log_info " Log level: ${SYNC_LOG_LEVEL}"
|
||||
if [ "${GOTIFY_ENABLED}" = "true" ]; then
|
||||
log_info " Gotify: abilitato (${GOTIFY_URL}), priorità ${GOTIFY_PRIORITY}"
|
||||
else
|
||||
log_info " Gotify: disabilitato"
|
||||
fi
|
||||
log_info "========================================="
|
||||
|
||||
# --- Gestione segnali per shutdown pulito ---
|
||||
RUNNING=true
|
||||
trap 'log_info "Ricevuto segnale di stop, arresto..."; RUNNING=false' SIGTERM SIGINT
|
||||
|
||||
# --- Funzione di sincronizzazione ---
|
||||
do_sync() {
|
||||
log_info "Inizio sincronizzazione..."
|
||||
write_status "syncing" "Sincronizzazione in corso..."
|
||||
|
||||
# Snapshot pre-sync per rilevare le modifiche
|
||||
local before_snap="${STATE_DIR}/.snap_before"
|
||||
local after_snap="${STATE_DIR}/.snap_after"
|
||||
take_snapshot "${before_snap}"
|
||||
|
||||
# Timestamp di inizio per calcolare la durata
|
||||
local start_ts
|
||||
start_ts=$(date +%s)
|
||||
|
||||
# Costruisci gli argomenti rclone
|
||||
RCLONE_ARGS=(
|
||||
"${RCLONE_CMD}"
|
||||
"${SOURCE_PATH}"
|
||||
"${LOCAL_PATH}"
|
||||
"--transfers" "${SYNC_TRANSFERS}"
|
||||
"--log-level" "${SYNC_LOG_LEVEL}"
|
||||
"--stats" "30s" # Mostra statistiche ogni 30s durante il trasferimento
|
||||
"--stats-one-line" # Statistiche su una riga (più leggibile nei log)
|
||||
"--no-update-modtime" # Non modifica i timestamp locali se il contenuto è uguale
|
||||
"--log-file" "${LOG_FILE}" # Log anche su file per la dashboard
|
||||
)
|
||||
|
||||
# Limite di banda (se specificato e diverso da 0)
|
||||
if [ "${SYNC_BANDWIDTH}" != "0" ]; then
|
||||
RCLONE_ARGS+=("--bwlimit" "${SYNC_BANDWIDTH}")
|
||||
fi
|
||||
|
||||
# Esegui rclone
|
||||
local end_ts duration
|
||||
if rclone "${RCLONE_ARGS[@]}"; then
|
||||
end_ts=$(date +%s)
|
||||
duration=$((end_ts - start_ts))
|
||||
|
||||
log_info "Sincronizzazione completata con successo in ${duration}s"
|
||||
|
||||
# Aggiorna contatori
|
||||
SYNC_COUNT=$((SYNC_COUNT + 1))
|
||||
LAST_SYNC_TS="\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\""
|
||||
|
||||
# Snapshot post-sync e calcolo differenze
|
||||
take_snapshot "${after_snap}"
|
||||
update_recent_changes "${after_snap}" "${before_snap}"
|
||||
|
||||
# Notifica Gotify: unica notifica a fine sync con riepilogo
|
||||
gotify_notify_sync_result "true" "${duration}" "${after_snap}" "${before_snap}"
|
||||
|
||||
# Aggiungi allo storico
|
||||
append_history "true" "${duration}" "0" "0 B"
|
||||
|
||||
# Aggiorna proprietario dei file se necessario
|
||||
chown -R "${PUID}:${PGID}" "${LOCAL_PATH}" 2>/dev/null || true
|
||||
|
||||
write_status "idle" "Sincronizzazione completata"
|
||||
else
|
||||
end_ts=$(date +%s)
|
||||
duration=$((end_ts - start_ts))
|
||||
|
||||
log_error "Sincronizzazione fallita dopo ${duration}s"
|
||||
ERROR_COUNT=$((ERROR_COUNT + 1))
|
||||
|
||||
# Notifica Gotify: unica notifica di errore (priorità alta)
|
||||
gotify_notify_sync_result "false" "${duration}"
|
||||
|
||||
append_history "false" "${duration}" "0" "0 B"
|
||||
write_status "error" "Sincronizzazione fallita"
|
||||
fi
|
||||
|
||||
# Pulizia snapshot temporanei
|
||||
rm -f "${before_snap}" "${after_snap}"
|
||||
|
||||
# Tronca log se troppo grande
|
||||
truncate_log_if_needed
|
||||
}
|
||||
|
||||
# --- Loop principale ---
|
||||
|
||||
# Esegui la prima sincronizzazione all'avvio (se abilitato)
|
||||
if [ "${SYNC_ON_START}" = "true" ]; then
|
||||
log_info "Sincronizzazione iniziale all'avvio..."
|
||||
do_sync
|
||||
else
|
||||
log_info "Sync all'avvio disabilitata, in attesa della prossima pianificazione..."
|
||||
write_status "waiting" "In attesa della prima esecuzione pianificata"
|
||||
fi
|
||||
|
||||
# Funzione per l'attesa interrompibile di N secondi
|
||||
wait_seconds() {
|
||||
local total="$1"
|
||||
local waited=0
|
||||
while [ "$RUNNING" = true ] && [ "$waited" -lt "$total" ]; do
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# Loop in base alla modalità di scheduling
|
||||
if [ "${SCHEDULE_MODE}" = "cron" ]; then
|
||||
# ===== MODALITÀ CRON =====
|
||||
# Calcola i secondi fino alla prossima esecuzione cron e attende
|
||||
|
||||
while [ "$RUNNING" = true ]; do
|
||||
# Calcola prossima esecuzione
|
||||
NEXT_RUN=$(python3 "${CRON_HELPER}" "${SYNC_SCHEDULE}" next)
|
||||
WAIT_SECS=$(python3 "${CRON_HELPER}" "${SYNC_SCHEDULE}" seconds)
|
||||
|
||||
log_info "Prossima esecuzione pianificata: ${NEXT_RUN} (tra ${WAIT_SECS}s)"
|
||||
write_status "waiting" "Prossima sync: ${NEXT_RUN}"
|
||||
|
||||
# Attesa interrompibile
|
||||
wait_seconds "${WAIT_SECS}"
|
||||
|
||||
# Esegui sync solo se siamo ancora in esecuzione
|
||||
if [ "$RUNNING" = true ]; then
|
||||
do_sync
|
||||
fi
|
||||
done
|
||||
else
|
||||
# ===== MODALITÀ INTERVALLO =====
|
||||
# Comportamento classico: attende SYNC_INTERVAL secondi tra le sync
|
||||
|
||||
while [ "$RUNNING" = true ]; do
|
||||
write_status "waiting" "Prossima sync tra ${SYNC_INTERVAL}s"
|
||||
|
||||
# Attesa interrompibile
|
||||
wait_seconds "${SYNC_INTERVAL}"
|
||||
|
||||
# Esegui sync solo se siamo ancora in esecuzione
|
||||
if [ "$RUNNING" = true ]; then
|
||||
do_sync
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
write_status "idle" "Sync arrestato"
|
||||
log_info "Sync arrestato correttamente. Arrivederci!"
|
||||
Reference in New Issue
Block a user