feat(installer): scaffold install.sh with constants and utilities
Creates the installer skeleton (Phase 2, Task 8) with version/registry
constants, color codes, default values, _ENV_* variable capture pattern,
config/state variable declarations, and utility functions (log_*, print_banner,
prompt, prompt_password, prompt_yesno, generate_password).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:22:21 +02:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
CAMELEER_INSTALLER_VERSION="1.0.0"
|
|
|
|
|
|
CAMELEER_DEFAULT_VERSION="latest"
|
|
|
|
|
|
REGISTRY="gitea.siegeln.net/cameleer"
|
|
|
|
|
|
|
|
|
|
|
|
# --- Colors ---
|
|
|
|
|
|
RED='\033[0;31m'
|
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
|
YELLOW='\033[1;33m'
|
|
|
|
|
|
BLUE='\033[0;34m'
|
|
|
|
|
|
BOLD='\033[1m'
|
|
|
|
|
|
NC='\033[0m'
|
|
|
|
|
|
|
|
|
|
|
|
# --- Defaults ---
|
|
|
|
|
|
DEFAULT_INSTALL_DIR="./cameleer"
|
|
|
|
|
|
DEFAULT_PUBLIC_PROTOCOL="https"
|
|
|
|
|
|
DEFAULT_ADMIN_USER="admin"
|
|
|
|
|
|
DEFAULT_TLS_MODE="self-signed"
|
|
|
|
|
|
DEFAULT_HTTP_PORT="80"
|
|
|
|
|
|
DEFAULT_HTTPS_PORT="443"
|
|
|
|
|
|
DEFAULT_LOGTO_CONSOLE_PORT="3002"
|
|
|
|
|
|
DEFAULT_LOGTO_CONSOLE_EXPOSED="true"
|
|
|
|
|
|
DEFAULT_VENDOR_ENABLED="false"
|
|
|
|
|
|
DEFAULT_VENDOR_USER="vendor"
|
|
|
|
|
|
DEFAULT_COMPOSE_PROJECT="cameleer-saas"
|
|
|
|
|
|
DEFAULT_DOCKER_SOCKET="/var/run/docker.sock"
|
|
|
|
|
|
|
|
|
|
|
|
# --- Config values (set by args/env/config/prompts) ---
|
|
|
|
|
|
# Save environment values before initialization (CLI args override these)
|
|
|
|
|
|
_ENV_PUBLIC_HOST="${PUBLIC_HOST:-}"
|
|
|
|
|
|
_ENV_PUBLIC_PROTOCOL="${PUBLIC_PROTOCOL:-}"
|
|
|
|
|
|
_ENV_POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-}"
|
|
|
|
|
|
_ENV_CLICKHOUSE_PASSWORD="${CLICKHOUSE_PASSWORD:-}"
|
|
|
|
|
|
_ENV_TLS_MODE="${TLS_MODE:-}"
|
|
|
|
|
|
_ENV_CERT_FILE="${CERT_FILE:-}"
|
|
|
|
|
|
_ENV_KEY_FILE="${KEY_FILE:-}"
|
|
|
|
|
|
_ENV_CA_FILE="${CA_FILE:-}"
|
|
|
|
|
|
_ENV_HTTP_PORT="${HTTP_PORT:-}"
|
|
|
|
|
|
_ENV_HTTPS_PORT="${HTTPS_PORT:-}"
|
|
|
|
|
|
_ENV_LOGTO_CONSOLE_PORT="${LOGTO_CONSOLE_PORT:-}"
|
|
|
|
|
|
_ENV_LOGTO_CONSOLE_EXPOSED="${LOGTO_CONSOLE_EXPOSED:-}"
|
|
|
|
|
|
_ENV_VENDOR_ENABLED="${VENDOR_ENABLED:-}"
|
|
|
|
|
|
_ENV_VENDOR_USER="${VENDOR_USER:-}"
|
|
|
|
|
|
_ENV_VENDOR_PASS="${VENDOR_PASS:-}"
|
|
|
|
|
|
_ENV_MONITORING_NETWORK="${MONITORING_NETWORK:-}"
|
|
|
|
|
|
_ENV_COMPOSE_PROJECT="${COMPOSE_PROJECT:-}"
|
|
|
|
|
|
_ENV_DOCKER_SOCKET="${DOCKER_SOCKET:-}"
|
|
|
|
|
|
_ENV_NODE_TLS_REJECT="${NODE_TLS_REJECT:-}"
|
|
|
|
|
|
|
|
|
|
|
|
INSTALL_DIR=""
|
|
|
|
|
|
PUBLIC_HOST=""
|
|
|
|
|
|
PUBLIC_PROTOCOL=""
|
|
|
|
|
|
ADMIN_USER=""
|
|
|
|
|
|
ADMIN_PASS=""
|
|
|
|
|
|
TLS_MODE=""
|
|
|
|
|
|
CERT_FILE=""
|
|
|
|
|
|
KEY_FILE=""
|
|
|
|
|
|
CA_FILE=""
|
|
|
|
|
|
POSTGRES_PASSWORD=""
|
|
|
|
|
|
CLICKHOUSE_PASSWORD=""
|
|
|
|
|
|
HTTP_PORT=""
|
|
|
|
|
|
HTTPS_PORT=""
|
|
|
|
|
|
LOGTO_CONSOLE_PORT=""
|
|
|
|
|
|
LOGTO_CONSOLE_EXPOSED=""
|
|
|
|
|
|
VENDOR_ENABLED=""
|
|
|
|
|
|
VENDOR_USER=""
|
|
|
|
|
|
VENDOR_PASS=""
|
|
|
|
|
|
MONITORING_NETWORK=""
|
|
|
|
|
|
VERSION=""
|
|
|
|
|
|
COMPOSE_PROJECT=""
|
|
|
|
|
|
DOCKER_SOCKET=""
|
|
|
|
|
|
NODE_TLS_REJECT=""
|
2026-04-13 18:18:25 +02:00
|
|
|
|
TENANT_ORG_NAME=""
|
feat(installer): scaffold install.sh with constants and utilities
Creates the installer skeleton (Phase 2, Task 8) with version/registry
constants, color codes, default values, _ENV_* variable capture pattern,
config/state variable declarations, and utility functions (log_*, print_banner,
prompt, prompt_password, prompt_yesno, generate_password).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:22:21 +02:00
|
|
|
|
|
|
|
|
|
|
# --- State ---
|
|
|
|
|
|
MODE="" # simple, expert, silent
|
|
|
|
|
|
IS_RERUN=false
|
|
|
|
|
|
RERUN_ACTION="" # upgrade, reconfigure, reinstall
|
|
|
|
|
|
CONFIRM_DESTROY=false
|
|
|
|
|
|
CONFIG_FILE_PATH=""
|
|
|
|
|
|
|
|
|
|
|
|
# --- Utility functions ---
|
|
|
|
|
|
|
|
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
|
|
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
|
|
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
|
|
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
|
|
|
|
|
|
|
|
|
|
print_banner() {
|
|
|
|
|
|
echo -e "${BOLD}"
|
|
|
|
|
|
echo " ____ _ "
|
|
|
|
|
|
echo " / ___|__ _ _ __ ___ ___ | | ___ ___ _ __ "
|
|
|
|
|
|
echo "| | / _\` | '_ \` _ \\ / _ \\| |/ _ \\/ _ \\ '__|"
|
|
|
|
|
|
echo "| |__| (_| | | | | | | __/| | __/ __/ | "
|
|
|
|
|
|
echo " \\____\\__,_|_| |_| |_|\\___||_|\\___|\\___||_| "
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " SaaS Platform Installer v${CAMELEER_INSTALLER_VERSION}"
|
|
|
|
|
|
echo -e "${NC}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prompt() {
|
|
|
|
|
|
local var_name="$1" prompt_text="$2" default="${3:-}"
|
|
|
|
|
|
local input
|
|
|
|
|
|
if [ -n "$default" ]; then
|
|
|
|
|
|
read -rp " $prompt_text [$default]: " input
|
|
|
|
|
|
eval "$var_name=\"\${input:-$default}\""
|
|
|
|
|
|
else
|
|
|
|
|
|
read -rp " $prompt_text: " input
|
|
|
|
|
|
eval "$var_name=\"\$input\""
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prompt_password() {
|
|
|
|
|
|
local var_name="$1" prompt_text="$2" default="${3:-}"
|
|
|
|
|
|
local input
|
|
|
|
|
|
if [ -n "$default" ]; then
|
|
|
|
|
|
read -rsp " $prompt_text [${default:+********}]: " input
|
|
|
|
|
|
echo
|
|
|
|
|
|
eval "$var_name=\"\${input:-$default}\""
|
|
|
|
|
|
else
|
|
|
|
|
|
read -rsp " $prompt_text: " input
|
|
|
|
|
|
echo
|
|
|
|
|
|
eval "$var_name=\"\$input\""
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prompt_yesno() {
|
|
|
|
|
|
local prompt_text="$1" default="${2:-n}"
|
|
|
|
|
|
local input
|
|
|
|
|
|
if [ "$default" = "y" ]; then
|
|
|
|
|
|
read -rp " $prompt_text [Y/n]: " input
|
|
|
|
|
|
case "${input:-y}" in
|
|
|
|
|
|
[nN]|[nN][oO]) return 1 ;; *) return 0 ;; esac
|
|
|
|
|
|
else
|
|
|
|
|
|
read -rp " $prompt_text [y/N]: " input
|
|
|
|
|
|
case "${input:-n}" in
|
|
|
|
|
|
[yY]|[yY][eE][sS]) return 0 ;; *) return 1 ;; esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generate_password() {
|
|
|
|
|
|
openssl rand -base64 24 | tr -d '/+=' | head -c 32
|
|
|
|
|
|
}
|
2026-04-13 16:24:35 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Argument parsing ---
|
|
|
|
|
|
|
|
|
|
|
|
parse_args() {
|
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
--silent) MODE="silent" ;;
|
|
|
|
|
|
--expert) MODE="expert" ;;
|
|
|
|
|
|
--config) CONFIG_FILE_PATH="$2"; shift ;;
|
|
|
|
|
|
--install-dir) INSTALL_DIR="$2"; shift ;;
|
|
|
|
|
|
--public-host) PUBLIC_HOST="$2"; shift ;;
|
|
|
|
|
|
--public-protocol) PUBLIC_PROTOCOL="$2"; shift ;;
|
|
|
|
|
|
--admin-user) ADMIN_USER="$2"; shift ;;
|
|
|
|
|
|
--admin-password) ADMIN_PASS="$2"; shift ;;
|
|
|
|
|
|
--tls-mode) TLS_MODE="$2"; shift ;;
|
|
|
|
|
|
--cert-file) CERT_FILE="$2"; shift ;;
|
|
|
|
|
|
--key-file) KEY_FILE="$2"; shift ;;
|
|
|
|
|
|
--ca-file) CA_FILE="$2"; shift ;;
|
|
|
|
|
|
--postgres-password) POSTGRES_PASSWORD="$2"; shift ;;
|
|
|
|
|
|
--clickhouse-password) CLICKHOUSE_PASSWORD="$2"; shift ;;
|
|
|
|
|
|
--http-port) HTTP_PORT="$2"; shift ;;
|
|
|
|
|
|
--https-port) HTTPS_PORT="$2"; shift ;;
|
|
|
|
|
|
--logto-console-port) LOGTO_CONSOLE_PORT="$2"; shift ;;
|
|
|
|
|
|
--logto-console-exposed) LOGTO_CONSOLE_EXPOSED="$2"; shift ;;
|
|
|
|
|
|
--vendor-enabled) VENDOR_ENABLED="$2"; shift ;;
|
|
|
|
|
|
--vendor-user) VENDOR_USER="$2"; shift ;;
|
|
|
|
|
|
--vendor-password) VENDOR_PASS="$2"; shift ;;
|
|
|
|
|
|
--monitoring-network) MONITORING_NETWORK="$2"; shift ;;
|
|
|
|
|
|
--version) VERSION="$2"; shift ;;
|
|
|
|
|
|
--compose-project) COMPOSE_PROJECT="$2"; shift ;;
|
|
|
|
|
|
--docker-socket) DOCKER_SOCKET="$2"; shift ;;
|
|
|
|
|
|
--node-tls-reject) NODE_TLS_REJECT="$2"; shift ;;
|
2026-04-13 18:18:25 +02:00
|
|
|
|
--tenant-org-name) TENANT_ORG_NAME="$2"; shift ;;
|
2026-04-13 16:24:35 +02:00
|
|
|
|
--reconfigure) RERUN_ACTION="reconfigure" ;;
|
|
|
|
|
|
--reinstall) RERUN_ACTION="reinstall" ;;
|
|
|
|
|
|
--confirm-destroy) CONFIRM_DESTROY=true ;;
|
|
|
|
|
|
--help|-h) show_help; exit 0 ;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
log_error "Unknown option: $1"
|
|
|
|
|
|
echo " Run with --help for usage."
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
shift
|
|
|
|
|
|
done
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
show_help() {
|
|
|
|
|
|
echo "Usage: install.sh [OPTIONS]"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "Modes:"
|
|
|
|
|
|
echo " (default) Interactive simple mode (6 questions)"
|
|
|
|
|
|
echo " --expert Interactive expert mode (all options)"
|
|
|
|
|
|
echo " --silent Non-interactive, use defaults + overrides"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "Options:"
|
|
|
|
|
|
echo " --install-dir DIR Install directory (default: ./cameleer)"
|
|
|
|
|
|
echo " --public-host HOST Public hostname (default: auto-detect)"
|
|
|
|
|
|
echo " --admin-user USER Admin username (default: admin)"
|
|
|
|
|
|
echo " --admin-password PASS Admin password (default: generated)"
|
|
|
|
|
|
echo " --tls-mode MODE self-signed or custom (default: self-signed)"
|
|
|
|
|
|
echo " --cert-file PATH TLS certificate file"
|
|
|
|
|
|
echo " --key-file PATH TLS key file"
|
|
|
|
|
|
echo " --ca-file PATH CA bundle file"
|
|
|
|
|
|
echo " --monitoring-network NAME Docker network for Prometheus scraping"
|
|
|
|
|
|
echo " --version TAG Image version tag (default: latest)"
|
|
|
|
|
|
echo " --config FILE Load config from file"
|
|
|
|
|
|
echo " --help Show this help"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "Expert options:"
|
|
|
|
|
|
echo " --postgres-password, --clickhouse-password, --http-port,"
|
|
|
|
|
|
echo " --https-port, --logto-console-port, --logto-console-exposed,"
|
|
|
|
|
|
echo " --vendor-enabled, --vendor-user, --vendor-password,"
|
|
|
|
|
|
echo " --compose-project, --docker-socket, --node-tls-reject"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "Re-run options:"
|
|
|
|
|
|
echo " --reconfigure Re-run interactive setup (preserve data)"
|
|
|
|
|
|
echo " --reinstall --confirm-destroy Fresh install (destroys data)"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "Config precedence: CLI flags > env vars > config file > defaults"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# --- Config file handling ---
|
|
|
|
|
|
|
|
|
|
|
|
load_config_file() {
|
|
|
|
|
|
local file="$1"
|
|
|
|
|
|
[ ! -f "$file" ] && return
|
|
|
|
|
|
while IFS='=' read -r key value; do
|
|
|
|
|
|
case "$key" in
|
|
|
|
|
|
\#*|"") continue ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
key=$(echo "$key" | tr -d ' ')
|
|
|
|
|
|
value=$(echo "$value" | sed 's/^[ ]*//;s/[ ]*$//')
|
|
|
|
|
|
case "$key" in
|
|
|
|
|
|
install_dir) [ -z "$INSTALL_DIR" ] && INSTALL_DIR="$value" ;;
|
|
|
|
|
|
public_host) [ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$value" ;;
|
|
|
|
|
|
public_protocol) [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$value" ;;
|
|
|
|
|
|
admin_user) [ -z "$ADMIN_USER" ] && ADMIN_USER="$value" ;;
|
|
|
|
|
|
admin_password) [ -z "$ADMIN_PASS" ] && ADMIN_PASS="$value" ;;
|
|
|
|
|
|
tls_mode) [ -z "$TLS_MODE" ] && TLS_MODE="$value" ;;
|
|
|
|
|
|
cert_file) [ -z "$CERT_FILE" ] && CERT_FILE="$value" ;;
|
|
|
|
|
|
key_file) [ -z "$KEY_FILE" ] && KEY_FILE="$value" ;;
|
|
|
|
|
|
ca_file) [ -z "$CA_FILE" ] && CA_FILE="$value" ;;
|
|
|
|
|
|
postgres_password) [ -z "$POSTGRES_PASSWORD" ] && POSTGRES_PASSWORD="$value" ;;
|
|
|
|
|
|
clickhouse_password) [ -z "$CLICKHOUSE_PASSWORD" ] && CLICKHOUSE_PASSWORD="$value" ;;
|
|
|
|
|
|
http_port) [ -z "$HTTP_PORT" ] && HTTP_PORT="$value" ;;
|
|
|
|
|
|
https_port) [ -z "$HTTPS_PORT" ] && HTTPS_PORT="$value" ;;
|
|
|
|
|
|
logto_console_port) [ -z "$LOGTO_CONSOLE_PORT" ] && LOGTO_CONSOLE_PORT="$value" ;;
|
|
|
|
|
|
logto_console_exposed) [ -z "$LOGTO_CONSOLE_EXPOSED" ] && LOGTO_CONSOLE_EXPOSED="$value" ;;
|
|
|
|
|
|
vendor_enabled) [ -z "$VENDOR_ENABLED" ] && VENDOR_ENABLED="$value" ;;
|
|
|
|
|
|
vendor_user) [ -z "$VENDOR_USER" ] && VENDOR_USER="$value" ;;
|
|
|
|
|
|
vendor_password) [ -z "$VENDOR_PASS" ] && VENDOR_PASS="$value" ;;
|
|
|
|
|
|
monitoring_network) [ -z "$MONITORING_NETWORK" ] && MONITORING_NETWORK="$value" ;;
|
|
|
|
|
|
version) [ -z "$VERSION" ] && VERSION="$value" ;;
|
|
|
|
|
|
compose_project) [ -z "$COMPOSE_PROJECT" ] && COMPOSE_PROJECT="$value" ;;
|
|
|
|
|
|
docker_socket) [ -z "$DOCKER_SOCKET" ] && DOCKER_SOCKET="$value" ;;
|
|
|
|
|
|
node_tls_reject) [ -z "$NODE_TLS_REJECT" ] && NODE_TLS_REJECT="$value" ;;
|
2026-04-13 18:18:25 +02:00
|
|
|
|
tenant_org_name) [ -z "$TENANT_ORG_NAME" ] && TENANT_ORG_NAME="$value" ;;
|
2026-04-13 16:24:35 +02:00
|
|
|
|
esac
|
|
|
|
|
|
done < "$file"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
load_env_overrides() {
|
|
|
|
|
|
[ -z "$INSTALL_DIR" ] && INSTALL_DIR="${CAMELEER_INSTALL_DIR:-}"
|
|
|
|
|
|
[ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$_ENV_PUBLIC_HOST"
|
|
|
|
|
|
[ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$_ENV_PUBLIC_PROTOCOL"
|
|
|
|
|
|
[ -z "$ADMIN_USER" ] && ADMIN_USER="${SAAS_ADMIN_USER:-}"
|
|
|
|
|
|
[ -z "$ADMIN_PASS" ] && ADMIN_PASS="${SAAS_ADMIN_PASS:-}"
|
|
|
|
|
|
[ -z "$TLS_MODE" ] && TLS_MODE="$_ENV_TLS_MODE"
|
|
|
|
|
|
[ -z "$CERT_FILE" ] && CERT_FILE="$_ENV_CERT_FILE"
|
|
|
|
|
|
[ -z "$KEY_FILE" ] && KEY_FILE="$_ENV_KEY_FILE"
|
|
|
|
|
|
[ -z "$CA_FILE" ] && CA_FILE="$_ENV_CA_FILE"
|
|
|
|
|
|
[ -z "$POSTGRES_PASSWORD" ] && POSTGRES_PASSWORD="$_ENV_POSTGRES_PASSWORD"
|
|
|
|
|
|
[ -z "$CLICKHOUSE_PASSWORD" ] && CLICKHOUSE_PASSWORD="$_ENV_CLICKHOUSE_PASSWORD"
|
|
|
|
|
|
[ -z "$HTTP_PORT" ] && HTTP_PORT="$_ENV_HTTP_PORT"
|
|
|
|
|
|
[ -z "$HTTPS_PORT" ] && HTTPS_PORT="$_ENV_HTTPS_PORT"
|
|
|
|
|
|
[ -z "$LOGTO_CONSOLE_PORT" ] && LOGTO_CONSOLE_PORT="$_ENV_LOGTO_CONSOLE_PORT"
|
|
|
|
|
|
[ -z "$LOGTO_CONSOLE_EXPOSED" ] && LOGTO_CONSOLE_EXPOSED="$_ENV_LOGTO_CONSOLE_EXPOSED"
|
|
|
|
|
|
[ -z "$VENDOR_ENABLED" ] && VENDOR_ENABLED="$_ENV_VENDOR_ENABLED"
|
|
|
|
|
|
[ -z "$VENDOR_USER" ] && VENDOR_USER="$_ENV_VENDOR_USER"
|
|
|
|
|
|
[ -z "$VENDOR_PASS" ] && VENDOR_PASS="$_ENV_VENDOR_PASS"
|
|
|
|
|
|
[ -z "$MONITORING_NETWORK" ] && MONITORING_NETWORK="$_ENV_MONITORING_NETWORK"
|
|
|
|
|
|
[ -z "$VERSION" ] && VERSION="${CAMELEER_VERSION:-}"
|
|
|
|
|
|
[ -z "$COMPOSE_PROJECT" ] && COMPOSE_PROJECT="$_ENV_COMPOSE_PROJECT"
|
|
|
|
|
|
[ -z "$DOCKER_SOCKET" ] && DOCKER_SOCKET="$_ENV_DOCKER_SOCKET"
|
|
|
|
|
|
[ -z "$NODE_TLS_REJECT" ] && NODE_TLS_REJECT="$_ENV_NODE_TLS_REJECT"
|
|
|
|
|
|
}
|
2026-04-13 16:24:55 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Prerequisites ---
|
|
|
|
|
|
|
|
|
|
|
|
check_prerequisites() {
|
|
|
|
|
|
log_info "Checking prerequisites..."
|
|
|
|
|
|
local errors=0
|
|
|
|
|
|
|
|
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
|
|
|
|
log_error "Docker is not installed."
|
|
|
|
|
|
echo " Install Docker Engine: https://docs.docker.com/engine/install/"
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
else
|
|
|
|
|
|
local docker_version
|
|
|
|
|
|
docker_version=$(docker version --format '{{.Server.Version}}' 2>/dev/null || echo "unknown")
|
|
|
|
|
|
log_info "Docker version: $docker_version"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
|
|
|
|
log_error "Docker Compose v2 is not available."
|
|
|
|
|
|
echo " 'docker compose' subcommand required (not standalone docker-compose)."
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
else
|
|
|
|
|
|
local compose_version
|
|
|
|
|
|
compose_version=$(docker compose version --short 2>/dev/null || echo "unknown")
|
|
|
|
|
|
log_info "Docker Compose version: $compose_version"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if ! command -v openssl >/dev/null 2>&1; then
|
|
|
|
|
|
log_error "OpenSSL is not installed (needed for password generation)."
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
local socket="${DOCKER_SOCKET:-$DEFAULT_DOCKER_SOCKET}"
|
|
|
|
|
|
if [ ! -S "$socket" ]; then
|
|
|
|
|
|
log_warn "Docker socket not found at $socket"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
check_port_available "${HTTP_PORT:-$DEFAULT_HTTP_PORT}" "HTTP"
|
|
|
|
|
|
check_port_available "${HTTPS_PORT:-$DEFAULT_HTTPS_PORT}" "HTTPS"
|
|
|
|
|
|
check_port_available "${LOGTO_CONSOLE_PORT:-$DEFAULT_LOGTO_CONSOLE_PORT}" "Logto Console"
|
|
|
|
|
|
|
|
|
|
|
|
if [ $errors -gt 0 ]; then
|
|
|
|
|
|
log_error "$errors prerequisite(s) not met. Please install missing dependencies and retry."
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
log_success "All prerequisites met."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
check_port_available() {
|
|
|
|
|
|
local port="$1" name="$2"
|
|
|
|
|
|
if command -v ss >/dev/null 2>&1; then
|
|
|
|
|
|
if ss -tlnp 2>/dev/null | grep -q ":${port} "; then
|
|
|
|
|
|
log_warn "Port $port ($name) is already in use."
|
|
|
|
|
|
fi
|
|
|
|
|
|
elif command -v netstat >/dev/null 2>&1; then
|
|
|
|
|
|
if netstat -tlnp 2>/dev/null | grep -q ":${port} "; then
|
|
|
|
|
|
log_warn "Port $port ($name) is already in use."
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# --- Auto-detection ---
|
|
|
|
|
|
|
|
|
|
|
|
auto_detect() {
|
|
|
|
|
|
if [ -z "$PUBLIC_HOST" ]; then
|
|
|
|
|
|
PUBLIC_HOST=$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "localhost")
|
2026-04-13 17:58:32 +02:00
|
|
|
|
# Normalize to lowercase (Windows hostnames are uppercase)
|
|
|
|
|
|
PUBLIC_HOST=$(echo "$PUBLIC_HOST" | tr '[:upper:]' '[:lower:]')
|
2026-04-13 16:24:55 +02:00
|
|
|
|
local primary_ip
|
|
|
|
|
|
primary_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}' || true)
|
|
|
|
|
|
if [ -n "$primary_ip" ]; then
|
|
|
|
|
|
local rdns
|
|
|
|
|
|
rdns=$(dig +short -x "$primary_ip" 2>/dev/null | sed 's/\.$//' || true)
|
|
|
|
|
|
[ -n "$rdns" ] && PUBLIC_HOST="$rdns"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -z "$DOCKER_SOCKET" ]; then
|
|
|
|
|
|
DOCKER_SOCKET="$DEFAULT_DOCKER_SOCKET"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
detect_existing_install() {
|
|
|
|
|
|
local dir="${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
|
|
|
|
|
|
if [ -f "$dir/cameleer.conf" ]; then
|
|
|
|
|
|
IS_RERUN=true
|
|
|
|
|
|
INSTALL_DIR="$dir"
|
|
|
|
|
|
load_config_file "$dir/cameleer.conf"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
2026-04-13 16:25:16 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Interactive prompts ---
|
|
|
|
|
|
|
|
|
|
|
|
select_mode() {
|
|
|
|
|
|
if [ -n "$MODE" ]; then return; fi
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Installation mode:"
|
|
|
|
|
|
echo " [1] Simple — 6 questions, sensible defaults (recommended)"
|
|
|
|
|
|
echo " [2] Expert — configure everything"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
local choice
|
|
|
|
|
|
read -rp " Select mode [1]: " choice
|
|
|
|
|
|
case "${choice:-1}" in
|
|
|
|
|
|
2) MODE="expert" ;;
|
|
|
|
|
|
*) MODE="simple" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_simple_prompts() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD}--- Simple Installation ---${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
prompt INSTALL_DIR "Install directory" "${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
|
|
|
|
|
|
prompt PUBLIC_HOST "Public hostname" "${PUBLIC_HOST:-localhost}"
|
|
|
|
|
|
prompt ADMIN_USER "Admin username" "${ADMIN_USER:-$DEFAULT_ADMIN_USER}"
|
|
|
|
|
|
|
|
|
|
|
|
if prompt_yesno "Auto-generate admin password?" "y"; then
|
|
|
|
|
|
ADMIN_PASS=""
|
|
|
|
|
|
else
|
|
|
|
|
|
prompt_password ADMIN_PASS "Admin password" ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
if prompt_yesno "Use custom TLS certificates? (no = self-signed)"; then
|
|
|
|
|
|
TLS_MODE="custom"
|
|
|
|
|
|
prompt CERT_FILE "Path to certificate file (PEM)" ""
|
|
|
|
|
|
prompt KEY_FILE "Path to private key file (PEM)" ""
|
|
|
|
|
|
if prompt_yesno "Include CA bundle?"; then
|
|
|
|
|
|
prompt CA_FILE "Path to CA bundle (PEM)" ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
TLS_MODE="self-signed"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
prompt MONITORING_NETWORK "Monitoring network name (empty = skip)" ""
|
2026-04-13 18:18:25 +02:00
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Deployment mode:"
|
|
|
|
|
|
echo " [1] Multi-tenant vendor — admin manages platform, creates tenants on demand"
|
|
|
|
|
|
echo " [2] Single tenant — set up one tenant for immediate use"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
local deploy_choice
|
|
|
|
|
|
read -rp " Select mode [1]: " deploy_choice
|
|
|
|
|
|
case "${deploy_choice:-1}" in
|
|
|
|
|
|
2)
|
|
|
|
|
|
VENDOR_ENABLED="false"
|
|
|
|
|
|
prompt TENANT_ORG_NAME "Organization / tenant name" ""
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
VENDOR_ENABLED="true"
|
|
|
|
|
|
TENANT_ORG_NAME=""
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
2026-04-13 16:25:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_expert_prompts() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD}--- Expert Installation ---${NC}"
|
|
|
|
|
|
|
|
|
|
|
|
run_simple_prompts
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD} Credentials:${NC}"
|
|
|
|
|
|
if prompt_yesno "Auto-generate database passwords?" "y"; then
|
|
|
|
|
|
POSTGRES_PASSWORD=""
|
|
|
|
|
|
CLICKHOUSE_PASSWORD=""
|
|
|
|
|
|
else
|
|
|
|
|
|
prompt_password POSTGRES_PASSWORD "PostgreSQL password" ""
|
|
|
|
|
|
prompt_password CLICKHOUSE_PASSWORD "ClickHouse password" ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
if prompt_yesno "Enable vendor account?"; then
|
|
|
|
|
|
VENDOR_ENABLED="true"
|
|
|
|
|
|
prompt VENDOR_USER "Vendor username" "${VENDOR_USER:-$DEFAULT_VENDOR_USER}"
|
|
|
|
|
|
if prompt_yesno "Auto-generate vendor password?" "y"; then
|
|
|
|
|
|
VENDOR_PASS=""
|
|
|
|
|
|
else
|
|
|
|
|
|
prompt_password VENDOR_PASS "Vendor password" ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
VENDOR_ENABLED="false"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD} Networking:${NC}"
|
|
|
|
|
|
prompt HTTP_PORT "HTTP port" "${HTTP_PORT:-$DEFAULT_HTTP_PORT}"
|
|
|
|
|
|
prompt HTTPS_PORT "HTTPS port" "${HTTPS_PORT:-$DEFAULT_HTTPS_PORT}"
|
|
|
|
|
|
prompt LOGTO_CONSOLE_PORT "Logto admin console port" "${LOGTO_CONSOLE_PORT:-$DEFAULT_LOGTO_CONSOLE_PORT}"
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD} Docker:${NC}"
|
|
|
|
|
|
prompt VERSION "Image version/tag" "${VERSION:-$CAMELEER_DEFAULT_VERSION}"
|
|
|
|
|
|
prompt COMPOSE_PROJECT "Compose project name" "${COMPOSE_PROJECT:-$DEFAULT_COMPOSE_PROJECT}"
|
|
|
|
|
|
prompt DOCKER_SOCKET "Docker socket path" "${DOCKER_SOCKET:-$DEFAULT_DOCKER_SOCKET}"
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD} Logto:${NC}"
|
|
|
|
|
|
if prompt_yesno "Expose Logto admin console externally?" "y"; then
|
|
|
|
|
|
LOGTO_CONSOLE_EXPOSED="true"
|
|
|
|
|
|
else
|
|
|
|
|
|
LOGTO_CONSOLE_EXPOSED="false"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
2026-04-13 16:25:34 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Config merge and validation ---
|
|
|
|
|
|
|
|
|
|
|
|
merge_config() {
|
|
|
|
|
|
: "${INSTALL_DIR:=$DEFAULT_INSTALL_DIR}"
|
|
|
|
|
|
: "${PUBLIC_HOST:=localhost}"
|
|
|
|
|
|
: "${PUBLIC_PROTOCOL:=$DEFAULT_PUBLIC_PROTOCOL}"
|
|
|
|
|
|
: "${ADMIN_USER:=$DEFAULT_ADMIN_USER}"
|
|
|
|
|
|
: "${TLS_MODE:=$DEFAULT_TLS_MODE}"
|
|
|
|
|
|
: "${HTTP_PORT:=$DEFAULT_HTTP_PORT}"
|
|
|
|
|
|
: "${HTTPS_PORT:=$DEFAULT_HTTPS_PORT}"
|
|
|
|
|
|
: "${LOGTO_CONSOLE_PORT:=$DEFAULT_LOGTO_CONSOLE_PORT}"
|
|
|
|
|
|
: "${LOGTO_CONSOLE_EXPOSED:=$DEFAULT_LOGTO_CONSOLE_EXPOSED}"
|
|
|
|
|
|
: "${VENDOR_ENABLED:=$DEFAULT_VENDOR_ENABLED}"
|
|
|
|
|
|
: "${VENDOR_USER:=$DEFAULT_VENDOR_USER}"
|
|
|
|
|
|
: "${VERSION:=$CAMELEER_DEFAULT_VERSION}"
|
|
|
|
|
|
: "${COMPOSE_PROJECT:=$DEFAULT_COMPOSE_PROJECT}"
|
|
|
|
|
|
: "${DOCKER_SOCKET:=$DEFAULT_DOCKER_SOCKET}"
|
|
|
|
|
|
|
2026-04-13 18:04:30 +02:00
|
|
|
|
# Force lowercase hostname — Logto normalizes internally, case mismatch breaks JWT validation
|
|
|
|
|
|
PUBLIC_HOST=$(echo "$PUBLIC_HOST" | tr '[:upper:]' '[:lower:]')
|
|
|
|
|
|
|
2026-04-13 16:25:34 +02:00
|
|
|
|
if [ -z "$NODE_TLS_REJECT" ]; then
|
|
|
|
|
|
if [ "$TLS_MODE" = "custom" ]; then
|
|
|
|
|
|
NODE_TLS_REJECT="1"
|
|
|
|
|
|
else
|
|
|
|
|
|
NODE_TLS_REJECT="0"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_config() {
|
|
|
|
|
|
local errors=0
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$TLS_MODE" = "custom" ]; then
|
|
|
|
|
|
if [ ! -f "$CERT_FILE" ]; then
|
|
|
|
|
|
log_error "Certificate file not found: $CERT_FILE"
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ ! -f "$KEY_FILE" ]; then
|
|
|
|
|
|
log_error "Key file not found: $KEY_FILE"
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ -n "$CA_FILE" ] && [ ! -f "$CA_FILE" ]; then
|
|
|
|
|
|
log_error "CA bundle not found: $CA_FILE"
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
for port_var in HTTP_PORT HTTPS_PORT LOGTO_CONSOLE_PORT; do
|
|
|
|
|
|
local port_val
|
|
|
|
|
|
eval "port_val=\$$port_var"
|
|
|
|
|
|
if ! echo "$port_val" | grep -qE '^[0-9]+$' || [ "$port_val" -lt 1 ] || [ "$port_val" -gt 65535 ]; then
|
|
|
|
|
|
log_error "Invalid port for $port_var: $port_val"
|
|
|
|
|
|
errors=$((errors + 1))
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
if [ $errors -gt 0 ]; then
|
|
|
|
|
|
log_error "Configuration validation failed."
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
log_success "Configuration validated."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generate_passwords() {
|
|
|
|
|
|
if [ -z "$ADMIN_PASS" ]; then
|
|
|
|
|
|
ADMIN_PASS=$(generate_password)
|
|
|
|
|
|
log_info "Generated admin password."
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ -z "$POSTGRES_PASSWORD" ]; then
|
|
|
|
|
|
POSTGRES_PASSWORD=$(generate_password)
|
|
|
|
|
|
log_info "Generated PostgreSQL password."
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ -z "$CLICKHOUSE_PASSWORD" ]; then
|
|
|
|
|
|
CLICKHOUSE_PASSWORD=$(generate_password)
|
|
|
|
|
|
log_info "Generated ClickHouse password."
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ "$VENDOR_ENABLED" = "true" ] && [ -z "$VENDOR_PASS" ]; then
|
|
|
|
|
|
VENDOR_PASS=$(generate_password)
|
|
|
|
|
|
log_info "Generated vendor password."
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
2026-04-13 16:30:32 +02:00
|
|
|
|
|
|
|
|
|
|
# --- File generation ---
|
|
|
|
|
|
|
|
|
|
|
|
copy_certs() {
|
|
|
|
|
|
local certs_dir="$INSTALL_DIR/certs"
|
|
|
|
|
|
mkdir -p "$certs_dir"
|
|
|
|
|
|
cp "$CERT_FILE" "$certs_dir/cert.pem"
|
|
|
|
|
|
cp "$KEY_FILE" "$certs_dir/key.pem"
|
|
|
|
|
|
if [ -n "$CA_FILE" ]; then
|
|
|
|
|
|
cp "$CA_FILE" "$certs_dir/ca.pem"
|
|
|
|
|
|
fi
|
|
|
|
|
|
log_info "Copied TLS certificates to $certs_dir/"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generate_env_file() {
|
|
|
|
|
|
local f="$INSTALL_DIR/.env"
|
|
|
|
|
|
cat > "$f" << EOF
|
|
|
|
|
|
# Cameleer SaaS Configuration
|
|
|
|
|
|
# Generated by installer v${CAMELEER_INSTALLER_VERSION} on $(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
|
|
|
|
|
|
|
|
|
|
# Image version
|
|
|
|
|
|
VERSION=${VERSION}
|
|
|
|
|
|
|
|
|
|
|
|
# Public access
|
|
|
|
|
|
PUBLIC_HOST=${PUBLIC_HOST}
|
|
|
|
|
|
PUBLIC_PROTOCOL=${PUBLIC_PROTOCOL}
|
|
|
|
|
|
|
|
|
|
|
|
# Ports
|
|
|
|
|
|
HTTP_PORT=${HTTP_PORT}
|
|
|
|
|
|
HTTPS_PORT=${HTTPS_PORT}
|
|
|
|
|
|
LOGTO_CONSOLE_PORT=${LOGTO_CONSOLE_PORT}
|
|
|
|
|
|
|
|
|
|
|
|
# PostgreSQL
|
|
|
|
|
|
POSTGRES_USER=cameleer
|
|
|
|
|
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
|
|
|
|
POSTGRES_DB=cameleer_saas
|
|
|
|
|
|
|
|
|
|
|
|
# ClickHouse
|
|
|
|
|
|
CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
|
|
|
|
|
|
|
|
|
|
|
|
# Admin user
|
|
|
|
|
|
SAAS_ADMIN_USER=${ADMIN_USER}
|
|
|
|
|
|
SAAS_ADMIN_PASS=${ADMIN_PASS}
|
|
|
|
|
|
|
|
|
|
|
|
# TLS
|
|
|
|
|
|
NODE_TLS_REJECT=${NODE_TLS_REJECT}
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$TLS_MODE" = "custom" ]; then
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
CERT_FILE=/user-certs/cert.pem
|
|
|
|
|
|
KEY_FILE=/user-certs/key.pem
|
|
|
|
|
|
EOF
|
|
|
|
|
|
if [ -n "$CA_FILE" ]; then
|
|
|
|
|
|
echo "CA_FILE=/user-certs/ca.pem" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
|
|
|
|
|
|
# Vendor account
|
|
|
|
|
|
VENDOR_SEED_ENABLED=${VENDOR_ENABLED}
|
|
|
|
|
|
VENDOR_USER=${VENDOR_USER}
|
|
|
|
|
|
VENDOR_PASS=${VENDOR_PASS:-}
|
|
|
|
|
|
|
2026-04-13 18:18:25 +02:00
|
|
|
|
# Single-tenant org (when vendor is disabled)
|
|
|
|
|
|
TENANT_ORG_NAME=${TENANT_ORG_NAME:-}
|
|
|
|
|
|
|
2026-04-13 16:30:32 +02:00
|
|
|
|
# Docker
|
|
|
|
|
|
DOCKER_SOCKET=${DOCKER_SOCKET}
|
2026-04-13 18:39:20 +02:00
|
|
|
|
DOCKER_GID=$(stat -c '%g' "${DOCKER_SOCKET}" 2>/dev/null || echo "0")
|
2026-04-13 16:30:32 +02:00
|
|
|
|
|
|
|
|
|
|
# Provisioning images
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer3-server:${VERSION}
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer3-server-ui:${VERSION}
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
log_info "Generated .env"
|
|
|
|
|
|
cp "$f" "$INSTALL_DIR/.env.bak"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generate_compose_file() {
|
|
|
|
|
|
local f="$INSTALL_DIR/docker-compose.yml"
|
|
|
|
|
|
: > "$f"
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
# Cameleer SaaS Platform
|
feat(installer): add main function and complete install.sh
Appends the main() entry point that wires together all installer phases:
arg parsing, config loading, rerun detection, prerequisites, auto-detect,
interactive prompts, config merge/validate, password generation, file
generation, docker pull/up, health verification, and output printing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:33:15 +02:00
|
|
|
|
# Generated by Cameleer installer <20> do not edit manually
|
2026-04-13 16:30:32 +02:00
|
|
|
|
|
|
|
|
|
|
services:
|
|
|
|
|
|
traefik:
|
|
|
|
|
|
image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest}
|
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "${HTTP_PORT:-80}:80"
|
|
|
|
|
|
- "${HTTPS_PORT:-443}:443"
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
- "${LOGTO_CONSOLE_PORT:-3002}:3002"
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
environment:
|
|
|
|
|
|
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
|
|
|
|
|
|
CERT_FILE: ${CERT_FILE:-}
|
|
|
|
|
|
KEY_FILE: ${KEY_FILE:-}
|
|
|
|
|
|
CA_FILE: ${CA_FILE:-}
|
|
|
|
|
|
volumes:
|
|
|
|
|
|
- certs:/certs
|
|
|
|
|
|
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$TLS_MODE" = "custom" ]; then
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
- ./certs:/user-certs:ro
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
networks:
|
|
|
|
|
|
- cameleer
|
|
|
|
|
|
- cameleer-traefik
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
echo " - ${MONITORING_NETWORK}" >> "$f"
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
labels:
|
|
|
|
|
|
- "prometheus.io/scrape=true"
|
|
|
|
|
|
- "prometheus.io/port=8082"
|
|
|
|
|
|
- "prometheus.io/path=/metrics"
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
|
|
|
|
|
|
postgres:
|
|
|
|
|
|
image: ${POSTGRES_IMAGE:-gitea.siegeln.net/cameleer/cameleer-postgres}:${VERSION:-latest}
|
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
environment:
|
|
|
|
|
|
POSTGRES_DB: cameleer_saas
|
|
|
|
|
|
POSTGRES_USER: ${POSTGRES_USER:-cameleer}
|
|
|
|
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
|
|
|
|
volumes:
|
|
|
|
|
|
- pgdata:/var/lib/postgresql/data
|
|
|
|
|
|
healthcheck:
|
|
|
|
|
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d cameleer_saas"]
|
|
|
|
|
|
interval: 5s
|
|
|
|
|
|
timeout: 5s
|
|
|
|
|
|
retries: 5
|
|
|
|
|
|
networks:
|
|
|
|
|
|
- cameleer
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
echo " - ${MONITORING_NETWORK}" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
|
|
|
|
|
|
clickhouse:
|
|
|
|
|
|
image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest}
|
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
environment:
|
|
|
|
|
|
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
|
|
|
|
|
|
volumes:
|
|
|
|
|
|
- chdata:/var/lib/clickhouse
|
|
|
|
|
|
healthcheck:
|
|
|
|
|
|
test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"]
|
|
|
|
|
|
interval: 10s
|
|
|
|
|
|
timeout: 5s
|
|
|
|
|
|
retries: 3
|
|
|
|
|
|
networks:
|
|
|
|
|
|
- cameleer
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
echo " - ${MONITORING_NETWORK}" >> "$f"
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
labels:
|
|
|
|
|
|
- "prometheus.io/scrape=true"
|
|
|
|
|
|
- "prometheus.io/port=9363"
|
|
|
|
|
|
- "prometheus.io/path=/metrics"
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
|
|
|
|
|
|
logto:
|
|
|
|
|
|
image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest}
|
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
depends_on:
|
|
|
|
|
|
postgres:
|
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
|
environment:
|
|
|
|
|
|
DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@postgres:5432/logto
|
|
|
|
|
|
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
|
|
|
|
|
|
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
|
|
|
|
|
|
TRUST_PROXY_HEADER: 1
|
|
|
|
|
|
NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}"
|
|
|
|
|
|
LOGTO_ENDPOINT: http://logto:3001
|
|
|
|
|
|
LOGTO_ADMIN_ENDPOINT: http://logto:3002
|
|
|
|
|
|
LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
|
|
|
|
|
|
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
|
|
|
|
|
|
PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https}
|
|
|
|
|
|
PG_HOST: postgres
|
|
|
|
|
|
PG_USER: ${POSTGRES_USER:-cameleer}
|
|
|
|
|
|
PG_PASSWORD: ${POSTGRES_PASSWORD}
|
|
|
|
|
|
PG_DB_SAAS: cameleer_saas
|
|
|
|
|
|
SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin}
|
|
|
|
|
|
SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:-admin}
|
|
|
|
|
|
VENDOR_SEED_ENABLED: "${VENDOR_SEED_ENABLED:-false}"
|
|
|
|
|
|
VENDOR_USER: ${VENDOR_USER:-vendor}
|
|
|
|
|
|
VENDOR_PASS: ${VENDOR_PASS:-vendor}
|
2026-04-13 18:18:25 +02:00
|
|
|
|
TENANT_ORG_NAME: ${TENANT_ORG_NAME:-}
|
2026-04-13 16:30:32 +02:00
|
|
|
|
healthcheck:
|
|
|
|
|
|
test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"]
|
|
|
|
|
|
interval: 10s
|
|
|
|
|
|
timeout: 5s
|
|
|
|
|
|
retries: 60
|
|
|
|
|
|
start_period: 30s
|
|
|
|
|
|
labels:
|
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
|
- traefik.http.routers.logto.rule=PathPrefix(`/`)
|
|
|
|
|
|
- traefik.http.routers.logto.priority=1
|
|
|
|
|
|
- traefik.http.routers.logto.entrypoints=websecure
|
|
|
|
|
|
- traefik.http.routers.logto.tls=true
|
|
|
|
|
|
- traefik.http.routers.logto.service=logto
|
|
|
|
|
|
- traefik.http.routers.logto.middlewares=logto-cors
|
|
|
|
|
|
- "traefik.http.middlewares.logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}"
|
|
|
|
|
|
- traefik.http.middlewares.logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS
|
|
|
|
|
|
- traefik.http.middlewares.logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type
|
|
|
|
|
|
- traefik.http.middlewares.logto-cors.headers.accessControlAllowCredentials=true
|
|
|
|
|
|
- traefik.http.services.logto.loadbalancer.server.port=3001
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
- traefik.http.routers.logto-console.rule=PathPrefix(`/`)
|
|
|
|
|
|
- traefik.http.routers.logto-console.entrypoints=admin-console
|
|
|
|
|
|
- traefik.http.routers.logto-console.tls=true
|
|
|
|
|
|
- traefik.http.routers.logto-console.service=logto-console
|
|
|
|
|
|
- traefik.http.services.logto-console.loadbalancer.server.port=3002
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
volumes:
|
|
|
|
|
|
- bootstrapdata:/data
|
|
|
|
|
|
networks:
|
|
|
|
|
|
- cameleer
|
|
|
|
|
|
|
|
|
|
|
|
cameleer-saas:
|
|
|
|
|
|
image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest}
|
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
|
depends_on:
|
|
|
|
|
|
logto:
|
|
|
|
|
|
condition: service_healthy
|
2026-04-13 18:58:04 +02:00
|
|
|
|
env_file:
|
|
|
|
|
|
- .env
|
2026-04-13 16:30:32 +02:00
|
|
|
|
environment:
|
2026-04-13 18:30:55 +02:00
|
|
|
|
DOCKER_HOST: unix:///var/run/docker.sock
|
2026-04-13 16:30:32 +02:00
|
|
|
|
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/cameleer_saas
|
|
|
|
|
|
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer}
|
|
|
|
|
|
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
|
|
|
|
|
|
CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://logto:3001
|
|
|
|
|
|
CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
|
2026-04-13 18:44:32 +02:00
|
|
|
|
CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer}
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD}
|
2026-04-13 18:55:36 +02:00
|
|
|
|
CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD}
|
2026-04-13 16:30:32 +02:00
|
|
|
|
CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https}
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost}
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server:latest}
|
|
|
|
|
|
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui:latest}
|
|
|
|
|
|
labels:
|
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
|
- traefik.http.routers.saas.rule=PathPrefix(`/platform`)
|
|
|
|
|
|
- traefik.http.routers.saas.entrypoints=websecure
|
|
|
|
|
|
- traefik.http.routers.saas.tls=true
|
|
|
|
|
|
- traefik.http.services.saas.loadbalancer.server.port=8080
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
- "prometheus.io/scrape=true"
|
|
|
|
|
|
- "prometheus.io/port=8080"
|
|
|
|
|
|
- "prometheus.io/path=/platform/actuator/prometheus"
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
volumes:
|
|
|
|
|
|
- bootstrapdata:/data/bootstrap:ro
|
|
|
|
|
|
- certs:/certs
|
|
|
|
|
|
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
|
|
|
|
|
|
networks:
|
|
|
|
|
|
- cameleer
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
echo " - ${MONITORING_NETWORK}" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-04-13 18:39:20 +02:00
|
|
|
|
# Detect Docker socket GID for container access
|
|
|
|
|
|
local docker_gid
|
|
|
|
|
|
docker_gid=$(stat -c '%g' "${DOCKER_SOCKET:-/var/run/docker.sock}" 2>/dev/null || echo "0")
|
|
|
|
|
|
cat >> "$f" << EOF
|
2026-04-13 16:30:32 +02:00
|
|
|
|
group_add:
|
2026-04-13 18:39:20 +02:00
|
|
|
|
- "${docker_gid}"
|
2026-04-13 16:30:32 +02:00
|
|
|
|
|
|
|
|
|
|
volumes:
|
2026-04-13 18:39:20 +02:00
|
|
|
|
EOF
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
2026-04-13 16:30:32 +02:00
|
|
|
|
pgdata:
|
|
|
|
|
|
chdata:
|
|
|
|
|
|
certs:
|
|
|
|
|
|
bootstrapdata:
|
|
|
|
|
|
|
|
|
|
|
|
networks:
|
|
|
|
|
|
cameleer:
|
|
|
|
|
|
driver: bridge
|
|
|
|
|
|
cameleer-traefik:
|
|
|
|
|
|
name: cameleer-traefik
|
|
|
|
|
|
driver: bridge
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
${MONITORING_NETWORK}:
|
|
|
|
|
|
external: true
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
log_info "Generated docker-compose.yml"
|
|
|
|
|
|
}
|
2026-04-13 16:30:53 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Docker operations ---
|
|
|
|
|
|
|
|
|
|
|
|
docker_compose_pull() {
|
|
|
|
|
|
log_info "Pulling Docker images..."
|
|
|
|
|
|
(cd "$INSTALL_DIR" && docker compose -p "$COMPOSE_PROJECT" pull)
|
|
|
|
|
|
log_success "All images pulled."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
docker_compose_up() {
|
|
|
|
|
|
log_info "Starting Cameleer SaaS platform..."
|
|
|
|
|
|
(cd "$INSTALL_DIR" && docker compose -p "$COMPOSE_PROJECT" up -d)
|
|
|
|
|
|
log_info "Containers started."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
docker_compose_down() {
|
|
|
|
|
|
log_info "Stopping Cameleer SaaS platform..."
|
|
|
|
|
|
(cd "$INSTALL_DIR" && docker compose -p "$COMPOSE_PROJECT" down)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# --- Health verification ---
|
|
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
wait_for_docker_healthy() {
|
|
|
|
|
|
local name="$1" service="$2" timeout_secs="${3:-300}"
|
2026-04-13 16:30:53 +02:00
|
|
|
|
local start_time=$(date +%s)
|
|
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
while true; do
|
|
|
|
|
|
local elapsed=$(( $(date +%s) - start_time ))
|
|
|
|
|
|
if [ $elapsed -ge $timeout_secs ]; then
|
|
|
|
|
|
printf " ${RED}[FAIL]${NC} %-20s not healthy after %ds\n" "$name" "$timeout_secs"
|
|
|
|
|
|
echo " Check: docker compose -p $COMPOSE_PROJECT logs $service"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
local health
|
|
|
|
|
|
health=$(cd "$INSTALL_DIR" && docker compose -p "$COMPOSE_PROJECT" ps "$service" --format '{{.Health}}' 2>/dev/null || echo "unknown")
|
|
|
|
|
|
case "$health" in
|
|
|
|
|
|
healthy)
|
|
|
|
|
|
local duration=$(( $(date +%s) - start_time ))
|
|
|
|
|
|
printf " ${GREEN}[ok]${NC} %-20s ready (%ds)\n" "$name" "$duration"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
unhealthy)
|
|
|
|
|
|
printf " ${RED}[FAIL]${NC} %-20s unhealthy\n" "$name"
|
|
|
|
|
|
echo " Check: docker compose -p $COMPOSE_PROJECT logs $service"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
sleep 3
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
check_endpoint() {
|
|
|
|
|
|
local name="$1" url="$2" timeout_secs="${3:-120}"
|
|
|
|
|
|
local start_time=$(date +%s)
|
|
|
|
|
|
|
|
|
|
|
|
while true; do
|
|
|
|
|
|
local elapsed=$(( $(date +%s) - start_time ))
|
|
|
|
|
|
if [ $elapsed -ge $timeout_secs ]; then
|
|
|
|
|
|
printf " ${RED}[FAIL]${NC} %-20s not reachable after %ds\n" "$name" "$timeout_secs"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
if curl -sfk -o /dev/null "$url" 2>/dev/null; then
|
|
|
|
|
|
local duration=$(( $(date +%s) - start_time ))
|
2026-04-13 16:30:53 +02:00
|
|
|
|
printf " ${GREEN}[ok]${NC} %-20s ready (%ds)\n" "$name" "$duration"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2026-04-13 18:28:04 +02:00
|
|
|
|
sleep 3
|
2026-04-13 16:30:53 +02:00
|
|
|
|
done
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
verify_health() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
log_info "Verifying installation..."
|
|
|
|
|
|
local failed=0
|
|
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
wait_for_docker_healthy "PostgreSQL" "postgres" 120 || failed=1
|
2026-04-13 16:30:53 +02:00
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
[ $failed -eq 0 ] && \
|
|
|
|
|
|
wait_for_docker_healthy "ClickHouse" "clickhouse" 120 || failed=1
|
2026-04-13 16:30:53 +02:00
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
[ $failed -eq 0 ] && \
|
|
|
|
|
|
wait_for_docker_healthy "Logto + Bootstrap" "logto" 300 || failed=1
|
2026-04-13 16:30:53 +02:00
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
[ $failed -eq 0 ] && \
|
|
|
|
|
|
check_endpoint "Cameleer SaaS" "https://localhost:${HTTPS_PORT}/platform/api/config" 120 || failed=1
|
2026-04-13 16:30:53 +02:00
|
|
|
|
|
2026-04-13 18:28:04 +02:00
|
|
|
|
[ $failed -eq 0 ] && \
|
|
|
|
|
|
check_endpoint "Traefik routing" "https://localhost:${HTTPS_PORT}/" 30 || failed=1
|
2026-04-13 16:30:53 +02:00
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
if [ $failed -ne 0 ]; then
|
2026-04-13 18:28:04 +02:00
|
|
|
|
log_error "Installation verification failed. Stack is running — check logs."
|
2026-04-13 16:30:53 +02:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
log_success "All services healthy."
|
|
|
|
|
|
}
|
2026-04-13 16:31:38 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Output file generation ---
|
|
|
|
|
|
|
|
|
|
|
|
write_config_file() {
|
|
|
|
|
|
local f="$INSTALL_DIR/cameleer.conf"
|
|
|
|
|
|
cat > "$f" << EOF
|
|
|
|
|
|
# Cameleer installation config
|
|
|
|
|
|
# Generated by installer v${CAMELEER_INSTALLER_VERSION} on $(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
|
|
|
|
|
|
|
|
|
|
install_dir=${INSTALL_DIR}
|
|
|
|
|
|
public_host=${PUBLIC_HOST}
|
|
|
|
|
|
public_protocol=${PUBLIC_PROTOCOL}
|
|
|
|
|
|
admin_user=${ADMIN_USER}
|
|
|
|
|
|
tls_mode=${TLS_MODE}
|
|
|
|
|
|
http_port=${HTTP_PORT}
|
|
|
|
|
|
https_port=${HTTPS_PORT}
|
|
|
|
|
|
logto_console_port=${LOGTO_CONSOLE_PORT}
|
|
|
|
|
|
logto_console_exposed=${LOGTO_CONSOLE_EXPOSED}
|
|
|
|
|
|
vendor_enabled=${VENDOR_ENABLED}
|
|
|
|
|
|
vendor_user=${VENDOR_USER}
|
|
|
|
|
|
monitoring_network=${MONITORING_NETWORK}
|
|
|
|
|
|
version=${VERSION}
|
|
|
|
|
|
compose_project=${COMPOSE_PROJECT}
|
|
|
|
|
|
docker_socket=${DOCKER_SOCKET}
|
|
|
|
|
|
node_tls_reject=${NODE_TLS_REJECT}
|
2026-04-13 18:18:25 +02:00
|
|
|
|
tenant_org_name=${TENANT_ORG_NAME}
|
2026-04-13 16:31:38 +02:00
|
|
|
|
EOF
|
|
|
|
|
|
log_info "Saved installer config to cameleer.conf"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generate_credentials_file() {
|
|
|
|
|
|
local f="$INSTALL_DIR/credentials.txt"
|
|
|
|
|
|
cat > "$f" << EOF
|
|
|
|
|
|
===========================================
|
|
|
|
|
|
CAMELEER PLATFORM CREDENTIALS
|
|
|
|
|
|
Generated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
|
|
|
|
|
|
|
|
|
|
SECURE THIS FILE AND DELETE AFTER NOTING
|
|
|
|
|
|
THESE CREDENTIALS CANNOT BE RECOVERED
|
|
|
|
|
|
===========================================
|
|
|
|
|
|
|
|
|
|
|
|
Admin Console: ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/platform/
|
|
|
|
|
|
Admin User: ${ADMIN_USER}
|
|
|
|
|
|
Admin Password: ${ADMIN_PASS}
|
|
|
|
|
|
|
|
|
|
|
|
PostgreSQL: cameleer / ${POSTGRES_PASSWORD}
|
|
|
|
|
|
ClickHouse: default / ${CLICKHOUSE_PASSWORD}
|
|
|
|
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$VENDOR_ENABLED" = "true" ]; then
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
Vendor User: ${VENDOR_USER}
|
|
|
|
|
|
Vendor Password: ${VENDOR_PASS}
|
|
|
|
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "Vendor User: (not enabled)" >> "$f"
|
|
|
|
|
|
echo "" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
|
|
|
|
|
|
echo "Logto Console: ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "Logto Console: (not exposed)" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
chmod 600 "$f"
|
|
|
|
|
|
log_info "Saved credentials to credentials.txt"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generate_install_doc() {
|
|
|
|
|
|
local f="$INSTALL_DIR/INSTALL.md"
|
|
|
|
|
|
local tls_desc="Self-signed (auto-generated)"
|
|
|
|
|
|
[ "$TLS_MODE" = "custom" ] && tls_desc="Custom certificate"
|
|
|
|
|
|
|
|
|
|
|
|
cat > "$f" << EOF
|
feat(installer): add main function and complete install.sh
Appends the main() entry point that wires together all installer phases:
arg parsing, config loading, rerun detection, prerequisites, auto-detect,
interactive prompts, config merge/validate, password generation, file
generation, docker pull/up, health verification, and output printing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:33:15 +02:00
|
|
|
|
# Cameleer SaaS <20> Installation Documentation
|
2026-04-13 16:31:38 +02:00
|
|
|
|
|
|
|
|
|
|
## Installation Summary
|
|
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| **Version** | ${VERSION} |
|
|
|
|
|
|
| **Date** | $(date -u '+%Y-%m-%d %H:%M:%S UTC') |
|
|
|
|
|
|
| **Installer** | v${CAMELEER_INSTALLER_VERSION} |
|
|
|
|
|
|
| **Install Directory** | ${INSTALL_DIR} |
|
|
|
|
|
|
| **Hostname** | ${PUBLIC_HOST} |
|
|
|
|
|
|
| **TLS** | ${tls_desc} |
|
|
|
|
|
|
|
|
|
|
|
|
## Service URLs
|
|
|
|
|
|
|
|
|
|
|
|
- **Platform UI:** ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/platform/
|
|
|
|
|
|
- **API Endpoint:** ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/platform/api/
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
|
|
|
|
|
|
echo "- **Logto Admin Console:** ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
|
|
|
|
|
|
## First Steps
|
|
|
|
|
|
|
|
|
|
|
|
1. Open the Platform UI in your browser
|
|
|
|
|
|
2. Log in with the admin credentials from `credentials.txt`
|
|
|
|
|
|
3. Create your first tenant via the Vendor console
|
|
|
|
|
|
4. The platform will provision a dedicated server instance for the tenant
|
|
|
|
|
|
|
|
|
|
|
|
## Architecture
|
|
|
|
|
|
|
|
|
|
|
|
| Container | Purpose |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `traefik` | Reverse proxy, TLS termination, routing |
|
|
|
|
|
|
| `postgres` | PostgreSQL database (SaaS + Logto + tenant schemas) |
|
|
|
|
|
|
| `clickhouse` | Time-series storage (traces, metrics, logs) |
|
|
|
|
|
|
| `logto` | OIDC identity provider + bootstrap |
|
|
|
|
|
|
| `cameleer-saas` | SaaS platform (Spring Boot + React) |
|
|
|
|
|
|
|
|
|
|
|
|
Per-tenant `cameleer3-server` and `cameleer3-server-ui` containers are provisioned dynamically when tenants are created.
|
|
|
|
|
|
|
|
|
|
|
|
## Networking
|
|
|
|
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
| Port | Service |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| ${HTTP_PORT} | HTTP (redirects to HTTPS) |
|
|
|
|
|
|
| ${HTTPS_PORT} | HTTPS (main entry point) |
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
|
|
|
|
|
|
echo "| ${LOGTO_CONSOLE_PORT} | Logto Admin Console |" >> "$f"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$MONITORING_NETWORK" ]; then
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
|
|
|
|
|
|
### Monitoring
|
|
|
|
|
|
|
|
|
|
|
|
Services are connected to the \`${MONITORING_NETWORK}\` Docker network with Prometheus labels for auto-discovery.
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
|
|
|
|
|
|
## TLS
|
|
|
|
|
|
|
|
|
|
|
|
**Mode:** ${tls_desc}
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$TLS_MODE" = "self-signed" ]; then
|
|
|
|
|
|
cat >> "$f" << 'EOF'
|
|
|
|
|
|
|
|
|
|
|
|
The platform generated a self-signed certificate on first boot. To replace it:
|
|
|
|
|
|
1. Log in as admin and navigate to **Certificates** in the vendor console
|
|
|
|
|
|
2. Upload your certificate and key via the UI
|
|
|
|
|
|
3. Activate the new certificate (zero-downtime swap)
|
|
|
|
|
|
EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
cat >> "$f" << EOF
|
|
|
|
|
|
|
|
|
|
|
|
## Data & Backups
|
|
|
|
|
|
|
|
|
|
|
|
| Docker Volume | Contains |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| \`pgdata\` | PostgreSQL data (tenants, licenses, audit) |
|
|
|
|
|
|
| \`chdata\` | ClickHouse data (traces, metrics, logs) |
|
|
|
|
|
|
| \`certs\` | TLS certificates |
|
|
|
|
|
|
| \`bootstrapdata\` | Logto bootstrap results |
|
|
|
|
|
|
|
|
|
|
|
|
### Backup Commands
|
|
|
|
|
|
|
|
|
|
|
|
\`\`\`bash
|
|
|
|
|
|
# PostgreSQL
|
|
|
|
|
|
docker compose -p ${COMPOSE_PROJECT} exec postgres pg_dump -U cameleer cameleer_saas > backup.sql
|
|
|
|
|
|
|
|
|
|
|
|
# ClickHouse
|
|
|
|
|
|
docker compose -p ${COMPOSE_PROJECT} exec clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native
|
|
|
|
|
|
\`\`\`
|
|
|
|
|
|
|
|
|
|
|
|
## Upgrading
|
|
|
|
|
|
|
|
|
|
|
|
Re-run the installer with a new version:
|
|
|
|
|
|
|
|
|
|
|
|
\`\`\`bash
|
|
|
|
|
|
curl -sfL https://install.cameleer.io | bash -s -- --install-dir ${INSTALL_DIR} --version NEW_VERSION
|
|
|
|
|
|
\`\`\`
|
|
|
|
|
|
|
|
|
|
|
|
The installer preserves your \`.env\`, credentials, and data volumes. Only the compose file and images are updated.
|
|
|
|
|
|
|
|
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
|
|
| Issue | Command |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| Service not starting | \`docker compose -p ${COMPOSE_PROJECT} logs SERVICE_NAME\` |
|
|
|
|
|
|
| Bootstrap failed | \`docker compose -p ${COMPOSE_PROJECT} logs logto\` |
|
|
|
|
|
|
| Routing issues | \`docker compose -p ${COMPOSE_PROJECT} logs traefik\` |
|
|
|
|
|
|
| Database issues | \`docker compose -p ${COMPOSE_PROJECT} exec postgres psql -U cameleer -d cameleer_saas\` |
|
|
|
|
|
|
|
|
|
|
|
|
## Uninstalling
|
|
|
|
|
|
|
|
|
|
|
|
\`\`\`bash
|
|
|
|
|
|
# Stop and remove containers
|
|
|
|
|
|
cd ${INSTALL_DIR} && docker compose -p ${COMPOSE_PROJECT} down
|
|
|
|
|
|
|
|
|
|
|
|
# Remove data volumes (DESTRUCTIVE)
|
|
|
|
|
|
cd ${INSTALL_DIR} && docker compose -p ${COMPOSE_PROJECT} down -v
|
|
|
|
|
|
|
|
|
|
|
|
# Remove install directory
|
|
|
|
|
|
rm -rf ${INSTALL_DIR}
|
|
|
|
|
|
\`\`\`
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
|
|
log_info "Generated INSTALL.md"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_credentials() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD}==========================================${NC}"
|
|
|
|
|
|
echo -e "${BOLD} CAMELEER PLATFORM CREDENTIALS${NC}"
|
|
|
|
|
|
echo -e "${BOLD}==========================================${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " Admin Console: ${BLUE}${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/platform/${NC}"
|
|
|
|
|
|
echo -e " Admin User: ${BOLD}${ADMIN_USER}${NC}"
|
|
|
|
|
|
echo -e " Admin Password: ${BOLD}${ADMIN_PASS}${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " PostgreSQL: cameleer / ${POSTGRES_PASSWORD}"
|
|
|
|
|
|
echo -e " ClickHouse: default / ${CLICKHOUSE_PASSWORD}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
if [ "$VENDOR_ENABLED" = "true" ]; then
|
|
|
|
|
|
echo -e " Vendor User: ${BOLD}${VENDOR_USER}${NC}"
|
|
|
|
|
|
echo -e " Vendor Password: ${BOLD}${VENDOR_PASS}${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
|
|
|
|
|
|
echo -e " Logto Console: ${BLUE}${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo -e " Credentials saved to: ${INSTALL_DIR}/credentials.txt"
|
|
|
|
|
|
echo -e " ${YELLOW}Secure this file and delete after noting credentials.${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_summary() {
|
|
|
|
|
|
echo -e "${GREEN}==========================================${NC}"
|
|
|
|
|
|
echo -e "${GREEN} Installation complete!${NC}"
|
|
|
|
|
|
echo -e "${GREEN}==========================================${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Install directory: $INSTALL_DIR"
|
|
|
|
|
|
echo " Documentation: $INSTALL_DIR/INSTALL.md"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " To manage the stack:"
|
|
|
|
|
|
echo " cd $INSTALL_DIR"
|
|
|
|
|
|
echo " docker compose -p $COMPOSE_PROJECT ps # status"
|
|
|
|
|
|
echo " docker compose -p $COMPOSE_PROJECT logs -f # logs"
|
|
|
|
|
|
echo " docker compose -p $COMPOSE_PROJECT down # stop"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
2026-04-13 16:32:02 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Re-run and upgrade ---
|
|
|
|
|
|
|
|
|
|
|
|
show_rerun_menu() {
|
|
|
|
|
|
local current_version
|
|
|
|
|
|
current_version=$(grep '^version=' "$INSTALL_DIR/cameleer.conf" 2>/dev/null | cut -d= -f2 || echo "unknown")
|
|
|
|
|
|
local current_host
|
|
|
|
|
|
current_host=$(grep '^public_host=' "$INSTALL_DIR/cameleer.conf" 2>/dev/null | cut -d= -f2 || echo "unknown")
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${BOLD}Existing Cameleer installation detected (v${current_version})${NC}"
|
|
|
|
|
|
echo " Install directory: $INSTALL_DIR"
|
|
|
|
|
|
echo " Public host: $current_host"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$MODE" = "silent" ]; then
|
|
|
|
|
|
RERUN_ACTION="${RERUN_ACTION:-upgrade}"
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$RERUN_ACTION" ]; then return; fi
|
|
|
|
|
|
|
|
|
|
|
|
local new_version="${VERSION:-$CAMELEER_DEFAULT_VERSION}"
|
|
|
|
|
|
echo " [1] Upgrade to v${new_version} (pull new images, update compose)"
|
|
|
|
|
|
echo " [2] Reconfigure (re-run interactive setup, preserve data)"
|
|
|
|
|
|
echo " [3] Reinstall (fresh install, WARNING: destroys data volumes)"
|
|
|
|
|
|
echo " [4] Cancel"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
local choice
|
|
|
|
|
|
read -rp " Select [1]: " choice
|
|
|
|
|
|
case "${choice:-1}" in
|
|
|
|
|
|
1) RERUN_ACTION="upgrade" ;;
|
|
|
|
|
|
2) RERUN_ACTION="reconfigure" ;;
|
|
|
|
|
|
3) RERUN_ACTION="reinstall" ;;
|
|
|
|
|
|
4) echo "Cancelled."; exit 0 ;;
|
|
|
|
|
|
*) echo "Invalid choice."; exit 1 ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handle_rerun() {
|
|
|
|
|
|
case "$RERUN_ACTION" in
|
|
|
|
|
|
upgrade)
|
|
|
|
|
|
log_info "Upgrading installation..."
|
|
|
|
|
|
load_config_file "$INSTALL_DIR/cameleer.conf"
|
|
|
|
|
|
load_env_overrides
|
|
|
|
|
|
merge_config
|
|
|
|
|
|
generate_compose_file
|
|
|
|
|
|
docker_compose_pull
|
|
|
|
|
|
docker_compose_down
|
|
|
|
|
|
docker_compose_up
|
|
|
|
|
|
verify_health
|
|
|
|
|
|
generate_install_doc
|
|
|
|
|
|
print_summary
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
reconfigure)
|
|
|
|
|
|
log_info "Reconfiguring installation..."
|
|
|
|
|
|
return
|
|
|
|
|
|
;;
|
|
|
|
|
|
reinstall)
|
|
|
|
|
|
if [ "${CONFIRM_DESTROY}" != "true" ]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
log_warn "This will destroy ALL data (databases, certificates, bootstrap)."
|
|
|
|
|
|
if ! prompt_yesno "Are you sure? This cannot be undone."; then
|
|
|
|
|
|
echo "Cancelled."
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
log_info "Reinstalling..."
|
|
|
|
|
|
docker_compose_down 2>/dev/null || true
|
|
|
|
|
|
(cd "$INSTALL_DIR" && docker compose -p "${COMPOSE_PROJECT:-cameleer-saas}" down -v 2>/dev/null || true)
|
|
|
|
|
|
rm -f "$INSTALL_DIR/.env" "$INSTALL_DIR/docker-compose.yml" \
|
|
|
|
|
|
"$INSTALL_DIR/cameleer.conf" "$INSTALL_DIR/credentials.txt" \
|
|
|
|
|
|
"$INSTALL_DIR/INSTALL.md" "$INSTALL_DIR/.env.bak"
|
|
|
|
|
|
rm -rf "$INSTALL_DIR/certs"
|
|
|
|
|
|
IS_RERUN=false
|
|
|
|
|
|
return
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
feat(installer): add main function and complete install.sh
Appends the main() entry point that wires together all installer phases:
arg parsing, config loading, rerun detection, prerequisites, auto-detect,
interactive prompts, config merge/validate, password generation, file
generation, docker pull/up, health verification, and output printing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:33:15 +02:00
|
|
|
|
|
|
|
|
|
|
# --- Main ---
|
|
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
|
parse_args "$@"
|
|
|
|
|
|
|
|
|
|
|
|
print_banner
|
|
|
|
|
|
|
|
|
|
|
|
# Load config sources (CLI already loaded via parse_args)
|
|
|
|
|
|
if [ -n "$CONFIG_FILE_PATH" ]; then
|
|
|
|
|
|
load_config_file "$CONFIG_FILE_PATH"
|
|
|
|
|
|
fi
|
|
|
|
|
|
load_env_overrides
|
|
|
|
|
|
|
|
|
|
|
|
# Check for existing installation
|
|
|
|
|
|
detect_existing_install
|
|
|
|
|
|
if [ "$IS_RERUN" = true ]; then
|
|
|
|
|
|
show_rerun_menu
|
|
|
|
|
|
handle_rerun
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Prerequisites
|
|
|
|
|
|
check_prerequisites
|
|
|
|
|
|
|
|
|
|
|
|
# Auto-detect defaults
|
|
|
|
|
|
auto_detect
|
|
|
|
|
|
|
|
|
|
|
|
# Interactive prompts (unless silent)
|
|
|
|
|
|
if [ "$MODE" != "silent" ]; then
|
|
|
|
|
|
select_mode
|
|
|
|
|
|
if [ "$MODE" = "expert" ]; then
|
|
|
|
|
|
run_expert_prompts
|
|
|
|
|
|
else
|
|
|
|
|
|
run_simple_prompts
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Merge remaining defaults and validate
|
|
|
|
|
|
merge_config
|
|
|
|
|
|
validate_config
|
|
|
|
|
|
|
|
|
|
|
|
# Generate passwords for any empty values
|
|
|
|
|
|
generate_passwords
|
|
|
|
|
|
|
|
|
|
|
|
# Create install directory
|
|
|
|
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
# Copy custom certs if provided
|
|
|
|
|
|
if [ "$TLS_MODE" = "custom" ]; then
|
|
|
|
|
|
copy_certs
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Generate configuration files
|
|
|
|
|
|
generate_env_file
|
|
|
|
|
|
generate_compose_file
|
|
|
|
|
|
write_config_file
|
|
|
|
|
|
|
|
|
|
|
|
# Pull and start
|
|
|
|
|
|
docker_compose_pull
|
|
|
|
|
|
docker_compose_up
|
|
|
|
|
|
|
|
|
|
|
|
# Verify health
|
|
|
|
|
|
verify_health
|
|
|
|
|
|
|
|
|
|
|
|
# Generate output files
|
|
|
|
|
|
generate_credentials_file
|
|
|
|
|
|
generate_install_doc
|
|
|
|
|
|
|
|
|
|
|
|
# Print results
|
|
|
|
|
|
print_credentials
|
|
|
|
|
|
print_summary
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main "$@"
|