Skip to content

feat: add intiail version of Helm chart for Bookstack S3 backup #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: release

on:
push:
branches:
- main

jobs:
release:
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"
- name: Install Helm
uses: azure/setup-helm@v3

- name: Run chart-releaser
uses: helm/[email protected]
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Helm-related files
*.tgz
*.lock
.chart-releaser.yaml
.release.yaml
.local.values.yaml
*.kubeconfig
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Helm Chart: Bookstack S3 Backup

This Helm chart deploys a CronJob to back up Bookstack documents to an S3-compatible storage with optional SSE-C encryption. The backup process overwrites existing files, so S3 bucket versioning should be enabled to maintain historical versions.

## Installation

To install with custom values:

```bash
helm repo add bookstack-backup https://pascalinthecloud.github.io/helm-bookstack-s3-backup/

helm install bookstack-backup bookstack-backup bookstack-backup/helm-bookstack-s3-backup -f values.yaml -n <NAMESPACE>
```

## Configuration

### Values

| Key | Default Value | Description |
|--------------------|---------------------|-------------|
| `cron` | `"0 4 * * 0"` | Cron schedule for backups (UTC) |
| `image.repository` | `pascaaal/docker-awscli-kubectl-zip` | Backup container image repository |
| `image.tag` | `v1.0.7` | Container image tag |
| `s3.accessKey` | `""` | S3 Access Key ID |
| `s3.secretKey` | `""` | S3 Secret Access Key |
| `s3.bucket` | `""` | Target S3 bucket name |
| `s3.endpoint` | `""` | S3 endpoint including `https://` |
| `s3.region` | `""` | S3 region |
| `s3.sseKey` | `""` | 32-byte hex key for server-side encryption (SSE-C) |
| `bookstack.namespace` | `""` | Namespace of Bookstack deployment |
| `bookstack.app` | `""` | Label of the Bookstack pod |
| `bookstack.container_name` | `""` | Bookstack container name |

## Example `values.yaml`

```yaml
cron: "0 3 * * *"

image:
repository: pascaaal/docker-awscli-kubectl-zip
tag: v1.0.7

s3:
accessKey: "your-access-key"
secretKey: "your-secret-key"
bucket: "your-backup-bucket"
endpoint: "https://s3.example.com"
region: "us-east-1"
sseKey: "your-32-byte-hex-key" # encryption is enabled, when key is set

bookstack:
namespace: "bookstack"
app: "bookstack"
container_name: "bookstack-container"
```

## Generate encryption key
```bash
openssl rand -hex 32
```

## Decrypt backup
```bash
export S3_SSE_KEY=154cd74642087d8d8d36c9136b89fb10b42a745c12108092895de44ed03518c0
echo $S3_SSE_KEY | xxd -r -p > .sse-c.key
aws s3 sync s3://<BUCKET_NAME> . --endpoint-url https://<ENDPOINT_URL> --sse-c AES256 --sse-c-key fileb://.sse-c.key
```

## View logs
```bash
kubectl logs -l app=bookstack-backup -n <NAMESPACE> -c bookstack-backup
```
## Uninstallation

To remove the deployment:

```bash
helm uninstall bookstack-backup
```

## Notes
- Ensure that the S3 credentials have appropriate permissions to write to the bucket.
- Treat your encryption key like your car keys—lose it, and you’re not going anywhere… especially not to your backups! 🚗🔑

23 changes: 23 additions & 0 deletions charts/helm-bookstack-s3-backup/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
6 changes: 6 additions & 0 deletions charts/helm-bookstack-s3-backup/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: helm-bookstack-s3-backup
description: A Helm chart for automating Bookstack backups to a S3 bucket in Kubernetes with optional encryption.
type: application
version: 0.1.0
appVersion: "1.0.0"
81 changes: 81 additions & 0 deletions charts/helm-bookstack-s3-backup/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
namespace: {{ .Release.Namespace }}
data:
backup.sh: |
#!/bin/sh
set -e

# Log functions
log() {
local level="$1"
local message="$2"
echo "[${level}] ${message}"
}

info() { log "INFO" "$1"; }
warn() { log "WARN" "$1"; }
error() { log "ERROR" "$1"; }

# Validate environment variables
: "${S3_BUCKET:?$(error 'Environment variable S3_BUCKET not set')}"
: "${S3_ENDPOINT:?$(error 'Environment variable S3_ENDPOINT not set')}"
: "${S3_REGION:?$(error 'Environment variable S3_REGION not set')}"

# Get the bookstack pod name & container name
BOOKSTACK_POD=$(kubectl get pod -n $BOOKSTACK_NAMESPACE -l app=$BOOKSTACK_APP -o jsonpath="{.items[0].metadata.name}")

# Set the timestamp and file name
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
FILE_NAME="bookstack-backup-$TIMESTAMP"

# Import the key if S3_SSE_KEY is set
if [ -n "$S3_SSE_KEY" ]; then
S3_SSE_C_PATH="/mnt/backup/.sse-c.key"
echo "$S3_SSE_KEY" | xxd -r -p > "$S3_SSE_C_PATH"
fi


#sleep infinity

info "Creating backup with document exporter..."
kubectl exec ${BOOKSTACK_POD} --container $BOOKSTACK_CONTAINER_NAME -- \
sh -c 'mysqldump --skip-ssl -h $DB_HOST -u $DB_USERNAME -p$DB_PASSWORD $DB_DATABASE > /tmp/'${FILE_NAME}'.sql'

kubectl exec ${BOOKSTACK_POD} --container $BOOKSTACK_CONTAINER_NAME -- \
sh -c 'cd /app/www/ && tar --dereference -czvf /tmp/bookstack-files-backup.tar.gz public/uploads storage/uploads themes'

info "Copying sql backup file to this pod..."
kubectl cp \
--container="${BOOKSTACK_CONTAINER_NAME}" \
${BOOKSTACK_POD}:/tmp/${FILE_NAME}.sql \
/mnt/backup/bookstack-backup-db.sql

info "Copying bookstack folder backup file to the host..."
kubectl cp \
--container="${BOOKSTACK_CONTAINER_NAME}" \
${BOOKSTACK_POD}:/tmp/bookstack-files-backup.tar.gz \
/mnt/backup/bookstack-files-backup.tar.gz

info "Unzipping backup file..."
tar -xzvf /mnt/backup/bookstack-files-backup.tar.gz -C /mnt/backup/

info "Removing tar file local..."
rm /mnt/backup/bookstack-files-backup.tar.gz

info "Cleaning up backup files remote..."
kubectl exec ${BOOKSTACK_POD} --container ${BOOKSTACK_CONTAINER_NAME} -- \
bash -c "rm /tmp/${FILE_NAME}.sql && \
rm /tmp/bookstack-files-backup.tar.gz"

info "Uploading backup to S3..."
CMD="aws s3 sync /mnt/backup s3://$S3_BUCKET --endpoint-url $S3_ENDPOINT --region $S3_REGION --exclude '.sse-c.key'"

if [ -n "$S3_SSE_KEY" ]; then
CMD="$CMD --sse-c AES256 --sse-c-key fileb://$S3_SSE_C_PATH"
info "Using server-side encryption with customer-provided key"
fi

eval "$CMD"
54 changes: 54 additions & 0 deletions charts/helm-bookstack-s3-backup/templates/cronjob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ .Release.Name }}-cronjob
namespace: {{ .Release.Namespace }}
spec:
schedule: "{{ .Values.cron }}"
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 2
jobTemplate:
spec:
backoffLimit: 2
template:
metadata:
name: {{ .Release.Name }}
labels:
app: {{ .Release.Name }}
spec:
serviceAccountName: {{ .Release.Name }}
restartPolicy: OnFailure

containers:
- name: bookstack-backup
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
command: ["bash", "-c", "/opt/bookstack-s3-backup/backup.sh"]
envFrom:
- secretRef:
name: {{ .Release.Name }}-secret
securityContext:
runAsUser: 34
seccompProfile:
type: RuntimeDefault
allowPrivilegeEscalation: false
runAsNonRoot: true
capabilities:
drop:
- ALL
volumeMounts:
- name: backup-volume
mountPath: /mnt/backup
- name: backup-script
mountPath: /opt/bookstack-s3-backup/backup.sh
subPath: backup.sh

volumes:
- name: backup-volume
emptyDir: {}
- name: backup-script
configMap:
name: {{ .Release.Name }}-config
items:
- key: backup.sh
path: backup.sh
defaultMode: 0555
9 changes: 9 additions & 0 deletions charts/helm-bookstack-s3-backup/templates/rbac/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["get", "list", "create"]
12 changes: 12 additions & 0 deletions charts/helm-bookstack-s3-backup/templates/rbac/rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace }}
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}
roleRef:
kind: Role
name: {{ .Release.Name }}
apiGroup: rbac.authorization.k8s.io
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace }}
17 changes: 17 additions & 0 deletions charts/helm-bookstack-s3-backup/templates/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-secret
namespace: {{ .Release.Namespace }}
type: Opaque
stringData:
AWS_ACCESS_KEY_ID: {{ .Values.s3.accessKey }}
AWS_SECRET_ACCESS_KEY: {{ .Values.s3.secretKey }}
S3_REGION: {{ .Values.s3.region }}
S3_BUCKET: {{ .Values.s3.bucket }}
S3_ENDPOINT: {{ .Values.s3.endpoint }}
S3_SSE_KEY: {{ .Values.s3.sseKey }}
BOOKSTACK_NAMESPACE: {{ .Values.bookstack.namespace }}
BOOKSTACK_APP: {{ .Values.bookstack.app }}
BOOKSTACK_CONTAINER_NAME: {{ .Values.bookstack.container_name }}

21 changes: 21 additions & 0 deletions charts/helm-bookstack-s3-backup/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Default values for helm-bookstack-s3-backup.
name: helm-bookstack-s3-backup
cron: "0 4 * * 0"

image:
repository: pascaaal/docker-awscli-kubectl-zip
tag: v1.0.8

s3:
accessKey:
secretKey:
bucket:
endpoint: # including https://
region:
sseKey:
# 32 bytes hex key (openssl rand -hex 32) for server-side encryption (encprytion enabled when set)

bookstack:
namespace:
app: # app label of the bookstack pod
container_name: