Compare commits
	
		
			15 Commits
		
	
	
		
			backup_vol
			...
			bb2-classi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5e373b24c8 | |||
| 7f1a02523e | |||
| b01ad30ea0 | |||
| 5e1032682b | |||
| 451c511554 | |||
| 87d584e4e8 | |||
| a171d9eea0 | |||
| 620ab4e3d7 | |||
| 83a3d82ea5 | |||
| 6450c80236 | |||
| 6f6a82153a | |||
| efc942c041 | |||
| 0c4bc19e2a | |||
| dde9987de6 | |||
| 5f734bc371 | 
							
								
								
									
										34
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								.drone.yml
									
									
									
									
									
								
							| @ -7,6 +7,38 @@ steps: | |||||||
|     commands: |     commands: | ||||||
|       - shellcheck backup.sh |       - shellcheck backup.sh | ||||||
|  |  | ||||||
|  |   - name: publish image | ||||||
|  |     image: plugins/docker | ||||||
|  |     settings: | ||||||
|  |       username: 3wordchant | ||||||
|  |       password: | ||||||
|  |         from_secret: git_coopcloud_tech_token_3wc | ||||||
|  |       repo: git.coopcloud.tech/coop-cloud/backup-bot-two | ||||||
|  |       tags: 1.0.0 | ||||||
|  |       registry: git.coopcloud.tech | ||||||
|  |     depends_on: | ||||||
|  |       - run shellcheck | ||||||
|  |     when: | ||||||
|  |       event: | ||||||
|  |         exclude: | ||||||
|  |           - pull_request | ||||||
|  |  | ||||||
| trigger: | trigger: | ||||||
|   branch: |   branch: | ||||||
|     - main |     - bb2-classic | ||||||
|  | --- | ||||||
|  | kind: pipeline | ||||||
|  | name: generate recipe catalogue | ||||||
|  | steps: | ||||||
|  |   - name: release a new version | ||||||
|  |     image: plugins/downstream | ||||||
|  |     settings: | ||||||
|  |       server: https://build.coopcloud.tech | ||||||
|  |       token: | ||||||
|  |         from_secret: drone_abra-bot_token | ||||||
|  |       fork: true | ||||||
|  |       repositories: | ||||||
|  |         - coop-cloud/auto-recipes-catalogue-json | ||||||
|  |  | ||||||
|  | trigger: | ||||||
|  |   event: tag | ||||||
|  | |||||||
| @ -22,3 +22,8 @@ REMOVE_BACKUP_VOLUME_AFTER_UPLOAD=1 | |||||||
| #SECRET_AWS_SECRET_ACCESS_KEY_VERSION=v1 | #SECRET_AWS_SECRET_ACCESS_KEY_VERSION=v1 | ||||||
| #AWS_ACCESS_KEY_ID=something-secret | #AWS_ACCESS_KEY_ID=something-secret | ||||||
| #COMPOSE_FILE="$COMPOSE_FILE:compose.s3.yml" | #COMPOSE_FILE="$COMPOSE_FILE:compose.s3.yml" | ||||||
|  |  | ||||||
|  | # HTTPS storage | ||||||
|  | #SECRET_HTTPS_PASSWORD_VERSION=v1 | ||||||
|  | #COMPOSE_FILE="$COMPOSE_FILE:compose.https.yml" | ||||||
|  | #RESTIC_USER=<somebody> | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ export DOCKER_CONTEXT=$SERVER_NAME | |||||||
| # or this: | # or this: | ||||||
| #export AWS_SECRET_ACCESS_KEY_FILE=s3 | #export AWS_SECRET_ACCESS_KEY_FILE=s3 | ||||||
| #export AWS_ACCESS_KEY_ID=easter-october-emphatic-tug-urgent-customer | #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 | # optionally limit subset of services for testing | ||||||
| #export SERVICES_OVERRIDE="ghost_domain_tld_app ghost_domain_tld_db" | #export SERVICES_OVERRIDE="ghost_domain_tld_app ghost_domain_tld_db" | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | FROM docker:24.0.6-dind | ||||||
|  |  | ||||||
|  | RUN apk add --upgrade --no-cache \ | ||||||
|  |   bash \ | ||||||
|  |   curl \ | ||||||
|  |   jq \ | ||||||
|  |   restic | ||||||
|  |  | ||||||
|  | COPY backup.sh /usr/bin/backup.sh | ||||||
|  | COPY setup-cron.sh /usr/bin/setup-cron.sh | ||||||
|  | RUN chmod +x /usr/bin/backup.sh /usr/bin/setup-cron.sh | ||||||
|  |  | ||||||
|  | ENTRYPOINT [ "/usr/bin/setup-cron.sh" ] | ||||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @ -4,7 +4,7 @@ | |||||||
|  |  | ||||||
| _This Time, It's Easily Configurable_ | _This Time, It's Easily Configurable_ | ||||||
|  |  | ||||||
| Automatically take backups from all volumes of running Docker Swarm services and runs pre- and post commands. | Automatically take backups from running Docker Swarm services into a volume. | ||||||
|  |  | ||||||
| ## Background | ## Background | ||||||
|  |  | ||||||
| @ -49,13 +49,15 @@ services: | |||||||
|   db: |   db: | ||||||
|     deploy: |     deploy: | ||||||
|       labels: |       labels: | ||||||
|         backupbot.backup: ${BACKUP:-"true"}  |         backupbot.backup: "true" | ||||||
|         backupbot.backup.pre-hook: 'mysqldump -u root -p"$(cat /run/secrets/db_root_password)" -f /volume_path/dump.db' |         backupbot.backup.pre-hook: 'mysqldump -u root -p"$(cat /run/secrets/db_root_password)" -f /tmp/dump/dump.db' | ||||||
|         backupbot.backup.post-hook: "rm -rf /volume_path/dump.db" |         backupbot.backup.post-hook: "rm -rf /tmp/dump/dump.db" | ||||||
|  |         backupbot.backup.path: "/tmp/dump/,/etc/foo/" | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| - `backupbot.backup` -- set to `true` to back up this service (REQUIRED) | - `backupbot.backup` -- set to `true` to back up this service (REQUIRED) | ||||||
| - `backupbot.backup.pre-hook` -- command to run before copying files (optional), save all dumps into the volumes | - `backupbot.backup.path` -- comma separated list of file paths within the service to copy (REQUIRED) | ||||||
|  | - `backupbot.backup.pre-hook` -- command to run before copying files (optional) | ||||||
| - `backupbot.backup.post-hook` -- command to run after copying files (optional) | - `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. | 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. | ||||||
|  | |||||||
							
								
								
									
										62
									
								
								backup.sh
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								backup.sh
									
									
									
									
									
								
							| @ -1,18 +1,18 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
|  |  | ||||||
| set -e |  | ||||||
|  |  | ||||||
| server_name="${SERVER_NAME:?SERVER_NAME not set}" | server_name="${SERVER_NAME:?SERVER_NAME not set}" | ||||||
|  |  | ||||||
| restic_password_file="${RESTIC_PASSWORD_FILE:?RESTIC_PASSWORD_FILE not set}" | restic_password_file="${RESTIC_PASSWORD_FILE:?RESTIC_PASSWORD_FILE not set}" | ||||||
|  |  | ||||||
| restic_host="${RESTIC_HOST:?RESTIC_HOST not set}" | restic_host="${RESTIC_HOST:?RESTIC_HOST not set}" | ||||||
|  |  | ||||||
| backup_paths=() | backup_path="${BACKUP_DEST:?BACKUP_DEST not set}" | ||||||
|  |  | ||||||
| # shellcheck disable=SC2153 | # shellcheck disable=SC2153 | ||||||
| ssh_key_file="${SSH_KEY_FILE}" | ssh_key_file="${SSH_KEY_FILE}" | ||||||
| s3_key_file="${AWS_SECRET_ACCESS_KEY_FILE}" | s3_key_file="${AWS_SECRET_ACCESS_KEY_FILE}" | ||||||
|  | # shellcheck disable=SC2153 | ||||||
|  | https_password_file="${HTTPS_PASSWORD_FILE}" | ||||||
|  |  | ||||||
| restic_repo= | restic_repo= | ||||||
| restic_extra_options= | restic_extra_options= | ||||||
| @ -41,8 +41,15 @@ if [ -n "$s3_key_file" ] && [ -f "$s3_key_file" ] && [ -n "$AWS_ACCESS_KEY_ID" ] | |||||||
| 	restic_repo="s3:$restic_host:/$server_name" | 	restic_repo="s3:$restic_host:/$server_name" | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  | if [ -n "$https_password_file" ] && [ -f "$https_password_file" ]; then | ||||||
|  | 	HTTPS_PASSWORD="$(cat "${https_password_file}")" | ||||||
|  | 	export HTTPS_PASSWORD | ||||||
|  | 	restic_user="${RESTIC_USER:?RESTIC_USER not set}" | ||||||
|  | 	restic_repo="rest:https://$restic_user:$HTTPS_PASSWORD@$restic_host" | ||||||
|  | fi | ||||||
|  |  | ||||||
| if [ -z "$restic_repo" ]; then | if [ -z "$restic_repo" ]; then | ||||||
| 	echo "you must configure either SFTP or S3 storage, see README" | 	echo "you must configure either SFTP, S3, or HTTPS storage, see README" | ||||||
| 	exit 1 | 	exit 1 | ||||||
| fi | fi | ||||||
|  |  | ||||||
| @ -73,8 +80,8 @@ else | |||||||
| 	mapfile -t services < <(docker service ls --format '{{ .Name }}') | 	mapfile -t services < <(docker service ls --format '{{ .Name }}') | ||||||
| fi | fi | ||||||
|  |  | ||||||
| post_commands=() |  | ||||||
| if [[ \ $*\  != *\ --skip-backup\ * ]]; then | if [[ \ $*\  != *\ --skip-backup\ * ]]; then | ||||||
|  | 	rm -rf "${backup_path}" | ||||||
|  |  | ||||||
| 	for service in "${services[@]}"; do | 	for service in "${services[@]}"; do | ||||||
| 		echo "service: $service" | 		echo "service: $service" | ||||||
| @ -82,21 +89,36 @@ if [[ \ $*\  != *\ --skip-backup\ * ]]; then | |||||||
| 		if echo "$details" | jq -r '.["backupbot.backup"]' | grep -q 'true'; then | 		if echo "$details" | jq -r '.["backupbot.backup"]' | grep -q 'true'; then | ||||||
| 			pre=$(echo "$details" | jq -r '.["backupbot.backup.pre-hook"]') | 			pre=$(echo "$details" | jq -r '.["backupbot.backup.pre-hook"]') | ||||||
| 			post=$(echo "$details" | jq -r '.["backupbot.backup.post-hook"]') | 			post=$(echo "$details" | jq -r '.["backupbot.backup.post-hook"]') | ||||||
|  | 			path=$(echo "$details" | jq -r '.["backupbot.backup.path"]') | ||||||
|  |  | ||||||
|  | 			if [ "$path" = "null" ]; then | ||||||
|  | 				echo "ERROR: missing 'path' for $service" | ||||||
|  | 				continue # or maybe exit? | ||||||
|  | 			fi | ||||||
|  |  | ||||||
| 			container=$(docker container ls -f "name=$service" --format '{{ .ID }}') | 			container=$(docker container ls -f "name=$service" --format '{{ .ID }}') | ||||||
| 			stack_name=$(echo "$details" | jq -r '.["com.docker.stack.namespace"]') |  | ||||||
|  | 			echo "backing up $service" | ||||||
|  |  | ||||||
| 			if [ "$pre" != "null" ]; then | 			if [ "$pre" != "null" ]; then | ||||||
| 				# run the precommand | 				# run the precommand | ||||||
| 				echo "executing precommand $pre in container $container" | 				# shellcheck disable=SC2086 | ||||||
| 				docker exec "$container" sh -c "$pre" | 				docker exec "$container" sh -c "$pre" | ||||||
| 			fi | 			fi | ||||||
| 			if [ "$post" != "null" ]; then |  | ||||||
| 				# append post command |  | ||||||
| 				post_commands+=("docker exec $container sh -c \"$post\"") |  | ||||||
| 			fi |  | ||||||
|  |  | ||||||
| 			# add volume paths to backup path | 			# run the backup | ||||||
| 			backup_paths+=(/var/lib/docker/volumes/"${stack_name}"_*) | 			for p in ${path//,/ }; do | ||||||
|  | 				# creates the parent folder, so `docker cp` has reliable behaviour no matter if $p ends with `/` or `/.` | ||||||
|  | 				dir=$backup_path/$service/$(dirname "$p") | ||||||
|  | 				test -d "$dir" || mkdir -p "$dir" | ||||||
|  | 				docker cp -a "$container:$p" "$dir/$(basename "$p")" | ||||||
|  | 			done | ||||||
|  |  | ||||||
|  | 			if [ "$post" != "null" ]; then | ||||||
|  | 				# run the postcommand | ||||||
|  | 				# shellcheck disable=SC2086 | ||||||
|  | 				docker exec "$container" sh -c "$post" | ||||||
|  | 			fi | ||||||
| 		fi | 		fi | ||||||
| 	done | 	done | ||||||
|  |  | ||||||
| @ -108,12 +130,10 @@ if [[ \ $*\  != *\ --skip-backup\ * ]]; then | |||||||
| fi | fi | ||||||
|  |  | ||||||
| if [[ \ $*\  != *\ --skip-upload\ * ]]; then | if [[ \ $*\  != *\ --skip-upload\ * ]]; then | ||||||
| 	echo "${backup_paths[@]}" | 	_restic backup --host "$server_name" --tag coop-cloud "$backup_path" | ||||||
| 	_restic backup --host "$server_name" --tag coop-cloud "${backup_paths[@]}" |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| # run post commands | 	if [ "$REMOVE_BACKUP_VOLUME_AFTER_UPLOAD" -eq 1 ]; then | ||||||
| for post in "${post_commands[@]}"; do | 		echo "Cleaning up ${backup_path}" | ||||||
| 	echo "executing postcommand $post" | 		rm -rf "${backup_path:?}"/* | ||||||
| 	eval "$post" | 	fi | ||||||
| done | fi | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								compose.https.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								compose.https.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | --- | ||||||
|  | version: "3.8" | ||||||
|  | services: | ||||||
|  |   app: | ||||||
|  |     environment: | ||||||
|  |       - HTTPS_PASSWORD_FILE=/run/secrets/https_password | ||||||
|  |       - RESTIC_USER | ||||||
|  |     secrets: | ||||||
|  |       - source: https_password | ||||||
|  |         mode: 0400 | ||||||
|  |  | ||||||
|  | secrets: | ||||||
|  |   https_password: | ||||||
|  |     external: true | ||||||
|  |     name: ${STACK_NAME}_https_password_${SECRET_HTTPS_PASSWORD_VERSION} | ||||||
							
								
								
									
										31
									
								
								compose.yml
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								compose.yml
									
									
									
									
									
								
							| @ -2,10 +2,11 @@ | |||||||
| version: "3.8" | version: "3.8" | ||||||
| services: | services: | ||||||
|   app: |   app: | ||||||
|     image: docker:24.0.2-dind |     image: git.coopcloud.tech/coop-cloud/backup-bot-two:1.0.0 | ||||||
|  | #    build: . | ||||||
|     volumes: |     volumes: | ||||||
|       - "/var/run/docker.sock:/var/run/docker.sock" |       - "/var/run/docker.sock:/var/run/docker.sock" | ||||||
|       - "/var/lib/docker/volumes/:/var/lib/docker/volumes/:ro" |       - "backups:/backups" | ||||||
|     environment: |     environment: | ||||||
|       - CRON_SCHEDULE |       - CRON_SCHEDULE | ||||||
|       - RESTIC_REPO |       - RESTIC_REPO | ||||||
| @ -18,25 +19,17 @@ services: | |||||||
|       - restic_password |       - restic_password | ||||||
|     deploy: |     deploy: | ||||||
|       labels: |       labels: | ||||||
|         - coop-cloud.${STACK_NAME}.version=0.1.0+latest |         - "traefik.enable=true" | ||||||
|     configs: |         - "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=8008" | ||||||
|       - source: entrypoint |         - "traefik.http.routers.${STACK_NAME}.rule=" | ||||||
|         target: /entrypoint.sh |         - "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure" | ||||||
|         mode: 0555 |         - "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}" | ||||||
|       - source: backup |         - coop-cloud.${STACK_NAME}.version=0.2.1+1.0.0 | ||||||
|         target: /backup.sh |  | ||||||
|         mode: 0555 | volumes: | ||||||
|     entrypoint: ['/entrypoint.sh'] |   backups: | ||||||
|  |  | ||||||
| secrets: | secrets: | ||||||
|   restic_password: |   restic_password: | ||||||
|     external: true |     external: true | ||||||
|     name: ${STACK_NAME}_restic_password_${SECRET_RESTIC_PASSWORD_VERSION} |     name: ${STACK_NAME}_restic_password_${SECRET_RESTIC_PASSWORD_VERSION} | ||||||
|  |  | ||||||
| configs: |  | ||||||
|   entrypoint: |  | ||||||
|     name: ${STACK_NAME}_entrypoint_${ENTRYPOINT_VERSION} |  | ||||||
|     file: entrypoint.sh |  | ||||||
|   backup: |  | ||||||
|     name: ${STACK_NAME}_backup_${BACKUP_VERSION} |  | ||||||
|     file: backup.sh |  | ||||||
|  | |||||||
| @ -1,12 +1,11 @@ | |||||||
| #!/bin/sh | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| set -e | set -e | ||||||
| 
 | set -o pipefail | ||||||
| apk add --upgrade --no-cache bash curl jq restic |  | ||||||
| 
 | 
 | ||||||
| cron_schedule="${CRON_SCHEDULE:?CRON_SCHEDULE not set}" | cron_schedule="${CRON_SCHEDULE:?CRON_SCHEDULE not set}" | ||||||
| 
 | 
 | ||||||
| echo "$cron_schedule /backup.sh" | crontab - | echo "$cron_schedule /usr/bin/backup.sh" | crontab - | ||||||
| crontab -l | crontab -l | ||||||
| 
 | 
 | ||||||
| crond -f -d8 -L /dev/stdout | crond -f -d8 -L /dev/stdout | ||||||
		Reference in New Issue
	
	Block a user