Giter Club home page Giter Club logo

proxmox-encrypted-remote-backup's Introduction

Proxmox Encrypted Remote Backup

What is it?

Proxmox Encrypted Remote Backup (PERB) is a vzdump hook script written in Python. Once the Backup job has finished successfully the script gathers the backed up files (.vma and .log) and puts them in encrypted tar files and then uploads it to an rclone remote destination (currently Google Drive is tested and supported).

Design choices & Why?

  1. I wanted an excuse to learn python3.
  2. Although I don't have a reason not to, I didn't want to trust rclones encryption, instead PERB uses gpg which is well established and trusted. PERB is small enough that you can validate the code your self and make sure I´m not getting up to any funny business.
  3. I wanted as little information leak as possible to the cloud storage provider, this solution only makes the numeric VM-id (not VM-name) and the backup timestamp available to the provider.
  4. I was uncomfortable streaming incomplete files to remote destinations, these files might need cluster level access which can be unreliable on remote destinations. Because of this encryption is done locally and then uploaded to the remote.
  5. G Suite currently offers potentially unlimited storage, making it a great remote backup
  6. PERB is written to log a lot, most of this can be seen directly in the Proxmox GUI when you look at the backup task, even more in $logFile.
  7. If PERB runs in to trouble, in most cases it will exit with an error that will be clearly visible in the Proxmox task list.

How?

The script is executed by a Proxmox backup job several times (phases) during the backup job, at every execution it´s given information about the backup job which is collected in a job file.

Below several $variables are mentioned, these are found in ProxmoxEncryptedBackupSettings.py Encryption is done with default gpg ciphers which on my machine is AES256. The encryption/upload can be done multi-threaded by setting $threads, but it´s not recommended as in most cases the speed of $gpgOutputDirectory will be your bottle neck.

Phases

There are four main phases in a Proxmox backup:

  1. job-start, this is the first phase. The job performs all the backup phases
  2. backup-start, backup-stop, these phases are triggered once for every VM backup.
  3. job-end, the job has ended successfully, all VM backups are done, now the magic happens.

Flow of events

Below is a flow of events, it doesn't cover absolutely everything but the resulting logs ($logFile) do if you are interested.

  1. The phase "job-start" is reached and the hook script is run for the first time, a job file is created in the $jobFolder. The job file is a json file that will contain metadata about the backup jobs execution.
  2. backup-start, backup-stop, a VM is backed up, metadata such as paths to the backup files are put in the job file.
  3. job-end phase, all the VM backups are complete, the job file is parsed to retrieve all the backed up files (.vma and .log)
  4. For every pair of .vma and .log file tar is used to create an uncompressed archive which is piped to gpg.
  5. gpg encrypts the file using a certificate you have previously setup and places them in $gpgOutputDirectory as .enc files.
  6. The encrypted files are uploaded to the cloud using a preconfigured rclone remote. If $rcloneVerifyUploads is set to True the hash of the local and remote file will be compared. If $rcloneRemoveSourceFile is set to True the local .enc file will be removed (.vma and .log are not deleted)
  7. job files older than $keepJobsForDays are removed.

Steps 4-6 are done in a single thread per VM backup, how many of these threads that are executed at once is determined by $threads

Decryption

Decryption presumes that you have private key for the gpg recipient on you machine and gpg knows about it. To decrypt simply run:

gpg -d your_archive.enc | tar -xv

Installation

Prerequisite:

  • rclone setup with a remote
    • Tested with v1.45 and google drive
  • gpg setup with at least a public key for a recipient
  • tar
  • python3
    • The pexpect library needs to be available
      • python3 -m pip install pexpect
  • ProxmoxEncryptedBackup.py is made executable
    • chmod o+x ProxmoxEncryptedBackup.py

Setup

  • Create a folder to keep the python files: /opt/scripting/encryptedRemoteBackup/
  • Put the files there:
    • GpgEncrypt.py
    • ProxmoxEncryptedBackup.py
    • ProxmoxEncryptedBackupSettings.py
    • ProxmoxEventHandler.py
    • Rclone.py
  • Make ProxmoxEncryptedBackup.py executable
    • chmod o+x ProxmoxEncryptedBackup.py
  • Update ProxmoxEncryptedBackupSettings.py
    • Se example file for sample settings and documentation
  • Edit /etc/pve/vzdump.cron
    • Add to relevant jobs: --script /opt/scripting/encryptedRemoteBackup/ProxmoxEncryptedBackup.py
  • Run the backup job and check the task output and log file

Gotchas & Troubleshooting

  • G Suite only allows for about 750GB upload per day
  • PERB is only tested with G Suite, but should function with most rclone remotes, however if they dont support MD5 hashing PERB will need to be updated to handle that.
  • $gpgOutputDirectory should if possible not be set to the same storage as the Proxmox backup as this will severley slowdown encryption. PERB will be reading and writing to the same storage.
  • Check the logs at $logFile and the Proxmox Tasks in the web gui.

proxmox-encrypted-remote-backup's People

Contributors

andersatriada avatar farthinder avatar korewakiyo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

proxmox-encrypted-remote-backup's Issues

TypeError: 'NoneType' object is not subscriptable

When I start the backup it will show the following error:

INFO: Traceback (most recent call last):
INFO:   File "/opt/scripting/encryptedRemoteBackup/ProxmoxEncryptedBackup.py", line 118, in <module>
INFO:     outputFilename = re.search("vzdump-qemu-(.*)\.\w*?$", index["tarfile"])[1]
INFO: TypeError: 'NoneType' object is not subscriptable

How do I solve the problem?

Full Log:

INFO: starting new backup job: vzdump 104 --compress lzo --script /opt/scripting/encryptedRemoteBackup/ProxmoxEncryptedBackup.py --quiet 1 --storage LocalBackup --all 0 --mailnotification failure --mode snapshot --node homerserver
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:job-start
INFO: CryptBackup - INFO - Creating new job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: Starting Backup of VM 104 (lxc)
INFO: Backup started at 2020-04-25 22:43:46
INFO: status = running
INFO: CT Name: Pihole
INFO: backup mode: snapshot
INFO: ionice priority: 7
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:backup-start
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:pre-stop
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: create storage snapshot 'vzdump'
  Logical volume "snap_vm-104-disk-0_vzdump" created.
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:pre-restart
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:post-restart
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: creating archive '/mnt/pve/LocalBackup/dump/vzdump-lxc-104-2020_04_25-22_43_46.tar.lzo'
INFO: Total bytes written: 1659904000 (1.6GiB, 140MiB/s)
INFO: archive file size: 638MB
INFO: delete old backup '/mnt/pve/LocalBackup/dump/vzdump-lxc-104-2020_04_25-22_42_30.tar.lzo'
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:backup-end
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: remove vzdump snapshot
  Logical volume "snap_vm-104-disk-0_vzdump" successfully removed
INFO: Finished Backup of VM 104 (00:00:13)
INFO: Backup finished at 2020-04-25 22:43:59
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:log-end
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:job-end
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - The backup job has ended successfully, will prepare to encrypt
INFO: Traceback (most recent call last):
INFO:   File "/opt/scripting/encryptedRemoteBackup/ProxmoxEncryptedBackup.py", line 118, in <module>
INFO:     outputFilename = re.search("vzdump-qemu-(.*)\.\w*?$", index["tarfile"])[1]
INFO: TypeError: 'NoneType' object is not subscriptable
INFO: CryptBackup - INFO - Encrypted BackupScript called
INFO: CryptBackup - INFO - Job File:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Phase:job-abort
INFO: CryptBackup - INFO - Parsing existing job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
INFO: CryptBackup - INFO - Updating job file:/opt/scripting/encryptedRemoteBackup/jobs/31315.job
ERROR: Backup job failed - command '/opt/scripting/encryptedRemoteBackup/ProxmoxEncryptedBackup.py job-end' failed: exit code 1
TASK ERROR: command '/opt/scripting/encryptedRemoteBackup/ProxmoxEncryptedBackup.py job-end' failed: exit code 1

Script stopped working due to changed phase name

Reference: https://forum.proxmox.com/threads/info-backup-exit-code-255-vzdump-mit-hook-script.107163/#post-460678

A new backup phase 'job-init' was added to pve-manager and causes the Encrypted-Remote-Backup to stop with an error due to the new unkown phase. It worked again after adding the new phase and changing the phase 'job-start' in ProxmoxEventHandler.py. DUMPDIR is not defined in phase 'job-init' except when --dumpdir is used directly

if self.args.phase == 'job-init':

            storeid = os.environ["STOREID"]

            self.jobinfo[self.args.phase].append(
                {
                    'storeid': storeid
                }
            )

            logging.debug("Environment Variables:" + str(self.jobinfo[self.args.phase]))

            if os.path.isfile(self.jobFilePath):
                logging.error("Existing job file found: " + self.jobFilePath)
                sys.exit(1)

            with open(self.jobFilePath, 'w') as outfile:
                logging.info("Creating new job file:" + self.jobFilePath)
                json.dump(self.jobinfo, outfile)

            return self.args.phase

        elif self.args.phase == 'job-start':

            dumpdir = os.environ["DUMPDIR"]
            storeid = os.environ["STOREID"]

            logging.debug("Environment Variables:" + str(self.jobinfo[self.args.phase]))

            if os.path.isfile(self.jobFilePath) is False:
                logging.error("Existing job file not found: " + self.jobFilePath)
                sys.exit(1)

            with open(self.jobFilePath) as json_file:
                logging.info("Parsing existing job file:" + self.jobFilePath)
                self.jobinfo = json.load(json_file)


            self.jobinfo[self.args.phase] = {
                    'dumpdir': dumpdir,
                    'storeid': storeid
                }


            logging.debug("Environment Variables:" + str(self.jobinfo[self.args.phase]))

            with open(self.jobFilePath, 'w') as outfile:
                logging.info("Updating existing job file:" + self.jobFilePath)
                json.dump(self.jobinfo, outfile)

            return self.args.phase

Not working within Proxmox 7 - KeyError: 'TARFILE'

Upgraded to Proxmox 7 and backup is failing with an error: INFO: KeyError: 'TARFILE'

Reason: "Hookscript: The TARFILE environment variable was deprecated in Proxmox VE 6, in favor of TARGET. In Proxmox VE 7, it has been removed entirely and thus, it is not exported to the hookscript anymore." (https://pve.proxmox.com/wiki/Roadmap)

Solution: Change ProxmoxEventHandler.py line #153 from tarfile = os.environ["TARFILE"] to tarfile = os.environ["TARGET"]

Now its working again

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.