#!/bin/bash
# Configuration management library for ssh-manager CLI
# Configuration paths
export SSH_MANAGER_HOME="${SSH_MANAGER_HOME:-$HOME/.ssh-manager}"
export SSH_MANAGER_CONFIG="$SSH_MANAGER_HOME/config.json"
# Resolve .env path - handle both direct and symlinked execution
if [ -z "$SSH_MANAGER_ENV" ]; then
if [ -n "$SCRIPT_DIR" ]; then
# Called from main ssh-manager script
SSH_MANAGER_ENV="$(dirname "$SCRIPT_DIR")/.env"
else
# Called directly - need to resolve path ourselves
if [ -L "$0" ]; then
REAL_PATH="$(readlink -f "$0" 2>/dev/null || readlink "$0")"
else
REAL_PATH="$0"
fi
REAL_DIR="$(cd "$(dirname "$REAL_PATH")" && pwd)"
SSH_MANAGER_ENV="$(dirname "$(dirname "$REAL_DIR")")/.env"
fi
export SSH_MANAGER_ENV
fi
export SSH_MANAGER_ALIASES="$SSH_MANAGER_HOME/aliases.json"
# Ensure config directory exists
init_config() {
if [ ! -d "$SSH_MANAGER_HOME" ]; then
mkdir -p "$SSH_MANAGER_HOME"
print_info "Created config directory: $SSH_MANAGER_HOME"
fi
# Create default config if not exists
if [ ! -f "$SSH_MANAGER_CONFIG" ]; then
cat > "$SSH_MANAGER_CONFIG" <<EOF
{
"default_editor": "${EDITOR:-nano}",
"default_shell": "${SHELL:-/bin/bash}",
"history_file": "$SSH_MANAGER_HOME/history",
"log_level": "info",
"color_output": true
}
EOF
print_info "Created default config: $SSH_MANAGER_CONFIG"
fi
}
# Load configuration value
get_config() {
local key="$1"
local default="$2"
if [ -f "$SSH_MANAGER_CONFIG" ] && command -v jq >/dev/null 2>&1; then
local value=$(jq -r ".$key // null" "$SSH_MANAGER_CONFIG" 2>/dev/null)
if [ "$value" != "null" ]; then
echo "$value"
else
echo "$default"
fi
else
echo "$default"
fi
}
# Set configuration value
set_config() {
local key="$1"
local value="$2"
if command -v jq >/dev/null 2>&1; then
local temp=$(mktemp)
jq ".$key = \"$value\"" "$SSH_MANAGER_CONFIG" > "$temp" && mv "$temp" "$SSH_MANAGER_CONFIG"
print_success "Updated config: $key = $value"
else
print_error "jq is required for config management"
return 1
fi
}
# Load servers from .env file
load_servers() {
local servers=()
if [ ! -f "$SSH_MANAGER_ENV" ]; then
return 1
fi
# Parse .env file for server definitions
while IFS= read -r line; do
if [[ "$line" =~ ^SSH_SERVER_(.+)_HOST= ]]; then
local server_name="${BASH_REMATCH[1]}"
# Convert to lowercase
server_name=$(echo "$server_name" | tr '[:upper:]' '[:lower:]')
servers+=("$server_name")
fi
done < "$SSH_MANAGER_ENV"
# Remove duplicates
printf '%s\n' "${servers[@]}" | sort -u
}
# Get server configuration
get_server_config() {
local server="$1"
local field="$2"
local server_upper="$(echo "$server" | tr '[:lower:]' '[:upper:]')"
if [ ! -f "$SSH_MANAGER_ENV" ]; then
return 1
fi
local field_upper="$(echo "$field" | tr '[:lower:]' '[:upper:]')"
local key="SSH_SERVER_${server_upper}_${field_upper}"
# Read value using IFS to preserve all special characters including quotes
local value=""
while IFS="=" read -r k v; do
if [[ "$k" == "$key" ]]; then
value="$v"
break
fi
done < <(grep "^${key}=" "$SSH_MANAGER_ENV" 2>/dev/null)
# Return error if no value found
if [ -z "$value" ]; then
return 1
fi
# Only remove surrounding quotes if they exist, preserve quotes within the value
if [[ "$value" =~ ^\"(.*)\"$ ]]; then
printf '%s' "${BASH_REMATCH[1]}"
else
printf '%s' "$value"
fi
}
# Add server to .env
add_server_to_env() {
local name="$1"
local host="$2"
local user="$3"
local auth_type="$4"
local auth_value="$5"
local port="${6:-22}"
local description="${7:-}"
local name_upper="$(echo "$name" | tr '[:lower:]' '[:upper:]')"
# Check if server already exists
if grep -q "^SSH_SERVER_${name_upper}_HOST=" "$SSH_MANAGER_ENV" 2>/dev/null; then
print_error "Server '$name' already exists"
return 1
fi
# Backup .env file
cp "$SSH_MANAGER_ENV" "$SSH_MANAGER_ENV.bak"
# Add server configuration
{
echo ""
echo "# Server: $name"
echo "SSH_SERVER_${name_upper}_HOST=$host"
echo "SSH_SERVER_${name_upper}_USER=$user"
echo "SSH_SERVER_${name_upper}_PORT=$port"
if [ "$auth_type" = "password" ]; then
echo "SSH_SERVER_${name_upper}_PASSWORD=$auth_value"
else
echo "SSH_SERVER_${name_upper}_KEYPATH=$auth_value"
fi
if [ -n "$description" ]; then
echo "SSH_SERVER_${name_upper}_DESCRIPTION=\"$description\""
fi
} >> "$SSH_MANAGER_ENV"
print_success "Server '$name' added successfully"
}
# Update server in .env
update_server_in_env() {
local name="$1"
local host="$2"
local user="$3"
local auth_type="$4"
local auth_value="$5"
local port="${6:-22}"
local description="${7:-}"
local default_dir="${8:-}"
local name_upper="$(echo "$name" | tr '[:lower:]' '[:upper:]')"
# Check if server exists
if ! grep -q "^SSH_SERVER_${name_upper}_HOST=" "$SSH_MANAGER_ENV" 2>/dev/null; then
print_error "Server '$name' not found"
return 1
fi
# Backup .env file
cp "$SSH_MANAGER_ENV" "$SSH_MANAGER_ENV.bak"
# Remove old server configuration (all lines including comments and old auth methods)
local temp=$(mktemp)
# Remove server config lines and the comment line before them
sed "/^# Server: $name$/d; /^SSH_SERVER_${name_upper}_/d" "$SSH_MANAGER_ENV" > "$temp"
# Find position to insert (after other servers or at end)
# Add updated server configuration
{
cat "$temp"
echo ""
echo "# Server: $name"
echo "SSH_SERVER_${name_upper}_HOST=$host"
echo "SSH_SERVER_${name_upper}_USER=$user"
echo "SSH_SERVER_${name_upper}_PORT=$port"
if [ "$auth_type" = "password" ]; then
echo "SSH_SERVER_${name_upper}_PASSWORD=$auth_value"
else
echo "SSH_SERVER_${name_upper}_KEYPATH=$auth_value"
fi
if [ -n "$description" ]; then
echo "SSH_SERVER_${name_upper}_DESCRIPTION=\"$description\""
fi
if [ -n "$default_dir" ]; then
echo "SSH_SERVER_${name_upper}_DEFAULT_DIR=$default_dir"
fi
} > "$SSH_MANAGER_ENV"
rm -f "$temp"
print_success "Server '$name' updated successfully"
}
# Remove server from .env
remove_server_from_env() {
local name="$1"
local name_upper="$(echo "$name" | tr '[:lower:]' '[:upper:]')"
if ! grep -q "^SSH_SERVER_${name_upper}_HOST=" "$SSH_MANAGER_ENV" 2>/dev/null; then
print_error "Server '$name' not found"
return 1
fi
# Backup .env file
cp "$SSH_MANAGER_ENV" "$SSH_MANAGER_ENV.bak"
# Remove all lines for this server
local temp=$(mktemp)
grep -v "^SSH_SERVER_${name_upper}_" "$SSH_MANAGER_ENV" > "$temp"
mv "$temp" "$SSH_MANAGER_ENV"
print_success "Server '$name' removed successfully"
}
# Test SSH connection
test_ssh_connection() {
local server="$1"
local host=$(get_server_config "$server" "HOST")
local user=$(get_server_config "$server" "USER")
local port=$(get_server_config "$server" "PORT")
local keypath=$(get_server_config "$server" "KEYPATH")
local password=$(get_server_config "$server" "PASSWORD")
port=${port:-22}
if [ -z "$host" ] || [ -z "$user" ]; then
print_error "Server '$server' not found or incomplete configuration"
return 1
fi
print_info "Testing connection to $server ($user@$host:$port)..."
local ssh_opts="-o ConnectTimeout=10 -o StrictHostKeyChecking=no"
local ssh_output=$(mktemp)
local ssh_status
if [ -n "$keypath" ]; then
ssh_opts="$ssh_opts -i $keypath"
fi
if [ -n "$password" ]; then
# Use sshpass if available
if command -v sshpass >/dev/null 2>&1; then
sshpass -p "$password" ssh $ssh_opts -p "$port" "$user@$host" "echo 'Connection successful'" > "$ssh_output" 2>&1
ssh_status=$?
else
print_warning "sshpass not installed, cannot test password authentication"
rm -f "$ssh_output"
return 1
fi
else
ssh $ssh_opts -p "$port" "$user@$host" "echo 'Connection successful'" > "$ssh_output" 2>&1
ssh_status=$?
fi
if [ $ssh_status -eq 0 ]; then
print_success "Connection successful"
rm -f "$ssh_output"
return 0
else
print_error "Connection failed"
# Show error details if SSH_MANAGER_DEBUG is set
if [ -n "$SSH_MANAGER_DEBUG" ]; then
print_warning "Debug output:"
cat "$ssh_output" | sed 's/^/ /'
else
print_info "Set SSH_MANAGER_DEBUG=1 to see detailed error output"
fi
rm -f "$ssh_output"
return 1
fi
}
# Validate server name
validate_server_name() {
local name="$1"
# Check if name is empty
if [ -z "$name" ]; then
print_error "Server name cannot be empty"
return 1
fi
# Check for invalid characters
if ! [[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
print_error "Server name can only contain letters, numbers, underscore and hyphen"
return 1
fi
# Check if name starts with a letter
if ! [[ "$name" =~ ^[a-zA-Z] ]]; then
print_error "Server name must start with a letter"
return 1
fi
return 0
}
# Check dependencies
check_dependencies() {
local missing=()
# Check for required commands
for cmd in ssh rsync; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
# Check for optional but recommended commands
local optional=()
for cmd in jq sshpass; do
if ! command -v "$cmd" >/dev/null 2>&1; then
optional+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
print_error "Missing required dependencies: ${missing[*]}"
print_info "Please install them and try again"
return 1
fi
if [ ${#optional[@]} -gt 0 ]; then
print_warning "Missing optional dependencies: ${optional[*]}"
print_info "Some features may not work without them"
fi
return 0
}