#!/bin/bash # PatchMon Agent Script v1.2.6 # This script sends package update information to the PatchMon server using API credentials # Configuration PATCHMON_SERVER="${PATCHMON_SERVER:-http://localhost:3001}" API_VERSION="v1" AGENT_VERSION="1.2.6" CONFIG_FILE="/etc/patchmon/agent.conf" CREDENTIALS_FILE="/etc/patchmon/credentials" LOG_FILE="/var/log/patchmon-agent.log" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging function log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } # Error handling error() { echo -e "${RED}ERROR: $1${NC}" >&2 log "ERROR: $1" exit 1 } # Info logging info() { echo -e "${BLUE}INFO: $1${NC}" log "INFO: $1" } # Success logging success() { echo -e "${GREEN}SUCCESS: $1${NC}" log "SUCCESS: $1" } # Warning logging warning() { echo -e "${YELLOW}WARNING: $1${NC}" log "WARNING: $1" } # Check if running as root check_root() { if [[ $EUID -ne 0 ]]; then error "This script must be run as root" fi } # Create necessary directories setup_directories() { mkdir -p /etc/patchmon mkdir -p /var/log touch "$LOG_FILE" chmod 600 "$LOG_FILE" } # Load configuration load_config() { if [[ -f "$CONFIG_FILE" ]]; then source "$CONFIG_FILE" fi } # Load API credentials load_credentials() { if [[ ! -f "$CREDENTIALS_FILE" ]]; then error "Credentials file not found at $CREDENTIALS_FILE. Please configure API credentials first." fi source "$CREDENTIALS_FILE" if [[ -z "$API_ID" ]] || [[ -z "$API_KEY" ]]; then error "API_ID and API_KEY must be configured in $CREDENTIALS_FILE" fi # Use PATCHMON_URL from credentials if available, otherwise use default if [[ -n "$PATCHMON_URL" ]]; then PATCHMON_SERVER="$PATCHMON_URL" fi } # Configure API credentials configure_credentials() { info "Setting up API credentials..." if [[ -z "$1" ]] || [[ -z "$2" ]]; then echo "Usage: $0 configure [SERVER_URL]" echo "" echo "Example:" echo " $0 configure patchmon_1a2b3c4d abcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890" echo " $0 configure patchmon_1a2b3c4d abcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890 http://patchmon.example.com" echo "" echo "Contact your PatchMon administrator to get your API credentials." exit 1 fi local api_id="$1" local api_key="$2" local server_url="${3:-$PATCHMON_SERVER}" # Validate API ID format if [[ ! "$api_id" =~ ^patchmon_[a-f0-9]{16}$ ]]; then error "Invalid API ID format. API ID should be in format: patchmon_xxxxxxxxxxxxxxxx" fi # Validate API Key format (64 hex characters) if [[ ! "$api_key" =~ ^[a-f0-9]{64}$ ]]; then error "Invalid API Key format. API Key should be 64 hexadecimal characters." fi # Validate server URL format if [[ ! "$server_url" =~ ^https?:// ]]; then error "Invalid server URL format. Must start with http:// or https://" fi # Create credentials file cat > "$CREDENTIALS_FILE" << EOF # PatchMon API Credentials # Generated on $(date) PATCHMON_URL="$server_url" API_ID="$api_id" API_KEY="$api_key" EOF chmod 600 "$CREDENTIALS_FILE" success "API credentials configured successfully" info "Credentials saved to: $CREDENTIALS_FILE" # Test credentials info "Testing API credentials..." test_credentials } # Test API credentials test_credentials() { load_credentials local response=$(curl -s -X POST \ -H "Content-Type: application/json" \ -H "X-API-ID: $API_ID" \ -H "X-API-KEY: $API_KEY" \ "$PATCHMON_SERVER/api/$API_VERSION/hosts/ping") if [[ $? -eq 0 ]] && echo "$response" | grep -q "success"; then success "API credentials are valid" local hostname=$(echo "$response" | grep -o '"hostname":"[^"]*' | cut -d'"' -f4) if [[ -n "$hostname" ]]; then info "Connected as host: $hostname" fi else error "API credentials test failed: $response" fi } # Detect OS and version detect_os() { if [[ -f /etc/os-release ]]; then source /etc/os-release OS_TYPE=$(echo "$ID" | tr '[:upper:]' '[:lower:]') OS_VERSION="$VERSION_ID" # Map OS variations to their appropriate categories case "$OS_TYPE" in "pop"|"linuxmint"|"elementary") OS_TYPE="ubuntu" ;; "opensuse"|"opensuse-leap"|"opensuse-tumbleweed") OS_TYPE="suse" ;; "rocky"|"almalinux") OS_TYPE="rhel" ;; esac elif [[ -f /etc/redhat-release ]]; then if grep -q "CentOS" /etc/redhat-release; then OS_TYPE="centos" elif grep -q "Red Hat" /etc/redhat-release; then OS_TYPE="rhel" fi OS_VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | head -1) else error "Unable to detect OS version" fi ARCHITECTURE=$(uname -m) HOSTNAME=$(hostname) IP_ADDRESS=$(hostname -I | awk '{print $1}') } # Get repository information based on OS get_repository_info() { local repos_json="[" local first=true case "$OS_TYPE" in "ubuntu"|"debian") get_apt_repositories repos_json first ;; "centos"|"rhel"|"fedora") get_yum_repositories repos_json first ;; *) # Return empty array for unsupported OS ;; esac repos_json+="]" echo "$repos_json" } # Get repository info for APT-based systems get_apt_repositories() { local -n repos_ref=$1 local -n first_ref=$2 # Parse traditional .list files local sources_files="/etc/apt/sources.list" if [[ -d "/etc/apt/sources.list.d" ]]; then sources_files="$sources_files $(find /etc/apt/sources.list.d -name '*.list' 2>/dev/null)" fi for file in $sources_files; do if [[ -f "$file" ]]; then while IFS= read -r line; do # Skip comments and empty lines if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then continue fi # Parse repository line (deb or deb-src) if [[ "$line" =~ ^[[:space:]]*(deb|deb-src)[[:space:]]+ ]]; then # Clean the line and extract components local clean_line=$(echo "$line" | xargs) local repo_type=$(echo "$clean_line" | awk '{print $1}') # Handle modern APT format with options like [signed-by=...] local url="" local distribution="" local components="" if [[ "$clean_line" =~ \[.*\] ]]; then # Modern format: deb [options] URL distribution components # Extract URL (first field after the options) url=$(echo "$clean_line" | sed 's/deb[^[:space:]]* \[[^]]*\] //' | awk '{print $1}') distribution=$(echo "$clean_line" | sed 's/deb[^[:space:]]* \[[^]]*\] //' | awk '{print $2}') components=$(echo "$clean_line" | sed 's/deb[^[:space:]]* \[[^]]*\] [^[:space:]]* [^[:space:]]* //') else # Traditional format: deb URL distribution components url=$(echo "$clean_line" | awk '{print $2}') distribution=$(echo "$clean_line" | awk '{print $3}') components=$(echo "$clean_line" | cut -d' ' -f4- | xargs) fi # Skip if URL doesn't look like a valid URL if [[ ! "$url" =~ ^https?:// ]] && [[ ! "$url" =~ ^ftp:// ]]; then continue fi # Skip if distribution is empty or looks malformed if [[ -z "$distribution" ]] || [[ "$distribution" =~ \[.*\] ]]; then continue fi # Determine if repository uses HTTPS local is_secure=false if [[ "$url" =~ ^https:// ]]; then is_secure=true fi # Generate repository name from URL and distribution local repo_name="$distribution" # Extract meaningful name from URL for better identification if [[ "$url" =~ archive\.ubuntu\.com ]]; then repo_name="ubuntu-$distribution" elif [[ "$url" =~ security\.ubuntu\.com ]]; then repo_name="ubuntu-$distribution-security" elif [[ "$url" =~ deb\.nodesource\.com ]]; then repo_name="nodesource-$distribution" elif [[ "$url" =~ packagecloud\.io ]]; then repo_name="packagecloud-$(echo "$url" | cut -d'/' -f4-5 | tr '/' '-')" elif [[ "$url" =~ ppa\.launchpad ]]; then repo_name="ppa-$(echo "$url" | cut -d'/' -f4-5 | tr '/' '-')" elif [[ "$url" =~ packages\.microsoft\.com ]]; then repo_name="microsoft-$(echo "$url" | cut -d'/' -f4-)" elif [[ "$url" =~ download\.docker\.com ]]; then repo_name="docker-$distribution" else # Fallback: use domain name + distribution local domain=$(echo "$url" | cut -d'/' -f3 | cut -d':' -f1) repo_name="$domain-$distribution" fi # Add component suffix if relevant if [[ "$components" =~ updates ]]; then repo_name="$repo_name-updates" elif [[ "$components" =~ security ]]; then repo_name="$repo_name-security" elif [[ "$components" =~ backports ]]; then repo_name="$repo_name-backports" fi if [[ "$first_ref" == true ]]; then first_ref=false else repos_ref+="," fi repos_ref+="{\"name\":\"$repo_name\",\"url\":\"$url\",\"distribution\":\"$distribution\",\"components\":\"$components\",\"repoType\":\"$repo_type\",\"isEnabled\":true,\"isSecure\":$is_secure}" fi done < "$file" fi done # Parse modern DEB822 format (.sources files) if [[ -d "/etc/apt/sources.list.d" ]]; then local sources_files_deb822=$(find /etc/apt/sources.list.d -name '*.sources' 2>/dev/null) for file in $sources_files_deb822; do if [[ -f "$file" ]]; then local deb822_result=$(parse_deb822_sources_simple "$file") if [[ -n "$deb822_result" ]]; then if [[ "$first_ref" == true ]]; then first_ref=false repos_ref+="$deb822_result" else repos_ref+=",$deb822_result" fi fi fi done fi } # Simple DEB822 parser that returns JSON string parse_deb822_sources_simple() { local file=$1 local result="" local enabled="" local types="" local uris="" local suites="" local components="" local name="" local first_entry=true while IFS= read -r line; do # Skip empty lines and comments if [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*# ]]; then continue fi # Parse key-value pairs if [[ "$line" =~ ^([^:]+):[[:space:]]*(.*)$ ]]; then local key="${BASH_REMATCH[1]}" local value="${BASH_REMATCH[2]}" case "$key" in "Enabled") enabled="$value" ;; "Types") types="$value" ;; "URIs") uris="$value" ;; "Suites") suites="$value" ;; "Components") components="$value" ;; "X-Repolib-Name") name="$value" ;; esac fi # Process repository entry when we hit a blank line if [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*$ ]]; then if [[ -n "$uris" && -n "$suites" && "$enabled" == "yes" ]]; then local entry_result=$(process_deb822_entry_simple "$name" "$types" "$uris" "$suites" "$components") if [[ -n "$entry_result" ]]; then if [[ "$first_entry" == true ]]; then first_entry=false result="$entry_result" else result="$result,$entry_result" fi fi fi # Reset variables for next entry enabled="" types="" uris="" suites="" components="" name="" fi done < "$file" # Process the last entry if file doesn't end with blank line if [[ -n "$uris" && -n "$suites" && "$enabled" == "yes" ]]; then local entry_result=$(process_deb822_entry_simple "$name" "$types" "$uris" "$suites" "$components") if [[ -n "$entry_result" ]]; then if [[ "$first_entry" == true ]]; then result="$entry_result" else result="$result,$entry_result" fi fi fi echo "$result" } # Process a DEB822 repository entry and return JSON process_deb822_entry_simple() { local name=$1 local types=$2 local uris=$3 local suites=$4 local components=$5 local result="" local first_entry=true # Handle multiple URIs for uri in $uris; do # Skip if URI doesn't look like a valid URL if [[ ! "$uri" =~ ^https?:// ]] && [[ ! "$uri" =~ ^ftp:// ]]; then continue fi # Handle multiple suites for suite in $suites; do # Skip if suite looks malformed if [[ -z "$suite" ]]; then continue fi # Determine if repository uses HTTPS local is_secure=false if [[ "$uri" =~ ^https:// ]]; then is_secure=true fi # Generate repository name local repo_name="" if [[ -n "$name" ]]; then repo_name=$(echo "$name" | tr ' ' '-' | tr '[:upper:]' '[:lower:]') else repo_name="$suite" fi # Extract meaningful name from URI for better identification if [[ "$uri" =~ apt\.pop-os\.org/ubuntu ]]; then repo_name="pop-os-ubuntu-$suite" elif [[ "$uri" =~ apt\.pop-os\.org/release ]]; then repo_name="pop-os-release-$suite" elif [[ "$uri" =~ apt\.pop-os\.org/proprietary ]]; then repo_name="pop-os-apps-$suite" elif [[ "$uri" =~ archive\.ubuntu\.com ]]; then repo_name="ubuntu-$suite" elif [[ "$uri" =~ security\.ubuntu\.com ]]; then repo_name="ubuntu-$suite-security" else # Fallback: use domain name + suite local domain=$(echo "$uri" | cut -d'/' -f3 | cut -d':' -f1) repo_name="$domain-$suite" fi # Add component suffix if relevant and not already included if [[ "$suite" != *"security"* && "$components" =~ security ]]; then repo_name="$repo_name-security" elif [[ "$suite" != *"updates"* && "$components" =~ updates ]]; then repo_name="$repo_name-updates" elif [[ "$suite" != *"backports"* && "$components" =~ backports ]]; then repo_name="$repo_name-backports" fi # Determine repo type (prefer deb over deb-src) local repo_type="deb" if [[ "$types" =~ deb-src ]] && [[ ! "$types" =~ ^deb[[:space:]] ]]; then repo_type="deb-src" fi local json_entry="{\"name\":\"$repo_name\",\"url\":\"$uri\",\"distribution\":\"$suite\",\"components\":\"$components\",\"repoType\":\"$repo_type\",\"isEnabled\":true,\"isSecure\":$is_secure}" if [[ "$first_entry" == true ]]; then first_entry=false result="$json_entry" else result="$result,$json_entry" fi done done echo "$result" } # Get repository info for YUM-based systems get_yum_repositories() { local -n repos_ref=$1 local -n first_ref=$2 # Parse yum/dnf repository configuration if command -v dnf >/dev/null 2>&1; then local repo_info=$(dnf repolist all --verbose 2>/dev/null | grep -E "^Repo-id|^Repo-baseurl|^Repo-name|^Repo-status") elif command -v yum >/dev/null 2>&1; then local repo_info=$(yum repolist all -v 2>/dev/null | grep -E "^Repo-id|^Repo-baseurl|^Repo-name|^Repo-status") fi # This is a simplified implementation - would need more work for full YUM support # For now, return empty for non-APT systems } # Get package information based on OS get_package_info() { local packages_json="[" local first=true case "$OS_TYPE" in "ubuntu"|"debian") get_apt_packages packages_json first ;; "centos"|"rhel"|"fedora") get_yum_packages packages_json first ;; *) error "Unsupported OS type: $OS_TYPE" ;; esac packages_json+="]" echo "$packages_json" } # Get package info for APT-based systems get_apt_packages() { local -n packages_ref=$1 local -n first_ref=$2 # Update package lists (use apt-get for older distros; quieter output) apt-get update -qq # Determine upgradable packages using apt-get simulation (compatible with Ubuntu 18.04) # Example line format: # Inst bash [4.4.18-2ubuntu1] (4.4.18-2ubuntu1.2 Ubuntu:18.04/bionic-updates [amd64]) local upgradable_sim=$(apt-get -s -o Debug::NoLocking=1 upgrade 2>/dev/null | grep "^Inst ") while IFS= read -r line; do # Extract package name, current version (in brackets), and available version (first token inside parentheses) if [[ "$line" =~ ^Inst[[:space:]]+([^[:space:]]+)[[:space:]]+\[([^\]]+)\][[:space:]]+\(([^[:space:]]+) ]]; then local package_name="${BASH_REMATCH[1]}" local current_version="${BASH_REMATCH[2]}" local available_version="${BASH_REMATCH[3]}" local is_security_update=false # Mark as security update if the line references a security pocket if echo "$line" | grep -qiE "(-|/)security"; then is_security_update=true fi if [[ "$first_ref" == true ]]; then first_ref=false else packages_ref+="," fi packages_ref+="{\"name\":\"$package_name\",\"currentVersion\":\"$current_version\",\"availableVersion\":\"$available_version\",\"needsUpdate\":true,\"isSecurityUpdate\":$is_security_update}" fi done <<< "$upgradable_sim" # Get installed packages that are up to date local installed=$(dpkg-query -W -f='${Package} ${Version}\n' | head -100) while IFS=' ' read -r package_name version; do if [[ -n "$package_name" && -n "$version" ]]; then # Check if this package is not in the upgrade list if ! echo "$upgradable" | grep -q "^$package_name/"; then if [[ "$first_ref" == true ]]; then first_ref=false else packages_ref+="," fi packages_ref+="{\"name\":\"$package_name\",\"currentVersion\":\"$version\",\"needsUpdate\":false,\"isSecurityUpdate\":false}" fi fi done <<< "$installed" } # Get package info for YUM/DNF-based systems get_yum_packages() { local -n packages_ref=$1 local -n first_ref=$2 local package_manager="yum" if command -v dnf &> /dev/null; then package_manager="dnf" fi # Get upgradable packages local upgradable=$($package_manager check-update 2>/dev/null | grep -v "^$" | grep -v "^Loaded" | grep -v "^Last metadata" | tail -n +2) while IFS= read -r line; do if [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then local package_name="${BASH_REMATCH[1]}" local available_version="${BASH_REMATCH[2]}" local repo="${BASH_REMATCH[3]}" # Get current version local current_version=$($package_manager list installed "$package_name" 2>/dev/null | grep "^$package_name" | awk '{print $2}') local is_security_update=false if echo "$repo" | grep -q "security"; then is_security_update=true fi if [[ "$first_ref" == true ]]; then first_ref=false else packages_ref+="," fi packages_ref+="{\"name\":\"$package_name\",\"currentVersion\":\"$current_version\",\"availableVersion\":\"$available_version\",\"needsUpdate\":true,\"isSecurityUpdate\":$is_security_update}" fi done <<< "$upgradable" # Get some installed packages that are up to date local installed=$($package_manager list installed 2>/dev/null | grep -v "^Loaded" | grep -v "^Installed" | head -100) while IFS= read -r line; do if [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then local package_name="${BASH_REMATCH[1]}" local version="${BASH_REMATCH[2]}" # Check if this package is not in the upgrade list if ! echo "$upgradable" | grep -q "^$package_name "; then if [[ "$first_ref" == true ]]; then first_ref=false else packages_ref+="," fi packages_ref+="{\"name\":\"$package_name\",\"currentVersion\":\"$version\",\"needsUpdate\":false,\"isSecurityUpdate\":false}" fi fi done <<< "$installed" } # Get hardware information get_hardware_info() { local cpu_model="" local cpu_cores=0 local ram_installed=0 local swap_size=0 local disk_details="[]" # CPU Information if command -v lscpu >/dev/null 2>&1; then cpu_model=$(lscpu | grep "Model name" | cut -d':' -f2 | xargs) cpu_cores=$(lscpu | grep "^CPU(s):" | cut -d':' -f2 | xargs) elif [[ -f /proc/cpuinfo ]]; then cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs) cpu_cores=$(grep -c "^processor" /proc/cpuinfo) fi # Memory Information if command -v free >/dev/null 2>&1; then ram_installed=$(free -g | grep "^Mem:" | awk '{print $2}') swap_size=$(free -g | grep "^Swap:" | awk '{print $2}') elif [[ -f /proc/meminfo ]]; then ram_installed=$(grep "MemTotal" /proc/meminfo | awk '{print int($2/1024/1024)}') swap_size=$(grep "SwapTotal" /proc/meminfo | awk '{print int($2/1024/1024)}') fi # Disk Information if command -v lsblk >/dev/null 2>&1; then disk_details=$(lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT | jq -c '[.blockdevices[] | select(.type == "disk") | {name: .name, size: .size, mountpoint: .mountpoint}]') elif command -v df >/dev/null 2>&1; then disk_details=$(df -h | grep -E "^/dev/" | awk '{print "{\"name\":\""$1"\",\"size\":\""$2"\",\"mountpoint\":\""$6"\"}"}' | jq -s .) fi echo "{\"cpuModel\":\"$cpu_model\",\"cpuCores\":$cpu_cores,\"ramInstalled\":$ram_installed,\"swapSize\":$swap_size,\"diskDetails\":$disk_details}" } # Get network information get_network_info() { local gateway_ip="" local dns_servers="[]" local network_interfaces="[]" # Gateway IP if command -v ip >/dev/null 2>&1; then gateway_ip=$(ip route | grep default | head -1 | awk '{print $3}') elif command -v route >/dev/null 2>&1; then gateway_ip=$(route -n | grep '^0.0.0.0' | head -1 | awk '{print $2}') fi # DNS Servers if [[ -f /etc/resolv.conf ]]; then dns_servers=$(grep "nameserver" /etc/resolv.conf | awk '{print $2}' | jq -R . | jq -s .) fi # Network Interfaces if command -v ip >/dev/null 2>&1; then network_interfaces=$(ip -j addr show | jq -c '[.[] | {name: .ifname, type: .link_type, addresses: [.addr_info[]? | {address: .local, family: .family}]}]') elif command -v ifconfig >/dev/null 2>&1; then network_interfaces=$(ifconfig -a | grep -E "^[a-zA-Z]" | awk '{print $1}' | jq -R . | jq -s .) fi echo "{\"gatewayIp\":\"$gateway_ip\",\"dnsServers\":$dns_servers,\"networkInterfaces\":$network_interfaces}" } # Get system information get_system_info() { local kernel_version="" local selinux_status="" local system_uptime="" local load_average="[]" # Kernel Version if [[ -f /proc/version ]]; then kernel_version=$(cat /proc/version | awk '{print $3}') elif command -v uname >/dev/null 2>&1; then kernel_version=$(uname -r) fi # SELinux Status if command -v getenforce >/dev/null 2>&1; then selinux_status=$(getenforce 2>/dev/null | tr '[:upper:]' '[:lower:]') elif [[ -f /etc/selinux/config ]]; then selinux_status=$(grep "^SELINUX=" /etc/selinux/config | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]') else selinux_status="disabled" fi # System Uptime if [[ -f /proc/uptime ]]; then local uptime_seconds=$(cat /proc/uptime | awk '{print int($1)}') local days=$((uptime_seconds / 86400)) local hours=$(((uptime_seconds % 86400) / 3600)) local minutes=$(((uptime_seconds % 3600) / 60)) system_uptime="${days}d ${hours}h ${minutes}m" elif command -v uptime >/dev/null 2>&1; then system_uptime=$(uptime | awk -F'up ' '{print $2}' | awk -F', load' '{print $1}') fi # Load Average if [[ -f /proc/loadavg ]]; then load_average=$(cat /proc/loadavg | awk '{print "["$1","$2","$3"]"}') elif command -v uptime >/dev/null 2>&1; then load_average=$(uptime | awk -F'load average: ' '{print "["$2"]"}' | tr -d ' ') fi echo "{\"kernelVersion\":\"$kernel_version\",\"selinuxStatus\":\"$selinux_status\",\"systemUptime\":\"$system_uptime\",\"loadAverage\":$load_average}" } # Send package update to server send_update() { load_credentials info "Collecting package information..." local packages_json=$(get_package_info) info "Collecting repository information..." local repositories_json=$(get_repository_info) info "Collecting hardware information..." local hardware_json=$(get_hardware_info) info "Collecting network information..." local network_json=$(get_network_info) info "Collecting system information..." local system_json=$(get_system_info) info "Sending update to PatchMon server..." # Merge all JSON objects into one local merged_json=$(echo "$hardware_json $network_json $system_json" | jq -s '.[0] * .[1] * .[2]') # Create the base payload and merge with system info local base_payload=$(cat </dev/null; then # Replace current script mv "/tmp/patchmon-agent-new.sh" "$0" chmod +x "$0" success "Agent updated successfully" info "Backup saved as: $0.backup.$(date +%Y%m%d_%H%M%S)" # Get the new version number local new_version=$(grep '^AGENT_VERSION=' "$0" | cut -d'"' -f2) info "Updated to version: $new_version" # Automatically run update to send new information to PatchMon info "Sending updated information to PatchMon..." if "$0" update; then success "Successfully sent updated information to PatchMon" else warning "Failed to send updated information to PatchMon (this is not critical)" fi else error "Downloaded script is invalid" rm -f "/tmp/patchmon-agent-new.sh" fi else error "Failed to download new agent script" fi else error "Failed to get update information" fi } # Update crontab with current policy update_crontab() { load_credentials info "Updating crontab with current policy..." local response=$(curl -s -X GET "$PATCHMON_SERVER/api/$API_VERSION/settings/update-interval") if [[ $? -eq 0 ]]; then local update_interval=$(echo "$response" | grep -o '"updateInterval":[0-9]*' | cut -d':' -f2) if [[ -n "$update_interval" ]]; then # Generate the expected crontab entry local expected_crontab="" if [[ $update_interval -eq 60 ]]; then # Hourly updates starting at current minute local current_minute=$(date +%M) expected_crontab="$current_minute * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" else # Custom interval updates expected_crontab="*/$update_interval * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" fi # Get current crontab local current_crontab=$(crontab -l 2>/dev/null | grep "patchmon-agent.sh update" | head -1) # Check if crontab needs updating if [[ "$current_crontab" == "$expected_crontab" ]]; then info "Crontab is already up to date (interval: $update_interval minutes)" return 0 fi info "Setting update interval to $update_interval minutes" echo "$expected_crontab" | crontab - success "Crontab updated successfully" else error "Could not determine update interval from server" fi else error "Failed to get update interval policy" fi } # Show detailed system diagnostics show_diagnostics() { info "PatchMon Agent Diagnostics v$AGENT_VERSION" echo "" # System information echo "=== System Information ===" echo "OS: $(uname -s)" echo "Architecture: $(uname -m)" echo "Kernel: $(uname -r)" echo "Hostname: $(hostname)" echo "Uptime: $(uptime -p 2>/dev/null || uptime)" echo "" # Agent information echo "=== Agent Information ===" echo "Version: $AGENT_VERSION" echo "Script Path: $0" echo "Config File: $CONFIG_FILE" echo "Credentials File: $CREDENTIALS_FILE" echo "Log File: $LOG_FILE" echo "Script Size: $(stat -c%s "$0" 2>/dev/null || echo "Unknown") bytes" echo "Last Modified: $(stat -c%y "$0" 2>/dev/null || echo "Unknown")" echo "" # Configuration if [[ -f "$CONFIG_FILE" ]]; then echo "=== Configuration ===" cat "$CONFIG_FILE" echo "" else echo "=== Configuration ===" echo "No configuration file found at $CONFIG_FILE" echo "" fi # Credentials status echo "=== Credentials Status ===" if [[ -f "$CREDENTIALS_FILE" ]]; then echo "Credentials file exists: Yes" echo "File size: $(stat -c%s "$CREDENTIALS_FILE" 2>/dev/null || echo "Unknown") bytes" echo "File permissions: $(stat -c%a "$CREDENTIALS_FILE" 2>/dev/null || echo "Unknown")" else echo "Credentials file exists: No" fi echo "" # Crontab status echo "=== Crontab Status ===" local crontab_entries=$(crontab -l 2>/dev/null | grep patchmon-agent || echo "None") if [[ "$crontab_entries" != "None" ]]; then echo "Crontab entries:" echo "$crontab_entries" else echo "No crontab entries found" fi echo "" # Network connectivity echo "=== Network Connectivity ===" if ping -c 1 -W 3 "$(echo "$PATCHMON_SERVER" | sed 's|http://||' | sed 's|https://||' | cut -d: -f1)" >/dev/null 2>&1; then echo "Server reachable: Yes" else echo "Server reachable: No" fi echo "Server URL: $PATCHMON_SERVER" echo "" # Recent logs echo "=== Recent Logs (last 10 lines) ===" if [[ -f "$LOG_FILE" ]]; then tail -10 "$LOG_FILE" 2>/dev/null || echo "Could not read log file" else echo "Log file does not exist" fi } # Show current configuration show_config() { info "Current Configuration:" echo " Server: ${PATCHMON_SERVER}" echo " API Version: ${API_VERSION}" echo " Agent Version: ${AGENT_VERSION}" echo " Config File: ${CONFIG_FILE}" echo " Credentials File: ${CREDENTIALS_FILE}" echo " Log File: ${LOG_FILE}" if [[ -f "$CREDENTIALS_FILE" ]]; then source "$CREDENTIALS_FILE" echo " API ID: ${API_ID}" echo " API Key: ${API_KEY:0:8}..." # Show only first 8 characters else echo " API Credentials: Not configured" fi } # Main function main() { case "$1" in "configure") check_root setup_directories load_config configure_credentials "$2" "$3" ;; "test") check_root setup_directories load_config test_credentials ;; "update") check_root setup_directories load_config detect_os send_update ;; "ping") check_root setup_directories load_config ping_server ;; "config") load_config show_config ;; "check-version") check_root setup_directories load_config check_version ;; "update-agent") check_root setup_directories load_config update_agent ;; "update-crontab") check_root setup_directories load_config update_crontab ;; "diagnostics") show_diagnostics ;; *) echo "PatchMon Agent v$AGENT_VERSION - API Credential Based" echo "Usage: $0 {configure|test|update|ping|config|check-version|update-agent|update-crontab|diagnostics}" echo "" echo "Commands:" echo " configure - Configure API credentials for this host" echo " test - Test API credentials connectivity" echo " update - Send package update information to server" echo " ping - Test connectivity to server" echo " config - Show current configuration" echo " check-version - Check for agent updates" echo " update-agent - Update agent to latest version" echo " update-crontab - Update crontab with current policy" echo " diagnostics - Show detailed system diagnostics" echo "" echo "Setup Process:" echo " 1. Contact your PatchMon administrator to create a host entry" echo " 2. Run: $0 configure (provided by admin)" echo " 3. Run: $0 test (to verify connection)" echo " 4. Run: $0 update (to send initial package data)" echo "" echo "Configuration:" echo " Edit $CONFIG_FILE to customize server settings" echo " PATCHMON_SERVER=http://your-server:3001" exit 1 ;; esac } # Run main function main "$@"