Files
docker-gen/docker-gen.sh
Chris King 9c069522b0 Initial commit of docker compose project generator
Creates project directory structure in /docker
Creates docker-compose.yml with sensible defaults
Creates borgmatic config in /etc/borgmatic.d
Adds proxy info to caddyfile and reloads caddy
Config file generation done via jsonnet and yq
Borgmatic configured to backup to borgbase repo
2025-01-08 00:08:54 -08:00

287 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# Exit immediately if a command exits with a non-zero status.
set -e
###############################################################################
# Usage:
# create-docker-compose.sh <project_name> [<image>]
# [--only-borgmatic]
# [--no-borgmatic]
# [--parent_directory PARENT_DIRECTORY]
# [--encryption_passphrase PASSPHRASE]
# [--tag TAG_NAME]
# [--web-port WEB_PORT]
#
# Examples:
# 1) Create Docker project + Borgmatic (default):
# ./create-docker-compose.sh my_project my_image
#
# 2) Create Docker project only (skip Borgmatic):
# ./create-docker-compose.sh my_project my_image --no-borgmatic
#
# 3) Create Borgmatic config only (skip Docker project & image param):
# ./create-docker-compose.sh my_project --only-borgmatic
#
# 4) Use a custom encryption passphrase:
# ./create-docker-compose.sh my_project my_image --encryption_passphrase "MyPass123"
#
# 5) Custom parent directory + custom passphrase:
# ./create-docker-compose.sh my_project my_image \
# --parent_directory /custom/path \
# --encryption_passphrase "SuperSecret!"
#
# 6) Specify a tag for the Docker image (default: "latest"):
# ./create-docker-compose.sh my_project my_image --tag dev
#
# 7) Specify a port mapping for a web UI (e.g., "8000:8080"):
# ./create-docker-compose.sh my_project my_image --web-port "8000:8080"
# This will pass "web_port" to the Jsonnet template and add a line to
# /etc/caddy/Caddyfile, then reload Caddy.
#
# Description:
# 1. By default (no --only-borgmatic), creates a Docker Compose project
# structure in <parent_directory>/<project_name>, generating:
# docker-compose.yml (from /code/scripts/docker-gen/docker-compose.jsonnet)
# config/
# data/
# owned by the user running this script.
#
# 2. By default, generates a Borgmatic config file in /etc/borgmatic.d/<project_name>.yaml
# (owned by root), unless --no-borgmatic is specified.
#
# 3. If --only-borgmatic is used, <image> becomes optional, and the script
# skips creating the Docker project folder entirely (only generating the
# Borgmatic config).
#
# 4. The default Borgmatic encryption passphrase is read from:
# /code/scripts/docker-gen/secrets/BORGMATIC_ENCRYPTION_PASSPHRASE
# If missing, the script falls back to "abcd1234".
# Can be overridden via --encryption_passphrase "<your_pass>".
#
# 5. --tag can override the default "latest" Docker tag in the Compose Jsonnet.
#
# 6. --web-port can pass an additional TLA param "web_port" to docker-compose.jsonnet,
# and if provided, a line is appended to /etc/caddy/Caddyfile in the format:
# import ttt-app <project_name> <host_port>
# followed by a reload of the Caddy service. The <host_port> is the left side
# of the "host:container" mapping (e.g. "8000" if --web-port "8000:8080").
#
###############################################################################
###############################################################################
# 1) Initialize Variables
###############################################################################
PROJECT_NAME=""
IMAGE=""
PARENT_DIR="/docker" # Default if not overridden
SKIP_BORGMATIC=false
ONLY_BORGMATIC=false
TAG="latest" # Default Docker image tag if not overridden
WEB_PORT="" # Optional Docker port, e.g. "8000:8001"
# Read the default encryption passphrase from file, or fallback
if [ -f "/code/scripts/docker-gen/secrets/BORGMATIC_ENCRYPTION_PASSPHRASE" ]; then
DEFAULT_ENCRYPTION_PASSPHRASE="$(cat /code/scripts/docker-gen/secrets/BORGMATIC_ENCRYPTION_PASSPHRASE)"
else
echo "Warning: /code/scripts/docker-gen/secrets/BORGMATIC_ENCRYPTION_PASSPHRASE not found. Using 'abcd1234'."
DEFAULT_ENCRYPTION_PASSPHRASE="abcd1234"
fi
ENCRYPTION_PASSPHRASE="$DEFAULT_ENCRYPTION_PASSPHRASE"
###############################################################################
# 2) Parse Command-line Arguments
###############################################################################
while [[ $# -gt 0 ]]; do
case "$1" in
--only-borgmatic)
ONLY_BORGMATIC=true
shift
;;
--no-borgmatic)
SKIP_BORGMATIC=true
shift
;;
--parent_directory)
PARENT_DIR="$2"
shift 2
;;
--encryption_passphrase)
ENCRYPTION_PASSPHRASE="$2"
shift 2
;;
--tag)
TAG="$2"
shift 2
;;
--web-port)
WEB_PORT="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 <project_name> [<image>] [--only-borgmatic] [--no-borgmatic] [--parent_directory PARENT_DIRECTORY] [--encryption_passphrase PASSPHRASE] [--tag TAG] [--web-port WEB_PORT]"
exit 0
;;
*)
# If we haven't set the project name, do that first
if [ -z "$PROJECT_NAME" ]; then
PROJECT_NAME="$1"
# If we haven't set the image AND we're not in only-borgmatic mode, set it
elif [ -z "$IMAGE" ] && [ "$ONLY_BORGMATIC" = false ]; then
IMAGE="$1"
else
echo "Unknown parameter: $1"
echo "Usage: $0 <project_name> [<image>] [--only-borgmatic] [--no-borgmatic] [--parent_directory PARENT_DIRECTORY] [--encryption_passphrase PASSPHRASE] [--tag TAG] [--web-port WEB_PORT]"
exit 1
fi
shift
;;
esac
done
###############################################################################
# 3) Validate Required Parameters
###############################################################################
if [ -z "$PROJECT_NAME" ]; then
echo "Error: Missing project name."
echo "Usage: $0 <project_name> [<image>] ..."
exit 1
fi
# If we're NOT in only-borgmatic mode, we require the image parameter
if [ "$ONLY_BORGMATIC" = false ] && [ -z "$IMAGE" ]; then
echo "Error: Missing image name. Provide <image> or use --only-borgmatic."
exit 1
fi
# Check for conflicting flags
if [ "$ONLY_BORGMATIC" = true ] && [ "$SKIP_BORGMATIC" = true ]; then
echo "Error: --only-borgmatic and --no-borgmatic are mutually exclusive."
exit 1
fi
# Ensure no whitespace in project name
if [[ "$PROJECT_NAME" =~ [[:space:]] ]]; then
echo "Error: Project name cannot contain whitespace."
exit 1
fi
# Ensure the project name has only allowed chars
if [[ ! "$PROJECT_NAME" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "Error: Project name contains illegal characters."
echo "Allowed: alphanumeric, underscores (_), dashes (-), and dots (.)"
exit 1
fi
###############################################################################
# 4) Prepare Directory Paths
###############################################################################
# Remove trailing slash from the parent directory path if any
PARENT_DIR="${PARENT_DIR%/}"
# Construct the project path
PROJECT_PATH="$PARENT_DIR/$PROJECT_NAME"
###############################################################################
# 5) Optionally Create the Docker Project Structure
###############################################################################
if [ "$ONLY_BORGMATIC" = false ]; then
if [ -d "$PROJECT_PATH" ]; then
echo "Error: Project directory '$PROJECT_PATH' already exists."
exit 1
fi
echo "Creating Docker Compose project folder at: $PROJECT_PATH"
mkdir -p "$PROJECT_PATH/config" "$PROJECT_PATH/data" "$PROJECT_PATH/secrets"
# Build jsonnet command for docker-compose
JSONNET_CMD=(
jsonnet
--tla-str "project=$PROJECT_NAME"
--tla-str "image=$IMAGE"
--tla-str "tag=$TAG"
)
# If a web port was specified, add it to the Jsonnet command
if [ -n "$WEB_PORT" ]; then
JSONNET_CMD+=( --tla-str "web_port=$WEB_PORT" )
fi
# Run jsonnet + yq to create docker-compose.yml
"${JSONNET_CMD[@]}" /code/scripts/docker-gen/docker-compose.jsonnet \
| yq e '... style="" | with(.services.app.ports[] ; . style="double")' - \
> "$PROJECT_PATH/docker-compose.yml"
# Change ownership to the user running the script
chown -R "$(id -u):$(id -g)" "$PROJECT_PATH"
# If a web port is provided, parse the host port and update Caddy
if [ -n "$WEB_PORT" ]; then
# Extract the host port (before the colon, e.g. "8000" if "8000:8080")
HOST_PORT="${WEB_PORT%%:*}"
# Append line to Caddyfile: import ttt-app <project_name> <host_port>
echo "import ttt-app $PROJECT_NAME $HOST_PORT" | sudo tee -a /etc/caddy/Caddyfile > /dev/null
# Reload Caddy
sudo systemctl reload caddy.service
echo "Updated /etc/caddy/Caddyfile and reloaded Caddy with 'import ttt-app $PROJECT_NAME $HOST_PORT'."
fi
else
echo "Skipping Docker project folder creation (--only-borgmatic)."
fi
###############################################################################
# 6) Generate the Borgmatic Configuration (unless --no-borgmatic)
###############################################################################
if [ "$SKIP_BORGMATIC" = false ]; then
echo "Generating Borgmatic configuration for project: '$PROJECT_NAME'..."
# Ensure /etc/borgmatic.d exists and is owned by root
sudo mkdir -p /etc/borgmatic.d
sudo chown root:root /etc/borgmatic.d
# Generate the config using jsonnet & yq, then tee it into place as root
jsonnet \
--tla-str project="$PROJECT_NAME" \
--tla-str encryption_passphrase="$ENCRYPTION_PASSPHRASE" \
/code/scripts/docker-gen/borgmatic/borg-config.jsonnet \
| yq -p json -o yaml - \
| sudo tee "/etc/borgmatic.d/${PROJECT_NAME}.yaml" >/dev/null
# Ensure the config file is owned by root
sudo chown root:root "/etc/borgmatic.d/${PROJECT_NAME}.yaml"
echo "Borgmatic config created at: /etc/borgmatic.d/${PROJECT_NAME}.yaml (owned by root)"
else
echo "Skipping Borgmatic configuration (--no-borgmatic)."
fi
###############################################################################
# 7) Final Output
###############################################################################
echo
if [ "$ONLY_BORGMATIC" = false ]; then
echo "Docker Compose project directory (if created) is at:"
echo " $PROJECT_PATH"
echo "Owned by user: $(id -un), group: $(id -gn)"
echo
echo "Directory structure:"
if [ -d "$PROJECT_PATH" ]; then
if command -v tree >/dev/null 2>&1; then
tree "$PROJECT_PATH"
else
ls -R "$PROJECT_PATH"
fi
else
echo " [Skipped creation of Docker project folder]"
fi
else
echo "Docker project folder creation was skipped (--only-borgmatic)."
fi
echo
echo "Script execution completed."