Zip files with PHP and upload to Dropbox

Important note: Dropbox API v1 is deprecated and will be disabled on June 28, 2017.
I’ll update this post as soon as I have time.

I needed a quick and dirty cheap backup solution for ever growing directory of MySQL backups.

Some were already backed up on Dropbox. So, I decided to throw together a PHP script which could upload those sql files without having to rely on Dropbox client. Then use cron to launch the script after daily MySQL dump. Few web searches later I found DropboxUploader on GitHub but I didn’t like its approach. Official API seems more elegant as it uses OAuth 2 authentication.

Requirements

First of all, take a look at introduction on using Dropbox API with PHP, then download Dropbox SDK for PHP.

Register an app on Dropbox App Console to get API key and secret. Paste values into config.json.

{
"key": "INSERT_APP_KEY_HERE",
"secret": "INSERT_SECRET_HERE"
}

Include Dropbox API autolaoder and define dbx alias for \Dropbox namespace:

# Include the Dropbox SDK libraries
require_once __DIR__."/dropbox-sdk/lib/Dropbox/autoload.php";
use \Dropbox as dbx;

I didn’t bother with the authentication part because I will use the “app” only for this script. I just generated access token in App Console and hardcoded it in my script.

$accessToken = 'NotARealTokenXQAAAAAAAAABmKadnX7wFdbUyIllvKphLHSXPqBNotARealToken';

createZip function

To make things easier, I wrapped the compression code in a function.

/**
 * Add array of $files to $output_file archive and deletes the
 * files after successful archiving
 * @param $files array Array of files to zip
 * @param $output_file string Archive path
 * @return string|boolean Path of zip file or FALSE if anything failed
 */
function createZip($files, $output_file){

	$zip = new ZipArchive();

	if ($zip->open($output_file, ZipArchive::CREATE)!==TRUE) {
		exit("cannot open '$output_file'".PHP_EOL);
	}
	$to_delete = array();
	foreach($files as $file){
		if(!file_exists($file) || !is_readable($file)){
			continue;
		}
		if($zip->addFile($file, basename($file))){
			$to_delete[] = $file;
		}
	}
	$status = $zip->status;
	$success = $zip->close();

	// Delete files after ZipArchive::close() because ZipArchive locks files
	if($success)
	foreach($to_delete as $file){
		@unlink($file);
	}

	return $status == 0 && $success ? $output_file : FALSE;
}

Load Dropbox client and set access token

$appInfo = dbx\AppInfo::loadFromJsonFile(__DIR__."/config.json");
$dbxClient = new dbx\Client($accessToken, "MySQLbumps-BACKUP/1.0");

And the main part

Zipping and uploading

// define some required paths
define('TEMP_DIR', sys_get_temp_dir());
// Local backup location. Change path as needed
define('BACKUPS_DIR', '/var/backup/mysql/');

// Find latest file and upload so that there is at least one fresh backup
$latest = glob(BACKUPS_DIR.'*_'.date('Y-m-d').'*.sql');
$latest_file = reset($latest);

if($latest_file && file_exists($latest_file)){
	echo "Found latest backup: '".basename($latest_file)."'".PHP_EOL;
	$temp_zip = TEMP_DIR.'latest_backup.zip';
	// delete old zip file if it exists otherwise the file will be added to that zip
	if(file_exists($temp_zip)){
		@unlink($temp_zip);
	}
	$upload_file = createZip(array($latest_file), $temp_zip);
	$f = fopen($temp_zip, "rb");
	$result = $dbxClient->uploadFile('/MySQL backups/latest_backup.zip', dbx\WriteMode::force(), $f);
	fclose($f);
	if($result)
		echo "Uploaded ".basename($temp_zip).PHP_EOL;
}

// Check if there are any files from past month
$date_part = date('Y-m', strtotime('last month'));
$search = BACKUPS_DIR.'*_'.$date_part.'*.sql';
$files = glob($search);
if(empty($files)){
	echo "Nothing to do: no files match '$search'".PHP_EOL;
	exit(0);
}

$zip_file = 'Backup_'.$date_part.'.zip';
echo("Creating $zip_file file for MySQL dumps from last month".PHP_EOL);
$upload_file = createZip($files, BACKUPS_DIR.$zip_file);
if(!$upload_file || !file_exists($upload_file)){
	$msg = 'Could not create zip archive';
	error_log($msg);
	exit($msg);
}

echo("Uploading $zip_file to Dropbox\n");
$f = fopen($upload_file, "rb");
$result = $dbxClient->uploadFile('/MySQL backups/'.$zip_file, dbx\WriteMode::force(), $f);
fclose($f);

I have set the script to run right after nightly MySQL dump. Now I can sleep in peace knowing that I’ll have all backups from previous months and one from last night if anything goes wrong.

Have any suggestions? Add them in comments sections.

Design, development and other nonsense