cPanel: backup completo tramite API e Python

| |

Esistono decine di plugin per WordPress che servono a effettuare (e spesso conservare) il backup del proprio spazio web in un posto sicuro, sia a pagamento che gratuiti. Io per anni ho utilizzato (e utilizzo ancora per diverse installazioni) BackUpWordPress, l’ho sempre reputato un buonissimo strumento che fa esattamente quello che gli chiedi, senza fronzoli e dritto al sodo. Il problema è che il plugin non viene aggiornato da un paio di anni e in questo campo un plugin “abbandonato” è un plugin potenzialmente pericoloso sul lungo andare. Perché allora non ripensare il modo di effettuare e conservare il backup per non andare incontro a facili grane?

È quello che ho fatto con qualche riga di Python e un compito programmato in Cron su una macchina DietPi che fa girare il mio NUC (Intel NUC 11 Performance kit (NUC11PAHi3) e Proxmox) e che si occupa di una serie di compiti legati a backup e non solo. È ciò che in parte facevo già con il Raspberry Pi e uno script bash che si collegava via FTP agli spazi web per recuperare i singoli backup dei database due volte al giorno, ma ho preso capito che un backup completo (file, database, configurazioni, ecc.) torna più utile nel momento del bisogno e se a occuparsene è direttamente cPanel, a me tocca esclusivamente scaricare il risultato e farlo sparire entro breve dallo spazio che mi mette a disposizione il provider.

HostingBackup

The inside of a hard drive is exposed.
Photo by benjamin lehman on Unsplash

Un repository GitHub che era nato con lo scopo di coprire la medesima esigenza per i due software più utilizzati (Plesk e cPanel) ma che almeno al momento è compatibile esclusivamente con cPanel, perché l’unico spazio web di cui usufruisco che utilizza Plesk pensa che le feature legati alle API e al backup debbano essere offerte a pagamento (cerca la voce “follia” sul vocabolario, potresti trovare il nome della società che presto conto di abbandonare).

L’indirizzo del repository GitHub è github.com/gioxx/HostingBackup.

Da qui in poi butto giù qualche riga per una rapida infarinatura su quello che ti serve sapere e come iniziare a mettere le mani in pasta, sentiti libero di usare l’area commenti per chiedere delucidazioni o fornire tu suggerimenti su come poter migliorare il tutto (anche se il posto migliore per farlo è GitHub stesso, aprendo una Issue).

API

cPanel mette a disposizione degli sviluppatori e degli amministratori di sistema una serie di API che possono essere utilizzate per impartire comandi al software, il tutto riportato e documentato all’indirizzo docs.cpanel.net/knowledge-base/security/how-to-use-cpanel-api-tokens.
Accedendo quindi gli strumenti di cPanel troverai la possibilità di gestire i tuoi token API alla voce Manage API Tokens. Qui ne potrai creare uno nuovo da dedicare agli script di backup, dovrai solo salvare il token generato in un posto sicuro perché una volta abbandonata la pagina di creazione, questo non sarà più leggibile e recuperabile (dovrai revocarlo e generarne uno nuovo).

Tempo di backup

Con il token disponibile potrai iniziare a parlare con le API di cPanel.
Nonostante esista un solo modo documentato per creare un backup completo del tuo account cPanel (file, database, ecc.), le possibili destinazioni sono invece due: il medesimo spazio disco che ospita già tutti i tuoi file (api.docs.cpanel.net/openapi/cpanel/operation/fullbackup_to_homedir) oppure uno spazio FTP alternativo, di terza parte (api.docs.cpanel.net/openapi/cpanel/operation/fullbackup_to_ftp).

Io ho inizialmente giocato con la seconda opzione e ho fatto qualche esperimento mettendo in ascolto un piccolo server FTP in casa, solo per poter ricevere i backup, ma poi ho deciso di abbandonare l’idea perché potenzialmente insicura e da gestire, scegliendo di salvare tutto nella cartella profilo e organizzandomi per andare a recuperare il file generato da cPanel e cancellarlo al termine del download, per poterlo conservare solo in un mio spazio sicuro.

Le operazioni sono quindi queste:

  1. chiedere a cPanel di lanciare un backup completo dell’account e del suo contenuto
    (già qui dovrai scegliere se farlo in locale o su FTP terza parte, io ho optato per la prima e articolo e script si basano su questa scelta).
  2. Al termine della creazione del backup occorrerà scaricarlo, conservarlo in un posto sicuro e poi cancellarlo dalla sorgente (quindi lo spazio disco che cPanel riserva alla mia cartella del profilo).

Le API di cPanel permettono – almeno nella teoria (api.docs.cpanel.net/openapi/cpanel/operation/list_backups) – di richiedere la lista dei backup disponibili sul mio spazio disco (quello messomi a disposizione dal provider), eppure questa cosa non sembra funzionare proprio con tutti i fornitori di hosting condiviso. Ho scritto lo script bckGet.py ma questo non mi ha portato ai risultati sperati, mi ha costretto quindi a inventare qualcosa in grado di aggirare l’ostacolo e scaricare il mio file di backup solo al termine della creazione.

Questo passaggio è fondamentale perché nel momento in cui io richiedo un backup completo a cPanel, il file di backup viene immediatamente creato ma continuerà a crescere di dimensione fino a quando ci saranno file da inserire al suo interno. Se io faccio partire il download del file di backup prima che questo venga completamente riempito, otterrò un file certamente non corrotto, che si può aprire, ma che sarà completamente inutile perché incoerente rispetto alla situazione lato server, mi porterò a casa solo una parte dei miei file. Più è ricco il contenuto della mia cartella di profilo, più questo controllo sarà assolutamente cruciale.

L’idea quindi è tutto sommato semplice: faccio un controllo di esistenza del file di backup, se questo esiste inizio a memorizzarmi (su una variabile temporanea) la sua occupazione su disco e attendo un certo numero di secondi prima di controllare se lo stesso identico file di backup occupa ancora la stessa dimensione su disco.
Se la risposta è positiva vorrà dire che il file si è stabilizzato, potrò quindi scaricarlo e poi cancellarlo dalla sorgente in tutta tranquillità. Se l’occupazione sarà invece variata durante l’intervallo di tempo speso nell’attesa, vorrà dire che cPanel sta ancora inserendo al suo interno dei file e che io dovrò nuovamente far passare un ciclo di attesa prima di ripetere la verifica:

while True:
        try:
            backup_file = get_first_backup_file(ftp)
            if backup_file:
                if print_backup_file != backup_file:
                    print_backup_file = backup_file
                    print(f"{backup_file} found.")

                # Verifico l'occupazione su disco del file
                file_size = ftp.size(backup_file)
                if file_size == previous_file_size:
                    print(f"Stable file size, exiting the loop and download {backup_file} to {destination_folder}.")
                    break
                else:
                    countdown(time_to_wait,"File size has changed, waiting") # Attendo XX secondi prima di ricontrollare se esiste il file di backup e se è stato più modificato dal server
                    previous_file_size = file_size

            else:
                # print("No backup file found, waiting before rechecking.")
                countdown(15,"No backup file found, waiting")

        except Exception as e:
            print("Error:", e)

È la porzione di codice che fa parte dello script bckFull.py e che si occupa proprio del controllo di cui ti ho parlato poco sopra. Sarà lo stesso script ad aver dapprima lanciato la richiesta per la creazione del nuovo backup completo via cPanel (e attenderà blocchi da 15 secondi fino a quando non vedrà comparire il file di backup che generalmente si troverà nella cartella principale dell’account cPanel, con un nome che inizierà per backup e terminerà con l’estensione tar.gz (vedi qui). A questo ho aggiunto un banale controllo per evitare di generare un nuovo backup completo se prima non è stato scaricato e cancellato un backup precedente: github.com/gioxx/HostingBackup/blob/main/bckFull.py#L77.

Lo faccio principalmente per due motivi: non sprecare inutilmente prezioso spazio messo a disposizione dal provider ed evitare di perdermi un file che sicuramente conterrà un backup del mio account cPanel ma che ancora non ho portato via, in una cartella sicura di mia scelta (cancellandolo quindi dalla sorgente). Se però vuoi scavalcare questo controllo e avere più file di backup ospitati anche contemporaneamente sul tuo spazio web, allora dovrai modificare la variabile check_existent_backup che si trova in riga 9 dello script. Da True portala a False, il controllo così verrà ignorato.

Mettendo insieme il tutto lo script ti permetterà quindi di creare il backup, attendere che questo sia consistente e in linea con tutti i file che possiedi sul tuo spazio web, e infine lo scaricherà in una cartella a tua scelta. Per fare ciò e per connettersi al giusto server FTP sul quale trovare il pacchetto tar.gz ho pensato di utilizzare un file JSON che dovrai compilare con i campi necessari e che potrai popolare anche con più di un dominio da tenere sotto backup. Un file di esempio è disponibile all’indirizzo github.com/gioxx/HostingBackup/blob/main/ftp_config_sample.json e per funzionare (in locale sulla tua installazione) dovrà prima essere rinominato in ftp_config.json.

{
    "cpanel1": {
        "host": "contoso.com",
        "backup_local_dest_folder": "/mnt/cPanelBackup",
        "cpanel_api_token": "A1BCDEFGHILMN23OPQRSTUVZ01234567",
        "cpanel_username": "contosoadmin",
        "ftp_password": "Your-Fantastic-Password",
        "ftp_username": "backup@contoso.com",
        "mail_to_notify": "hello@contoso.com",
        "time_to_wait": 60
    },
    "cpanel2": {
        "host": "ftp.lab.com",
        "backup_local_dest_folder": "/mnt/cPanelBackup",
        "cpanel_api_token": "A1BCDEFGHILMN23OPQRSTUVZ01234567",
        "cpanel_username": "labadmin",
        "ftp_password": "Your-Fantastic-Laboratory-Password",
        "ftp_username": "backup@lab.com",
        "mail_to_notify": "hello@lab.com",
        "time_to_wait": 30
    }
}

cpanel1 e cpanel2 sono nomi che assegnerai a ciascun sito web da tenere sotto backup, per poterli facilmente richiamare da riga di comando quando andrai a eseguire bckFull.py (ma non solo).
Dovrai specificare l’indirizzo host da raggiungere (contoso.com o ftp.lab.com nell’esempio poco sopra), la cartella di destinazione all’interno della quale scaricare i file di backup che eseguirà cPanel (e dovrà quindi trattarsi di una cartella che il tuo PC che esegue bckFull.py possa raggiungere in lettura e scrittura, nell’esempio sopra si tratta di /mnt/cPanelBackup), il token API generato precedentemente su cPanel (e lo username che userà lo script per collegarsi al giusto URL), l’utente e la password dello spazio FTP, l’indirizzo di posta elettronica da notificare (cPanel potrà mandarti un’email nel momento in cui verrà richiesto di effettuare il backup completo tramite API) e l’intervallo di tempo da attendere tra un controllo e l’altro in attesa che il file di backup sia completo.

Io ti propongo un file JSON di esempio che contiene due siti web da tenere sotto backup, tu puoi specificarne quanti ne vuoi.

Sono pronto, partiamo!

Ok, credo di averti detto più o meno tutto quello che ti serve sapere. Io sto usando questi script già da diversi mesi e ho in testa un paio di modifiche almeno per renderli ancora più “personalizzabili“, al momento però funzionano, fanno il loro mestiere e mi consentono di vivere sonni un po’ più sereni (e sì, ho anche già testato dei restore completi che sono andati a buon fine). Quello che dovrai fare quindi è semplice:

git clone https://github.com/gioxx/HostingBackup.git

(direttamente sulla macchina sulla quale hai intenzione di usare il tutto, e se non hai Git ma hai solo Python allora potrai semplicemente scaricare un file ZIP del repository più aggiornato a questo indirizzo: go.gioxx.org/hostingbackup-getzip, a quel punto ti basterà scompattarlo).

Modifica ora il file JSON (ftp_config_sample.json dovrà diventare ftp_config.json e dovrai compilare i dati richiesti), salvalo e installa il necessario per Python:

pip install -r requirements.txt

Salvo errori sei ora pronto a usare lo script di backup e download. Lancia quindi da riga di comando:

python3 /bckFull.py NomeMioSitoWeb

Dove NomeMioSitoWeb è il nome che hai scelto di dare al set di dati utili per il backup (quello che nel mio file di esempio JSON trovi come cpanel1 o cpanel2). Salvo errori lo script contatterà il tuo cPanel chiedendogli di creare il file di backup completo, attenderà che questo venga generato e verificherà a ogni intervallo di secondi (stabiliti dalla variabile time_to_wait del file JSON) se l’occupazione su disco si è stabilizzata. Lo scaricherà nella cartella da te scelta e al termine lo cancellerà dallo spazio FTP.

A video ti verrà sempre mostrato tutto, aggiornamenti di stato, avanzamento del download, qualsiasi cosa possa tornarti utile per non tenerti sulle spine :-)

Il gioco è fatto. Ora non dovrai fare altro che programmare l’esecuzione dello script anche senza la necessità che questo venga presidiato.
Io l’ho fatto tramite Crontab UI (te ne parlerò entro breve) e ne ho approfittato per ottenere anche un’email al termine dell’esecuzione script che possa informarmi dandomi le stesse informazioni che vedrei a monitor eseguendo manualmente lo script, fa coppia perfetta con quella che mi viene inviata automaticamente dal cPanel quando lancio il backup via API.

Extra: in caso di problemi

In casi un po’ più rari (account cPanel particolarmente ricchi di file che quindi generano un file di backup più importante) ho notato che lo script bckFull.py può riscontrare qualche difficoltà sul controllo di consistenza del tar.gz e tende a far partire il download prima del previsto. Due sono i miei consigli: il primo è quello di aumentare i secondi di attesa (variabile time_to_wait del file JSON, ricordi?) tra un check e l’altro, il secondo è quello di separare le operazioni di creazione del backup via API e download del risultato.

Per farlo ho creato bckCreate.py e bckDownload.py. Forse facile intuire che il primo script si occuperà di richiedere il backup via API a cPanel; il secondo invece si occuperà di collegarsi allo spazio FTP, verificare l’esistenza del backup e quindi effettuare download e cancellazione del file sulla sorgente al termine. Separando le due operazioni e programmandole a distanza forzata l’una dall’altra (se dopo diversi test ti accorgi che la creazione del file tar.gz di backup impiega una decina di minuti in tutto, allora è ragionevole ipotizzare che impostare la partenza del download 20 minuti dopo la richiesta di creazione sia più che sufficiente) ci sono scarsissime probabilità che il processo non vada a buon fine, risolvendo così il problema e mettendoci una giusta pezza.

Tutto rimane invariato rispetto al funzionamento dello script unico, compreso l’utilizzo del file JSON per le impostazioni. Semplicemente per richiamare le due operazioni separatamente dovrai lanciare i due script così:

python3 /bckCreate.py NomeMioSitoWeb
python3 /bckDownload.py NomeMioSitoWeb

Anche in questo caso programmare l’esecuzione degli script tramite Crontab UI è stato davvero semplice, un paio di clic di mouse e qualche battuta sulla tastiera (ma nulla che non si possa già fare da riga di comando e Crontab).

Migliorabile? Assolutamente sì, e ho anche qualche idea in merito, spero di poterla sviluppare in un futuro abbastanza vicino alla data di pubblicazione di questo articolo :-)

In caso di problemi e/o suggerimenti tecnici sullo sviluppo ti prego di aprire una Issue su GitHub o direttamente una Pull da integrare al codice originale. Se invece vuoi discutere in maniera più leggera dello script sai benissimo che l’area commenti è a tua totale disposizione, poco più giù rispetto all’articolo 😬✌️

#KeepItSimple


Immagine di copertina Artturi Jalli on Unsplash

Correzioni, suggerimenti? Lascia un commento nell'apposita area qui di seguito o contattami privatamente.
Ti è piaciuto l'articolo? Offrimi un caffè! ☕ :-)

L'articolo potrebbe non essere aggiornato

Questo post è stato scritto più di 5 mesi fa, potrebbe non essere aggiornato. Per qualsiasi dubbio ti invito a lasciare un commento per chiedere ulteriori informazioni! :-)

Condividi l'articolo con i tuoi contatti:
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Commenti
Inline Feedbacks
View all comments