diff --git a/README.md b/README.md index 07a3112..245170f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # šŸš€ DevOps Bash Toolkit Assessment ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/your-username/devops-bash-toolkit/grade.yml) @@ -228,3 +229,13 @@ logs/app.log * Handle script failures gracefully **Submission link:** [CLICK HERE](https://forms.gle/jrhpKjXsQXZxLopN6) +======= +This assignment tests my DevOps fundamentals: + + Bash scripting + Git workflow (branching, commits, pull requests) + Automation mindset + System monitoring and logging + +I will build a real-world automation toolkit and submit it via a Pull Request (PR). +>>>>>>> 3e1f32f (feat: complete bash scripts) diff --git a/bash-assignment b/bash-assignment new file mode 160000 index 0000000..bd24f9c --- /dev/null +++ b/bash-assignment @@ -0,0 +1 @@ +Subproject commit bd24f9c88ae29f8d2f637359b6ea9924919f3ae3 diff --git a/run_all.sh b/run_all.sh new file mode 100755 index 0000000..ec386ba --- /dev/null +++ b/run_all.sh @@ -0,0 +1,224 @@ +#!/bin/bash +set -euo pipefail + +# Create necessary directories +mkdir -p scripts logs + +# Global log file +logfile="logs/app.log" + +# Function to log actions +log_action() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$logfile" +} + +# Function to display banner +show_banner() { + cat << "EOF" +======================================== + SYSTEM MANAGEMENT DASHBOARD +======================================== +EOF +} + +# Function to check if scripts exist +check_scripts() { + local missing=0 + + if [[ ! -f "scripts/user_info.sh" ]]; then + echo "Warning: scripts/user_info.sh not found" >&2 + missing=1 + fi + if [[ ! -f "scripts/system_report.sh" ]]; then + echo "Warning: scripts/system_check.sh not found" >&2 + missing=1 + fi + if [[ ! -f "scripts/file_manager.sh" ]]; then + echo "Warning: scripts/file_manager.sh not found" >&2 + missing=1 + fi + if [[ ! -f "scripts/backup.sh" ]]; then + echo "Warning: scripts/backup.sh not found" >&2 + missing=1 + fi + if [[ ! -f "scripts/process_monitor.sh" ]]; then + echo "Warning: scripts/process_monitor.sh not found" >&2 + missing=1 + fi + + return $missing +} + +# Function to run all scripts +run_all() { + echo "" + echo "=========================================" + echo "RUNNING ALL SCRIPTS" + echo "=========================================" + log_action "Starting 'Run All' operation" + + # Run user_info.sh + echo -e "\n[1/5] Running user_info.sh..." + if [[ -f "scripts/user_info.sh" ]]; then + bash scripts/user_info.sh + log_action "Executed: user_info.sh" + else + echo "Skipped: user_info.sh not found" + log_action "FAILED: user_info.sh not found" + fi + + # Run system_check.sh + echo -e "\n[2/5] Running system_check.sh..." + if [[ -f "scripts/system_check.sh" ]]; then + bash scripts/system_check.sh + log_action "Executed: system_check.sh" + else + echo "Skipped: system_check.sh not found" + log_action "FAILED: system_check.sh not found" + fi + + # Run file_manager.sh (example: create a test file) + echo -e "\n[3/5] Running file_manager.sh..." + if [[ -f "scripts/file_manager.sh" ]]; then + echo " Creating test file..." + bash scripts/file_manager.sh create test_$(date +%s).txt 2>/dev/null || true + bash scripts/file_manager.sh list 2>/dev/null || true + log_action "Executed: file_manager.sh" + else + echo "Skipped: file_manager.sh not found" + log_action "FAILED: file_manager.sh not found" + fi + + # Run backup.sh + echo -e "\n[4/5] Running backup.sh..." + if [[ -f "scripts/backup.sh" ]]; then + # Backup current directory (or specify a test directory) + mkdir -p test_backup_dir + echo "Test content" > test_backup_dir/test.txt + bash scripts/backup.sh test_backup_dir 2>/dev/null || true + log_action "Executed: backup.sh" + else + echo "Skipped: backup.sh not found" + log_action "FAILED: backup.sh not found" + fi + + # Run process_monitor.sh + echo -e "\n[5/5] Running process_monitor.sh..." + if [[ -f "scripts/process_monitor.sh" ]]; then + bash scripts/process_monitor.sh ssh 2>/dev/null || true + log_action "Executed: process_monitor.sh" + else + echo "Skipped: process_monitor.sh not found" + log_action "FAILED: process_monitor.sh not found" + fi + + echo -e "\nāœ“ All scripts completed!" + log_action "Completed 'Run All' operation" +} + +# Function to run system check +system_check() { + echo "" + echo "=========================================" + echo "SYSTEM CHECK" + echo "=========================================" + log_action "Starting 'System Check' operation" + + if [[ -f "scripts/system_check.sh" ]]; then + bash scripts/system_check.sh + log_action "Executed: system_check.sh" + else + echo "Error: scripts/system_check.sh not found" + log_action "FAILED: system_check.sh not found" + return 1 + fi + + log_action "Completed 'System Check' operation" +} + +# Function to run backup +run_backup() { + echo "" + echo "=========================================" + echo "BACKUP OPERATION" + echo "=========================================" + log_action "Starting 'Backup' operation" + + # Prompt for directory to backup + read -p "Enter directory path to backup: " backup_dir + + if [[ -z "$backup_dir" ]]; then + echo "Error: No directory provided. Using current directory." + backup_dir="." + fi + + if [[ -f "scripts/backup.sh" ]]; then + bash scripts/backup.sh "$backup_dir" + log_action "Executed: backup.sh for directory '$backup_dir'" + else + echo "Error: scripts/backup.sh not found" + log_action "FAILED: backup.sh not found" + return 1 + fi + + log_action "Completed 'Backup' operation" +} + +# Function to display menu +show_menu() { + echo "" + echo "=========================================" + echo " MAIN MENU" + echo "=========================================" + echo "1. Run All Scripts" + echo "2. System Check (Disk/Memory/CPU)" + echo "3. Backup Directory" + echo "4. Exit" + echo "=========================================" + echo -n "Enter your choice [1-4]: " +} + +# Main program loop +main() { + show_banner + + # Check if scripts directory has required scripts + check_scripts + + while true; do + show_menu + read choice + + case "$choice" in + 1) + run_all + ;; + 2) + system_check + ;; + 3) + run_backup + ;; + 4) + echo "" + echo "Exiting... Goodbye!" + log_action "User exited the application" + exit 0 + ;; + *) + echo "Error: Invalid choice. Please enter 1, 2, 3, or 4" >&2 + log_action "ERROR: Invalid menu choice '$choice'" + ;; + esac + + # Pause before showing menu again + echo "" + read -p "Press Enter to continue..." + done +} + +# Trap Ctrl+C for clean exit +trap 'echo ""; echo "Interrupted by user"; log_action "Application interrupted (SIGINT)"; exit 0' INT + +# Run main function +main diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..bc985c3 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -euo pipefail + +# Create necessary directories +mkdir -p backups logs + +logfile="logs/backup.log" + +# Function to log actions +log_action() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$logfile" +} + +# Function to display usage +usage() { + echo "Usage: $0 " + echo "Example: $0 /home/user/documents" + exit 1 +} + +# Check if directory argument is provided +if [[ $# -ne 1 ]]; then + echo "Error: Please provide exactly one directory path" >&2 + usage +fi + +source_dir="$1" + +# Validate that directory exists +if [[ ! -d "$source_dir" ]]; then + echo "Error: Directory '$source_dir' does not exist" >&2 + log_action "FAILED: Backup - Directory '$source_dir' not found" + exit 1 +fi + +# Create timestamp and backup filename +timestamp=$(date '+%Y%m%d_%H%M%S') +backup_name="backup_${timestamp}.tar.gz" +backup_path="backups/${backup_name}" + +# Create compressed backup +echo "Creating backup of: $source_dir" +log_action "Starting backup of '$source_dir'" + +if tar -czf "$backup_path" -C "$(dirname "$source_dir")" "$(basename "$source_dir")" 2>/dev/null; then + backup_size=$(du -h "$backup_path" | cut -f1) + echo "āœ“ Backup created: $backup_path ($backup_size)" + log_action "SUCCESS: Created $backup_name (Size: $backup_size) from '$source_dir'" +else + echo "Error: Failed to create backup" >&2 + log_action "FAILED: Could not create backup of '$source_dir'" + exit 1 +fi + +# Keep only the last 5 backups (delete older ones) +echo "Checking for old backups to remove..." +log_action "Checking backups in 'backups/' directory" + +# List backups sorted by name (timestamp descending), skip first 5, delete rest +old_backups=$(ls -1 backups/backup_*.tar.gz 2>/dev/null | sort -r | tail -n +6) + +if [[ -n "$old_backups" ]]; then + while IFS= read -r old_backup; do + rm "$old_backup" + echo " Removed old backup: $(basename "$old_backup")" + log_action "REMOVED: $(basename "$old_backup") - Keeping only last 5 backups" + done <<< "$old_backups" +else + echo " No old backups to remove (less than 5 total)" +fi + +# Show current backups count +backup_count=$(ls -1 backups/backup_*.tar.gz 2>/dev/null | wc -l) +echo "āœ“ Current backups in 'backups/': $backup_count (max: 5)" +log_action "Backup cleanup complete. Total backups kept: $backup_count" + +echo "Backup process finished successfully!" diff --git a/scripts/blue.txt b/scripts/blue.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/file_manager.sh b/scripts/file_manager.sh new file mode 100755 index 0000000..613b7cf --- /dev/null +++ b/scripts/file_manager.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -euo pipefail + +# Create logs directory if it doesn't exist +mkdir -p logs + +logfile="logs/file_manager.log" + +# Function to log actions +log_action() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$logfile" +} + +# Function to display usage +usage() { + echo "Usage: $0 " + echo "Commands:" + echo " create - Create a new file" + echo " delete - Delete a file" + echo " list [directory] - List files (default: current directory)" + echo " rename - Rename a file" + exit 1 +} + +# Check if at least command is provided +if [[ $# -lt 1 ]]; then + usage +fi + +command="$1" + +case "$command" in + create) + if [[ $# -ne 2 ]]; then + echo "Error: 'create' requires a filename" >&2 + usage + fi + filename="$2" + + if [[ -e "$filename" ]]; then + echo "Error: File '$filename' already exists. Not overwriting." >&2 + log_action "FAILED: create $filename - File already exists" + exit 1 + fi + + touch "$filename" + echo "Created: $filename" + log_action "SUCCESS: create $filename" + ;; + + delete) + if [[ $# -ne 2 ]]; then + echo "Error: 'delete' requires a filename" >&2 + usage + fi + filename="$2" + + if [[ ! -f "$filename" ]]; then + echo "Error: File '$filename' does not exist" >&2 + log_action "FAILED: delete $filename - File not found" + exit 1 + fi + + rm "$filename" + echo "Deleted: $filename" + log_action "SUCCESS: delete $filename" + ;; + + list) + target="${2:-.}" + + if [[ ! -d "$target" ]]; then + echo "Error: Directory '$target' does not exist" >&2 + log_action "FAILED: list $target - Directory not found" + exit 1 + fi + + echo "Listing files in: $target" + ls -la "$target" + log_action "SUCCESS: list $target" + ;; + + rename) + if [[ $# -ne 3 ]]; then + echo "Error: 'rename' requires old and new filename" >&2 + usage + fi + oldname="$2" + newname="$3" + + if [[ ! -f "$oldname" ]]; then + echo "Error: File '$oldname' does not exist" >&2 + log_action "FAILED: rename $oldname to $newname - Source not found" + exit 1 + fi + + if [[ -e "$newname" ]]; then + echo "Error: Target '$newname' already exists. Not overwriting." >&2 + log_action "FAILED: rename $oldname to $newname - Target exists" + exit 1 + fi + + mv "$oldname" "$newname" + echo "Renamed: $oldname -> $newname" + log_action "SUCCESS: rename $oldname to $newname" + ;; + + *) + echo "Error: Unknown command '$command'" >&2 + usage + ;; +esac diff --git a/scripts/logs/app.log b/scripts/logs/app.log new file mode 100644 index 0000000..30ac18c --- /dev/null +++ b/scripts/logs/app.log @@ -0,0 +1,2 @@ +2026-04-15 05:56:41 - Running all scripts +2026-04-15 05:56:47 - Running system check diff --git a/scripts/logs/backup.log b/scripts/logs/backup.log new file mode 100644 index 0000000..ad5d867 --- /dev/null +++ b/scripts/logs/backup.log @@ -0,0 +1 @@ +2026-04-15 05:48:18 - FAILED: Backup - Directory '/home/user/documents' not found diff --git a/scripts/logs/file_manager.log b/scripts/logs/file_manager.log new file mode 100644 index 0000000..8b96398 --- /dev/null +++ b/scripts/logs/file_manager.log @@ -0,0 +1,2 @@ +2026-04-15 05:43:17 - SUCCESS: create blue.txt +2026-04-15 05:43:43 - FAILED: create blue.txt - File already exists diff --git a/scripts/logs/system_report_20260415_053744.log b/scripts/logs/system_report_20260415_053744.log new file mode 100644 index 0000000..a5afb09 --- /dev/null +++ b/scripts/logs/system_report_20260415_053744.log @@ -0,0 +1,30 @@ +========================================== +System Report - 2026-04-15 05:37:44 +========================================== +\n--- DISK USAGE --- +Filesystem Size Used Avail Use% Mounted on +tmpfs 387M 2.6M 385M 1% /run +/dev/sda1 217G 13G 193G 7% / +tmpfs 1.9G 0 1.9G 0% /dev/shm +tmpfs 5.0M 4.0K 5.0M 1% /run/lock +/dev/sda2 252M 209M 24M 90% /boot +/dev/sda5 265G 319M 251G 1% /home +tmpfs 387M 128K 387M 1% /run/user/1000 +\n--- MEMORY USAGE --- + total used free shared buff/cache available +Mem: 3868 2181 256 35 1431 1566 +Swap: 9154 216 8938 +\n--- CPU LOAD --- + 05:37:44 up 12:11, 1 user, load average: 0.03, 0.09, 0.14 +\n--- PROCESS COUNT --- +Total running processes: 324 +\n--- TOP 5 MEMORY-CONSUMING PROCESSES --- +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +osboxes 5845 0.9 14.7 3710824 585720 ? Sl Apr14 4:54 /snap/firefox/8107/usr/lib/firefox/firefox +suricata 1095 1.5 9.9 864812 392296 ? Ssl Apr14 11:09 /usr/bin/suricata --af-packet -c /etc/suricata/suricata.yaml --pidfile /run/suricata.pid --user suricata --group suricata +osboxes 6558 0.2 5.9 2779044 237240 ? Sl Apr14 1:13 /snap/firefox/8107/usr/lib/firefox/firefox -contentproc -isForBrowser -prefsHandle 0:42890 -prefMapHandle 1:283274 -jsInitHandle 2:156136 -parentBuildID 20260404010525 -sandboxReporter 3 -chrootClient 4 -ipcHandle 5 -initialChannelId {291156e2-57d5-450c-83f9-00d255c18f09} -parentPid 5845 -crashReporter 6 -crashHelper 7 -greomni /snap/firefox/8107/usr/lib/firefox/omni.ja -appomni /snap/firefox/8107/usr/lib/firefox/browser/omni.ja -appDir /snap/firefox/8107/usr/lib/firefox/browser 9 tab +osboxes 2273 1.1 5.4 4484428 217880 ? Ssl Apr14 8:31 /usr/bin/gnome-shell +osboxes 6400 0.1 4.8 2697048 192652 ? Sl Apr14 0:56 /snap/firefox/8107/usr/lib/firefox/firefox -contentproc -isForBrowser -prefsHandle 0:41963 -prefMapHandle 1:283274 -jsInitHandle 2:156136 -parentBuildID 20260404010525 -sandboxReporter 3 -chrootClient 4 -ipcHandle 5 -initialChannelId {0092547a-5d8c-44d1-bdf1-dd51a4d1b38b} -parentPid 5845 -crashReporter 6 -crashHelper 7 -greomni /snap/firefox/8107/usr/lib/firefox/omni.ja -appomni /snap/firefox/8107/usr/lib/firefox/browser/omni.ja -appDir /snap/firefox/8107/usr/lib/firefox/browser 7 tab +\n========================================== +Report saved to: logs/system_report_20260415_053744.log +========================================== diff --git a/scripts/process_monitor.sh b/scripts/process_monitor.sh new file mode 100755 index 0000000..88a0916 --- /dev/null +++ b/scripts/process_monitor.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -euo pipefail + +mkdir -p logs +logfile="logs/process_monitor.log" + +services=("nginx" "ssh" "docker") + +log_action() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$logfile" +} + +usage() { + echo "Usage: $0 " + echo "Monitor: ${services[*]}" + exit 1 +} + +[[ $# -ne 1 ]] && usage + +process="$1" + +# Check if in services array +valid=0 +for s in "${services[@]}"; do + [[ "$s" == "$process" ]] && valid=1 && break +done +[[ $valid -eq 0 ]] && echo "Error: $process not monitored" && usage + +# Check and act +if pgrep -x "$process" >/dev/null 2>&1; then + echo "Running" + log_action "$process - Running" +else + echo "Stopped" + log_action "$process - Stopped" + + # Simulate restart + echo "Attempting restart..." + log_action "$process - Restart attempted (simulated)" + echo "Restarted" + log_action "$process - Restarted (simulated)" +fi diff --git a/scripts/system_check.sh b/scripts/system_check.sh new file mode 100755 index 0000000..aebb691 --- /dev/null +++ b/scripts/system_check.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Create logs directory if it doesn't exist +mkdir -p logs + +# Generate timestamp for log file +timestamp=$(date +"%Y%m%d_%H%M%S") +logfile="logs/system_report_${timestamp}.log" + +# Function to log to both terminal and file +log() { + echo "$1" | tee -a "$logfile" +} + +# Start report +log "==========================================" +log "System Report - $(date '+%Y-%m-%d %H:%M:%S')" +log "==========================================" + +# 1. Disk usage +log "\n--- DISK USAGE ---" +df -h | tee -a "$logfile" + +# Check disk usage warning (80% threshold) +disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//') +if [[ -n "$disk_usage" ]] && (( disk_usage > 80 )); then + log "\nāš ļø WARNING: Disk usage is at ${disk_usage}% (exceeds 80%)" +fi + +# 2. Memory usage +log "\n--- MEMORY USAGE ---" +free -m | tee -a "$logfile" + +# 3. CPU load +log "\n--- CPU LOAD ---" +uptime | tee -a "$logfile" + +# 4. Count total running processes +log "\n--- PROCESS COUNT ---" +process_count=$(ps aux --no-headers | wc -l) +log "Total running processes: $process_count" + +# 5. Top 5 memory-consuming processes +log "\n--- TOP 5 MEMORY-CONSUMING PROCESSES ---" +ps aux --sort=-%mem | head -6 | tee -a "$logfile" + +log "\n==========================================" +log "Report saved to: $logfile" +log "==========================================" + + diff --git a/scripts/user_info.sh b/scripts/user_info.sh new file mode 100755 index 0000000..0974bd9 --- /dev/null +++ b/scripts/user_info.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -euo pipefail + +# Create logs directory if it doesn't exist +mkdir -p logs + +# Redirect all output to both terminal and log file +exec > >(tee -a logs/user_info.log) 2>&1 + +# Prompt for user info +read -p "Enter your name: " name +read -p "Enter your age: " age +read -p "Enter your country: " country + +# Validate age is numeric +if ! [[ "$age" =~ ^[0-9]+$ ]]; then + echo "Error: Age must be a number." >&2 + exit 1 +fi + +# Determine age category +if (( age < 18 )); then + category="Minor" +elif (( age <= 65 )); then + category="Adult" +else + category="Senior" +fi + +# Output greeting +echo "Hello $name from $country! You are an $category ($age years old)." + +