Skip to content

Commit

Permalink
added global variables and a new sudo argument
Browse files Browse the repository at this point in the history
  • Loading branch information
apprell committed Apr 2, 2023
1 parent 2aeb995 commit c56ba9b
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 39 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ chmod +x /root/proxmox-autosnap/proxmox-autosnap.py
| includevmstate | no | bool | false | Include the VM state in snapshots. |
| dryrun | no | bool | false | Do not create or delete snapshots, just print the commands. |
| date-iso-format | no | bool | false | Store snapshots in ISO 8601 format. |
| sudo | no | bool | false | Launch commands through sudo. |

> proxmox-autosnap.py --help
Expand Down Expand Up @@ -60,6 +61,22 @@ proxmox-autosnap.py --clean --vmid all --label hourly --keep 0
proxmox-autosnap.py --snap --vmid 100 --date-iso-format
```

## SUDO

In order to run with sudo argument, you must first create a user and specify minimum accesses for him, for example:

`cat /etc/sudoers.d/proxmox-backup`

```bash
proxmox-backup ALL=NOPASSWD: /usr/bin/cat /etc/pve/.vmlist, /usr/sbin/pct snapshot *, /usr/sbin/pct listsnapshot *, /usr/sbin/pct delsnapshot *, /usr/sbin/qm snapshot *, /usr/sbin/qm listsnapshot *, /usr/sbin/qm delsnapshot *
```

After that you can run the script with the argument

```bash
proxmox-autosnap.py --snap --vmid 100 --date-iso-format --sudo
```

## Cron

```bash
Expand Down
92 changes: 53 additions & 39 deletions proxmox-autosnap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
import subprocess
from datetime import datetime, timedelta

MUTE = False
DRY_RUN = False
USE_SUDO = False
ONLY_ON_RUNNING = False
DATE_ISO_FORMAT = False
INCLUDE_VM_STATE = False


def running(func):
@functools.wraps(func)
Expand All @@ -30,6 +37,9 @@ def create_pid(*args, **kwargs):


def run_command(command: list) -> dict:
if USE_SUDO:
command.insert(0, 'sudo')

run = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = run.communicate()
if run.returncode == 0:
Expand All @@ -50,47 +60,52 @@ def vm_is_stopped(vmid: str, virtualization: str) -> bool:
def vmid_list(exclude: list, vmlist_path: str = '/etc/pve/.vmlist') -> dict:
vm_id = {}
node = socket.gethostname().split('.')[0]
with open(vmlist_path) as vmlist:
data = json.load(vmlist)

run = run_command(['cat', vmlist_path])
if not run['status']:
raise SystemExit(run['message'])

data = json.loads(run['message'])
for key, value in data['ids'].items():
if value['type'] == 'lxc' and value['node'] == node and key not in exclude:
vm_id[key] = 'pct'
elif value['type'] == 'qemu' and value['node'] == node and key not in exclude:
vm_id[key] = 'qm'

return vm_id


def create_snapshot(vmid: str, virtualization: str, label: str = 'daily', mute: bool = False,
only_on_running: bool = False, savevmstate: bool = False, dryrun: bool = False,
date_iso_format: bool = False):
if only_on_running and vm_is_stopped(vmid, virtualization):
print('VM {0} - status is stopped, skipping...'.format(vmid)) if not mute else None
def create_snapshot(vmid: str, virtualization: str, label: str = 'daily') -> None:
if ONLY_ON_RUNNING and vm_is_stopped(vmid, virtualization):
print('VM {0} - status is stopped, skipping...'.format(vmid)) if not MUTE else None
return

name = {'hourly': 'autohourly', 'daily': 'autodaily', 'weekly': 'autoweekly', 'monthly': 'automonthly'}
suffix_datetime = datetime.now() + timedelta(seconds=1)
if date_iso_format:
if DATE_ISO_FORMAT:
suffix = "_" + suffix_datetime.isoformat(timespec="seconds").replace("-", "_").replace(":", "_")
else:
suffix = suffix_datetime.strftime('%y%m%d%H%M%S')
snapshot_name = name[label] + suffix
params = [virtualization, 'snapshot', vmid, snapshot_name, '--description', 'autosnap']
if virtualization == 'qm' and savevmstate:

if virtualization == 'qm' and INCLUDE_VM_STATE:
params.append('--vmstate')
if dryrun:
print(' '.join(params)) if not mute else None

if DRY_RUN:
params.insert(0, 'sudo') if USE_SUDO else None
print(' '.join(params))
else:
run = run_command(params)
if run['status']:
print('VM {0} - Creating snapshot {1}'.format(vmid, snapshot_name)) if not mute else None
print('VM {0} - Creating snapshot {1}'.format(vmid, snapshot_name)) if not MUTE else None
else:
print('VM {0} - {1}'.format(vmid, run['message']))


def remove_snapshot(vmid: str, virtualization: str, label: str = 'daily', keep: int = 30, mute: bool = False,
only_on_running: bool = False, dryrun: bool = False):
if only_on_running and vm_is_stopped(vmid, virtualization):
print('VM {0} - status is stopped, skipping...'.format(vmid)) if not mute else None
def remove_snapshot(vmid: str, virtualization: str, label: str = 'daily', keep: int = 30) -> None:
if ONLY_ON_RUNNING and vm_is_stopped(vmid, virtualization):
print('VM {0} - status is stopped, skipping...'.format(vmid)) if not MUTE else None
return

listsnapshot = []
Expand All @@ -106,12 +121,13 @@ def remove_snapshot(vmid: str, virtualization: str, label: str = 'daily', keep:
if old_snapshots:
for old_snapshot in old_snapshots:
params = [virtualization, 'delsnapshot', vmid, old_snapshot]
if dryrun:
print(' '.join(params)) if not mute else None
if DRY_RUN:
params.insert(0, 'sudo') if USE_SUDO else None
print(' '.join(params))
else:
run = run_command(params)
if run['status']:
print('VM {0} - Removing snapshot {1}'.format(vmid, old_snapshot)) if not mute else None
print('VM {0} - Removing snapshot {1}'.format(vmid, old_snapshot)) if not MUTE else None
else:
print('VM {0} - {1}'.format(vmid, run['message']))

Expand All @@ -135,44 +151,42 @@ def main():
parser.add_argument('-i', '--includevmstate', action='store_true', help='Include the VM state in snapshots.')
parser.add_argument('-d', '--dryrun', action='store_true',
help='Do not create or delete snapshots, just print the commands.')
parser.add_argument('--sudo', action='store_true', help='Launch commands through sudo.')
argp = parser.parse_args()

global MUTE, DRY_RUN, USE_SUDO, ONLY_ON_RUNNING, INCLUDE_VM_STATE, DATE_ISO_FORMAT
MUTE = argp.mute
DRY_RUN = argp.dryrun
USE_SUDO = argp.sudo
ONLY_ON_RUNNING = argp.running
DATE_ISO_FORMAT = argp.date_iso_format
INCLUDE_VM_STATE = argp.includevmstate

all_vmid = vmid_list(exclude=argp.exclude)

if argp.snap:
if 'all' in argp.vmid:
for k, v in all_vmid.items():
create_snapshot(vmid=k, virtualization=v, label=argp.label, mute=argp.mute,
only_on_running=argp.running, savevmstate=argp.includevmstate, dryrun=argp.dryrun,
date_iso_format=argp.date_iso_format)
create_snapshot(vmid=k, virtualization=v, label=argp.label)
else:
for vm in argp.vmid:
create_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label, mute=argp.mute,
only_on_running=argp.running, savevmstate=argp.includevmstate, dryrun=argp.dryrun,
date_iso_format=argp.date_iso_format)
create_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label)
elif argp.clean:
if 'all' in argp.vmid:
for k, v in all_vmid.items():
remove_snapshot(vmid=k, virtualization=v, label=argp.label, keep=argp.keep, mute=argp.mute,
only_on_running=argp.running, dryrun=argp.dryrun)
remove_snapshot(vmid=k, virtualization=v, label=argp.label, keep=argp.keep)
else:
for vm in argp.vmid:
remove_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label, keep=argp.keep, mute=argp.mute,
only_on_running=argp.running, dryrun=argp.dryrun)
remove_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label, keep=argp.keep)
elif argp.autosnap:
if 'all' in argp.vmid:
for k, v in all_vmid.items():
create_snapshot(vmid=k, virtualization=v, label=argp.label, mute=argp.mute,
only_on_running=argp.running, savevmstate=argp.includevmstate, dryrun=argp.dryrun,
date_iso_format=argp.date_iso_format)
remove_snapshot(vmid=k, virtualization=v, label=argp.label, keep=argp.keep, mute=argp.mute,
only_on_running=argp.running, dryrun=argp.dryrun)
create_snapshot(vmid=k, virtualization=v, label=argp.label)
remove_snapshot(vmid=k, virtualization=v, label=argp.label, keep=argp.keep)
else:
for vm in argp.vmid:
create_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label, mute=argp.mute,
only_on_running=argp.running, savevmstate=argp.includevmstate, dryrun=argp.dryrun,
date_iso_format=argp.date_iso_format)
remove_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label, keep=argp.keep, mute=argp.mute,
only_on_running=argp.running, dryrun=argp.dryrun)
create_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label)
remove_snapshot(vmid=vm, virtualization=all_vmid[vm], label=argp.label, keep=argp.keep)
else:
parser.print_help()

Expand Down

0 comments on commit c56ba9b

Please sign in to comment.