Quando un job cron copia file su S3, il problema non è quasi mai il comando in sé. Il problema è tutto intorno: rete instabile, credenziali esposte, log ingestibili, exit code ambigui e processi che restano appesi.
In hosting capita spesso con backup, export CSV, rotazioni di file e sync di cache. Qui vediamo un caso concreto: uno script bash che carica archivi compressi su S3 con rclone, retry controllati, lock per evitare esecuzioni doppie e gestione errori adatta a produzione.
Warning: un automatismo “semplice” che gira come root e usa chiavi in chiaro diventa un rischio operativo. Prima di automatizzare, va deciso dove sta il segreto, chi può leggere i log e come fallisce il job.
Prerequisiti
Lo scenario è questo:
- server Linux con cron o systemd timer;
- rclone configurato verso S3, Backblaze o storage compatibile;
- cartella sorgente con archivi giornalieri;
- utente dedicato, non root;
- accesso a log di sistema o file di log dedicato.
Se usi un pannello come Plesk o cPanel, la parte di schedulazione si imposta anche da interfaccia.
In Plesk vai in Strumenti e impostazioni → Utilità pianificate. In cPanel vai in Cron Jobs.
Note: la logica di retry, trap ed exit code resta comunque nello script. Il pannello avvia solo il comando.
Step 1: crea un utente dedicato e isola i segreti
Il primo errore da evitare è riusare l’utente amministratore. Un job di backup deve avere permessi minimi. Deve leggere solo ciò che serve e scrivere solo nella destinazione prevista.
Su shell, crea un utente tecnico e una directory privata per configurazione e log.
sudo useradd --system --home /var/lib/backup-sync --shell /usr/sbin/nologin backupsync
sudo install -d -o backupsync -g backupsync -m 0750 /var/lib/backup-sync
sudo install -d -o backupsync -g backupsync -m 0750 /var/log/backup-sync
Salva il file di configurazione di rclone nella home dell’utente, non in una directory condivisa.
sudo -u backupsync rclone config file
# Output: /var/lib/backup-sync/.config/rclone/rclone.conf
Proteggi il file con permessi stretti.
chmod 600 /var/lib/backup-sync/.config/rclone/rclone.conf
Warning: non passare access key e secret nella riga di comando. Finiscono nella history, nei log del processo o in strumenti di monitoraggio.
Step 2: scrivi uno script con trap, lock e retry
Qui serve una struttura da produzione, non un one-liner. Lo script deve:
- bloccare esecuzioni concorrenti;
- registrare timestamp e exit code;
- interrompersi in caso di variabili non definite;
- riprovare solo gli errori transitori;
- uscire con codice chiaro per il monitoraggio.
Usa flock per impedire doppie esecuzioni. Usa trap per pulire file temporanei e loggare i fallimenti.
#!/usr/bin/env bash
set -Eeuo pipefail
LOCK_FILE="/run/backup-sync.lock"
LOG_FILE="/var/log/backup-sync/job.log"
SRC_DIR="/srv/exports/daily"
REMOTE="s3prod:archive-bucket/exports"
TMP_LIST="$(mktemp)"
cleanup() {
rm -f "$TMP_LIST"
}
on_error() {
local exit_code=$?
echo "$(date -Is) ERROR exit=$exit_code line=${BASH_LINENO[0]} cmd=${BASH_COMMAND}" >> "$LOG_FILE"
cleanup
exit "$exit_code"
}
trap cleanup EXIT
trap on_error ERR
exec 9>"$LOCK_FILE"
flock -n 9 || { echo "$(date -Is) WARN already running" >> "$LOG_FILE"; exit 0; }
find "$SRC_DIR" -maxdepth 1 -type f -name '*.tar.gz' -print > "$TMP_LIST"
rclone copy --files-from "$TMP_LIST" \
--retries 3 --low-level-retries 5 --retries-sleep 20s \
--transfers 2 --checkers 4 \
--log-file "$LOG_FILE" --log-level INFO \
"$SRC_DIR" "$REMOTE"
# Output: upload completato con exit code 0 oppure errore tracciato nel log
La combinazione set -Eeuo pipefail è utile, ma non basta. Senza trap perdi il contesto dell’errore. Senza flock rischi due job in parallelo e un carico inutile sullo storage.
Note: il blocco sopra usa rclone direttamente. Se preferisci PowerShell su Windows Server, la logica è simile: try/catch, Start-Process -Wait, file lock e exit code non zero su errore.
Step 3: rendi il comportamento prevedibile con timeout e log separati
In produzione un job bloccato è peggio di un job fallito. Imposta un timeout esterno, così il sistema ammazza il processo se resta appeso su rete o DNS.
Con cron, l’approccio più semplice è richiamare timeout dal comando schedulato.
timeout --signal=TERM --kill-after=30s 25m /usr/local/bin/backup-sync.sh
# Output: il job termina entro 25 minuti o viene forzato con TERM e poi KILL
Separare i log aiuta molto. Tieni un file per il log applicativo e un altro per l’orchestrazione. Il primo lo scrive rclone. Il secondo lo scrive lo script.
Un esempio di wrapper minimo in cron:
*/30 * * * * backupsync timeout --signal=TERM --kill-after=30s 25m /usr/local/bin/backup-sync.sh
Se usi un pannello, inserisci il comando completo nella riga cron. In Plesk e cPanel non serve inventare altro. Il punto è mantenere identico il comportamento in produzione e in test.
Perché non basta “|| exit 1”
Un semplice fallback cattura solo il fallimento finale. Non registra linee, comandi e punto di rottura. In un ambiente hosting, questo fa perdere tempo durante incidenti notturni.
Con trap ERR sai dove è caduto lo script. Con flock eviti la causa più banale ma frequente: il job precedente è ancora vivo.
Step 4: limita il danno con permessi, umask e variabili pulite
Molti script funzionano, ma lasciano superfici inutili. La parte di sicurezza non è decorativa. Serve a evitare che un altro utente legga file temporanei o che il job erediti ambiente sporco.
Aggiungi umask e una whitelist minima delle variabili.
umask 077
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
unset CDPATH
# Output: file creati con permessi privati e ambiente ridotto
Se il job deve leggere dati sensibili, evita directory world-readable. Se deve scrivere file temporanei, usa /run o /tmp con file unici e permessi stretti.
Warning: non salvare token S3 in uno script dentro /usr/local/bin con permessi 755. È un errore classico e ancora troppo comune.
Step 5: aggiungi un controllo preflight prima del trasferimento
Prima di copiare tutto, verifica spazio, connettività e presenza della configurazione. Così fallisci prima e in modo leggibile.
[ -r /var/lib/backup-sync/.config/rclone/rclone.conf ] || exit 10
[ -d "$SRC_DIR" ] || exit 11
command -v rclone >/dev/null || exit 12
# Output: il job si ferma subito con codice specifico se manca un prerequisito
Questo aiuta anche il monitoraggio. Un codice 10 per config mancante è molto più utile di un generico 1.
Se vuoi un controllo più forte, aggiungi una verifica di connettività verso l’endpoint S3, ma senza esporre chiavi nei log.
rclone lsd s3prod:archive-bucket >/dev/null
# Output: elencazione bucket senza errori di autenticazione o rete
Step 6: integra il job con monitoraggio e alert
Un job da produzione non deve solo girare. Deve anche parlare quando fallisce. La soluzione più semplice è un controllo sul codice di uscita.
Se usi systemd, puoi far partire lo script da service e timer. Il vantaggio è il logging unificato e i restart controllati. Se usi cron, aggiungi un alert esterno o un check su file di stato.
Un approccio pratico è scrivere l’ultima esecuzione e il risultato in un file dedicato.
STATUS_FILE="/var/lib/backup-sync/last-run.status"
echo "$(date -Is) OK" > "$STATUS_FILE"
# Output: file di stato aggiornato con timestamp e OK
Per il monitoraggio, puoi collegare un alert su mtime del file oppure su assenza di nuove righe nel log. In ambienti più maturi, esporta il check verso Prometheus o un sistema di synthetic monitoring.
Verifica finale
Prima di mettere lo script in produzione, fai tre test separati.
- Test 1: esegui lo script manualmente con un file piccolo.
- Test 2: blocca temporaneamente la rete o usa un endpoint sbagliato per verificare il retry.
- Test 3: lancia due istanze insieme e conferma che
flockne lascia partire una sola.
Controlla questi punti:
- il log mostra timestamp e codice d’uscita;
- il lock file impedisce doppie esecuzioni;
- le credenziali non compaiono in ps, history o log;
- il timeout interrompe processi appesi;
- il job termina con exit code coerente.
Esempio di controllo rapido:
tail -n 20 /var/log/backup-sync/job.log
echo $?
# Output: ultime righe del job e codice di uscita 0 oppure un errore specifico
Troubleshooting
1) flock: cannot open lock file /run/backup-sync.lock: No such file or directory
Causa: la directory /run non contiene il file o il servizio parte prima della creazione della cartella.
Fix:
sudo install -d -o backupsync -g backupsync -m 0750 /run
sudo touch /run/backup-sync.lock
sudo chown backupsync:backupsync /run/backup-sync.lock
# Output: il lock file diventa scrivibile dall’utente tecnico
2) rclone: failed to create file system for "s3prod:archive-bucket": no such remote
Causa: il remote non esiste nella configurazione dell’utente che esegue il job.
Fix:
sudo -u backupsync rclone config
sudo -u backupsync rclone listremotes
# Output: il remote s3prod compare nell’elenco dei remoti disponibili
3) ERROR : Failed to copy: context canceled
Causa: il comando è stato interrotto da timeout esterno o da una rete instabile durante il trasferimento.
Fix:
timeout --signal=TERM --kill-after=30s 40m /usr/local/bin/backup-sync.sh
# Output: più tempo disponibile al job, con stop controllato se resta bloccato
Conclusione
Un job ripetitivo funziona davvero solo se fallisce bene. Lock, trap, timeout, permessi stretti e log leggibili fanno la differenza tra automazione e roulette.
Il prossimo passo concreto è trasformare questo script in servizio systemd con timer, oppure in cron con alert esterno sul codice di uscita. Da lì puoi aggiungere retention, checksum e test di restore periodici.
Commenti (0)
Nessun commento ancora.
Segnala contenuto
Elimina commento
Eliminare definitivamente questo commento?
L'azione non si può annullare.