Skip to content

Commit 2124902

Browse files
authored
Backups for docmost + nocodb (#152)
Add some backup cronjobs for nocodb and docmost! These are kinda rough/ready backups. Both nocodb and docmost have a storage directory (for images etc), and also of course data in their respective database. How does the restore process work? Yes Where are the backups stored? On a hetzner storage box (another ~3.5 eur/month) How can we extract the backups if we ever need them? At present, I'm the only one with the keys here. I'll do some thinking about the best way to share this. (see below) Why did we need new containers? I couldn't find any open/maintained images that had both psql + ssh. It would be nice to not have to manage some containers, but not the end of the world. Could also manage this automagically with github actions in theory. Storage box file structure: ``` | # root user (me) \- k3s # sub-1 |- nocodb # sub-2 | \- backups | |- pg_backup_<date>.tar.gz | \- file_backup_<date>.tar.gz \- docmost # sub-3 \- backups |- pg_backup_<date>.tar.gz \- file_backup_<date>.tar.gz ``` :point_up: Each individual service (docmost/nocodb) has it's own set of ssh keys to access the storage box. This means if one service gets pwned, at least it doesn't pwn the other. The good news is that I had some foresight to have a separate account for the `k3s` directory, which means we could create some ssh keys there, and share them in the keepass for emergency access etc.
1 parent c0bc291 commit 2124902

File tree

16 files changed

+499
-0
lines changed

16 files changed

+499
-0
lines changed

infra/containers/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# backup containers
2+
3+
This directory contains a collection of containers to help with backing up data.
4+
5+
## Contaienrs
6+
7+
- `quay.io/b-3-n/postgres-backup` - A container that inherits from `postgres:<v>-alpine` and installs a SSH client for remote backups.
8+
- `quay.io/b-3-n/file-backup` - A container based on `alpine` with an installed SSH client for remote backups.
9+
10+
## Building
11+
12+
Podman/Docker:
13+
14+
```sh
15+
podman build -t quay.io/b-3-n/<container>:latest -f <contianer>/Containerfile .
16+
```
17+
18+
e.g.
19+
20+
```sh
21+
podman build -t quay.io/b-3-n/file-backup:latest -f file-backup/Containerfile .
22+
```
23+
24+
> [!NOTE]
25+
> The above command is run from this directory, not the container sub-directory.
26+
27+
## Usage
28+
29+
### `postgres-backup`
30+
31+
Connects to a given Postgres, runs a `pg_dumpall`, throws it into a gzip, and uploads to ssh.
32+
33+
```
34+
podman run \
35+
--rm \
36+
--env SSH_HOST=backup.example.com \
37+
quay.io/b-3-n/postgres-backup:latest
38+
```
39+
40+
Required ENV vars:
41+
42+
- `SSH_HOST`: SSH hostname for the backup destination
43+
44+
Optional ENV vars:
45+
46+
- `DEBUG`: Extra logs (default: `0`)
47+
- `DB_HOST`: Hostname of the Postgres database (default: `localhost`)
48+
- `DB_PORT`: Port of the Postgres database (default: `5432`)
49+
- `DB_USER`: Username for the Postgres database (default: `postgres`)
50+
- `DB_PASSWORD`: Password for the Postgres database (default: empty)
51+
- `SSH_USER`: SSH username for the backup destination (default: `root`)
52+
- `SSH_PORT`: SSH port for the backup destination (default: `22`)
53+
- `SSH_REMOTE_DIR`: Directory on the backup destination to store backups (default: `""`)
54+
55+
### `file-backup`
56+
57+
Takes whatever is in $FILE_PATH, tar+gzips it, uploads to ssh.
58+
59+
```
60+
podman run \
61+
--rm \
62+
--env SSH_HOST=backup.example.com \
63+
--env FILE_PATH=/data/ \
64+
quay.io/b-3-n/file-backup:latest
65+
```
66+
67+
Required ENV vars:
68+
69+
- `SSH_HOST`: SSH hostname for the backup destination
70+
- `FILE_PATH`: The directory to back up
71+
72+
Optional ENV vars:
73+
74+
- `DEBUG`: Extra logs (default: `0`)
75+
- `DB_HOST`: Hostname of the Postgres database (default: `localhost`)
76+
- `DB_PORT`: Port of the Postgres database (default: `5432`)
77+
- `DB_USER`: Username for the Postgres database (default: `postgres`)
78+
- `DB_PASSWORD`: Password for the Postgres database (default: empty)
79+
- `SSH_USER`: SSH username for the backup destination (default: `root`)
80+
- `SSH_PORT`: SSH port for the backup destination (default: `22`)
81+
- `SSH_REMOTE_DIR`: Directory on the backup destination to store backups (default: `""`)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
DEBUG=${DEBUG:-"0"}
4+
5+
function log() {
6+
echo "$(date -Iseconds) $1"
7+
}
8+
9+
function ldebug() {
10+
if [ "$DEBUG" = "1" ]; then
11+
log "[DEBUG] $1"
12+
fi
13+
}
14+
15+
function linfo() {
16+
log "[INFO] $1"
17+
}
18+
19+
function lerror() {
20+
log "[ERROR] $1"
21+
exit 1
22+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/bash
2+
3+
4+
# SSH connection setup
5+
ssh_user=${SSH_USER:-"root"}
6+
ssh_port=${SSH_PORT:-"22"}
7+
ssh_host=${SSH_HOST:-""}
8+
ssh_key_path=${SSH_KEY_PATH:-"/root/.ssh/id_ed25519"}
9+
ssh_addr=${ssh_user}@${ssh_host}
10+
ssh_opts="-o ConnectTimeout=5"
11+
12+
function ssh_setup() {
13+
if [ -z "$ssh_host" ]; then
14+
lerror "SSH_HOST is not set."
15+
fi
16+
17+
ldebug "SSH connection: ${ssh_addr} on port ${ssh_port}"
18+
ldebug "SSH Keyfile Path: ${ssh_key_path}"
19+
20+
linfo "Testing SSH connection..."
21+
ssh -i "${ssh_key_path}" -p$ssh_port ${ssh_opts} "${ssh_addr}" "ls" 1>/dev/null
22+
if [ $? -ne 0 ]; then
23+
lerror "SSH connection failed connecting to ${ssh_addr} on port ${ssh_port}. Exiting"
24+
fi
25+
linfo "SSH connection successful."
26+
}
27+
28+
function ssh_send_file() {
29+
local source_path="$1"
30+
local dest_path="$2"
31+
32+
if [ ! -f "$source_path" ]; then
33+
lerror "Source file $source_path does not exist."
34+
fi
35+
36+
source_file=${source_path##*/}
37+
source_extension="${source_file#*.}"
38+
source_filename="${source_file%%.*}"
39+
backup_file="${source_filename}_$(date +%Y%m%d_%H%M%S).${source_extension}"
40+
backup_path="${backup_file}"
41+
if [[ "$dest_path" != "" ]]; then
42+
backup_path="${dest_path}/${backup_path}"
43+
fi
44+
ldebug "Prepared backup file name: ${backup_path}"
45+
46+
linfo "Transferring backup to remote server..."
47+
scp -i "${ssh_key_path}" -P$ssh_port ${ssh_opts} "$source_path" "${ssh_addr}:${backup_path}"
48+
linfo "Backup transferred to ${ssh_addr}:${backup_path}"
49+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
set -e
3+
4+
for f in /docker-entrypoint.d/*.sh; do
5+
source $f
6+
done
7+
8+
if [ "$1" = 'file-backup.sh' ]; then
9+
. file-backup.sh
10+
elif [ "$1" = 'postgres-backup.sh' ]; then
11+
. postgres-backup.sh
12+
else
13+
exec "$@"
14+
fi
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
ARG ALPINE_VERSION=3.22
2+
FROM alpine:$ALPINE_VERSION
3+
4+
RUN apk update && apk add --no-cache openssh bash
5+
6+
COPY docker-entrypoint.d/ /docker-entrypoint.d/
7+
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
8+
9+
COPY file-backup/file-backup.sh /usr/local/bin/file-backup.sh
10+
11+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin/file-backup.sh
12+
13+
ENTRYPOINT ["docker-entrypoint.sh"]
14+
CMD ["file-backup.sh"]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/sh
2+
set -e
3+
4+
# Setup the SSH connection
5+
ssh_setup
6+
7+
file_path=${FILE_PATH:-""}
8+
if [ -z "$file_path" ]; then
9+
lerror "FILE_PATH is not set. Exiting."
10+
fi
11+
if [ ! -e "$file_path" ]; then
12+
lerror "File $file_path does not exist. Exiting."
13+
fi
14+
15+
# Backup process
16+
backup_dir="/tmp"
17+
backup_file="file_backup.sql.gz"
18+
backup_path="${backup_dir}/${backup_file}"
19+
20+
tar -czf $backup_path $file_path
21+
22+
file_info=$(ls -lh "$backup_path")
23+
ldebug "Backup file at $backup_path info: $file_info"
24+
25+
ssh_dir=${SSH_REMOTE_DIR:-""}
26+
ssh_send_file "$backup_path" "$ssh_dir"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
ARG POSTGRES_VERSION=17-alpine
2+
FROM postgres:$POSTGRES_VERSION
3+
4+
RUN apk update && apk add --no-cache openssh
5+
6+
COPY docker-entrypoint.d /docker-entrypoint.d
7+
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
8+
9+
COPY postgres-backup/postgres-backup.sh /usr/local/bin/postgres-backup.sh
10+
11+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin/postgres-backup.sh
12+
13+
ENTRYPOINT ["docker-entrypoint.sh"]
14+
CMD ["postgres-backup.sh"]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/sh
2+
set -e
3+
4+
# Database connection setup
5+
db_host=${DB_HOST:-"localhost"}
6+
db_port=${DB_PORT:-"5432"}
7+
db_user=${DB_USER:-"postgres"}
8+
db_password=${DB_PASSWORD:-""}
9+
10+
function pg_setup() {
11+
ldebug "Using database at ${db_host}:${db_port} as user ${db_user}"
12+
linfo "Testing DB connection..."
13+
PGHOST=$db_host PGPASSWORD=$db_password PGPORT=$db_port PGUSER=$db_user psql -c "SELECT 1;" 1>/dev/null
14+
linfo "Database connection successful."
15+
}
16+
17+
function pg_backup() {
18+
local backup_path=$1
19+
# Backup process
20+
linfo "Starting database backup..."
21+
ldebug "Using database at ${db_host}:${db_port} as user ${db_user} for backup"
22+
PGHOST=$db_host PGPASSWORD=$db_password PGPORT=$db_port PGUSER=$db_user pg_dumpall | gzip > "$backup_path"
23+
linfo "Database backup created at $backup_path"
24+
}
25+
26+
# SSH connection setup
27+
pg_setup
28+
ssh_setup
29+
30+
# Backup process
31+
backup_dir="/tmp"
32+
backup_file="pg_backup.sql.gz"
33+
backup_path="${backup_dir}/${backup_file}"
34+
pg_backup "$backup_path"
35+
36+
file_info=$(ls -lh "$backup_path")
37+
ldebug "Backup file at $backup_path info: $file_info"
38+
39+
ssh_dir=${SSH_REMOTE_DIR:-""}
40+
ssh_send_file "$backup_path" "$ssh_dir"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: docmost-backup
5+
data:
6+
# For postgres-backup
7+
DB_HOST: docmost-postgres.docmost.svc
8+
DB_PORT: "5432"
9+
DB_USER: docmost
10+
# For file-backup
11+
FILE_PATH: /data/docmost
12+
# Destination SSH server details
13+
SSH_HOST: u499668-sub3.your-storagebox.de
14+
SSH_USER: u499668-sub3
15+
SSH_PORT: "23"
16+
SSH_KEY_PATH: /root/.ssh/id_ed25519
17+
SSH_REMOTE_DIR: backups
18+
DEBUG: "1"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
apiVersion: batch/v1
2+
kind: CronJob
3+
metadata:
4+
name: docmost-backup
5+
spec:
6+
schedule: "0 3 * * *"
7+
concurrencyPolicy: Forbid
8+
jobTemplate:
9+
spec:
10+
template:
11+
metadata:
12+
labels:
13+
app: docmost-backup
14+
spec:
15+
containers:
16+
- name: postgres-backup
17+
image: quay.io/b-3-n/postgres-backup:latest # PATCHME
18+
imagePullPolicy: Always
19+
env:
20+
- name: DB_PASSWORD
21+
valueFrom:
22+
secretKeyRef:
23+
name: docmost-backup
24+
key: DB_PASSWORD
25+
envFrom:
26+
- configMapRef:
27+
name: docmost-backup
28+
volumeMounts:
29+
- name: ssh
30+
mountPath: /root/.ssh/id_ed25519
31+
subPath: id_ed25519
32+
readOnly: true
33+
- name: ssh
34+
mountPath: /root/.ssh/known_hosts
35+
subPath: known_hosts
36+
readOnly: true
37+
- name: data-backup
38+
image: quay.io/b-3-n/file-backup:latest # PATCHME
39+
imagePullPolicy: Always
40+
envFrom:
41+
- configMapRef:
42+
name: docmost-backup
43+
volumeMounts:
44+
- name: ssh
45+
mountPath: /root/.ssh/id_ed25519
46+
subPath: id_ed25519
47+
readOnly: true
48+
- name: ssh
49+
mountPath: /root/.ssh/known_hosts
50+
subPath: known_hosts
51+
readOnly: true
52+
- name: docmost-data
53+
mountPath: /data/docmost
54+
readOnly: true
55+
restartPolicy: OnFailure
56+
volumes:
57+
- name: ssh
58+
secret:
59+
secretName: docmost-backup
60+
defaultMode: 384
61+
items:
62+
- key: id_ed25519
63+
path: id_ed25519
64+
- key: SSH_KNOWN_HOSTS
65+
path: known_hosts
66+
- name: docmost-data
67+
persistentVolumeClaim:
68+
claimName: docmost-data-docmost-docmost-0

0 commit comments

Comments
 (0)