From 90fd4f95576864b413008ce035ca0bf79cc689b4 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 13 Sep 2025 12:05:29 +0300 Subject: Update content for html --- Bash Golf/style-override.css | 0 about/resources.html | 204 ++--- ...021-05-16-personal-bash-coding-style-guide.html | 1 + ...5-gemtexter-one-bash-script-to-rule-it-all.html | 1 + gemfeed/2021-11-29-bash-golf-part-1.html | 2 + gemfeed/2022-01-01-bash-golf-part-2.html | 2 + ...static-web-photo-albums-with-photoalbum.sh.html | 1 + gemfeed/2023-12-10-bash-golf-part-3.html | 2 + .../2024-01-13-one-reason-why-i-love-openbsd.html | 2 + .../2024-08-05-typing-127.1-words-per-minute.html | 2 +- gemfeed/2025-09-14-bash-golf-part-4.html | 662 ++++++++++++++ gemfeed/atom.xml | 962 ++++++++++++++------- gemfeed/index.html | 1 + index.html | 3 +- uptime-stats.html | 2 +- 15 files changed, 1440 insertions(+), 407 deletions(-) create mode 100644 Bash Golf/style-override.css create mode 100644 gemfeed/2025-09-14-bash-golf-part-4.html diff --git a/Bash Golf/style-override.css b/Bash Golf/style-override.css new file mode 100644 index 00000000..e69de29b diff --git a/about/resources.html b/about/resources.html index 0155cb99..bd943e47 100644 --- a/about/resources.html +++ b/about/resources.html @@ -50,108 +50,108 @@ In random order:


Technical references



I didn't read them from the beginning to the end, but I am using them to look up things. The books are in random order:


Self-development and soft-skills books



In random order:


Here are notes of mine for some of the books

@@ -160,31 +160,31 @@ Some of these were in-person with exams; others were online learning lectures only. In random order:


Technical guides



These are not whole books, but guides (smaller or larger) which I found very useful. in random order:


Podcasts



@@ -193,30 +193,30 @@ In random order:


Podcasts I liked



I liked them but am not listening to them anymore. The podcasts have either "finished" (no more episodes) or I stopped listening to them due to time constraints or a shift in my interests.


@@ -225,28 +225,28 @@ This is a mix of tech and non-tech newsletters I am subscribed to. In random order:


Magazines I like(d)



This is a mix of tech I like(d). I may not be a current subscriber, but now and then, I buy an issue. In random order:


Formal education



diff --git a/gemfeed/2021-05-16-personal-bash-coding-style-guide.html b/gemfeed/2021-05-16-personal-bash-coding-style-guide.html index 04f6dced..3a832d5b 100644 --- a/gemfeed/2021-05-16-personal-bash-coding-style-guide.html +++ b/gemfeed/2021-05-16-personal-bash-coding-style-guide.html @@ -486,6 +486,7 @@ return_codes=( "${PIPESTATUS[@]}" )
Other related posts are:

+2025-09-14 Bash Golf Part 4
2023-12-10 Bash Golf Part 3
2022-01-01 Bash Golf Part 2
2021-11-29 Bash Golf Part 1
diff --git a/gemfeed/2021-06-05-gemtexter-one-bash-script-to-rule-it-all.html b/gemfeed/2021-06-05-gemtexter-one-bash-script-to-rule-it-all.html index af062624..45f35835 100644 --- a/gemfeed/2021-06-05-gemtexter-one-bash-script-to-rule-it-all.html +++ b/gemfeed/2021-06-05-gemtexter-one-bash-script-to-rule-it-all.html @@ -208,6 +208,7 @@ assert::equals "$(generate::make_link md "$gemtext<
Other related posts are:

+2025-09-14 Bash Golf Part 4
2024-10-02 Gemtexter 3.0.0 - Let's Gemtext again⁴
2023-12-10 Bash Golf Part 3
2023-07-21 Gemtexter 2.1.0 - Let's Gemtext again³
diff --git a/gemfeed/2021-11-29-bash-golf-part-1.html b/gemfeed/2021-11-29-bash-golf-part-1.html index ed90e38d..22c38b78 100644 --- a/gemfeed/2021-11-29-bash-golf-part-1.html +++ b/gemfeed/2021-11-29-bash-golf-part-1.html @@ -20,6 +20,7 @@ 2021-11-29 Bash Golf Part 1 (You are currently reading this)
2022-01-01 Bash Golf Part 2
2023-12-10 Bash Golf Part 3
+2025-09-14 Bash Golf Part 4

      '\                   .  .                        |>18>>
@@ -494,6 +495,7 @@ bash: line 1: 1/10.0 : syntax error: invalid arithmetic operator (error token is
 
Other related posts are:

+2025-09-14 Bash Golf Part 4
2023-12-10 Bash Golf Part 3
2022-01-01 Bash Golf Part 2
2021-11-29 Bash Golf Part 1 (You are currently reading this)
diff --git a/gemfeed/2022-01-01-bash-golf-part-2.html b/gemfeed/2022-01-01-bash-golf-part-2.html index a1bee695..07335dbf 100644 --- a/gemfeed/2022-01-01-bash-golf-part-2.html +++ b/gemfeed/2022-01-01-bash-golf-part-2.html @@ -20,6 +20,7 @@ 2021-11-29 Bash Golf Part 1
2022-01-01 Bash Golf Part 2 (You are currently reading this)
2023-12-10 Bash Golf Part 3
+2025-09-14 Bash Golf Part 4

     '\       '\                   .  .                |>18>>
@@ -513,6 +514,7 @@ PAUL:X:1000:1000:PAUL BUETOW:/HOME/PAUL:/BIN/BASH
 
Other related posts are:

+2025-09-14 Bash Golf Part 4
2023-12-10 Bash Golf Part 3
2022-01-01 Bash Golf Part 2 (You are currently reading this)
2021-11-29 Bash Golf Part 1
diff --git a/gemfeed/2023-10-29-kiss-static-web-photo-albums-with-photoalbum.sh.html b/gemfeed/2023-10-29-kiss-static-web-photo-albums-with-photoalbum.sh.html index 8112aafd..b67d90e4 100644 --- a/gemfeed/2023-10-29-kiss-static-web-photo-albums-with-photoalbum.sh.html +++ b/gemfeed/2023-10-29-kiss-static-web-photo-albums-with-photoalbum.sh.html @@ -293,6 +293,7 @@ blurs html index.html photos thumbs
Other Bash and KISS-related posts are:

+2025-09-14 Bash Golf Part 4
2024-04-01 KISS high-availability with OpenBSD
2023-12-10 Bash Golf Part 3
2023-10-29 KISS static web photo albums with photoalbum.sh (You are currently reading this)
diff --git a/gemfeed/2023-12-10-bash-golf-part-3.html b/gemfeed/2023-12-10-bash-golf-part-3.html index eed46b1d..0b848821 100644 --- a/gemfeed/2023-12-10-bash-golf-part-3.html +++ b/gemfeed/2023-12-10-bash-golf-part-3.html @@ -20,6 +20,7 @@ 2021-11-29 Bash Golf Part 1
2022-01-01 Bash Golf Part 2
2023-12-10 Bash Golf Part 3 (You are currently reading this)
+2025-09-14 Bash Golf Part 4

     '\       '\        '\                   .  .          |>18>>
@@ -419,6 +420,7 @@ echo baz
 
Other related posts are:

+2025-09-14 Bash Golf Part 4
2023-12-10 Bash Golf Part 3 (You are currently reading this)
2022-01-01 Bash Golf Part 2
2021-11-29 Bash Golf Part 1
diff --git a/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.html b/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.html index 662f6fe6..9981ab4b 100644 --- a/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.html +++ b/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.html @@ -45,6 +45,8 @@ $ doas sysupgrade # Update all binaries (including Kerne
sysupgrade downloaded and upgraded to the next release and rebooted the system. After the reboot, I run:

+Note to myself: I have to undo the /var/www symlink before upgrading, and re-establishing the symlink afterwards again. This is due to disk space constraings on my setup!
+
+
printf 'a\nb\n' \
+    | tee >(sed 's/.*/X:&/; s/$/ :c1/') >(tr a-z A-Z | sed 's/$/ :c2/') \
+    | sed 's/$/ :c3/'
+
+
+Output:
+
+
+a :c3
+b :c3
+A :c2 :c3
+B :c2 :c3
+X:a :c1 :c3
+X:b :c1 :c3
+
+
+This relies on Bash process substitution (>(...)). Make sure your shell is Bash and not a POSIX /bin/sh.
+
+Example (fails under dash/POSIX sh):
+
+ +
/bin/sh -c 'echo hi | tee >(cat)'
+# /bin/sh: 1: Syntax error: "(" unexpected
+
+
+Combine with set -o pipefail if failures in side branches should fail the whole pipeline.
+
+Example:
+
+ +
set -o pipefail
+printf 'ok\n' | tee >(false) | cat >/dev/null
+echo $?   # 1 because a side branch failed
+
+
+Further reading:
+
+Splitting pipelines with tee
+
+

Heredocs for remote sessions (and their gotchas)


+
+Heredocs are great to send multiple commands over SSH in a readable way:
+
+ +
ssh "$SSH_USER@$SSH_HOST" <<EOF
+    # Go to the work directory
+    cd "$WORK_DIR"
+  
+    # Make a git pull
+    git pull
+  
+    # Export environment variables required for the service to run
+    export AUTH_TOKEN="$APP_AUTH_TOKEN"
+  
+    # Start the service
+    docker compose up -d --build
+EOF
+
+
+Tips:
+
+Quoting the delimiter changes interpolation. Use <<'EOF' to avoid local expansion and send the content literally.
+
+Example:
+
+ +
FOO=bar
+cat <<'EOF'
+$FOO is not expanded here
+EOF
+
+
+Prefer explicit quoting for variables (as above) to avoid surprises. Example (spaces preserved only when quoted):
+
+ +
WORK_DIR="/tmp/my work"
+ssh host <<EOF
+    cd $WORK_DIR      # may break if unquoted
+    cd "$WORK_DIR"   # safe
+EOF
+
+
+Consider set -euo pipefail at the top of the remote block for stricter error handling. Example:
+
+ +
ssh host <<'EOF'
+    set -euo pipefail
+    false   # causes immediate failure
+    echo never
+EOF
+
+
+Indent-friendly variant: use a dash to strip leading tabs in the body:
+
+ +
cat <<-EOF > script.sh
+	#!/usr/bin/env bash
+	echo "tab-indented content is dedented"
+EOF
+
+
+Further reading:
+
+Heredoc headaches and fixes
+
+

Namespacing and dynamic dispatch with ::


+
+You can emulate simple namespacing by encoding hierarchy in function names. One neat pattern is pseudo-inheritance via a tiny super helper that maps pkg::lang::action to a pkg::base::action default.
+
+ +
#!/usr/bin/env bash
+set -euo pipefail
+
+super() {
+    local -r fn=${FUNCNAME[1]}
+    # Split name on :: and dispatch to base implementation
+    local -a parts=( ${fn//::/ } )
+    "${parts[0]}::base::${parts[2]}" "$@"
+}
+
+foo::base::greet() { echo "base: $@"; }
+foo::german::greet()  { super "Guten Tag, $@!"; }
+foo::english::greet() { super "Good day,  $@!"; }
+
+for lang in german english; do
+    foo::$lang::greet Paul
+done
+
+
+Output:
+
+
+base: Guten Tag, Paul!
+base: Good day,  Paul!
+
+
+

Indirect references with namerefs


+
+declare -n creates a name reference — a variable that points to another variable. It’s cleaner than eval for indirection:
+
+ +
user_name=paul
+declare -n ref=user_name
+echo "$ref"       # paul
+ref=julia
+echo "$user_name" # julia
+
+
+Output:
+
+
+paul
+julia
+
+
+Namerefs are local to functions when declared with local -n. Requires Bash ≥4.3.
+
+You can also construct the target name dynamically:
+
+ +
make_var() {
+    local idx=$1; shift
+    local name="slot_$idx"
+    printf -v "$name" '%s' "$*"   # create variable slot_$idx
+}
+
+get_var() {
+    local idx=$1
+    local -n ref="slot_$idx"      # bind ref to slot_$idx
+    printf '%s\n' "$ref"
+}
+
+make_var 7 "seven"
+get_var 7
+
+
+Output:
+
+
+seven
+
+
+

Function declaration forms


+
+All of these work in Bash, but only the first one is POSIX-ish:
+
+ +
foo() { echo foo; }
+function foo { echo foo; }
+function foo() { echo foo; }
+
+
+Recommendation: prefer name() { ... } for portability and consistency.
+
+

Chaining function calls in conditionals


+
+Functions return a status like commands. You can short-circuit them in conditionals:
+
+ +
deploy_check() { test -f deploy.yaml; }
+smoke_test()   { curl -fsS http://localhost/healthz >/dev/null; }
+
+if deploy_check || smoke_test; then
+    echo "All good."
+else
+    echo "Something failed." >&2
+fi
+
+
+You can also compress it golf-style:
+
+ +
deploy_check || smoke_test && echo ok || echo fail >&2
+
+
+

Grep, sed, awk quickies


+
+Word match and context: grep -w word file; with context: grep -C3 foo file (same as -A3 -B3). Example:
+
+ +
cat > /tmp/ctx.txt <<EOF
+one
+foo
+two
+three
+bar
+EOF
+grep -C1 foo /tmp/ctx.txt
+
+
+Output:
+
+
+one
+foo
+two
+
+
+Skip a directory while recursing: grep -R --exclude-dir=foo 'bar' /path. Example:
+
+ +
mkdir -p /tmp/golf/foo /tmp/golf/src
+printf 'bar\n' > /tmp/golf/src/a.txt
+printf 'bar\n' > /tmp/golf/foo/skip.txt
+grep -R --exclude-dir=foo 'bar' /tmp/golf
+
+
+Output:
+
+
+/tmp/golf/src/a.txt:bar
+
+
+Insert lines with sed: sed -e '1isomething' -e '3isomething' file. Example:
+
+ +
printf 'A\nB\nC\n' > /tmp/s.txt
+sed -e '1iHEAD' -e '3iMID' /tmp/s.txt
+
+
+Output:
+
+
+HEAD
+A
+B
+MID
+C
+
+
+Drop last column with awk: awk 'NF{NF-=1};1' file. Example:
+
+ +
printf 'a b c\nx y z\n' > /tmp/t.txt
+cat /tmp/t.txt
+echo
+awk 'NF{NF-=1};1' /tmp/t.txt
+
+
+Output:
+
+
+a b c
+x y z
+
+a b
+x y
+
+
+

Safe xargs with NULs


+
+Avoid breaking on spaces/newlines by pairing find -print0 with xargs -0:
+
+ +
find . -type f -name '*.log' -print0 | xargs -0 rm -f
+
+
+Example with spaces and NULs only:
+
+ +
printf 'a\0b c\0' | xargs -0 -I{} printf '<%s>\n' {}
+
+
+Output:
+
+
+<a>
+<b c>
+
+
+

Efficient file-to-variable and arrays


+
+Read a whole file into a variable without spawning cat:
+
+ +
cfg=$(<config.ini)
+
+
+Read lines into an array safely with mapfile (aka readarray):
+
+ +
mapfile -t lines < <(grep -v '^#' config.ini)
+printf '%s\n' "${lines[@]}"
+
+
+Assign formatted strings without a subshell using printf -v:
+
+ +
printf -v msg 'Hello %s, id=%04d' "$USER" 42
+echo "$msg"
+
+
+Output:
+
+
+Hello paul, id=0042
+
+
+Read NUL-delimited data (pairs well with -print0):
+
+ +
mapfile -d '' -t files < <(find . -type f -print0)
+printf '%s\n' "${files[@]}"
+
+
+

Quick password generator


+
+Pure Bash with /dev/urandom:
+
+ +
LC_ALL=C tr -dc 'A-Za-z0-9_' </dev/urandom | head -c 16; echo
+
+
+Alternative using openssl:
+
+ +
openssl rand -base64 16 | tr -d '\n' | cut -c1-22
+
+
+

yes for automation


+
+yes streams a string repeatedly; handy for feeding interactive commands or quick load generation:
+
+ +
yes | rm -r large_directory        # auto-confirm
+yes n | dangerous-command          # auto-decline
+yes anything | head -n1            # prints one line: anything
+
+
+

Forcing true to fail (and vice versa)


+
+You can shadow builtins with functions:
+
+ +
true()  { return 1; }
+false() { return 0; }
+
+true  || echo 'true failed'
+false && echo 'false succeeded'
+
+# Bypass function with builtin/command
+builtin true # returns 0
+command true # returns 0
+
+
+To disable a builtin entirely: enable -n true (re-enable with enable true).
+
+Further reading:
+
+Force true to return false
+
+

Restricted Bash


+
+bash -r (or rbash) starts a restricted shell that limits potentially dangerous actions, for example:
+
+
    +
  • Changing directories (cd).
  • +
  • Modifying PATH, SHELL, BASH_ENV, or ENV.
  • +
  • Redirecting output.
  • +
  • Running commands with / in the name.
  • +
  • Using exec.
  • +

+It’s a coarse sandbox for highly constrained shells; read man bash (RESTRICTED SHELL) for details and caveats.
+
+Example session:
+
+ +
rbash -c 'cd /'            # cd: restricted
+rbash -c 'PATH=/tmp'       # PATH: restricted
+rbash -c 'echo hi > out'   # redirection: restricted
+rbash -c '/bin/echo hi'    # commands with /: restricted
+rbash -c 'exec ls'         # exec: restricted
+
+
+

Useless use of cat (and when it’s ok)


+
+Avoid the extra process if a command already reads files or STDIN:
+
+ +
# Prefer
+grep -i foo file
+<file grep -i foo        # or feed via redirection
+
+# Over
+cat file | grep -i foo
+
+
+But for interactive composition, or when you truly need to concatenate multiple sources into a single stream, cat is fine, as you may think, "First I need the content, then I do X." Changing the "useless use of cat" in retrospect is really a waste of time for one-time interactive use:
+
+ +
cat file1 file2 | grep -i foo
+
+
+From notes: “Good for interactivity; Useless use of cat” — use judgment.
+
+

Atomic locking with mkdir


+
+Portable advisory locks can be emulated with mkdir because it’s atomic:
+
+ +
lockdir=/tmp/myjob.lock
+if mkdir "$lockdir" 2>/dev/null; then
+    trap 'rmdir "$lockdir"' EXIT INT TERM
+    # critical section
+    do_work
+else
+    echo "Another instance is running" >&2
+    exit 1
+fi
+
+
+This works well on Linux. Remove the lock in trap so crashes don’t leave stale locks.
+
+

Smarter globs and faster find-exec


+
+
    +
  • Enable extended globs when useful: shopt -s extglob; then patterns like !(tmp|cache) work.
  • +
  • Use -exec ... {} + to batch many paths in fewer process invocations:
  • +

+ +
find . -name '*.log' -exec gzip -9 {} +
+
+
+Example for extglob (exclude two dirs from listing):
+
+ +
shopt -s extglob
+ls -d -- !(.git|node_modules) 2>/dev/null
+
+
+E-Mail your comments to paul@nospam.buetow.org :-)
+
+Other related posts are:
+
+2025-09-14 Bash Golf Part 4 (You are currently reading this)
+2023-12-10 Bash Golf Part 3
+2022-01-01 Bash Golf Part 2
+2021-11-29 Bash Golf Part 1
+2021-06-05 Gemtexter - One Bash script to rule it all
+2021-05-16 Personal Bash coding style guide
+
+Back to the main site
+ + + diff --git a/gemfeed/atom.xml b/gemfeed/atom.xml index 9aed5466..0cccf072 100644 --- a/gemfeed/atom.xml +++ b/gemfeed/atom.xml @@ -1,11 +1,664 @@ - 2025-09-11T11:09:24+03:00 + 2025-09-13T12:04:03+03:00 foo.zone feed To be in the .zone! https://foo.zone/ + + Bash Golf Part 4 + + https://foo.zone/gemfeed/2025-09-14-bash-golf-part-4.html + 2025-09-13T12:04:03+03:00 + + Paul Buetow aka snonux + paul@dev.buetow.org + + This is the fourth blog post about my Bash Golf series. This series is random Bash tips, tricks, and weirdnesses I have encountered over time. + +
+

Bash Golf Part 4


+
+This is the fourth blog post about my Bash Golf series. This series is random Bash tips, tricks, and weirdnesses I have encountered over time.
+
+2021-11-29 Bash Golf Part 1
+2022-01-01 Bash Golf Part 2
+2023-12-10 Bash Golf Part 3
+2025-09-14 Bash Golf Part 4 (You are currently reading this)
+
+
+    '\       '\        '\        '\                   .  .        |>18>>
+      \        \         \         \              .         ' .   |
+     O>>      O>>       O>>       O>>         .                 'o |
+      \       .\. ..    .\. ..    .\. ..   .                      |
+      /\    .  /\     .  /\     .  /\    . .                      |
+     / /   .  / /  .'.  / /  .'.  / /  .'    .                    |
+jgs^^^^^^^`^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+                        Art by Joan Stark, mod. by Paul Buetow
+
+
+

Table of Contents


+
+
+

Split pipelines with tee + process substitution


+
+Sometimes you want to fan out one stream to multiple consumers and still continue the original pipeline. tee plus process substitution does exactly that:
+
+
+somecommand \
+    | tee >(command1) >(command2) \
+    | command3
+
+
+All of command1, command2, and command3 see the output of somecommand. Example:
+
+ +
printf 'a\nb\n' \
+    | tee >(sed 's/.*/X:&/; s/$/ :c1/') >(tr a-z A-Z | sed 's/$/ :c2/') \
+    | sed 's/$/ :c3/'
+
+
+Output:
+
+
+a :c3
+b :c3
+A :c2 :c3
+B :c2 :c3
+X:a :c1 :c3
+X:b :c1 :c3
+
+
+This relies on Bash process substitution (>(...)). Make sure your shell is Bash and not a POSIX /bin/sh.
+
+Example (fails under dash/POSIX sh):
+
+ +
/bin/sh -c 'echo hi | tee >(cat)'
+# /bin/sh: 1: Syntax error: "(" unexpected
+
+
+Combine with set -o pipefail if failures in side branches should fail the whole pipeline.
+
+Example:
+
+ +
set -o pipefail
+printf 'ok\n' | tee >(false) | cat >/dev/null
+echo $?   # 1 because a side branch failed
+
+
+Further reading:
+
+Splitting pipelines with tee
+
+

Heredocs for remote sessions (and their gotchas)


+
+Heredocs are great to send multiple commands over SSH in a readable way:
+
+ +
ssh "$SSH_USER@$SSH_HOST" <<EOF
+    # Go to the work directory
+    cd "$WORK_DIR"
+  
+    # Make a git pull
+    git pull
+  
+    # Export environment variables required for the service to run
+    export AUTH_TOKEN="$APP_AUTH_TOKEN"
+  
+    # Start the service
+    docker compose up -d --build
+EOF
+
+
+Tips:
+
+Quoting the delimiter changes interpolation. Use <<'EOF' to avoid local expansion and send the content literally.
+
+Example:
+
+ +
FOO=bar
+cat <<'EOF'
+$FOO is not expanded here
+EOF
+
+
+Prefer explicit quoting for variables (as above) to avoid surprises. Example (spaces preserved only when quoted):
+
+ +
WORK_DIR="/tmp/my work"
+ssh host <<EOF
+    cd $WORK_DIR      # may break if unquoted
+    cd "$WORK_DIR"   # safe
+EOF
+
+
+Consider set -euo pipefail at the top of the remote block for stricter error handling. Example:
+
+ +
ssh host <<'EOF'
+    set -euo pipefail
+    false   # causes immediate failure
+    echo never
+EOF
+
+
+Indent-friendly variant: use a dash to strip leading tabs in the body:
+
+ +
cat <<-EOF > script.sh
+	#!/usr/bin/env bash
+	echo "tab-indented content is dedented"
+EOF
+
+
+Further reading:
+
+Heredoc headaches and fixes
+
+

Namespacing and dynamic dispatch with ::


+
+You can emulate simple namespacing by encoding hierarchy in function names. One neat pattern is pseudo-inheritance via a tiny super helper that maps pkg::lang::action to a pkg::base::action default.
+
+ +
#!/usr/bin/env bash
+set -euo pipefail
+
+super() {
+    local -r fn=${FUNCNAME[1]}
+    # Split name on :: and dispatch to base implementation
+    local -a parts=( ${fn//::/ } )
+    "${parts[0]}::base::${parts[2]}" "$@"
+}
+
+foo::base::greet() { echo "base: $@"; }
+foo::german::greet()  { super "Guten Tag, $@!"; }
+foo::english::greet() { super "Good day,  $@!"; }
+
+for lang in german english; do
+    foo::$lang::greet Paul
+done
+
+
+Output:
+
+
+base: Guten Tag, Paul!
+base: Good day,  Paul!
+
+
+

Indirect references with namerefs


+
+declare -n creates a name reference — a variable that points to another variable. It’s cleaner than eval for indirection:
+
+ +
user_name=paul
+declare -n ref=user_name
+echo "$ref"       # paul
+ref=julia
+echo "$user_name" # julia
+
+
+Output:
+
+
+paul
+julia
+
+
+Namerefs are local to functions when declared with local -n. Requires Bash ≥4.3.
+
+You can also construct the target name dynamically:
+
+ +
make_var() {
+    local idx=$1; shift
+    local name="slot_$idx"
+    printf -v "$name" '%s' "$*"   # create variable slot_$idx
+}
+
+get_var() {
+    local idx=$1
+    local -n ref="slot_$idx"      # bind ref to slot_$idx
+    printf '%s\n' "$ref"
+}
+
+make_var 7 "seven"
+get_var 7
+
+
+Output:
+
+
+seven
+
+
+

Function declaration forms


+
+All of these work in Bash, but only the first one is POSIX-ish:
+
+ +
foo() { echo foo; }
+function foo { echo foo; }
+function foo() { echo foo; }
+
+
+Recommendation: prefer name() { ... } for portability and consistency.
+
+

Chaining function calls in conditionals


+
+Functions return a status like commands. You can short-circuit them in conditionals:
+
+ +
deploy_check() { test -f deploy.yaml; }
+smoke_test()   { curl -fsS http://localhost/healthz >/dev/null; }
+
+if deploy_check || smoke_test; then
+    echo "All good."
+else
+    echo "Something failed." >&2
+fi
+
+
+You can also compress it golf-style:
+
+ +
deploy_check || smoke_test && echo ok || echo fail >&2
+
+
+

Grep, sed, awk quickies


+
+Word match and context: grep -w word file; with context: grep -C3 foo file (same as -A3 -B3). Example:
+
+ +
cat > /tmp/ctx.txt <<EOF
+one
+foo
+two
+three
+bar
+EOF
+grep -C1 foo /tmp/ctx.txt
+
+
+Output:
+
+
+one
+foo
+two
+
+
+Skip a directory while recursing: grep -R --exclude-dir=foo 'bar' /path. Example:
+
+ +
mkdir -p /tmp/golf/foo /tmp/golf/src
+printf 'bar\n' > /tmp/golf/src/a.txt
+printf 'bar\n' > /tmp/golf/foo/skip.txt
+grep -R --exclude-dir=foo 'bar' /tmp/golf
+
+
+Output:
+
+
+/tmp/golf/src/a.txt:bar
+
+
+Insert lines with sed: sed -e '1isomething' -e '3isomething' file. Example:
+
+ +
printf 'A\nB\nC\n' > /tmp/s.txt
+sed -e '1iHEAD' -e '3iMID' /tmp/s.txt
+
+
+Output:
+
+
+HEAD
+A
+B
+MID
+C
+
+
+Drop last column with awk: awk 'NF{NF-=1};1' file. Example:
+
+ +
printf 'a b c\nx y z\n' > /tmp/t.txt
+cat /tmp/t.txt
+echo
+awk 'NF{NF-=1};1' /tmp/t.txt
+
+
+Output:
+
+
+a b c
+x y z
+
+a b
+x y
+
+
+

Safe xargs with NULs


+
+Avoid breaking on spaces/newlines by pairing find -print0 with xargs -0:
+
+ +
find . -type f -name '*.log' -print0 | xargs -0 rm -f
+
+
+Example with spaces and NULs only:
+
+ +
printf 'a\0b c\0' | xargs -0 -I{} printf '<%s>\n' {}
+
+
+Output:
+
+
+<a>
+<b c>
+
+
+

Efficient file-to-variable and arrays


+
+Read a whole file into a variable without spawning cat:
+
+ +
cfg=$(<config.ini)
+
+
+Read lines into an array safely with mapfile (aka readarray):
+
+ +
mapfile -t lines < <(grep -v '^#' config.ini)
+printf '%s\n' "${lines[@]}"
+
+
+Assign formatted strings without a subshell using printf -v:
+
+ +
printf -v msg 'Hello %s, id=%04d' "$USER" 42
+echo "$msg"
+
+
+Output:
+
+
+Hello paul, id=0042
+
+
+Read NUL-delimited data (pairs well with -print0):
+
+ +
mapfile -d '' -t files < <(find . -type f -print0)
+printf '%s\n' "${files[@]}"
+
+
+

Quick password generator


+
+Pure Bash with /dev/urandom:
+
+ +
LC_ALL=C tr -dc 'A-Za-z0-9_' </dev/urandom | head -c 16; echo
+
+
+Alternative using openssl:
+
+ +
openssl rand -base64 16 | tr -d '\n' | cut -c1-22
+
+
+

yes for automation


+
+yes streams a string repeatedly; handy for feeding interactive commands or quick load generation:
+
+ +
yes | rm -r large_directory        # auto-confirm
+yes n | dangerous-command          # auto-decline
+yes anything | head -n1            # prints one line: anything
+
+
+

Forcing true to fail (and vice versa)


+
+You can shadow builtins with functions:
+
+ +
true()  { return 1; }
+false() { return 0; }
+
+true  || echo 'true failed'
+false && echo 'false succeeded'
+
+# Bypass function with builtin/command
+builtin true # returns 0
+command true # returns 0
+
+
+To disable a builtin entirely: enable -n true (re-enable with enable true).
+
+Further reading:
+
+Force true to return false
+
+

Restricted Bash


+
+bash -r (or rbash) starts a restricted shell that limits potentially dangerous actions, for example:
+
+
    +
  • Changing directories (cd).
  • +
  • Modifying PATH, SHELL, BASH_ENV, or ENV.
  • +
  • Redirecting output.
  • +
  • Running commands with / in the name.
  • +
  • Using exec.
  • +

+It’s a coarse sandbox for highly constrained shells; read man bash (RESTRICTED SHELL) for details and caveats.
+
+Example session:
+
+ +
rbash -c 'cd /'            # cd: restricted
+rbash -c 'PATH=/tmp'       # PATH: restricted
+rbash -c 'echo hi > out'   # redirection: restricted
+rbash -c '/bin/echo hi'    # commands with /: restricted
+rbash -c 'exec ls'         # exec: restricted
+
+
+

Useless use of cat (and when it’s ok)


+
+Avoid the extra process if a command already reads files or STDIN:
+
+ +
# Prefer
+grep -i foo file
+<file grep -i foo        # or feed via redirection
+
+# Over
+cat file | grep -i foo
+
+
+But for interactive composition, or when you truly need to concatenate multiple sources into a single stream, cat is fine, as you may think, "First I need the content, then I do X." Changing the "useless use of cat" in retrospect is really a waste of time for one-time interactive use:
+
+ +
cat file1 file2 | grep -i foo
+
+
+From notes: “Good for interactivity; Useless use of cat” — use judgment.
+
+

Atomic locking with mkdir


+
+Portable advisory locks can be emulated with mkdir because it’s atomic:
+
+ +
lockdir=/tmp/myjob.lock
+if mkdir "$lockdir" 2>/dev/null; then
+    trap 'rmdir "$lockdir"' EXIT INT TERM
+    # critical section
+    do_work
+else
+    echo "Another instance is running" >&2
+    exit 1
+fi
+
+
+This works well on Linux. Remove the lock in trap so crashes don’t leave stale locks.
+
+

Smarter globs and faster find-exec


+
+
    +
  • Enable extended globs when useful: shopt -s extglob; then patterns like !(tmp|cache) work.
  • +
  • Use -exec ... {} + to batch many paths in fewer process invocations:
  • +

+ +
find . -name '*.log' -exec gzip -9 {} +
+
+
+Example for extglob (exclude two dirs from listing):
+
+ +
shopt -s extglob
+ls -d -- !(.git|node_modules) 2>/dev/null
+
+
+E-Mail your comments to paul@nospam.buetow.org :-)
+
+Other related posts are:
+
+2025-09-14 Bash Golf Part 4 (You are currently reading this)
+2023-12-10 Bash Golf Part 3
+2022-01-01 Bash Golf Part 2
+2021-11-29 Bash Golf Part 1
+2021-06-05 Gemtexter - One Bash script to rule it all
+2021-05-16 Personal Bash coding style guide
+
+Back to the main site
+
+
+
Random Weird Things - Part Ⅲ @@ -8667,7 +9320,7 @@ jgs \\`_..---.Y.---.._`//
As I mentioned, keyboards will remain an expensive hobby of mine. I don't regret anything here, though. After all, I use keyboards at my day job. I've ordered a Kinesis custom build with the Gateron Kangaroo switches, and I'm excited to see how that compares to my current setup. I'm still deciding whether to keep my Gateron Brown-equipped Kinesis as a secondary keyboard or possibly leave it at my in-laws for use when visiting or to sell it.

-Update 2025-02-22: I've received my custom Kinesis Adv. 360 build with the Gateron Baby Kangaroo key switches. I am absolutely in love! I will keep my Gateron Brown versin around, though.
+Update 2025-02-22: I've received my custom Kinesis Adv. 360 build with the Gateron Baby Kangaroo key switches. I am absolutely in love! I will keep my Gateron Brown version around, though.

Conclusion



@@ -10792,6 +11445,8 @@ $ doas sysupgrade # Update all binaries (including Kerne
sysupgrade downloaded and upgraded to the next release and rebooted the system. After the reboot, I run:

+Note to myself: I have to undo the /var/www symlink before upgrading, and re-establishing the symlink afterwards again. This is due to disk space constraings on my setup!
+
2023-04-01 "Never split the difference" book notes
2023-03-16 "The Pragmatic Programmer" book notes

-Back to the main site
- - -
- - KISS server monitoring with Gogios - - https://foo.zone/gemfeed/2023-06-01-kiss-server-monitoring-with-gogios.html - 2023-06-01T21:10:17+03:00 - - Paul Buetow aka snonux - paul@dev.buetow.org - - Gogios is a minimalistic and easy-to-use monitoring tool I programmed in Google Go designed specifically for small-scale self-hosted servers and virtual machines. The primary purpose of Gogios is to monitor my personal server infrastructure for `foo.zone`, my MTAs, my authoritative DNS servers, my NextCloud, Wallabag and Anki sync server installations, etc. - -
-

KISS server monitoring with Gogios


-
-Published at 2023-06-01T21:10:17+03:00
-
-Gogios is a minimalistic and easy-to-use monitoring tool I programmed in Google Go designed specifically for small-scale self-hosted servers and virtual machines. The primary purpose of Gogios is to monitor my personal server infrastructure for foo.zone, my MTAs, my authoritative DNS servers, my NextCloud, Wallabag and Anki sync server installations, etc.
-
-With compatibility with the Nagios Check API, Gogios offers a simple yet effective solution to monitor a limited number of resources. In theory, Gogios scales to a couple of thousand checks, though. You can clone it from Codeberg here:
-
-https://codeberg.org/snonux/gogios
-
-Gogios logo
-
-

Table of Contents


-
-
-
-    _____________________________    ____________________________
-   /                             \  /                            \
-  |    _______________________    ||    ______________________    |
-  |   /                       \   ||   /                      \   |
-  |   | # Alerts with status c|   ||   | # Unhandled alerts:  |   |
-  |   | hanged:               |   ||   |                      |   |
-  |   |                       |   ||   | CRITICAL: Check Pizza|   |
-  |   | OK->CRITICAL: Check Pi|   ||   | : Late delivery      |   |
-  |   | zza: Late delivery    |   ||   |                      |   |
-  |   |                       |   ||   | WARNING: Check Thirst|   |
-  |   |                       |   ||   | : OutofKombuchaExcept|   |
-  |   \_______________________/   ||   \______________________/   |
-  |  /|\ GOGIOS MONITOR 1    _    ||  /|\ GOGIOS MONITOR 2   _    |
-   \_____________________________/  \____________________________/
-     !_________________________!      !________________________!
-
-------------------------------------------------
-ASCII art was modified by Paul Buetow
-The original can be found at
-https://asciiart.website/index.php?art=objects/computers
-
-
-

Motivation


-
-With experience in monitoring solutions like Nagios, Icinga, Prometheus and OpsGenie, these tools often came with many features that I didn't necessarily need for personal use. Contact groups, host groups, check clustering, and the requirement of operating a DBMS and a WebUI added complexity and bloat to my monitoring setup.
-
-My primary goal was to have a single email address for notifications and a simple mechanism to periodically execute standard Nagios check scripts and notify me of any state changes. I wanted the most minimalistic monitoring solution possible but wasn't satisfied with the available options.
-
-This led me to create Gogios, a lightweight monitoring tool tailored to my specific needs. I chose the Go programming language for this project as it comes, in my opinion, with the best balance of ease to use and performance.
-
-

Features


-
-
    -
  • Compatible with Nagios Check scripts: Gogios leverages the widely-used Nagios Check API, allowing to use existing Nagios plugins.
  • -
  • Lightweight and Minimalistic: Gogios is designed to be simple and fairly easy to set up.
  • -
  • Configurable Check Timeout and Concurrency: Gogios allows you to set a timeout for checks and configure the number of concurrent checks, offering flexibility in monitoring your resources.
  • -
  • Configurable check dependency: A check can depend on another check, which enables scenarios like not executing an HTTP check when the server isn't pingable.
  • -
  • Retries: Check retry and retry intervals are configurable per check.
  • -
  • Email Notifications: Gogios can send email notifications regarding the status of monitored services, ensuring you stay informed about potential issues.
  • -
  • CRON-based Execution: Gogios can be quickly scheduled to run periodically via CRON, allowing you to automate monitoring without needing a complex setup.
  • -

-

Example alert


-
-This is an example alert report received via E-Mail. Whereas, [C:2 W:0 U:0 OK:51] means that we've got two alerts in status critical, 0 warnings, 0 unknowns and 51 OKs.
-
-
-Subject: GOGIOS Report [C:2 W:0 U:0 OK:51]
-
-This is the recent Gogios report!
-
-# Alerts with status changed:
-
-OK->CRITICAL: Check ICMP4 vulcan.buetow.org: Check command timed out
-OK->CRITICAL: Check ICMP6 vulcan.buetow.org: Check command timed out
-
-# Unhandled alerts:
-
-CRITICAL: Check ICMP4 vulcan.buetow.org: Check command timed out
-CRITICAL: Check ICMP6 vulcan.buetow.org: Check command timed out
-
-Have a nice day!
-
-
-

Installation


-
-

Compiling and installing Gogios


-
-This document is primarily written for OpenBSD, but applying the corresponding steps to any Unix-like (e.g. Linux-based) operating system should be easy. On systems other than OpenBSD, you may always have to replace does with the sudo command and replace the /usr/local/bin path with /usr/bin.
-
-To compile and install Gogios on OpenBSD, follow these steps:
-
- -
git clone https://codeberg.org/snonux/gogios.git
-cd gogios
-go build -o gogios cmd/gogios/main.go
-doas cp gogios /usr/local/bin/gogios
-doas chmod 755 /usr/local/bin/gogios
-
-
-You can use cross-compilation if you want to compile Gogios for OpenBSD on a Linux system without installing the Go compiler on OpenBSD. Follow these steps:
-
- -
export GOOS=openbsd
-export GOARCH=amd64
-go build -o gogios cmd/gogios/main.go
-
-
-On your OpenBSD system, copy the binary to /usr/local/bin/gogios and set the correct permissions as described in the previous section. All steps described here you could automate with your configuration management system of choice. I use Rexify, the friendly configuration management system, to automate the installation, but that is out of the scope of this document.
-
-https://www.rexify.org
-
-

Setting up user, group and directories


-
-It is best to create a dedicated system user and group for Gogios to ensure proper isolation and security. Here are the steps to create the _gogios user and group under OpenBSD:
-
- -
doas adduser -group _gogios -batch _gogios
-doas usermod -d /var/run/gogios _gogios
-doas mkdir -p /var/run/gogios
-doas chown _gogios:_gogios /var/run/gogios
-doas chmod 750 /var/run/gogios
-
-
-Please note that creating a user and group might differ depending on your operating system. For other operating systems, consult their documentation for creating system users and groups.
-
-

Installing monitoring plugins


-
-Gogios relies on external Nagios or Icinga monitoring plugin scripts. On OpenBSD, you can install the monitoring-plugins package with Gogios. The monitoring-plugins package is a collection of monitoring plugins, similar to Nagios plugins, that can be used to monitor various services and resources:
-
- -
doas pkg_add monitoring-plugins
-doas pkg_add nrpe # If you want to execute checks remotely via NRPE.
-
-
-Once the installation is complete, you can find the monitoring plugins in the /usr/local/libexec/nagios directory, which then can be configured to be used in gogios.json.
-
-

Configuration


-
-

MTA


-
-Gogios requires a local Mail Transfer Agent (MTA) such as Postfix or OpenBSD SMTPD running on the same server where the CRON job (see about the CRON job further below) is executed. The local MTA handles email delivery, allowing Gogios to send email notifications to monitor status changes. Before using Gogios, ensure that you have a properly configured MTA installed and running on your server to facilitate the sending of emails. Once the MTA is set up and functioning correctly, Gogios can leverage it to send email notifications.
-
-You can use the mail command to send an email via the command line on OpenBSD. Here's an example of how to send a test email to ensure that your email server is working correctly:
-
-
-echo 'This is a test email from OpenBSD.' | mail -s 'Test Email' your-email@example.com
-
-
-Check the recipient's inbox to confirm the delivery of the test email. If the email is delivered successfully, it indicates that your email server is configured correctly and functioning. Please check your MTA logs in case of issues.
-
-

Configuring Gogios


-
-To configure Gogios, create a JSON configuration file (e.g., /etc/gogios.json). Here's an example configuration:
-
- -
{
-  "EmailTo": "paul@dev.buetow.org",
-  "EmailFrom": "gogios@buetow.org",
-  "CheckTimeoutS": 10,
-  "CheckConcurrency": 2,
-  "StateDir": "/var/run/gogios",
-  "Checks": {
-    "Check ICMP4 www.foo.zone": {
-      "Plugin": "/usr/local/libexec/nagios/check_ping",
-      "Args": [ "-H", "www.foo.zone", "-4", "-w", "50,10%", "-c", "100,15%" ],
-      "Retries": 3,
-      "RetryInterval": 10
-    },
-    "Check ICMP6 www.foo.zone": {
-      "Plugin": "/usr/local/libexec/nagios/check_ping",
-      "Args": [ "-H", "www.foo.zone", "-6", "-w", "50,10%", "-c", "100,15%" ],
-      "Retries": 3,
-      "RetryInterval": 10
-    },
-    "www.foo.zone HTTP IPv4": {
-      "Plugin": "/usr/local/libexec/nagios/check_http",
-      "Args": ["www.foo.zone", "-4"],
-      "DependsOn": ["Check ICMP4 www.foo.zone"]
-    },
-    "www.foo.zone HTTP IPv6": {
-      "Plugin": "/usr/local/libexec/nagios/check_http",
-      "Args": ["www.foo.zone", "-6"],
-      "DependsOn": ["Check ICMP6 www.foo.zone"]
-    }
-    "Check NRPE Disk Usage foo.zone": {
-      "Plugin": "/usr/local/libexec/nagios/check_nrpe",
-      "Args": ["-H", "foo.zone", "-c", "check_disk", "-p", "5666", "-4"]
-    }
-  }
-}
-
-
-
    -
  • EmailTo: Specifies the recipient of the email notifications.
  • -
  • EmailFrom: Indicates the sender's email address for email notifications.
  • -
  • CheckTimeoutS: Sets the timeout for checks in seconds.
  • -
  • CheckConcurrency: Determines the number of concurrent checks that can run simultaneously.
  • -
  • StateDir: Specifies the directory where Gogios stores its persistent state in a state.json file.
  • -
  • Checks: Defines a list of checks to be performed, each with a unique name, plugin path, and arguments.
  • -

-Adjust the configuration file according to your needs, specifying the checks you want Gogios to perform.
-
-If you want to execute checks only when another check succeeded (status OK), use DependsOn. In the example above, the HTTP checks won't run when the hosts aren't pingable. They will show up as UNKNOWN in the report.
-
-Retries and RetryInterval are optional check configuration parameters. In case of failure, Gogios will retry Retries times each RetryInterval seconds.
-
-For remote checks, use the check_nrpe plugin. You also need to have the NRPE server set up correctly on the target host (out of scope for this document).
-
-The state.json file mentioned above keeps track of the monitoring state and check results between Gogios runs, enabling Gogios only to send email notifications when there are changes in the check status.
-
-

Running Gogios


-
-Now it is time to give it a first run. On OpenBSD, do:
-
- -
doas -u _gogios /usr/local/bin/gogios -cfg /etc/gogios.json
-
-
-To run Gogios via CRON on OpenBSD as the gogios user and check all services once per minute, follow these steps:
-
-Type doas crontab -e -u _gogios and press Enter to open the crontab file for the _gogios user for editing and add the following lines to the crontab file:
-
-
-*/5 8-22 * * * /usr/local/bin/gogios -cfg /etc/gogios.json
-0 7 * * * /usr/local/bin/gogios -renotify -cfg /etc/gogios.json
-
-
-Gogios is now configured to run every five minutes from 8 am to 10 pm via CRON as the _gogios user. It will execute the checks and send monitoring status whenever a check status changes via email according to your configuration. Also, Gogios will run once at 7 am every morning and re-notify all unhandled alerts as a reminder.
-
-

High-availability


-
-To create a high-availability Gogios setup, you can install Gogios on two servers that will monitor each other using the NRPE (Nagios Remote Plugin Executor) plugin. By running Gogios in alternate CRON intervals on both servers, you can ensure that even if one server goes down, the other will continue monitoring your infrastructure and sending notifications.
-
-
    -
  • Install Gogios on both servers following the compilation and installation instructions provided earlier.
  • -
  • Install the NRPE server (out of scope for this document) and plugin on both servers. This plugin allows you to execute Nagios check scripts on remote hosts.
  • -
  • Configure Gogios on both servers to monitor each other using the NRPE plugin. Add a check to the Gogios configuration file (/etc/gogios.json) on both servers that uses the NRPE plugin to execute a check script on the other server. For example, if you have Server A and Server B, the configuration on Server A should include a check for Server B, and vice versa.
  • -
  • Set up alternate CRON intervals on both servers. Configure the CRON job on Server A to run Gogios at minutes 0, 10, 20, ..., and on Server B to run at minutes 5, 15, 25, ... This will ensure that if one server goes down, the other server will continue monitoring and sending notifications.
  • -
  • Gogios doesn't support clustering. So it means when both servers are up, unhandled alerts will be notified via E-Mail twice; from each server once. That's the trade-off for simplicity.
  • -

-There are plans to make it possible to execute certain checks only on certain nodes (e.g. on elected leader or master nodes). This is still in progress (check out my Gorum Git project).
-
-

Conclusion:


-
-Gogios is a lightweight and straightforward monitoring tool that is perfect for small-scale environments. With its compatibility with the Nagios Check API, email notifications, and CRON-based scheduling, Gogios offers an easy-to-use solution for those looking to monitor a limited number of resources. I personally use it to execute around 500 checks on my personal server infrastructure. I am very happy with this solution.
-
-E-Mail your comments to paul@nospam.buetow.org :-)
-
-Other KISS-related posts are:
-
-2024-04-01 KISS high-availability with OpenBSD
-2023-10-29 KISS static web photo albums with photoalbum.sh
-2023-06-01 KISS server monitoring with Gogios (You are currently reading this)
-2021-09-12 Keep it simple and stupid
-
Back to the main site
diff --git a/gemfeed/index.html b/gemfeed/index.html index 6ed46931..635a36bb 100644 --- a/gemfeed/index.html +++ b/gemfeed/index.html @@ -15,6 +15,7 @@

To be in the .zone!



+2025-09-14 - Bash Golf Part 4
2025-08-15 - Random Weird Things - Part Ⅲ
2025-08-05 - Local LLM for Coding with Ollama on macOS
2025-07-14 - f3s: Kubernetes with FreeBSD - Part 6: Storage
diff --git a/index.html b/index.html index 08d6072d..20a7d978 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@

Hello!



-This site was generated at 2025-09-11T11:13:29+03:00 by Gemtexter
+This site was generated at 2025-09-13T12:04:03+03:00 by Gemtexter

Welcome to the foo.zone!

@@ -51,6 +51,7 @@

Posts



+2025-09-14 - Bash Golf Part 4
2025-08-15 - Random Weird Things - Part Ⅲ
2025-08-05 - Local LLM for Coding with Ollama on macOS
2025-07-14 - f3s: Kubernetes with FreeBSD - Part 6: Storage
diff --git a/uptime-stats.html b/uptime-stats.html index 0fc914dc..70a5ffc8 100644 --- a/uptime-stats.html +++ b/uptime-stats.html @@ -13,7 +13,7 @@

My machine uptime stats



-This site was last updated at 2025-09-11T11:13:29+03:00
+This site was last updated at 2025-09-13T12:04:03+03:00

The following stats were collected via uptimed on all of my personal computers over many years and the output was generated by guprecords, the global uptime records stats analyser of mine.

-- cgit v1.2.3