Backups are now “pushed” to where they are stored, rather than “pulled”. This means Cameo can offer more control over them. Administrators control backups in admin → backups.

Background

While Cameo actually makes its backups, previously a separate backup server initiated the process (daily). That asks Cameo to make a backup. Then it downloads (pulls) the file produced from Cameo, so creating a copy on the backup server. The backup server took care of then copying the file to any third-party storage which you nominated to receive your backups.

The backup server needed an upgrade, so I have taken the opportunity to change how Cameo initiates its backups.

Also, the backup server used a little utility called GDrive to transfer backup files to Google Drive. Unfortunately, Google has withdrawn the method GDrive used to authenticate with Google, and their terms of service prohibit storing backup files, so it is no longer possible to use it. So we need a new method.

Existing live installations have the new method turned on. In most cases, any third-party backups that were already happening are also included.

“Push” instead of “pull”

Now, Cameo starts the backup for itself (daily) and copies (pushes) the file to the backup server and any other destinations you may choose. I no longer need to arrange this with you manually.

Backup file format and the six-week cycle of full, accumulative and incremental backups have not changed at all. Cameo encrypts backup files. Obtain the encryption password with the get the password link in admin → backups. When you add your own destinations, please make a note of this now. If you leave it until you need a backup, it may be too late!

If you have no backup destinations set up in admin → backups, Cameo does not make backups at all.

When there is only one destination, it should normally be Cameo’s Backup Server (Fig 1).

Fig 1: Backup destinations

Add an additional backup destination with the new button (Fig 1). Give it a name (Fig 2: 1), and choose its type (which third-party service it should use to copy files to) from the menu provided (Fig 2: 2).

Fig 2: New backup destination

Click credentials for the newly created backup destination to provide the information the third-party service needs to allow Cameo to connect to it (Fig 3). These vary by service, and the box that pops up explains where to get them from. Fig 4 shows the credentials box for Backblaze, for example.

Use the test link once you have entered your credentials to check they work.

Fig 3: Credentials
Fig 4: Backblaze credentials box

Backup destination services available

Cameo currently provides several backup destination services. Some of these are proprietary services. Others are generic methods by which many servers offer access. Usually, Cameo deletes backup files from third-party services after 90 days so you don’t run out of storage. That is enough to store two complete backup cycles.

  • Cameo backup server: all Cameo installations that require backups should use Cameo’s own backup server. Its credentials are built-in: you don’t have to provide any. Cameo backup server stores files more or less indefinitely.
  • Backblaze B2 cloud is a cloud storage provider particularly designed with this kind of application in mind. It provides 10GB free (though there is a $0.01 charge per GB to get the data out again and it is now free to download the data up to 3x your monthly upload volume, after which there is a fee). That should be ample for two complete backup cycles for most Cameo installations. Hopefully, you should never have to download the data! It is probably the easiest of the commercially-provided services to set up.
  • Cloudflare R2, better known for its market-leading content delivery network which speeds website access, also offers a cloud storage product, R2. While this is not free, and you are required to provide credit card details, you do get a free 10GB allowance, which will usually be plenty to store two sets of backups.
  • Dropbox is better known than Backblaze. While it is popular, it only offers 2GB of storage free, so unless you already have a suitable Dropbox account, you’d only be able to use it free for smaller Cameo installations.
  • HTTPS file upload is similar to how you upload files to a server from a form in a web page. Cameo can, in effect, behave as such a form. See below for technical details for the server side.
  • SCP/SSH, sometimes known as SFTP, is how you log in to most accounts on a Linux server remotely. Cameo can use this method to log in and copy over its files to such an account. It uses a key-pair to do so, rather than a password. You may well already have a SSH account on the server where your website is hosted, in which case this would be an ideal destination. It is probably the easiest destination to set up. With some port-forwarding in your router, you could use a Raspberry Pi at home to make use of this method.
  • Synology C2 is a proprietary cloud storage product. It has a generous free 15GB tier, no payment method required, which will be plenty to store two sets of backups.
  • WebDAV is an extension to HTTP which allows manipulation of a file store on a remote server. It is fairly easy for someone technically-knowledgeable to set up a WebDAV server using off-the-shelf Apache web server modules. It is also offered by a number of open source cloud storage products such as ownCloud and Nextcloud (so you could run your own cloud storage solution rather like Dropbox).

We don’t offer Google Drive as a backup destination as its terms of service do not allow it. We also don’t offer the two largest providers, Amazon S3 and Google Cloud, as they don’t have a free tier. Amazon S3 would be easy to add, if you really want to pay for storage!

Caveat

Cameo is usually hosted at Mythic Beasts. So if you nominate a self-hosted backup destination which is also at Mythic Beasts, it would be wise to consider whether the storage overlaps. This especially applies to hosted Raspberry Pis which all share a common disk. If you put your backup copy on the same server, or in the same data centre, there is always a risk that both may be destroyed at the same time by some catastrophe. Cameo’s backup server does not overlap with any destination you may choose.

HTTPS file upload, receiver scripts

Highly technical! If you don’t understand the following paragraph, this section is not for you.

The following simple scripts can serve as a basis for receiving files on a HTTPS file upload destination, in binary or text (multipart/form-encoded) respectively. This uses PHP, either with the Apache PHP module or FCGI.

Each assumes the Apache configuration requires a Basic Auth username and password for authentication, and uses HTTPS. You may also want to limit access to the IP of your Cameo installation. Consider also running the server on a non-standard port.

You will need to increase the maximum upload size (post_max_size setting) and maximum file size (upload_max_filesize setting) allowed by PHP from their 8MB defaults, either in php.ini, .htaccess, or the php-fpm pool file. A full backup can exceed 1GB on larger installations. Because of the standard way the text method encodes the data, the upload is at least 30% larger than the actual file size.

The backup destination appends to the URL as query parameters:

  • client: the domain of the sender (which could potentially form part of the path to the destination file repository)
  • filename: the name of the file (which, with suitable sanitisation, you might use to name the file in the destination file repository
  • binary: either binary or text according to whichever was provided in the credentials (which would allow you to write a single script which receives either).

binary

<?php

header('content-type: text/plain');

function oops($s) {
  http_response_code(400); // bad request
  error_log("400 error because '{$s}'");
  echo $s, "\n";
  exit;
}

$client = $_GET['client'] ?? '';
if (empty($client)) { oops("No client specified"); }
if ($client[0] == '.') { oops("Invalid client '{$client}' (starts with dot)"); }
if (strpos($client, '/') !== FALSE) { oops("Invalid client '{$client}' (contains slash)"); }

$filename = $_GET['filename'] ?? '';
if ($filename == '') { oops("Empty filename"); }
if ($filename[0] == '.') { oops("Invalid filename '{$filename}' (starts with dot)"); }
if (strpos($filename, '/') !== FALSE) { oops("Invalid filename '{$filename}' (contains slash)"); }
$suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

$notpermittedsuffixes = ['php','pl','js', 'exe'];
if (in_array($suffix, $notpermittedsuffixes)) {
  oops("File type '{$suffix}' not permitted");
}

$path = '/path/to/file/repository';
if (! file_exists($path)) { mkdir($path); }
$path .= "/{$client}";
if (! file_exists($path)) { mkdir($path); }
$path .= "/{$filename}";
if (file_exists($path)) {
   oops("file already exists on backup server '{$filename}'");
}

if (! copy('php://input', $path)) { oops("cannot save '{$filename}'"); }

echo "ok\n";

text

<?php
header('content-type: text/plain');

function oops($s) {
  http_response_code(400); // bad request
  error_log("400 error because '{$s}'");
  echo $s, "\n";
  exit;
}

if (empty($_FILES)) { oops('Empty upload'); }

$client = $_GET['client'] ?? '';
if (empty($client)) { oops("No client specified"); }
if ($client[0] == '.') { oops("Invalid client '{$client}' (starts with dot)"); }
if (strpos($client, '/') !== FALSE) { oops("Invalid client '{$client}' (contains slash)"); }

foreach($_FILES as $file) {
  if (! isset($file['error'])) { oops("No error report"); }

  switch ($file['error']) {
    case UPLOAD_ERR_OK:
      break;
    case UPLOAD_ERR_NO_FILE:
      oops('No file sent');
    case UPLOAD_ERR_INI_SIZE:
    case UPLOAD_ERR_FORM_SIZE:
      oops('File size limit exceeded');
    default:
      oops('Unknown error');
  }

  $filename = pathinfo($file['name'] ?? '', PATHINFO_BASENAME);
  if ($filename == '') { oops("Empty filename"); }
  if ($filename[0] == '.') { oops("Invalid filename '{$filename}' (starts with dot)"); }
  if (strpos($filename, '/') !== FALSE) { oops("Invalid filename '{$filename}' (contains slash)"); }
  $suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

  $notpermittedsuffixes = ['php','pl','js', 'exe'];
  if (in_array($suffix, $notpermittedsuffixes)) {
    oops("File type '{$suffix}' not permitted");
  }

  $path = '/path/to/file/repository';
  if (! file_exists($path)) { mkdir($path); }
  $path .= "/{$client}";
  if (! file_exists($path)) { mkdir($path); }
  $path .= "/{$filename}";
  if (file_exists($path)) {
     oops("file already exists on backup server '{$filename}'");
  }
  if (! move_uploaded_file($file['tmp_name'], $path)) { oops("cannot save '{$filename}'"); }
}

echo "ok\n";