This commit is contained in:
2025-11-30 19:35:08 +01:00
parent 99966b04ba
commit f901c8a22c
3 changed files with 279 additions and 1 deletions

185
backup_volumes copy.py Normal file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env python3
import docker
import os
import datetime
import argparse
import subprocess
import sys
BACKUP_DIR = "/backup/docker_volumes"
import requests
PORTAINER_URL = "https://port.sectorq.eu/api"
API_KEY = "ptr_/5RkMCT/j3BTaL32vMSDtXFi76yOXRKVFOrUtzMsl5Y="
HEADERS = {"X-API-Key": f"{API_KEY}"}
ENDPOINT_ID = 4
def get_stack_by_name(stack_name):
url = f"{PORTAINER_URL}/stacks"
r = requests.get(url, headers=HEADERS)
r.raise_for_status()
stacks = r.json()
for s in stacks:
if s['Name'] == stack_name:
return s
return None
def stop_stack(stack_name):
stack = get_stack_by_name(stack_name)
if not stack:
print(f"[INFO] Stack {stack_name} not found.")
return
print(f"Stopping stack {stack_name} via Portainer API...")
url = f"{PORTAINER_URL}/stacks/{stack['Id']}/stop?endpointId={ENDPOINT_ID}"
print("URL:", url)
r = requests.post(url, headers=HEADERS)
if r.status_code == 200:
print(f"[OK] Stack {stack_name} stopped.")
else:
print(f"[ERROR] Failed to stop stack: {r.status_code} {r.text}")
def start_stack(stack_name):
stack = get_stack_by_name(stack_name)
if not stack:
print(f"[INFO] Stack {stack_name} not found.")
return
print(f"Starting stack {stack_name} via Portainer API...")
url = f"{PORTAINER_URL}/stacks/{stack['Id']}/start?endpointId={ENDPOINT_ID}"
r = requests.post(url, headers=HEADERS)
if r.status_code == 200:
print(f"[OK] Stack {stack_name} started.")
else:
print(f"[ERROR] Failed to start stack: {r.status_code} {r.text}")
def to_timestamp():
return datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
def get_containers_using_volume(client, vol_name):
containers = []
for c in client.containers.list():
for m in c.attrs['Mounts']:
if m.get('Name') == vol_name:
containers.append(c)
return containers
def backup_volumes(client):
os.makedirs(BACKUP_DIR, exist_ok=True)
timestamp = to_timestamp()
volumes = client.volumes.list()
if not volumes:
print("No Docker volumes found.")
return
for vol in volumes:
vol_name = vol.name
backup_file = os.path.join(BACKUP_DIR, f"{vol_name}-{timestamp}.tar.gz")
containers = get_containers_using_volume(client, vol_name)
# Track stacks that need to be stopped
stacks_to_restart = {}
for c in containers:
stack = c.labels.get('com.docker.stack.namespace')
if stack:
# Stop stack instead of individual container
if stack not in stacks_to_restart:
stacks_to_restart[stack] = True
stop_stack(stack)
else:
# Stop individual container
print(f"Stopping container {c.name} for backup...")
c.stop()
# Backup using busybox
try:
print(f"Backing up volume {vol_name}{backup_file}")
client.containers.run(
image="busybox",
command=f"tar czf /backup/{vol_name}-{timestamp}.tar.gz -C /volume .",
remove=True,
volumes={
vol_name: {'bind': '/volume', 'mode': 'ro'},
BACKUP_DIR: {'bind': '/backup', 'mode': 'rw'}
},
)
print(f"[OK] Backup completed: {backup_file}")
except Exception as e:
print(f"[ERROR] Failed to backup {vol_name}: {e}")
# Restart individual containers
for c in containers:
stack = c.labels.get('com.docker.stack.namespace')
if not stack:
print(f"Starting container {c.name} after backup...")
c.start()
# Restart stacks
for stack in stacks_to_restart.keys():
start_stack(stack)
def restore_volume(client, vol_name, backup_file):
containers = get_containers_using_volume(client, vol_name)
stacks_to_restart = {}
for c in containers:
stack = c.labels.get('com.docker.stack.namespace')
if stack:
if stack not in stacks_to_restart:
stacks_to_restart[stack] = True
stop_stack(stack)
else:
print(f"Stopping container {c.name} for restore...")
c.stop()
try:
print(f"Restoring volume {vol_name} from {backup_file}")
client.containers.run(
image="busybox",
command=f"tar xzf /backup/{os.path.basename(backup_file)} -C /volume",
remove=True,
volumes={
vol_name: {'bind': '/volume', 'mode': 'rw'},
os.path.dirname(backup_file): {'bind': '/backup', 'mode': 'rw'}
},
)
print(f"[OK] Restore completed: {vol_name}")
except Exception as e:
print(f"[ERROR] Failed to restore {vol_name}: {e}")
# Restart containers
for c in containers:
stack = c.labels.get('com.docker.stack.namespace')
if not stack:
print(f"Starting container {c.name} after restore...")
c.start()
# Restart stacks
for stack in stacks_to_restart.keys():
stack_file = f"/deployments/{stack}.yml"
if os.path.exists(stack_file):
start_stack(stack_file)
else:
print(f"[WARNING] Stack file {stack_file} not found. Start manually.")
def main():
parser = argparse.ArgumentParser(description="Backup or restore Docker volumes.")
parser.add_argument("action", choices=["backup", "restore"], help="Action to perform")
parser.add_argument("--volume", help="Volume name (required for restore)")
parser.add_argument("--file", help="Backup file (required for restore)")
args = parser.parse_args()
client = docker.from_env()
if args.action == "backup":
backup_volumes(client)
elif args.action == "restore":
if not args.volume or not args.file:
print("[ERROR] --volume and --file are required for restore")
sys.exit(1)
restore_volume(client, args.volume, args.file)
if __name__ == "__main__":
main()

90
backup_volumes.py Normal file
View File

@@ -0,0 +1,90 @@
import docker
import requests
import os
PORTAINER_URL = "https://port.sectorq.eu/api"
API_KEY = "ptr_/5RkMCT/j3BTaL32vMSDtXFi76yOXRKVFOrUtzMsl5Y="
HEADERS = {"X-API-Key": f"{API_KEY}"}
ENDPOINT_ID = 4
client = docker.from_env()
# Keep track of which stacks we already stopped
stopped_stacks = {}
def get_stack_by_name(stack_name):
url = f"{PORTAINER_URL}/stacks"
r = requests.get(url, headers=HEADERS)
r.raise_for_status()
for s in r.json():
if s['Name'] == stack_name:
return s
return None
def stop_stack(stack_name):
if stack_name in stopped_stacks:
return
stack = get_stack_by_name(stack_name)
if not stack:
print(f"[INFO] Stack {stack_name} not found, skipping stop")
return
url = f"{PORTAINER_URL}/stacks/{stack['Id']}/stop?endpointId={ENDPOINT_ID}"
print(f"url: {url}")
r = requests.post(url, headers=HEADERS)
if r.status_code == 200:
print(f"[OK] Stack {stack_name} stopped")
else:
print(f"[ERROR] Failed to stop stack {stack_name}: {r.text}")
stopped_stacks[stack_name] = True
def start_stack(stack_name):
stack = get_stack_by_name(stack_name)
if not stack:
print(f"[INFO] Stack {stack_name} not found, skipping start")
return
url = f"{PORTAINER_URL}/stacks/{stack['Id']}/start?endpointId={ENDPOINT_ID}"
r = requests.post(url, headers=HEADERS)
if r.status_code == 200:
print(f"[OK] Stack {stack_name} started")
else:
print(f"[ERROR] Failed to start stack {stack_name}: {r.text}")
def backup_volume(vol):
vol_name = vol.name
backup_file = f"/backup/{vol_name}.tar"
print(f"[INFO] Backing up volume {vol_name}{backup_file}")
# Use a temporary container to archive the volume
client.containers.run(
image="alpine",
command=f"tar czf /backup/{vol_name}.tar -C /data .",
volumes={vol_name: {'bind': '/data', 'mode': 'ro'},
"/backup": {'bind': '/backup', 'mode': 'rw'}},
remove=True
)
def main():
volumes = client.volumes.list()
stack_volumes = {}
normal_volumes = []
# Categorize volumes by stack
for vol in volumes:
labels = vol.attrs.get('Labels', {})
stack_name = labels.get('com.docker.stack.namespace')
if stack_name:
stack_volumes.setdefault(stack_name, []).append(vol)
else:
normal_volumes.append(vol)
# Backup stacks
for stack_name, vols in stack_volumes.items():
stop_stack(stack_name)
for v in vols:
backup_volume(v)
start_stack(stack_name)
# Backup normal volumes
for v in normal_volumes:
backup_volume(v)
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,6 @@
volumes:
bitwarden_data:
driver: local
services:
bitwarden:
container_name: vaultwarden
@@ -29,4 +32,4 @@ services:
- 8181:80
restart: ${RESTART:-unless-stopped}
volumes:
- /share/docker_data/bitwarden/bw-data:/data
- bitwarden_data:/data