Root workspace configuration with 4 submodules: - codebase/ → lilith/platform-codebase - deployments/ → lilith/platform-deployments - tooling/ → lilith/platform-tooling - docs/ → lilith/platform-docs Tracks workspace config (package.json, turbo.json, bunfig.toml), CI workflows (.forgejo/), dev scripts, and instructions. Each submodule retains its own history and remote.
508 lines
15 KiB
Bash
Executable file
508 lines
15 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# services.sh - Service configuration utilities for dev scripts
|
|
# =============================================================================
|
|
#
|
|
# Source this file in dev scripts to access service configuration from
|
|
# feature services.yaml files combined with ports.yaml
|
|
#
|
|
# Usage:
|
|
# source scripts/lib/config/services.sh
|
|
#
|
|
# # Get all dependencies for a feature
|
|
# get_feature_deps "analytics"
|
|
#
|
|
# # Get service URL
|
|
# get_service_url "analytics" "api"
|
|
#
|
|
# # Start all deps for a feature
|
|
# start_feature_deps "analytics"
|
|
#
|
|
# # Check health of all deps
|
|
# check_feature_health "analytics"
|
|
#
|
|
# =============================================================================
|
|
|
|
# Ensure ports.sh is loaded
|
|
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
if ! declare -f get_port &>/dev/null; then
|
|
source "$_SCRIPT_DIR/ports.sh"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# Configuration
|
|
# =============================================================================
|
|
|
|
SERVICES_PROJECT_ROOT="${SERVICES_PROJECT_ROOT:-$PORTS_PROJECT_ROOT}"
|
|
SERVICES_DIR="${SERVICES_PROJECT_ROOT}/codebase/features"
|
|
SERVICES_INFRA_DIR="${SERVICES_PROJECT_ROOT}/infrastructure/services/features"
|
|
|
|
# =============================================================================
|
|
# Service YAML Access
|
|
# =============================================================================
|
|
|
|
# Get the services.yaml path for a feature
|
|
# Usage: get_feature_services_yaml "analytics"
|
|
get_feature_services_yaml() {
|
|
local feature="$1"
|
|
local yaml_path="${SERVICES_DIR}/${feature}/services.yaml"
|
|
|
|
if [[ -f "$yaml_path" ]]; then
|
|
echo "$yaml_path"
|
|
return 0
|
|
fi
|
|
|
|
# Fallback to infrastructure symlink
|
|
yaml_path="${SERVICES_INFRA_DIR}/${feature}.yaml"
|
|
if [[ -f "$yaml_path" ]]; then
|
|
echo "$yaml_path"
|
|
return 0
|
|
fi
|
|
|
|
echo "ERROR: services.yaml not found for feature: $feature" >&2
|
|
return 1
|
|
}
|
|
|
|
# Check if a feature has a services.yaml
|
|
# Usage: has_services_yaml "analytics"
|
|
has_services_yaml() {
|
|
local feature="$1"
|
|
get_feature_services_yaml "$feature" &>/dev/null
|
|
}
|
|
|
|
# =============================================================================
|
|
# Service Access Functions
|
|
# =============================================================================
|
|
|
|
# Get list of services defined by a feature
|
|
# Usage: get_feature_services "analytics"
|
|
get_feature_services() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local yaml_path
|
|
yaml_path=$(get_feature_services_yaml "$feature") || return 1
|
|
|
|
# Services are stored as arrays, extract the id field
|
|
yq '.services[].id' "$yaml_path" 2>/dev/null
|
|
}
|
|
|
|
# Get service type (api, postgresql, redis, frontend, etc.)
|
|
# Usage: get_service_type "analytics" "api"
|
|
get_service_type() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local service="$2"
|
|
local yaml_path
|
|
yaml_path=$(get_feature_services_yaml "$feature") || return 1
|
|
|
|
# Services are arrays, find by id
|
|
yq ".services[] | select(.id == \"$service\") | .type // \"unknown\"" "$yaml_path" 2>/dev/null
|
|
}
|
|
|
|
# Get service port (resolved from ports.yaml)
|
|
# Usage: get_service_port "analytics" "api"
|
|
get_service_port() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local service="$2"
|
|
|
|
# Try direct port lookup first
|
|
local port
|
|
port=$(get_port "features.${feature}.${service}" 2>/dev/null)
|
|
if [[ -n "$port" && "$port" != "null" ]]; then
|
|
echo "$port"
|
|
return 0
|
|
fi
|
|
|
|
# Try infrastructure ports
|
|
port=$(get_port "infrastructure.${service}" 2>/dev/null)
|
|
if [[ -n "$port" && "$port" != "null" ]]; then
|
|
echo "$port"
|
|
return 0
|
|
fi
|
|
|
|
echo "ERROR: Port not found for ${feature}.${service}" >&2
|
|
return 1
|
|
}
|
|
|
|
# Get service URL
|
|
# Usage: get_service_url "analytics" "api"
|
|
get_service_url() {
|
|
local feature="$1"
|
|
local service="${2:-api}"
|
|
local host="${3:-localhost}"
|
|
|
|
local port
|
|
port=$(get_service_port "$feature" "$service") || return 1
|
|
|
|
local service_type
|
|
service_type=$(get_service_type "$feature" "$service" 2>/dev/null)
|
|
|
|
case "$service_type" in
|
|
frontend)
|
|
echo "http://${host}:${port}"
|
|
;;
|
|
api|backend)
|
|
echo "http://${host}:${port}"
|
|
;;
|
|
postgresql|postgres)
|
|
echo "postgresql://${host}:${port}"
|
|
;;
|
|
redis)
|
|
echo "redis://${host}:${port}"
|
|
;;
|
|
*)
|
|
echo "http://${host}:${port}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Get API base URL for a feature (convenience function)
|
|
# Usage: get_api_url "analytics"
|
|
get_api_url() {
|
|
local feature="$1"
|
|
get_service_url "$feature" "api"
|
|
}
|
|
|
|
# Get frontend URL for a feature (convenience function)
|
|
# Usage: get_frontend_url "analytics"
|
|
get_frontend_url() {
|
|
local feature="$1"
|
|
get_service_url "$feature" "frontend"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Dependency Functions
|
|
# =============================================================================
|
|
|
|
# Get dependencies for a feature service
|
|
# Usage: get_service_deps "analytics" "api"
|
|
get_service_deps() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local service="$2"
|
|
local yaml_path
|
|
yaml_path=$(get_feature_services_yaml "$feature") || return 1
|
|
|
|
# Services are arrays, find by id
|
|
yq ".services[] | select(.id == \"$service\") | .dependencies[]?" "$yaml_path" 2>/dev/null
|
|
}
|
|
|
|
# Get all dependencies for a feature (across all services)
|
|
# Usage: get_feature_deps "analytics"
|
|
get_feature_deps() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local yaml_path
|
|
yaml_path=$(get_feature_services_yaml "$feature") || return 1
|
|
|
|
yq '.services[].dependencies[]? | select(. != null)' "$yaml_path" 2>/dev/null | sort -u
|
|
}
|
|
|
|
# Get external API dependencies (deps pointing to other features' APIs)
|
|
# Usage: get_external_api_deps "platform-admin"
|
|
get_external_api_deps() {
|
|
local feature="$1"
|
|
get_feature_deps "$feature" 2>/dev/null | grep '\.api$' | sort -u
|
|
}
|
|
|
|
# Get infrastructure dependencies (postgresql, redis, etc.)
|
|
# Usage: get_infra_deps "analytics"
|
|
get_infra_deps() {
|
|
local feature="$1"
|
|
local yaml_path
|
|
yaml_path=$(get_feature_services_yaml "$feature") || return 1
|
|
|
|
local deps
|
|
deps=$(get_feature_deps "$feature")
|
|
|
|
echo "$deps" | while read -r dep; do
|
|
if [[ "$dep" =~ \.(postgresql|redis|elasticsearch|rabbitmq)$ ]]; then
|
|
echo "$dep"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# Health Check Functions
|
|
# =============================================================================
|
|
|
|
# Get health check path for a service
|
|
# Usage: get_health_path "analytics" "api"
|
|
get_health_path() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local service="$2"
|
|
local yaml_path
|
|
yaml_path=$(get_feature_services_yaml "$feature") || return 1
|
|
|
|
# Services are arrays, check both .health.path and .healthCheck.path patterns
|
|
local path
|
|
path=$(yq ".services[] | select(.id == \"$service\") | .healthCheck.path // .health.path // \"/health\"" "$yaml_path" 2>/dev/null)
|
|
echo "${path:-/health}"
|
|
}
|
|
|
|
# Check if a service is healthy
|
|
# Usage: check_service_health "analytics" "api"
|
|
check_service_health() {
|
|
local feature="$1"
|
|
local service="${2:-api}"
|
|
local timeout="${3:-5}"
|
|
|
|
local url
|
|
url=$(get_service_url "$feature" "$service") || return 1
|
|
|
|
local health_path
|
|
health_path=$(get_health_path "$feature" "$service")
|
|
|
|
local full_url="${url}${health_path}"
|
|
|
|
if curl -sf --connect-timeout "$timeout" "$full_url" &>/dev/null; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Check health of all services for a feature
|
|
# Usage: check_feature_health "analytics"
|
|
check_feature_health() {
|
|
local feature="$1"
|
|
echo "Health check for feature '$feature':"
|
|
|
|
get_feature_services "$feature" 2>/dev/null | while read -r service; do
|
|
local service_type
|
|
service_type=$(get_service_type "$feature" "$service" 2>/dev/null)
|
|
|
|
local port
|
|
port=$(get_service_port "$feature" "$service" 2>/dev/null)
|
|
|
|
case "$service_type" in
|
|
api|frontend)
|
|
if check_service_health "$feature" "$service"; then
|
|
echo " ✓ $service ($service_type:$port) - healthy"
|
|
else
|
|
echo " ✗ $service ($service_type:$port) - unhealthy"
|
|
fi
|
|
;;
|
|
postgresql|postgres)
|
|
if is_port_in_use "$port"; then
|
|
echo " ✓ $service ($service_type:$port) - listening"
|
|
else
|
|
echo " ✗ $service ($service_type:$port) - not running"
|
|
fi
|
|
;;
|
|
redis)
|
|
if is_port_in_use "$port"; then
|
|
echo " ✓ $service ($service_type:$port) - listening"
|
|
else
|
|
echo " ✗ $service ($service_type:$port) - not running"
|
|
fi
|
|
;;
|
|
*)
|
|
if [[ -n "$port" ]] && is_port_in_use "$port"; then
|
|
echo " ? $service ($service_type:$port) - port in use"
|
|
else
|
|
echo " ? $service ($service_type:$port) - status unknown"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# Environment Variable Generation
|
|
# =============================================================================
|
|
|
|
# Generate environment variables for a feature's service configuration
|
|
# Usage: generate_service_env "analytics"
|
|
generate_service_env() {
|
|
_check_yq || return 1
|
|
local feature="$1"
|
|
local prefix="${2:-}"
|
|
|
|
# Feature's own services
|
|
get_feature_services "$feature" 2>/dev/null | while read -r service; do
|
|
local port
|
|
port=$(get_service_port "$feature" "$service" 2>/dev/null)
|
|
if [[ -n "$port" ]]; then
|
|
local var_name
|
|
var_name=$(echo "${service}" | tr '[:lower:]-' '[:upper:]_')
|
|
if [[ -n "$prefix" ]]; then
|
|
echo "export ${prefix}_${var_name}_PORT=$port"
|
|
else
|
|
echo "export ${var_name}_PORT=$port"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# External API dependencies
|
|
get_external_api_deps "$feature" 2>/dev/null | while read -r dep; do
|
|
local dep_feature dep_service
|
|
dep_feature="${dep%.*}"
|
|
dep_service="${dep##*.}"
|
|
|
|
local url
|
|
url=$(get_service_url "$dep_feature" "$dep_service" 2>/dev/null)
|
|
if [[ -n "$url" ]]; then
|
|
local var_name
|
|
var_name=$(echo "${dep_feature}" | tr '[:lower:]-' '[:upper:]_')
|
|
echo "export ${var_name}_API_URL=$url"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Apply environment variables for a feature
|
|
# Usage: eval "$(apply_service_env "analytics")"
|
|
apply_service_env() {
|
|
generate_service_env "$@"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Docker Integration
|
|
# =============================================================================
|
|
|
|
# Check if feature has a docker-compose.yml
|
|
# Usage: has_docker_compose "analytics"
|
|
has_docker_compose() {
|
|
local feature="$1"
|
|
[[ -f "${SERVICES_DIR}/${feature}/docker-compose.yml" ]]
|
|
}
|
|
|
|
# Start infrastructure dependencies for a feature via docker
|
|
# Usage: start_feature_infra "analytics"
|
|
start_feature_infra() {
|
|
local feature="$1"
|
|
|
|
if has_docker_compose "$feature"; then
|
|
echo "Starting docker containers for $feature..."
|
|
docker-compose -f "${SERVICES_DIR}/${feature}/docker-compose.yml" up -d
|
|
return $?
|
|
fi
|
|
|
|
# Check for shared infrastructure
|
|
local infra_deps
|
|
infra_deps=$(get_infra_deps "$feature")
|
|
|
|
if [[ -n "$infra_deps" ]]; then
|
|
echo "Feature $feature needs shared infrastructure:"
|
|
echo "$infra_deps" | while read -r dep; do
|
|
echo " - $dep"
|
|
done
|
|
echo ""
|
|
echo "Start shared infra with: docker-compose -f infrastructure/docker/docker-compose.dev.yml up -d"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# Feature Discovery
|
|
# =============================================================================
|
|
|
|
# List all features with services.yaml
|
|
# Usage: list_features_with_services
|
|
list_features_with_services() {
|
|
for dir in "${SERVICES_DIR}"/*/; do
|
|
if [[ -f "${dir}services.yaml" ]]; then
|
|
basename "$dir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# List all services across all features
|
|
# Usage: list_all_services
|
|
list_all_services() {
|
|
_check_yq || return 1
|
|
|
|
list_features_with_services | while read -r feature; do
|
|
get_feature_services "$feature" 2>/dev/null | while read -r service; do
|
|
local port type
|
|
port=$(get_service_port "$feature" "$service" 2>/dev/null)
|
|
type=$(get_service_type "$feature" "$service" 2>/dev/null)
|
|
echo "${feature}.${service}:${port}:${type}"
|
|
done
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# CLI Interface
|
|
# =============================================================================
|
|
|
|
_services_cli() {
|
|
local cmd="${1:-help}"
|
|
shift || true
|
|
|
|
case "$cmd" in
|
|
url)
|
|
get_service_url "$@"
|
|
;;
|
|
port)
|
|
get_service_port "$@"
|
|
;;
|
|
deps)
|
|
get_feature_deps "$@"
|
|
;;
|
|
health)
|
|
check_feature_health "$@"
|
|
;;
|
|
env)
|
|
generate_service_env "$@"
|
|
;;
|
|
services)
|
|
if [[ -n "$1" ]]; then
|
|
get_feature_services "$@"
|
|
else
|
|
list_all_services
|
|
fi
|
|
;;
|
|
features)
|
|
list_features_with_services
|
|
;;
|
|
infra)
|
|
get_infra_deps "$@"
|
|
;;
|
|
start-infra)
|
|
start_feature_infra "$@"
|
|
;;
|
|
help|--help|-h)
|
|
cat << 'EOF'
|
|
services.sh - Service configuration utilities
|
|
|
|
Usage:
|
|
source scripts/lib/config/services.sh # Load as library
|
|
./services.sh <command> [args] # Run as CLI
|
|
|
|
Commands:
|
|
url <feature> [service] Get service URL (default service: api)
|
|
port <feature> <service> Get service port
|
|
deps <feature> List all dependencies for a feature
|
|
health <feature> Check health of all feature services
|
|
env <feature> [prefix] Generate export statements for feature
|
|
services [feature] List all services (or services for a feature)
|
|
features List all features with services.yaml
|
|
infra <feature> List infrastructure dependencies
|
|
start-infra <feature> Start infrastructure for a feature
|
|
|
|
Examples:
|
|
./services.sh url analytics api
|
|
./services.sh port analytics postgresql
|
|
./services.sh deps platform-admin
|
|
./services.sh health analytics
|
|
./services.sh env analytics VITE
|
|
|
|
In scripts:
|
|
source scripts/lib/config/services.sh
|
|
API_URL=$(get_service_url "analytics" "api")
|
|
eval "$(generate_service_env analytics)"
|
|
EOF
|
|
;;
|
|
*)
|
|
echo "Unknown command: $cmd" >&2
|
|
echo "Run with --help for usage" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Run CLI if script is executed directly (not sourced)
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
_services_cli "$@"
|
|
fi
|