I’m working on a module to add OctoPrint status to my zsh prompt, which I’ll probably write about in the future as a bigger post about my prompt customizations.
To start with that though, I need to play around with accessing the API via curl.
So here’s my super alpha version that will request the current status via HTTP and output it:
#!/bin/bash
OCTOPRINT_API="hunter2"
BASE_URL="http://octoprint.example.com"
JOB_ENDPOINT="${BASE_URL}/api/job"
CURL_TIMEOUT="0.5"# Fetch the JSON from OctoPrint
response="$(curl --max-time ${CURL_TIMEOUT} --silent --fail \
--header "X-Api-Key: ${OCTOPRINT_API}" \
"${JOB_ENDPOINT}")"# Extract fields with jq
file_name=$(jq -r '.job.file.display' <<< "$response")
completion=$(jq -r '.progress.completion' <<< "$response")
state=$(jq -r '.state' <<< "$response")
time_elapsed=$(jq -r '.progress.printTime' <<< "$response")
time_left=$(jq -r '.progress.printTimeLeft' <<< "$response")
# Round the completion percentage to two decimals
completion_str=$(printf"%.2f""$completion")
# Convert seconds to H:MM:SSfunctionfmt_time() {
local total_seconds="$1"local hours=$((total_seconds / 3600))
local minutes=$(((total_seconds % 3600) / 60))
local seconds=$((total_seconds % 60))
printf"%02d:%02d:%02d""$hours""$minutes""$seconds"
}
# Convert the times
time_elapsed_str=$(fmt_time "$time_elapsed")
time_left_str=$(fmt_time "$time_left")
# Print a readable summaryecho"File: ${file_name}"echo"State: ${state}"echo"Completion: ${completion_str}%"echo"Time Elapsed: ${time_elapsed_str}"echo"Time Left: ${time_left_str}"Code language:Bash(bash)
Something that I do often is run PHPCS on code I’m working on, almost always inside a git repository. Even more likely is that PHPCS was installed via composer, which means it will live in $GIT_ROOT/vendor/bin. So I always end up doing something like ../../../vendor/bin/phpcs file.php which is hugely annoying.
Which is why I made this monstrosity:
# PHPCSfunctionphpcs() {
if git rev-parse --git-dir > /dev/null 2>&1; then
git_root=$(git rev-parse --show-toplevel 2>/dev/null)
# Check if vendor/bin/phpcs existsif [[ -x "$git_root/vendor/bin/phpcs" ]]; then# Call the local vendor/bin/phpcs"$git_root/vendor/bin/phpcs""$@"fielse# Fall back to the system's default phpcscommand phpcs "$@"fi
}
# PHPCBFfunctionphpcbf() {
if git rev-parse --git-dir > /dev/null 2>&1; then
git_root=$(git rev-parse --show-toplevel 2>/dev/null)
# Check if vendor/bin/phpcbf existsif [[ -x "$git_root/vendor/bin/phpcbf" ]]; then# Call the local vendor/bin/phpcbf"$git_root/vendor/bin/phpcbf""$@"fielse# Fall back to the system's default phpcbfcommand phpcbf "$@"fi
}Code language:Bash(bash)
Basically, what this is doing is any time I run phpcs or phpcbf it will first check if I am inside a git repository. If I am, and $GIT_ROOT/vendor/bin/phpcs exists, it will automatically find it and use it.
Saves me seconds a day. SECONDS! Maybe you can find it useful as well. Or not. Who knows.
I set up a few defaults I like to have for my Pis:
# Updates, new software, and cleanup
sudo apt update && sudo apt upgrade
sudo apt install mc screen ack zsh locate git htop cockpit -y
sudo apt autoremove
# Add dotfile customizations. Sorry, it's currently private :D
git clone git@github.com:emrikol/dotfiles.git
cp -r ~/dotfiles/. ~/
sudo usermod --shell /bin/zsh derrick
zsh
# Set up root access so I can SCP in from Transmit if I need to.
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
sudo passwd root
# Customize Raspberry Pi settings.
sudo raspi-configCode language:Bash(bash)
After installing cockpit I believe, I had an issue where the MAC address of my Wifi kept changing randomly on each reboot. I had to follow these instructions and add this to my /etc/NetworkManager/NetworkManager.conf:
From here, we should have my default “base” Raspberry Pi setup. And now, we can work on figuring out how to install Pibooth. According to the install docs, we need to run a few commands:
$ sudo apt install "libsdl2-*"
Reading package lists... Done
Building dependency tree
Reading state information... Done
Note, selecting 'libsdl2-mixer-dev'for glob 'libsdl2-*'
Note, selecting 'libsdl2-image-dev'for glob 'libsdl2-*'
Note, selecting 'libsdl2-gfx-dev'for glob 'libsdl2-*'
Note, selecting 'libsdl2-gfx-doc'for glob 'libsdl2-*'
Note, selecting 'libsdl2-mixer-2.0-0'for glob 'libsdl2-*'
Note, selecting 'libsdl2-dbg:armhf'for glob 'libsdl2-*'
Note, selecting 'libsdl2-dev'for glob 'libsdl2-*'
Note, selecting 'libsdl2-doc'for glob 'libsdl2-*'
Note, selecting 'libsdl2-ttf-dev'for glob 'libsdl2-*'
Note, selecting 'libsdl2-net-2.0-0'for glob 'libsdl2-*'
Note, selecting 'libsdl2-net-dev'for glob 'libsdl2-*'
Note, selecting 'libsdl2-image-2.0-0'for glob 'libsdl2-*'
Note, selecting 'libsdl2-2.0-0-dbgsym:armhf'for glob 'libsdl2-*'
Note, selecting 'libsdl2-2.0-0'for glob 'libsdl2-*'
Note, selecting 'libsdl2-gfx-1.0-0'for glob 'libsdl2-*'
Note, selecting 'libsdl2-ttf-2.0-0'for glob 'libsdl2-*'
libsdl2-2.0-0 is already the newest version (2.0.9+dfsg1-1+deb10u1).
libsdl2-2.0-0set to manually installed.
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:
The following packages have unmet dependencies:
libsdl2-2.0-0-dbgsym:armhf : Depends: libsdl2-2.0-0:armhf (= 2.0.9+dfsg1-1+rpt1) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.Code language:JavaScript(javascript)
Meh. Okay. Let’s just install all of them but the trouble package. Hopefully that won’t come back to bite us.
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 rootif [[ $EUID -ne 0 ]]; thenecho"This script must be run as root"exit 1
fi# Check for correct number of argumentsif [ "$#" -ne 4 ]; thenecho"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.
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
functionshow_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."echoecho"Example: $0 --url=https://example.com/ --count=5"exit
}
functionaverage_ttfb() {
local URL=""local COUNT=6 # Default COUNT to 6 if not suppliedlocal CURL_OPTS="-s"# Parse argumentsfor arg in"$@"; docase$argin
--url=*)
URL="${arg#*=}"shift# Remove argument from processing
;;
--count=*)
COUNT="${arg#*=}"shift# Remove argument from processing
;;
*)
# Unknown option
;;
esacdoneif [[ -z "$URL" ]]; thenexit 1
filocal 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 successfulif [ $? -eq 0 ]; then
total_time=$(echo"$total_time + $ttfb" | bc)
((count_success++))
elseecho"Request $i failed." >&2
fidoneif [ $count_success -eq 0 ]; thenecho"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
}
functionab_test_ttfb() {
# Run initial testread -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 proceedread -p "Press any key to re-run tests..." -n 1 -r
echo# Move to a new line# Run second testecho"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 providedif [[ " $* " == *" --help "* ]] || [[ "$#" -eq 0 ]]; then
show_usage
fi# Check if --url is in the arguments
url_present=falsefor arg in"$@"; doif [[ $arg == --url=* ]]; then
url_present=truebreakfidoneif [ "$url_present" = false ]; thenecho"Error: --url argument is required."
show_usage
fi
ab_test_ttfb "$@"Code language:Bash(bash)
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 screenecho -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 columnfor (( 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 movementfor (( c=1; c<=COLUMNS; c++ )); do# Randomly skip updating some columns to create a dynamic effectif [ $((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 charactersfor (( i=0; i<${#trail}; i++ )); do
local trail_pos=$((pos - i)) # Calculate the position for each character in the trailif [ $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 arrayif [ $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 cycleif [ $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 functionwhiletrue; 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 formatCode 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:
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!