Skip to content

Commit a9333e9

Browse files
Merge pull request #1 from siameseoriental/DO-101
DO-101
2 parents 03b6acb + dca60d1 commit a9333e9

File tree

6 files changed

+320
-2
lines changed

6 files changed

+320
-2
lines changed

.github/workflows/main.yaml

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Docker Image CI
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- 'main'
8+
- 'master'
9+
tags:
10+
- 'v*'
11+
pull_request:
12+
branches:
13+
- 'main'
14+
- 'master'
15+
16+
env:
17+
REGISTRY: docker.io
18+
IMAGE_NAME: ${{ github.repository }}
19+
20+
jobs:
21+
22+
build:
23+
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v3
29+
30+
# Workaround: https://github.com/docker/build-push-action/issues/461
31+
- name: Setup Docker buildx
32+
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
33+
34+
# Login against a Docker registry
35+
# https://github.com/docker/login-action
36+
- name: Log into registry ${{ env.REGISTRY }}
37+
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
38+
with:
39+
registry: ${{ env.REGISTRY }}
40+
username: ${{ secrets.DOCKERHUB_LOGIN }}
41+
password: ${{ secrets.DOCKERHUB_PASSWORD }}
42+
43+
# Extract metadata (tags, labels) for Docker
44+
# https://github.com/docker/metadata-action
45+
- name: Extract Docker metadata
46+
id: meta
47+
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
48+
with:
49+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
50+
51+
# Build and push Docker image with Buildx (don't push on PR)
52+
# https://github.com/docker/build-push-action
53+
- name: Build and push Docker image
54+
id: build-and-push
55+
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
56+
with:
57+
context: .
58+
push: ${{ github.event_name != 'pull_request' }}
59+
tags: ${{ steps.meta.outputs.tags }}
60+
labels: ${{ steps.meta.outputs.labels }}
61+
cache-from: type=gha
62+
cache-to: type=gha,mode=max

Dockerfile

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# syntax=docker/dockerfile:1.2
2+
3+
FROM debian:stable as base
4+
5+
MAINTAINER NumDes <[email protected]>
6+
7+
LABEL org.opencontainers.image.vendor="Numerical Design LLC"
8+
LABEL org.opencontainers.image.description="Docker image for universal postgres backups"
9+
10+
ARG GOCRONVER=v0.0.10
11+
12+
RUN apt-get update \
13+
&& apt-get install --no-install-recommends -y \
14+
curl \
15+
ca-certificates \
16+
postgresql-client \
17+
&& apt-get clean \
18+
&& rm -rf /var/lib/apt/lists/* \
19+
&& curl --fail --retry 4 --retry-all-errors -o /usr/local/bin/go-cron.gz -L https://github.com/prodrigestivill/go-cron/releases/download/$GOCRONVER/go-cron-linux-amd64.gz \
20+
&& gzip -vnd /usr/local/bin/go-cron.gz && chmod a+x /usr/local/bin/go-cron
21+
22+
RUN curl --location --output /usr/local/bin/mcli "https://dl.min.io/client/mc/release/linux-amd64/mc" && \
23+
chmod +x /usr/local/bin/mcli
24+
RUN mcli -v
25+
26+
ENV POSTGRES_DB="**None**" \
27+
POSTGRES_HOST="**None**" \
28+
POSTGRES_PORT=5432 \
29+
POSTGRES_USER="**None**" \
30+
POSTGRES_PASSWORD="**None**" \
31+
POSTGRES_EXTRA_OPTS="--blobs" \
32+
SCHEDULE="@daily" \
33+
HEALTHCHECK_PORT=8080 \
34+
S3_ACCESS_KEY_ID="**None**" \
35+
S3_SECRET_ACCESS_KEY="**None**" \
36+
S3_BUCKET="**None**" \
37+
S3_ENDPOINT="**None**"
38+
39+
COPY backup.sh /backup.sh
40+
RUN chmod +x backup.sh
41+
42+
COPY hooks /hooks
43+
44+
ENTRYPOINT ["/bin/sh", "-c"]
45+
CMD ["exec /usr/local/bin/go-cron -s \"$SCHEDULE\" -p \"$HEALTHCHECK_PORT\" -- /backup.sh"]
46+
47+
HEALTHCHECK --interval=5m --timeout=3s \
48+
CMD curl -f "http://localhost:$HEALTHCHECK_PORT/" || exit 1

README.md

+101-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,105 @@
22
Docker image for universal postgres backups
33

44
# Roadmap
5-
- [ ] Add support for S3
5+
- [X] Add support for S3
6+
- [X] Add CI/CD to publish image to DockerHub
67
- [ ] Add retention policy settings by env vars
7-
- [ ] Notify about backup status by HTTP-request
8+
- [X] Notify about backup status by HTTP-request
9+
- [ ] Add docker-compose example
10+
11+
## Docker build
12+
```shell
13+
docker build . -t numdes/nd_postgres_backup:v*.*.*
14+
```
15+
16+
# Usage
17+
## Backup manually:
18+
```shell
19+
docker run --rm -it \
20+
-e POSTGRES_HOST="FQDN-OR-IP" \
21+
-e POSTGRES_DB="DB-NAME" \
22+
-e POSTGRES_USER="DB-USER" \
23+
-e POSTGRES_PASSWORD="PASS" \
24+
-e S3_ENDPOINT=http://YOUR-S3 \
25+
-e S3_ACCESS_KEY_ID="KEY-ID" \
26+
-e S3_SECRET_ACCESS_KEY="KEY-SECRET" \
27+
-e S3_BUCKET="BUCKET-NAME" \
28+
-e PRIVATE_NOTIFICATION_URL=http://webhook \
29+
-e TELEGRAM_CHAT_ID=point_to_notify_group \
30+
-e POSTGRES_PORT=if_not_5432 \
31+
--entrypoint /bin/bash \
32+
numdes/nd_postgres_backup:v*.*.*
33+
```
34+
To run backup, in active container shell call `backup.sh` script
35+
```shell
36+
./backup.sh
37+
```
38+
39+
## Backup using `go-cron`
40+
```shell
41+
docker run -d \
42+
-e POSTGRES_HOST="FQDN-OR-IP" \
43+
-e POSTGRES_DB="DB-NAME" \
44+
-e POSTGRES_USER="DB-USER" \
45+
-e POSTGRES_PASSWORD="PASS" \
46+
-e S3_ENDPOINT=http://YOUR-S3 \
47+
-e S3_ACCESS_KEY_ID="KEY-ID" \
48+
-e S3_SECRET_ACCESS_KEY="KEY-SECRET" \
49+
-e S3_BUCKET="BUCKET-NAME" \
50+
-e PRIVATE_NOTIFICATION_URL=http://webhook \
51+
-e TELEGRAM_CHAT_ID=point_to_notify_group \
52+
-e POSTGRES_PORT=if_not_5432 \
53+
-e SCHEDULE=Chosen_schedule
54+
numdes/nd_postgres_backup:v*.*.*
55+
```
56+
:wave: By default `SCHEDULE` variable is set to `@daily` in case if you need other scheduling options, please refer to `go-cron` *[Documentation](https://pkg.go.dev/github.com/robfig/cron?utm_source=godoc#hdr-Predefined_schedules)*.
57+
58+
## Variables
59+
### `Gitlab Actions` *[variables](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)*:
60+
| Name | Description |
61+
|-------------------|-----------------------------------------------------|
62+
|DOCKERHUB_LOGIN | `Actions` Repository secret |
63+
|DOCKERHUB_PASSWORD | `Actions` Repository secret |
64+
65+
### Notification environmental variables
66+
| Name | Description |
67+
|---------------------------|-----------------------------------------------------|
68+
|TELEGRAM_CHAT_ID | Notifying group |
69+
|PRIVATE_NOTIFICATION_URL | Private notifier URL |
70+
|TELEGRAM_BOT_TOKEN | Only used to call Telegram's public API |
71+
72+
### Environmental variables
73+
| Name | Default value | Description |
74+
|------------------------| :------ |------------------------------------------------|
75+
| POSTGRES_DB | - | Database name |
76+
| POSTGRES_HOST | - | PostgreSQL IP address or hostname |
77+
| POSTGRES_PORT | 5432 | Connection TCP port |
78+
| POSTGRES_USER | - | Database user |
79+
| POSTGRES_PASSWORD | - | Database user password |
80+
| POSTGRES_EXTRA_OPTS | --blobs | Extra options `pg_dump` run |
81+
| SCHEDULE | @daily | `go-cron` schedule. See [this](#backup-using-go-cron) |
82+
| HEALTHCHECK_PORT | 8080 | Port listening for cron-schedule health check. |
83+
| S3_ACCESS_KEY_ID | - | Key or username with RW access to bucket |
84+
| S3_SECRET_ACCESS_KEY | - | Secret or password for `S3_ACCESS_KEY_ID` |
85+
| S3_BUCKET | - | Name of bucket created for backups |
86+
| S3_ENDPOINT | - | URL of S3 storage |
87+
88+
### Notification selection
89+
90+
It is possible to use either private Telegram bot if you have it or Telegram public API.
91+
92+
In scenario with private bot `PRIVATE_NOTIFICATION_URL` must be set alongside with `TELEGRAM_CHAT_ID`.
93+
94+
In scenario with Telegram's public API `TELEGRAM_BOT_TOKEN` must be set as it is received (`Use this token to access the HTTP API:`) from `@BotFather` Telegram Bot. Variable `TELEGRAM_CHAT_ID` must be a proper Telegram ID of bot
95+
96+
In `docker ...` command need to replace:
97+
```
98+
-e PRIVATE_NOTIFICATION_URL=http://webhook \
99+
-e TELEGRAM_CHAT_ID=point_to_notify_group \
100+
```
101+
to
102+
```
103+
-e TELEGRAM_BOT_TOKEN='XXXXXXX:XXXXxxxxXXXXxxx' \
104+
-e TELEGRAM_CHAT_ID=000000000 \
105+
```
106+
- If `TELEGRAM_CHAT_ID` has a proper format (Only digits not less than 5 not more than 32) and `TELEGRAM_BOT_TOKEN` is set, script will try to send notification through Telegram's public API.

backup.sh

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Script made for backup PostgreSQL database from local (${POSTGRES_HOST}=127.0.0.1)
4+
# or remote host. Created backup stores in S3 storage. On completion script calls
5+
# notification scripts from hooks/ directory to send report to given Telegram Chat
6+
# based on variables set private or public notification method will be selected
7+
8+
set -euo pipefail
9+
IFS=$'\n\t'
10+
11+
# Will be name of directory in backet yyyy-mm-dd_HH:MM:SS
12+
timestamp="$(date +%F_%T)"
13+
14+
export PGPASSWORD=${POSTGRES_PASSWORD}
15+
16+
# Will create base backup
17+
echo "Creating backup of ${POSTGRES_DB} database. From ${POSTGRES_HOST} and port \
18+
is ${POSTGRES_PORT}. Username: ${POSTGRES_USER}. With following extra options: \
19+
${POSTGRES_EXTRA_OPTS}"
20+
pg_dump --username="${POSTGRES_USER}" \
21+
--host="${POSTGRES_HOST}" \
22+
--port="${POSTGRES_PORT}" \
23+
--dbname="${POSTGRES_DB}" \
24+
"${POSTGRES_EXTRA_OPTS}" \
25+
> "${POSTGRES_DB}".sql
26+
27+
# Declaring variables for informational purposes
28+
copy_file_name="${POSTGRES_DB}.tar.gz"
29+
copy_path="${S3_BUCKET}/${POSTGRES_DB}/${timestamp}"
30+
mcli_copy_path="${copy_path}/${copy_file_name}"
31+
info_copy_path="${S3_ENDPOINT}/${copy_path}"
32+
33+
# Do compression
34+
tar --create \
35+
--gzip \
36+
--verbose \
37+
--file "${copy_file_name}" \
38+
"${POSTGRES_DB}.sql"
39+
40+
# Count file size
41+
send_file_size="$(ls -lh "${copy_file_name}" | awk '{print $5}')"
42+
43+
echo "Created ${copy_file_name} with file size: ${send_file_size}"
44+
45+
# Set S3 connection configuration
46+
mcli alias set backup "${S3_ENDPOINT}" "${S3_ACCESS_KEY_ID}" "${S3_SECRET_ACCESS_KEY}"
47+
48+
echo "Starting to copy ${copy_file_name} to ${info_copy_path}..."
49+
50+
# Copying backup to S3
51+
mcli cp "${copy_file_name}" backup/"${mcli_copy_path}"
52+
53+
# Do nettoyage
54+
echo "Maid is here... Doing cleaning..."
55+
rm --force "${POSTGRES_DB}".*
56+
57+
# Do anounce
58+
run-parts --reverse \
59+
--arg "${copy_file_name}" \
60+
--arg "${send_file_size}" \
61+
--arg "${info_copy_path}" \
62+
/hooks

hooks/send_private_notification

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Use http request to send notification to Telegram chat through local Telegram bot
4+
5+
set -eo pipefail
6+
IFS=$'\n\t'
7+
8+
copy_file_name="${1}"
9+
send_file_size="${2}"
10+
info_copy_path="${3}"
11+
12+
message_text="Backed up ${copy_file_name} with file size: ${send_file_size} \
13+
to ${info_copy_path}"
14+
15+
if [[ -n "${PRIVATE_NOTIFICATION_URL}" ]]; then
16+
curl -XPOST \
17+
--url "${PRIVATE_NOTIFICATION_URL}" \
18+
--header 'Content-Type: application/json' \
19+
--data "{\"key\": \"${TELEGRAM_CHAT_ID}\", \"text\": \"${message_text}\"}" \
20+
--max-time 10 \
21+
--retry 5
22+
fi

hooks/send_telegram_message

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Use http request to send notification to Telegram chat through external Telegram API
4+
5+
set -eo pipefail
6+
IFS=$'\n\t'
7+
8+
copy_file_name="${1}"
9+
send_file_size="${2}"
10+
info_copy_path="${3}"
11+
12+
message_text="Backed up ${copy_file_name} with file size: ${send_file_size} \
13+
to ${info_copy_path}"
14+
15+
if [[ -n "${TELEGRAM_BOT_TOKEN}" ]]; then
16+
if [[ "${TELEGRAM_CHAT_ID}" =~ ^[0-9]{5,32}$ ]]; then
17+
curl -s \
18+
--data "text=${message_text}" \
19+
--data "chat_id=${TELEGRAM_CHAT_ID}" \
20+
'https://api.telegram.org/bot'"${TELEGRAM_BOT_TOKEN}"'/sendMessage' > /dev/null
21+
else
22+
echo "Telegram chatID doesn't matched to standard pattern. Probably \
23+
TELEGRAM_BOT_TOKEN variable was set by mistake"
24+
fi
25+
fi

0 commit comments

Comments
 (0)