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:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
secrets/
|
||||||
77
borgmatic/borg-config.jsonnet
Normal file
77
borgmatic/borg-config.jsonnet
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
function(
|
||||||
|
project,
|
||||||
|
encryption_passphrase,
|
||||||
|
parent_dir='/docker',
|
||||||
|
repo_path='ssh://s1dw3340@s1dw3340.repo.borgbase.com/./repo',
|
||||||
|
repo_label='winterfell-docker on BorgBase',
|
||||||
|
exclude_patterns=[
|
||||||
|
// Generic caches & temp
|
||||||
|
'**/tmp/',
|
||||||
|
'**/temp/',
|
||||||
|
'**/.cache/',
|
||||||
|
'**/cache/',
|
||||||
|
|
||||||
|
// Potential ephemeral DB areas (adjust if you do raw file backups)
|
||||||
|
'**/pg_stat_tmp/',
|
||||||
|
'**/pg_replslot/',
|
||||||
|
|
||||||
|
// Node-based ephemeral
|
||||||
|
'**/node_modules/',
|
||||||
|
|
||||||
|
// System or FS-specific
|
||||||
|
'**/lost+found/',
|
||||||
|
'**/*.lock',
|
||||||
|
],
|
||||||
|
one_file_system=true,
|
||||||
|
compression='auto,zstd',
|
||||||
|
archive_name_format='docker-{now:%Y-%m-%d-%H%M%S}',
|
||||||
|
prefix_project_name_archive=true,
|
||||||
|
retries=0,
|
||||||
|
retry_wait=2,
|
||||||
|
keep_daily=3,
|
||||||
|
keep_weekly=4,
|
||||||
|
keep_monthly=12,
|
||||||
|
checks=[
|
||||||
|
{
|
||||||
|
"name": "repository",
|
||||||
|
"frequency": "4 weeks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archives",
|
||||||
|
"frequency": "8 weeks"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
check_last=3,
|
||||||
|
before_backup='/code/scripts/docker-gen/borgmatic/pre-backup-docker-compose-down.sh {{project}}',
|
||||||
|
after_backup='/code/scripts/docker-gen/borgmatic/post-backup-docker-compose-up.sh {{project}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
'source_directories': [
|
||||||
|
std.rstripChars(parent_dir, '/') + '/' + std.stripChars(project, ' ')
|
||||||
|
],
|
||||||
|
'repositories': [
|
||||||
|
{
|
||||||
|
'path': repo_path,
|
||||||
|
'label': repo_label
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'exclude_patterns': exclude_patterns,
|
||||||
|
'one_file_system': one_file_system,
|
||||||
|
'compression': compression,
|
||||||
|
'encryption_passphrase': encryption_passphrase,
|
||||||
|
'archive_name_format': if prefix_project_name_archive then project + '-' + archive_name_format else archive_name_format,
|
||||||
|
'retries': retries,
|
||||||
|
'retry_wait': retry_wait,
|
||||||
|
'keep_daily': keep_daily,
|
||||||
|
'keep_weekly': keep_weekly,
|
||||||
|
'keep_monthly': keep_monthly,
|
||||||
|
'checks': checks,
|
||||||
|
'check_last': check_last,
|
||||||
|
'before_backup': [
|
||||||
|
std.strReplace(before_backup, '{{project}}', project)
|
||||||
|
],
|
||||||
|
'after_backup': [
|
||||||
|
std.strReplace(after_backup, '{{project}}', project)
|
||||||
|
]
|
||||||
|
}
|
||||||
16
borgmatic/post-backup-docker-compose-up.sh
Executable file
16
borgmatic/post-backup-docker-compose-up.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: $0 <folder1> [folder2] ..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for folder in "$@"; do
|
||||||
|
cd "/docker/$folder" || {
|
||||||
|
echo "Directory /docker/$folder not found!"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#echo "would docker compose up -d in /docker/$folder"
|
||||||
|
docker compose up -d
|
||||||
|
done
|
||||||
15
borgmatic/pre-backup-docker-compose-down.sh
Executable file
15
borgmatic/pre-backup-docker-compose-down.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: $0 <folder1> [folder2] ..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for folder in "$@"; do
|
||||||
|
cd "/docker/$folder" || {
|
||||||
|
echo "Directory /docker/$folder not found!"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
docker compose down --remove-orphans
|
||||||
|
done
|
||||||
8
create-all-borg-configs.sh
Executable file
8
create-all-borg-configs.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
for dir in /docker/*; do
|
||||||
|
if [ -d "$dir" ] && [ "$(basename "$dir")" != "scripts" ]; then
|
||||||
|
base_name=$(basename "$dir")
|
||||||
|
/code/scripts/docker-gen/docker-gen.sh "$base_name" --only-borgmatic
|
||||||
|
fi
|
||||||
|
done
|
||||||
33
docker-compose.jsonnet
Normal file
33
docker-compose.jsonnet
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
function(
|
||||||
|
project,
|
||||||
|
image,
|
||||||
|
web_port="0000:0000",
|
||||||
|
ports=[],
|
||||||
|
tag="latest",
|
||||||
|
restart="unless-stopped"
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": project,
|
||||||
|
"services": {
|
||||||
|
"app": {
|
||||||
|
"image": image + ':' + tag,
|
||||||
|
"restart": restart,
|
||||||
|
"ports": std.uniq(std.sort(std.flattenArrays([ports, [web_port]]))),
|
||||||
|
"environment": {
|
||||||
|
"DOCKER_TEMPLATE_CREATED": true
|
||||||
|
},
|
||||||
|
"volumes": [
|
||||||
|
'./data:/data'
|
||||||
|
],
|
||||||
|
"secrets": [
|
||||||
|
'asecret'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"secrets": {
|
||||||
|
"asecret": {
|
||||||
|
"file": './secrets/ASECRET'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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