Category: Dev Stuff

  • Garbage Sysadmin: Easily Make CIFS Mounts

    Garbage Sysadmin: Easily Make CIFS Mounts

    I’ve been rebuilding my Raspberry Pi collection from scratch, and moving from Ubuntu Server to Debian/Raspbian Bookworm. One of the tasks that I quickly tried to automate was reconnecting my CIFS mounts. I wanted to do it better, and came across this method, with the help of ChatGPT, to mount them at boot:

    #!/bin/bash
    
    # Check if the script is run as root
    if [[ $EUID -ne 0 ]]; then
       echo "This script must be run as root"
       exit 1
    fi
    
    # Check for correct number of arguments
    if [ "$#" -ne 4 ]; then
        echo "Usage: $0 <RemoteDirectory> <MountDirectory> <Username> <Password>"
        exit 1
    fi
    
    REMOTE_DIR="$1"
    MOUNT_DIR="$2"
    USERNAME="$3"
    PASSWORD="$4"
    CREDENTIALS_PATH="/etc/samba/credentials-$(basename "$MOUNT_DIR")"
    
    # Escape the mount directory for systemd
    UNIT_NAME=$(systemd-escape -p --suffix=mount "$MOUNT_DIR")
    
    # Create mount directory
    mkdir -p "$MOUNT_DIR"
    
    # Create credentials file
    touch "$CREDENTIALS_PATH"
    echo "username=$USERNAME" > "$CREDENTIALS_PATH"
    echo "password=$PASSWORD" >> "$CREDENTIALS_PATH"
    chmod 600 "$CREDENTIALS_PATH"
    
    # Create systemd unit file
    UNIT_FILE_PATH="/etc/systemd/system/$UNIT_NAME"
    echo "[Unit]
    Description=Mount Share at $MOUNT_DIR
    After=network-online.target
    Wants=network-online.target
    
    [Mount]
    What=$REMOTE_DIR
    Where=$MOUNT_DIR
    Type=cifs
    Options=_netdev,iocharset=utf8,file_mode=0777,dir_mode=0777,credentials=$CREDENTIALS_PATH
    TimeoutSec=30
    
    [Install]
    WantedBy=multi-user.target" > "$UNIT_FILE_PATH"
    
    # Reload systemd, enable and start the unit
    systemctl daemon-reload
    systemctl enable "$UNIT_NAME"
    systemctl start "$UNIT_NAME"
    
    echo "Mount setup complete. Mounted $REMOTE_DIR at $MOUNT_DIR"Code language: Bash (bash)

    I’m sure this is totally insecure and a terrible idea, but it works for me so back off, buddy!

    Please don’t follow me as an example of what to do, but take this code for anything you need.

  • Bash Script: Calculate before/after 2: Calculate Harder

    Bash Script: Calculate before/after 2: Calculate Harder

    As an update, or an evolution of my earlier script that did some simple math for me, I’ve made one that will full-on test a URL while I’m making changes to see what the impact performance is of my updates.

    $ abtesturl.sh --url=https://example.com/ --count=10
    Press any key to run initial tests...
    Initial average TTFB: 3.538 seconds
    Press any key to re-run tests...
    
    Running second test...
    Second average TTFB: 1.975 seconds
    Before TTFB: 3.538 seconds
    After TTFB: 1.975 seconds
    Change in TTFB: -1.563 seconds
    Percentage Change: -44.00%Code language: JavaScript (javascript)

    It makes it much simpler to gather data to write reports or figure out of a change is worth the effort.

    Well, that’s about it so here’s the script:

    #!/bin/bash
    
    function show_usage() {
    	echo "Usage: $0 --url=<URL> [--count=<number of requests>]"
    	echo "  --url        Specifies the URL to test."
    	echo "  --count      Optional. Specifies the number of requests to send. Default is 6."
    	echo
    	echo "Example: $0 --url=https://example.com/ --count=5"
    	exit
    }
    
    function average_ttfb() {
    	local URL=""
    	local COUNT=6 # Default COUNT to 6 if not supplied
    	local CURL_OPTS="-s"
    
    	# Parse arguments
    	for arg in "$@"; do
    		case $arg in
    			--url=*)
    			URL="${arg#*=}"
    			shift # Remove argument from processing
    			;;
    		--count=*)
    			COUNT="${arg#*=}"
    			shift # Remove argument from processing
    			;;
    		*)
    			# Unknown option
    			;;
    		esac
    	done
    
    	if [[ -z "$URL" ]]; then
    		exit 1
    	fi
    
    	local total_time=0
    	local count_success=0
    
    	for ((i=1; i<=COUNT; i++))
    	do
    		# Perform the curl command, extracting the time to first byte
    		ttfb=$(curl $CURL_OPTS -o /dev/null -w "%{time_starttransfer}\n" $URL)
    		
    		# Check if the curl command was successful
    		if [ $? -eq 0 ]; then
    			total_time=$(echo "$total_time + $ttfb" | bc)
    			((count_success++))
    		else
    			echo "Request $i failed." >&2
    		fi
    	done
    
    	if [ $count_success -eq 0 ]; then
    		echo "All requests failed." >&2
    		return 1
    	fi
    
    	# Calculate the average TTFB
    	average_time=$(echo "scale=3; $total_time / $count_success" | bc)
    	echo $average_time # This line now simply outputs the average time
    }
    
    function ab_test_ttfb() {
    	# Run initial test
    	read -p "Press any key to run initial tests..." -n 1 -r
    	initial_ttfb=$(set -e; average_ttfb "$@"; set +e)
    	echo "Initial average TTFB: $initial_ttfb seconds"
    	
    	# Wait for user input to proceed
    	read -p "Press any key to re-run tests..." -n 1 -r
    	echo # Move to a new line
    
    	# Run second test
    	echo "Running second test..."
    	second_ttfb=$(average_ttfb "$@")
    	echo "Second average TTFB: $second_ttfb seconds"
    
    	# Calculate and output the difference and percentage change
    	difference=$(echo "$second_ttfb - $initial_ttfb" | bc)
    	percent_change=$(echo "scale=2; ($difference / $initial_ttfb) * 100" | bc)
    
    	echo "Before TTFB: $initial_ttfb seconds"
    	echo "After TTFB: $second_ttfb seconds"
    	echo "Change in TTFB: $difference seconds"
    	echo "Percentage Change: $percent_change%"
    }
    
    # Check if help is requested or no arguments are provided
    if [[ " $* " == *" --help "* ]] || [[ "$#" -eq 0 ]]; then
    	show_usage
    fi
    
    # Check if --url is in the arguments
    url_present=false
    for arg in "$@"; do
    	if [[ $arg == --url=* ]]; then
    		url_present=true
    		break
    	fi
    done
    
    if [ "$url_present" = false ]; then
    	echo "Error: --url argument is required."
    	show_usage
    fi
    
    ab_test_ttfb "$@"Code language: Bash (bash)

    Don’t break anything!

  • Matrix Reimagined: Crafting Digital Rain with Bash and ChatGPT

    Matrix Reimagined: Crafting Digital Rain with Bash and ChatGPT

    Just for fun, and I have no idea why I thought about it, I decided to work with ChatGPT (4) to build a simple bash-based version of the Matrix Digital Rain. I know there’s already better versions, like cmatrix, but we do not do things because they are easy. We do them because we are bored.

    I’ve asked ChatGPT to heavily comment the code for us so that we can see exactly what’s going on:

    #!/bin/bash
    
    # This script creates a Matrix-style falling text effect in the terminal.
    
    # Define strings for extra characters (Japanese Katakana) and extended ASCII characters
    extra_chars="カキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン"
    extended_ascii="│┤┐└┴┬├─┼┘┌≡"
    
    # Define arrays of color codes for a fading green color effect, and a static color
    fade_colors=('\033[38;2;0;255;0m' '\033[38;2;0;192;0m' '\033[38;2;0;128;0m' '\033[38;2;0;64;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;16;0m' '\033[38;2;0;8;0m') # Fading green colors
    static_color='\033[38;2;0;0;0m' # Static dark green color
    white_bold='\033[1;37m' # White and bold for the primary character
    
    # Get terminal dimensions
    COLUMNS=$(tput cols) # Number of columns in the terminal
    ROWS=$(tput lines) # Number of rows in the terminal
    
    
    # Hide the cursor for a cleaner effect and clear the screen
    echo -ne '\033[?25l'
    clear
    
    # Function to generate a random character from the set of extra characters and extended ASCII
    random_char() {
    	local chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789${extra_chars}${extended_ascii}"
    	echo -n "${chars:RANDOM%${#chars}:1}"
    }
    
    # Generate a list of 1000 random characters
    random_chars=""
    for (( i=0; i<1000; i++ )); do
    	random_chars+=$(random_char) # Add a random character to the end of the string
    done
    
    # Initialize a counter for cycling through the random characters
    char_counter=0 # Counter for cycling through the random characters
    
    # Initialize arrays to keep track of the position and trail characters of each column
    positions=() # Array to store the current position in each column
    trail_chars=() # Array to store the trail characters in each column
    for (( c=1; c<=COLUMNS; c++ )); do
    	positions[$c]=$((RANDOM % ROWS)) # Random starting position for each column
    	trail_chars[$c]="" # Start with an empty trail for each column
    done
    
    # Function to update the display with the falling text effect
    update_line() {
    	local last_pos=0  # Track the last position to optimize cursor movement
    
    	for (( c=1; c<=COLUMNS; c++ )); do
    		# Randomly skip updating some columns to create a dynamic effect
    		if [ $((RANDOM % 4)) -ne 0 ]; then
    			continue
    		fi
    
    		local new_char=${random_chars:$char_counter:1} # Select the next character from the random string
    		char_counter=$(( (char_counter + 1) % 1000 )) # Update the counter, cycling back after 1000
    
    		local pos=${positions[$c]} # Current position in this column
    		local trail=${trail_chars[$c]} # Current trail of characters in this column
    
    		trail_chars[$c]="${new_char}${trail:0:$((ROWS - 1))}" # Update the trail by adding new character at the top
    
    		# Render the trail of characters
    		for (( i=0; i<${#trail}; i++ )); do
    			local trail_pos=$((pos - i)) # Calculate the position for each character in the trail
    			if [ $trail_pos -ge 0 ] && [ $trail_pos -lt $ROWS ]; then
    				local color=${fade_colors[i]:-$static_color} # Choose color from the fade array or static color if beyond the array
    				if [ $i -eq 0 ]; then
    					color=$white_bold # First character in the trail is white and bold
    				fi
    				if [ $last_pos -ne $trail_pos ]; then
    					printf "%b" "\033[${trail_pos};${c}H" # Move cursor to the right position
    					last_pos=$trail_pos
    				fi
    				printf "%b" "${color}${trail:$i:1}\033[0m" # Print the character with color
    			fi
    		done
    
    		positions[$c]=$((pos + 1)) # Update the position for the next cycle
    		if [ $pos -ge $((ROWS + ${#fade_colors[@]})) ]; then
    			positions[$c]=0 # Reset position if it moves off screen
    			trail_chars[$c]=""
    		fi
    	done
    }
    
    # Main loop for continuous execution of the update_line function
    while true; do
    	update_line
    done
    
    # Reset terminal settings on exit (show cursor, clear screen, reset text format)
    echo -ne '\033[?25h' # Show cursor
    clear
    tput sgr0 # Reset text format
    Code language: PHP (php)

    Challenges Faced

    Developing the Matrix Digital Rain script presented specific challenges, especially in terms of performance. The initial use of tput for cursor manipulation proved inefficient for the dynamic text display. This issue was resolved by switching to printf and ANSI escape sequences, which significantly enhanced the rendering performance.

    Another problem arose with the use of fullwidth Katakana characters, which were incompatible with monospaced fonts, disrupting the visual flow. The solution involved adopting halfwidth Katakana, ensuring better compatibility and preserving the uniformity essential for the Matrix-style effect.

    Another notable challenge emerged in development: the inefficiency of generating random characters on the fly. Due to Bash’s slower handling of string functions, this method significantly hindered performance. To tackle this, a strategic shift was made from real-time character generation to utilizing a predefined lookup table.

    This approach involved generating a large set of pseudorandom characters before entering the main loop of the program. By doing so, I could rapidly access this table during runtime, boosting performance. This change played a crucial role in maintaining fluidity and responsiveness. It also preserved the illusion of randomness, essential for the authentic Matrix effect, thus striking a balance between performance efficiency and visual fidelity.

    If you’d like to see what it looks like, here’s an example:

    So yeah. That’s it I guess? Enjoy!

  • Quick Tip: Add Screen Name to Bash Prompt

    Quick Tip: Add Screen Name to Bash Prompt

    I often SSH into servers to get some work done, and one of the things I discovered recently is that I may not always know or remember if I’m in a screen session.

    So I had the bright idea to just add it to my shell prompt!

    Simply just add one of these to your RC file of choice:

    Bash

    # Add Screen name to PS1 if we're in a screen.
    if [ -n "$STY" ]; then
    	PS1="\[\e[1m\](Screen: $STY)\[\e[0m\]\n$PS1"
    fiCode language: PHP (php)

    ZSH

    # Add Screen name to PROMPT if we're in a screen.
    if [[ -n "$STY" ]]; then
    	PROMPT="%B(Screen: $STY)%b"$'\n'"$PROMPT"
    fiCode language: PHP (php)

    And remember, if you’re asking yourself if you should run something in a screen, you’re already too late!

  • Bash Script: Calculate before/after averages

    Bash Script: Calculate before/after averages

    I’ve been doing some performance testing, and wanted a quick way to test how well or poorly changes affect a site. Normally I’d whip out the ol’ calculator app and do this manually. That got tiring after a while, so instead with the help of ChatGPT, I made this little bash script that will do the work for you:

    #!/bin/bash
    
    # Function to calculate the average of a list of numbers
    average() {
    	local sum=0
    	local count=0
    
    	for num in "$@"; do
    		sum=$(echo "$sum + $num" | bc -l)
    		count=$((count+1))
    	done
    
    	echo "$sum / $count" | bc -l
    }
    
    # Parse arguments
    for i in "$@"; do
    	case $i in
    		--before=*)
    		BEFORE="${i#*=}"
    		shift
    		;;
    		--after=*)
    		AFTER="${i#*=}"
    		shift
    		;;
    		*)
    		# unknown option
    		;;
    	esac
    done
    
    # Check if both BEFORE and AFTER parameters are provided
    if [ -z "$BEFORE" ] || [ -z "$AFTER" ]; then
    	echo "Error: Missing required parameters."
    	echo "Usage: $0 --before=<comma-separated-values> --after=<comma-separated-values>"
    	exit 1
    fi
    
    IFS=',' read -ra BEFORE_LIST <<< "$BEFORE"
    IFS=',' read -ra AFTER_LIST <<< "$AFTER"
    
    # Calculate average for before and after lists
    BEFORE_AVG=$(printf "%.2f\n" $(average "${BEFORE_LIST[@]}"))
    AFTER_AVG=$(printf "%.2f\n" $(average "${AFTER_LIST[@]}"))
    
    echo "Before average: $BEFORE_AVG"
    echo "After average: $AFTER_AVG"
    
    # Calculate average percent increase, decrease or no change for the list
    if [ "$BEFORE_AVG" != "0.00" ]; then
    	PERCENT_CHANGE=$(echo "(($AFTER_AVG - $BEFORE_AVG) / $BEFORE_AVG) * 100" | bc -l)
    	if [ "$(echo "$PERCENT_CHANGE > 0" | bc -l)" -eq 1 ]; then
    		printf "Average percent increased: %.2f%%\n" "$PERCENT_CHANGE"
    	elif [ "$(echo "$PERCENT_CHANGE < 0" | bc -l)" -eq 1 ]; then
    		printf "Average percent decreased: %.2f%%\n" "$PERCENT_CHANGE" | tr -d '-'
    	else
    		echo "No change in average."
    	fi
    else
    	echo "Percent change from before to after: undefined (division by zero)"
    fi
    Code language: Bash (bash)

    It runs like this:

    $ average.sh --before=13.07,9.75,16.14,7.71,10.32 --after=1.22,1.28,1.13,1.19,1.26
    Before average: 11.40
    After average: 1.22
    Average percent decreased: 89.30%
    

    In this instance, it was calculating seconds–but you need to remember that it only goes to two decimal places, so if you need something finer you’ll need to adjust the code or your inputs.

    Happy Slacking!

  • Macbook Battery Stats in Your ZSH Terminal Prompt

    Macbook Battery Stats in Your ZSH Terminal Prompt

    As a power user of my Macbook, I’ve found that I often overlook the small battery icon on my menu bar, especially when I’m immersed in a fun project. This minor inconvenience sparked a thought: why not incorporate the battery status directly into my terminal prompt? Thus, I embarked on a fun exploration into ZSH scripting. With a bit of coding magic, I was able to enhance my terminal prompt to dynamically display my Macbook’s battery percentage. Let me guide you through the process.

    We start by loading the ZSH hook module. This module allows us to add functions that run before and/or after each command, giving us the ability to update our battery status prompt in real-time.

    autoload -U add-zsh-hookCode language: Bash (bash)

    The Battery Status Function

    Next, I crafted a function, terminal_battery_stats, that retrieves battery information and displays it in the terminal prompt. Here’s how it works:

    function terminal_battery_stats {
        # Retrieve battery statistics using the pmset command. This is a macOS
        # command that allows power management and battery status retrieval.
        # The awk command is used to format the output into a useful string.
        bat_info=$(pmset -g batt | awk 'NR==2 {gsub(/;/,""); print $3 " " $4}')
        
        # Extract the battery percentage and state from the bat_info string.
        bat_percent=$(echo $bat_info | cut -d' ' -f1 | tr -d '%')
        bat_state=$(echo $bat_info | cut -d' ' -f2)
    
        # Check if the battery is charging or on AC power.
        if [ $bat_state = 'charging' ] || [ $bat_state = 'AC' ] || [ $bat_state = 'charged' ] || [ $bat_state = 'finishing' ]; then
            # If the battery is over 66%, don't display a battery prompt.
            if [ $bat_percent -gt 66 ]; then
                bat_prompt=""
            else
                # Otherwise, set the battery icon to a plug, and the color to green.
                bat_icon='🔌'
                bat_color='%F{green}'
                # Format the prompt with the battery color, percentage, and icon.
                bat_prompt="〔$bat_color$bat_percent%% $bat_icon%f〕"
            fi
        else
            # If the battery is discharging, choose a battery icon and color based on the battery level.
            if [ $bat_percent -le 33 ]; then
                bat_icon='🪫'
                bat_color='%F{red}'
            elif [ $bat_percent -gt 66 ]; then
                bat_icon='🔋'
                bat_color='%F{green}'
            else
                bat_icon='🔋'
                bat_color='%F{yellow}'
            fi
            # Format the prompt with the battery color, percentage, and icon.
            bat_prompt="〔$bat_color$bat_percent%% $bat_icon%f〕"
        fi
    
        # Check if the current prompt already contains a battery status.
        if [[ "$PROMPT" == *"〔"* ]]; then
            # If it does, remove the existing battery status from the prompt.
            PROMPT=${PROMPT#*"〕"}
        fi
    
        # Add the new battery status to the prompt.
        PROMPT="${bat_prompt}${PROMPT}"
    }Code language: Bash (bash)

    To get some basic understanding of the magic behind ZSH scripting and manipulating the terminal prompt, check this helpful resource.

    Applying the Battery Status Function

    After creating the function, I added terminal_battery_stats to the command prompt via ZSH’s pre-command hook. Now, my function runs before each command entered in the terminal, keeping the battery stats up-to-date.

    add-zsh-hook precmd terminal_battery_statsCode language: Bash (bash)

    All the above code is added to my ~/.zshrc file, turning my terminal prompt into a dynamic display of my Macbook’s battery status. The resulting terminal prompt looks like this:

    Conclusion

    Through the power of ZSH scripting magic, my terminal now offers real-time updates of my Macbook’s battery status after every command. I set it to disappear once the battery reaches 67%, a level I consider to be within the safe zone. This is an excellent example of how minor inconveniences can lead to innovative solutions that enhance productivity.

    Here’s the full script ready to drop in to your own ~/.zshrc file:


    # Load the zsh hook module. This is a module that allows adding functions
    # that get run before and/or after each command.
    autoload -U add-zsh-hook
    
    # Function to retrieve and display battery statistics in the terminal prompt.
    # Uses the pmset command to retrieve battery information, and awk to format
    # it into a useful string. Depending on the battery's state and level, 
    # different icons and colors will be displayed in the terminal prompt.
    # 
    # @return void
    function terminal_battery_stats {
        # Retrieve battery statistics using the pmset command. This is a macOS
        # command that allows power management and battery status retrieval.
        # The awk command is used to format the output into a useful string.
        bat_info=$(pmset -g batt | awk 'NR==2 {gsub(/;/,""); print $3 " " $4}')
        
        # Extract the battery percentage and state from the bat_info string.
        bat_percent=$(echo $bat_info | cut -d' ' -f1 | tr -d '%')
        bat_state=$(echo $bat_info | cut -d' ' -f2)
    
        # Check if the battery is charging or on AC power.
        if [ $bat_state = 'charging' ] || [ $bat_state = 'AC' ] || [ $bat_state = 'charged' ] || [ $bat_state = 'finishing' ]; then
            # If the battery is over 66%, don't display a battery prompt.
            if [ $bat_percent -gt 66 ]; then
                bat_prompt=""
            else
                # Otherwise, set the battery icon to a plug, and the color to green.
                bat_icon='🔌'
                bat_color='%F{green}'
                # Format the prompt with the battery color, percentage, and icon.
                bat_prompt="〔$bat_color$bat_percent%% $bat_icon%f〕"
            fi
        else
            # If the battery is discharging, choose a battery icon and color based on the battery level.
            if [ $bat_percent -le 33 ]; then
                bat_icon='🪫'
                bat_color='%F{red}'
            elif [ $bat_percent -gt 66 ]; then
                bat_icon='🔋'
                bat_color='%F{green}'
            else
                bat_icon='🔋'
                bat_color='%F{yellow}'
            fi
            # Format the prompt with the battery color, percentage, and icon.
            bat_prompt="〔$bat_color$bat_percent%% $bat_icon%f〕"
        fi
    
        # Check if the current prompt already contains a battery status.
        if [[ "$PROMPT" == *"〔"* ]]; then
            # If it does, remove the existing battery status from the prompt.
            PROMPT=${PROMPT#*"〕"}
        fi
    
        # Add the new battery status to the prompt.
        PROMPT="${bat_prompt}${PROMPT}"
    }
    
    # Adds the function terminal_battery_stats to the command prompt 
    # meaning it will be run before each command entered in the terminal. 
    add-zsh-hook precmd terminal_battery_stats
    
    Code language: Bash (bash)

    Good luck!

  • Sending Prowl Alerts via Bash

    Sending Prowl Alerts via Bash

    I’m working on some server scripting and I wanted to find a way to make sure I get proper alerts. I tried hard getting Twilio working cheaply, but that failed. So I remembered that I use Prowl for other things on my phone–so why not just send more alerts that way?

    So, again with the help of ChatGPT I have made a terrible monstrosity:

    #!/bin/bash
    
    # Exit if any command fails, if an unset variable is used, or if a command in a pipeline fails
    set -euo pipefail
    
    # Default values
    application="Shell Notification"
    priority=0
    event="Event"
    message=""
    logfile="/var/log/send-prowl-alert.log"
    url=""
    
    # Usage information
    usage() {
        echo "Usage: $0 --message=<message> [--application=<application>] [--event=<event>] [--description=<description>] [--priority=<priority>] [--url=<url>]"
        echo "Required:"
        echo "  --message=<message>       A description of the event, generally terse"
        echo "                            Maximum of 10000 bytes"
        echo "Optional:"
        echo "  --application=<application>  The name of the application (default: 'Shell Notification')"
        echo "                               Maximum of 256 bytes"
        echo "  --event=<event>              The name of the event or subject of the notification (default: 'Event')"
        echo "                               Maximum of 1024 bytes"
        echo "  --priority=<priority>        The priority of the alert (default: 0)"
        echo "                               An integer value ranging [-2, 2] representing:"
        echo "                                   -2 Very Low"
        echo "                                   -1 Moderate"
        echo "                                    0 Normal"
        echo "                                    1 High"
        echo "                                    2 Emergency"
        echo "                               Emergency priority messages may bypass quiet hours according to the user's settings."
        echo "  --url=<url>                  The URL which should be attached to the notification"
        echo "                               Maximum of 512 bytes"
        echo "  --help                       Displays this help message"
    }
    
    # URL Encodes a string
    urlencode() {
      local string="$1"
      local length="${#string}"
      local i
    
      for ((i = 0; i < length; i++)); do
        local c="${string:i:1}"
        case $c in
          [a-zA-Z0-9.~_-])
            printf '%s' "$c"
            ;;
          *)
            printf '%%%02X' "'$c"
            ;;
        esac
      done
    }
    
    # Check if the script is being run manually or via cron
    if [[ -t 1 ]]; then
        # Output to console if being run manually
        log_message() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
    else
        # Output to log file if being run via cron
        log_message() {
            echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "${logfile}"
            # Truncate the log file to 1 megabyte
            if [[ $(wc -c <"${logfile}") -gt 1000000 ]]; then
                tail -c 1000000 "${logfile}" > "${logfile}.tmp" && mv "${logfile}.tmp" "${logfile}"
            fi
        }
    fi
    
    # Parse arguments
    for arg in "$@"; do
        case "${arg}" in
            --message=*)
                message=${arg#*=}
                ;;
            --application=*)
                application=${arg#*=}
                ;;
            --event=*)
                event=${arg#*=}
                ;;
            --priority=*)
                priority=${arg#*=}
                ;;
            --url=*)
                url=${arg#*=}
                ;;
            --help)
                usage
                exit 0
                ;;
            *)
                echo "Error: Unsupported argument ${arg}"
                usage
                exit 1
                ;;
        esac
    done
    
    
    # Create a variable to store error messages
    errors=""
    
    # Check if curl is installed
    if ! command -v curl &> /dev/null; then
        errors+="Error: curl is not installed. Please install curl and try again.\n"
    fi
    
    # Check that a message was provided
    if [[ -z "${message}" ]]; then
        errors+="Error: No message provided."
    fi
    
    # Check if the URL is valid
    if [[ -n $url && ! $url =~ ^(https?://)[^\s/$.?#].[^\s]*$ ]]; then
        errors+="Error: Invalid URL.\n"
    fi
    
    # Check byte size of the parameters and append error if they exceed the max limit
    if [[ "${#url}" -gt 512 ]]; then
        errors+="Error: URL exceeds maximum byte limit (512 bytes).\n"
    fi
    
    if [[ "${#application}" -gt 256 ]]; then
        errors+="Error: Application name exceeds maximum byte limit (256 bytes).\n"
    fi
    
    if [[ "${#event}" -gt 1024 ]]; then
        errors+="Error: Event name exceeds maximum byte limit (1024 bytes).\n"
    fi
    
    if [[ "${#message}" -gt 10000 ]]; then
        errors+="Error: Message exceeds maximum length (10000 bytes).\n"
    fi
    
    if [[ ! "${priority}" =~ ^-?[0-2]$ ]]; then
        errors+="Error: Invalid priority. Must be an integer between -2 and 2.\n"
    fi
    
    # Check if the API key file exists and is not empty
    if [[ ! -s ~/.prowl_api_key ]]; then
        errors+="Error: The Prowl API key file does not exist or is empty. Please create a file at ~/.prowl_api_key and add your API key to it.\nExample:\n  echo 'your_api_key' > ~/.prowl_api_key\n  chmod 600 ~/.prowl_api_key\n"
    fi
    
    # Check the permissions on the API key file, if it exists
    # We have to do it in a convoluted cross platform way because macos does not support `stat -c`
    if [[ -f ~/.prowl_api_key ]]; then
        # Retrieve the file permissions and remove any trailing '@' symbol (indicating extended attributes)
        permissions=$(ls -l -d ~/.prowl_api_key | awk '{print $1}' | tr -d '@')
    
        # Check if the permissions are not set to '-rw-------'
        if [[ "$permissions" != "-rw-------" ]]; then
            # Add an error message to the 'errors' variable
            errors+="Error: The permissions on the API key file are not set correctly. Please run 'chmod 600 ~/.prowl_api_key' and try again.\n"
        fi
    fi
    
    # If any errors were detected, print the error messages and exit
    if [[ -n $errors ]]; then
        echo -e "${errors}"  # -e enables interpretation of backslash escapes like \n
        exit 1
    fi
    
    # Get the Prowl API key
    prowl_api_key=$(cat ~/.prowl_api_key)
    
    # Save the alert data for logging before we encode it
    alert_log_string="$application : $event : $priority : $url : $message"
    
    # URL-encode the variables
    prowl_api_key=$(urlencode "$prowl_api_key")
    application=$(urlencode "$application")
    event=$(urlencode "$event")
    message=$(urlencode "$message")
    priority=$(urlencode "$priority")
    url=$(urlencode "$url")
    
    # Send the alert
    response=$(curl -s "https://api.prowlapp.com/publicapi/add?apikey=${prowl_api_key}&application=${application}&event=${event}&description=${message}&priority=${priority}&url=${url}")
    
    # Check if the alert was sent successfully
    if [[ "${response}" != *"success code=\"200\""* ]]; then
        log_message "Error sending alert: ${alert_log_string}"
        log_message "Response: ${response}"
        exit 1
    fi
    
    log_message "Alert sent successfully: $alert_log_string"
    exit 0
    Code language: PHP (php)

    Good luck!

  • Supercharge Your Clipboard with a Shell Function for iTerm2

    Supercharge Your Clipboard with a Shell Function for iTerm2

    Well, I’ve been playing with ChatGPT more, and I had ChatGPT (GPT-4) write this blog post for me. I told it that I was going to give it some of my blog posts to determine my writing style, and to have it write me a blog post about this bash function. It’s …. meh … not really “my style” but whatever.

    The idea of this shell function is to integrate with iTerm2’s proprietary escape sequences to allow you to pipe content right to your clipboard. I’m using the name pbcopy from MacOS so that I don’t need to think about it, and can use pbcopy anywhere. Now I can work on a remote server and cat stupid-stuff.log | pbcopy and get a copy of it locally to mess with, without having to click and drag or download the file. Super lazy ❇

    Enjoy the AI blog post:


    Hey folks, today we’re going to explore a neat shell function that can make your life a lot easier when you’re working in iTerm2 on macOS. The purpose of this function is to make the built-in pbcopy command work seamlessly in iTerm2, even when the command isn’t available in your shell environment.

    Without further ado, let’s dive into the code:

    # pbcopy via iTerm2
    function pbcopy() {
    	if which pbcopy >/dev/null 2>&1; then
    		pbcopy "$@"
    	else
    		# Replace ^[ with the ASCII escape character
    		local start="\e]1337;CopyToClipboard\a"
    		local end="\e]1337;EndCopy\a"
    		printf "${start}$(cat)${end}"
    	fi
    }
    Code language: PHP (php)

    You might be wondering how this magic works. Let’s break it down step by step:

    1. The function first checks if the pbcopy command is available in your shell environment using the which command. If it is, it simply calls the built-in pbcopy command with the provided arguments.
    2. If the pbcopy command isn’t available, the function creates two local variables, start and end, containing special escape sequences that iTerm2 understands.
    3. The printf command is then used to concatenate the start escape sequence, the contents of the input (read using cat), and the end escape sequence. This formatted string is what gets sent to iTerm2.

    When iTerm2 receives this specially formatted string, it recognizes the escape sequences and takes care of copying the text between them to the system clipboard.

    And that’s it! With this function in your shell environment, you can copy text to your clipboard using pbcopy without any hassle, whether the command is natively available or not. To use this function, simply add it to your shell startup file (like .bashrc or .zshrc), and you’re good to go.

    In a practical scenario, let’s say you’re working on a project and you want to share the contents of a log file with a colleague. Instead of manually opening the log file, selecting its content, and copying it to your clipboard, you can use this shell script to streamline the process. With the pbcopy function added to your shell startup file, simply open your terminal and run the command cat logfile.txt | pbcopy. This command will read the content of the logfile.txt file and pipe it directly to the pbcopy function, which in turn places the content into your clipboard. Now you can easily paste the log file content into an email, chat application, or any other medium you choose, all without leaving the comfort of your terminal. This not only saves time but also enhances your overall productivity when working with text-based data in a command-line environment.

    In conclusion, this shell function is a fantastic little tool to enhance your iTerm2 experience on macOS. I hope you find it as useful as I do. As always, if you have any questions or comments, feel free to drop them below. Happy coding!


    Happy coding, indeed 🤖

  • More Garbage Sysadmin: Reboot Linux Server on Kernel Panic

    More Garbage Sysadmin: Reboot Linux Server on Kernel Panic

    Just like restarting a server when the memory is low, I’ve had a recent problem with kernel panics on my Raspberry Pi, and I’ve found a terrible solution to fix it: Just reboot.

    Setting the /proc/sys/kernel/panic file contents to a non-zero integer will reboot the server on kernel panic after that many seconds.

    Because I’m lazy, I asked ChatGPT to write me up a startup script to do this for me, and here’s what I have now:

    To set the panic timeout value on Ubuntu Server 20.04 and later versions, you can create a systemd service unit file.

    Here are the steps to create a systemd service unit file:

    1. Open a terminal window on your Ubuntu Server.
    2. Create a new service unit file with the following command:

      sudo nano /etc/systemd/system/panic-timeout.service

      This will open a new file named panic-timeout.service in the nano text editor with superuser privileges.
    3. Add the following lines to the file:
    [Unit]
    Description=Panic Timeout
    
    [Service]
    Type=oneshot
    ExecStart=/bin/bash -c "echo 60 > /proc/sys/kernel/panic"
    
    [Install]
    WantedBy=multi-user.target
    Code language: JavaScript (javascript)

    This service unit file sets the panic timeout to 60 seconds.

    1. Save the file by pressing Ctrl+O, then exit nano by pressing Ctrl+X.
    2. Reload the systemd daemon to recognize the new service unit file with the following command:

      sudo systemctl daemon-reload
    3. Enable the service unit file to run at boot time with the following command:

      sudo systemctl enable panic-timeout.service
    4. Reboot the server to test the service unit file. After the server reboots, the panic-timeout.service will automatically run the echo command and set the panic timeout to 60 seconds.

    That’s it! With these steps, you can set the panic timeout value on the latest versions of Ubuntu Server.

    Well there you have it! Don’t forget to follow along for more terrible ideas!