From 331995dad7d2290bce3ea7295332863a98a43b6b Mon Sep 17 00:00:00 2001 From: James Tufarelli <8152401+Minituff@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:14:34 -0700 Subject: [PATCH] Add labels (#34) * Added more labels * Updated docs * Add override feature to labels --- Dockerfile | 5 +- README.md | 5 +- docs/arguments.md | 122 ++++++++++++++++++++++++----------------- docs/index.md | 2 + docs/labels.md | 134 ++++++++++++++++++++++++++++++++++++++++++++++ docs/todo.md | 9 ---- mkdocs.yml | 4 +- pkg/backup.sh | 72 ++++++++++++++++++------- pkg/entry.sh | 3 ++ 9 files changed, 274 insertions(+), 82 deletions(-) create mode 100644 docs/labels.md delete mode 100644 docs/todo.md diff --git a/Dockerfile b/Dockerfile index 8fca5e58..0d59e208 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM docker:24.0.6-cli # Install dependencies -RUN apk add bash rsync tzdata dos2unix +RUN apk add bash rsync tzdata dos2unix jq # Copy all necessary files into the container (from /pkg in the repository to /app in the container) COPY pkg app @@ -37,5 +37,8 @@ ENV USE_DEFAULT_RSYNC_ARGS="true" # Apply custom rsync args (in addition to the default args) ENV RSYNC_CUSTOM_ARGS="" +# Require the Docker Label `nautical-backup.enable=true` to be present on each contianer or it will be skipped. +ENV REQUIRE_LABEL="false" + # Run the entry script and pass all variables to it ENTRYPOINT [ "bash", "-c", "exec ./entry.sh \"${@}\"", "--"] diff --git a/README.md b/README.md index 2538cfaf..ce3cb748 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ A simple Docker volume backup tool.
[![Pulls from DockerHub](https://img.shields.io/docker/pulls/minituff/nautical-backup?logo=docker)](https://hub.docker.com/r/minituff/nautical-backup) - [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/minituff/nautical-backup)](https://hub.docker.com/r/minituff/nautical-backup) - [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/minituff/nautical-backup/latest)](https://hub.docker.com/r/minituff/nautical-backup) + [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/minituff/nautical-backup?label=latest%20version)](https://hub.docker.com/r/minituff/nautical-backup) + [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/minituff/nautical-backup/latest?label=size)](https://hub.docker.com/r/minituff/nautical-backup) @@ -23,6 +23,7 @@ Full documentation is available at [https://minituff.github.io/nautical-backup]( Docker Compose ```yaml +version: '3' services: nautical-backup: image: minituff/nautical-backup:0.1.1 diff --git a/docs/arguments.md b/docs/arguments.md index 63d4a1e5..16c4caa1 100644 --- a/docs/arguments.md +++ b/docs/arguments.md @@ -2,6 +2,10 @@ Nautical provides configuration in the form of Docker enviornment variables. See the [Installation Section](./installation.md), which contains a few examples of applying enviornment variables. +### Enviornment Variable vs Label Priority +If a container has an Enviornment Variable applied as well as a conflicting Label, then: +> The continer Label takes priority over the global Natical enviornment variable. + ## Time Zone Sets the time-zone to be used by the CRON schedule. If this environment variable is not set, Nautical will use the default time-zone: `Etc/UTC`. @@ -29,83 +33,103 @@ Tell Nautical to skip backup of containers in this list. This list can either be the container `name` or full `id`. -> **Default**: *empty* +> **Default**: *empty* (no skips) + +=== "Example 1" + ```properties + SKIP_CONTAINERS=container-name1,container-name2,container-name3 + ``` + +=== "Example 2" + ```properties + SKIP_CONTAINERS=container-name1,056bd2e970c1338782733fdbf1009c6e158c715d0d105b11de88bd549430e7f5 + ``` -```properties -SKIP_CONTAINERS=container-name1,container-name2,container-name3 -``` -```properties -SKIP_CONTAINERS=container-name1,056bd2e970c1338782733fdbf1009c6e158c715d0d105b11de88bd549430e7f5 -``` !!! tip "Getting the full container ID" Usally, it's easier to just use the `container-name`, but if you need to use the full ID, these commands will help: * `docker ps --no-trunc` * `docker inspect ` +🔄 This is the same action as the [Skip Containers](./labels.md#skip) label, but applied globally. + +## Require Label +Require the Docker ^^Label^^ `nautical-backup.enable=true` to be present on *each* contianer or it will be skipped. + +> **Default**: false + +```properties +REQUIRE_LABEL=true +``` + +See the [Labels Enable Section](./labels.md#enable-nautical) for more details. + ## Override Source Directory Allows a source directory and container-name that do not match. -> **Default**: *empty* +> **Default**: *empty* (use container name) > **Format**: `:` (comma seperated for multiple items) Normally a container is backed up *only* when the `container-name` is the exact same as the `source folder name`. -Example 1: +=== "Example 1" -For example, a container named `Pi.Alert` will be skipped with a source directory name of `pialert`. -To fix this, we can override the source directory name so that it does not need to match the container name. + For example, a container named `Pi.Alert` will be skipped with a source directory name of `pialert`. + To fix this, we can override the source directory name so that it does not need to match the container name. -```properties -OVERRIDE_SOURCE_DIR=Pi.Alert:pialert -``` + ```properties + OVERRIDE_SOURCE_DIR=Pi.Alert:pialert + ``` -Example 2: +=== "Example 2" -We can override multiple containers if we seperate them with a comma. -```properties -OVERRIDE_SOURCE_DIR=example1:example1-new-source-data,ctr2:ctr2-new-source -``` - The example above would yield the following results: + We can override multiple containers if we seperate them with a comma. + ```properties + OVERRIDE_SOURCE_DIR=example1:example1-new-source-data,ctr2:ctr2-new-source + ``` + The example above would yield the following results: -| Container Name | Old Source Directory | New Source Directory | -| -------------- | -------------------- | ---------------------------- | -| example1 | `src/example1` | `src/example1-new-dest-data` | -| ctr2 | `src/ctr2` | `src/newdest` | + | Container Name | Old Source Directory | New Source Directory | + | -------------- | -------------------- | ---------------------------- | + | example1 | `src/example1` | `src/example1-new-dest-data` | + | ctr2 | `src/ctr2` | `src/newdest` | + +🔄 This is the same action as the [Override Source Directory](./labels.md#override-source-directory-name) label, but applied globally. ## Override Destination Directory Changes the destination backup name to be something other than the container name. -> **Default**: *empty* +> **Default**: *empty* (use container name) > **Format**: `:` (comma seperated for multiple items) Normally, a container is backed to a folder with the ^^same name^^ as the `container-name`. -Example 1: +=== "Example 1" -For example, let's say we have a container named `Pi.Alert`. By default, the container will be backed up to a folder named `Pi.Alert`. -If we want to change this destination folder name to be `pialert`, we can do that using overrides. + For example, let's say we have a container named `Pi.Alert`. By default, the container will be backed up to a folder named `Pi.Alert`. + If we want to change this destination folder name to be `pialert`, we can do that using overrides. -```properties -OVERRIDE_DEST_DIR=Pi.Alert:pialert -``` + ```properties + OVERRIDE_DEST_DIR=Pi.Alert:pialert + ``` -Example 2: +=== "Example 2" -```properties -OVERRIDE_DEST_DIR=example1:example1-new-dest-data,ctr2:newdest -``` + ```properties + OVERRIDE_DEST_DIR=example1:example1-new-dest-data,ctr2:newdest + ``` - The example above would yield the following results: + The example above would yield the following results: -| Container Name | Old Destination Directory | New Destination Directory | -| -------------- | ------------------------- | ----------------------------- | -| example1 | `dest/example1` | `dest/example1-new-dest-data` | -| ctr2 | `dest/ctr2` | `dest/newdest` | + | Container Name | Old Destination Directory | New Destination Directory | + | -------------- | ------------------------- | ----------------------------- | + | example1 | `dest/example1` | `dest/example1-new-dest-data` | + | ctr2 | `dest/ctr2` | `dest/newdest` | +🔄 This is the same action as the [Override Destination Directory](./labels.md#override-destination-directory-name) label, but applied globally. ## Report file Enable or Disable the automatically generated report file. @@ -119,7 +143,7 @@ REPORT_FILE=true ## Skip Stopping Containers Bypass stopping the container before performing a backup. This can be useful for containers with minimal configuration. -> **Default**: *empty* +> **Default**: *empty* (no containers will be skipped) ```properties SKIP_STOPPING=example1,example2 @@ -129,6 +153,9 @@ SKIP_STOPPING=example1,example2 Only do this on containers you know for certain do not need to be shutdown before backup. + +🔄 This is the same action as the [Stop Before Backup](./labels.md#stop-before-backup) label, but applied globally. + ## Backup on Start Will immediatly perform a backup when the container is started in addition to the CRON sheduled backup. @@ -167,21 +194,20 @@ USE_DEFAULT_RSYNC_ARGS=false ## Custom rsync Arguments Apply custom `rsync` args (in addition to the [default](#use-default-rsync-arguments) args) -> **Default**: *empty* +> **Default**: *empty* (no custom rsync args will be applied) The `RSYNC_CUSTOM_ARGS` will be inserted after the `$DEFAULT_RSYNC_ARGS` as shown: ```bash rsync $DEFAULT_RSYNC_ARGS $RSYNC_CUSTOM_ARGS $src_dir/ $dest_dir/ ``` - There are many `rsync` arguments and customizations that be be used here. -Examples: -```properties -# Don't backup any .log or any .txt files -RSYNC_CUSTOM_ARGS=--exclude='*.log' --exclude='*.txt' -``` +=== "Example 1" + ```properties + # Don't backup any .log or any .txt files + RSYNC_CUSTOM_ARGS=--exclude='*.log' --exclude='*.txt' + ```

diff --git a/docs/index.md b/docs/index.md index 2c98fc29..28b3597a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,6 +7,8 @@

Pulls from DockerHub + Docker Image Version (latest semver) + Docker Image Size (tag)

diff --git a/docs/labels.md b/docs/labels.md new file mode 100644 index 00000000..4bb8fc90 --- /dev/null +++ b/docs/labels.md @@ -0,0 +1,134 @@ +Docker Labels allow us to apply settings to Nautical on a per-container basis. Instead of applying [enviornment variables](./arguments.md), we can apply the label to the each container seperately. + +### How to add labels + +Here are a few examples of how to add labels to a Docker container. +Remember, these labels can be added to any container (other than Nautical itself). + +=== "Docker Compose Example 1" + ```yaml + version: '3' + services: + # Service config ... + labels: + - "nautical-backup.enable=true" + - "nautical-backup.stop-before-backup=true" + ``` + +=== "Docker Compose Example 2" + ```yaml + version: '3' + services: + pihole: + container_name: pihole + image: pihole/pihole:latest + ports: + - "53:53/tcp" + - "53:53/udp" + - "80:80/tcp" + volumes: + - './etc-pihole:/etc/pihole' + - './etc-dnsmasq.d:/etc/dnsmasq.d' + labels: + - "nautical-backup.enable=true" + - "nautical-backup.stop-before-backup=true" + ``` + +=== "Docker Run Example" + ```bash + docker run --name example-image \ + -l nautical-backup.enable=true \ + -l nautical-backup.stop-before-backup=true \ + my-image:latest + ``` + +=== "Docker Run Example 2" + ```bash + docker run -d \ + --name pihole \ + -p 53:53/tcp -p 53:53/udp \ + -p 80:80 \ + -e TZ="America/Chicago" \ + -v "${PIHOLE_BASE}/etc-pihole:/etc/pihole" \ + -v "${PIHOLE_BASE}/etc-dnsmasq.d:/etc/dnsmasq.d" \ + -l nautical-backup.enable=true \ + -l nautical-backup.stop-before-backup=true \ + pihole/pihole:latest + ``` + +### Label vs Enviornment Variable Priority +If a container has an Enviornment Variable applied as well as a conflicting Label, then: +> The continer Label takes priority over the global Natical enviornment variable. + +## Enable Nautical +With the [Require Label](./arguments.md#require-label) enviornment variable set to `true`, then all containers will be skipped unless they have this label. + +> **Default If Missing**: true + +```properties +nautical-backup.enable=false +``` + +If the [Require Label](./arguments.md#require-label) enviornment variable is *missing* or set to `false`, then [this label](#enable-nautical) will not be needed since the container will be backed up anyway. + +## Skip +Skip any containers completely if this label is present. + +> **Default If Missing**: false + +```properties +nautical-backup.skip=true +``` + +🔄 This is the same action as the [Skip Containers](./arguments.md#skip-containers) variable, but applied only to this container. + +## Stop Before Backup + +With this label applied, the container will not be stopped before performing a backup. + +> **Default If Missing**: false + +```properties +nautical-backup.stop-before-backup=true +``` + +!!! warning "Not stoppping containers can produce *corrupt* backups." + Containers with databases--particularly SQL--need to be shutdown before backup. + + Only do this on containers you know for certain do not need to be shutdown before backup. + +🔄 This is the same action [Skip Stopping Containers](./arguments.md#skip-stopping-containers) variable, but applied only to this container. + +## Override Source Directory Name + +Changes the source directory name that Nautical will look for. + +By default, Nautical will look for the source directory that is the same name as the container name. + +> **Default If Missing**: *empty* (use container name) + +=== "Example 1" + ```properties + nautical-backup.override-source-dir=new_folder_name + ``` + +=== "Example 2" + To backup the container `Pi.Alert`, the source directory name must be named `Pi.Alert`, but we can use the override to allow a backup of the folder named `pialert`. + ```properties + nautical-backup.override-source-dir=pialert + ``` + +🔄 This is the same action as the [Override Source Directory](./arguments.md#override-source-directory) variable, but applied only to this container. + +## Override Destination Directory Name +Changes the destination/output directory name that Nautical will create during backups. + +By default, Nautical will create destination directory that is the same name as the container name. + +> **Default If Missing**: *empty* (use container name) + +```properties +nautical-backup.override-destination-dir=new_folder_name +``` + +🔄 This is the same action as the [Override Destination Directory](./arguments.md#override-destination-directory) variable, but applied only to this container. \ No newline at end of file diff --git a/docs/todo.md b/docs/todo.md deleted file mode 100644 index 58cd307d..00000000 --- a/docs/todo.md +++ /dev/null @@ -1,9 +0,0 @@ -This project is not complete, and is still under active development. Below are a few examples of things we would like to add: - -* [ ] Config file `yml` - * Used in addition to enviornment variables to configure Nautical. -* [ ] Backup file exclusions - * For example, you could exclude `*.log` file from only the Trilium container. -* [ ] Lifecycle hooks - * Allow calling a script in the container that will about to be shutdown. - diff --git a/mkdocs.yml b/mkdocs.yml index 90eb9f65..143cb30e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,10 +35,10 @@ nav: - Home: index.md - Introduction: introduction.md - Installation: installation.md - - Advanced Usage: advanced.md - Variables: arguments.md + - Docker Labels: labels.md + - Advanced Usage: advanced.md - Q & A: q-and-a.md - - To Do: todo.md - Developer Documentation: - Contributing to the Docs: docs.md diff --git a/pkg/backup.sh b/pkg/backup.sh index 712318d6..f4e72e35 100644 --- a/pkg/backup.sh +++ b/pkg/backup.sh @@ -4,24 +4,23 @@ echo "Starting backup script..." # Convert the string back to an array if [ ! -z "$CONTAINER_SKIP_LIST_STR" ]; then - IFS=',' read -ra SKIP_CONTAINERS <<< "$CONTAINER_SKIP_LIST_STR" + IFS=',' read -ra SKIP_CONTAINERS <<<"$CONTAINER_SKIP_LIST_STR" fi # Convert the string back to an array if [ ! -z "$SKIP_STOPPING_STR" ]; then - IFS=',' read -ra SKIP_STOPPING <<< "$SKIP_STOPPING_STR" + IFS=',' read -ra SKIP_STOPPING <<<"$SKIP_STOPPING_STR" fi - # Function to populate override directories populate_override_dirs() { - local -n override_dirs_ref=$1 # Use nameref to update the associative array passed as argument - local override_var=$2 # The environment variable containing the override info - + local -n override_dirs_ref=$1 # Use nameref to update the associative array passed as argument + local override_var=$2 # The environment variable containing the override info + if [ ! -z "$override_var" ]; then - IFS=',' read -ra PAIRS <<< "$override_var" + IFS=',' read -ra PAIRS <<<"$override_var" for pair in "${PAIRS[@]}"; do - IFS=':' read -ra KV <<< "$pair" + IFS=':' read -ra KV <<<"$pair" override_dirs_ref[${KV[0]}]=${KV[1]} done fi @@ -35,7 +34,6 @@ declare -A override_dest_dirs populate_override_dirs override_source_dirs "$OVERRIDE_SOURCE_DIR" populate_override_dirs override_dest_dirs "$OVERRIDE_DEST_DIR" - # Fetch both container names and IDs containers=$(docker ps --no-trunc --format="{{.ID}}:{{.Names}}") number_of_containers=$(echo "$containers" | wc -l) @@ -52,7 +50,7 @@ report_file="Backup Report - $(date +'%Y-%m-%d').txt" if [ "$REPORT_FILE" = "true" ]; then rm -f "$DEST_LOCATION/Backup Report - "*.txt # Initialize the current report file with a header - echo "Backup Report - $(date)" > "$DEST_LOCATION/$report_file" + echo "Backup Report - $(date)" >"$DEST_LOCATION/$report_file" fi default_rsync_args="-ahq" @@ -66,7 +64,6 @@ if [ ! -z "$RSYNC_CUSTOM_ARGS" ]; then custom_args="$RSYNC_CUSTOM_ARGS" fi - # Get arguments: # -s = skips # -d = override DEST_LOCATION @@ -85,13 +82,12 @@ containers_completed=0 log_entry() { local message="$1" echo "$message" - + if [ "$REPORT_FILE" = "true" ]; then - echo "$(date) - $message" >> "$DEST_LOCATION/$report_file" + echo "$(date) - $message" >>"$DEST_LOCATION/$report_file" fi } - BackupContainer() { local container=$1 @@ -99,22 +95,44 @@ BackupContainer() { for skip in "${SKIP_STOPPING[@]}"; do if [ "$skip" == "$container" ]; then skip_stopping=1 + log_entry "Skipping stopping of $container as it's in the SKIP_STOPPING list." break fi done + # Use docker inspect to get the labels for the container + labels=$(docker inspect --format '{{json .Config.Labels}}' $id) + + # Check if the label nautical-backup.skip is set to true + if echo "$labels" | grep -q '"nautical-backup.stop-before-backup":"false"'; then + log_entry "Skipping stopping of $container because of label." + skip_stopping=1 + fi + local src_dir="$SOURCE_LOCATION/$container" if [ ! -z "${override_source_dirs[$container]}" ]; then src_dir="$SOURCE_LOCATION/${override_source_dirs[$container]}" log_entry "Overriding source directory for $container to ${override_source_dirs[$container]}" fi + if echo "$labels" | grep -q '"nautical-backup.override-source-dir"'; then + new_src_dir=$(echo "$labels" | jq -r '.["nautical-backup.override-source-dir"]') + src_dir="$SOURCE_LOCATION/$new_src_dir" + log_entry "Overriding source directory for $container to $new_src_dir from label" + fi + local dest_dir="$DEST_LOCATION/$container" if [ ! -z "${override_dest_dirs[$container]}" ]; then dest_dir="$DEST_LOCATION/${override_dest_dirs[$container]}" log_entry "Overriding destination directory for $container to ${override_dest_dirs[$container]}" fi + if echo "$labels" | grep -q '"nautical-backup.override-destination-dir"'; then + new_destination_dir=$(echo "$labels" | jq -r '.["nautical-backup.override-destination-dir"]') + dest_dir="$DEST_LOCATION/$new_destination_dir" + log_entry "Overriding destination directory for $container to $new_destination_dir from label" + fi + if [ -d "$src_dir" ]; then if [ $skip_stopping -eq 0 ]; then log_entry "Stopping $container..." @@ -123,8 +141,6 @@ BackupContainer() { log_entry "Error stopping container $container. Skipping backup for this container." return fi - else - log_entry "Skipping stopping of $container as it's in the SKIP_STOPPING list." fi log_entry "Backing up $container data..." @@ -153,21 +169,37 @@ BackupContainer() { fi } - # Loop through all running containers IFS=$'\n' for entry in $containers; do id=${entry%%:*} name=${entry##*:} - skip=0 + + if [ "$REQUIRE_LABEL" = "true" ]; then + skip=1 # Skip by default unless lable is found + fi + + # Use docker inspect to get the labels for the container + labels=$(docker inspect --format '{{json .Config.Labels}}' $id) + + if echo "$labels" | grep -q '"nautical-backup.enable":"true"'; then + echo "Enabling $name based on label." + skip=0 + fi + + if echo "$labels" | grep -q '"nautical-backup.skip":"true"'; then + echo "Skipping $name based on label." + skip=1 # Add the container to the skip list + fi + for cur in "${SKIP_CONTAINERS[@]}"; do if [ "$cur" == "$name" ]; then skip=1 echo "Skipping $name based on name." break fi - if [ "$cur" == "$id" ] ; then + if [ "$cur" == "$id" ]; then skip=1 if [ "$cur" == "$SELF_CONTAINER_ID" ]; then break # Exclude self from logs @@ -184,4 +216,4 @@ done containers_skipped=$((number_of_containers - containers_completed)) -echo "Success. $containers_completed containers backed up! $containers_skipped skipped." \ No newline at end of file +echo "Success. $containers_completed containers backed up! $containers_skipped skipped." diff --git a/pkg/entry.sh b/pkg/entry.sh index c4794403..cc2485c5 100644 --- a/pkg/entry.sh +++ b/pkg/entry.sh @@ -118,6 +118,9 @@ if [ "$USE_DEFAULT_RSYNC_ARGS" = "false" ]; then echo "USE_DEFAULT_RSYNC_ARGS: $USE_DEFAULT_RSYNC_ARGS" fi +if [ "$REQUIRE_LABEL" = "true" ]; then + echo "REQUIRE_LABEL: $REQUIRE_LABEL" +fi if [ "$BACKUP_ON_START" = "true" ]; then echo "BACKUP_ON_START: $BACKUP_ON_START"