Riepilogo post nella categoria Linux
Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2016, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

I servizi di Cloud Storage, come ad esempio Dropbox o Google Drive si sono affermati nel corso degli anni come degli strumenti praticamente indispensabili per la informatizzazione dei processi aziendali, e per la condivisione di informazioni tra colleghi e professionisti.

L'unico grosso problema di questi servizi, è che con il crescere dell'operatività aziendale, con il crescere delle persone che utilizzano contemporaneamente il servizio, e con il crescere dell'esigenza di informatizzare tutti i processi aziendali, lo spazio di archiviazione diventa un fattore di criticità, perchè il prezzo di questi servizi aumenta considerevolmente aumentando lo spazio ( e il numero di utenti simultanei ) richiesto.

Giusto per fare un esempio, Google Drive costa circa 2 euro al mese per 100GB di spazio, e il piano tariffario subito successivo obbliga ad acquistare 1TB di storage al costo di circa 10 euro al mese.

Quindi, come si può implementare una soluzione più economica e più scalabile per cloudicizzare l'operatività?

Esiste un software opensource di nome OwnCloud, che mette a disposizione sia il software necessario per l'installazione del "core" su un server Linux, sia i client windows, mac e linux per collegare appunto il server alle proprie sincronizzazioni.

In più, una volta installato il Core sul proprio server, OwnCloud mette a disposizione una interfaccia grafica wia web davvero professionale e accattivante, che servirà all'amministratore per configurare l'ambiente di condivisione, per creare utenti e grupppi, e configurare le impostazioni di base come quelle del relay di posta per le notifiche.

Quindi, quali sono i passaggi per installare e configurare OwnCloud? Ecco una breve guida per l'installazione di OwnCloud

  • 1. Acquistare un server Linux economico con abbastanza spazio. Per questo consiglio caldamente l'acquisto di un server su Backupsy. Con circa 5.5 euro al mese si può acquistare un server da 250GB con una versione di Linux a piacimento. E' caldamente consigliato l'utilizzo di Debian o Ubuntu.
  • 2. Configurare il server LAMP ( Apache, Mysql, PHP ) oppure LEMP ( Nginx, Mysql, PHP ) per poter ospitare correttamente l'installazione di OwnCloud. Attenzione: in caso di utilizzo di Debian 7, l'installazione del server LAMP sarà un pò più complessa perchè il repository ufficiale di Debian 7 mette a disposizione PHP versione 5.4, mentre per l'installazione di OwnCloud è caldamente consigliabile utilizzare una versione di PHP maggiore o uguale alla 5.6, sebbene OwnCloud supporti i sistemi PHP dalla 5.4 in poi. Il fatto di avere una installazione LAMP con una versione di PHP più aggiornata è derivato dal fatto che gli aggiornamenti di PHP non solo correggono delle grosse falle di sicurezza, ma sono molto più performanti.
  • 3. Configurare PHP installando queste estensioni: Ctype, Dom, GD, Iconv, JSON, LibXml, mbstring, Posix, SimpleXML, XMLWriter, Zip, Zlib. Inoltre, è caldamente consigliato installare anche i moduli APC o APCu ( sono delle estensioni di Memory Caching per PHP ). Se avrete bisogno anche di funzionalità di anteprima dei file multimediali all'interno di OwnCloud, come ad esempio le immagini e i file video / audio, dovrete anche occuparvi dell'installazione dei moduli Imagick di PHP, FFMPEG e OpenOffice sul sistema.
  • 4. Configurare un VirtualHost con un dominio esistente e raggiungibile da server DNS ( ci servirà per la configurazione dell'HTTPS ). Ad esempio, se la vostra azienda ha un sito internet con nome di dominio www.aziendapincopallo.it, vi consiglio di creare un sottodominio, ad esempio cloud.aziendapincopallo.it, e configurare il VirtualHost sul nuovo server in modo che risponda a questo sottodominio. Attenzione alle configurazioni che permettono l'accesso diretto via indirizzo IP del server. I VirtualHost sono da configurare come si deve.
  • 5. Installare un certificato SSL gratis tramite l'utilizzo di Let's Encrypt. Nella fattispecie, dovrete utilizzare il tool certbot che si occuperà di configurarvi automaticamente il server Apache ( con il virtualhost configurato correttamente ). L'utilizzo di SSL è seriamente e caldamente consigliato perchè, in caso di assenza, le password di autenticazione tra i Client di sincronizzazione e il Server con il "core" di OwnCloud viaggerebbero in chiaro sulla rete Internet. Quindi, un qualsiasi man in the middle, anche con pochissima esperienza in hacking, potrebbe prendere il controllo del vostro ambiente Cloud, con tutte le conseguenze del caso.
  • 6. Completate l'installazione di OwnCloud andando con il browser sulla posizione di installazione, e configurare i parametri di connessione mysql. Ovviamente, non c'è neanche da ricordarlo, durante tutte le fasi di configurazione, usare sempre password CASUALI maggiori di 18 caratteri.
  • 7. Installare i Client di sincronizzazione sul vostro PC, Windows Mac o Linux che sia. Per la visualizzazione in mobilità, invece, consiglio l'utilizzo di una applicazione per Android che si chiama Solid Explorer. E' un file viewer per Android, che però permette di inserire delle "cartelle virtuali" come se esistessero all'interno del vostro smartphone. Tra le tante opzioni per la creazione di cartelle virtuali, tra cui Dropbox, Google Drive, Amazon S3, c'è anche la possibilità di inserire un server OwnCloud. Vi basterà quindi semplicemente digitare l'indirizzo di installazione ( in https ) del vostro OwnCloud, username e password, e avrete a disposizione tutti i vostri file.

E' chiaro che l'installazione e la configurazione sono fattibili solo da persone che hanno buone competenze in Linux e in amministrazione di sistemi LAMP, ma con un pò di tempo e pazienza, e leggendo qualche guida, è fattibile da chiunque.

Nel caso in cui tu stia valutando l'opportunità di usare OwnCloud per la tua azienda, e avessi bisogno di consulenza PHP a Torino o mysql database administrator a Torino, contattami usando il modulo presente in questo sito.

Backup di Mysql con mysqldump senza lock sulle tabelle

Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2016, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Effettuare il backup di uno o più database Mysql è un compito facilmente eseguibile con il tool mysqldump. Basta dargli in pasto qualche parametro, e il backup è pronto. Ad esempio, per effettuare il backup di DB1, DB2 e DB3:

mysqldump -u UTENTE -pPASSWORD --databases DB1 DB2 DB3 | gzip > mysql.sql.gz

Il comando creerà un archivio .gz contenente il dump in formato .sql dei vostri database. Però, c'è un problema: se volete eseguire mysqldump mentre tutti i servizi sono operativi, quindi ad esempio, su un server di produzione attualmente in uso di un sito web, lanciare il comando mysqldump implicherà un serio rallentamento, se non uno stato di blocco, di tutti i servizi che utilizzano mysql.

Per ovviare a questo problema, c'è la soluzione: bisogna lanciare mysqldump senza lock sulle tabelle.

Il comando qui di seguito vi permetterà di lanciare un dump completo di mysql senza particolari problemi su un server di produzione attualmente attivo e in uso, anche se dovesse avere un load molto elevato.

nice -15 mysqldump -u UTENTE -pPASSWORD --skip-add-locks --compact --quick --lock-tables=false --databases DB1 DB2 DB3 | gzip > mysql.sql.gz

Ho personalmente testato il tutto su un VPS Debian da 16GB di RAM, Quad Core, con doppio SSD, lanciando mysqldump su un database di circa 30GB di dati con circa 500 milioni di righe. Il risultato è stato un dump completo in 65 minuti, che non ha assolutamente comportato il blocco dei servizi attivi.

Questo perchè, oltre alle direttive --skip-add-locks e --lock-tables=false ho anche aggiunto --compact --quick ( meno output verboso sul dump ) e soprattutto ho usato nice, che è un semplice tool che modifica la priorità del processo che segue. La priorità di nice va da -20 ( urgentissimo ) a +19 ( fallo quando riesci ). Con nice -15, quindi, andiamo a dare molta meno priorità al processo di mysqldump, che verrà eseguito proprio quando non c'è null'altro da fare a livello di risorse del sistema.

Per finire, un'ultimo consiglio. I backup, in generale, *non* dovrebbero essere mantenuti e conservati sulla macchina principale, quella dalla quale abbiamo eseguito il backup. Il senso del backup è sempre lo stesso: se si rompe tutto sulla macchina A, devo possedere una macchina B che mi faccia da disaster recovery. Quindi, prendete l'abitudine di spostare i vostri backup verso altre macchine remote, meglio se siano preposte solo a contenere backup.

Il comando per inviare un file ad un server remoto, via SSH, è molto facile e potete integrarlo all'interno dei vostri script .sh preposti ai dump e backup ( leggere la nota in fondo alla guida )

sshpass -p 'PASSWORD' scp -l 3000 /file/del/backup UTENTE@SERVER_REMOTO:/directory/destinazione/

Con questo parametro, inviamo al SERVER_REMOTO il file /file/del/backup all'interno della cartella /directory/destinazione usando UTENTE, PASSWORD per l'accesso. In più, con la direttiva -l 3000 passata a scp, impostiamo un limite in kbit/s per l'invio del dump. Molto utile sui server di produzione, dove non bisogna assolutamente saturare la banda di upload.

Attenzione: il comando qui sopra deve essere utilizzato *solo* in fase di test dei sistemi di backup. A regime, bisogna *sempre* usare un fattore di autenticazione a chiave pubblica / privata nell'utilizzo dei tool via SSH come ad esempio scp. Ripeto, è assolutamente sconsigliato usare sshpass se non per testare i sistemi usando un comando veloce e mnemonico.

Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2016, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

You surely have experienced a lot of pain when handling your backups coming from the various servers you have online. A very important thing to remember is, in fact, to have backups of your live web servers, so if things go crazy, you have the "last resource".

Plus, backups should be kept for at least some days, so that if you find out a web server breach or something you haven't noticed before, you can roll back the situation to the backup of N-days before.

If you're running cPanel, getting automated backups is actually pretty easy and straightforward. With cPanel you can even have a default backup retention period of N days, and retain that backups directly on the main system. But, that retention period is relative to the HDD of the main server you're running on. And, another negative fact is, if your main cPanel server that keeps your 10-days old backups fails or gets destroyed by the sys admin?

This is why, in my opinion, the best thing to do is have an external backup server where you send, via FTP, every type of backup from the webservers you are currently running.

Having an external backup server also enables you to have more than one server to be backed-up. Personally, I run a backup server that accepts backups from 3 different cPanel servers sources.

To help your backup server, and to help you setting everything up quick, I've coded a little script, that can be cron-jobbed and run via PHP CLI, that reads the backup destination directory ( the one into which you make your remote cPanel FTP send into ) and organizes the files contained into it by day, purging the backups that are too old to be retained.

If you're looking at guides on how to turn on automated cPanel backup and FTP remote send, there are plenty of guides and walkthroughs. Google is you friend.

Let's have a look at the code:

<?php
	/*
	|--------------------------------------------------------------------------
	| PHP / CGI Automated "Backup File Rotation" script
	|--------------------------------------------------------------------------
	|
	| Version: 1.1.0
	| Author: Maurizio Fonte
	| Author URL: https://www.mauriziofonte.it
	| Description:
	|     This script ( that needs to be run via CLI e.g /usr/bin/php -q this_script.php ) serves as an automated backup rotation organization.
	|     In fact, it takes all subdirectories under the main BACKUP_DIR folder and copies them to another location in which these backup files will be stored and rotated.
	|     For example, consider these directories and their contents:
	|         
	|     BACKUP_DIR = /root/backup
	|
	|             /root/backup
	|                 |-> folder_a
	|                 |       -> folder_a_backup_file_1.tar.gz
	|                 |       -> folder_a_backup_file_2.tar.gz
	|                 |-> folder_b
	|                 |       -> folder_b_backup_file_1.tar.gz
	|                 |       -> folder_b_backup_file_2.tar.gz
	|                 |       -> folder_b_backup_file_3.tar.gz
	|                 |-> folder_c
	|                 |       -> folder_c_backup_file_1.tar.gz
	|
	|    For every execution day, the script will read the contents of the /root/backup folder, and re-organize the files under the directory /back_rot/
	|    The script will create this rotation structure:
	|
	|    BACKUP_ROTATION_DIR = /back_rot/
	|
	|        /back_rot/
	|             |-> 2016-01-01
	|                 |-> folder_a
	|                 |       -> folder_a_backup_file_1.tar.gz
	|                 |       -> folder_a_backup_file_2.tar.gz
	|                 |-> folder_b
	|                 |       -> folder_b_backup_file_1.tar.gz
	|                 |       -> folder_b_backup_file_2.tar.gz
	|                 |       -> folder_b_backup_file_3.tar.gz
	|                 |-> folder_c
	|                 |       -> folder_c_backup_file_1.tar.gz
	|             |-> 2016-01-02
	|                 ...
	|                 ...
	|
	|    After a backup file / folder in the BACKUP_ROTATION_DIR becomes "too old to survive" ( after BACKUP_RETENTION_PERIOD days ), the folder will be automatically purged.
	|    
	|    Please consider that, in order for this automated backup rotator to work *properly*, these conditions have to be met:
	|        1) Each day, some other script ( from a remote server, for example ) has to fill its own directory on BACKUP_DIR ( in the example provided, "folder_a", "folder_b", "folder_c" )
	|        2) This script needs to be run once per day ( nothing harmful will happen if you run this more than once per day, though. Simply, it will see there are no actions to do ).
	|        3) This script is meant to be executed via CLI
	*/
	@ini_set('max_execution_time', 300);
	@ini_set('memory_limit', '256M');
	date_default_timezone_set ( 'Europe/Rome' );
	define ( 'HOME_DIR', rtrim ( dirname ( __FILE__ ), '/' ) . '/' );
	define ( 'LOGS_DIR', 'logs/' );
	define ( 'BACKUP_DIR', '/BACKUPS/' );
	define ( 'BACKUP_ROTATION_DIR', '/BACKUPS_ROTATION/' );
	define ( 'BACKUP_RETENTION_PERIOD', 20 );
	define ( 'EXCLUDE_BACKUP_FILENAME', 'exclude_this_backup_filename.tar.gz' );
	define ( 'EXCLUDE_BACKUP_FOLDER', 'exclude_this_directory_name_while_parsing' );
	
	ob_start ();
	out ( '#############################################################################################' );
	out ( '#############                                                                   #############' );
	out ( '#############     Automated PHP/CGI BACKUP FILE ROTATION ALGORITHM  v1.1.0      #############' );
	out ( '#############       Copyright(c) 2016 Maurizio Fonte - mauriziofonte.it         #############' );
	out ( '#############                                                                   #############' );
	out ( '#############################################################################################' );
	out ( '###############                   ' . date ( 'Y-m-d H:i:s' ) . '                         ###############' );
	out ( '#############################################################################################' );
	
	// fase 0: se non esiste la cartella BACKUP_DIR, non possiamo fare ovviamente nulla...
	if ( ! is_dir ( BACKUP_DIR ) ) closenow ( true, 'This magic script can't do anything as long BACKUP_DIR does not exists ... actual BACKUP_DIR is "' . BACKUP_DIR . '"' );
	
	// fase 1: controllo che la directory dei backup giornalieri e dei backup rotation sia leggibile e scrivibile e mi pre-carico i suoi contenuti in un semplice array di file
	out ( ' ** Starting "' . BACKUP_DIR . '" recursive directory iterator...' );
	$backup_dir_contents = Array ();
	$objects = new RecursiveIteratorIterator ( new RecursiveDirectoryIterator ( BACKUP_DIR ), RecursiveIteratorIterator::SELF_FIRST );
	if ( $objects ) {
		foreach ( $objects as $name => $object ){
			if ( $name !== '.' && $name !== '..' ) {
				$name = realpath ( $name );
				if ( ! in_array ( $name, $backup_dir_contents ) && $name !== '/' && $name !== rtrim ( BACKUP_DIR, '/' ) ) $backup_dir_contents[] = $name;
			}
		}
	}
	
	// fase 1a: controllo che abbiamo realmente qualcosa da fare dentro $backup_dir_contents e creo le directory di destinazione, se non esistono
	if ( count ( $backup_dir_contents ) == 0 ) closenow ( true, 'Nothing found on "' . BACKUP_DIR . '" that can be eligible to a copy/paste! Is that directory empty?' );
	
	if ( ! is_dir ( BACKUP_ROTATION_DIR . '_placeholder' ) ) @mkdir ( BACKUP_ROTATION_DIR . '_placeholder', 0755 );
	if ( ! is_dir ( BACKUP_ROTATION_DIR . '_placeholder' ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );
	@touch ( BACKUP_ROTATION_DIR . 'test.txt' );
	if ( ! is_file ( BACKUP_ROTATION_DIR . 'test.txt' ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );
	@unlink ( BACKUP_ROTATION_DIR . 'test.txt' );
	
	// fase 2: ciclo di ricognizione del file tree della directory SORGENTE dei backup
	out ( ' ** Starting "' . BACKUP_DIR . '" folder accounts+files recognition...' );
	$files_found = 0;
	$accounts_found = 0;
	$backup_tree = Array ();
	$remember_backup_account_roots = Array ();
	foreach ( $backup_dir_contents as $i => $fullpath ) {
		$stripped_path = str_replace ( BACKUP_DIR, '', $fullpath );
		$chunks = explode ( '/', $stripped_path );
		if ( count ( $chunks ) == 1 ) {
			if ( ! array_key_exists ( $chunks[0], $backup_tree ) && $chunks[0] != EXCLUDE_BACKUP_FOLDER ) {
				$backup_tree[$chunks[0]] = Array ();
				$remember_backup_account_roots[] = $fullpath;
				$accounts_found++;
				continue;
			}
		}
		else if ( count ( $chunks ) == 2 ) {
			if ( ! array_key_exists ( $chunks[0], $backup_tree ) && $chunks[0] != EXCLUDE_BACKUP_FOLDER ) {
				$backup_tree[$chunks[0]] = Array ();
				$remember_backup_account_roots[] = $fullpath;
				$accounts_found++;
			}
			if ( is_file ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] ) && $chunks[1] != EXCLUDE_BACKUP_FILENAME ) {
				$mtime = filemtime ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] );
				$backup_tree[$chunks[0]][bdate($mtime)][] = BACKUP_DIR . $chunks[0] . '/' . $chunks[1];
				$files_found++;
			}
			else continue;
		}
		else if ( count ( $chunks ) == 3 ) {
			if ( ! array_key_exists ( $chunks[0], $backup_tree ) && $chunks[0] != EXCLUDE_BACKUP_FOLDER ) {
				$backup_tree[$chunks[0]] = Array ();
				$remember_backup_account_roots[] = $fullpath;
				$accounts_found++;
			}
			if ( is_file ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] . '/' . $chunks[2] ) && $chunks[2] != EXCLUDE_BACKUP_FILENAME ) {
				$mtime = filemtime ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] . '/' . $chunks[2] );
				$backup_tree[$chunks[0]][bdate($mtime)][] = BACKUP_DIR . $chunks[0] . '/' . $chunks[1] . '/' . $chunks[2];
				$files_found++;
			}
			else continue;
		}
	}
	out ( ' // Done working on "' . BACKUP_DIR . '": found ' . $accounts_found . ' eligible ACCOUNTS and ' . $files_found . ' eligible FILES to be copied into backup rotation directory' );
	
	// fase 3: per ogni elemento nell'array $backup_tree controllo nella directory dei backup rotation se ho quella data e quell'account "root" salvato
	out ( ' ** Starting "' . BACKUP_ROTATION_DIR . '" smart copy...' );
	foreach ( $backup_tree as $account_name => $date_to_filename ) {
		foreach ( $date_to_filename as $date_ymd => $filenames ) {
			foreach ( $filenames as $i => $filename ) {
				// prima di tutto, se non esiste ancora, creo la directory con la data come "livello zero"
				if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd ) ) {
					out ( ' --> Going to create a folder: ' . BACKUP_ROTATION_DIR . $date_ymd );
					@mkdir ( BACKUP_ROTATION_DIR . $date_ymd, 0755 );
				}
				if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );
				
				// poi, se non esiste ancora, creo la directory dell'account
				if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name ) ) {
					out ( ' --> Going to create a folder: ' . BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name );
					@mkdir ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name, 0755 );
				}
				if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );
				
				// in ultimo, se non esiste il file, lo copio
				if ( ! is_file ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name . '/' . $filename ) ) {
					out ( ' --> Going to copy a backup file: ' . $filename );
					$file_basename = pathinfo ( $filename, PATHINFO_BASENAME );
					if ( copy ( $filename, BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name . '/' . $file_basename ) ) {
						@unlink ( $filename );
					}
					else closenow ( true, 'Closing backup rotation now... the file copy of "' . $filename . '" to "' . BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name . '/' . $file_basename . '" failed with no apparent reason...' );
				}
			}
		}
	}
	
	// fase 4: creo l'albero delle cartelle/file sulla directory di rotazione backup
	out ( ' ** Starting "' . BACKUP_ROTATION_DIR . '" automated erase of backup files greater than ' . BACKUP_RETENTION_PERIOD . ' days old' );
	$backuprotation_dir_contents = Array ();
	$objects = new RecursiveIteratorIterator ( new RecursiveDirectoryIterator ( BACKUP_ROTATION_DIR ), RecursiveIteratorIterator::SELF_FIRST );
	if ( $objects ) {
		foreach ( $objects as $name => $object ){
			if ( $name !== '.' && $name !== '..' ) {
				$name = realpath ( $name );
				if ( ! in_array ( $name, $backuprotation_dir_contents ) && $name !== '/' && $name !== rtrim ( BACKUP_ROTATION_DIR, '/' ) ) $backuprotation_dir_contents[] = $name;
			}
		}
	}
	
	// fase 5: cancello tutti i file, sulla directory di backup, che sono più anziani della BACKUP_RETENTION_PERIOD ( ovvero, i backup troppo vecchi )
	$today_tstamp = strtotime ( date ( 'Y' ) . '-' . date ( 'm' ) . '-' . date ( 'd' ) . ' 00:00:00' );
	$backup_retention_deadline_tstamp = strtotime ( '-' . BACKUP_RETENTION_PERIOD . ' days', $today_tstamp );
	foreach ( $backuprotation_dir_contents as $i => $fullpath ) {
		// sicuramente il primo chunk è relativo alla data, per come è costruito l'albero di copia/incolla
		$stripped_path = str_replace ( BACKUP_ROTATION_DIR, '', $fullpath );
		$chunks = explode ( '/', $stripped_path );
		if ( count ( $chunks ) == 1 && $chunks[0] !== '_placeholder' ) {
			if ( preg_match ( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $chunks[0], $match ) ) {
				$date_of_folder_tstamp = strtotime ( $chunks[0] . ' 00:00:00' );
				if ( $date_of_folder_tstamp < $backup_retention_deadline_tstamp ) {
					out ( ' --> folder ' . $chunks[0] . ' needs to be purged, it's more than ' . BACKUP_RETENTION_PERIOD . ' days old...' );
					emptyDirectory ( BACKUP_ROTATION_DIR . $chunks[0], true );
				}
				else out ( ' --> folder ' . $chunks[0] . ' can be left on place... it's not time for its death ... for now ...' );
			}
			else closenow ( true, 'Damnit... folder ' . $fullpath . ' failed the preg_match check on the first chunk ( ' . $chunks[0] . ' )' );
		}
	}
	
	// fase 5: per ogni "$remember_backup_account_roots" devo svuotare la cartella perchè è già stata processata
	out ( ' ** Starting "' . BACKUP_DIR . '" automated erase of backup files that have been already copied in this session...' );
	foreach ( $remember_backup_account_roots as $i => $folder ) {
		emptyDirectory ( $folder );
	}
	
	// fatto!
	out ( '' );
	out ( '    yayyy!! everything done!' );
	out ( '' );
	out ( '    Copyright (c) Maurizio Fonte 2016 - https://www.mauriziofonte.it' );
	closenow ();
	
	function bdate ( $tstamp ) {
		return date ( 'Y-m-d', $tstamp );
	}
	
	function emptyDirectory ( $directory, $remove_parent = false ) {
		$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator ( $directory, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST );
		if ( $files ) {
			foreach ( $files as $fileinfo ) {
				$todo = ( $fileinfo -> isDir ( ) ) ? 'rmdir' : 'unlink';
				$todo ( $fileinfo -> getRealPath ( ) );
			}
			if ( $remove_parent ) rmdir ( $directory );
		}
	}
	
	function closenow ( $error = false, $error_string = null ) {
		
		if ( ! is_dir ( HOME_DIR . LOGS_DIR ) ) mkdir ( HOME_DIR . LOGS_DIR, 0755 );
		if ( ! is_dir ( HOME_DIR . LOGS_DIR . date ( 'Y-m' ) . '/' ) ) mkdir ( HOME_DIR . LOGS_DIR . date ( 'Y-m' ) . '/', 0755 );
		
		if ( $error && ! empty ( $error_string ) ) {
			out ( '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' );
			out ( '!! program detected an error and is going to be killed NOW !!' );
			out ( '!!' );
			out ( '!! ERROR_REASON = ' . $error_string );
		}
		
		$out = ob_get_clean ();
		file_put_contents ( HOME_DIR . LOGS_DIR . date ( 'Y-m' ) . '/' . date ( 'Y-m-d-H-i-s' ) . '.txt', $out );
		exit ();
	}
	
	function out ( $string ) {
		echo $string . chr(10);
	}
?>

The code is pretty straightforward. If your backups fall every day into /BACKUPS/name_of_server, then you make this script "read" the contents of the /BACKUP/ directory. It will take care of moving the backups into "BACKUP_ROTATION_DIR" and will make sure that backup files that are older than "BACKUP_RETENTION_PERIOD" will get deleted at the right day.

To make it run correctly, place this script into its own directory, because it will automatically create a "logs" folder for you. If things go bad, go check the script verbose output into the right log file. Then, make it run via a new cronjob, and make sure that the user that actually runs the PHP script has the necessary permissions to read/write into the backup rotation folder.

Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2016, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Su Debian capita che possano succedere dei problemi con il file system ext3 o ext4, specialmente se state lavorando su un server che viene usato principalmente per lo storage di file / backup di grosse dimensioni, e specialmente se esiste più di un processo simultaneo che scrive e/o rimuove questi file di grosse dimensioni.

Quindi, per ovviare al problema, consiglio di impostare il controllo automatico del file system durante il boot con il modulo fsck ( che è l'alter-ego del vetusto chkdisk su Windows ) in modo che l'hard disk di sistema non risulti ancora montato, e in aggiunta un cron per il reboot automatico giornaliero per fare in modo che questo controllo venga effettuato giornalmente.

Ovviamente, il riavvio automatico giornaliero non è ottimale ed è da tralasciare nel caso in cui stiate facendo girare un webserver o un server mysql, in quanto non vi permetterebbe di avere abbastanza storicità nell'utilizzo di mysql per effettuare delle migliorie nelle performance ( ad esempio con mysqltuner )

Ecco come attivare il fsck automatico ( guida basata su Debian ):

sudo nano /etc/default/rcS
# be more verbose during the boot process
VERBOSE=yes
# automatically repair filesystems with inconsistencies during boot
FSCKFIX=yes

Poi, attivate il logging dei messaggi di boot per avere più controllo sulle operazioni effettuate durante il boot:

sudo apt-get install bootlogd sudo nano /etc/default/bootlogd

Nel file /etc/default/bootlogd inserite questa riga:

BOOTLOGD_ENABLE=yes

Infine, attivate l'auto-reboot giornaliero con una riga nel crontab. Consiglio di effettuare questa operazione sempre con sudo o con i permessi di root.

sudo crontab -e
# REBOOT GIORNALIERO AUTOMATICO ALLE 4 DEL MATTINO
0 4 * * * /sbin/shutdown -r now

Avete finito. Per fare una prova, date il comando

shutdown -r now

E, una volta completato l'avvio, controllate il file di log su /var/log/boot.

Buon lavoro!