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
This commit is contained in:
286
docker-gen.sh
Executable file
286
docker-gen.sh
Executable file
@@ -0,0 +1,286 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user