diff --git a/src/Dockerfile b/src/Dockerfile index 7f711da..654a2c6 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,11 +1,82 @@ -FROM restic/restic:0.9.6 +FROM alpine AS builder -RUN apk update && apk add python3 dcron mariadb-client postgresql-client +RUN mkdir -p /opt + +ARG IMAGE_ARCH=amd64 + +ARG RCON_CLI_VERSION=1.4.4 + +ADD https://github.com/itzg/rcon-cli/releases/download/${RCON_CLI_VERSION}/rcon-cli_${RCON_CLI_VERSION}_linux_${IMAGE_ARCH}.tar.gz /tmp/rcon-cli.tar.gz + +RUN tar x -f /tmp/rcon-cli.tar.gz -C /opt/ && \ + chmod +x /opt/rcon-cli + +ARG RESTIC_VERSION=0.9.5 + +ADD https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_${IMAGE_ARCH}.bz2 /tmp/restic.bz2 + +RUN bunzip2 /tmp/restic.bz2 && \ + mv /tmp/restic /opt/restic && \ + chmod +x /opt/restic + +ARG DEMOTER_VERSION=0.1.0 + +ADD https://github.com/itzg/entrypoint-demoter/releases/download/${DEMOTER_VERSION}/entrypoint-demoter_${DEMOTER_VERSION}_linux_${IMAGE_ARCH}.tar.gz /tmp/entrypoint-demoter.tar.gz + +RUN tar x -f /tmp/entrypoint-demoter.tar.gz -C /opt/ && \ + chmod +x /opt/entrypoint-demoter + +ARG RCLONE_VERSION=1.49.5 + +ADD https://downloads.rclone.org/v${RCLONE_VERSION}/rclone-v${RCLONE_VERSION}-linux-${IMAGE_ARCH}.zip /tmp/rclone.zip + +RUN mkdir -p /tmp/rclone && \ + unzip /tmp/rclone.zip -d /tmp/rclone && \ + mv /tmp/rclone/rclone-v${RCLONE_VERSION}-linux-${IMAGE_ARCH}/rclone /opt/rclone && \ + chmod +x /opt/rclone + + +FROM alpine + +RUN apk -U --no-cache add \ + bash \ + coreutils \ + openssh-client \ + python3 \ + dcron \ + mariadb-client \ + postgresql-client + +COPY --from=builder /opt/rcon-cli /opt/rcon-cli + +RUN ln -s /opt/rcon-cli /usr/bin + +COPY --from=builder /opt/restic /opt/restic + +RUN ln -s /opt/restic /usr/bin + + +COPY --from=builder /opt/entrypoint-demoter /opt/entrypoint-demoter + +RUN ln -s /opt/entrypoint-demoter /usr/bin + + +COPY --from=builder /opt/rclone /opt/rclone + +RUN ln -s /opt/rclone /usr/bin + +# install rcb python app ADD . /restic-compose-backup WORKDIR /restic-compose-backup + RUN pip3 install -U pip setuptools wheel && pip3 install -e . ENV XDG_CACHE_HOME=/cache +# end install + +ADD backup.sh /backup.sh +RUN chmod +x ./backup.sh + ENTRYPOINT [] -CMD ["./entrypoint.sh"] +CMD ["./entrypoint.sh"] \ No newline at end of file diff --git a/src/backup.sh b/src/backup.sh new file mode 100644 index 0000000..ae2426f --- /dev/null +++ b/src/backup.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +set -euo pipefail + +if [ "${DEBUG:-false}" == "true" ]; then + set -x +fi + +: "${RCON_HOST:=localhost}" +: "${RCON_PORT:=25575}" +: "${RCON_PASSWORD:=minecraft}" + +export RCON_HOST +export RCON_PORT +export RCON_PASSWORD + +############### +## common ## +## functions ## +############### + +is_elem_in_array() { + # $1 = element + # All remaining arguments are array to search for the element in + if [ "$#" -lt 2 ]; then + log INTERNALERROR "Wrong number of arguments passed to is_elem_in_array function" + return 2 + fi + local element="${1}" + shift + local e + for e; do + if [ "${element}" == "${e}" ]; then + return 0 + fi + done + return 1 +} + +log() { + if [ "$#" -lt 1 ]; then + log INTERNALERROR "Wrong number of arguments passed to log function" + return 2 + fi + local level="${1}" + shift + local valid_levels=( + "INFO" + "WARN" + "ERROR" + "INTERNALERROR" + ) + if ! is_elem_in_array "${level}" "${valid_levels[@]}"; then + log INTERNALERROR "Log level ${level} is not a valid level." + return 2 + fi + ( + # If any arguments are passed besides log level + if [ "$#" -ge 1 ]; then + # then use them as log message(s) + <<<"${*}" cat - + else + # otherwise read log messages from standard input + cat - + fi + if [ "${level}" == "INTERNALERROR" ]; then + echo "Please report this: https://github.com/itzg/docker-mc-backup/issues" + fi + ) | awk -v level="${level}" '{ printf("%s %s %s\n", strftime("%FT%T%z"), level, $0); fflush(); }' +} >&2 + +retry() { + if [ "$#" -lt 3 ]; then + log INTERNALERROR "Wrong number of arguments passed to retry function" + return 1 + fi + + # How many times should we retry? + # Value smaller than zero means infinitely + local retries="${1}" + # Time to sleep between retries + local interval="${2}" + readonly retries interval + shift 2 + + if (( retries < 0 )); then + local retries_msg="infinite" + else + local retries_msg="${retries}" + fi + + local i=-1 # -1 since we will increment it before printing + while (( retries >= ++i )) || [ "${retries_msg}" != "${retries}" ]; do + # Send SIGINT after 5 minutes. If it doesn't shut down in 30 seconds, kill it. + if output="$(timeout --signal=SIGINT --kill-after=30s 5m "${@}" 2>&1 | tr '\n' '\t')"; then + log INFO "Command executed successfully ${*}" + return 0 + else + log ERROR "Unable to execute ${*} - try ${i}/${retries_msg}. Retrying in ${interval}" + if [ -n "${output}" ]; then + log ERROR "Failure reason: ${output}" + fi + fi + # shellcheck disable=SC2086 + sleep ${interval} + done + return 2 +} + +is_function() { + if [ "${#}" -ne 1 ]; then + log INTERNALERROR "is_function expects 1 argument, received ${#}" + fi + name="${1}" + [ "$(type -t "${name}")" == "function" ] +} + +call_if_function_exists() { + if [ "${#}" -lt 1 ]; then + log INTERNALERROR "call_if_function_exists expects at least 1 argument, received ${#}" + return 2 + fi + function_name="${1}" + if is_function "${function_name}"; then + eval "${@}" + else + log INTERNALERROR "${function_name} is not a valid function!" + return 2 + fi +} + +########## +## main ## +########## + + +log INFO "waiting for rcon readiness..." +# 20 times, 10 second delay +retry 20 10s rcon-cli save-on + + +if retry 5 10s rcon-cli save-off; then + # No matter what we were doing, from now on if the script crashes + # or gets shut down, we want to make sure saving is on + trap 'retry 5 5s rcon-cli save-on' EXIT + + retry 5 10s rcon-cli save-all + retry 5 10s sync + + rcb backup + + retry 20 10s rcon-cli save-on + # Remove our exit trap now + trap EXIT + else + log ERROR "Unable to turn saving off. Is the server running?" + exit 1 + fi + + if (( PRUNE_BACKUPS_DAYS > 0 )); then + rcb cleanup + fi \ No newline at end of file diff --git a/src/crontab b/src/crontab index 23ddd03..29a9bc9 100644 --- a/src/crontab +++ b/src/crontab @@ -1,2 +1,2 @@ -10 2 * * * source /env.sh && rcb backup > /proc/1/fd/1 +10 2 * * * source /env.sh && /backup.sh > /proc/1/fd/1