Files
supabase/docker/run.sh

298 lines
10 KiB
Bash
Executable File

#!/bin/sh
#
# Manage the self-hosted Supabase docker compose stack.
#
# Override files are layered via docker compose's native COMPOSE_FILE env
# var in .env. Format: colon-separated list with docker-compose.yml first.
#
# Examples in .env:
# COMPOSE_FILE=docker-compose.yml
# COMPOSE_FILE=docker-compose.yml:docker-compose.pg17.yml
#
# Manage with: sh run.sh config add <name> | config remove <name>
# (accepts either a short name like 'pg17' or 'docker-compose.pg17.yml')
#
# Usage:
# sh run.sh start # docker compose up -d --wait
# sh run.sh stop # docker compose down
# sh run.sh restart [service] # restart the stack (or named services)
# sh run.sh restart --except <svc>... # restart all services except the named ones
# sh run.sh recreate [service] # stop then start (or force-recreate one service)
# sh run.sh recreate --except <svc>... # force-recreate all services except the named ones
# sh run.sh status # docker compose ps
# sh run.sh logs [service] # follow logs (all or one service)
# sh run.sh inspect <service> # docker inspect on a service's container
# sh run.sh printenv <service> # print a service's environment variables
# sh run.sh pull # pull images
# sh run.sh config # show the active COMPOSE_FILE list
# sh run.sh config add <name> # add an override to COMPOSE_FILE in .env
# sh run.sh config remove <name> # remove an override from COMPOSE_FILE in .env
# sh run.sh compose-config # dump fully-resolved docker compose config
# sh run.sh secrets # print key passwords and API keys from .env
#
set -e
cd "$(dirname "$0")"
if [ ! -f docker-compose.yml ]; then
echo "ERROR: docker-compose.yml not found in $(pwd)" >&2
exit 1
fi
# Normalize an override argument:
# pg17 -> docker-compose.pg17.yml
# docker-compose.pg17.yml -> docker-compose.pg17.yml
# ./docker-compose.pg17.yml -> docker-compose.pg17.yml
# docker-compose.yml -> error (base file, always implicit)
normalize_override() {
arg="${1#./}"
case "$arg" in
docker-compose.yml)
echo "ERROR: docker-compose.yml is the base file, always included" >&2
return 1
;;
docker-compose.*.yml)
echo "$arg"
;;
*)
echo "docker-compose.${arg}.yml"
;;
esac
}
# Read COMPOSE_FILE from .env (stripping quotes and CR).
read_compose_file() {
[ -f .env ] || return 0
grep '^COMPOSE_FILE=' .env | head -n1 | cut -d= -f2- | tr -d "\r\"'"
}
# Pretty-print the effective compose file list.
print_config() {
val="$1"
[ -z "$val" ] && val="docker-compose.yml"
echo "COMPOSE_FILE=$val"
echo "compose files:"
OLD_IFS=$IFS
IFS=:
for f in $val; do
echo " $f"
done
IFS=$OLD_IFS
echo ""
}
# Update or append COMPOSE_FILE in .env.
write_compose_file() {
new_value="$1"
if [ ! -f .env ]; then
echo "ERROR: .env not found in $(pwd)" >&2
exit 1
fi
new_line="COMPOSE_FILE=$new_value"
if grep -q '^COMPOSE_FILE=' .env; then
sed -i.old -e "s|^COMPOSE_FILE=.*$|$new_line|" .env
rm -f .env.old
else
cat >> .env <<EOF
############
# Docker compose override files to layer on top of docker-compose.yml.
# Colon-separated list. Manage with: sh run.sh config add|remove <name>.
#
# Examples:
# COMPOSE_FILE=docker-compose.yml
# COMPOSE_FILE=docker-compose.yml:docker-compose.pg17.yml
############
$new_line
EOF
fi
}
# Echoes the list of services (one per line) minus those passed as args.
# Warns on unknown names; returns 1 if no services remain.
services_except() {
all_services=$(docker compose config --services)
filtered="$all_services"
for ex in "$@"; do
echo "$all_services" | grep -qFx "$ex" \
|| echo "Warning: '$ex' is not a service in this project" >&2
filtered=$(echo "$filtered" | grep -vFx "$ex" || true)
done
if [ -z "$filtered" ]; then
echo "No services left after applying --except" >&2
return 1
fi
printf '%s\n' "$filtered"
}
CMD="${1:-help}"
[ "$#" -gt 0 ] && shift
case "$CMD" in
start|up)
exec docker compose up -d --wait "$@"
;;
stop|down)
exec docker compose down "$@"
;;
restart)
if [ "${1:-}" = "--except" ]; then
shift
[ $# -eq 0 ] && { echo "Usage: $(basename "$0") restart --except <svc>..." >&2; exit 1; }
services=$(services_except "$@") || exit 1
# shellcheck disable=SC2086
exec docker compose restart $services
fi
exec docker compose restart "$@"
;;
recreate)
if [ "${1:-}" = "--except" ]; then
shift
[ $# -eq 0 ] && { echo "Usage: $(basename "$0") recreate --except <svc>..." >&2; exit 1; }
services=$(services_except "$@") || exit 1
# shellcheck disable=SC2086
exec docker compose up -d --wait --force-recreate --no-deps $services
fi
if [ $# -eq 0 ]; then
docker compose down
exec docker compose up -d --wait
fi
# Single-service recreate: force-recreate the named services only,
# leave their dependencies running.
exec docker compose up -d --wait --force-recreate --no-deps "$@"
;;
status|ps)
exec docker compose ps "$@"
;;
logs)
exec docker compose logs -f "$@"
;;
inspect)
[ $# -eq 0 ] && { echo "Usage: $(basename "$0") inspect <service> [docker-inspect-args]" >&2; exit 1; }
svc="$1"; shift
cid=$(docker compose ps -q "$svc")
[ -z "$cid" ] && { echo "Service '$svc' is not running" >&2; exit 1; }
exec docker inspect "$cid" "$@"
;;
printenv)
[ $# -eq 0 ] && { echo "Usage: $(basename "$0") printenv <service>" >&2; exit 1; }
svc="$1"
cid=$(docker compose ps -q "$svc")
[ -z "$cid" ] && { echo "Service '$svc' is not running" >&2; exit 1; }
exec docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' "$cid"
;;
pull)
exec docker compose pull "$@"
;;
compose-config)
exec docker compose config "$@"
;;
config)
sub="${1:-show}"
[ "$#" -gt 0 ] && shift
current=$(read_compose_file)
case "$sub" in
show)
print_config "$current"
;;
add)
[ $# -eq 0 ] && { echo "Usage: $(basename "$0") config add <name>..." >&2; exit 1; }
new_value="${current:-docker-compose.yml}"
changed=false
for arg in "$@"; do
file=$(normalize_override "$arg") || exit 1
if [ ! -f "$file" ]; then
echo "ERROR: $file not found" >&2
exit 1
fi
case ":$new_value:" in
*":$file:"*) echo "Already present: $file" ;;
*) new_value="$new_value:$file"; changed=true ;;
esac
done
[ "$changed" = true ] && write_compose_file "$new_value"
print_config "$new_value"
;;
remove|rm)
[ $# -eq 0 ] && { echo "Usage: $(basename "$0") config remove <name>..." >&2; exit 1; }
new_value="${current:-docker-compose.yml}"
changed=false
for arg in "$@"; do
file=$(normalize_override "$arg") || exit 1
case ":$new_value:" in
*":$file:"*)
# Drop $file by rebuilding the colon list
tmp=""
OLD_IFS=$IFS
IFS=:
for tok in $new_value; do
[ "$tok" = "$file" ] || tmp="${tmp:+$tmp:}$tok"
done
IFS=$OLD_IFS
new_value="$tmp"
changed=true
;;
*) echo "Not present: $file" ;;
esac
done
[ "$changed" = true ] && write_compose_file "$new_value"
print_config "$new_value"
;;
*)
echo "Unknown config subcommand: $sub" >&2
echo "Use: config | config add <name>... | config remove <name>..." >&2
exit 1
;;
esac
;;
secrets)
if [ ! -f .env ]; then
echo "ERROR: .env not found in $(pwd)" >&2
exit 1
fi
for var in POSTGRES_PASSWORD DASHBOARD_PASSWORD \
SUPABASE_PUBLISHABLE_KEY SUPABASE_SECRET_KEY \
S3_PROTOCOL_ACCESS_KEY_ID S3_PROTOCOL_ACCESS_KEY_SECRET; do
line=$(grep "^${var}=" .env | head -n1)
if [ -n "$line" ]; then
echo "$line"
else
echo "${var}="
fi
done
echo ""
;;
help|-h|--help)
cat <<EOF
Usage: $(basename "$0") <command>
Commands:
start Start the stack (docker compose up -d --wait)
stop Stop the stack (docker compose down)
restart [service] Restart the stack (or named services)
restart --except <svc>...
Restart all services except the named ones
recreate [service] Stop then start, or force-recreate one service (--no-deps)
recreate --except <svc>...
Force-recreate all services except the named ones (--no-deps)
status Show service status
logs [service] Follow logs (optionally for a single service)
inspect <service> Inspect a service's container (forwards extra args to docker inspect)
printenv <service> Print a service's environment variables (one per line)
pull Pull all images
config Show the active COMPOSE_FILE list
config add <name> Add an override to COMPOSE_FILE in .env (short name or full filename)
config remove <name> Remove an override from COMPOSE_FILE in .env
compose-config Dump the fully-resolved docker compose config
secrets Show key passwords and API keys from .env
EOF
;;
*)
echo "Unknown command: $CMD" >&2
echo "Run '$0 help' for usage." >&2
exit 1
;;
esac