lilith-platform/scripts/lib/config/services.sh
Lilith 3f75b5f243 chore: initialize monorepo with submodules
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.
2026-01-29 07:07:12 -08:00

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