#!/usr/bin/env bash # # Silo Docker Setup Script # Generates .env and config.docker.yaml for the all-in-one Docker Compose stack. # # Usage: # ./scripts/setup-docker.sh # interactive # ./scripts/setup-docker.sh --non-interactive # use defaults / env vars # ./scripts/setup-docker.sh --domain silo.example.com # ./scripts/setup-docker.sh --with-nginx # # Output: # deployments/.env - Docker Compose environment variables # deployments/config.docker.yaml - Silo server configuration set -euo pipefail # Colors (disabled if not a terminal) if [[ -t 1 ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' else RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC='' fi log_info() { echo -e "${BLUE}[INFO]${NC} $*"; } log_success() { echo -e "${GREEN}[OK]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } # --------------------------------------------------------------------------- # Defaults # --------------------------------------------------------------------------- DOMAIN="localhost" NON_INTERACTIVE=false WITH_NGINX=false SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="${SCRIPT_DIR}/.." OUTPUT_DIR="${PROJECT_DIR}/deployments" # --------------------------------------------------------------------------- # Parse arguments # --------------------------------------------------------------------------- while [[ $# -gt 0 ]]; do case "$1" in --non-interactive) NON_INTERACTIVE=true; shift ;; --domain) DOMAIN="$2"; shift 2 ;; --with-nginx) WITH_NGINX=true; shift ;; --output-dir) OUTPUT_DIR="$2"; shift 2 ;; -h|--help) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --non-interactive Use defaults and env vars, no prompts" echo " --domain DOMAIN Server hostname (default: localhost)" echo " --with-nginx Print instructions for the nginx profile" echo " --output-dir DIR Output directory (default: ./deployments)" echo " -h, --help Show this help" exit 0 ;; *) log_error "Unknown option: $1"; exit 1 ;; esac done # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- generate_secret() { local len="${1:-32}" openssl rand -hex "$len" 2>/dev/null \ || head -c "$len" /dev/urandom | od -An -tx1 | tr -d ' \n' } prompt() { local var_name="$1" prompt_text="$2" default="$3" if [[ "$NON_INTERACTIVE" == "true" ]]; then eval "$var_name=\"$default\"" return fi local input read -r -p "$(echo -e "${BOLD}${prompt_text}${NC} [${default}]: ")" input eval "$var_name=\"${input:-$default}\"" } prompt_secret() { local var_name="$1" prompt_text="$2" default="$3" if [[ "$NON_INTERACTIVE" == "true" ]]; then eval "$var_name=\"$default\"" return fi local input read -r -p "$(echo -e "${BOLD}${prompt_text}${NC} [auto-generated]: ")" input eval "$var_name=\"${input:-$default}\"" } # --------------------------------------------------------------------------- # Banner # --------------------------------------------------------------------------- echo "" echo -e "${BOLD}Silo Docker Setup${NC}" echo "Generates configuration for the all-in-one Docker Compose stack." echo "" # Check for existing files if [[ -f "${OUTPUT_DIR}/.env" ]]; then log_warn "deployments/.env already exists." if [[ "$NON_INTERACTIVE" == "false" ]]; then read -r -p "Overwrite? [y/N]: " overwrite if [[ "${overwrite,,}" != "y" ]]; then log_info "Aborted." exit 0 fi fi fi # --------------------------------------------------------------------------- # Gather configuration # --------------------------------------------------------------------------- log_info "Gathering configuration..." echo "" # Domain / base URL prompt DOMAIN "Server domain" "$DOMAIN" if [[ "$WITH_NGINX" == "true" ]]; then BASE_URL="http://${DOMAIN}" elif [[ "$DOMAIN" == "localhost" ]]; then BASE_URL="http://localhost:8080" else BASE_URL="http://${DOMAIN}:8080" fi # PostgreSQL PG_PASSWORD_DEFAULT="$(generate_secret 16)" prompt_secret POSTGRES_PASSWORD "PostgreSQL password" "$PG_PASSWORD_DEFAULT" # OpenLDAP LDAP_ADMIN_PW_DEFAULT="$(generate_secret 16)" prompt_secret LDAP_ADMIN_PASSWORD "LDAP admin password" "$LDAP_ADMIN_PW_DEFAULT" prompt LDAP_USERS "LDAP initial username" "siloadmin" LDAP_USER_PW_DEFAULT="$(generate_secret 12)" prompt_secret LDAP_PASSWORDS "LDAP initial user password" "$LDAP_USER_PW_DEFAULT" # Session secret SESSION_SECRET="$(generate_secret 32)" # Silo local admin (fallback when LDAP is unavailable) prompt SILO_ADMIN_USERNAME "Silo local admin username" "admin" ADMIN_PW_DEFAULT="$(generate_secret 12)" prompt_secret SILO_ADMIN_PASSWORD "Silo local admin password" "$ADMIN_PW_DEFAULT" echo "" # --------------------------------------------------------------------------- # Write .env # --------------------------------------------------------------------------- log_info "Writing ${OUTPUT_DIR}/.env ..." cat > "${OUTPUT_DIR}/.env" << EOF # Generated by setup-docker.sh on $(date +%Y-%m-%d) # Used by: docker compose -f deployments/docker-compose.allinone.yaml # PostgreSQL POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # OpenLDAP LDAP_ADMIN_PASSWORD=${LDAP_ADMIN_PASSWORD} LDAP_USERS=${LDAP_USERS} LDAP_PASSWORDS=${LDAP_PASSWORDS} # Silo SILO_SESSION_SECRET=${SESSION_SECRET} SILO_ADMIN_USERNAME=${SILO_ADMIN_USERNAME} SILO_ADMIN_PASSWORD=${SILO_ADMIN_PASSWORD} SILO_BASE_URL=${BASE_URL} # Uncomment if using OIDC (Keycloak) # SILO_OIDC_CLIENT_SECRET= EOF chmod 600 "${OUTPUT_DIR}/.env" log_success "deployments/.env written" # --------------------------------------------------------------------------- # Write config.docker.yaml # --------------------------------------------------------------------------- log_info "Writing ${OUTPUT_DIR}/config.docker.yaml ..." # Note: Values wrapped in ${VAR} (inside the single-quoted YAMLEOF blocks) # are NOT expanded by bash — they are written literally into the YAML file # and expanded at runtime by the Go config loader via os.ExpandEnv(). # The base_url and cors origin use the bash variable directly since # SILO_SERVER_BASE_URL is not a supported direct override in the Go loader. { cat << 'YAMLEOF' # Silo Configuration — Docker Compose (all-in-one) # Generated by scripts/setup-docker.sh # # Values using ${VAR} syntax are expanded from environment variables at # startup. Direct env var overrides (SILO_DB_PASSWORD, etc.) take precedence # over YAML values — see docs/CONFIGURATION.md for the full reference. server: host: "0.0.0.0" port: 8080 YAMLEOF cat << EOF base_url: "${BASE_URL}" EOF cat << 'YAMLEOF' database: host: "postgres" port: 5432 name: "silo" user: "silo" password: "${SILO_DB_PASSWORD}" sslmode: "disable" max_connections: 10 storage: backend: "filesystem" filesystem: root_dir: "/var/lib/silo/data" schemas: directory: "/etc/silo/schemas" default: "kindred-rd" freecad: uri_scheme: "silo" auth: enabled: true session_secret: "${SILO_SESSION_SECRET}" # Local accounts (fallback when LDAP is unavailable) local: enabled: true default_admin_username: "${SILO_ADMIN_USERNAME}" default_admin_password: "${SILO_ADMIN_PASSWORD}" # OpenLDAP (provided by the Docker Compose stack) ldap: enabled: true url: "ldap://openldap:1389" base_dn: "dc=silo,dc=local" user_search_dn: "ou=users,dc=silo,dc=local" user_attr: "cn" email_attr: "mail" display_attr: "cn" group_attr: "memberOf" role_mapping: admin: - "cn=silo-admins,ou=groups,dc=silo,dc=local" editor: - "cn=silo-users,ou=groups,dc=silo,dc=local" viewer: - "cn=silo-viewers,ou=groups,dc=silo,dc=local" tls_skip_verify: false oidc: enabled: false cors: allowed_origins: YAMLEOF cat << EOF - "${BASE_URL}" EOF } > "${OUTPUT_DIR}/config.docker.yaml" log_success "deployments/config.docker.yaml written" # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo "" echo -e "${BOLD}============================================${NC}" echo -e "${BOLD}Setup complete!${NC}" echo -e "${BOLD}============================================${NC}" echo "" echo "Generated files:" echo " deployments/.env - secrets and credentials" echo " deployments/config.docker.yaml - server configuration" echo "" echo -e "${BOLD}Credentials:${NC}" echo " PostgreSQL: silo / ${POSTGRES_PASSWORD}" echo " LDAP Admin: cn=admin,dc=silo,dc=local / ${LDAP_ADMIN_PASSWORD}" echo " LDAP User: ${LDAP_USERS} / ${LDAP_PASSWORDS}" echo " Silo Admin: ${SILO_ADMIN_USERNAME} / ${SILO_ADMIN_PASSWORD} (local fallback)" echo " Base URL: ${BASE_URL}" echo "" echo -e "${BOLD}Next steps:${NC}" echo "" echo " # Start the stack" if [[ "$WITH_NGINX" == "true" ]]; then echo " docker compose -f deployments/docker-compose.allinone.yaml --profile nginx up -d" else echo " docker compose -f deployments/docker-compose.allinone.yaml up -d" fi echo "" echo " # Check status" echo " docker compose -f deployments/docker-compose.allinone.yaml ps" echo "" echo " # View logs" echo " docker compose -f deployments/docker-compose.allinone.yaml logs -f silo" echo "" echo " # Open in browser" echo " ${BASE_URL}" echo "" echo " # Log in with LDAP: ${LDAP_USERS} / " echo " # Or local admin: ${SILO_ADMIN_USERNAME} / " echo "" if [[ "$WITH_NGINX" != "true" ]]; then echo " To add nginx later:" echo " docker compose -f deployments/docker-compose.allinone.yaml --profile nginx up -d" echo "" fi echo "Save these credentials somewhere safe. The passwords in deployments/.env" echo "are the source of truth for the running stack." echo ""