From 172684a5a768cb2f53bd1326763f40e7c5f09c01 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 30 Mar 2026 02:02:57 +0100 Subject: [PATCH] Add prepare-compose.sh for composable docker-compose (#363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add prepare-compose.sh for composible modules, options and overlays * feat(prepare-compose): add --quiet and --skip-validation flags Add loud() function for conditional output. Use --quiet to suppress normal output, --skip-validation to bypass docker compose config check. * chore: add mise config to gitignore * chore: add GOCLAW_DIR env var and update context paths - Add GOCLAW_DIR to prepare-compose.sh for .env generation - Fix context paths in docker-compose files to use ${GOCLAW_DIR:-.} - Restore symlinks in compose.d/ and compose.options/ * fix: broken symlink, safe .env update, export COMPOSE_FILE before validate - Fix compose.d/00-goclaw.yml symlink: docker-compose.yml → ../docker-compose.yml - Add compose.d/.gitkeep so directory is tracked - Export COMPOSE_FILE before running docker compose config validation - Replace rm+sed .env update pattern with safe temp file approach - Quote GOCLAW_DIR value in .env to handle paths with spaces --------- Co-authored-by: viettranx --- .gitignore | 11 ++++ compose.d/.gitkeep | 0 compose.d/00-goclaw.yml | 1 + compose.options/11-postgres.yml | 1 + compose.options/12-selfservice.yml | 1 + compose.options/13-upgrade.yml | 1 + compose.options/14-browser.yml | 1 + compose.options/15-otel.yml | 1 + compose.options/16-redis.yml | 1 + compose.options/17-sandbox.yml | 1 + compose.options/18-tailscale.yml | 1 + docker-compose.selfservice.yml | 2 +- docker-compose.upgrade.yml | 2 +- docker-compose.yml | 2 +- options/postgres/postgres-low-cpu.yml | 12 +++++ prepare-compose.sh | 76 +++++++++++++++++++++++++++ 16 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 compose.d/.gitkeep create mode 120000 compose.d/00-goclaw.yml create mode 120000 compose.options/11-postgres.yml create mode 120000 compose.options/12-selfservice.yml create mode 120000 compose.options/13-upgrade.yml create mode 120000 compose.options/14-browser.yml create mode 120000 compose.options/15-otel.yml create mode 120000 compose.options/16-redis.yml create mode 120000 compose.options/17-sandbox.yml create mode 120000 compose.options/18-tailscale.yml create mode 100644 options/postgres/postgres-low-cpu.yml create mode 100755 prepare-compose.sh diff --git a/.gitignore b/.gitignore index 3f73931c..269ad676 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,14 @@ goclaw.db-shm # Auto-generated by Makefile (git describe) VERSION ui/desktop/frontend/dist/index.html + +# Local compose modules +compose.d/* +!compose.d/.gitkeep +!compose.d/0* + +# Mise-en-place +/.miserc.toml +/mise.*.toml +/mise +/.mise diff --git a/compose.d/.gitkeep b/compose.d/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/compose.d/00-goclaw.yml b/compose.d/00-goclaw.yml new file mode 120000 index 00000000..5c8318ef --- /dev/null +++ b/compose.d/00-goclaw.yml @@ -0,0 +1 @@ +../docker-compose.yml \ No newline at end of file diff --git a/compose.options/11-postgres.yml b/compose.options/11-postgres.yml new file mode 120000 index 00000000..618529b5 --- /dev/null +++ b/compose.options/11-postgres.yml @@ -0,0 +1 @@ +../docker-compose.postgres.yml \ No newline at end of file diff --git a/compose.options/12-selfservice.yml b/compose.options/12-selfservice.yml new file mode 120000 index 00000000..6c90b043 --- /dev/null +++ b/compose.options/12-selfservice.yml @@ -0,0 +1 @@ +../docker-compose.selfservice.yml \ No newline at end of file diff --git a/compose.options/13-upgrade.yml b/compose.options/13-upgrade.yml new file mode 120000 index 00000000..cfcde769 --- /dev/null +++ b/compose.options/13-upgrade.yml @@ -0,0 +1 @@ +../docker-compose.upgrade.yml \ No newline at end of file diff --git a/compose.options/14-browser.yml b/compose.options/14-browser.yml new file mode 120000 index 00000000..eff2597a --- /dev/null +++ b/compose.options/14-browser.yml @@ -0,0 +1 @@ +../docker-compose.browser.yml \ No newline at end of file diff --git a/compose.options/15-otel.yml b/compose.options/15-otel.yml new file mode 120000 index 00000000..91959568 --- /dev/null +++ b/compose.options/15-otel.yml @@ -0,0 +1 @@ +../docker-compose.otel.yml \ No newline at end of file diff --git a/compose.options/16-redis.yml b/compose.options/16-redis.yml new file mode 120000 index 00000000..ff53cce5 --- /dev/null +++ b/compose.options/16-redis.yml @@ -0,0 +1 @@ +../docker-compose.redis.yml \ No newline at end of file diff --git a/compose.options/17-sandbox.yml b/compose.options/17-sandbox.yml new file mode 120000 index 00000000..671aa330 --- /dev/null +++ b/compose.options/17-sandbox.yml @@ -0,0 +1 @@ +../docker-compose.sandbox.yml \ No newline at end of file diff --git a/compose.options/18-tailscale.yml b/compose.options/18-tailscale.yml new file mode 120000 index 00000000..945eb09b --- /dev/null +++ b/compose.options/18-tailscale.yml @@ -0,0 +1 @@ +../docker-compose.tailscale.yml \ No newline at end of file diff --git a/docker-compose.selfservice.yml b/docker-compose.selfservice.yml index cfd0ff88..fe4a0e40 100644 --- a/docker-compose.selfservice.yml +++ b/docker-compose.selfservice.yml @@ -9,7 +9,7 @@ services: goclaw-ui: image: ghcr.io/nextlevelbuilder/goclaw-web:latest build: - context: ./ui/web + context: ${GOCLAW_DIR:-.}/ui/web dockerfile: Dockerfile ports: - "${GOCLAW_UI_PORT:-3000}:80" diff --git a/docker-compose.upgrade.yml b/docker-compose.upgrade.yml index 50b8df71..bafae0a2 100644 --- a/docker-compose.upgrade.yml +++ b/docker-compose.upgrade.yml @@ -16,7 +16,7 @@ services: upgrade: image: ghcr.io/nextlevelbuilder/goclaw:latest build: - context: . + context: ${GOCLAW_DIR:-.} dockerfile: Dockerfile args: ENABLE_OTEL: "false" diff --git a/docker-compose.yml b/docker-compose.yml index c4f5509f..415029cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: goclaw: image: ghcr.io/nextlevelbuilder/goclaw:latest build: - context: . + context: ${GOCLAW_DIR:-.} dockerfile: Dockerfile args: ENABLE_OTEL: "${ENABLE_OTEL:-false}" diff --git a/options/postgres/postgres-low-cpu.yml b/options/postgres/postgres-low-cpu.yml new file mode 100644 index 00000000..cb2a30b0 --- /dev/null +++ b/options/postgres/postgres-low-cpu.yml @@ -0,0 +1,12 @@ +# Low CPU settings for postgres +services: + postgres: + deploy: + resources: + limits: + cpus: '0.5' + memory: 512M + command: + - "postgres" + - "-c" + - "max_connections=10" diff --git a/prepare-compose.sh b/prepare-compose.sh new file mode 100755 index 00000000..c3ff50df --- /dev/null +++ b/prepare-compose.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Generates COMPOSE_FILE from compose.d/*.yml +set -euo pipefail + +SCRIPT="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" +ENV_FILE="${GOCLAW_ENV_FILE:-$SCRIPT_DIR/.env}" + +loud() { + [[ "${QUIET:-false}" != true ]] && echo "$@" + true +} + +# Show help +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + echo "Usage: $SCRIPT [--quiet] [--skip-validation]" + echo "" + echo " --quiet Suppress normal output" + echo " --skip-validation Skip docker compose config validation" + echo "" + echo " Generates COMPOSE_FILE from compose.d/*.yml files (sorted)" + echo " Updates .env with the resulting COMPOSE_FILE value" + echo "" + echo "Note: docker-compose reads .env automatically" + echo " for podman-compose: source .env first" + exit 0 +fi + +# Parse flags +SKIP_VALIDATION=false +for arg in "$@"; do + case "$arg" in + --quiet) QUIET=true ;; + --skip-validation) SKIP_VALIDATION=true ;; + esac +done + +cd "$SCRIPT_DIR" >/dev/null 2>&1 + +[[ ! -f "docker-compose.yml" ]] && echo "docker-compose.yml not found" && exit 1 + +# Build COMPOSE_FILE from compose.d files (sorted) +COMPOSE_FILE="" +for f in compose.d/*.yml; do + [[ -e "$f" ]] && COMPOSE_FILE="$COMPOSE_FILE${COMPOSE_FILE:+:}$f" +done +export COMPOSE_FILE + +# Validate compose files +if [[ "$SKIP_VALIDATION" != true ]]; then + DOCKER_CMD="${DOCKER_CMD:-docker}" + $DOCKER_CMD compose config > /dev/null 2>&1 || { echo "Compose config validation failed"; exit 1; } + loud "Compose config valid" +fi + +# Update a key=value line in .env safely (via temp file) +update_env() { + local key="$1" value="$2" + if grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then + sed "s|^${key}=.*|${key}='${value}'|" "$ENV_FILE" > "$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" "$ENV_FILE" + else + echo "${key}='${value}'" >> "$ENV_FILE" + fi +} + +# Update .env with COMPOSE_FILE and GOCLAW_DIR +if [[ -f "$ENV_FILE" ]]; then + update_env "COMPOSE_FILE" "$COMPOSE_FILE" + update_env "GOCLAW_DIR" "$SCRIPT_DIR" + loud "COMPOSE_FILE updated in $ENV_FILE" + loud " COMPOSE_FILE=$COMPOSE_FILE" +else + loud "File not found: $ENV_FILE" +fi + +loud "(run '${SCRIPT} --help' for help)"