feature: the router makes scheduled backups to a local folder, copied to the cloud with rclone, or to a Proxmox Backup Server, and vpn-router-backup restores them.
Release / release (push) Successful in 13s
Release / release (push) Successful in 13s
This commit is contained in:
1 parent
7d1f639508
commit
fe45272f7c
16 files changed
+986
-19
No files matched your search
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/python3
|
||||
"""Make, list and restore backups of the router (vpnrouter_backup).
|
||||
|
||||
run makes a backup as backup in /etc/vpn-router/vpn-router.json says, whether or not backups are
|
||||
enabled, and prints its result; vpn-router-backup.service runs it on the schedule of its timer.
|
||||
list prints the local archives of this host in backup.path, newest first, or with the pbs target
|
||||
the snapshots of this host on the Proxmox Backup Server. restore puts back a local archive, or
|
||||
with --pbs a snapshot, such as host/router/2026-10-25T03:00:00Z, after showing what it holds and
|
||||
asking for yes, which --yes gives; what it replaces is kept in
|
||||
/var/lib/vpn-router/backup/before-restore. A failure is a message on standard error; the exit
|
||||
status is 2 for an invalid request and 1 for any other failure. -h or --help shows the options.
|
||||
|
||||
vpn-router-backup run
|
||||
vpn-router-backup list
|
||||
vpn-router-backup restore [--yes] ARCHIVE
|
||||
vpn-router-backup restore [--yes] --pbs SNAPSHOT
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/usr/lib/vpn-router')
|
||||
|
||||
import vpnrouter
|
||||
import vpnrouter_backup
|
||||
|
||||
|
||||
class Parser(argparse.ArgumentParser):
|
||||
"""An argument parser whose errors end with status 2."""
|
||||
|
||||
def error(self, message):
|
||||
print(f'{self.prog}: {message}; see {self.prog} --help', file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def confirmed(manifest, yes):
|
||||
"""Whether to restore the backup of the manifest: yes, or the answer yes to the question."""
|
||||
print(f'Backup of {manifest.get("host")} made {manifest.get("time")} by vpn-router {manifest.get("version")}:')
|
||||
for item in manifest.get('items', []):
|
||||
print(f' {item}')
|
||||
if yes:
|
||||
return True
|
||||
return input('Replace the router state with it? Type yes: ').strip() == 'yes'
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = Parser(prog='vpn-router-backup', description='Make, list and restore backups of the router.')
|
||||
commands = parser.add_subparsers(dest='command', required=True, parser_class=Parser)
|
||||
commands.add_parser('run', help='make a backup now')
|
||||
commands.add_parser('list', help='list the backups of this host')
|
||||
restorer = commands.add_parser('restore', help='put a backup back')
|
||||
restorer.add_argument('source', metavar='ARCHIVE|SNAPSHOT', help='a local archive, or with --pbs a snapshot')
|
||||
restorer.add_argument('--pbs', action='store_true', help='restore a snapshot of the Proxmox Backup Server')
|
||||
restorer.add_argument('--yes', action='store_true', help='do not ask before replacing the router state')
|
||||
options = parser.parse_args(argv)
|
||||
os.umask(0o077)
|
||||
|
||||
try:
|
||||
if options.command == 'run':
|
||||
record = vpnrouter_backup.run()
|
||||
if record['result'] == 'failed':
|
||||
print(f'vpn-router-backup: {record["error"]}', file=sys.stderr)
|
||||
return 1
|
||||
print(f'Backup {record["archive"] or record["snapshot"]} made, {record["size"]} bytes.')
|
||||
for name in record['removed']:
|
||||
print(f'Removed {name}.')
|
||||
if record['cloud']:
|
||||
print(f'Cloud copy to {record["cloud"]["remote"]}: {record["cloud"]["error"] or "ok"}.')
|
||||
return 0
|
||||
cfg = vpnrouter.load_config()
|
||||
if options.command == 'list':
|
||||
if cfg['backup']['target'] == 'pbs':
|
||||
print(vpnrouter_backup.snapshots(cfg), end='')
|
||||
else:
|
||||
for item in vpnrouter_backup.archives(cfg['backup']['path']):
|
||||
print(f'{item["name"]} {item["size"]}')
|
||||
return 0
|
||||
if options.pbs and options.source.startswith('/'):
|
||||
print('vpn-router-backup: a snapshot is type/id/time, such as host/router/2026-10-25T03:00:00Z',
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
saved = vpnrouter_backup.restore(options.source, options.pbs, lambda manifest: confirmed(manifest, options.yes))
|
||||
except vpnrouter_backup.Failure as exc:
|
||||
print(f'vpn-router-backup: {exc}', file=sys.stderr)
|
||||
return 1
|
||||
except OSError as exc:
|
||||
print(f'vpn-router-backup: {exc}', file=sys.stderr)
|
||||
return 1
|
||||
if saved is None:
|
||||
print('Nothing restored.')
|
||||
return 1
|
||||
print(f'Restored; what it replaced is in {saved}.')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
Reference in new issue
Block a user