#!/bin/bash
# Custom Helper Functions for Logging/Errors
WarningCount=0
ErrorCount=0
function msg() {
if [ "$DEBUG_MODE" = "true" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >&2
fi
echo " - $*"
}
# Function to print debug messages only if DEBUG_MODE is enabled
debug_msg() {
if [ "$DEBUG_MODE" = "true" ]; then
echo "DEBUG: $(date '+%Y-%m-%d %H:%M:%S') - $*" >&2
fi
}
function warnmsg() {
msg "Warning: ${1:-Unknown Warning}" >&2
WarningCount=$((WarningCount + 1))
}
function errmsg() {
msg "Error: ${1:-Unknown Error}" >&2
(( ErrorCount++ ))
}
function bail() {
errmsg "${1:-Unknown Error}"
exit "${2:-1}"
}
# run($cmd, $desc) - Executes a command with an optional description.
function run {
local cmd="$1"
local desc="${2:-}"
[[ -n "$desc" ]] && msg "$desc"
msg "Running: $cmd"
if [ "$DRY_RUN" = "true" ]; then
msg "[DRY RUN] (Not actually running '$cmd')"
return 0
fi
eval "$cmd"
}
# Function to detect instances from systemd service
detect_instance_from_service() {
local exec_line
local detected_instance
# Check if the service exists before attempting to extract the instance
if ! sudo systemctl cat command-runner.service --no-pager -l &>/dev/null; then
debug_msg "command-runner.service not found. Skipping instance detection from systemd." >&2
return 1
fi
# Extract the ExecStart line from systemd
exec_line=$(sudo systemctl cat command-runner.service --no-pager -l | grep ExecStart= | head -n 1 || true)
if [[ -n "$exec_line" ]]; then
debug_msg "Found command-runner service ExecStart: $exec_line" >&2
# Extract --instance=<value> from the ExecStart line (portable sed instead of grep -oP)
detected_instance=$(echo "$exec_line" | sed -n 's/.*--instance=\([^ ]*\).*/\1/p' | head -n 1)
if [[ -n "$detected_instance" ]]; then
echo "$detected_instance" # Output only the instance name
return 0
fi
fi
return 1
}
# Function to detect instances based on /p4/<instance>/root
detect_instances_sdp_structure() {
local instances=()
local instance_name root_dir resolved_root
debug_msg "Starting instance detection in $DEFAULT_BASE_DIR" >&2
if [ ! -d "$DEFAULT_BASE_DIR" ]; then
debug_msg "No /p4 directory found. Skipping instance detection." >&2
return 0
fi
for dir in "$DEFAULT_BASE_DIR"/*/; do
if [ ! -d "$dir" ]; then
debug_msg "No directories found in $DEFAULT_BASE_DIR" >&2
break
fi
instance_name="$(basename "$dir")"
# Skip known non-instance directories
if [[ "$instance_name" = "common" ]] || [[ "$instance_name" = "sdp" ]] || [[ "$instance_name" = "ssl" ]]; then
debug_msg "Skipping '$instance_name' directory." >&2
continue
fi
root_dir="$dir/root"
if [ -L "$root_dir" ]; then
resolved_root="$(readlink -f "$root_dir")"
if [ -d "$resolved_root" ]; then
debug_msg "Detected instance '$instance_name' with root at '$resolved_root'" >&2
instances+=("$instance_name")
else
warnmsg "'$root_dir' symlink -> '$resolved_root' is not a directory."
fi
elif [ -d "$root_dir" ]; then
debug_msg "Detected instance '$instance_name' with root at '$root_dir'" >&2
instances+=("$instance_name")
else
warnmsg "'root' not found for '$instance_name'"
fi
done
if [ "${#instances[@]}" -eq 0 ]; then
debug_msg "No instances found." >&2
else
debug_msg "Detected instances: ${instances[*]}" >&2
echo "${instances[@]}"
fi
}
verify_instance() {
if [ "$SDP_MODE" = "false" ]; then
msg "Using non-P4 defaults..."
return 0
fi
msg "Using P4 SDP instance detection..."
# If instance is already set, do nothing
if [ -n "${INSTANCE:-}" ]; then
return 0
fi
# Try detecting instances using SDP structure
mapfile -t instances < <(detect_instances_sdp_structure)
INSTANCE_COUNT=${#instances[@]}
if [ "$INSTANCE_COUNT" -eq 0 ]; then
msg "Error: No valid instances found in $DEFAULT_BASE_DIR." >&2
return 1
elif [ "$INSTANCE_COUNT" -eq 1 ]; then
INSTANCE="${instances[0]}"
msg "Detected single instance: $INSTANCE"
else
if [ "$FORCE_INSTALL" = "true" ]; then
msg "Error: Multiple instances found. Please specify one with -i <instance>." >&2
exit 1
fi
msg "Multiple valid instances found. Please select one:"
select selected_instance in "${instances[@]}" "Cancel"; do
if [[ "$selected_instance" == "Cancel" ]]; then
msg "Installation canceled."
exit 1
elif [[ " ${instances[*]} " == *" $selected_instance "* ]]; then
INSTANCE="$selected_instance"
break
else
msg "Invalid selection."
fi
done
fi
}
# Function to confirm installation
function confirm_proceed() {
if [ "$FORCE_INSTALL" = "true" ]; then
msg "Force install selected: skipping installation confirmation prompt."
return
fi
read -rp "Do you want to proceed with the installation? [y/N]: " confirmation
case "$confirmation" in
[Yy]|[Yy][Ee][Ss]) msg "Proceeding with installation..." ;;
*) msg "Installation aborted by user."; exit 0 ;;
esac
}
# Function to display summary and confirm
function prompt_summary_and_confirm() {
if [ "$DRY_RUN" = "false" ]; then
display_summary
confirm_proceed
else
msg "DRY RUN MODE: No changes will be applied."
display_summary
msg "Re-run with -y to apply changes."
fi
}
# Function to display binary version
function get_bin_version() {
if [[ -z "${NEW_BINARY:-}" ]]; then
msg "NEW_BINARY is not set; cannot determine new binary version."
NEW_VERSION=""
return 0
fi
if [[ ! -x "$NEW_BINARY" ]]; then
msg "New binary '$NEW_BINARY' is not executable or not found; skipping new version detection."
NEW_VERSION=""
return 0
fi
new_raw="$($NEW_BINARY --version 2>/dev/null | sed '/^[[:space:]]*$/d' | head -n1 || true)"
debug_msg "Raw version output from new binary: $new_raw"
if [[ -z "$new_raw" ]]; then
msg "No version info from '$NEW_BINARY'."
NEW_VERSION=""
else
parsed_new="$(echo "$new_raw" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+')"
if [[ -n "$parsed_new" ]]; then
NEW_VERSION="$parsed_new"
debug_msg "Parsed new version: $NEW_VERSION"
else
msg "Unable to parse semantic version from '$NEW_BINARY' output."
NEW_VERSION=""
fi
fi
}
# Function to check if a user exists
function user_exists() {
local user="$1"
[[ -z "$user" ]] && { errmsg "user_exists: no user specified"; return 1; }
if id "$user" &>/dev/null; then
debug_msg "User '$user' exists."
return 0
else
errmsg "User '$user' does not exist."
return 1
fi
}
# Function to check if a group exists
function group_exists() {
local group="$1"
[[ -z "$group" ]] && { errmsg "group_exists: no group specified"; return 1; }
if getent group "$group" &>/dev/null; then
debug_msg "Group '$group' exists."
return 0
else
errmsg "Group '$group' does not exist."
return 1
fi
}
# Function to check both user and group
function user_group_exists() {
local user="$1"
local group="$2"
user_exists "$user" || return 1
group_exists "$group" || return 1
msg "User '$user' and group '$group' both exist."
return 0
}
# Function to display the installation summary
function display_summary() {
cat <<EOF
====================== Installation Summary ======================
Instance: $INSTANCE
Binary Install Path: $INSTALL_PATH
Configuration Directory: $CONFIG_DIR
VMAgent Directory: ${VMAGENT_DIR:-/var/vmagent}
Log File: $LOG_FILE
Service Runs As: $SVC_USER:$SVC_GROUP
Timer Mode: $( [ "$TIMER_MODE" = "test" ] && echo "TEST (Runs every 5 minutes)" || echo "PRODUCTION (Runs daily at 23:00)" )
New Binary Version: $NEW_VERSION
Existing Version: ${CURRENT_VERSION:-None}
Force Install: $( [ "$FORCE_INSTALL" = "true" ] && echo "Yes" || echo "No" )
SELinux Mode: ${SELinuxMode:-Unknown}
===============================================================
EOF
}
# Function to display installation completion message
function installation_complete() {
msg "==================================================="
if [ "$DRY_RUN" = "true" ]; then
msg "DRY RUN COMPLETE (no changes made)."
msg "Re-run with -y to apply changes."
else
msg "INSTALLATION COMPLETE!"
fi
msg "==================================================="
msg "Owner: $OWNER"
msg "New binary version: $NEW_VERSION"
msg "Previously installed: ${CURRENT_VERSION:-None}"
msg "Installed to: $INSTALL_PATH"
msg "Config installed to: $CONFIG_DIR/cmd_config.yaml"
msg "Log file: $LOG_FILE"
msg "Instance: $INSTANCE"
msg "VMAgent Directory: ${VMAGENT_DIR:-/var/vmagent}"
msg "Old cron references: $OLD_SCRIPTNAME"
msg "Service runs as: $SVC_USER:$SVC_GROUP"
if [ "$TIMER_MODE" = "test" ]; then
msg "Timer Mode: TEST (Runs every 5 minutes)"
else
msg "Timer Mode: PRODUCTION (Runs daily at 23:00)"
fi
msg ""
msg "Systemd service: /etc/systemd/system/$SERVICE_NAME"
msg "Systemd timer: /etc/systemd/system/$TIMER_NAME"
if [ "$TIMER_MODE" = "test" ]; then
msg "Runs every 5 minutes (OnBootSec=5m, OnUnitActiveSec=5m)"
else
msg "Runs daily at 23:00 (OnCalendar=*-*-* 23:00:00)"
fi
msg "Check status: systemctl status $TIMER_NAME"
msg ""
msg "==================================================="
}
print_args_box() {
if [ "$DEBUG_MODE" = "true" ]; then
local ARGS="$*"
local ARG_LENGTH=${#ARGS}
local WIDTH=$((ARG_LENGTH + 10)) # Adjust width for spacing
# Create the top border
printf '=%.0s' $(seq 1 $WIDTH)
echo ""
# Create the empty line with side borders
printf "|%*s|\n" "$WIDTH" " "
# Print the ARGS line, centered
printf "| ARGS %s%*s |\n" "$ARGS" $((WIDTH - ARG_LENGTH - 6)) " "
# Create another empty line with side borders
printf "|%*s|\n" "$WIDTH" " "
# Create the bottom border
printf '=%.0s' $(seq 1 $WIDTH)
echo ""
echo "DEBUG: $(date '+%Y-%m-%d %H:%M:%S') - $*" >&2
fi
}
update_sdp_instance_yaml() {
local config_file="$CONFIG_DIR/cmd_config.yaml"
local backup_file="${config_file}.bak"
debug_msg "Updating SDP instance in: $config_file"
# Ensure the config file exists before proceeding
if [ ! -f "$config_file" ]; then
errmsg "Configuration file not found at $config_file"
return 1
fi
# Create a backup before modifying (portable approach)
debug_msg "Creating backup at: $backup_file"
if ! cp "$config_file" "$backup_file"; then
errmsg "Failed to create backup of $config_file"
return 1
fi
# Perform the replacement of the entire line for sdp_instance
debug_msg "Replacing 'sdp_instance' with: $INSTANCE"
if ! sed -i.tmp "s|^sdp_instance:.*|sdp_instance: \"$INSTANCE\"|g" "$config_file"; then
errmsg "Failed to update sdp_instance in $config_file"
cp "$backup_file" "$config_file" # Restore from backup
return 1
fi
rm -f "${config_file}.tmp" # Remove sed backup file
if [ "$SDP_MODE" = "false" ]; then
# More robust replacement of `p4: true` → `p4: false` inside `enable_commands`
debug_msg "Setting 'p4' to false in $config_file"
if ! awk '
/enable_commands:/ { inside_block=1 }
inside_block && /p4: true/ { sub(/p4: true/, "p4: false") }
inside_block && /^[^ ]/ { inside_block=0 } # Stop if we leave indentation level
{ print }
' "$config_file" > "${config_file}.tmp"; then
errmsg "Failed to modify p4 setting in $config_file"
cp "$backup_file" "$config_file" # Restore from backup
return 1
fi
mv "${config_file}.tmp" "$config_file"
else
debug_msg "SDP Mode is enabled. No additional modifications."
fi
# Verify replacements
if grep -q "sdp_instance: \"$INSTANCE\"" "$config_file" && \
( [ "$SDP_MODE" = "true" ] || grep -q " p4: false" "$config_file" ); then
debug_msg "SDP instance successfully updated to: $INSTANCE"
return 0
else
errmsg "Failed to update SDP instance or 'p4' flag in $config_file"
cp "$backup_file" "$config_file" # Restore from backup
return 1
fi
}