#!/bin/sh set -eu SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd -P) cd "$SCRIPT_DIR/.." env_override=${ENV_OVERRIDE:-./.env.override} cloud_dir=${CLOUD_DIR:-../cloud} SHELLHUB_EDITION=$( . ./.env 2>/dev/null [ -f "$env_override" ] && . "$env_override" eval "printf '%s' \"\${SHELLHUB_EDITION:-community}\"" ) SHELLHUB_EDITION=$(printf '%s' "$SHELLHUB_EDITION" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]') case "$SHELLHUB_EDITION" in community|enterprise|cloud) ;; *) echo "🚫 ERROR: invalid SHELLHUB_EDITION '$SHELLHUB_EDITION': must be community, enterprise, or cloud." >&2 exit 1 ;; esac env_var() { var=$1; default=$2 edition=$SHELLHUB_EDITION ( . ./.env 2>/dev/null [ "$edition" != "community" ] && [ -f "$cloud_dir/.env" ] \ && . "$cloud_dir/.env" [ -f "$env_override" ] && . "$env_override" eval "printf '%s' \"\${${var}:-${default}}\"" ) } SHELLHUB_ENV=$(env_var SHELLHUB_ENV production) SHELLHUB_AUTO_SSL=$(env_var SHELLHUB_AUTO_SSL false) SHELLHUB_DATABASE=$(env_var SHELLHUB_DATABASE postgres) SHELLHUB_POSTGRES_EXTERNAL=$(env_var SHELLHUB_POSTGRES_EXTERNAL false) SHELLHUB_BILLING=$(env_var SHELLHUB_BILLING "") EXTRA_COMPOSE_FILE=${EXTRA_COMPOSE_FILE:-$(env_var EXTRA_COMPOSE_FILE "")} sbin_path="$PATH:/usr/sbin:/sbin" daemon_is_rootless() { docker info --format '{{.SecurityOptions}}' 2>/dev/null | grep -q 'name=rootless' } daemon_socket() { docker_host=${DOCKER_HOST:-$(docker context inspect --format '{{.Endpoints.docker.Host}}' 2>/dev/null || true)} case "$docker_host" in unix://*) echo "${docker_host#unix://}" ;; esac } rootlesskit_capabilities() { rootlesskit=${ROOTLESSKIT-$(command -v rootlesskit 2>/dev/null)} [ -n "$rootlesskit" ] || return 127 PATH="$sbin_path" getcap "$rootlesskit" 2>/dev/null } lowest_published_port() { docker compose config 2>/dev/null \ | sed -n 's/^ *published: "\{0,1\}\([0-9][0-9]*\)"\{0,1\}$/\1/p' \ | sort -n | head -n 1 } rootless=false if [ "$SHELLHUB_ENV" = "development" ] && daemon_is_rootless; then rootless=true socket=$(daemon_socket) [ -z "$socket" ] || export SHELLHUB_DEV_DOCKER_SOCKET="$socket" fi COMPOSE_ENV_FILES="./.env,./versions.env" [ "$SHELLHUB_EDITION" != "community" ] && [ -f ./.env.enterprise ] \ && COMPOSE_ENV_FILES="$COMPOSE_ENV_FILES,./.env.enterprise" [ "$SHELLHUB_EDITION" != "community" ] && [ -f "$cloud_dir/.env" ] \ && COMPOSE_ENV_FILES="$COMPOSE_ENV_FILES,$cloud_dir/.env" [ -f "$env_override" ] && COMPOSE_ENV_FILES="$COMPOSE_ENV_FILES,$env_override" export COMPOSE_ENV_FILES COMPOSE_FILE="docker-compose.yml" [ "$SHELLHUB_AUTO_SSL" = "true" ] && COMPOSE_FILE="${COMPOSE_FILE}:docker-compose.autossl.yml" [ "$SHELLHUB_ENV" = "development" ] && COMPOSE_FILE="${COMPOSE_FILE}:docker-compose.dev.yml:docker-compose.agent.yml" [ "$SHELLHUB_EDITION" != "community" ] && [ "$SHELLHUB_ENV" != "development" ] && COMPOSE_FILE="${COMPOSE_FILE}:docker-compose.enterprise.yml" [ "$SHELLHUB_EDITION" != "community" ] && [ "$SHELLHUB_ENV" = "development" ] && [ -f "$cloud_dir/docker-compose.enterprise.dev.yml" ] && COMPOSE_FILE="${COMPOSE_FILE}:$cloud_dir/docker-compose.enterprise.dev.yml" [ "$SHELLHUB_EDITION" = "cloud" ] && [ -f "$cloud_dir/docker-compose.yml" ] && COMPOSE_FILE="${COMPOSE_FILE}:$cloud_dir/docker-compose.yml" [ -n "$SHELLHUB_BILLING" ] && export COMPOSE_PROFILES="$SHELLHUB_BILLING" [ "$SHELLHUB_DATABASE" = "postgres" ] \ || echo "⚠️ WARNING: Unknown SHELLHUB_DATABASE '$SHELLHUB_DATABASE'. Defaulting to postgres." >&2 [ "$SHELLHUB_POSTGRES_EXTERNAL" = "true" ] || COMPOSE_FILE="${COMPOSE_FILE}:docker-compose.postgres.yml" [ -f docker-compose.override.yml ] && COMPOSE_FILE="${COMPOSE_FILE}:docker-compose.override.yml" if [ -n "$EXTRA_COMPOSE_FILE" ]; then if [ ! -f "$EXTRA_COMPOSE_FILE" ]; then echo "🚫 ERROR: EXTRA_COMPOSE_FILE '$EXTRA_COMPOSE_FILE' does not exist." >&2 exit 1 fi COMPOSE_FILE="${COMPOSE_FILE}:${EXTRA_COMPOSE_FILE}" fi export COMPOSE_FILE installed_playwright_version() { sed -n '/"node_modules\/@playwright\/test": {/{n;s/.*"version": "\([^"]*\)".*/\1/p;}' ui/package-lock.json 2>/dev/null } export SHELLHUB_WORKSPACE="${SHELLHUB_WORKSPACE:-$(cd .. && pwd -P)}" export E2E_PLAYWRIGHT_VERSION="${E2E_PLAYWRIGHT_VERSION:-$(installed_playwright_version)}" if [ "$rootless" = true ]; then port=$(lowest_published_port) unprivileged_start=$(PATH="$sbin_path" sysctl -n net.ipv4.ip_unprivileged_port_start 2>/dev/null || echo 1024) if [ -n "$port" ] && [ "$port" -lt "$unprivileged_start" ]; then capabilities=$(rootlesskit_capabilities) && getcap_status=0 || getcap_status=$? if [ "$getcap_status" -eq 127 ]; then echo "⚠️ WARNING: getcap or rootlesskit not found, cannot tell whether rootless Docker may publish port $port." >&2 elif ! echo "$capabilities" | grep -q 'cap_net_bind_service'; then echo "🚫 ERROR: rootless Docker cannot publish port $port. Either grant rootlesskit the capability:" >&2 echo " sudo setcap cap_net_bind_service=ep \"\$(command -v rootlesskit)\" && systemctl --user restart docker" >&2 echo " or set SHELLHUB_HTTP_PORT, SHELLHUB_HTTPS_PORT and SHELLHUB_SSH_PORT to $unprivileged_start or above in .env.override." >&2 exit 1 fi fi fi exec docker compose "$@"