#!/bin/sh
set -eu
umask 077
/usr/bin/flock -n /run/hush-backup.lock /usr/bin/python3 - <<'PY'
import pathlib, shutil, subprocess, json, os
from urllib.parse import urlparse,unquote
stage = pathlib.Path('/var/backups/hush-v2')
stage.mkdir(mode=0o700, parents=True, exist_ok=True)
config=json.loads(pathlib.Path('/etc/hush/messenger.json').read_text())
# Account/public-key metadata is recoverable. Transient delivery ciphertext,
# presence, sessions, and attachment blobs are intentionally excluded, so an
# acknowledged/deleted message is not retained in recurring server backups.
command=['/usr/bin/pg_dump','--format=custom','--no-owner','--no-acl','--file='+str(stage/'metadata.dump')]
for table in ['message_queue','message_deliveries','message_ids','attachments','sessions','maculab_sessions','maculab_states','reports']:
 command.append('--exclude-table-data='+table)
u=urlparse(config['postgres']);env={**os.environ,'PGHOST':u.hostname,'PGPORT':str(u.port or 5432),'PGDATABASE':u.path[1:],'PGUSER':unquote(u.username),'PGPASSWORD':unquote(u.password)}
subprocess.run(command,env=env,check=True)
for source,name in [('/var/lib/hush/opaque-setup','opaque-setup'),('/etc/hush/maculab.json','maculab.json'),('/etc/hush/messenger.json','messenger.json')]:
 shutil.copy2(source,stage/name)
# HushAnon has independent accounts and OPAQUE setup. Do not copy its local
# ciphertext attachment spool or any transient delivery/session table data.
anon=stage/'hushanon'
if pathlib.Path('/etc/hushanon/messenger.json').exists() and pathlib.Path('/var/lib/hushanon/opaque-setup').exists():
 anon.mkdir(mode=0o700,exist_ok=True)
 config=json.loads(pathlib.Path('/etc/hushanon/messenger.json').read_text())
 u=urlparse(config['postgres']);env={**os.environ,'PGHOST':u.hostname,'PGPORT':str(u.port or 5432),'PGDATABASE':u.path[1:],'PGUSER':unquote(u.username),'PGPASSWORD':unquote(u.password)}
 anon_command=[arg if not arg.startswith('--file=') else '--file='+str(anon/'metadata.dump') for arg in command]
 subprocess.run(anon_command,env=env,check=True)
 for source,name in [('/var/lib/hushanon/opaque-setup','opaque-setup'),('/etc/hushanon/messenger.json','messenger.json'),('/etc/hushanon/networks.json','networks.json')]:
  if pathlib.Path(source).exists():shutil.copy2(source,anon/name)
# Stable onion/I2P identities and I2P TLS keys belong only in this encrypted
# operator backup, never in the source tree or public certificate directory.
network=stage/'privacy-network'
if pathlib.Path('/etc/hush-privacy').exists():
 network.mkdir(mode=0o700,exist_ok=True)
 for name in ['maculab','hush','hushanon']:
  source=pathlib.Path('/var/lib/tor')/name
  if source.exists():shutil.copytree(source,network/('tor-'+name),dirs_exist_ok=True)
  source=pathlib.Path('/var/lib/i2pd')/(name+'-site.dat')
  if source.exists():shutil.copy2(source,network/source.name)
 for source,name in [('/etc/hush/networks.json','hush-networks.json'),('/etc/tor/torrc','torrc'),('/etc/i2pd/i2pd.conf','i2pd.conf'),('/etc/i2pd/tunnels.conf','tunnels.conf')]:
  if pathlib.Path(source).exists():shutil.copy2(source,network/name)
 for name in ['certs','public']:
  shutil.copytree('/etc/hush-privacy/'+name,network/name,dirs_exist_ok=True)
subprocess.run(['/usr/local/sbin/maculab-restic','backup',str(stage),'--tag','hush-v2'],check=True)
# Retain no unencrypted local copy of the backup, even though the dump has no
# normal message plaintext or messaging private keys.
for name in ['metadata.dump','opaque-setup','maculab.json','messenger.json']:(stage/name).unlink()
for path in [anon,network]:
 if path.exists():shutil.rmtree(path)
PY
