Commit 66a383ad authored by Samuel Laurén's avatar Samuel Laurén
Browse files

Initial commit

parents
# Backup Docker Containers :floppy_disk:
This repository contains a Bash script for backing up Docker containers along
with their associated data volumes to a Git repository. The backups can be
automatically signed and encrypted to one or more recipients using GPG.
container-backup.sh [-znh] [-s KEY] [-e KEY]... DIR CONTAINER
Export CONTAINERs to DIR. Containers are stopped before they are exported.
This is done in the order they are specified in the command line. Afterwards,
the containers are restarted in the reverse order.
-s KEY Sign backups with KEY
-e KEY Encrypt backups for KEY (can appear multiple times)
-m MANAGER Use MANAGER for container management
-z Compress backups
-n Do not push
-h Display this help
## Example Invocation
# container-backup.sh -z -n backup-dir web-server database-server
## About External Service Managers
Since containers are often managed by an external service manager such as
`systemd`, the script supports handing off container management commands to
those. This can be done using the `-m` option. Example manager can be found in
`systemctl-wrap.sh`.
#!/usr/bin/env bash
# Export containers and their associated mount points into a git repository.
set -euo pipefail
declare -a DEPS=( docker gpg jq tar git gzip sha1sum )
declare -a STOPPED=()
function export-container { # $1: container
echo "Exporting container \"${1}\""
docker save "$(get-image "$c")" | compress | shield > "${STORE}/${1}/${1}.tar$(suffix)"
# Probably not necessary but nice to have anyway
docker inspect "$c" | compress | shield > "${STORE}/${1}/${1}-properties.json$(suffix)"
}
function get-image { # $1: container
docker inspect "$1" | \
jq -re '.[] | .Image'
}
function get-mounts { # $1: container
docker inspect "$1" | \
jq -r '.[] | .Mounts | .[] | .Source,.Name'
}
function save-mount { # $1: container -> $2: source -> $3: name
echo "Saving mount content for \"${name}\""
tar -cp -C "$2" . | compress | shield > "${STORE}/${1}/${3}.tar$(suffix)"
}
function backup {
local source name
for c in "${CONTAINERS[@]}"; do
export-container "$c"
get-mounts "$c" | {
while true; do
read source || break
read name || break
if [[ "$name" = "null" ]]; then
read -d" " name <<< "$(sha1sum -b <<< "$source")"
fi
save-mount "$c" "$source" "$name"
done
}
done
}
function upload {
# Not exactly the best way to store big binary blobs...
echo "Adding changes"
pushd "$STORE"
git add .
git commit -m "Backup $(date)"
if [[ "$PUSH" -eq 1 ]]; then
echo "Uploading"
git push
fi
popd
}
function prepare {
[[ -d "$STORE" && -d "$STORE/.git" ]] \
|| error "\"$STORE\" is not a repository"
for c in "${CONTAINERS[@]}"; do
mkdir -p "$STORE/$c"
done
}
function stop-containers {
echo "Stopping containers: ${CONTAINERS[@]}"
for c in "${CONTAINERS[@]}"; do
status="$(docker inspect "$c" | jq -re '.[] | .State | .Running')"
if [[ "$status" = "true" ]]; then
STOPPED+=("$c")
fi
done
$MANAGER stop "${STOPPED[@]}"
}
function shield {
# FIXME: Proper quoting
local args=""
if [[ -n "$SIGNER" ]]; then
args="$args --local-user $SIGNER --sign"
fi
if [[ -n "${RECIPIENTS+1}" ]]; then
args="$args --trust-model=always --encrypt $(printf "%s" "${RECIPIENTS[@]/#/ --recipient }")"
fi
if [[ -n "$args" ]]; then
gpg $args
else
tee
fi
}
function compress {
[[ "$COMPRESS" -eq 1 ]] && gzip -9 || tee
}
function suffix {
if [[ "$COMPRESS" -eq 1 ]]; then
echo -n ".gz"
fi
if [[ -n "${SIGNER}" || -n "${RECIPIENTS+1}" ]]; then
echo -n ".gpg"
fi
}
function resume-containers {
if [[ -n "${STOPPED+1}" ]]; then
local -a reversed=()
for (( i=${#STOPPED[@]}-1; i>=0; i-- )); do
reversed+=("${STOPPED[i]}")
done
echo "Restarting stopped containers: ${STOPPED[@]}"
$MANAGER start "${reversed[@]}"
fi
}
trap resume-containers EXIT
function check-deps {
for d in "${DEPS[@]}"; do
hash "$d" 2>/dev/null || error "$d is required"
done
}
function error {
echo "Error: $1" >&2
exit 1
}
# ------------------------------------------------------------------------------
DOC="$(cat <<EOF
$0 [-znh] [-s KEY] [-e KEY]... DIR CONTAINER
Export CONTAINERs to DIR. Containers are stopped before they are exported.
This is done in the order they are specified in the command line. Afterwards,
the containers are restarted in the reverse order.
-s KEY Sign backups with KEY
-e KEY Encrypt backups for KEY (can appear multiple times)
-m MANAGER Use MANAGER for container management
-z Compress backups
-n Do not push
-h Display this help
EOF
)"
[[ "$EUID" -eq 0 ]] || error "$0 requires root"
declare SIGNER=""
declare MANAGER="docker"
declare -a RECIPIENTS=()
declare -i COMPRESS=0 PUSH=1
while getopts ":s:e:m:znh" opt; do
case "$opt" in
's')
if [[ "$OPTARG" != -* ]]; then
SIGNER="$OPTARG"
else
error "-s requires an argument"
fi
;;
'e')
if [[ "$OPTARG" != -* ]]; then
RECIPIENTS+=("$OPTARG")
else
error "-e requires an argument"
fi
;;
'm')
if [[ "$OPTARG" != -* ]]; then
MANAGER="$OPTARG"
else
error "-m requires an argument"
fi
;;
'z')
COMPRESS=1
;;
'n')
PUSH=0
;;
'h')
echo "$DOC"
exit
;;
'?')
error "Invalid option: -$OPTARG"
;;
esac
done
shift "$((OPTIND-1))"
if [[ "$#" -lt 2 ]]; then
echo "$DOC"
exit 1
else
declare -r STORE="$(realpath "$1")"; shift
declare -ra CONTAINERS=( "$@" )
fi
check-deps
stop-containers
prepare
backup
upload
#!/usr/bin/env bash
set -euo pipefail
function error {
echo "Error: $1" >&2
exit 1
}
[[ "$EUID" -eq 0 ]] || error "$0 requires root"
declare -r COMMAND="$1"; shift
case "$COMMAND" in
"start")
for c in "$@"; do
systemctl start "${c}_docker"
done
;;
"stop")
for c in "$@"; do
systemctl stop "${c}_docker"
done
;;
esac
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment