Last active
December 30, 2025 01:56
-
-
Save superyngo/7ff78b1ccc213ff6a6cb3b1b2dbd77d0 to your computer and use it in GitHub Desktop.
Manage logrotate configurations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # wrtman - Manage logrotate configurations | |
| # Usage: | |
| # ./wrtman.sh install [rotate_name] | |
| # ./wrtman.sh uninstall [rotate_name] | |
| # ./wrtman.sh info [rotate_name] | |
| # ./wrtman.sh list | |
| # ./wrtman.sh test [rotate_name] | |
| # v0.2.1 fix config permission, add permission check for testing | |
| set -euo pipefail | |
| # Configuration | |
| readonly ROTATE_DIR="/etc/logrotate.d" | |
| readonly SELF_UID="$(id -u)" | |
| readonly SELF_GID="$(id -g)" | |
| readonly SELF_USER="$(id -un)" | |
| readonly SELF_GROUP="$(id -gn)" | |
| # Colors for output | |
| readonly RED='\033[0;31m' | |
| readonly GREEN='\033[0;32m' | |
| readonly YELLOW='\033[1;33m' | |
| readonly NC='\033[0m' # No Color | |
| # Generate logrotate configuration | |
| generate_config() { | |
| local -n logs_ref=$1 | |
| cat <<EOF | |
| # Generated by wrtman | |
| # Log files: ${logs_ref[*]} | |
| ${logs_ref[*]} { | |
| # Rotation schedule | |
| daily | |
| rotate 14 | |
| # Compression | |
| compress | |
| delaycompress | |
| # Error handling | |
| missingok | |
| notifempty | |
| # File permissions | |
| create 0644 $SELF_USER $SELF_GROUP | |
| # Date format in rotated files (optional) | |
| dateext | |
| dateformat -%Y%m%d | |
| # Post-rotation script (optional) | |
| # postrotate | |
| # /usr/bin/systemctl reload your-service > /dev/null 2>&1 || true | |
| # endscript | |
| } | |
| EOF | |
| } | |
| # Logging functions | |
| log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } | |
| log_success() { echo -e "${GREEN}[OK]${NC} $*"; } | |
| log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } | |
| log_info() { echo "[INFO] $*"; } | |
| # Check if running with sudo/root | |
| require_sudo() { | |
| if [[ $EUID -eq 0 ]]; then | |
| return 0 | |
| fi | |
| if ! command -v sudo >/dev/null 2>&1; then | |
| log_error "This operation requires sudo, but sudo is not installed" | |
| exit 1 | |
| fi | |
| if ! sudo -n true 2>/dev/null; then | |
| log_error "This operation requires sudo privileges" | |
| log_info "Run: sudo $0 $*" | |
| exit 1 | |
| fi | |
| } | |
| # Validate rotation name | |
| validate_rotate_name() { | |
| local name="$1" | |
| if [[ -z "$name" ]]; then | |
| log_error "Rotation name cannot be empty" | |
| return 1 | |
| fi | |
| if [[ ! "$name" =~ ^[a-zA-Z0-9._-]+$ ]]; then | |
| log_error "Invalid rotation name: '$name'" | |
| log_info "Name must contain only alphanumeric characters, dots, underscores, and hyphens" | |
| return 1 | |
| fi | |
| if [[ "${#name}" -gt 255 ]]; then | |
| log_error "Rotation name too long (max 255 characters)" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Validate log path | |
| validate_log_path() { | |
| local path="$1" | |
| if [[ -z "$path" ]]; then | |
| log_error "Log path cannot be empty" | |
| return 1 | |
| fi | |
| if [[ "$path" != /* ]]; then | |
| log_error "Log path must be absolute: '$path'" | |
| return 1 | |
| fi | |
| # Check for suspicious paths | |
| if [[ "$path" =~ ^/etc/|^/boot/|^/sys/|^/proc/|^/dev/ ]]; then | |
| log_error "Cannot rotate system directory logs: '$path'" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Create log file if it doesn't exist | |
| ensure_log_file() { | |
| local log="$1" | |
| local dir | |
| dir="$(dirname "$log")" | |
| if [[ ! -f "$log" ]]; then | |
| log_info "Creating log file: $log" | |
| if [[ ! -d "$dir" ]]; then | |
| sudo mkdir -p "$dir" || { | |
| log_error "Failed to create directory: $dir" | |
| return 1 | |
| } | |
| fi | |
| sudo touch "$log" || { | |
| log_error "Failed to create log file: $log" | |
| return 1 | |
| } | |
| sudo chown "$SELF_UID:$SELF_GID" "$log" || { | |
| log_warn "Could not change ownership of $log" | |
| } | |
| sudo chmod 644 "$log" || { | |
| log_warn "Could not change permissions of $log" | |
| } | |
| fi | |
| return 0 | |
| } | |
| check_root_owner() { | |
| local config_path="$1" | |
| if [ "$(stat -c %u "$config_path")" -eq 0 ]; then | |
| log_success "Permission OK: owner is root" | |
| return 0 | |
| else | |
| log_error "Permission error: owner is not root" | |
| return 1 | |
| fi | |
| } | |
| # Test logrotate configuration | |
| test_config() { | |
| local config_file="$1" | |
| if ! command -v logrotate >/dev/null 2>&1; then | |
| log_warn "logrotate command not found, skipping validation" | |
| return 0 | |
| fi | |
| log_info "Testing configuration..." | |
| # Create a temporary copy owned by root for testing | |
| local test_file="/tmp/logrotate-test-$" | |
| sudo cp "$config_file" "$test_file" | |
| sudo chown root:root "$test_file" | |
| sudo chmod 644 "$test_file" | |
| # Capture both stdout and stderr | |
| local test_output | |
| test_output=$(sudo logrotate -d "$test_file" 2>&1) | |
| local test_result=$? | |
| # Clean up test file | |
| sudo rm -f "$test_file" | |
| # Check for actual errors (ignore warnings about debug mode) | |
| if echo "$test_output" | grep -i "error:" | grep -v "file owner is wrong" >/dev/null; then | |
| log_error "Configuration test failed" | |
| echo "$test_output" | grep -i "error:" | |
| return 1 | |
| fi | |
| log_success "Configuration is valid" | |
| return 0 | |
| } | |
| # Install logrotate configuration | |
| install() { | |
| require_sudo "$@" | |
| local rotate_name="$1" | |
| # Prompt for name if not provided | |
| if [[ -z "$rotate_name" ]]; then | |
| read -rp "Enter rotation config name: " rotate_name | |
| fi | |
| validate_rotate_name "$rotate_name" || exit 1 | |
| local config_path="$ROTATE_DIR/$rotate_name" | |
| # Check if already exists | |
| if [[ -f "$config_path" ]]; then | |
| log_warn "Configuration already exists: $config_path" | |
| read -rp "Overwrite? (y/N): " confirm | |
| [[ "$confirm" != [yY] ]] && { log_info "Aborted"; exit 0; } | |
| fi | |
| # Collect log paths | |
| echo "Enter log file paths (one per line, empty line to finish):" | |
| local -a logs=() | |
| while IFS= read -r line; do | |
| [[ -z "$line" ]] && break | |
| if validate_log_path "$line"; then | |
| logs+=("$line") | |
| log_success "Added: $line" | |
| else | |
| log_error "Skipped invalid path: $line" | |
| fi | |
| done | |
| if [[ ${#logs[@]} -eq 0 ]]; then | |
| log_error "No valid log paths provided" | |
| exit 1 | |
| fi | |
| # Create log files if needed | |
| for log in "${logs[@]}"; do | |
| ensure_log_file "$log" || exit 1 | |
| done | |
| # Generate and install config | |
| local tmp_config | |
| tmp_config="$(mktemp)" | |
| trap 'rm -f "$tmp_config"' EXIT | |
| generate_config logs > "$tmp_config" | |
| log_info "Generated configuration:" | |
| cat "$tmp_config" | |
| echo | |
| # Test configuration | |
| if ! test_config "$tmp_config"; then | |
| log_error "Installation aborted due to configuration errors" | |
| exit 1 | |
| fi | |
| # Install | |
| sudo mv "$tmp_config" "$config_path" || { | |
| log_error "Failed to install configuration" | |
| exit 1 | |
| } | |
| sudo chmod 644 "$config_path" | |
| sudo chown root:root "$config_path" | |
| log_success "Installed: $config_path" | |
| log_info "Logs will be rotated according to the schedule" | |
| } | |
| # Uninstall logrotate configuration | |
| uninstall() { | |
| require_sudo "$@" | |
| local rotate_name="$1" | |
| if [[ -z "$rotate_name" ]]; then | |
| read -rp "Enter rotation config name to remove: " rotate_name | |
| fi | |
| validate_rotate_name "$rotate_name" || exit 1 | |
| local config_path="$ROTATE_DIR/$rotate_name" | |
| if [[ ! -f "$config_path" ]]; then | |
| log_error "Configuration not found: $config_path" | |
| exit 1 | |
| fi | |
| # Show what will be removed | |
| log_info "Configuration to remove:" | |
| cat "$config_path" | |
| echo | |
| read -rp "Remove this configuration? (y/N): " confirm | |
| [[ "$confirm" != [yY] ]] && { log_info "Aborted"; exit 0; } | |
| sudo rm -f "$config_path" || { | |
| log_error "Failed to remove configuration" | |
| exit 1 | |
| } | |
| log_success "Removed: $config_path" | |
| } | |
| # Show information about a configuration | |
| info() { | |
| local rotate_name="$1" | |
| if [[ -z "$rotate_name" ]]; then | |
| read -rp "Enter rotation config name: " rotate_name | |
| fi | |
| validate_rotate_name "$rotate_name" || exit 1 | |
| local config_path="$ROTATE_DIR/$rotate_name" | |
| if [[ ! -f "$config_path" ]]; then | |
| log_error "Configuration not found: $config_path" | |
| exit 1 | |
| fi | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "Configuration: $rotate_name" | |
| echo "Path: $config_path" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo | |
| echo "=== Configuration Content ===" | |
| cat "$config_path" | |
| echo | |
| echo "=== Log Files Status ===" | |
| # Extract log paths from config (handles multi-line log definitions) | |
| awk ' | |
| # Collect all lines until we hit the opening brace | |
| /^[^#]/ && !found_brace { | |
| line = line " " $0 | |
| if ($0 ~ /{/) { | |
| found_brace = 1 | |
| gsub(/{/, "", line) | |
| # Split the line into words and print each non-comment word | |
| n = split(line, words) | |
| for (i = 1; i <= n; i++) { | |
| if (words[i] !~ /^#/ && words[i] != "") { | |
| print words[i] | |
| } | |
| } | |
| } | |
| } | |
| ' "$config_path" | while IFS= read -r log; do | |
| if [[ -n "$log" && "$log" != "{" ]]; then | |
| if [[ -f "$log" ]]; then | |
| local size | |
| size=$(du -h "$log" 2>/dev/null | cut -f1) | |
| log_success "$log (size: $size)" | |
| else | |
| log_warn "$log (missing)" | |
| fi | |
| fi | |
| done | |
| echo | |
| # Calculate rotated files statistics | |
| echo "=== Rotated Files Statistics ===" | |
| local total_rotated=0 | |
| local total_size=0 | |
| # Get all log paths into an array | |
| local -a log_paths=() | |
| while IFS= read -r log; do | |
| [[ -n "$log" && "$log" != "{" ]] && log_paths+=("$log") | |
| done < <(awk ' | |
| /^[^#]/ && !found_brace { | |
| line = line " " $0 | |
| if ($0 ~ /{/) { | |
| found_brace = 1 | |
| gsub(/{/, "", line) | |
| n = split(line, words) | |
| for (i = 1; i <= n; i++) { | |
| if (words[i] !~ /^#/ && words[i] != "") { | |
| print words[i] | |
| } | |
| } | |
| } | |
| } | |
| ' "$config_path") | |
| # Process each log path | |
| for log in "${log_paths[@]}"; do | |
| # Check if main log file exists (use sudo for permission-restricted files) | |
| if ! sudo test -f "$log" 2>/dev/null && [[ ! -f "$log" ]]; then | |
| continue | |
| fi | |
| local rotated_count=0 | |
| local rotated_size=0 | |
| local log_dir | |
| local log_base | |
| log_dir=$(dirname "$log") | |
| log_base=$(basename "$log") | |
| # Get all rotated files into an array (use sudo for permission-restricted directories) | |
| local -a rotated_files=() | |
| while IFS= read -r -d '' rotated_file; do | |
| rotated_files+=("$rotated_file") | |
| done < <(sudo find "$log_dir" -maxdepth 1 -type f -name "${log_base}*" -print0 2>/dev/null) | |
| # Calculate stats for rotated files | |
| for rotated_file in "${rotated_files[@]}"; do | |
| # Skip the main log file itself | |
| [[ "$rotated_file" == "$log" ]] && continue | |
| # Get file size in bytes using stat (use sudo for permission-restricted files) | |
| if sudo test -f "$rotated_file" 2>/dev/null; then | |
| local file_size | |
| # Try Linux stat first, then BSD stat (use sudo) | |
| file_size=$(sudo stat -c%s "$rotated_file" 2>/dev/null || sudo stat -f%z "$rotated_file" 2>/dev/null || echo 0) | |
| # Only count if we got a valid size | |
| if [[ "$file_size" =~ ^[0-9]+$ ]]; then | |
| ((rotated_count++)) || true | |
| ((rotated_size += file_size)) || true | |
| fi | |
| fi | |
| done | |
| # Display stats for this log if it has rotated files | |
| if [[ $rotated_count -gt 0 ]]; then | |
| # Convert bytes to MB | |
| local size_mb | |
| size_mb=$(awk "BEGIN {printf \"%.2f\", $rotated_size / 1024 / 1024}") | |
| echo " $log_base: $rotated_count file(s), ${size_mb} MB" | |
| ((total_rotated += rotated_count)) || true | |
| ((total_size += rotated_size)) || true | |
| fi | |
| done | |
| # Show total or no files message | |
| if [[ $total_rotated -gt 0 ]]; then | |
| local total_mb | |
| total_mb=$(awk "BEGIN {printf \"%.2f\", $total_size / 1024 / 1024}") | |
| echo | |
| echo "Total: $total_rotated rotated file(s), ${total_mb} MB" | |
| else | |
| log_info "No rotated files found" | |
| fi | |
| } | |
| # List all configurations | |
| list() { | |
| if [[ ! -d "$ROTATE_DIR" ]]; then | |
| log_error "Logrotate directory not found: $ROTATE_DIR" | |
| exit 1 | |
| fi | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "Logrotate Configurations in $ROTATE_DIR" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| local count=0 | |
| # Use ls with proper error handling | |
| if ls -1 "$ROTATE_DIR" 2>/dev/null | sort | while IFS= read -r file; do | |
| # Skip if it's a directory | |
| [[ -d "$ROTATE_DIR/$file" ]] && continue | |
| echo " • $file" | |
| ((count++)) | |
| done; then | |
| : | |
| fi | |
| # Count files directly | |
| count=$(find "$ROTATE_DIR" -maxdepth 1 -type f 2>/dev/null | wc -l) | |
| echo | |
| if [[ $count -eq 0 ]]; then | |
| log_info "No configurations found" | |
| else | |
| echo "Total: $count configuration(s)" | |
| fi | |
| } | |
| # Test a specific configuration | |
| test() { | |
| local rotate_name="$1" | |
| if [[ -z "$rotate_name" ]]; then | |
| read -rp "Enter rotation config name to test: " rotate_name | |
| fi | |
| validate_rotate_name "$rotate_name" || exit 1 | |
| local config_path="$ROTATE_DIR/$rotate_name" | |
| if [[ ! -f "$config_path" ]]; then | |
| log_error "Configuration not found: $config_path" | |
| exit 1 | |
| fi | |
| test_config "$config_path" | |
| check_root_owner "$config_path" | |
| } | |
| # clean rotated log files | |
| clean() { | |
| require_sudo "$@" | |
| local rotate_name="$1" | |
| if [[ -z "$rotate_name" ]]; then | |
| read -rp "Enter rotation config name: " rotate_name | |
| fi | |
| validate_rotate_name "$rotate_name" || exit 1 | |
| local config_path="$ROTATE_DIR/$rotate_name" | |
| if [[ ! -f "$config_path" ]]; then | |
| log_error "Configuration not found: $config_path" | |
| exit 1 | |
| fi | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "clean Log Files for: $rotate_name" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo | |
| # Get all log paths into an array | |
| local -a log_paths=() | |
| while IFS= read -r log; do | |
| [[ -n "$log" && "$log" != "{" ]] && log_paths+=("$log") | |
| done < <(awk ' | |
| /^[^#]/ && !found_brace { | |
| line = line " " $0 | |
| if ($0 ~ /{/) { | |
| found_brace = 1 | |
| gsub(/{/, "", line) | |
| n = split(line, words) | |
| for (i = 1; i <= n; i++) { | |
| if (words[i] !~ /^#/ && words[i] != "") { | |
| print words[i] | |
| } | |
| } | |
| } | |
| } | |
| ' "$config_path") | |
| if [[ ${#log_paths[@]} -eq 0 ]]; then | |
| log_error "No log paths found in configuration" | |
| exit 1 | |
| fi | |
| # Collect all rotated files | |
| local -a all_files=() | |
| local total_size=0 | |
| local total_count=0 | |
| for log in "${log_paths[@]}"; do | |
| # Check if main log file exists | |
| if ! sudo test -f "$log" 2>/dev/null && [[ ! -f "$log" ]]; then | |
| continue | |
| fi | |
| local log_dir | |
| local log_base | |
| log_dir=$(dirname "$log") | |
| log_base=$(basename "$log") | |
| # Find all rotated files (excluding main log) | |
| while IFS= read -r -d '' rotated_file; do | |
| # Skip the main log file itself | |
| [[ "$rotated_file" == "$log" ]] && continue | |
| if sudo test -f "$rotated_file" 2>/dev/null; then | |
| all_files+=("$rotated_file") | |
| # Get file size | |
| local file_size | |
| file_size=$(sudo stat -c%s "$rotated_file" 2>/dev/null || sudo stat -f%z "$rotated_file" 2>/dev/null || echo 0) | |
| if [[ "$file_size" =~ ^[0-9]+$ ]]; then | |
| ((total_size += file_size)) || true | |
| ((total_count++)) || true | |
| fi | |
| fi | |
| done < <(sudo find "$log_dir" -maxdepth 1 -type f -name "${log_base}*" -print0 2>/dev/null) | |
| done | |
| if [[ ${#all_files[@]} -eq 0 ]]; then | |
| log_info "No rotated files found to clean" | |
| exit 0 | |
| fi | |
| # Show files to be deleted | |
| echo "Files to be deleted:" | |
| for file in "${all_files[@]}"; do | |
| echo " • $file" | |
| done | |
| echo | |
| # Show statistics | |
| local total_mb | |
| total_mb=$(awk "BEGIN {printf \"%.2f\", $total_size / 1024 / 1024}") | |
| log_warn "Total: $total_count file(s), ${total_mb} MB will be deleted" | |
| echo | |
| # Confirm deletion | |
| read -rp "Delete all rotated log files? (y/N): " confirm | |
| [[ "$confirm" != [yY] ]] && { log_info "Aborted"; exit 0; } | |
| # Delete files | |
| local deleted=0 | |
| local failed=0 | |
| for file in "${all_files[@]}"; do | |
| if sudo rm -f "$file" 2>/dev/null; then | |
| ((deleted++)) || true | |
| else | |
| ((failed++)) || true | |
| log_error "Failed to delete: $file" | |
| fi | |
| done | |
| echo | |
| if [[ $failed -eq 0 ]]; then | |
| log_success "Successfully deleted $deleted file(s), freed ${total_mb} MB" | |
| else | |
| log_warn "Deleted $deleted file(s), failed $failed file(s)" | |
| fi | |
| # Ask if user wants to delete main log files as well | |
| echo | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "Main Log Files" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo | |
| # Collect existing main log files | |
| local -a main_logs=() | |
| local main_total_size=0 | |
| for log in "${log_paths[@]}"; do | |
| if sudo test -f "$log" 2>/dev/null || [[ -f "$log" ]]; then | |
| main_logs+=("$log") | |
| local log_size | |
| log_size=$(sudo stat -c%s "$log" 2>/dev/null || sudo stat -f%z "$log" 2>/dev/null || echo 0) | |
| if [[ "$log_size" =~ ^[0-9]+$ ]]; then | |
| ((main_total_size += log_size)) || true | |
| fi | |
| fi | |
| done | |
| if [[ ${#main_logs[@]} -eq 0 ]]; then | |
| log_info "No main log files found" | |
| else | |
| echo "Main log files:" | |
| for log in "${main_logs[@]}"; do | |
| echo " • $log" | |
| done | |
| echo | |
| local main_mb | |
| main_mb=$(awk "BEGIN {printf \"%.2f\", $main_total_size / 1024 / 1024}") | |
| log_warn "Total: ${#main_logs[@]} file(s), ${main_mb} MB" | |
| echo | |
| read -rp "Delete main log files as well? (y/N): " confirm_main | |
| if [[ "$confirm_main" == [yY] ]]; then | |
| local main_deleted=0 | |
| local main_failed=0 | |
| for log in "${main_logs[@]}"; do | |
| if sudo rm -f "$log" 2>/dev/null; then | |
| ((main_deleted++)) || true | |
| else | |
| ((main_failed++)) || true | |
| log_error "Failed to delete: $log" | |
| fi | |
| done | |
| echo | |
| if [[ $main_failed -eq 0 ]]; then | |
| log_success "Successfully deleted $main_deleted main log file(s), freed ${main_mb} MB" | |
| else | |
| log_warn "Deleted $main_deleted main log file(s), failed $main_failed file(s)" | |
| fi | |
| else | |
| log_info "Main log files kept intact" | |
| fi | |
| fi | |
| } | |
| # Tar rotated log files | |
| tar_logs() { | |
| local rotate_name="$1" | |
| local target_path="${2:-}" | |
| if [[ -z "$rotate_name" ]]; then | |
| read -rp "Enter rotation config name: " rotate_name | |
| fi | |
| validate_rotate_name "$rotate_name" || exit 1 | |
| local config_path="$ROTATE_DIR/$rotate_name" | |
| if [[ ! -f "$config_path" ]]; then | |
| log_error "Configuration not found: $config_path" | |
| exit 1 | |
| fi | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "Archive Log Files for: $rotate_name" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo | |
| # Determine target file path | |
| local tar_file | |
| if [[ -z "$target_path" ]]; then | |
| # Default: current directory | |
| tar_file="$(pwd)/${rotate_name}.tar.gz" | |
| elif [[ -d "$target_path" ]]; then | |
| # Directory provided, append filename | |
| tar_file="${target_path%/}/${rotate_name}.tar.gz" | |
| else | |
| # Full path provided | |
| tar_file="$target_path" | |
| fi | |
| # Check if target file already exists | |
| if [[ -f "$tar_file" ]]; then | |
| log_warn "Target file already exists: $tar_file" | |
| read -rp "Overwrite? (y/N): " confirm | |
| [[ "$confirm" != [yY] ]] && { log_info "Aborted"; exit 0; } | |
| fi | |
| # Verify target directory is writable | |
| local tar_dir | |
| tar_dir=$(dirname "$tar_file") | |
| if [[ ! -d "$tar_dir" ]]; then | |
| log_error "Target directory does not exist: $tar_dir" | |
| exit 1 | |
| fi | |
| if [[ ! -w "$tar_dir" ]]; then | |
| log_error "Target directory is not writable: $tar_dir" | |
| exit 1 | |
| fi | |
| # Get all log paths | |
| local -a log_paths=() | |
| while IFS= read -r log; do | |
| [[ -n "$log" && "$log" != "{" ]] && log_paths+=("$log") | |
| done < <(awk ' | |
| /^[^#]/ && !found_brace { | |
| line = line " " $0 | |
| if ($0 ~ /{/) { | |
| found_brace = 1 | |
| gsub(/{/, "", line) | |
| n = split(line, words) | |
| for (i = 1; i <= n; i++) { | |
| if (words[i] !~ /^#/ && words[i] != "") { | |
| print words[i] | |
| } | |
| } | |
| } | |
| } | |
| ' "$config_path") | |
| if [[ ${#log_paths[@]} -eq 0 ]]; then | |
| log_error "No log paths found in configuration" | |
| exit 1 | |
| fi | |
| # Collect all log files (including main log and rotated files) | |
| local -a all_files=() | |
| local total_size=0 | |
| for log in "${log_paths[@]}"; do | |
| # Check if main log file exists | |
| if ! sudo test -f "$log" 2>/dev/null && [[ ! -f "$log" ]]; then | |
| log_warn "Log file not found: $log" | |
| continue | |
| fi | |
| local log_dir | |
| local log_base | |
| log_dir=$(dirname "$log") | |
| log_base=$(basename "$log") | |
| # Find all log files (including main and rotated) | |
| while IFS= read -r -d '' log_file; do | |
| if sudo test -f "$log_file" 2>/dev/null; then | |
| all_files+=("$log_file") | |
| # Get file size | |
| local file_size | |
| file_size=$(sudo stat -c%s "$log_file" 2>/dev/null || sudo stat -f%z "$log_file" 2>/dev/null || echo 0) | |
| if [[ "$file_size" =~ ^[0-9]+$ ]]; then | |
| ((total_size += file_size)) || true | |
| fi | |
| fi | |
| done < <(sudo find "$log_dir" -maxdepth 1 -type f -name "${log_base}*" -print0 2>/dev/null) | |
| done | |
| if [[ ${#all_files[@]} -eq 0 ]]; then | |
| log_error "No log files found to archive" | |
| exit 1 | |
| fi | |
| # Show statistics | |
| local total_mb | |
| total_mb=$(awk "BEGIN {printf \"%.2f\", $total_size / 1024 / 1024}") | |
| log_info "Files to archive: ${#all_files[@]}" | |
| log_info "Total size: ${total_mb} MB" | |
| log_info "Target: $tar_file" | |
| echo | |
| # Create temporary file list | |
| local file_list | |
| file_list=$(mktemp) | |
| trap 'rm -f "$file_list"' EXIT | |
| printf '%s\n' "${all_files[@]}" > "$file_list" | |
| # Create tar.gz archive | |
| log_info "Creating archive..." | |
| if sudo tar -czf "$tar_file" -T "$file_list" --absolute-names 2>/dev/null; then | |
| # Change owner to current user | |
| sudo chown "$SELF_UID:$SELF_GID" "$tar_file" || { | |
| log_warn "Could not change ownership of archive" | |
| } | |
| # Get archive size | |
| local archive_size | |
| archive_size=$(stat -c%s "$tar_file" 2>/dev/null || stat -f%z "$tar_file" 2>/dev/null || echo 0) | |
| local archive_mb | |
| archive_mb=$(awk "BEGIN {printf \"%.2f\", $archive_size / 1024 / 1024}") | |
| # Calculate compression ratio | |
| local ratio | |
| if [[ $total_size -gt 0 ]]; then | |
| ratio=$(awk "BEGIN {printf \"%.1f\", ($total_size - $archive_size) * 100.0 / $total_size}") | |
| else | |
| ratio="0.0" | |
| fi | |
| echo | |
| log_success "Archive created successfully" | |
| log_info "Archive size: ${archive_mb} MB (${ratio}% compression)" | |
| log_info "Location: $tar_file" | |
| else | |
| log_error "Failed to create archive" | |
| exit 1 | |
| fi | |
| } | |
| # Show usage | |
| usage() { | |
| cat <<EOF | |
| Usage: $0 COMMAND [OPTIONS] | |
| Commands: | |
| install [NAME] Install a new logrotate configuration | |
| uninstall [NAME] Remove an existing logrotate configuration | |
| info [NAME] Show details about a configuration | |
| list List all logrotate configurations | |
| test [NAME] Test a configuration without applying it | |
| clean [NAME] Delete all rotated log files for a configuration | |
| tar [NAME] [PATH] Archive all log files to tar.gz (default: ./{NAME}.tar.gz) | |
| Options: | |
| NAME Name of the logrotate configuration | |
| PATH Target path for tar archive (optional) | |
| Examples: | |
| $0 install myapp | |
| $0 info myapp | |
| $0 list | |
| $0 clean myapp | |
| $0 tar myapp | |
| $0 tar myapp /backup/logs.tar.gz | |
| $0 tar myapp /backup/ | |
| $0 uninstall myapp | |
| EOF | |
| } | |
| # Main | |
| main() { | |
| local command="${1:-}" | |
| local arg="${2:-}" | |
| local arg2="${3:-}" | |
| case "$command" in | |
| install) | |
| install "$arg" | |
| ;; | |
| uninstall) | |
| uninstall "$arg" | |
| ;; | |
| info) | |
| info "$arg" | |
| ;; | |
| list) | |
| list | |
| ;; | |
| test) | |
| test "$arg" | |
| ;; | |
| clean) | |
| clean "$arg" | |
| ;; | |
| tar) | |
| tar_logs "$arg" "$arg2" | |
| ;; | |
| help|--help|-h) | |
| usage | |
| ;; | |
| "") | |
| log_error "No command specified" | |
| echo | |
| usage | |
| exit 1 | |
| ;; | |
| *) | |
| log_error "Unknown command: $command" | |
| echo | |
| usage | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment