forked from Deuxfleurs/infrastructure
Create a backup script
This commit is contained in:
parent
1183583fdf
commit
9701b863fd
5 changed files with 143 additions and 62 deletions
|
@ -1,22 +0,0 @@
|
|||
FROM golang:buster as builder
|
||||
|
||||
WORKDIR /root
|
||||
RUN git clone https://filippo.io/age && cd age/cmd/age && go build -o age .
|
||||
|
||||
FROM amd64/debian:buster
|
||||
|
||||
COPY --from=builder /root/age/cmd/age/age /usr/local/bin/age
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -qq -y full-upgrade && \
|
||||
apt-get install -y rsync wget openssh-client postgresql-client && \
|
||||
apt-get clean && \
|
||||
rm -f /var/lib/apt/lists/*_*
|
||||
|
||||
RUN mkdir -p /root/.ssh
|
||||
WORKDIR /root
|
||||
|
||||
COPY do_backup.sh /root/do_backup.sh
|
||||
|
||||
CMD "/root/do_backup.sh"
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -x -e
|
||||
|
||||
cd /root
|
||||
|
||||
chmod 0600 .ssh/id_ed25519
|
||||
|
||||
cat > .ssh/config <<EOF
|
||||
Host backuphost
|
||||
HostName $TARGET_SSH_HOST
|
||||
Port $TARGET_SSH_PORT
|
||||
User $TARGET_SSH_USER
|
||||
EOF
|
||||
|
||||
echo "export sql"
|
||||
export PGPASSWORD=$REPL_PSQL_PWD
|
||||
pg_basebackup \
|
||||
--pgdata=- \
|
||||
--format=tar \
|
||||
--max-rate=1M \
|
||||
--no-slot \
|
||||
--wal-method=none \
|
||||
--gzip \
|
||||
--compress=8 \
|
||||
--checkpoint=spread \
|
||||
--progress \
|
||||
--verbose \
|
||||
--status-interval=10 \
|
||||
--username=$REPL_PSQL_USER \
|
||||
--port=5432 \
|
||||
--host=psql-proxy.service.2.cluster.deuxfleurs.fr | \
|
||||
age -r "$(cat /root/.ssh/id_ed25519.pub)" | \
|
||||
ssh backuphost "cat > $TARGET_SSH_DIR/matrix/db-$(date --iso-8601=minute).gz.age"
|
||||
|
||||
MATRIX_MEDIA="/mnt/glusterfs/chat/matrix/synapse/media"
|
||||
echo "export local_content"
|
||||
tar -vzcf - ${MATRIX_MEDIA} | \
|
||||
age -r "$(cat /root/.ssh/id_ed25519.pub)" | \
|
||||
ssh backuphost "cat > $TARGET_SSH_DIR/matrix/media-$(date --iso-8601=minute).gz.age"
|
16
app/backup/build/backup-psql/default.nix
Normal file
16
app/backup/build/backup-psql/default.nix
Normal file
|
@ -0,0 +1,16 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
let
|
||||
python-with-my-packages = pkgs.python3.withPackages (p: with p; [
|
||||
minio
|
||||
]);
|
||||
in
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
python-with-my-packages
|
||||
pkgs.age
|
||||
pkgs.postgresql_14
|
||||
];
|
||||
shellHook = ''
|
||||
PYTHONPATH=${python-with-my-packages}/${python-with-my-packages.sitePackages}
|
||||
'';
|
||||
}
|
101
app/backup/build/backup-psql/do_backup.py
Executable file
101
app/backup/build/backup-psql/do_backup.py
Executable file
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env python3
|
||||
import shutil,sys,os,datetime,minio
|
||||
|
||||
working_directory = "."
|
||||
if 'CACHE_DIR' in os.environ: working_directory = os.environ['CACHE_DIR']
|
||||
required_space_in_bytes = 20 * 1024 * 1024 * 1024
|
||||
bucket = os.environ['AWS_BUCKET']
|
||||
key = os.environ['AWS_ACCESS_KEY_ID']
|
||||
secret = os.environ['AWS_SECRET_ACCESS_KEY']
|
||||
endpoint = os.environ['AWS_ENDPOINT']
|
||||
pubkey = os.environ['CRYPT_PUBLIC_KEY']
|
||||
psql_host = os.environ['PSQL_HOST']
|
||||
psql_user = os.environ['PSQL_USER']
|
||||
s3_prefix = str(datetime.datetime.now())
|
||||
files = [ "backup_manifest", "base.tar.gz", "pg_wal.tar.gz" ]
|
||||
clear_paths = [ os.path.join(working_directory, f) for f in files ]
|
||||
crypt_paths = [ os.path.join(working_directory, f) + ".age" for f in files ]
|
||||
s3_keys = [ s3_prefix + "/" + f for f in files ]
|
||||
|
||||
def abort(msg):
|
||||
for p in clear_paths + crypt_paths:
|
||||
if os.path.exists(p):
|
||||
print(f"Remove {p}")
|
||||
os.remove(p)
|
||||
|
||||
if msg: sys.exit(msg)
|
||||
else: print("success")
|
||||
|
||||
# Check we have enough space on disk
|
||||
if shutil.disk_usage(working_directory).free < required_space_in_bytes:
|
||||
abort(f"Not enough space on disk at path {working_directory} to perform a backup, aborting")
|
||||
|
||||
# Check postgres password is set
|
||||
if 'PGPASSWORD' not in os.environ:
|
||||
abort(f"You must pass postgres' password through the environment variable PGPASSWORD")
|
||||
|
||||
# Check our working directory is empty
|
||||
if len(os.listdir(working_directory)) != 0:
|
||||
abort(f"Working directory {working_directory} is not empty, aborting")
|
||||
|
||||
# Check Minio
|
||||
client = minio.Minio(endpoint, key, secret)
|
||||
if not client.bucket_exists(bucket):
|
||||
abort(f"Bucket {bucket} does not exist or its access is forbidden, aborting")
|
||||
|
||||
# Perform the backup locally
|
||||
ret = os.system(f"""
|
||||
pg_basebackup \
|
||||
--host={psql_host} \
|
||||
--username={psql_user} \
|
||||
--pgdata={working_directory} \
|
||||
--format=tar \
|
||||
--wal-method=stream \
|
||||
--gzip \
|
||||
--compress=6 \
|
||||
--progress \
|
||||
--max-rate=5M
|
||||
""")
|
||||
if ret != 0:
|
||||
abort(f"pg_baseckup exit code is {ret}, 0 expected. aborting")
|
||||
|
||||
# Check that the expected files are here
|
||||
for p in clear_paths:
|
||||
print(f"Checking that {p} exists locally")
|
||||
if not os.path.exists(p):
|
||||
abort(f"File {p} expected but not found, aborting")
|
||||
|
||||
# Cipher them
|
||||
for c, e in zip(clear_paths, crypt_paths):
|
||||
print(f"Ciphering {c} to {e}")
|
||||
ret = os.system(f"age -r {pubkey} -o {e} {c}")
|
||||
if ret != 0:
|
||||
abort(f"age exit code is {ret}, 0 expected. aborting")
|
||||
|
||||
# Upload the backup to S3
|
||||
for p, k in zip(crypt_paths, s3_keys):
|
||||
try:
|
||||
print(f"Uploading {p} to {k}")
|
||||
result = client.fput_object(bucket, k, p)
|
||||
print(
|
||||
"created {0} object; etag: {1}, version-id: {2}".format(
|
||||
result.object_name, result.etag, result.version_id,
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
abort(f"Exception {e} occured while upload {p}. aborting")
|
||||
|
||||
# Check that the files have been uploaded
|
||||
for k in s3_keys:
|
||||
try:
|
||||
print(f"Checking that {k} exists remotely")
|
||||
result = client.stat_object(bucket, k)
|
||||
print(
|
||||
"last-modified: {0}, size: {1}".format(
|
||||
result.last_modified, result.size,
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
abort(f"{k} not found on S3. {e}. aborting")
|
||||
|
||||
abort(None)
|
26
op_guide/stolon/nomad_full_backup.md
Normal file
26
op_guide/stolon/nomad_full_backup.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
Start by following ../backup-minio
|
||||
|
||||
## Garbage collect old backups
|
||||
|
||||
```
|
||||
mc ilm import deuxfleurs/${BUCKET_NAME} <<EOF
|
||||
{
|
||||
"Rules": [
|
||||
{
|
||||
"Expiration": {
|
||||
"Days": 62
|
||||
},
|
||||
"ID": "PurgeOldBackups",
|
||||
"Status": "Enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Check that it has been activated:
|
||||
|
||||
```
|
||||
mc ilm ls deuxfleurs/${BUCKET_NAME}
|
||||
```
|
||||
|
Loading…
Reference in a new issue