From 5bdff0d436d18af6cb539df85d8c4c718b3e9705 Mon Sep 17 00:00:00 2001 From: Alexandru-Victor Macocian Date: Thu, 30 Oct 2025 16:16:50 +0100 Subject: [PATCH] Update with support for mac --- custom-metrics/disk-usage.sh | 32 +++- custom-metrics/power-usage.sh | 133 +++++++++------ custom-metrics/temperature.sh | 106 +++++++----- orchestrator/setup-crontab.sh | 267 +++++++++++++++++++++++++------ orchestrator/setup-filebeat.sh | 17 +- orchestrator/setup-logrotate.sh | 73 +++++++-- orchestrator/setup-logstash.sh | 19 ++- orchestrator/setup-metricbeat.sh | 17 +- share/os-detection.sh | 196 +++++++++++++++++++++++ 9 files changed, 689 insertions(+), 171 deletions(-) create mode 100644 share/os-detection.sh diff --git a/custom-metrics/disk-usage.sh b/custom-metrics/disk-usage.sh index aa24668..19e274f 100644 --- a/custom-metrics/disk-usage.sh +++ b/custom-metrics/disk-usage.sh @@ -2,8 +2,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" LOGFILE=/var/log/disk_usage.log +OS_TYPE=$(DetectOS) # Create log file with proper error handling if ! touch "$LOGFILE" 2>/dev/null; then @@ -12,17 +14,35 @@ if ! touch "$LOGFILE" 2>/dev/null; then fi chmod 644 "$LOGFILE" 2>/dev/null -LogInformation "Starting disk usage monitoring" +LogInformation "Starting disk usage monitoring (OS: $OS_TYPE)" for i in {1..6}; do timestamp=$(GetTimestamp) # Use process substitution instead of pipe to avoid subshell issues - while read -r mount used total; do - if [[ -n "$mount" && -n "$used" && -n "$total" ]]; then - echo "$timestamp $mount $used $total" >> "$LOGFILE" - fi - done < <(df --output=target,used,size -B1 2>/dev/null | tail -n +2) + if [[ "$OS_TYPE" == "macos" ]]; then + # macOS df format: Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on + while read -r filesystem blocks used avail capacity iused ifree iusedpct mount; do + # Skip header line + if [[ "$filesystem" == "Filesystem" ]]; then + continue + fi + + # Convert 512-byte blocks to bytes + if [[ -n "$mount" && -n "$used" && -n "$blocks" ]]; then + used_bytes=$((used * 512)) + total_bytes=$((blocks * 512)) + echo "$timestamp $mount $used_bytes $total_bytes" >> "$LOGFILE" + fi + done < <(df 2>/dev/null) + else + # Linux df with --output flag + while read -r mount used total; do + if [[ -n "$mount" && -n "$used" && -n "$total" ]]; then + echo "$timestamp $mount $used $total" >> "$LOGFILE" + fi + done < <(df --output=target,used,size -B1 2>/dev/null | tail -n +2) + fi LogInformation "Disk usage data collected (iteration $i/6)" diff --git a/custom-metrics/power-usage.sh b/custom-metrics/power-usage.sh index 25e555e..6ef37f1 100644 --- a/custom-metrics/power-usage.sh +++ b/custom-metrics/power-usage.sh @@ -2,8 +2,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" LOGFILE=/var/log/power_usage.log +OS_TYPE=$(DetectOS) # Create log file with proper error handling if ! touch "$LOGFILE" 2>/dev/null; then @@ -14,64 +16,99 @@ chmod 644 "$LOGFILE" 2>/dev/null # Check if bc is available if ! command -v bc >/dev/null 2>&1; then - LogError "bc command not found. Please install bc: sudo apt-get install bc" + LogError "bc command not found. Please install bc:" + if [[ "$OS_TYPE" == "macos" ]]; then + LogError " macOS: brew install bc" + else + LogError " Ubuntu/Debian: sudo apt-get install bc" + fi exit 1 fi -# Path to the energy_uj file for the CPU package -RAPL_PATH="/sys/class/powercap/intel-rapl:0/energy_uj" - -# Check if RAPL interface is available -if [[ ! -r "$RAPL_PATH" ]]; then - LogError "Intel RAPL interface not available at $RAPL_PATH" - LogError "This may be due to:" - LogError "1. Non-Intel CPU" - LogError "2. RAPL not supported by kernel" - LogError "3. Insufficient permissions" - LogError "4. Module not loaded (try: sudo modprobe intel_rapl_common)" +# Check power monitoring support +if ! IsPowerMonitoringSupported; then + if [[ "$OS_TYPE" == "macos" ]]; then + LogError "powermetrics not available at /usr/bin/powermetrics" + LogError "Power monitoring requires macOS with powermetrics support" + else + LogError "Intel RAPL interface not available" + LogError "This may be due to:" + LogError "1. Non-Intel CPU" + LogError "2. RAPL not supported by kernel" + LogError "3. Insufficient permissions" + LogError "4. Module not loaded (try: sudo modprobe intel_rapl_common)" + fi exit 1 fi -LogInformation "Starting power usage monitoring" +LogInformation "Starting power usage monitoring (OS: $OS_TYPE)" for i in {1..6}; do - # Read initial energy value - if ! energy_1=$(cat "$RAPL_PATH" 2>/dev/null); then - LogError "Failed to read energy value from $RAPL_PATH" - exit 1 - fi - - sleep 1 - - # Read second energy value - if ! energy_2=$(cat "$RAPL_PATH" 2>/dev/null); then - LogError "Failed to read energy value from $RAPL_PATH" - exit 1 - fi - - energy_diff=$((energy_2 - energy_1)) - - # Convert microjoules to joules (1 J = 1,000,000 µJ) - energy_joules=$(echo "scale=6; $energy_diff / 1000000" | bc 2>/dev/null) - - if [[ $? -ne 0 ]]; then - LogError "Failed to calculate energy in joules" - continue - fi - - # Since we waited 1 second, the power usage is simply the energy difference in joules (W) - power_watts=$(echo "scale=6; $energy_joules / 1" | bc 2>/dev/null) - - if [[ $? -ne 0 ]]; then - LogError "Failed to calculate power in watts" - continue - fi - timestamp=$(GetTimestamp) + + if [[ "$OS_TYPE" == "macos" ]]; then + # Use powermetrics for macOS (requires sudo) + # Sample for 1 second and extract CPU power + power_data=$(sudo powermetrics -n 1 -i 1000 --samplers cpu_power 2>/dev/null | grep "CPU Power" | head -1) + + if [[ -n "$power_data" ]]; then + # Extract power value (format: "CPU Power: 1234 mW") + power_mw=$(echo "$power_data" | grep -o '[0-9]\+ mW' | grep -o '[0-9]\+') + + if [[ -n "$power_mw" ]]; then + # Convert milliwatts to watts + power_watts=$(echo "scale=6; $power_mw / 1000" | bc 2>/dev/null) + + if [[ $? -eq 0 ]]; then + echo "$timestamp $power_watts" >> "$LOGFILE" + LogInformation "Power usage recorded: ${power_watts}W (iteration $i/6)" + else + LogError "Failed to calculate power in watts" + fi + fi + else + LogWarning "Could not retrieve power data from powermetrics" + fi + else + # Linux RAPL interface + RAPL_PATH="/sys/class/powercap/intel-rapl:0/energy_uj" + + # Read initial energy value + if ! energy_1=$(cat "$RAPL_PATH" 2>/dev/null); then + LogError "Failed to read energy value from $RAPL_PATH" + exit 1 + fi + + sleep 1 - # Write power usage to log file - echo "$timestamp $power_watts" >> "$LOGFILE" - LogInformation "Power usage recorded: ${power_watts}W (iteration $i/6)" + # Read second energy value + if ! energy_2=$(cat "$RAPL_PATH" 2>/dev/null); then + LogError "Failed to read energy value from $RAPL_PATH" + exit 1 + fi + + energy_diff=$((energy_2 - energy_1)) + + # Convert microjoules to joules (1 J = 1,000,000 µJ) + energy_joules=$(echo "scale=6; $energy_diff / 1000000" | bc 2>/dev/null) + + if [[ $? -ne 0 ]]; then + LogError "Failed to calculate energy in joules" + continue + fi + + # Since we waited 1 second, the power usage is simply the energy difference in joules (W) + power_watts=$(echo "scale=6; $energy_joules / 1" | bc 2>/dev/null) + + if [[ $? -ne 0 ]]; then + LogError "Failed to calculate power in watts" + continue + fi + + # Write power usage to log file + echo "$timestamp $power_watts" >> "$LOGFILE" + LogInformation "Power usage recorded: ${power_watts}W (iteration $i/6)" + fi if [ $i -lt 6 ]; then sleep 9 diff --git a/custom-metrics/temperature.sh b/custom-metrics/temperature.sh index d2cd6f3..1fce618 100644 --- a/custom-metrics/temperature.sh +++ b/custom-metrics/temperature.sh @@ -2,8 +2,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" LOGFILE=/var/log/temperature.log +OS_TYPE=$(DetectOS) # Create log file with proper error handling if ! touch "$LOGFILE" 2>/dev/null; then @@ -12,55 +14,83 @@ if ! touch "$LOGFILE" 2>/dev/null; then fi chmod 644 "$LOGFILE" 2>/dev/null -# Check if thermal zones exist -THERMAL_ZONES=(/sys/class/thermal/thermal_zone*/type) -if [[ ! -e "${THERMAL_ZONES[0]}" ]]; then - LogError "No thermal zones found in /sys/class/thermal/" - LogError "This system may not support thermal monitoring" +# Check thermal monitoring support +if ! IsThermalMonitoringSupported; then + if [[ "$OS_TYPE" == "macos" ]]; then + LogError "Temperature monitoring not available on macOS" + LogError "Install osx-cpu-temp: brew install osx-cpu-temp" + LogError "Or use powermetrics (requires sudo)" + else + LogError "No thermal zones found in /sys/class/thermal/" + LogError "This system may not support thermal monitoring" + fi exit 1 fi -LogInformation "Starting temperature monitoring" +LogInformation "Starting temperature monitoring (OS: $OS_TYPE)" for i in {1..6}; do timestamp=$(GetTimestamp) - # Use arrays to avoid complex pipe chains that can hang - types=() - temps=() - - # Read thermal zone types - for type_file in /sys/class/thermal/thermal_zone*/type; do - if [[ -r "$type_file" ]]; then - type_value=$(cat "$type_file" 2>/dev/null) - if [[ -n "$type_value" ]]; then - types+=("$type_value") + if [[ "$OS_TYPE" == "macos" ]]; then + # Try osx-cpu-temp first + if command -v osx-cpu-temp &> /dev/null; then + temp_output=$(osx-cpu-temp 2>/dev/null) + if [[ $? -eq 0 && -n "$temp_output" ]]; then + # Output format: "50.0°C" + echo "$timestamp CPU $temp_output" >> "$LOGFILE" fi - fi - done - - # Read thermal zone temperatures - for temp_file in /sys/class/thermal/thermal_zone*/temp; do - if [[ -r "$temp_file" ]]; then - temp_value=$(cat "$temp_file" 2>/dev/null) - if [[ -n "$temp_value" && "$temp_value" =~ ^[0-9]+$ ]]; then - # Convert millidegrees to degrees with one decimal place - temp_celsius=$(echo "scale=1; $temp_value / 1000" | bc 2>/dev/null) - if [[ $? -eq 0 ]]; then - temps+=("${temp_celsius}°C") - else - temps+=("${temp_value}m°C") + # Fallback to powermetrics (requires sudo) + elif [[ -x /usr/bin/powermetrics ]]; then + # Run powermetrics for 1 second and extract temperature + temp_data=$(sudo powermetrics -n 1 -i 1000 --samplers smc 2>/dev/null | grep -i "CPU die temperature" | head -1) + if [[ -n "$temp_data" ]]; then + # Extract temperature value (format: "CPU die temperature: 50.50 C") + temp_value=$(echo "$temp_data" | grep -o '[0-9.]\+ C' | head -1) + if [[ -n "$temp_value" ]]; then + echo "$timestamp CPU ${temp_value}" >> "$LOGFILE" fi fi fi - done - - # Output paired type and temperature data - for j in "${!types[@]}"; do - if [[ $j -lt ${#temps[@]} ]]; then - echo "$timestamp ${types[$j]} ${temps[$j]}" >> "$LOGFILE" - fi - done + else + # Linux thermal zone reading + # Use arrays to avoid complex pipe chains that can hang + types=() + temps=() + + # Read thermal zone types + for type_file in /sys/class/thermal/thermal_zone*/type; do + if [[ -r "$type_file" ]]; then + type_value=$(cat "$type_file" 2>/dev/null) + if [[ -n "$type_value" ]]; then + types+=("$type_value") + fi + fi + done + + # Read thermal zone temperatures + for temp_file in /sys/class/thermal/thermal_zone*/temp; do + if [[ -r "$temp_file" ]]; then + temp_value=$(cat "$temp_file" 2>/dev/null) + if [[ -n "$temp_value" && "$temp_value" =~ ^[0-9]+$ ]]; then + # Convert millidegrees to degrees with one decimal place + temp_celsius=$(echo "scale=1; $temp_value / 1000" | bc 2>/dev/null) + if [[ $? -eq 0 ]]; then + temps+=("${temp_celsius}°C") + else + temps+=("${temp_value}m°C") + fi + fi + fi + done + + # Output paired type and temperature data + for j in "${!types[@]}"; do + if [[ $j -lt ${#temps[@]} ]]; then + echo "$timestamp ${types[$j]} ${temps[$j]}" >> "$LOGFILE" + fi + done + fi LogInformation "Temperature data collected (iteration $i/6)" diff --git a/orchestrator/setup-crontab.sh b/orchestrator/setup-crontab.sh index 6cd71d5..c0bca59 100755 --- a/orchestrator/setup-crontab.sh +++ b/orchestrator/setup-crontab.sh @@ -2,6 +2,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" # Configuration SCRIPTS=( @@ -10,6 +11,9 @@ SCRIPTS=( "../custom-metrics/temperature.sh" ) +OS_TYPE=$(DetectOS) +LAUNCHD_DIR="$HOME/Library/LaunchAgents" + CheckScriptExists() { local script="$1" local script_path="${SCRIPT_DIR}/${script}" @@ -29,17 +33,29 @@ InstallCrontabEntries() { local installed_count=0 local failed_count=0 - BackupCrontab + if [[ "$OS_TYPE" == "macos" ]]; then + BackupLaunchAgents + else + BackupCrontab + fi for script in "${SCRIPTS[@]}"; do LogInformation "Processing $script..." if CheckScriptExists "$script"; then LogInformation "Script $script found and is executable" - if ManageCrontabEntry "$script"; then - ((installed_count++)) + if [[ "$OS_TYPE" == "macos" ]]; then + if ManageLaunchAgent "$script"; then + ((installed_count++)) + else + ((failed_count++)) + fi else - ((failed_count++)) + if ManageCrontabEntry "$script"; then + ((installed_count++)) + else + ((failed_count++)) + fi fi else LogWarning "Script $script not found or not accessible, skipping..." @@ -54,6 +70,62 @@ InstallCrontabEntries() { fi } +ManageLaunchAgent() { + local script="$1" + local script_path="${SCRIPT_DIR}/${script}" + local script_name=$(basename "$script" .sh) + local plist_name="com.servermetrics.${script_name}.plist" + local plist_path="${LAUNCHD_DIR}/${plist_name}" + + # Ensure LaunchAgents directory exists + if [[ ! -d "$LAUNCHD_DIR" ]]; then + mkdir -p "$LAUNCHD_DIR" + fi + + # Create plist file + cat > "$plist_path" << EOF + + + + + Label + ${plist_name%.plist} + ProgramArguments + + ${script_path} + + StartInterval + 60 + RunAtLoad + + StandardErrorPath + /tmp/${script_name}.err + StandardOutPath + /tmp/${script_name}.out + + +EOF + + if [[ $? -eq 0 ]]; then + LogInformation "Created LaunchAgent plist: $plist_path" + + # Unload if already loaded (ignore errors) + launchctl unload "$plist_path" 2>/dev/null || true + + # Load the plist + if launchctl load "$plist_path" 2>/dev/null; then + LogInformation "Loaded LaunchAgent for $script" + return 0 + else + LogError "Failed to load LaunchAgent for $script" + return 1 + fi + else + LogError "Failed to create plist for $script" + return 1 + fi +} + ManageCrontabEntry() { local script="$1" local script_path="${SCRIPT_DIR}/${script}" @@ -95,7 +167,13 @@ ManageCrontabEntry() { } ShowStatus() { - LogInformation "Crontab entries status:" + LogInformation "Scheduled task status:" + LogInformation "OS: $OS_TYPE" + if [[ "$OS_TYPE" == "macos" ]]; then + LogInformation "Method: LaunchAgents" + else + LogInformation "Method: Crontab" + fi LogInformation "" local found_entries=0 @@ -103,64 +181,117 @@ ShowStatus() { local script_path="${SCRIPT_DIR}/${script}" local script_name=$(basename "$script") - if crontab -l 2>/dev/null | grep -q "$script_path"; then - LogInformation "✓ $script_name - INSTALLED" - ((found_entries++)) - elif [[ -f "$script_path" ]]; then - LogInformation "✗ $script_name - NOT INSTALLED (script available)" + if [[ "$OS_TYPE" == "macos" ]]; then + local plist_name="com.servermetrics.$(basename "$script" .sh).plist" + local plist_path="${LAUNCHD_DIR}/${plist_name}" + + if [[ -f "$plist_path" ]]; then + if launchctl list | grep -q "$(basename "$plist_name" .plist)"; then + LogInformation "✓ $script_name - LOADED" + else + LogInformation "⚠ $script_name - INSTALLED but NOT LOADED" + fi + ((found_entries++)) + elif [[ -f "$script_path" ]]; then + LogInformation "✗ $script_name - NOT INSTALLED (script available)" + else + LogInformation "✗ $script_name - SCRIPT MISSING" + fi else - LogInformation "✗ $script_name - SCRIPT MISSING" + if crontab -l 2>/dev/null | grep -q "$script_path"; then + LogInformation "✓ $script_name - INSTALLED" + ((found_entries++)) + elif [[ -f "$script_path" ]]; then + LogInformation "✗ $script_name - NOT INSTALLED (script available)" + else + LogInformation "✗ $script_name - SCRIPT MISSING" + fi fi done LogInformation "" if [[ $found_entries -gt 0 ]]; then LogInformation "Commands:" - LogInformation " View crontab: crontab -l" - LogInformation " Edit crontab: crontab -e" - LogInformation " Check logs: sudo tail -f /var/log/syslog | grep CRON" + if [[ "$OS_TYPE" == "macos" ]]; then + LogInformation " List agents: launchctl list | grep servermetrics" + LogInformation " View logs: tail -f /tmp/*.out" + LogInformation " Unload agent: launchctl unload ~/Library/LaunchAgents/com.servermetrics.*.plist" + else + LogInformation " View crontab: crontab -l" + LogInformation " Edit crontab: crontab -e" + LogInformation " Check logs: sudo tail -f /var/log/syslog | grep CRON" + fi else - LogWarning "No crontab entries are currently installed" + LogWarning "No scheduled tasks are currently installed" fi } RemoveCrontabEntries() { local removed_count=0 - local current_crontab - current_crontab=$(crontab -l 2>/dev/null || echo "") - - if [[ -z "$current_crontab" ]]; then - LogWarning "No crontab entries found to remove" - return - fi - - local new_crontab="$current_crontab" - - for script in "${SCRIPTS[@]}"; do - local script_path="${SCRIPT_DIR}/${script}" - local script_name=$(basename "$script") + if [[ "$OS_TYPE" == "macos" ]]; then + # Remove LaunchAgents + for script in "${SCRIPTS[@]}"; do + local script_name=$(basename "$script" .sh) + local plist_name="com.servermetrics.${script_name}.plist" + local plist_path="${LAUNCHD_DIR}/${plist_name}" + + if [[ -f "$plist_path" ]]; then + LogInformation "Removing LaunchAgent for $script_name..." + + # Unload the agent + launchctl unload "$plist_path" 2>/dev/null || true + + # Remove the plist file + rm -f "$plist_path" + ((removed_count++)) + else + LogDebug "No LaunchAgent found for $script_name" + fi + done - if echo "$new_crontab" | grep -q "$script_path"; then - LogInformation "Removing crontab entry for $script_name..." - new_crontab=$(echo "$new_crontab" | grep -v "$script_path") - ((removed_count++)) + if [[ $removed_count -gt 0 ]]; then + LogInformation "Removed $removed_count LaunchAgents" else - LogDebug "No crontab entry found for $script_name" + LogWarning "No LaunchAgents were found to remove" fi - done - - if [[ $removed_count -gt 0 ]]; then - if [[ -z "$new_crontab" ]]; then - crontab -r 2>/dev/null || true - LogInformation "Removed all entries, crontab cleared" - else - echo "$new_crontab" | crontab - - LogInformation "Updated crontab with remaining entries" - fi - LogInformation "Removed $removed_count crontab entries" else - LogWarning "No matching crontab entries were found to remove" + # Remove crontab entries + local current_crontab + current_crontab=$(crontab -l 2>/dev/null || echo "") + + if [[ -z "$current_crontab" ]]; then + LogWarning "No crontab entries found to remove" + return + fi + + local new_crontab="$current_crontab" + + for script in "${SCRIPTS[@]}"; do + local script_path="${SCRIPT_DIR}/${script}" + local script_name=$(basename "$script") + + if echo "$new_crontab" | grep -q "$script_path"; then + LogInformation "Removing crontab entry for $script_name..." + new_crontab=$(echo "$new_crontab" | grep -v "$script_path") + ((removed_count++)) + else + LogDebug "No crontab entry found for $script_name" + fi + done + + if [[ $removed_count -gt 0 ]]; then + if [[ -z "$new_crontab" ]]; then + crontab -r 2>/dev/null || true + LogInformation "Removed all entries, crontab cleared" + else + echo "$new_crontab" | crontab - + LogInformation "Updated crontab with remaining entries" + fi + LogInformation "Removed $removed_count crontab entries" + else + LogWarning "No matching crontab entries were found to remove" + fi fi } @@ -174,16 +305,48 @@ BackupCrontab() { fi } +BackupLaunchAgents() { + local backup_dir="${SCRIPT_DIR}/launchagents_backup_$(date +%Y%m%d_%H%M%S)" + mkdir -p "$backup_dir" + + local backup_count=0 + for script in "${SCRIPTS[@]}"; do + local script_name=$(basename "$script" .sh) + local plist_name="com.servermetrics.${script_name}.plist" + local plist_path="${LAUNCHD_DIR}/${plist_name}" + + if [[ -f "$plist_path" ]]; then + cp "$plist_path" "$backup_dir/" + ((backup_count++)) + fi + done + + if [[ $backup_count -gt 0 ]]; then + LogInformation "LaunchAgents backed up to: $backup_dir ($backup_count files)" + else + LogWarning "No existing LaunchAgents to backup" + rmdir "$backup_dir" 2>/dev/null + fi +} + ShowUsage() { + local task_method="crontab" + if [[ "$OS_TYPE" == "macos" ]]; then + task_method="LaunchAgents" + fi + echo "Usage: $0 [install|remove|status|help]" echo "" echo "Commands:" - echo " install - Install all crontab entries (default)" - echo " remove - Remove all crontab entries" - echo " status - Show status of crontab entries" + echo " install - Install all scheduled tasks (default)" + echo " remove - Remove all scheduled tasks" + echo " status - Show status of scheduled tasks" echo " help - Show this help message" echo "" - echo "This script manages crontab entries for monitoring scripts:" + echo "OS: $OS_TYPE" + echo "Method: $task_method" + echo "" + echo "This script manages scheduled tasks for monitoring scripts:" echo "$(printf ' %s\n' "${SCRIPTS[@]}" | sed 's|../custom-metrics/||')" echo "" echo "Examples:" @@ -196,12 +359,12 @@ Main() { case "$action" in install) - LogInformation "Installing crontab entries for server monitoring scripts" + LogInformation "Installing scheduled tasks for server monitoring scripts (OS: $OS_TYPE)" InstallCrontabEntries - LogInformation "Crontab setup completed successfully!" + LogInformation "Scheduled task setup completed successfully!" ;; remove) - LogInformation "Removing crontab entries for server monitoring scripts" + LogInformation "Removing scheduled tasks for server monitoring scripts (OS: $OS_TYPE)" RemoveCrontabEntries ;; status) diff --git a/orchestrator/setup-filebeat.sh b/orchestrator/setup-filebeat.sh index 4fc557f..1add467 100755 --- a/orchestrator/setup-filebeat.sh +++ b/orchestrator/setup-filebeat.sh @@ -2,12 +2,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" # Configuration FILEBEAT_CONFIG_DIR="${SCRIPT_DIR}/../filebeat" -FILEBEAT_INSTALL_DIR="/etc/filebeat" +FILEBEAT_INSTALL_DIR=$(GetFilebeatConfigDir) FILEBEAT_MODULES_D_DIR="${FILEBEAT_INSTALL_DIR}/modules.d" CONFIG_FILE="filebeat.yml" +OS_TYPE=$(DetectOS) CheckRoot() { if [[ $EUID -ne 0 ]]; then @@ -129,6 +131,8 @@ ValidateFilebeatConfig() { ShowStatus() { LogInformation "Filebeat configuration status:" + LogInformation "OS: $OS_TYPE" + LogInformation "Install directory: $FILEBEAT_INSTALL_DIR" LogInformation "" # Check main config file @@ -157,8 +161,15 @@ ShowStatus() { LogInformation "Commands:" LogInformation " Test config: sudo filebeat test config -c ${FILEBEAT_INSTALL_DIR}/${CONFIG_FILE}" LogInformation " Test output: sudo filebeat test output -c ${FILEBEAT_INSTALL_DIR}/${CONFIG_FILE}" - LogInformation " Start Filebeat: sudo systemctl start filebeat" - LogInformation " Check status: sudo systemctl status filebeat" + + local service_cmd=$(GetServiceCommand) + if [[ "$OS_TYPE" == "macos" ]]; then + LogInformation " Start Filebeat: sudo $service_cmd start filebeat" + LogInformation " Check status: $service_cmd list | grep filebeat" + else + LogInformation " Start Filebeat: sudo $service_cmd start filebeat" + LogInformation " Check status: sudo $service_cmd status filebeat" + fi } RemoveFilebeatConfigs() { diff --git a/orchestrator/setup-logrotate.sh b/orchestrator/setup-logrotate.sh index cdf4108..34604ce 100755 --- a/orchestrator/setup-logrotate.sh +++ b/orchestrator/setup-logrotate.sh @@ -2,9 +2,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" LOGROTATE_DIR="${SCRIPT_DIR}/../logrotate" -LOGROTATE_INSTALL_DIR="/etc/logrotate.d" +LOGROTATE_INSTALL_DIR=$(GetLogRotateConfigDir) +OS_TYPE=$(DetectOS) # List of logrotate config files to manage (without .conf extension) LOGROTATE_CONFIGS=( @@ -19,13 +21,27 @@ CheckRoot() { } CheckLogrotateInstalled() { - if ! command -v logrotate &> /dev/null; then - LogError "logrotate is not installed. Please install it first:" - LogError " Ubuntu/Debian: sudo apt-get install logrotate" - LogError " CentOS/RHEL: sudo yum install logrotate" + local log_cmd=$(GetLogRotateCommand) + + if ! IsLogRotateAvailable; then + LogError "$log_cmd is not installed. Please install it first:" + if [[ "$OS_TYPE" == "macos" ]]; then + LogError " macOS uses newsyslog (built-in) but may need configuration at $LOGROTATE_INSTALL_DIR" + LogError " If directory doesn't exist: sudo mkdir -p $LOGROTATE_INSTALL_DIR" + else + LogError " Ubuntu/Debian: sudo apt-get install logrotate" + LogError " CentOS/RHEL: sudo yum install logrotate" + fi exit 1 fi - LogInformation "logrotate is installed" + LogInformation "$log_cmd is available" + + # Ensure the config directory exists (especially for macOS newsyslog.d) + if [[ ! -d "$LOGROTATE_INSTALL_DIR" ]]; then + LogInformation "Creating config directory: $LOGROTATE_INSTALL_DIR" + mkdir -p "$LOGROTATE_INSTALL_DIR" + chmod 755 "$LOGROTATE_INSTALL_DIR" + fi } InstallLogrotateConfigs() { @@ -60,10 +76,11 @@ InstallLogrotateConfigs() { } TestLogrotateConfigs() { - LogInformation "Testing logrotate configurations..." + LogInformation "Testing log rotation configurations..." local test_passed=0 local test_failed=0 + local log_cmd=$(GetLogRotateCommand) for config_name in "${LOGROTATE_CONFIGS[@]}"; do local config_file="${LOGROTATE_INSTALL_DIR}/${config_name}" @@ -74,13 +91,25 @@ TestLogrotateConfigs() { fi LogDebug "Testing configuration: $config_name" - if logrotate -d "$config_file" &> /dev/null; then - LogDebug "Test passed: $config_name" - ((test_passed++)) + + if [[ "$OS_TYPE" == "macos" ]]; then + # newsyslog uses different syntax, just check file readability + if [[ -r "$config_file" ]]; then + LogDebug "Test passed: $config_name (file readable)" + ((test_passed++)) + else + LogError "Test failed: $config_name (file not readable)" + ((test_failed++)) + fi else - LogError "Test failed: $config_name" - LogError "Run for details: logrotate -d $config_file" - ((test_failed++)) + if logrotate -d "$config_file" &> /dev/null; then + LogDebug "Test passed: $config_name" + ((test_passed++)) + else + LogError "Test failed: $config_name" + LogError "Run for details: logrotate -d $config_file" + ((test_failed++)) + fi fi done @@ -93,7 +122,11 @@ TestLogrotateConfigs() { } ShowStatus() { - LogInformation "Logrotate configurations:" + local log_cmd=$(GetLogRotateCommand) + LogInformation "Log rotation configurations:" + LogInformation "OS: $OS_TYPE" + LogInformation "Tool: $log_cmd" + LogInformation "Install directory: $LOGROTATE_INSTALL_DIR" LogInformation "" local found_configs=0 @@ -114,9 +147,15 @@ ShowStatus() { LogInformation "" if [[ $found_configs -gt 0 ]]; then LogInformation "Commands:" - LogInformation " Test rotation: sudo logrotate -f /etc/logrotate.d/" - LogInformation " Check status: sudo cat /var/lib/logrotate/status" - LogInformation " Manual run: sudo logrotate /etc/logrotate.conf" + if [[ "$OS_TYPE" == "macos" ]]; then + LogInformation " Manual run: sudo newsyslog -v" + LogInformation " Check config: cat /etc/newsyslog.conf" + LogInformation " Force rotation: sudo newsyslog -F" + else + LogInformation " Test rotation: sudo logrotate -f /etc/logrotate.d/" + LogInformation " Check status: sudo cat /var/lib/logrotate/status" + LogInformation " Manual run: sudo logrotate /etc/logrotate.conf" + fi else LogWarning "No configurations are currently installed" fi diff --git a/orchestrator/setup-logstash.sh b/orchestrator/setup-logstash.sh index aec90b4..bcb0b03 100755 --- a/orchestrator/setup-logstash.sh +++ b/orchestrator/setup-logstash.sh @@ -2,12 +2,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" # Configuration LOGSTASH_CONFIG_DIR="${SCRIPT_DIR}/../logstash" -LOGSTASH_INSTALL_DIR="/etc/logstash" +LOGSTASH_INSTALL_DIR=$(GetLogstashConfigDir) LOGSTASH_CONF_D_DIR="${LOGSTASH_INSTALL_DIR}/conf.d" OUTPUT_CONFIG_FILE="conf.d/90-output.conf" +OS_TYPE=$(DetectOS) CheckRoot() { if [[ $EUID -ne 0 ]]; then @@ -143,6 +145,8 @@ ValidateLogstashConfig() { ShowStatus() { LogInformation "Logstash configuration status:" + LogInformation "OS: $OS_TYPE" + LogInformation "Install directory: $LOGSTASH_INSTALL_DIR" LogInformation "" # Check main config files @@ -175,9 +179,16 @@ ShowStatus() { LogInformation "" LogInformation "Commands:" - LogInformation " Test config: sudo logstash -t --path.settings=/etc/logstash" - LogInformation " Start Logstash: sudo systemctl start logstash" - LogInformation " Check status: sudo systemctl status logstash" + local service_cmd=$(GetServiceCommand) + if [[ "$OS_TYPE" == "macos" ]]; then + LogInformation " Test config: logstash -t --path.settings=$LOGSTASH_INSTALL_DIR" + LogInformation " Start Logstash: $service_cmd start logstash" + LogInformation " Check status: $service_cmd list | grep logstash" + else + LogInformation " Test config: sudo logstash -t --path.settings=/etc/logstash" + LogInformation " Start Logstash: sudo $service_cmd start logstash" + LogInformation " Check status: sudo $service_cmd status logstash" + fi } RemoveLogstashConfigs() { diff --git a/orchestrator/setup-metricbeat.sh b/orchestrator/setup-metricbeat.sh index 3caf1cc..8d81525 100755 --- a/orchestrator/setup-metricbeat.sh +++ b/orchestrator/setup-metricbeat.sh @@ -2,12 +2,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "${SCRIPT_DIR}/../share/logging.sh" +. "${SCRIPT_DIR}/../share/os-detection.sh" # Configuration METRICBEAT_CONFIG_DIR="${SCRIPT_DIR}/../metricbeat" -METRICBEAT_INSTALL_DIR="/etc/metricbeat" +METRICBEAT_INSTALL_DIR=$(GetMetricbeatConfigDir) METRICBEAT_MODULES_D_DIR="${METRICBEAT_INSTALL_DIR}/modules.d" CONFIG_FILE="metricbeat.yml" +OS_TYPE=$(DetectOS) CheckRoot() { if [[ $EUID -ne 0 ]]; then @@ -129,6 +131,8 @@ ValidateMetricbeatConfig() { ShowStatus() { LogInformation "Metricbeat configuration status:" + LogInformation "OS: $OS_TYPE" + LogInformation "Install directory: $METRICBEAT_INSTALL_DIR" LogInformation "" # Check main config file @@ -157,8 +161,15 @@ ShowStatus() { LogInformation "Commands:" LogInformation " Test config: sudo metricbeat test config -c ${METRICBEAT_INSTALL_DIR}/${CONFIG_FILE}" LogInformation " Test output: sudo metricbeat test output -c ${METRICBEAT_INSTALL_DIR}/${CONFIG_FILE}" - LogInformation " Start Metricbeat: sudo systemctl start metricbeat" - LogInformation " Check status: sudo systemctl status metricbeat" + + local service_cmd=$(GetServiceCommand) + if [[ "$OS_TYPE" == "macos" ]]; then + LogInformation " Start Metricbeat: sudo $service_cmd start metricbeat" + LogInformation " Check status: $service_cmd list | grep metricbeat" + else + LogInformation " Start Metricbeat: sudo $service_cmd start metricbeat" + LogInformation " Check status: sudo $service_cmd status metricbeat" + fi } RemoveMetricbeatConfigs() { diff --git a/share/os-detection.sh b/share/os-detection.sh new file mode 100644 index 0000000..cf919a3 --- /dev/null +++ b/share/os-detection.sh @@ -0,0 +1,196 @@ +#!/bin/bash + +# OS Detection and Path Configuration +# This script provides OS-specific paths and configurations + +# Detect OS +DetectOS() { + if [[ "$OSTYPE" == "darwin"* ]]; then + echo "macos" + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "linux" + else + echo "unknown" + fi +} + +# Get config directory for Filebeat +GetFilebeatConfigDir() { + local os=$(DetectOS) + case "$os" in + macos) + echo "/usr/local/etc/filebeat" + ;; + linux) + echo "/etc/filebeat" + ;; + *) + echo "/etc/filebeat" + ;; + esac +} + +# Get config directory for Metricbeat +GetMetricbeatConfigDir() { + local os=$(DetectOS) + case "$os" in + macos) + echo "/usr/local/etc/metricbeat" + ;; + linux) + echo "/etc/metricbeat" + ;; + *) + echo "/etc/metricbeat" + ;; + esac +} + +# Get config directory for Logstash +GetLogstashConfigDir() { + local os=$(DetectOS) + case "$os" in + macos) + # Homebrew installation path + echo "/usr/local/etc/logstash" + ;; + linux) + echo "/etc/logstash" + ;; + *) + echo "/etc/logstash" + ;; + esac +} + +# Get logrotate/newsyslog config directory +GetLogRotateConfigDir() { + local os=$(DetectOS) + case "$os" in + macos) + # macOS uses newsyslog instead of logrotate + echo "/usr/local/etc/newsyslog.d" + ;; + linux) + echo "/etc/logrotate.d" + ;; + *) + echo "/etc/logrotate.d" + ;; + esac +} + +# Check if logrotate is available +IsLogRotateAvailable() { + local os=$(DetectOS) + case "$os" in + macos) + # macOS uses newsyslog + command -v newsyslog &> /dev/null + ;; + linux) + command -v logrotate &> /dev/null + ;; + *) + command -v logrotate &> /dev/null + ;; + esac +} + +# Get log rotation command +GetLogRotateCommand() { + local os=$(DetectOS) + case "$os" in + macos) + echo "newsyslog" + ;; + linux) + echo "logrotate" + ;; + *) + echo "logrotate" + ;; + esac +} + +# Check if thermal monitoring is supported +IsThermalMonitoringSupported() { + local os=$(DetectOS) + case "$os" in + macos) + # Check if osx-cpu-temp or powermetrics is available + command -v osx-cpu-temp &> /dev/null || [[ -x /usr/bin/powermetrics ]] + ;; + linux) + # Check if thermal zones exist + [[ -e /sys/class/thermal/thermal_zone0/type ]] + ;; + *) + return 1 + ;; + esac +} + +# Check if power monitoring is supported +IsPowerMonitoringSupported() { + local os=$(DetectOS) + case "$os" in + macos) + # powermetrics requires sudo and is available on all recent macOS versions + [[ -x /usr/bin/powermetrics ]] + ;; + linux) + # Check if Intel RAPL is available + [[ -r /sys/class/powercap/intel-rapl:0/energy_uj ]] + ;; + *) + return 1 + ;; + esac +} + +# Get OS-specific df flags for disk usage +GetDfFlags() { + local os=$(DetectOS) + case "$os" in + macos) + # macOS df doesn't support --output flag + echo "" + ;; + linux) + echo "--output=target,used,size -B1" + ;; + *) + echo "" + ;; + esac +} + +# Get service management command (systemctl vs launchctl) +GetServiceCommand() { + local os=$(DetectOS) + case "$os" in + macos) + echo "brew services" + ;; + linux) + echo "systemctl" + ;; + *) + echo "systemctl" + ;; + esac +} + +# Export functions for sourcing +export -f DetectOS +export -f GetFilebeatConfigDir +export -f GetMetricbeatConfigDir +export -f GetLogstashConfigDir +export -f GetLogRotateConfigDir +export -f IsLogRotateAvailable +export -f GetLogRotateCommand +export -f IsThermalMonitoringSupported +export -f IsPowerMonitoringSupported +export -f GetDfFlags +export -f GetServiceCommand