Useful scripts

Dumping a little helper for sysutils/vm-bhyve I created today, adding a possibility for a "post-startup" script in a horribly hacky way ? ... placed in /var/vm/poststartup.sh

Bash:
#!/bin/sh

vm_script=/var/vm/${1}/poststartup.sh
vm_logfile=/var/vm/${1}/vm-bhyve.log

vm_log()
{
  if [ -w ${vm_logfile} ]; then
    LANG=C echo "`date '+%b %d %H:%M:%S:'` $@" >>${vm_logfile}
  fi
}

if [ -r ${vm_script} ]; then (
  sleep 1;

  for i in 1 2 3 4 5 6 7 8; do
    vm_status=`vm list | grep "^${1} .* Running ("`
    if [ -n "${vm_status}" ]; then
      vm_pid=`echo "${vm_status}" | sed -e 's:.*Running (\([0-9]*\).*:\1:'`
      vm_log poststartup.sh: Running ${vm_script}.
      . ${vm_script}
      exit
    else
      sleep 1
    fi
  done
  vm_log poststartup.sh: Giving up, vm is not running!
) </dev/null >/dev/null 2>&1 &
else
  vm_log poststartup.sh: ${vm_script} is missing!
fi

To use it, just add prestart="/var/vm/poststartup.sh" to your vm config and place an individual poststartup.sh in its directory. E.g. I have the following for my router/firewall vm, so that routing is never ever slowed down or killed by OOM:

Code:
protect -p ${vm_pid}
rtprio 15 -${vm_pid}
 
why did you use () instead of {} for command grouping ?
Dumping a little helper for sysutils/vm-bhyve I created today, adding a possibility for a "post-startup" script in a horribly hacky way ? ... placed in /var/vm/poststartup.sh

Bash:
#!/bin/sh

vm_script=/var/vm/${1}/poststartup.sh
vm_logfile=/var/vm/${1}/vm-bhyve.log

vm_log()
{
  if [ -w ${vm_logfile} ]; then
    LANG=C echo "`date '+%b %d %H:%M:%S:'` $@" >>${vm_logfile}
  fi
}

if [ -r ${vm_script} ]; then (
  sleep 1;

  for i in 1 2 3 4 5 6 7 8; do
    vm_status=`vm list | grep "^${1} .* Running ("`
    if [ -n "${vm_status}" ]; then
      vm_pid=`echo "${vm_status}" | sed -e 's:.*Running (\([0-9]*\).*:\1:'`
      vm_log poststartup.sh: Running ${vm_script}.
      . ${vm_script}
      exit
    else
      sleep 1
    fi
  done
  vm_log poststartup.sh: Giving up, vm is not running!
) </dev/null >/dev/null 2>&1 &
else
  vm_log poststartup.sh: ${vm_script} is missing!
fi

To use it, just add prestart="/var/vm/poststartup.sh" to your vm config and place an individual poststartup.sh in its directory. E.g. I have the following for my router/firewall vm, so that routing is never ever slowed down or killed by OOM:

Code:
protect -p ${vm_pid}
rtprio 15 -${vm_pid}
 
covacat That's not for grouping, I need the subshell (running in background) here, otherwise I'd block the parent preventing it from actually starting the vm, defeating the purpose of the script ?
 
last day of month
date -v -1d -j $(date -v +1m +%Y%m010000) +%d
or say you want to run a cron job in the last day of the month
Code:
#!/bin/sh
[ $(date -v +1d +%m) != $(date  +%m) ] || exit 0
# actual stuff here
Hmm. Today is the last day of the month if tomorrow is a first. That's one less invocation of date:

if test $(date -v +1d +%d) = 01; then; ...; fi
 
Secure Subversion (via svnserve)

Though git tends to be more popular these days, one of the best aspects of Subversion is the standalone server (svnserve) with simple authentication and virtual accounts making it suitable for small to medium teams. Unfortunately traffic (minus passwords) are sent in plain text. One solution is SASL which is fiddly and pulls in a load of random dependencies. Another solution is simply wrapping it all via OpenSSL (and svnserve via stunnel). The following is a quick overview how to do it:

ssvn (svn wrapper)
Code:
#!/bin/sh

set -e

if [ -n "$SVN_TUNNEL" ]; then
  exec openssl s_client -quiet -connect "$1:3691" 2>/dev/null
fi

export SVN_TUNNEL=1

exec svn \
  --no-auth-cache \
  --config-option config:tunnels:ssl="$0" \
  "$@"

stunnel.conf (i.e $ stunnel /path/to/stunnel.conf)
Code:
foreground = yes

[svn]
accept = 3691
connect = 3690
cert = /path/to/random/self/signed.pem

With this in place, you can now access repositories securely using svn+ssl://. For example:

$ ssvn co svn+ssl://example.com/myproj/trunk myproj
 
what a cool topic!!!

I have an script for taking zfs snapshots and sending to an backup HD that are not mounted. The script mounts the partition, and sends the snapshots as backups:


sh:
#!/bin/sh

new_snapshot="zroot@$(date +%d-%m-%Y)"
log_file="/var/log/backup.log"

send_not_script="/root/.dotfiles/scripts/send_notifications"

log_with_timestamp() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$log_file"
}

send_notification() {
    message="$1"
    sh $send_not_script "ZFS Backup" "$message" # Call the send_not script with the message
}

attach_backup_pool() {
    geli_partition="/dev/ada0p2"
    geli_keyfile="/root/keys/ada0p2.key"
    echo | geli attach -k $geli_keyfile -j - $geli_partition
    poolname=$(zpool import | grep "pool:" | awk '{print $2}')
    if [ -z "$(zpool list | grep $poolname)" ]; then
        zpool import -f $poolname
    fi
}

log_with_timestamp "Attaching Backup Pool"
send_notification "Attaching Backup Pool"
attach_backup_pool

log_with_timestamp "Creating new snapshot: $new_snapshot"
zfs snapshot -r "$new_snapshot" >> "$log_file" 2>> "$log_file"

if [ $? -eq 0 ]; then
    log_with_timestamp "Snapshot $new_snapshot created successfully."
    send_notification "Snapshot $new_snapshot created successfully."
   
    log_with_timestamp "Sending snapshot $new_snapshot to backup pool."

    zfs send -R "$new_snapshot" > /backup/"$new_snapshot"
    if [ $? -eq 0 ]; then
        log_with_timestamp "Successfully sent $new_snapshot to backup pool."
        send_notification "Successfully sent $new_snapshot to backup pool."
    else
        log_with_timestamp "Error sending $new_snapshot to backup pool."
        send_notification "Error sending $new_snapshot to backup pool."
    fi
else
    log_with_timestamp "Error creating snapshot $new_snapshot."
    send_notification "Error creating snapshot $new_snapshot."
fi

if [ ! -z "$(zpool list | grep $poolname)" ]; then
    zpool export $poolname
fi

geli detach $geli_partition

send notification:


sh:
#!/bin/sh

tittle="$1"
message="$2"
user="base"
export DISPLAY=":0"
doas -u $user env DISPLAY=$DISPLAY notify-send "$tittle" "$message"


the funny part is that I never tried to revert or actualy see if I can recover a system from the snapshots. But this makes me feel safe anyways.
 
Made a devd rule to have my joystick move to /dev/uhid# automatically for FlightGear:

Code:
/usr/local/etc/devd/sidewinder-precision-pro-joystick-uhidd.conf

Code:
notify 0 {
        match "type" "ATTACH";
        match "ugen" "ugen[0-9]+.[0-9]+";
        match "vendor" "0x045e";
        match "product" "0x0008";
        action "/usr/local/sbin/uhidd -h -H uhid -u /dev/$ugen";
};
 
This is a script I wrote to take care of the arduous task of updating the ssh key of a single machine on some the machines listed in the variable, HOSTS below. Comments and suggestions are welcome.
I’m not sure whether this was part of a FreeBSD installation in 2009, but today you can use ssh-copy-id(1). Moreover, it prevents duplicate entries, too. ?️‍♀️
Some backup tools have problems with long names. So we have to check the length of the path + file name. This can be done with: […]
Depending on your requirements pathchk(1) might be just the right tool for you. ?​
Bash:
pathchk -p '/some/path/to/check' # -p stands for portability check
Some of people want to ping some hosts and find which host is up […]
Consider setting up rwhod(8) instead. ?️​
This night I was compiling LibreOffice. By one o'clock it still wasn't finished.
I didn't want computer to stay up all night, so I wrote this little script: […]
Bash:
pwait $(pgrep make) && shutdown -p now # replace `make` as appropriate☻
I would only change that above into that below: […]
[…] Yes, that seems stupid running /usr/bin/basename twelve times instead of one. […]
You use basename(1) if you know nothing about the value’s structure (read user input). ?‍♀️ We know about $0 though that it successfully led to reading our shell script, so it is a valid pathname to a regular file, and thus using parameter expansion is sufficient; no spawning of new processes necessary.​
Bash:
printf 'My name is %s.\n' "${0##*/}" # `##` means remove largest prefix
[…] Please, post here useful scripts which can help to do some useful or difficult operation on FreeBSD OS. […]
Yeah, the above comments exemplify sometimes it’s just ignorance. The difficultly really lies in knowing your way around. ?️ And I certainly scripted some redundant stuff, too.​
Here is a very simple little script that I use to reduce the size of pictures. […]
It may be worth pointing out any prerequisites; in your particular script not everyone necessarily knows convert(1) is part of graphics/ImageMagick. ?​
In bash I use alias recall='history | egrep' so that I can do something like recall ssh. […]
And if you set HISTIGNORE='recall *' it does not even pollute the history. ☸️
admin script... […]
admin(1) is the name of a POSIX™‐standardized utility from devel/sccs, so maybe it’s not the best choice. ?​
[…] Sometimes it's difficult to locate a particular directory in a long $PATH variable. […]
$ echo $PATH | tr ':' ' ' | xargs -n 1 echoYou can probably write this even shorter
How about
% echo $PATH | tr ':' '\n'
Neat, yet this implementation does not take account of the possibility that $PATH members may be empty strings. ?‍⬛ An empty string implicilty denotes the current working directory; concededly, this notation is a legacy feature.​
Here's better one
$ echo $PATH | awk 'BEGIN {RS=":"}; {print $0}'

this one will respect spaces in directories
I always get a blank line at the end. Considering that an empty string is a valid $PATH specification, this is a bug. ? My take on the task:​
Bash:
#!/bin/sh -u
#        name: print path
# description: list values of `${PATH}` and highlight current working directory
#  maintainer: Firstname Lastname <mailbox@host> # in multi‐sysadmin environment

# Reset graphics rendition selection.
# `2>&-` removes any complaint should `tput` not be accessible via `${PATH}`.
[ -t 1 ] && tput sgr0 2>&-
# We presume this script never gets `source`d by another script.
IFS=':'
# The empty strings are necessary so a leading or trailing `:`
# is regarded as a word _splitter_, i. e. a character _between_ two words.
for path in ''${PATH:?Empty \$PATH has implementation-defined behavior.}''
do
    # Enumerating items is helpful
    # if there are too few `tput lines` to display everything on one screen.
    printf '%2d. ' "${list_item_number:=1}"
    list_item_number=$((${list_item_number} + 1))
   
    # Print current working directory in bold (if supported).
    [ -t 1 ] && [ "${path%.}"              ] || tput bold 2>&-
    # Some people like to indicate directories with a trailing slash.
    [ -t 1 ] && [ "${path%/}" = "${PWD%/}" ] && tput bold 2>&-
   
    # Empty string implicitly means current working directory.
    printf '%s\n' "${path:-(current working directory)}"
    # The last command in a loop determines the entire loop’s exit status.
    # If `tput` is not present in `${PATH}`, this will be non‐zero.
    # We want to report success, though, hence the null command `:`.
    [ -t 1 ] && tput sgr0 2>&- || :
done
# EOF
which - Obtain a full path to the script if run from $PATH (i.e 'run_blender')
Why do you still include a which when olli@ clarified:​
[…] When you call a shell script via PATH, $0 always includes the directory in which it was found.
cd && pwd - Use temp shell to cd into directory and output current directory name (solves relative paths)
Unnecessary, realpath(1) always returns an absolute pathname. ? FYI, the GNU coreutils version of realpath grealpath(1) lets you retain symbolic links unresolved. This is the only reason I could think of for using the cd/ pwd detour (if it had not been for realpath(1) already resolving symbolic links).​
Bash:
grealpath -L "${0}" # -L prints logical pathname, symlinks remaining unresolved
 
Why do you still include a which when olli@ clarified:​
Because after testing it on a range of different (often non-conforming) platforms, the experience did not align to the clarification. Robustness is key and of course some interpreters strip off the path if it is absolute.

Unnecessary, realpath(1) always returns an absolute pathname.​
Similarly. Robustness. I recall there was an (older) version of busybox who's realpath was not non-conforming on the Windows platform. Cygwin also had issues due to the logical drive mapping. The cd && pwd worked around that.

This script has been evolved over many years to be "robust". Making an assumption that everything is perfectly aligned with POSIX unfortunately undermines that. That said, it is still a "best effort" approach. There are at least two platforms I have used that which fails when a relative path is passed through it. So feel free to cut parts out as needed.
 
Code:
#!/bin/sh

# 0--------------------------------------------------------0
# | validate_ip                                            |
# | resource-efficient sh-script to validate a given v4 ip |
# 0--------------------------------------------------------0

ip=$1
result="."
if [ ${#ip} -lt 7 ]
then
  # -> too short
  result="invalid"
else
  if [ ${#ip} -gt 15 ]
  then
    # -> too long
    result="invalid"
  else
    result="valid"
    if [ $(echo "$ip" | sed 's/[^.]//g' | awk '{ print length }') -ne 3 ]
    then
      # -> no 3 dots
      result="invalid"
    else
      result="valid"
      digit1=$(echo $ip | cut -d '.' -f 1)
      case $digit1 in
        (*[^0-9]*|'')
          # -> not a number
          result="invalid"
        ;;
        (*)
          result="valid"
          if [ $digit1 -lt 0 ] || [ $digit1 -gt 255 ]
          then
            # -> 1st byte invalid (0..255)
            result="invalid"
          else
            digit2=$(echo $ip | cut -d '.' -f 2)
            case $digit2 in
              (*[^0-9]*|'')
                # -> not a number
                result="invalid"
              ;;
              (*)
                result="valid"
                if [ $digit2 -lt 0 ] || [ $digit2 -gt 255 ]
                then
                  # -> 2nd byte invalid (0..255)
                  result="invalid"
                else
                  result="valid"
                  digit3=$(echo $ip | cut -d '.' -f 3)
                  case $digit3 in
                    (*[^0-9]*|'')
                      # -> not a number
                      result="invalid"
                    ;;
                    (*)
                      result="valid"
                      if [ $digit3 -lt 0 ] || [ $digit3 -gt 255 ]
                      then
                        # -> 3rd byte invalid (0..255)
                        result="invalid"
                      else
                        result="valid"
                        digit4=$(echo $ip | cut -d '.' -f 4)
                        case $digit4 in
                          (*[^0-9]*|'')
                            # -> not a number
                            result="invalid"
                          ;;
                          (*)
                            result="valid"
                            if [ $digit4 -lt 0 ] || [ $digit4 -gt 255 ]
                            then
                              # -> 4th byte invalid (0..255)
                              result="invalid"
                            else
                              # -> ip valid
                              result="valid"
                            fi
                          ;;
                        esac
                      fi
                    ;;
                  esac
                fi
              ;;
            esac
          fi
        ;;
      esac
    fi
  fi
fi
echo "$result"
 
Code:
# | resource-efficient sh-script to validate a given v4 ip |
Way too complicated and – in my book – “resource‑efficient” for a shell script (i. e. already consuming quite some resources for an awfully trivial task) implies relying on shell built‑ins as much as possible (not spawning extra processes).​
Bash:
#!/bin/sh
#        name: valid IPv4
# description: validate IPv4 quad‑octet address specification (decimal only)
#  maintainer: Firstname Lastname <mailbox@host> # in multi‐sysadmin environment

# We presume this script never gets `source`d by another script.
# Otherwise overwriting IFS without restoring it is bound to cause trouble.
IFS='.'
# Split the shell script’s first argument on period.
# The empty strings are necessary because the period _separates_ fields.
# Without surrounding empty strings a leading or trailing empty field vanishes.
set -- ''${1:?Usage: $0 ⟨decimal IPv4 address string⟩ # Example: $0 8.8.8.8}''
# Check that there are exactly four octets.
[ $# -eq 4 ] || exit 1
# Check that each octet is a value in the interval 0 – 255.
for octet do
	case ${octet} in
		([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
			# Intentionally empty.
			;;
		(*)
			exit 1
			;;
	esac
done
# The script returns the last statement’s exit status.
# In this case the `for` statement is otherwise successful.
There is a programming dogma that each sub‑program should fit on one screen, on one printed page. This supposedly improved understanding. Now this implementation certainly fulfills this criterion.

Note that inet_addr(3) accepts octal and hexadecimal numbers, too, and such unusual values as 127.123.45678 (= 2138813038), so in this regard the shell script is incomplete.​
 
Way too complicated and – in my book – “resource‑efficient” for a shell script (i. e. already consuming quite some resources for an awfully trivial task) implies relying on shell built‑ins as much as possible (not spawning extra processes).​
Bash:
#!/bin/sh
#        name: valid IPv4
# description: validate IPv4 quad‑octet address specification (decimal only)
#  maintainer: Firstname Lastname <mailbox@host> # in multi‐sysadmin environment

# We presume this script never gets `source`d by another script.
# Otherwise overwriting IFS without restoring it is bound to cause trouble.
IFS='.'
# Split the shell script’s first argument on period.
set -- $1
# Check that there are exactly four octets.
[ $# -eq 4 ] || exit 1
# Check that each octet is a value in the interval 0 – 255.
for octet do
    case ${octet} in
        ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
            # Intentionally empty.
            ;;
        (*)
            exit 1
            ;;
    esac
done
# The script returns the last statement’s exit status.
# In this case the `for` statement is otherwise successful.
There is a programming dogma that each sub‑program should fit on one screen, on one printed page. This supposedly improved understanding. Now this implementation certainly fulfills this criterion.

Note that inet_addr(3) accepts octal and hexadecimal numbers, too, and such unusual values as 127.123.45678 (= 2138813038), so in this regard the shell script is incomplete.​
The trick is to stop processing at the first fault, and have the possible dropouts ordered in likeliness.
 
As I wrote, this script only takes decimal specifications. The leading zero is – by the standardized inet_addr(3) function – interpreted as an octal base indicator. That means 010.000.001.001 = 8.0.1.1 = 134217985.​
Yes, but people are largely ignorant of the subtleties of IP(v4) address formats. But, many equipment vendors will flush out each octet to a three digit value "to look nice" or to arrange for addresses to nicely align in columnar outputs. Folks copying a script would be likely be ignorant of this unless explicitly stated.

[IIRC, my NCDs were this way. And I think at least one of my QNAPs has a "two button" front panel feature to set IP address that relies on three digit fields -- so you don't have to count up from "0", one at a time.]

Sadly, there is no way to alert a user providing input in that form of the potential for misinterpretation -- unless you see an invalid octal character prefaced with a leading zero, etc.

Safer to insist on an absence of leading zeroes and throw an error if any are detected (another case in your script). I.e., error handling (as always) often leads to code bloat.
 
Code:
#!/usr/local/bin/bash
# 0----------------------------------------------------------------------------0
# | play_doom.sh                                                               |
# | Trying to be non-interactive:                                              |
# | Download Doom 1 shareware version, auto-configure                          |
# | and play in Dosbox in attempted fullscreen                                 |
# | - using www.dosgamesarchive.com to download the game archive.              |
# | - programs emulators/dosbox and x11/xdotool must be installed              |
# | - using /tmp directory, usually RAM-based so nothing is stored permanently |
# 0----------------------------------------------------------------------------0
cd /tmp
mkdir doom
cd doom
fetch https://www.dosgamesarchive.com/file.php?id=987
# 0------------------0
# | this is a zip... |
# 0------------------0
mv "file.php?id=987" doom.zip
unzip doom.zip
# 0-----------------------0
# | trying get fullscreen |
# 0-----------------------0
echo "[render]" > /tmp/doom/dosbox.conf
echo "aspect=true" >> /tmp/doom/dosbox.conf
echo "scaler=normal3x" >> /tmp/doom/dosbox.conf
# 0---------------------------------------------------------------------0
# | Automated DOS installation procedure                                |
# | (Use xdotool to push timed keyboard actions ahead of the processes) |
# 0---------------------------------------------------------------------0
(
  sleep 1
  xdotool key c
  sleep .1
  xdotool key Enter
  sleep .1
  xdotool key Y
) & dosbox -conf /tmp/doom/dosbox.conf -c "mount c ." -c "c:" -c "deice.exe" -c "exit"
# 0----------------------------------------------------------0
# | Extracting is too slow in Dosbox, so we do that outside. |
# 0----------------------------------------------------------0
cd /tmp/doom/DOOMS
unzip DOOMS_19.EXE
(
  sleep 1
  xdotool key Escape
  sleep .5
  xdotool key Escape
  sleep .5
  xdotool key Escape
  sleep .5
  xdotool key Enter
) & dosbox -fullscreen -conf /tmp/doom/dosbox.conf -c "mount c ." -c "c:" -c "setup"
 
Hi people!

I'm coming here after this thread, where I'm asking about utility that can unambiguously determine the source code directory for executable in FreeBSD.

Well, as we see, there is no such utility quite yet. So I decided to make my own one.

So, here's the script that I've written (all credits go to covacat, on top of whose script I've actually built mine. You can find his original script in the abovementioned thread):

sh:
#!/bin/sh

# src -- locate source code for executable in FreeBSD.
#
# It determines the source code depending on information
# that can be obtained from Makefiles under /usr/src.
# Operations with Makefiles require certain time, that's
# why it does it once and stores the information in the index
# file (under /usr/local/share/src by default), that is later
# issued for actual fast searching.


TOP="/usr/src"
INDEX_DIR="/usr/local/share/src"
INDEX_NAME="base.index"
INDEX="$INDEX_DIR/$INDEX_NAME"

# The program supports two forms of invocation:
#  `-u [-v]': creating an index file that we will make use of when
#             actually locating the sources.
#             `-v' activates verbose mode - added lines will be printed.
#  `[-a] file ...': find sources for specified files.
#                   With `-a' all the found paths are printed.
usage() {
    echo "usage: src -u [-v]
       src [-a] file ..."
    exit 1
}

if [ "$#" = "0" ];then
    usage
fi

# Check if we have a source tree.
if [ ! -d "$TOP" ];then
    echo "[src]: source code directory $TOP doesn't exist." 1>&2
    exit 1
fi

# The program is invoked in "update" mode, which creates an index file.
# The index file consists of lines:
#   `<executable path and its hard links, if any> . <source paths>'.
# Example:
#   `/bin/ed /bin/ed /bin/red . /usr/src/bin/ed'.
if [ "$1" = "-u" ];then
    # If in the verbose mode, the actual lines that go to the index
    # file will be directed to stdout as well.
    if [ "$2" = "-v" ];then
        verbose="1"
    fi
   
    # Delete previously created index file if it exists.
    rm -f "$INDEX" 2>/dev/null
    # Create a path for index if it doesn't exist.
    mkdir -p "$INDEX_DIR" 2>/dev/null
    if [ $? -ne 0 ];then
        echo "[src]: can not create directory for index file $INDEX_DIR." 1>&2
        exit 1
    fi
   
    # Since index file will be situated under /usr/local/share, only
    # root user will be able to modify the file. We want to check if
    # we have appropriate permissions for this before we start.
    if ! touch "$INDEX" 2>/dev/null;then
        echo "[src]: can not create an index file at $INDEX." 1>&2
        exit 1
    fi
   
    # Collect all the Makefiles we possibly will be interested in.
    # Also avoiding lots of Makefiles that are for tests.
    for mf in $(find ${TOP}/bin/ ${TOP}/sbin/ \
                     ${TOP}/usr.bin/ ${TOP}/usr.sbin/ \
                     ${TOP}/libexec/ \
                     ${TOP}/secure/usr.bin/ ${TOP}/secure/usr.sbin/ \
                     ${TOP}/cddl/ ${TOP}/kerberos5/ \
                -type f \
                -name Makefile \
                -not -path "*\/*tests\/*" \
                -maxdepth 4)
    do
        mfd=$(dirname $mf)
       
        # Now we want to filter the Makefiles we don't need.
        # We're only looking for those ones that are for producing
        # binaries (PROG or PROG_CXX) or scripts (man(1), for example,
        # is actually a shell script, but we want to be able to search
        # for its source too).
        if grep -Eq "(PROG(_CXX)|SCRIPTS)?\??( |    )*=" $mf ;then
            # Here we're obtaining paths that this Makefile searches through.
            # It's stored in `.PATH' variable.
            sea_paths=$(make -C "$mfd" \
                            -V '${.PATH}' \
                            -f $mf \
                       2>/dev/null \
                       |sed "s/\.//")
           
            # If no such paths, then just skip this Makefile.
            if [ "$sea_paths" = " ." ];then
                continue
            fi
           
            seas=" ."
            # Now we try to filter out all the search paths
            # that do not actually contain the source code.
            # If the path does not have source code files for
            # C, C++ programs or shell scripts, or there are
            # no executable files (that usually go without extensions),
            # then it's most likely not a source code directory.
            for sea in $sea_paths;do
                prog_files=$(find "$sea" -type f \
                                \( -perm -111 \
                                -or -name "*\.c" \
                                -or -name "*\.cpp" \
                                -or -name "*\.cc" \
                                -or -name "*\.sh" \
                                -or -name "*\.y" \) \
                                -maxdepth 1)
                if [ -n "$prog_files" ];then
                    seas="$seas $sea"
                fi
            done
           
            # Obtain binary destination path and its hard links.
            bin_and_links=$(make -C "$mfd" \
                                 -V '${BINDIR}/${PROGNAME} ${LINKS}' \
                                 -f $mf \
                            2>/dev/null
            )
           
            # We have included scripts in our search along with binaries,
            # but a lot of Makefiles produce scripts that are used for
            # building, I guess, and they are not system-wide.
            # We can tell it's one of such scripts if its search
            # paths look like that:
            if [ "$bin_and_links" = "/ " ] \
               || [ "$bin_and_links" = "/bin/ " ] \
               || [ "$bin_and_links" = "/sbin/ " ] \
               || [ "$bin_and_links" = "/usr/bin/ " ] \
               || [ "$bin_and_links" = "/usr/sbin/ " ];then
                continue
            fi
           
            # Build up an index line entry.
            index_line=$(echo "$bin_and_links$seas" |sed 's/  / /g')
            if [ "$verbose" = "1" ];
            then echo "$index_line" |tee -a "$INDEX"
            else echo "$index_line" >>"$INDEX"
            fi
        fi
    done
   
    exit 0
fi

# If we've reached this place, this means we're in the
# second invocation form (searching sources).

# First, check if we have an index file.
if [ ! -f "$INDEX" ];then
    echo "[src]: index file $INDEX not found." 1>&2
    exit 1
fi

# In "print all" mode all the paths, that were found
# (usually more than one path is found, when the program is
# built of several modules or it also can be that the program
# includes a source code of some external program).
if [ "$1" = "-a" ];then
    all_mode="1"
    shift
   
    if [ "$#" = "0" ];then
        usage
    fi
fi

# Search paths for specified programs one-by-one.
tgts="$@"
for tgt in $tgts;do
    tgt_path=$(which $tgt 2>/dev/null)
   
    if [ "$?" != "0" ];then
        echo "[src]: $tgt is not found." 1>&2
        continue
    fi
   
    # Resolve symlinks (like tar(1) is a symlink to /usr/bin/bsdtar).
    # And also escape characters like "[" and "." for executables
    # like [(1) (which is also test(1)) (otherwise, they will be
    # treated as part of regex sytax by grep(1)).
    tgt_path=$(realpath "$tgt_path" \
               |sed -e 's/\[/\\\[/g' \
                    -e 's/\./\\\./g')
   
    # Search the target path in the index file.
    # Looking only into the first part of line, where binaries and links.
    srcs=$(grep "$tgt_path .*\. " "$INDEX")
   
    if [ -z "$srcs" ];then
        echo "[src]: no sources found for $tgt." 1>&2
        continue
    fi
   
    srcs=$(echo "$srcs" |sed 's/.* \. //')
    # Print only first path or all of them if appropriate flag is set.
    for src in $srcs;do
        echo "$src"
       
        if [ "$all_mode" != "1" ];then
            break
        fi
    done
done

First it needs to create an index file (according to hier(7) I decided that /usr/local/share would be the right place) with (as root): src -u (also you can provide -v flag to see the lines written to the file). It's needed because walking through /usr/src and issuing lots of Makefiles takes long time. As a result, index file takes 59K on my machine.

And when it's done you can find sources with src <program path or name> ... (you can also provide -a flag to print all the paths were found).

I tried to make it cover all the (edge)cases, because it appeared that there are some tricky ones. Let me show you some examples:

1) src sha1 -> /usr/src/sbin/md5.
/sbin/sha1 is a hardlink to /sbin/md5.

2) src strip -> /usr/src/contrib/elftoolchain/elfcopy.
Also hard link case, but source directory is not that obvious.

3) src tar -> /usr/src/contrib/libarchive/tar.
/usr/bin/tar is a symlink to /usr/bin/bsdtar.

4) src man -> /usr/src/usr.bin/man.
I didn't know, but /usr/bin/man is a shell script. It also works.

However, there are few cases that don't work or perhaps work not exactly the way we want them to:

1) src make -> /usr/src/contrib/bmake/filemon
The scripts looks into .PATH make(1) variable to find the source path. But some sources include external (or additional) modules *before* the main module. As you can see, this is one of these cases. But, well, from the path it returned it's still possible to figure that actual source directory is one level up.

2) src wpa_cli -> can't find sources.
I sorta found what's the problem. There is a line from /usr/src/usr.sbin/wpa/wpa_cli/Makefile:
Makefile:
.PATH.c:${WPA_SUPPLICANT_DISTDIR}
Yeah, it uses .PATH.c instead of .PATH. But when I try to execute make -V '${.PATH.c}' on this file, it doesn't give anything (but it works with .PATH, which doesn't include the path we need). Here I want to ask you: maybe you know why this happens / any ideas on how to handle this case?

Well, it seems to be all. You can also look into source code itself - I tried to leave explanatory comments.

Thank you,
Artem
 
Two of my small pool name agnostic scripts I do use often to create and delete ZFS snapshots (can be improved, but work for me)

To snapshot all datasets
Code:
#!/usr/local/bin/bash

zfs snapshot -r $(zpool list -H|awk '{print $1}')@"snap_$(date "+%Y-%m-%d_%H:%M:%S")_$(uuidgen -r)"

To delete the oldest snapshots created by previous script
Code:
#!/usr/local/bin/bash

zfs destroy -r "$(zfs list -H -o name -t snap|head -n 1)"
 
I wrote a script that will launch my terminal emulator of choice and will directly attach to tmux session. It also prevents me from launching more than one terminal because I use tmux and i think if you use tmux, you don't need another terminal open. My window manager of choice, which is dwm, directly launches this script on keyboard shortcut. It also sends a notification if terminal is already open.


sh:
#!/bin/sh

st=$(pidof st)
if [ -z "$st" ]; then
    st -e tmux a
else
    notify-send "st is already open."
fi

Also, i have a script for dwm status bar that will show current keyboard layout and date/time.
1737665226956.png

sh:
#!/bin/sh

while true; do
        TIME=$(date +"%A %d %B %H:%M")
        LAYOUT=$(xkblayout-state print "%s")
        xsetroot -name "$LAYOUT $TIME"
        sleep 1
done

I also wrote two new scripts, one is for comparing installed packages in my system with poudriere package list and other one is for rollbacking to specific zfs snapshot which i hadn't tried it yet but I think it would work because I was wrote it before once.


Code:
% tail -n +1 /rollback.sh .local/bin/compare-pkg.sh

==> /rollback.sh <==

#!/bin/sh


echo "Snapshot to rollback to: "

read -r rollback2

if [ ! -z "$rollback2" ];then

        for zf in `zfs list -rH -o name -t filesystem zroot`; do zfs rollback $zf@$rollback2; done

        echo "Rollback is successful."

fi


==> .local/bin/compare-pkg.sh <==

#!/bin/sh

cd /tmp

cat /poudriere/packages | sort -u > poudrierepackages

pkg prime-origins | sort -u > prime-origins

diff -Naur poudrierepackages prime-origins | grep -vE 'devel/git|editors/vim|graphics/gpu-firmware-amd-kmod|sysutils/vimpager' | vimpager
 
Last edited:
Code:
#!/bin/sh
# excute as root
#
# (opt) edit /usr/local/etc/void-zones/my_void_hosts.txt

# https://github.com/hagezi/dns-blocklists
fetch -o - \
    https://raw.githubusercontent.com/hagezi/dns-blocklists/main/domains/pro.txt \
    > /usr/local/etc/void-zones/x_void_list.txt

/usr/local/bin/void-zones-update.sh

service unbound restart

This tiny script uses DNS-blocklists + void-zones-tools +unbound for home DNS. Note there are a few alternative blocklists in hagezi/dns-blocklists
 
In order to know if a system has the latest patch level one have to compare the output of the command freebsd-version -kru with what's on the mailing list, the FreeBSD news, the forum, or just blindly run freebsd-update fetch and wait few seconds/minutes to get an answer.
Then one day I found this page on the Wiki (thanks to the author of this article) that inspired me to write this script that basically just tells very quickly if the system is update or not, no need to check online by yourself anymore.
In case it's not clear yet, the goal is not to check if the system runs with the last packages or the last release, this is only about patch level.
I use this script for few months now and tested it on 13.X and 14.X so it should be fine.

If the system is okay this is what the script returns:
Code:
~ > cpl
No updates needed.

In case the system doesn't have the last patches then the script gives an output like this:
Code:
~ > cpl
Current_patches:  14.2-RELEASE-p2
Latest_patches:   14.2-RELEASE-p3

WARNING! The system is not up to date.

Code:
#!/usr/bin/env sh
# NAME: cpl c(heck) (p)atch l(evel)
# DESCRIPTION: Verify if the running FreeBSD installation has the latest patches
# this script is based on the following wiki page https://wiki.freebsd.org/FreeBSD_Update
# DEPENDENCIES: none
# AUTHOR: gotnull
# LICENSE: Unlicense

set -eu
set +x

# temporary directory
tmpdir=$(mktemp -t cpl --directory)

# if needed remove the patch number from the release name 
CURRENT_VERSION="$(freebsd-version)"
CURRENT_VERSION_STRAPPED=$(echo "$CURRENT_VERSION" | awk '{print substr ($0, 6)}')
if [ "$CURRENT_VERSION_STRAPPED" != "RELEASE" ] ; then
    VERSION_NAME="${CURRENT_VERSION%-*}"
else
    VERSION_NAME="$CURRENT_VERSION"
fi

# download files
fetch -q http://update.freebsd.org/"$VERSION_NAME"/amd64/pub.ssl -o "$tmpdir"
fetch -q http://update.freebsd.org/"$VERSION_NAME"/amd64/latest.ssl -o "$tmpdir"

# check server signature
SIGNATURE=$(sha256 -q "$tmpdir"/pub.ssl)
HASH=$(awk '/KeyPrint/ {print $2}' /etc/freebsd-update.conf)

if [ "$SIGNATURE" != "$HASH" ] ; then
    echo "Error: signature invalid."
    rm -rf "$tmpdir"
    exit 1
fi

# check for latest available version
RESULT="$(openssl rsautl -pubin -inkey "${tmpdir}"/pub.ssl -verify < "${tmpdir}"/latest.ssl 2>/dev/null)"

# catch patch number of the latest version
PATCH_NUMBER_LATEST=$(echo "$RESULT" | awk -F'|' '{print $4}')

# catch patch number of the current version
PATCH_NUMBER_CURRENT="${CURRENT_VERSION##*-}"
PATCH_NUMBER_CURRENT_STRAPPED=$(echo "$PATCH_NUMBER_CURRENT" | awk '{sub(/p/, "")} 1')

# compare versions
# treat XX.X-RELEASE-p0 as XX-X-RELEASE
if [ "$PATCH_NUMBER_LATEST" -eq 0 ] ; then
    echo "No updates needed."
    rm -rf "$tmpdir"
    exit 0
fi

# compare versions
if [ "$PATCH_NUMBER_LATEST" != "$PATCH_NUMBER_CURRENT_STRAPPED" ] ; then
    RELEASE_NAME=$(echo "$CURRENT_VERSION" | awk '{print substr ($0, 0,12)}')
    RELEASE_NAME_PATCHED="$RELEASE_NAME"-p"$PATCH_NUMBER_LATEST"

    TEXT="Current_patches:
    Latest_patches:"
    VERSIONS="$CURRENT_VERSION
    $RELEASE_NAME_PATCHED"
    printf "%s\n %s\n" "$TEXT" "$VERSIONS" | pr -s -t -2 | column -t
    echo ""
    echo "WARNING! The system is not up to date."
    rm -rf "$tmpdir"
    exit 1
else
    echo "No updates needed."
    rm -rf "$tmpdir"
    exit 0
fi
 
I see this thread is also still alive, nice!

So, I think I posted this script before but even so this is the updated version (1.5, whoohoo!). This is snapshot.zfs which I keep around in /root/bin and use through crontab(1), also see crontab(5) for more detail on this.

Making ZFS snapshots is one thing, keeping track of them another; you don't want to risk your system filling up with useless data afterall. I also don't like seeing snapshots pop up in unneeded locations (like zroot/src, aka /usr/src).

As such I made the following script:

Code:
#!/bin/sh

## Snapshot.ZFS v1.5
##
## A script which will manage ZFS snapshots on the
## filesystem(s) of your choosing.  // By u/ShelLuser42

### Configuration section.

# ZFS pool to use.
POOL="zroot";

# Dataset(s) to use.
FS="home local var/log var/mail var/imapd"

# Frequency; how often do we make snapshots? (d = daily, w = weekly).
FREQ=d;

# Retention; how many snapshots should be kept?
RETENTION=7

# Recursive; process a dataset and all its children? ('yes' or undefined).
RECURSE=yes;

## System settings

PATH=$PATH:/sbin:/usr/sbin

### Script definitions <-> ** Don't change anything below this line! **

PROG=$(basename $0);
CURDAT=$(date "+%d%m%y");

if [ ${RECURSE} == "yes" ]; then
        OPTS="-r";
fi

### Script starts here ###

if [ -z $1 ]; then
        echo "Error: no command specified."             > /dev/stderr;
        echo                                            > /dev/stderr;
        echo "Usage:"                                   > /dev/stderr;
        echo "${PROG} y : Manage snapshots."            > /dev/stderr;
        echo                                            > /dev/stderr;
        exit 1;
fi

if [ $FREQ != "d" -a $FREQ != "w" ]; then
        echo "Error: frequency misconfigured!"          > /dev/stderr;
        echo                                            > /dev/stderr;
        echo '$FREQ' can only be \'d\' or \'w\'.        > /dev/stderr;
        echo                                            > /dev/stderr;
        exit 1;
else
        PRVDAT=$(date -v-${RETENTION}${FREQ} "+%d%m%y");
fi

if [ "$1" == "y" ]; then
        # Make & clean snapshot(s)
        for a in $FS; do
                ZFS=$(zfs list -r ${POOL} | grep -e "$a" | cut -d ' ' -f1);
                if [ -z $ZFS ]; then
                        echo "${PROG}: Can't process ${a}: not a ZFS filesystem." >/dev/stderr;
                else
                        $(zfs snapshot ${OPTS} ${ZFS}@${CURDAT} > /dev/null 2>&1) || echo "${PROG}: Error creating snapshot ${ZFS}@${CURDAT}" > /dev/stderr;
                        $(zfs destroy ${OPTS} ${ZFS}@${PRVDAT} > /dev/null 2>&1) || echo "${PROG}: Error destroying snapshot ${ZFS}@${PRVDAT}" > /dev/stderr;
                fi
        done;
else
        echo "Error: wrong parameter used."             > /dev/stderr;
        echo                                            > /dev/stderr;
        echo "Usage:"                                   > /dev/stderr;
        echo "${PROG} y : Manage snapshots."            > /dev/stderr;
        echo                                            > /dev/stderr;
        exit 1;
fi

It's not 100% perfect though, I suppose I should streamline the error checking routine(s) a bit and also make sure that RETENTION is an actual number instead of being unset. But I couldn't be bothered with that so far.
 
Back
Top