diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index e2f0fe8..0000000 --- a/.drone.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -kind: pipeline -name: linters -steps: - - name: run shellcheck - image: koalaman/shellcheck-alpine - commands: - - shellcheck backup.sh - -trigger: - branch: - - main diff --git a/.env.sample b/.env.sample index 3db56ad..812088b 100644 --- a/.env.sample +++ b/.env.sample @@ -1,34 +1,10 @@ -TYPE=backup-bot-two - -SECRET_RESTIC_PASSWORD_VERSION=v1 - -COMPOSE_FILE=compose.yml +STACK_NAME=backup-bot-two RESTIC_REPOSITORY=/backups/restic CRON_SCHEDULE='30 3 * * *' -# Push Notifiactions +# Push Notifications #PUSH_URL_START=https://status.example.com/api/push/xxxxxxxxxx?status=up&msg=start #PUSH_URL_SUCCESS=https://status.example.com/api/push/xxxxxxxxxx?status=up&msg=OK #PUSH_URL_FAIL=https://status.example.com/api/push/xxxxxxxxxx?status=down&msg=fail - -# swarm-cronjob, instead of built-in cron -#COMPOSE_FILE="$COMPOSE_FILE:compose.swarm-cronjob.yml" - -# SSH storage -#SECRET_SSH_KEY_VERSION=v1 -#SSH_HOST_KEY="hostname ssh-rsa AAAAB3... -#COMPOSE_FILE="$COMPOSE_FILE:compose.ssh.yml" - -# S3 storage -#SECRET_AWS_SECRET_ACCESS_KEY_VERSION=v1 -#AWS_ACCESS_KEY_ID=something-secret -#COMPOSE_FILE="$COMPOSE_FILE:compose.s3.yml" - -# Secret restic repository -# use a secret to store the RESTIC_REPOSITORY if the repository location contains a secret value -# i.E rest:https://user:SECRET_PASSWORD@host:8000/ -# it overwrites the RESTIC_REPOSITORY variable -#SECRET_RESTIC_REPO_VERSION=v1 -#COMPOSE_FILE="$COMPOSE_FILE:compose.secret.yml" diff --git a/.envrc.sample b/.envrc.sample deleted file mode 100644 index 5d58849..0000000 --- a/.envrc.sample +++ /dev/null @@ -1,17 +0,0 @@ -export RESTIC_HOST="user@domain.tld" -export RESTIC_PASSWORD_FILE=/run/secrets/restic-password -export BACKUP_DEST=/backups - -export SERVER_NAME=domain.tld -export DOCKER_CONTEXT=$SERVER_NAME - -# uncomment either this: -#export SSH_KEY_FILE=~/.ssh/id_rsa -# or this: -#export AWS_SECRET_ACCESS_KEY_FILE=s3 -#export AWS_ACCESS_KEY_ID=easter-october-emphatic-tug-urgent-customer -# or this: -#export HTTPS_PASSWORD_FILE=/run/secrets/https_password - -# optionally limit subset of services for testing -#export SERVICES_OVERRIDE="ghost_domain_tld_app ghost_domain_tld_db" diff --git a/.gitignore b/.gitignore index 1dd0e0a..2eea525 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/testing +.env \ No newline at end of file diff --git a/README.md b/README.md index af76c69..5d4be15 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,27 @@ # Backupbot II -[![Build Status](https://build.coopcloud.tech/api/badges/coop-cloud/backup-bot-two/status.svg)](https://build.coopcloud.tech/coop-cloud/backup-bot-two) - -_This Time, It's Easily Configurable_ - -Automatically take backups from all volumes of running Docker Swarm services and runs pre- and post commands. - - - -* **Category**: Utilities -* **Status**: 0, work-in-progress -* **Image**: [`thecoopcloud/backup-bot-two`](https://hub.docker.com/r/thecoopcloud/backup-bot-two), 4, upstream -* **Healthcheck**: No -* **Backups**: N/A -* **Email**: N/A -* **Tests**: No -* **SSO**: N/A - - +Wiki Cafe's configuration for a Backupbot II deployment. Originally slimmed down from an `abra` [recipe](https://git.coopcloud.tech/coop-cloud/backup-bot-two) by [Co-op Cloud](https://coopcloud.tech/). -## Background +## Deploying the app with Docker Swarm -There are lots of Docker volume backup systems; all of them have one or both of these limitations: - - You need to define all the volumes to back up in the configuration system - - Backups require services to be stopped to take consistent copies +Set the environment variables from the .env file during the shell session. -Backupbot II tries to help, by -1. **letting you define backups using Docker labels**, so you can **easily collect your backups for use with another system** like docker-volume-backup. -2. **running pre- and post-commands** before and after backups, for example to use database tools to take a backup from a running service. - -## Deployment - -### With Co-op Cloud - - -* `abra app new backup-bot-two` -* `abra app config ` - - set storage options. Either configure `CRON_SCHEDULE`, or set up `swarm-cronjob` -* `abra app secret generate -a ` -* `abra app deploy ` - -## Configuration - -Per default Backupbot stores the backups locally in the repository `/backups/restic`, which is accessible as volume at `/var/lib/docker/volumes/_backups/_data/restic/` - -The backup location can be changed using the `RESTIC_REPOSITORY` env variable. - -### S3 Storage - -To use S3 storage as backup location set the following envs: ``` -RESTIC_REPOSITORY=s3:/ -SECRET_AWS_SECRET_ACCESS_KEY_VERSION=v1 -AWS_ACCESS_KEY_ID= -COMPOSE_FILE="$COMPOSE_FILE:compose.s3.yml" +set -a && source .env && set +a ``` -and add your `` as docker secret: -`abra app secret insert aws_secret_access_key v1 ` -See [restic s3 docs](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#amazon-s3) for more information. +Set the secrets. -### SFTP Storage - -> With sftp it is not possible to prevent the backupbot from deleting backups in case of a compromised machine. Therefore we recommend to use S3, REST or rclone server without delete permissions. - -To use SFTP storage as backup location set the following envs: ``` -RESTIC_REPOSITORY=sftp:user@host:/restic-repo-path -SECRET_SSH_KEY_VERSION=v1 -SSH_HOST_KEY="hostname ssh-rsa AAAAB3... -COMPOSE_FILE="$COMPOSE_FILE:compose.ssh.yml" +printf "SECRET_HERE" | docker secret create SECRET_NAME - ``` -To get the `SSH_HOST_KEY` run the following command `ssh-keyscan ` -Generate an ssh keypair: `ssh-keygen -t ed25519 -f backupkey -P ''` -Add the key to your `authorized_keys`: -`ssh-copy-id -i backupkey @` -Add your `SSH_KEY` as docker secret: -``` -abra app secret insert ssh_key v1 """$(cat backupkey) -""" -``` -> Attention: This command needs to be executed exactly as stated above, because it places a trailing newline at the end, if this is missing you will get the following error: `Load key "/run/secrets/ssh_key": error in libcrypto` +Deploy using the `-c` flag to specify one or multiple compose files. -### Restic REST server Storage - -You can simply set the `RESTIC_REPOSITORY` variable to your REST server URL `rest:http://host:8000/`. -If you access the REST server with a password `rest:https://user:pass@host:8000/` you should hide the whole URL containing the password inside a secret. -Uncomment these lines: ``` -SECRET_RESTIC_REPO_VERSION=v1 -COMPOSE_FILE="$COMPOSE_FILE:compose.secret.yml" +docker stack deploy backup-bot-two -c compose.yaml ``` -Add your REST server url as secret: -``` -`abra app secret insert restic_repo v1 "rest:https://user:pass@host:8000/"` -``` -The secret will overwrite the `RESTIC_REPOSITORY` variable. - - -See [restic REST docs](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#rest-server) for more information. ## Push notifications @@ -113,71 +34,82 @@ PUSH_URL_SUCCESS=https://status.example.com/api/push/xxxxxxxxxx?status=up&msg=OK PUSH_URL_FAIL=https://status.example.com/api/push/xxxxxxxxxx?status=down&msg=fail ``` +## Commands -## Usage -Run the cronjob that creates a backup, including the push notifications and docker logging: -`abra app cmd app run_cron` +- Find the ID or name of the backup container: + ``` + docker ps --filter "name=backup-bot-two_app" + ``` -Create a backup of all apps: +2. Run the desired command using `docker exec`: + ``` + docker exec -it backup [options] + ``` + Replace `` with the ID or name of the backup container. -`abra app run app -- backup create` + Available commands: + - `create`: Initiate the backup process. + - `restore`: Restore a specific snapshot to a target directory. + - `snapshots`: List available snapshots. + - `ls`: List files in a specific snapshot. + - `download`: Download specific files, volumes, or secrets from a snapshot. -> The apps to backup up need to be deployed + Options: + - `--host`, `-h`: Specify the service name (e.g., `app`). + - `--repo`, `-r`: Specify the Restic repository location (e.g., `/run/secrets/restic_repo`). + - `--log`, `-l`: Set the log level (e.g., `debug`, `info`, `warning`, `error`). + - `--machine-logs`, `-m`: Enable machine-readable JSON logging. -Create an individual backup: +## Examples -`abra app run app -- backup --host create` - -Create a backup to a local repository: - -`abra app run app -- backup create -r /backups/restic` - -> It is recommended to shutdown/undeploy an app before restoring the data - -Restore the latest snapshot of all including apps: - -`abra app run app -- backup restore` - -Restore a specific snapshot of an individual app: - -`abra app run app -- backup --host restore --snapshot ` - -Show all snapshots: - -`abra app run app -- backup snapshots` - -Show all snapshots containing a specific app: - -`abra app run app -- backup --host snapshots` - -Show all files inside the latest snapshot (can be very verbose): - -`abra app run app -- backup ls` - -Show specific files inside a selected snapshot: - -`abra app run app -- backup ls --snapshot --path /var/lib/docker/volumes/` - -Download files from a snapshot: +Create a backup: ``` -filename=$(abra app run app -- backup download --snapshot --path ) -abra app cp app:$filename . +docker exec -it backup create --host app ``` -## Run restic +Restore a snapshot: ``` -abra app run app bash -export AWS_SECRET_ACCESS_KEY=$(cat $AWS_SECRET_ACCESS_KEY_FILE) -export RESTIC_PASSWORD=$(cat $RESTIC_PASSWORD_FILE) -restic snapshots +docker exec -it backup restore --snapshot --target /path/to/restore ``` +List snapshots: + + ``` + docker exec -it backup snapshots + ``` + +List files in a snapshot: + +``` +docker exec -it backup ls --snapshot --path /path/to/directory +``` + +Download files, volumes, or secrets from a snapshot: + +``` +docker exec -it backup download --snapshot [--path /path/to/file] [--volumes] [--secrets] +``` + +Note: Make sure to replace `` and `` with the appropriate values for your setup. + +Remember to review and adjust the Docker Compose file and environment variables according to your specific requirements before running the backup commands. + +When using `docker exec`, you don't need to specify the volume mounts or the Restic repository location as command-line arguments because they are already defined in the Docker Compose file and are available within the running container. + +If you need to access the downloaded files, volumes, or secrets from the backup, you can use `docker cp` to copy them from the container to the host machine: + +``` +docker cp :/path/to/backup/file /path/on/host +``` + +This allows you to retrieve the backed-up data from the container. + ## Recipe Configuration -Like Traefik, or `swarm-cronjob`, Backupbot II uses access to the Docker socket to read labels from running Docker Swarm services: +Backupbot II uses access to the Docker socket to read labels from running Docker Swarm services: ``` services: @@ -194,5 +126,3 @@ services: - `backupbot.backup.post-hook` -- command to run after copying files (optional) As in the above example, you can reference Docker Secrets, e.g. for looking up database passwords, by reading the files in `/run/secrets` directly. - -[abra]: https://git.autonomic.zone/autonomic-cooperative/abra diff --git a/abra.sh b/abra.sh deleted file mode 100644 index 29c8f69..0000000 --- a/abra.sh +++ /dev/null @@ -1,11 +0,0 @@ -export ENTRYPOINT_VERSION=v1 -export BACKUPBOT_VERSION=v1 -export SSH_CONFIG_VERSION=v1 - -run_cron () { - schedule="$(crontab -l | tr -s " " | cut -d ' ' -f-5)" - rm -f /tmp/backup.log - echo "* * * * * $(crontab -l | tr -s " " | cut -d ' ' -f6-)" | crontab - - while [ ! -f /tmp/backup.log ]; do sleep 1; done - echo "$schedule $(crontab -l | tr -s " " | cut -d ' ' -f6-)" | crontab - -} diff --git a/compose.s3.yml b/compose.s3.yml deleted file mode 100644 index 25a8bfb..0000000 --- a/compose.s3.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -version: "3.8" -services: - app: - environment: - - AWS_ACCESS_KEY_ID - - AWS_SECRET_ACCESS_KEY_FILE=/run/secrets/aws_secret_access_key - secrets: - - aws_secret_access_key - -secrets: - aws_secret_access_key: - external: true - name: ${STACK_NAME}_aws_secret_access_key_${SECRET_AWS_SECRET_ACCESS_KEY_VERSION} diff --git a/compose.secret.yml b/compose.secret.yml deleted file mode 100644 index 22fdb9f..0000000 --- a/compose.secret.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -version: "3.8" -services: - app: - environment: - - RESTIC_REPOSITORY_FILE=/run/secrets/restic_repo - secrets: - - restic_repo - -secrets: - restic_repo: - external: true - name: ${STACK_NAME}_restic_repo_${SECRET_RESTIC_REPO_VERSION} diff --git a/compose.ssh.yml b/compose.ssh.yml deleted file mode 100644 index bb48647..0000000 --- a/compose.ssh.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -version: "3.8" -services: - app: - environment: - - SSH_KEY_FILE=/run/secrets/ssh_key - - SSH_HOST_KEY - secrets: - - source: ssh_key - mode: 0400 - configs: - - source: ssh_config - target: /root/.ssh/config - -secrets: - ssh_key: - external: true - name: ${STACK_NAME}_ssh_key_${SECRET_SSH_KEY_VERSION} - -configs: - ssh_config: - name: ${STACK_NAME}_ssh_config_${SSH_CONFIG_VERSION} - file: ssh_config diff --git a/compose.swarm-cronjob.yml b/compose.swarm-cronjob.yml deleted file mode 100644 index b0fde90..0000000 --- a/compose.swarm-cronjob.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -version: "3.8" -services: - app: - deploy: - mode: replicated - replicas: 0 - labels: - - "swarm.cronjob.enable=true" - # Note(3wc): every 5m, testing - - "swarm.cronjob.schedule=*/5 * * * *" - # Note(3wc): blank label to be picked up by `abra recipe sync` - restart_policy: - condition: none - entrypoint: [ "/usr/bin/backup.sh" ] diff --git a/compose.yml b/compose.yaml similarity index 54% rename from compose.yml rename to compose.yaml index 3a0292d..1c3c3d2 100644 --- a/compose.yml +++ b/compose.yaml @@ -1,5 +1,3 @@ ---- -version: "3.8" services: app: image: docker:24.0.7-dind @@ -7,18 +5,11 @@ services: - "/var/run/docker.sock:/var/run/docker.sock" - "/var/lib/docker/volumes/:/var/lib/docker/volumes/" - "/var/lib/docker/containers/:/var/lib/docker/containers/:ro" - - backups:/backups environment: - CRON_SCHEDULE - - RESTIC_REPOSITORY - - RESTIC_PASSWORD_FILE=/run/secrets/restic_password + - RESTIC_REPOSITORY_FILE=/run/secrets/restic_repo secrets: - - restic_password - deploy: - labels: - - coop-cloud.${STACK_NAME}.version=0.1.0+latest - - coop-cloud.${STACK_NAME}.timeout=${TIMEOUT:-300} - - coop-cloud.backupbot.enabled=true + - restic_repo configs: - source: entrypoint target: /entrypoint.sh @@ -27,7 +18,6 @@ services: target: /usr/bin/backup mode: 0555 entrypoint: ['/entrypoint.sh'] - #entrypoint: ['tail', '-f','/dev/null'] healthcheck: test: "pgrep crond" interval: 30s @@ -36,17 +26,14 @@ services: start_period: 5m secrets: - restic_password: + restic_repo: external: true - name: ${STACK_NAME}_restic_password_${SECRET_RESTIC_PASSWORD_VERSION} - -volumes: - backups: + name: ${STACK_NAME}_restic_repo configs: entrypoint: - name: ${STACK_NAME}_entrypoint_${ENTRYPOINT_VERSION} + name: ${STACK_NAME}_entrypoint file: entrypoint.sh backupbot: - name: ${STACK_NAME}_backupbot_${BACKUPBOT_VERSION} + name: ${STACK_NAME}_backupbot file: backupbot.py diff --git a/entrypoint.sh b/entrypoint.sh index 6af0132..ab7a2b1 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -7,11 +7,6 @@ apk add --upgrade --no-cache restic bash python3 py3-pip py3-click py3-docker-py # Todo use requirements file with specific versions pip install --break-system-packages resticpy==1.0.2 -if [ -n "$SSH_HOST_KEY" ] -then - echo "$SSH_HOST_KEY" > /root/.ssh/known_hosts -fi - cron_schedule="${CRON_SCHEDULE:?CRON_SCHEDULE not set}" if [ -n "$PUSH_URL_START" ] diff --git a/release/1.0.0+latest b/release/1.0.0+latest deleted file mode 100644 index 5495d57..0000000 --- a/release/1.0.0+latest +++ /dev/null @@ -1,3 +0,0 @@ -Breaking Change: the variables `SERVER_NAME` and `RESTIC_HOST` are merged into `RESTIC_REPOSITORY`. The format can be looked up here: https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html -ssh/sftp: `sftp:user@host:/repo-path` -S3: `s3:https://s3.example.com/bucket_name` diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 7190a60..0000000 --- a/renovate.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json" -} diff --git a/ssh_config b/ssh_config deleted file mode 100644 index 294dc88..0000000 --- a/ssh_config +++ /dev/null @@ -1,4 +0,0 @@ -Host * - IdentityFile /run/secrets/ssh_key - ServerAliveInterval 60 - ServerAliveCountMax 240