#!/usr/bin/env bash # AMMIDS Shared Functions Library # Location: init/scripts/functions.sh # Sourced by all other scripts in this directory # Prevent direct execution if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then echo "This is a library, not a script. Source it instead." exit 1 fi # --- Color Codes --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' TITLE='\033[38;2;17;97;2m' NC='\033[0m' # --- Path Resolution --- # Standardised for project structure: /init/scripts/xxx.sh # Sets: SCRIPT_DIR, INIT_DIR, ROOT_DIR # --- Path Resolution --- resolve_paths() { SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)" # Detect if we're in init/scripts/ or at project root if [[ "$SCRIPT_DIR" == */init/scripts ]]; then # Called from init/scripts/ (e.g., set_env.sh, config_gen.sh) INIT_DIR="$(dirname "$SCRIPT_DIR")" ROOT_DIR="$(dirname "$INIT_DIR")" else # Called from project root (e.g., setup.sh) ROOT_DIR="$SCRIPT_DIR" INIT_DIR="${ROOT_DIR}/init" fi ENV_FILE="${ROOT_DIR}/.env" COMPOSE_FILE="${ROOT_DIR}/compose.yaml" export SCRIPT_DIR INIT_DIR ROOT_DIR ENV_FILE COMPOSE_FILE } # --- Logging --- log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # --- State Management (init.ini) --- write_state() { local key="$1" local value="$2" local init_file="${INIT_DIR}/init.ini" local upper_value upper_key=$(echo "$key" | tr '[:lower:]' '[:upper:]') upper_value=$(echo "$value" | tr '[:lower:]' '[:upper:]') echo "${upper_key}=${upper_value}" >> "$init_file" } load_state() { local init_file="${INIT_DIR}/init.ini" if [[ -f "$init_file" ]]; then source "$init_file" else log_warn "State file not found at ${init_file}. Using defaults." fi } toggle_block() { local target_file="$1" local start_marker="$2" local end_marker="$3" local keep_markers="$4" # "true" or "false" if [[ -z "$target_file" ]]; then echo "Error: Output file path is required for toggle_block." >&2 return 1 fi if [[ ! -f "$target_file" ]]; then echo "Error: Target file '$target_file' not found." >&2 return 1 fi if [[ "$keep_markers" == "true" ]]; then # Remove lines containing markers individually sed -i "/${start_marker}/d; /${end_marker}/d" "$target_file" else # Remove block from start to end sed -i "/${start_marker},/${end_marker}/d" "$target_file" fi } clear_state() { local init_file="${INIT_DIR}/init.ini" > "$init_file" } # --- Secrets --- generate_secret() { local length="${1:-32}" if command -v openssl &> /dev/null; then openssl rand -hex "$((length / 2))" elif command -v pwgen &> /dev/null; then pwgen -s "$length" 1 else tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$length" fi } # --- Interactive Prompts --- prompt_var() { local var_name="$1" local prompt_text="$2" local default_val="$3" local is_password="$4" local base_domain="${5:-}" local input local final_value echo -n "$prompt_text [Default: $default_val]: " read -r input if [[ -z "$input" ]]; then if [[ "$is_password" == "yes" ]]; then input=$(generate_secret 32) else input="$default_val" fi fi # Append base domain if provided if [[ -n "$base_domain" ]]; then final_value="${input}.${base_domain}" else final_value="$input" fi final_value="${final_value//\\/\\\\}" final_value="${final_value//\"/\\\"}" final_value="${final_value//\$/\\\$}" final_value="${final_value//\`/\\\`}" echo "${var_name}=\"${final_value}\"" >> "$ENV_FILE" } auto_var() { local name="$1" local default="$2" local is_pass="$3" if [[ "$is_pass" == "yes" ]]; then local secret secret=$(generate_secret 32) echo "${name}=\"${secret}\"" >> "$ENV_FILE" log_info " Generated ${name}" else echo "${name}=\"${default}\"" >> "$ENV_FILE" log_info " Set ${name} to default: ${default}" fi } # --- File Permissions --- fix_ownership() { local target="$1" local uid="${2:-${USER_ID:-1000}}" local gid="${3:-${GROUP_ID:-1000}}" if [[ -e "$target" ]] && [[ "$(stat -c '%U' "$target" 2>/dev/null)" != "$USER" ]]; then log_warn "Fixing ownership on $target..." sudo chown -R "${uid}:${gid}" "$target" fi } # --- Dependency Checks --- require_command() { local cmd="$1" if ! command -v "$cmd" &> /dev/null; then log_error "Required command '${cmd}' not found. Install it and retry." exit 1 fi } require_docker() { require_command "docker" if ! docker compose version &> /dev/null; then log_error "Docker Compose plugin not found. Install it and retry." exit 1 fi } require_file() { local file="$1" local description="${2:-File}" if [[ ! -f "$file" ]]; then log_error "${description} not found at ${file}" exit 1 fi } require_env() { local file="${ENV_FILE:-${ROOT_DIR}/.env}" if [[ ! -f "$file" ]]; then log_error ".env file not found at ${file}. Run set_env.sh first." exit 1 fi # Source the file source "$file" # Verify critical variables exist if [[ -z "${DOMAIN:-}" ]]; then log_error "DOMAIN variable missing in .env. Re-run set_env.sh." exit 1 fi } # --- Volume Path Resolution --- resolve_volume_path() { VOLUME_PATH="${VOLUME_PATH:-./matrix}" if [[ "$VOLUME_PATH" != /* ]]; then VOLUME_PATH="${ROOT_DIR}/${VOLUME_PATH}" fi export VOLUME_PATH } # Add to functions.sh quiet_echo() { [[ "${QUIET_MODE:-0}" == "1" ]] || echo "$1" }