[manage.py] Add vaultwarden + complete backup support
This commit is contained in:
parent
441c7f21de
commit
252c827d92
5 changed files with 104 additions and 21 deletions
1
env.yml
1
env.yml
|
@ -72,7 +72,6 @@ projects_to_backup:
|
||||||
- stump
|
- stump
|
||||||
- synapse
|
- synapse
|
||||||
- uptime-kuma
|
- uptime-kuma
|
||||||
- vaultwarden
|
|
||||||
|
|
||||||
|
|
||||||
borg_repodir: "{{ cifs_mounts['backups']['path'] }}/borg"
|
borg_repodir: "{{ cifs_mounts['backups']['path'] }}/borg"
|
||||||
|
|
88
manage.py
88
manage.py
|
@ -10,29 +10,73 @@ import yaml
|
||||||
|
|
||||||
|
|
||||||
def backupProj(project):
|
def backupProj(project):
|
||||||
|
database = None
|
||||||
|
paths = []
|
||||||
|
|
||||||
|
if project in secrets['postgres'].keys():
|
||||||
|
database = project
|
||||||
|
|
||||||
|
if project in env['backup'].keys():
|
||||||
|
for path in env['backup'][project]:
|
||||||
|
paths += [path]
|
||||||
|
|
||||||
|
if database is not None or len(paths) > 0:
|
||||||
print(f"Running backup for project {project}.")
|
print(f"Running backup for project {project}.")
|
||||||
# loop env.volumes & secrets.postgres
|
|
||||||
|
if len(paths) == 0:
|
||||||
|
borgCreate(project, database=database)
|
||||||
|
else:
|
||||||
|
borgCreate(project, path=paths[0], database=database)
|
||||||
|
|
||||||
|
for path in paths[1:]:
|
||||||
|
archiveName = re.sub('/', '-', path)
|
||||||
|
borgCreate(f"{project}-{archiveName}", path=path)
|
||||||
|
|
||||||
|
|
||||||
def borgCreate(name, path=None, database=None):
|
def borgCreate(name, path=None, database=None):
|
||||||
if path is None and database is None:
|
if path is None and database is None:
|
||||||
print(f"Backup failed, you must pass at least one parameter amongst: path, database (archive name = {name}).", file=sys.stderr)
|
print(f"Cannot create backup, you must pass at least one parameter amongst: path, database (archive name = {name}).", file=sys.stderr)
|
||||||
return
|
return 1
|
||||||
|
|
||||||
|
borgPaths = []
|
||||||
|
|
||||||
if path is not None:
|
if path is not None:
|
||||||
absPath = Path(path).absolute()
|
absPath = Path(path).absolute()
|
||||||
|
|
||||||
print(f"Creating archive '{name}' from path {absPath}.")
|
|
||||||
|
|
||||||
parentDir = absPath.parent.absolute()
|
parentDir = absPath.parent.absolute()
|
||||||
os.chdir(parentDir)
|
os.chdir(parentDir)
|
||||||
|
|
||||||
runBorg(["create", "--compression=lzma", f"{env['borg_repo']}::{name}-" + '{now:%Y-%m-%d_%H-%M-%S}', absPath.relative_to(parentDir)])
|
borgPaths += [absPath.relative_to(parentDir)]
|
||||||
|
|
||||||
runBorg(["prune", "--glob-archives", f"{name}-*"] + env['borg_prune_opts'] + [env['borg_repo']])
|
borgInput = None
|
||||||
|
|
||||||
|
if database is not None:
|
||||||
|
print(f"Dumping database {database}.")
|
||||||
|
|
||||||
|
dumpProc = subprocess.run(["docker", "exec", "postgres", "pg_dump", "-c", database], capture_output=True, text=True)
|
||||||
|
if dumpProc.returncode != 0:
|
||||||
|
print(f"Failed to dump database {database}.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
borgPaths += ["-", "--stdin-name", f"{database}.sql"]
|
||||||
|
|
||||||
|
borgInput = dumpProc.stdout
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
print(f"Creating archive '{name}' from database {database}.")
|
||||||
|
elif database is None:
|
||||||
|
print(f"Creating archive '{name}' from path {path}.")
|
||||||
|
else:
|
||||||
|
print(f"Creating archive '{name}' from database {database} and path {absPath}.")
|
||||||
|
|
||||||
|
borgArgs = ["create", "--compression=lzma", f"{env['borg_repo']}::{name}_{{utcnow}}"]
|
||||||
|
runBorg(borgArgs + borgPaths, input=borgInput)
|
||||||
|
|
||||||
os.chdir(os.path.realpath(sys.path[0]))
|
os.chdir(os.path.realpath(sys.path[0]))
|
||||||
|
|
||||||
|
print(f"Pruning archives '{name}_*'.")
|
||||||
|
runBorg(["prune", "--glob-archives", f"{name}_*"] + env['borg_prune_opts'] + [env['borg_repo']])
|
||||||
|
|
||||||
|
|
||||||
def getImageId (image):
|
def getImageId (image):
|
||||||
return runPodman("image", ["inspect", "--format", "{{.Id}}", image]).stdout.strip()
|
return runPodman("image", ["inspect", "--format", "{{.Id}}", image]).stdout.strip()
|
||||||
|
@ -77,20 +121,18 @@ def renderFile(templateFile):
|
||||||
outputFile.close()
|
outputFile.close()
|
||||||
|
|
||||||
|
|
||||||
def runBorg(args):
|
def runBorg(args, input=None):
|
||||||
if isinstance(args, str):
|
if isinstance(args, str):
|
||||||
args = args.split(' ')
|
args = args.split(' ')
|
||||||
|
|
||||||
env = {"BORG_PASSPHRASE": secrets['borg']}
|
borgEnv = {"BORG_PASSPHRASE": secrets['borg']}
|
||||||
child = subprocess.Popen(["borg"] + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env)
|
proc = subprocess.run(["borg"] + args, input=input, capture_output=True, text=True, env=borgEnv)
|
||||||
|
|
||||||
child.wait()
|
stderr = proc.stderr.strip()
|
||||||
|
|
||||||
stderr = child.stderr.read().strip()
|
|
||||||
if stderr != '':
|
if stderr != '':
|
||||||
print(stderr, file=sys.stderr)
|
print(stderr, file=sys.stderr)
|
||||||
|
|
||||||
return child
|
return proc
|
||||||
|
|
||||||
|
|
||||||
def runPodman(cmd, args):
|
def runPodman(cmd, args):
|
||||||
|
@ -103,9 +145,16 @@ def runPodman(cmd, args):
|
||||||
runArgs = ["podman", cmd] + args
|
runArgs = ["podman", cmd] + args
|
||||||
|
|
||||||
if env['rootless']:
|
if env['rootless']:
|
||||||
return subprocess.run(runArgs, capture_output=True, text=True)
|
proc = subprocess.run(runArgs, capture_output=True, text=True)
|
||||||
|
stderr = proc.stderr.strip()
|
||||||
else:
|
else:
|
||||||
return runSudo(runArgs)
|
proc = runSudo(runArgs)
|
||||||
|
stderr = proc.stderr.read().strip()
|
||||||
|
|
||||||
|
if stderr != '':
|
||||||
|
print(stderr, file=sys.stderr)
|
||||||
|
|
||||||
|
return proc
|
||||||
|
|
||||||
|
|
||||||
def runSudo(args):
|
def runSudo(args):
|
||||||
|
@ -184,6 +233,7 @@ def setPerms(path, mode):
|
||||||
def setupProj(project):
|
def setupProj(project):
|
||||||
print(f"Running setup for project {project}.")
|
print(f"Running setup for project {project}.")
|
||||||
|
|
||||||
|
if os.path.isfile(f"projects/{project}/compose.yaml.rendered"):
|
||||||
backupProj(project)
|
backupProj(project)
|
||||||
|
|
||||||
if project == 'diun':
|
if project == 'diun':
|
||||||
|
@ -206,14 +256,16 @@ def setupProj(project):
|
||||||
|
|
||||||
if project in env['volumes']:
|
if project in env['volumes']:
|
||||||
for volume in env['volumes'][project].values():
|
for volume in env['volumes'][project].values():
|
||||||
|
if not os.path.exists(volume):
|
||||||
|
os.makedirs(volume)
|
||||||
setPerms(volume, 750)
|
setPerms(volume, 750)
|
||||||
setOwner(volume, getUid(project), getUid(project))
|
setOwner(volume, getUid(project), env['podman_uid'])
|
||||||
|
|
||||||
upProj(project)
|
upProj(project)
|
||||||
|
|
||||||
|
|
||||||
def upProj(project):
|
def upProj(project):
|
||||||
if runPodman("container", ["exists", project]).returncode == 0:
|
if runPodman("pod", ["exists", f"pod_{project}"]).returncode == 0:
|
||||||
print(f"Tearing down stack for project {project}.")
|
print(f"Tearing down stack for project {project}.")
|
||||||
runPodman("compose", ["-f", f"projects/{project}/compose.yaml.rendered", "down"])
|
runPodman("compose", ["-f", f"projects/{project}/compose.yaml.rendered", "down"])
|
||||||
|
|
||||||
|
|
13
projects/vaultwarden/.env.mako
Normal file
13
projects/vaultwarden/.env.mako
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
ADMIN_TOKEN='${secrets["vw_admin_token_hash"]}'
|
||||||
|
DOMAIN=https://vw.${env['domain']}
|
||||||
|
ROCKET_PORT=8080
|
||||||
|
SIGNUPS_ALLOWED=false
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql://${secrets['postgres']['vaultwarden']['user']}:${secrets['postgres']['vaultwarden']['pass']}@postgres.${env['domain']}:${env['ports']['postgres']}/vaultwarden
|
||||||
|
|
||||||
|
SMTP_HOST=mail.${env['domain']}
|
||||||
|
SMTP_FROM=vaultwarden@${env['domain']}
|
||||||
|
SMTP_PORT=${env['ports']['mailserver_smtps']}
|
||||||
|
SMTP_SECURITY=force_tls
|
||||||
|
SMTP_USERNAME='${secrets["mailserver"]["vaultwarden"]["user"]}'
|
||||||
|
SMTP_PASSWORD='${secrets["mailserver"]["vaultwarden"]["pass"]}'
|
12
projects/vaultwarden/compose.yaml.mako
Normal file
12
projects/vaultwarden/compose.yaml.mako
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
services:
|
||||||
|
vaultwarden:
|
||||||
|
container_name: vaultwarden
|
||||||
|
image: docker.io/vaultwarden/server:alpine
|
||||||
|
network_mode: pasta:-a,${env['pasta']['vaultwarden']['ipv4']},-a,${env['pasta']['vaultwarden']['ipv6']}
|
||||||
|
restart: always
|
||||||
|
user: ${env['users']['vaultwarden']}:${env['users']['vaultwarden']}
|
||||||
|
env_file: .env.rendered
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:${env['ports']['vaultwarden']}:8080
|
||||||
|
volumes:
|
||||||
|
- ${env['volumes']['vaultwarden']['datadir']}:/data
|
|
@ -20,6 +20,10 @@ socket: "/run/podman/podman.sock"
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
|
|
||||||
|
backup:
|
||||||
|
vaultwarden:
|
||||||
|
- /mnt/vwdata/attachments
|
||||||
|
|
||||||
borg_repo: /mnt/storagebox/backups/borg2
|
borg_repo: /mnt/storagebox/backups/borg2
|
||||||
borg_prune_opts:
|
borg_prune_opts:
|
||||||
- "--keep-within=1d"
|
- "--keep-within=1d"
|
||||||
|
@ -48,6 +52,9 @@ pasta:
|
||||||
syncthing_relaysrv:
|
syncthing_relaysrv:
|
||||||
ipv4: 10.86.21.1
|
ipv4: 10.86.21.1
|
||||||
ipv6: fc86::21
|
ipv6: fc86::21
|
||||||
|
vaultwarden:
|
||||||
|
ipv4: 10.86.23.1
|
||||||
|
ipv6: fc86::23
|
||||||
|
|
||||||
|
|
||||||
# Ports exposed to host
|
# Ports exposed to host
|
||||||
|
|
Loading…
Reference in a new issue