Skip to content
Open
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
103 changes: 103 additions & 0 deletions backup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Backup

This backup solution is designed for a SQLite local database and single-instance VaultWarden deployment. It can also be used for attachments, while persistent PostgreSQL/MySQL backups should be handled separately.

The backup utilizes the [vaultwarden-backup](https://github.com/ttionya/vaultwarden-backup) solution. Each scheduled backup creates an additional sidecar vaultwarden-backup container where the data folder is shared between the VaultWarden container and the backup sidecar container.

## VaultWarden Settings

For the backup to access the data, VaultWarden must run with an appropriate security context, matching the backup container. For example:

```yaml
podSecurityContext:
## @param runAsGroup group ID for VaultWarden and backup run with
## Same as default user for vaultwarden-backup
runAsUser: 1100
runAsGroup: 1100
fsGroup: 1100
```

## Parameters


| Name | Description | Value |
|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
| `backup.enabled` | Enable the backup | true/false |
| `backup.image` | Docker image, see https://hub.docker.com/r/ttionya/vaultwarden-backup | <docker image> |
| `backup.rcloneConfig` | [rClone config](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#configure-rclone-%EF%B8%8F-must-read-%EF%B8%8F). Recommended to keep it secure, sops, helm secrets | <config> |
| `backup.remoteName` | Backup remote name, see [RCLONE_REMOTE_NAME](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#rclone_remote_name) | |
| `backup.globalFlags` | rClone global flags [RCLONE_GLOBAL_FLAG](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#rclone_global_flag) | |
| `backup.zipPassword` | Password to encrypt backup archive with, see [ZIP_PASSWORD](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#zip_password) | |
| `backup.healthcheckPingKey` | See [Ping, Healthchecks.io](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#ping) | |
| `backup.timezone` | Backup timezone, see [TIMEZONE](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#timezone). | UTC |
| `backup.smtp...` | SMTP parameters, [Mail](https://github.com/ttionya/vaultwarden-backup?tab=readme-ov-file#mail) | |
| `backup.backups` | Array of backups, each of it's own schedule | |
| `backup.backups[].name` | Backup name, container to be named after it | "hourly", "weekly" etc |
| `backup.backups[].schedule` | Cron job syntax schedule | "5 * * * *" |
| `backup.backups[].keepDays` | Backup to be delete after these N days. 0 - keep forever | 7, 0 ... |
| `backup.backups[].fileDateSuffix` | Suffix for the each archive file | "-%H-%M-%S" |
| `backup.backups[].healthCheckPing` | healthchecks.io ping url, see [Ping](). Such as `https://hc-ping.com/{ping_key}/vaultwarden-<name>` Set it on most-frequent backup | https://hc-ping.com/{ping_key}/vaultwarden-main |
| | | |
| | | |

### Example
```yaml
podSecurityContext:
## @param runAsGroup group ID for VaultWarden and backup run with
## Same as default user for vaultwarden-backup
runAsUser: 1100
runAsGroup: 1100
fsGroup: 1100

backup:
enabled: true
backups:
- name: hourly
remoteDir: "/vaultWarden-main/hourly/"
# Every hour at 5 mins
cron: "5 * * * *"
keepDays: 7
fileDateSuffix: "-%H-%M-%S"
healthCheckPing: "https://hc-ping.com/<my-key>/vaultwarden-main"

- name: daily
remoteDir: "/vaultWarden-main/daily/"
# every day at 03:23
cron: "23 3 * * *"
keepDays: 60
fileDateSuffix: "-%H-%M-%S"

- name: monthly
remoteDir: "/vaultWarden-main/monthly/"
# every 20th day at 03:47
cron: "47 3 20 * *"
# Keep forever
keepDays: 0
fileDateSuffix: "-%H-%M-%S"
```

## Restore

If the deployment is lost, including PVs, it can be restored from the backup:

1. Download the backup archive from remote storage.
2. Have the `zipPassword` ready for unzipping the archive.
3. Run the restore script (requires functional kubectl):

zipFile is a part of Helm chart secret

`./restore.sh --archive <archive-file> --release <helm-release> --storage-class <Storage class>`

Example:

`./restore.sh --archive /tmp/backup.20221103-19-05-01.zip --release vaultwarden --storage-class "local-path"`

Kubernetes Namespace and context can be set with kubectl beforehand or passed as arguments. Create one if needed

The script will create a PV and PVC in the target cluster and namespace. When the VaultWarden helm chart is deployed, it will use this PV and PVC.

Use kubectl to check if another PVC is created in case of a mismatch. Adjust script parameters accordingly.




9 changes: 8 additions & 1 deletion charts/vaultwarden/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ Determine whether to use deployment or statefulset
{{- end }}
{{- end }}

{{/*
Do backup? Needs backup enabled and persistence
*/}}
{{- define "vaultwarden.doBackup" -}}
{{- and .Values.backup.enabled (hasKey .Values.storage "data") -}}
{{- end }}

{{/*
Return true when the HIBP API key should be sourced from a Kubernetes Secret.
*/}}
Expand All @@ -109,4 +116,4 @@ Return the legacy hibpApiKey string value when hibpApiKey is set and hibp is not
{{- if and (not (include "vaultwarden.hibpUseSecret" .)) (kindIs "string" .Values.hibpApiKey) .Values.hibpApiKey -}}
{{- .Values.hibpApiKey -}}
{{- end -}}
{{- end -}}
{{- end -}}
68 changes: 67 additions & 1 deletion charts/vaultwarden/templates/_podSpec.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,30 @@ priorityClassName: {{ . | quote }}
securityContext:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with .Values.initContainers }}
{{- if or .Values.initContainers (eq (include "vaultwarden.doBackup" .) "true") }}
initContainers:
{{- with .Values.initContainers }}
{{- toYaml . | nindent 2 }}
{{- end }}

{{- if eq (include "vaultwarden.doBackup" .) "true" }}
# Copy rclone config from read-only secret mount to writable share
# https://github.com/rclone/rclone/issues/3655
- name: copy-config
image: busybox:latest
command: ["sh", "-c", "cp -v /src-config/rclone.conf /config/"]
volumeMounts:
- name: backup-secret-conf
mountPath: "/src-config/"
readOnly: true
- name: config
mountPath: "/config/"
{{- end }}
{{- end }}
{{- if not .Values.enableServiceLinks }}
enableServiceLinks: false
{{- end }}

containers:
- image: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
Expand Down Expand Up @@ -253,6 +270,55 @@ containers:
successThreshold: {{ .Values.startupProbe.successThreshold }}
failureThreshold: {{ .Values.startupProbe.failureThreshold }}
{{- end }}
{{- if eq (include "vaultwarden.doBackup" .) "true" }}
{{- range .Values.backup.backups }}
- image: {{ $.Values.backup.image }}
name: backup-{{ .name }}
securityContext:
allowPrivilegeEscalation: false
env:
- name: DATA_DIR
value: {{ default "/data" .path | quote }}
- name: RCLONE_REMOTE_NAME
value: {{ $.Values.backup.remoteName | quote }}
- name: RCLONE_REMOTE_DIR
value: {{ .remoteDir | quote }}
- name: RCLONE_GLOBAL_FLAG
value: "{{ $.Values.backup.globalFlags }} --config /config/rclone.conf"
- name: CRON
value: {{ .cron | quote }}
- name: ZIP_PASSWORD
value: {{ $.Values.backup.zipPassword | quote }}
- name: BACKUP_KEEP_DAYS
value: {{ .keepDays | quote }}
- name: BACKUP_FILE_DATE_SUFFIX
value: {{ .fileDateSuffix | quote }}
- name: TIMEZONE
value: {{ $.Values.backup.timezone | quote }}
{{- if .healthCheckPing }}
- name: PING_URL
value: {{ (tpl .healthCheckPing $) | quote }}
{{- end }}
{{- if $.Values.backup.smtp.enabled }}
- name: MAIL_SMTP_ENABLE
value: "true"
- name: MAIL_SMTP_VARIABLES
value: {{ $.Values.backup.smtp.smtpVariables | quote }}
- name: MAIL_TO
value: {{ $.Values.backup.smtp.mailTo | quote }}
- name: MAIL_WHEN_SUCCESS
value: {{ $.Values.backup.smtp.mailWhenSuccess | quote }}
- name: MAIL_WHEN_FAILURE
value: {{ $.Values.backup.smtp.mailWhenFailure | quote }}
{{- end }}
# When run as non-root, script cannot create crontabs in home folcer
volumeMounts:
- name: vaultwarden-data
mountPath: {{ default "/data" $.Values.storage.data.path }}
- name: config
mountPath: "/config/"
{{- end }}
{{- end }}
{{- with .Values.sidecars }}
{{- toYaml . | nindent 2 }}
{{- end }}
Expand Down
12 changes: 11 additions & 1 deletion charts/vaultwarden/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ spec:
{{- end }}
spec:
{{- include "vaultwarden.podSpec" . | nindent 6 }}
{{- if or (.Values.storage.existingVolumeClaim) (.Values.storage.data) (.Values.storage.attachments) (.Values.rocket.tls.secretName) (.Values.extraVolumes) }}
{{- if or (.Values.storage.existingVolumeClaim) (.Values.storage.data) (.Values.storage.attachments) (.Values.rocket.tls.secretName) (.Values.extraVolumes) (eq (include "vaultwarden.doBackup" .) "true") }}
volumes:
{{- if .Values.storage.existingVolumeClaim }}
{{- with .Values.storage.existingVolumeClaim }}
Expand All @@ -64,5 +64,15 @@ spec:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if eq (include "vaultwarden.doBackup" .) "true" }}
- name: backup-secret-conf
secret:
secretName: {{ include "vaultwarden.fullname" . }}-rclone
optional: false
# readable by user/owner
defaultMode: 0400
- name: config
emptyDir: {}
{{- end }}
{{- end }}
{{- end }}
12 changes: 12 additions & 0 deletions charts/vaultwarden/templates/secret-backup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{- if eq (include "vaultwarden.doBackup" .) "true" }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "vaultwarden.fullname" . }}-rclone
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/component: vaultwarden
type: Opaque
data:
rclone.conf: {{ .Values.backup.rcloneConfig | b64enc | quote }}
{{- end }}
12 changes: 11 additions & 1 deletion charts/vaultwarden/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ spec:
{{- end }}
spec:
{{- include "vaultwarden.podSpec" . | nindent 6 }}
{{- if or (.Values.storage.existingVolumeClaim) (.Values.rocket.tls.secretName) (.Values.extraVolumes) }}
{{- if or (.Values.storage.existingVolumeClaim) (.Values.rocket.tls.secretName) (.Values.extraVolumes) (eq (include "vaultwarden.doBackup" .) "true") }}
volumes:
{{- with .Values.storage.existingVolumeClaim }}
- name: vaultwarden-data
Expand All @@ -56,6 +56,16 @@ spec:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if eq (include "vaultwarden.doBackup" .) "true" }}
- name: backup-secret-conf
secret:
secretName: {{ include "vaultwarden.fullname" . }}-rclone
optional: false
# readable by user/owner
defaultMode: 0400
- name: config
emptyDir: {}
{{- end }}
{{- end }}
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
Expand Down
49 changes: 49 additions & 0 deletions charts/vaultwarden/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -976,3 +976,52 @@ sso:
## @param sso.clientSecret.existingSecretKey When using an existing secret, specify the key which contains the password.
##
existingSecretKey: ""

## @section Backup Configuration
## https://github.com/ttionya/vaultwarden-backup
backup:
## @param backup.enabled Enable backup
##
enabled: false

## @param backup.image Backup image FQDN
##
image: "ttionya/vaultwarden-backup:1.22.0"

## @param backup.rcloneConfig Raw rClone backup
## Recommended to provide as an encrypted content
rcloneConfig: ""

remoteName: "vaultWarden"

globalFlags: ""

# cron: "5 * * * *"

zipPassword: "changeme"

# https://blog.healthchecks.io/2021/09/new-feature-slug-urls/
# Obtain from https://healthchecks.io/projects/<project-id>/settings/
healthcheckPingKey: "get-from-healthchecks.io"

timezone: "UTC"

smtp:
enable: false
smtpVariables: ""
mailTo: ""
mailWhenSuccess: false
mailWhenFailure: true

## An array for backups
## Make sure they not happen at the same time!
## e.g:
## backups:
## - name: hourly
## remoteDir: "/BitwardenBackup/hourly/"
## cron: "5 * * * *"
## keepDays: 7
## fileDateSuffix: "-%H-%M-%S"
## healthCheckPing: https://hc-ping.com/{ping_key}/vaultwarden-utility
##
backups: []
Loading
Loading