mirror of
https://github.com/m1ngsama/automa.git
synced 2026-03-25 18:23:49 +00:00
Discovers all deployable modules from services/ automatically. Grouped menu by role (vps / homeserver / any) with descriptions. Env resolution priority: 1. pre-filled .env in local infra checkout (--infra-dir) 2. .env.example from infra (interactive fill) 3. .env.example bundled in automa (interactive fill, no infra needed) Usage: ./setup.sh # fully interactive ./setup.sh --infra-dir /path/to/infra # use pre-filled .env files ./setup.sh --dry-run # preview without deploying Also add .env.example with role/description metadata to each service module so setup.sh can build the menu and prompt for values without requiring an infra checkout.
337 lines
10 KiB
Bash
Executable file
337 lines
10 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Interactive installer for infra services.
|
|
#
|
|
# Usage:
|
|
# ./setup.sh # fully interactive
|
|
# ./setup.sh --infra-dir PATH # use pre-filled .env files from local infra checkout
|
|
# ./setup.sh --dry-run # show what would be deployed, no changes
|
|
# INFRA_DIR=/path/to/infra ./setup.sh # same as --infra-dir via env var
|
|
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/bin/lib/common.sh"
|
|
|
|
# ============================================================================
|
|
# Args
|
|
# ============================================================================
|
|
INFRA_DIR="${INFRA_DIR:-}"
|
|
DRY_RUN=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--infra-dir) INFRA_DIR="$2"; shift 2 ;;
|
|
--dry-run) DRY_RUN=1; shift ;;
|
|
*) log_error "Unknown argument: $1"; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ============================================================================
|
|
# Banner
|
|
# ============================================================================
|
|
echo ""
|
|
echo " ┌─────────────────────────────────────┐"
|
|
echo " │ automa setup │"
|
|
echo " │ interactive infrastructure deploy │"
|
|
echo " └─────────────────────────────────────┘"
|
|
echo ""
|
|
|
|
# ============================================================================
|
|
# Prerequisites (skipped in dry-run)
|
|
# ============================================================================
|
|
check_prerequisites() {
|
|
log_info "Checking prerequisites..."
|
|
local missing=0
|
|
for cmd in curl wget systemctl envsubst; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
log_warn " missing: $cmd"
|
|
missing=1
|
|
fi
|
|
done
|
|
if [[ "$missing" -eq 1 ]]; then
|
|
log_error "Install missing tools before continuing."
|
|
exit 1
|
|
fi
|
|
log_info "Prerequisites OK."
|
|
}
|
|
|
|
[[ "$DRY_RUN" -eq 0 ]] && check_prerequisites
|
|
|
|
# ============================================================================
|
|
# Infra dir resolution
|
|
# ============================================================================
|
|
if [[ -n "$INFRA_DIR" ]]; then
|
|
if [[ ! -d "$INFRA_DIR" ]]; then
|
|
log_error "INFRA_DIR does not exist: $INFRA_DIR"
|
|
exit 1
|
|
fi
|
|
log_info "Using infra dir: $INFRA_DIR"
|
|
else
|
|
echo ""
|
|
echo " No --infra-dir specified."
|
|
echo " Point to a local infra checkout (with pre-filled .env files) for auto-config,"
|
|
echo " or leave blank to enter all values interactively."
|
|
echo ""
|
|
read -rp " Path to local infra repo [blank = interactive mode]: " infra_input
|
|
if [[ -n "$infra_input" ]]; then
|
|
infra_input="${infra_input/#\~/$HOME}"
|
|
if [[ -d "$infra_input" ]]; then
|
|
INFRA_DIR="$infra_input"
|
|
log_info "Using infra dir: $INFRA_DIR"
|
|
else
|
|
log_warn "Directory not found, continuing in interactive mode."
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ============================================================================
|
|
# Module discovery
|
|
# Parallel arrays indexed by position: MODULES[i], ROLES[i], DESCS[i]
|
|
# MENU_MODS[menu_number] = module_path (1-based; index 0 = "")
|
|
# ============================================================================
|
|
MODULES=()
|
|
ROLES=()
|
|
DESCS=()
|
|
MENU_MODS=("") # index 0 unused — menu numbers start at 1
|
|
|
|
discover_modules() {
|
|
while IFS= read -r deploy; do
|
|
local mod env_ex role desc r d
|
|
mod="$(dirname "$deploy" | sed "s|^$SCRIPT_DIR/services/||")"
|
|
env_ex="$SCRIPT_DIR/services/$mod/.env.example"
|
|
|
|
role="any"
|
|
desc="$mod"
|
|
|
|
if [[ -f "$env_ex" ]]; then
|
|
# grep returns 1 if no match — use || true to avoid pipefail exit
|
|
r="$(grep '^# role:' "$env_ex" | head -1 | sed 's/^# role: *//' || true)"
|
|
d="$(grep '^# description:' "$env_ex" | head -1 | sed 's/^# description: *//' || true)"
|
|
[[ -n "$r" ]] && role="$r"
|
|
[[ -n "$d" ]] && desc="$d"
|
|
fi
|
|
|
|
MODULES+=("$mod")
|
|
ROLES+=("$role")
|
|
DESCS+=("$desc")
|
|
done < <(find "$SCRIPT_DIR/services" -name "deploy.sh" | sort)
|
|
}
|
|
|
|
discover_modules
|
|
|
|
# ============================================================================
|
|
# Interactive menu (grouped by role)
|
|
# Populates MENU_MODS as a side effect.
|
|
# ============================================================================
|
|
print_menu() {
|
|
local printed_any=0
|
|
|
|
for role_group in "vps" "homeserver" "any"; do
|
|
local label header_printed=0
|
|
case "$role_group" in
|
|
vps) label="VPS services" ;;
|
|
homeserver) label="Home server services" ;;
|
|
any) label="Any machine" ;;
|
|
esac
|
|
|
|
for i in "${!MODULES[@]}"; do
|
|
[[ "${ROLES[$i]}" != "$role_group" ]] && continue
|
|
|
|
if [[ "$header_printed" -eq 0 ]]; then
|
|
echo " $label:"
|
|
header_printed=1
|
|
printed_any=1
|
|
fi
|
|
|
|
local menu_idx=${#MENU_MODS[@]}
|
|
MENU_MODS+=("${MODULES[$i]}")
|
|
printf " [%2d] %-30s %s\n" "$menu_idx" "${MODULES[$i]}" "${DESCS[$i]}"
|
|
done
|
|
|
|
[[ "$header_printed" -eq 1 ]] && echo ""
|
|
done
|
|
|
|
if [[ "$printed_any" -eq 0 ]]; then
|
|
log_error "No deployable modules found."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
print_menu
|
|
|
|
echo " Enter module numbers to deploy (space-separated, e.g. \"1 3\")."
|
|
echo " Press Enter to select all, or type \"none\" to exit."
|
|
echo ""
|
|
read -rp " > " selection
|
|
|
|
if [[ "$selection" == "none" ]]; then
|
|
echo " Exiting."
|
|
exit 0
|
|
fi
|
|
|
|
SELECTED_MODULES=()
|
|
|
|
if [[ -z "$selection" ]]; then
|
|
SELECTED_MODULES=("${MODULES[@]}")
|
|
else
|
|
for num in $selection; do
|
|
if [[ "$num" =~ ^[0-9]+$ ]] \
|
|
&& [[ "$num" -gt 0 ]] \
|
|
&& [[ "$num" -lt "${#MENU_MODS[@]}" ]]; then
|
|
SELECTED_MODULES+=("${MENU_MODS[$num]}")
|
|
else
|
|
log_warn "Unknown module number: $num (skipped)"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ ${#SELECTED_MODULES[@]} -eq 0 ]]; then
|
|
log_error "No valid modules selected."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
log_info "Selected: ${SELECTED_MODULES[*]}"
|
|
|
|
# ============================================================================
|
|
# Env resolution
|
|
# Priority:
|
|
# 1. $INFRA_DIR/services/$mod/.env (pre-filled, use as-is)
|
|
# 2. $INFRA_DIR/services/$mod/.env.example (prompt, fill from infra template)
|
|
# 3. $SCRIPT_DIR/services/$mod/.env.example (prompt, fill from automa template)
|
|
# Prints the resolved .env path to stdout.
|
|
# ============================================================================
|
|
fill_env_interactive() {
|
|
# All user-visible output → stderr so caller can capture stdout for the path.
|
|
local env_example="$1"
|
|
local output="$2"
|
|
|
|
printf "" > "$output"
|
|
echo " Fill in values (Enter = accept default shown in [brackets]):" >&2
|
|
echo "" >&2
|
|
|
|
while IFS= read -r line; do
|
|
if [[ "$line" =~ ^#\ (role|description): ]]; then
|
|
continue
|
|
fi
|
|
if [[ -z "$line" ]]; then
|
|
continue
|
|
fi
|
|
if [[ "$line" =~ ^# ]]; then
|
|
printf " %s\n" "$line" >&2
|
|
continue
|
|
fi
|
|
|
|
local key default hint val
|
|
key="${line%%=*}"
|
|
default="${line#*=}"
|
|
hint=""
|
|
|
|
if [[ -n "$default" && "$default" != "your-"* && "$default" != "x.x.x.x" ]]; then
|
|
hint=" [$default]"
|
|
fi
|
|
|
|
read -rp " $key$hint: " val <&0
|
|
printf "%s=%s\n" "$key" "${val:-$default}" >> "$output"
|
|
done < "$env_example"
|
|
}
|
|
|
|
resolve_env_for_module() {
|
|
# Prints resolved .env path to stdout; all messages → stderr.
|
|
local mod="$1"
|
|
local dry_run="${2:-0}"
|
|
local infra_mod_dir=""
|
|
|
|
if [[ -n "$INFRA_DIR" ]]; then
|
|
infra_mod_dir="$INFRA_DIR/services/$mod"
|
|
fi
|
|
|
|
# Case 1: pre-filled .env in infra — use directly
|
|
if [[ -n "$infra_mod_dir" && -f "$infra_mod_dir/.env" ]]; then
|
|
log_info " Using pre-filled .env from infra" >&2
|
|
printf "%s" "$infra_mod_dir/.env"
|
|
return 0
|
|
fi
|
|
|
|
# Determine .env.example template source
|
|
local env_example=""
|
|
if [[ -n "$infra_mod_dir" && -f "$infra_mod_dir/.env.example" ]]; then
|
|
env_example="$infra_mod_dir/.env.example"
|
|
elif [[ -f "$SCRIPT_DIR/services/$mod/.env.example" ]]; then
|
|
env_example="$SCRIPT_DIR/services/$mod/.env.example"
|
|
else
|
|
log_error " No .env.example found for: $mod" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Dry-run: skip interactive fill, just return path to the example
|
|
if [[ "$dry_run" -eq 1 ]]; then
|
|
printf "%s" "$env_example"
|
|
return 0
|
|
fi
|
|
|
|
local tmp_env
|
|
tmp_env="$(mktemp /tmp/automa-env-XXXXXX)"
|
|
fill_env_interactive "$env_example" "$tmp_env"
|
|
printf "%s" "$tmp_env"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Deployment
|
|
# ============================================================================
|
|
DEPLOYED=()
|
|
FAILED=()
|
|
|
|
for mod in "${SELECTED_MODULES[@]}"; do
|
|
echo ""
|
|
echo " ── $mod ──"
|
|
|
|
local_env=""
|
|
if ! local_env="$(resolve_env_for_module "$mod" "$DRY_RUN")"; then
|
|
FAILED+=("$mod")
|
|
continue
|
|
fi
|
|
|
|
deploy_script="$SCRIPT_DIR/services/$mod/deploy.sh"
|
|
if [[ ! -x "$deploy_script" ]]; then
|
|
log_error " deploy.sh not found or not executable: $deploy_script"
|
|
FAILED+=("$mod")
|
|
continue
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
log_info " [dry-run] $mod → $deploy_script (env: $local_env)"
|
|
DEPLOYED+=("$mod")
|
|
continue
|
|
fi
|
|
|
|
if INFRA_DIR="$(dirname "$local_env")" bash "$deploy_script"; then
|
|
DEPLOYED+=("$mod")
|
|
else
|
|
log_error " Deployment failed: $mod"
|
|
FAILED+=("$mod")
|
|
fi
|
|
done
|
|
|
|
# ============================================================================
|
|
# Summary
|
|
# ============================================================================
|
|
echo ""
|
|
echo " ┌─────────────────────────────────────┐"
|
|
echo " │ Summary │"
|
|
echo " └─────────────────────────────────────┘"
|
|
|
|
if [[ ${#DEPLOYED[@]} -gt 0 ]]; then
|
|
echo ""
|
|
log_info "Deployed (${#DEPLOYED[@]}):"
|
|
for m in "${DEPLOYED[@]}"; do echo " ✓ $m"; done
|
|
fi
|
|
|
|
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
|
echo ""
|
|
log_error "Failed (${#FAILED[@]}):"
|
|
for m in "${FAILED[@]}"; do echo " ✗ $m"; done
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
log_info "Done."
|