Howto: be notified of FreeBSD upgrades, security updates and package updates at login

Hello all FreeBSD users,

At the invitation of Crivens, I repost here the little script I've come up with and which received improvements from the community (thanks SirDice, unitrunker, emmex).

Context: the idea was to obtain information about FreeBSD upgrades, security updates and package updates right at login.
It's something I find has been lacking for years for the average self-hosted FreeBSD user like me who occasionally logs in to his server to check if everything's going well, perform package upgrades etc, and wants to stay up to date with the latest version available of the operating system. Yet AFAIK (unless I missed something in the documentation for all these years, which is totally possible) there's no simple way for the system to tell you "hey there, a new production version is available, you should start considering upgrading" right at login. I know I could subscribe to a mailing list to be notified directly in my mailbox, but these emails rarely come at a moment where I'm ready to address this issue, and I'd rather be notified of that when I login to my server - because that's exactly when I'm available to perform routine maintenance tasks.

The following script (which I currently use on my FreeBSD machines) answers this need ; I commit myself to come back and post updates and fixes whenever it'll be necessary.

How to use : paste the following lines in a file, make it executable (chmod +x file) and append a line in your ~/.profile (or whatever login script your shell uses) to call it at login time.

Code:
#!/usr/bin/env sh

# let's do an automatic update check (use http as long as it's supported, https also works)
_ICON_THUMBSUP="\xf0\x9f\x91\x8d"
_ICON_WARNING="\xe2\x9a\xa0\xef\xb8\x8f"
_ICON_NOTICE="\xf0\x9f\x92\xac"
_ICON_WAIT1="\xe2\x8f\xb3"
_ICON_WAIT2="\xe2\x8c\x9b"
echo ""
printf "  ${_ICON_WAIT1}  Checking for operating system updates, please wait... "
_CUR_MMN="$(freebsd-version|awk -F- '{print $1}')"
_CUR_VER="$(freebsd-version)"
_AVL_MMN="$(HTTP_TIMEOUT=3 fetch -qo - "http://www.freebsd.org" 2>/dev/null|tr -d '\n'|sed -E -e 's/<\/?(a|span)[^>]*>//g' -e 's/<\/?(br|li|p|div)>/\n/g'|grep "Production:"|tr ',' ' '|awk '{print $NF}')"
_AVL_VER="${_AVL_MMN}-$(HTTP_TIMEOUT=3 fetch -qo - "http://cgit.freebsd.org/src/plain/sys/conf/newvers.sh?h=releng/${_AVL_MMN}" 2>/dev/null|grep "^BRANCH="|cut -d'"' -f2)"
if [ -z "${_AVL_MMN}" ]; then
    printf "\r  ${_ICON_WARNING}  Automatic update check failed (error retrieving the latest production version from www.freebsd.org).\n"
elif [ -z "$(echo "${_AVL_VER}"|grep "RELEASE")" ]; then
    printf "\r  ${_ICON_WARNING}  Automatic update check failed (error retrieving the latest patchlevel version from cgit.freebsd.org).\n"
elif [ "_${_CUR_VER}" = "_${_AVL_VER}" ]; then
    printf "\r  ${_ICON_WAIT2}  Checking for available package updates, please wait... "
    _PKG_CNT="$(pkg upgrade -n|grep -E -o "^The following [0-9]+"|grep -E -o "[0-9]+"||echo "0")"
    if [ "${_PKG_CNT}" -gt 0 ]; then
        printf "\r  ${_ICON_NOTICE}  You have \e[1;37m${_PKG_CNT}\e[0m outdated package(s). Consider using \e[33mpkg(8) upgrade\e[0m.\n"
    else
        printf "\r  ${_ICON_THUMBSUP}  Your system is up to date. Congratulations!           \n"
    fi
elif [ "_${_CUR_MMN}" = "_${_AVL_MMN}" ]; then
    printf "\r  ${_ICON_WARNING}  A security update is available: \e[1;37m${_AVL_VER}\e[0m. Consider using \e[33mfreebsd-update(8)\e[0m.\n"
else
    printf "\r  ${_ICON_WARNING}  A new FreeBSD version is available: \e[1;37m${_AVL_VER}\e[0m. Consider using \e[33mfreebsd-update(8)\e[0m.\n"
fi
unset _AVL_VER _AVL_MMN _CUR_VER _CUR_MMN _PKG_CNT _ICON_WAIT2 _ICON_WAIT1 _ICON_NOTICE _ICON_WARNING _ICON_THUMBSUP
echo ""

Then, each time you log in you can be notified of OS security updates, OS upgrades and package updates.

How it works:
Basically [...], it simply fetches the www.freebsd.org website index, parses the returned HTML and looks at the number that's right after "Production:" in the middle of the page that tells the visitor which is the latest supported production release. Then it does a second query to cgit.freebsd.org to find out the current patchlevel for this version out of the sys/conf/newvers.sh script. It then assembles that data in a variable, and compares it with the current version string that the "freebsd-version" command returns. If the major/minor part differs, it suggests an OS upgrade. If the patchlevel differs, it tells you a security update is available. And if an error occured while trying to retrieve the latest available version tag, it tells you where it happened.

Only one single line of output will be displayed : each intermediate message is erased by the next one. It does not flood your terminal.

Extra candy: if your SSH terminal supports Unicode emoticons (e.g. macOS' Terminal.app) you get color icons prefixed in the message lines printed by the script :

⏳ Checking for operating system updates, please wait...

⌛ Checking for available package updates, please wait...

⚠️ A security update is available: 13.1-RELEASE-p2. Consider using freebsd-update(8).

? You have 15 outdated package(s). Consider using pkg(8) upgrade.

? Your system is up to date. Congratulations!

Of course, if your terminal doesn't support Unicode, or you prefer ASCII-only output, empty these variables in the script, or "simplify" them, for example like this:
Code:
_ICON_THUMBSUP=":-)"
_ICON_WARNING="/!\\"
_ICON_NOTICE="/!\\"
_ICON_WAIT1="[.]"
_ICON_WAIT2="[:]"

TBH I'm not 100% happy with the HTML parsing it does but I wanted something simple, light, and fast. This does the job. It would be ideal if FreeBSD.org defined some sort of stable API to get this information in one single HTTP request. Should that be decided one day, please notify me and I'll make the necessary changes.

Thanks to all the contributors so far. Comments, suggestions, and contributions/improvements are welcome !
 
Hello again all,

I wanted to share with you the latest improvements I made to this .profile addition.

Basically, in under 350 lines of shellcode, each time you login on your server you get something like this:

Enregistrement de l’écran 2026-07-05 à 14.52.13.gif


Here is the code:
sh:
#!/bin/sh

####################################################################
# small root shell dashboard for Debian/Ubuntu Linux and FreeBSD systems
# author/maintainer: Pierre-Marie Baty <pm@pmbaty.com>
# how to use: just append these lines to your ~/.profile

print()
{
    # usage: print 'icon' 'title' 'possibly multiline word list'
    _TERMWIDTH="$(tput cols 2>/dev/null || printf 80)"
    _STARTPOS="27"
    _HEAD_PRINTED=false
    printf '\r%*s' "${_STARTPOS}" ''; _LINEPOS="$((_STARTPOS))"
    for _WORD in $(printf "%b" "${3}"|sed 's/$/ __EOL__/g'); do
        if [ "${_WORD}" = "__EOL__" ]; then
            ${_HEAD_PRINTED} || printf '\r      %b\r  %b' "$(printf "%s" "${2}"|cut -c "1-$((_STARTPOS - 7))")" "${1}" && _HEAD_PRINTED=true
            printf '\n%*s' "${_STARTPOS}" ''; _LINEPOS="$((_STARTPOS))"
        else
            # strip escape sequences and count UTF-8 codepoints regardless of installed locales. Do not change a SINGLE character of the line below unless you know what you are doing.
            _WORDLEN="$(printf "%s" "${_WORD}"|LC_ALL=C sed -E "s/\x1B\[[0-9;]*[a-zA-Z]//g;$(printf 's/[\360-\364][\200-\277][\200-\277][\200-\277]/x/g;s/[\340-\357][\200-\277][\200-\277]/x/g;s/[\302-\337][\200-\277]/x/g')"|wc -c|tr -d ' ')"
            if [ "$((_LINEPOS + 1 + _WORDLEN))" -ge "${_TERMWIDTH}" ]; then
                ${_HEAD_PRINTED} || printf '\r      %b\r  %b' "$(printf "%s" "${2}"|cut -c "1-$((_STARTPOS - 7))")" "${1}" && _HEAD_PRINTED=true
                printf '\n%*s' "${_STARTPOS}" ''; _LINEPOS="$((_STARTPOS))"
            fi
            printf ' %b' "${_WORD}"; _LINEPOS="$((_LINEPOS + 1 + _WORDLEN))"
        fi
    done
    ${_HEAD_PRINTED} || printf '\r      %b\r  %b' "$(printf "%s" "${2}"|cut -c "1-$((_STARTPOS - 7))")" "${1}"
    test "${_LINEPOS}" -gt "${_STARTPOS}" && printf '\n' || printf '\r'
    unset _WORDLEN _WORD _LINEPOS _HEAD_PRINTED _STARTPOS _TERMWIDTH
}

print_logo()
{
    # ASCII art FTW. Sixel capability is unfortunately not detectable server-side.
    # display whatever logo you want here...
    printf ''
}

print_hostid()
{
    # print machine hostname, operating system, hardware type, uptime and IP addresses
    print "🪪️" "host" "\033[1;97m$(hostname)\033[0m ($(uname -o) on $(uname -m))"
    _UPTIME="$(uptime|awk -F' up ' '{print $2}'|sed -E 's/, +[0-9]+ user.+//')"
    if ! printf "%s" "${_UPTIME}"|grep -q 'day'; then                    _AGE="kid 👶"
    elif [ "$(printf "%s" "${_UPTIME}"|awk '{print $1}')" -lt 30 ]; then _AGE="grown up 😎️"
    else                                                                 _AGE="old timer 👴"
    fi
    print "⏱️" "uptime" "${_UPTIME} - ${_AGE}"
    print "🔌️" "IP" "$(
        if command -v ip > /dev/null; then
            ip addr show scope global|awk '/inet / {print $2}'|sed -E 's;/[0-9]+;;g'
        elif command -v ifconfig > /dev/null; then
            ifconfig|awk '/inet / && $2 !~ /^127\./ {print $2}'
        fi|while read -r _ADDR; do
            if printf "%s" "${_ADDR}"|grep -Eq -e '10\.' -e '127\.' -e '172\.(1[6-9]|2[0-9]|3[0-1])\.' -e '192\.168\.'; then
                printf " 🏠️ \033[93m%s\033[0m (local)" "${_ADDR}"
            else
                printf " 🌍️ \033[97m%s\033[0m" "${_ADDR}"
            fi
        done)"
    unset _UPTIME _AGE
}

print_hwstats()
{
    # print CPU/RAM/SSD occupation graphs
    _barchart()
    {
        # usage: barchart <percentage> [barlen]
        _PERCENT="$(printf "%s" "${1}"|tr -d '%')"; test -z "${_PERCENT}" && _PERCENT=0
        _BARLEN="${2}"; test -z "${_BARLEN}" && _BARLEN=25
        _FULLBLOCK="$((_PERCENT * _BARLEN / 100))"
        test -z "${_FULLBLOCK}" && _FULLBLOCK=0
        test "${_FULLBLOCK}" -gt "${_BARLEN}" && _FULLBLOCK="${_BARLEN}"
        _PARTBLOCK="$((_PERCENT * _BARLEN % 100))"
        test -z "${_PARTBLOCK}" -o "${_FULLBLOCK}" -eq "${_BARLEN}" && _PARTBLOCK=0
        test "${_PERCENT}" -gt 80 && printf '\033[0;31m' || printf '\033[0;36m'
        test "${_FULLBLOCK}" -gt 0 && printf '%*s' "${_FULLBLOCK}" ''|sed 's/ /█/g'
        if   [ "${_PARTBLOCK}" -gt 87 ]; then printf '▉'
        elif [ "${_PARTBLOCK}" -gt 75 ]; then printf '▊'
        elif [ "${_PARTBLOCK}" -gt 62 ]; then printf '▋'
        elif [ "${_PARTBLOCK}" -gt 50 ]; then printf '▌'
        elif [ "${_PARTBLOCK}" -gt 37 ]; then printf '▍'
        elif [ "${_PARTBLOCK}" -gt 25 ]; then printf '▎'
        elif [ "${_PARTBLOCK}" -gt 12 ]; then printf '▏'
        fi; test "${_PARTBLOCK}" -gt 12 && _FULLBLOCK="$((_FULLBLOCK + 1))"
        printf '\033[0m'
        _REMAINING="$((_BARLEN - _FULLBLOCK))"
        test "${_REMAINING}" -gt 0 && printf '%*s' "${_REMAINING}" ''|sed 's/ /·/g'
        unset _REMAINING _PARTBLOCK _FULLBLOCK _BARLEN _PERCENT
    }
    _LOAD="$(LC_ALL="C" uptime|awk '{print $(NF-2)}'|tr -d '.,'|sed -E 's/^0+//')"
    test -z "${_LOAD}" && _LOAD=0
    _NPROC="$(getconf _NPROCESSORS_ONLN)"
    case "$(uname)" in
        Linux)
            _TOTMEM="$(awk '/MemTotal/     {print $2}' /proc/meminfo)"
            _AVLMEM="$(awk '/MemAvailable/ {print $2}' /proc/meminfo)"
            ;;
        FreeBSD)
            _TOTMEM="$(sysctl -n hw.physmem)"; _TOTMEM="$((_TOTMEM / 1024))"
            _TOPMEM="$(top|grep "^Mem:")"
            _WIRMEM="$(printf "%s" "${_TOPMEM}"|awk '{print $8}' |sed -e 's/K//' -e 's/M/ * 1024/' -e 's/G/ * 1024 * 1024/' -e 's/T/ * 1024 * 1024 * 1024/')"
            _BUFMEM="$(printf "%s" "${_TOPMEM}"|awk '{print $10}'|sed -e 's/K//' -e 's/M/ * 1024/' -e 's/G/ * 1024 * 1024/' -e 's/T/ * 1024 * 1024 * 1024/')"
            _ARCMEM="$(sysctl -n kstat.zfs.misc.arcstats.size)"; _ARCMEM="$((_ARCMEM / 1024))"
            _ACTMEM="$(printf "%s" "${_TOPMEM}"|awk '{print $2}' |sed -e 's/K//' -e 's/M/ * 1024/' -e 's/G/ * 1024 * 1024/' -e 's/T/ * 1024 * 1024 * 1024/')"
            _AVLMEM="$((_TOTMEM - _WIRMEM - _BUFMEM + _ARCMEM - _ACTMEM))"
            unset _ACTMEM _ARCMEM _BUFMEM _WIRMEM _TOPMEM
            ;;
        *)
            _TOTMEM=0
            ;;
    esac
    _PERCENTAGE="$((_LOAD / _NPROC))"
    print "🖥️" "Load average" "$(_barchart "${_PERCENTAGE}" 25)  \033[1;97m${_PERCENTAGE}%\033[0m of ${_NPROC} cores"
    _PERCENTAGE="$((100 - 100 * _AVLMEM / _TOTMEM))"
    print "💻️" "RAM"          "$(_barchart "${_PERCENTAGE}" 25)  \033[1;97m${_PERCENTAGE}%\033[0m of $((_TOTMEM / 1024))M"
    if command -v zpool > /dev/null && test -n "$(zpool get size)"; then
        _CAPACITY="$(zpool get size|tail -n +2|awk '{print $3}')"
        _PERCENTAGE="$(zpool get capacity|tail -n +2|awk '{print $3}')"
        print "💽️" "ZFS root" "$(_barchart "${_PERCENTAGE}" 25)  \033[1;97m${_PERCENTAGE}\033[0m of ${_CAPACITY}"
    fi
    LC_ALL="C" df -P -h 2>/dev/null|grep "^/"|while read -r _MOUNTPOINT _CAPACITY _ _ _PERCENTAGE _; do
        print "💽️" "${_MOUNTPOINT}" "$(_barchart "${_PERCENTAGE}" 25)  \033[1;97m${_PERCENTAGE}\033[0m of ${_CAPACITY}" 1>&2
    done
    unset _LOAD _PERCENTAGE _NPROC _AVLMEM _TOTMEM _CAPACITY
    unset -f _barchart
}

print_services()
{
    # print the running services and open ports
    printf "  ⏳  Scanning services... "
    print "⚙️" "Live services" "$(
        case "$(uname)" in
            Linux)
                _SVCLST="$(SYSTEMD_COLORS=0 systemctl --plain list-units --type=service --no-pager --no-legend|grep -E -v -e ' exited ' -e '@[^\s]+\.service'|sed -E 's/\.service\s+.+$//')"
                _svcstat() { systemctl show "${1}" -p ActiveState -p SubState --value|tr '\n' ' '; }
                ;;
            FreeBSD)
                _SVCLST="$(service -e|grep -v -e '/ip6addrctl' -e '/newsyslog' -e '/clamav-freshclam'|while read -r _LINE; do basename "${_LINE}"; done)"
                _svcstat() { service "${1}" status 2>/dev/null|tr -d '.'; }
                ;;
        esac
        for _SVCNAME in ${_SVCLST}; do
            _STATUS="$(_svcstat "${_SVCNAME}")"
            test -z "${_STATUS}" && continue
            if printf "%s" " ${_STATUS} "|grep -q -i -e " not-found " -e " error " -e " inactive " -e " failed " -e " dead " -e " disabled " -e " not running " -e " powered off "; then
                printf " \033[5;1;31m•\033[0m\033[4;31m%s\033[0m" "${_SVCNAME}"
            elif printf "%s" " ${_STATUS} "|grep -q -i -e " enabled" -e " running "; then
                printf " \033[1;32m•\033[0m%s" "${_SVCNAME}"
            else
                printf " •\033[97m%s\033[0m" "${_SVCNAME}"
            fi
        done)"
    printf "  ⏳  Scanning open ports... "
    print "🚪" "Open ports" "$(
        case "$(uname)" in
            Linux)   ss -tunlpH|grep -v -e ' 127\.0\.0\.1:' -e ' \[::\]:'|awk '{printf "%s %s\n", $5, $7}'|sed -e 's/^\*://' -e 's/^0\.0\.0\.0://' -e 's/ users:(("/ /' -E -e 's/".+$//';;
            FreeBSD) sockstat -46lLwq|awk '{printf "%s %s\n", $6, $2}'|grep '^\*:'|sed 's/\*://'|grep -v '^\* ';;
        esac|sort -n|uniq|while read -r _PORT _SVCNAME; do
            printf " \033[33m•\033[0m\033[97m%s\033[0m(%s)" "${_PORT}" "${_SVCNAME}"
        done)"
}

print_dockervms()
{
    # print this machine's running Docker VMs
    printf "  ⏳  Scanning Docker VMs... "
    print "📦️" "Docker VMs" "$(
        command -v docker > /dev/null && _LIST="$(docker ps -a --format "{{.Names}} {{.Ports}} {{.Status}}"|sed 's/, /,/g'|while read -r _VMNAME _VMINFO _VMSTATUS; do
            case "${_VMSTATUS}" in
                Up\ *)
                    printf "\033[1;32m•\033[0m\033[97m%s\033[0m %s" "${_VMNAME}" "$(echo "${_VMINFO}"|sed -e 's/0\.0\.0\.0:/🌍️/g' -e 's/\[::\]:/🌍️/g' -e 's/127\.0\.0\.1:/➰/g'|tr ',' '\n'|uniq|tr '\n' ' ')"
                    # uncomment the following line to display extra stats (costy)
                    #printf " 📊 $(docker stats --no-stream --format "cpu {{.CPUPerc}}, ram {{.MemPerc}}" "${_VMNAME}")"
                    printf "\n";;
                *)
                    printf "\033[5;1;31m•\033[0m\033[4;31m%s\033[0m (not running)\n" "${_VMNAME}";;
            esac
        done)" && test -n "${_LIST}" && printf "%b\n" "${_LIST}" || printf "none\n")"
}

print_vboxvms()
{
    # print this machine's running VirtualBox VMs
    printf "  ⏳  Scanning VirtualBox VMs... "
    print "📦️" "VirtualBox VMs" "$(
        command -v vboxheadless > /dev/null && _LIST="$(service vboxheadless status|tail +3|while read -r _VMNAME _VMSTATUS; do
            _VMINFO="$(sudo -u vboxusers vboxmanage showvminfo "${_VMNAME}" --details)"
            case "${_VMSTATUS}" in
                *[pP]owered*) printf " \033[5;1;31m•\033[0m\033[4;97m%s\033[0m" "${_VMNAME}";;
                *[rR]unning*) printf " \033[1;32m•\033[0m\033[97m%s\033[0m" "${_VMNAME}";;
                *)            printf " •\033[97m%s\033[0m" "${_VMNAME}";;
            esac
            printf ": %s core, %s RAM\n" "$(printf "%s" "${_VMINFO}"|awk '/Number of CPUs:/ {print $4}')" "$(printf "%s" "${_VMINFO}"|awk '/Memory size:/ {print $3}')"
        done)" && test -n "${_LIST}" && printf "%b\n" "${_LIST}" || printf "none\n")"
}

print_connected()
{
    # print active logins
    printf "  ⏳  Resolving your IP... "
    _YOUR_IP="$(who am i|grep '('|awk '{print $NF}'|tr -d '()')"
    if printf "%s" "${_YOUR_IP}"|grep -q '[^0-9.]'; then
        if   command -v getent   > /dev/null 2>&1; then _YOUR_IP="$(getent hosts "${_YOUR_IP}"|awk '{print $1}'|grep -v ':'|head -n 1)"
        elif command -v host     > /dev/null 2>&1; then _YOUR_IP="$(host "${_YOUR_IP}"|awk -F' has address ' '{print $2}'|grep -v ':'|head -n 1)"
        elif command -v dig      > /dev/null 2>&1; then _YOUR_IP="$(dig +short "${_YOUR_IP}"|grep -v ':'|head -n 1)"
        elif command -v nslookup > /dev/null 2>&1; then _YOUR_IP="$(nslookup "${_YOUR_IP}"|grep -A10 'authoritative answer'|grep 'Address:'|awk '{print $2}'|grep -v ':'|head -n 1)"
        fi
    fi
    if [ -z "${_YOUR_IP}" ]; then
        print "👤️" "Your IP" "\033[1;97mlocal terminal\033[0m"
    elif printf "%s" "${_YOUR_IP}"|grep -Eq -e '10\.' -e '127\.' -e '172\.(1[6-9]|2[0-9]|3[0-1])\.' -e '192\.168\.' -e '\.local$' -e '\.home$'; then
        print "👤️" "Your IP" "🏠️ \033[93m${_YOUR_IP}\033[0m (local or VPN)"
    else
        print "👤️" "Your IP" "🌍️ \033[97m${_YOUR_IP}\033[0m (no VPN)"
    fi
    printf "  ⏳  Scanning sessions... "
    print "👥️" "Who's there" "$(
        case "$(uname)" in
            Linux)   who|sed -E 's/ {1,8}/|/;s/ {1,8}/|/;s/ {1,8}/|/';;
            FreeBSD) who|grep -v 'pts/'|sed -E 's/ {1,8}/|/;s/ {1,8}/|/;s/ {1,8}/|/'; ps -A -e -o pid,args|grep ' sshd-session: '|grep -v -e 'grep  sshd-session:' -e ' \['|awk '{printf "%d %s\n", $1, $3}'|while read -r _PID _NPTY; do printf '%s|sshd|%s|XXX XX XX:XX (%s)\n' "$(printf "%s" "${_NPTY}"|awk -F@ '{print $1}')" "$(printf "%s" "${_NPTY}"|awk -F@ '{print $2}')" "$(sockstat -4 -6|grep " ${_PID} "|grep -v '\*'|awk '{print $7}'|awk -F: '{print $1}')"; done;;
        esac|while IFS="|" read -r _LOGIN _ _TTY _INFO; do
            case "${_INFO}" in
                *"(${_YOUR_IP})") printf "\033[1;32m•\033[0m\033[1;37m%s\033[0m from your IP" "${_LOGIN}";;
                *)                printf "\033[5;1;32m•\033[0m\033[1;37m%s\033[0m from \033[1;32m%s\033[0m" "${_LOGIN}" "$(printf "%s" "${_INFO}"|sed -n 's/.*(\(.*\)).*/\1/p')";;
            esac
            test -n "${_TTY}" && printf " on %s\n" "${_TTY}" || printf " (no terminal)\n"
        done)"
    unset _YOUR_IP
}

print_usablelogins()
{
    # print usable logins
    printf "  ⏳  Scanning user accounts... "
    print "🔐️" "Usable logins" "$(grep -v '^#' /etc/passwd|while IFS=: read -r _LOGIN _ _ _ _ _HOMEDIR _; do
        case "$(uname)" in
            Linux)   _HAS_PASSWD="$(passwd -S "${_LOGIN}"|grep -q ' P ' && printf "true" || printf "false")";;
            FreeBSD) _HAS_PASSWD="$(grep "^${_LOGIN}:" /etc/master.passwd|grep -q -v -e ':\*:' -e ':!:' -e ':!\*' -e ':\*LOCKED\*' && printf "true" || printf "false")";;
            *)       _HAS_PASSWD="false";;
        esac
        test -d "${_HOMEDIR}/.ssh" && ssh-keygen -lf "${_HOMEDIR}/.ssh/authorized_keys" > "${_HOMEDIR}/.ssh/authorized_keys.digest" 2>/dev/null
        if ! ${_HAS_PASSWD} && test -f "${_HOMEDIR}/.ssh/authorized_keys.digest" && test "$(cat "${_HOMEDIR}/.ssh/authorized_keys.digest"|wc -l)" -eq 0; then
            rm -f "${_HOMEDIR}/.ssh/authorized_keys.digest"
            continue
        elif ${_HAS_PASSWD}; then
            printf "\033[90mab\033[0m \033[1;97m%s\033[0m by password" "${_LOGIN}"
            passwd -S "${_LOGIN}" > /dev/null 2>&1 && passwd -S "${_LOGIN}"|awk '{printf ", last changed %s", $3}'
            printf "\n"
        fi
        test -f "${_HOMEDIR}/.ssh/authorized_keys.digest" && while read -r _KEYLINE; do
            _KEYTAG="$(printf "%s" "${_KEYLINE}"|awk '{for (i=3; i<NF; i++) printf "%s ", $i}'|sed 's/ $//')"
            if grep "${_KEYTAG}" "${_HOMEDIR}/.ssh/authorized_keys"|grep -q "bin/gitea"; then
                _USERID="$(printf "%s" "${_KEYTAG}"|awk -F- '{print $2}')"
                printf "🔑️ \033[1;97m%s\033[0m by key tagged %s corresponding to Gitea user \033[1;97m%s\033[0m\n" "${_LOGIN}" "${_KEYTAG}" "$(sqlite3 /var/lib/gitea/data/gitea.db "SELECT name FROM user WHERE id = ${_USERID}" 2>/dev/null || printf "??")"
            else
                printf "🔑️ \033[1;97m%s\033[0m by key tagged \033[1;97m%s\033[0m\n" "${_LOGIN}" "${_KEYTAG}"
            fi
        done < "${_HOMEDIR}/.ssh/authorized_keys.digest"
        rm -f "${_HOMEDIR}/.ssh/authorized_keys.digest"
    done)"
}

check_for_updates()
{
    # do an automatic update check
    case "$(uname)" in
        Linux)
            if [ ! "$(uname -r)" = "$(find /boot -type f -name 'vmlinuz-*'|sort -V|tail -n 1|sed 's;^/boot/vmlinuz-;;')" ]; then
                printf "  ⚠️  A system restart is required to upgrade the running kernel. Consider planning a \033[33mshutdown(8) -r\033[0m.\n"
            else
                printf "  ⏳  Checking for available package updates, please wait... "
                test ! -f "${HOME}/.cache/dotprofile-apt-update" || find "${HOME}/.cache/dotprofile-apt-update" -mtime +1 >/dev/null 2>&1 && apt update > /dev/null 2>&1
                mkdir -p "${HOME}/.cache" && touch "${HOME}/.cache/dotprofile-apt-update"
                _PKG_CNT="$(apt list --upgradable 2>/dev/null|tail -n +2|wc -l)"
                test -z "${_PKG_CNT}" && _PKG_CNT=0
                if [ "${_PKG_CNT}" -gt 0 ]; then
                    printf "\r  ℹ️  You have \033[1;37m%d\033[0m outdated package(s). Consider doing an \033[33mapt(8) upgrade\033[0m.\n" "${_PKG_CNT}"
                else
                    printf "\r  👍️  This \033[97m%s\033[0m operating system is up to date. Congratulations! \n" "$(grep PRETTY_NAME /etc/os-release|awk -F'"' '{print $2}')"
                fi
            fi
            ;;
        FreeBSD)
            if [ ! "$(freebsd-version -k)" = "$(freebsd-version -r)" ]; then
                printf "  ⚠️  A system restart is required to upgrade the running kernel. Consider planning a \033[33mshutdown(8) -r\033[0m.\n"
            elif [ ! "$(freebsd-version -r)" = "$(freebsd-version -u)" ]; then
                printf "  ⚠️  A system upgrade is halfway. You should perform a \033[33mfreebsd-update(8) install\033[0m.\n"
            else
                printf "  ⏳  Checking for operating system updates, please wait... "
                _AVL_MMN="$(HTTP_TIMEOUT=3 fetch -qo - "https://download.freebsd.org/releases/$(uname -m)/" 2>/dev/null|sed -e 's/[\r\n]//g' -e 's/></>\n</g'|grep ".-RELEASE"|sed -e 's/<[^>]*>//g' -e 's/-RELEASE.//'|sort -n|tail -n 1)"
                _AVL_VER="${_AVL_MMN}-$(HTTP_TIMEOUT=3 fetch -qo - "https://cgit.freebsd.org/src/plain/sys/conf/newvers.sh?h=releng/${_AVL_MMN}" 2>/dev/null|grep "^BRANCH="|cut -d'"' -f2)"
                if [ -z "${_AVL_MMN}" ]; then
                    printf "\r  ⚠️  Automatic update check failed (error retrieving the latest production version from www.freebsd.org).\n"
                elif ! printf "%s" "${_AVL_VER}"|grep -q "RELEASE"; then
                       printf "\r  ⚠️  Automatic update check failed (error retrieving the latest patchlevel version from cgit.freebsd.org).\n"
                elif [ "$(freebsd-version)" = "${_AVL_VER}" ]; then
                    printf "\r  ⌛  Checking for available package updates, please wait... "
                    test ! -f "${HOME}/.cache/dotprofile-pkg-update" || find "${HOME}/.cache/dotprofile-pkg-update" -mtime +1 >/dev/null 2>&1 && pkg update > /dev/null 2>&1
                    mkdir -p "${HOME}/.cache" && touch "${HOME}/.cache/dotprofile-pkg-update"
                    _PKG_CNT="$(pkg upgrade -n|grep -E -o "^The following [0-9]+"|grep -E -o "[0-9]+" || printf "0")"
                    if [ "${_PKG_CNT}" -gt 0 ]; then
                        printf "\r  ℹ️  You have \033[1;37m%d\033[0m outdated package(s). Consider using \033[33mpkg(8) upgrade\033[0m.\n" "${_PKG_CNT}"
                    else
                        printf "\r  👍️  This \033[97m%s\033[0m operating system is up to date. Congratulations! \n" "$(grep PRETTY_NAME /etc/os-release|awk -F'"' '{print $2}')"
                    fi
                elif [ "$(freebsd-version|awk -F- '{print $1}')" = "${_AVL_MMN}" ]; then
                    printf "\r  ⚠️  A security update is available: \033[1;37m%s\033[0m. Consider using \033[33mfreebsd-update(8)\033[0m.\n" "${_AVL_VER}"
                    printf "\n\033[3m"
                    HTTP_TIMEOUT=3 fetch -qo - "https://cgit.freebsd.org/src/plain/UPDATING?h=releng/${_AVL_MMN}" 2>/dev/null|tr '\n' '§'|sed 's/§§\t/§\t/g'|tr '§' '\n'|awk -v RS= "/${_AVL_VER}/"
                    printf "\033[0m"
                else
                    printf "\r  ⚠️  A new FreeBSD version is available: \033[1;37m%s\033[0m. Consider using \033[33mfreebsd-update(8)\033[0m.\n" "${_AVL_VER}"
                fi
                unset _AVL_VER _AVL_MMN
            fi
            ;;
    esac; unset _PKG_CNT
}

# now pimp up my screen (but only if we're on a login shell)
case $- in *i*)
    case "$0" in -*)
        print_logo
        print_hostid
        print_hwstats
        printf "\n"
        print_services
        printf "\n"
        case "$(uname)" in
            Linux)   print_dockervms;;
            FreeBSD) print_vboxvms;;
        esac
        printf "\n"
        print_connected
        test "$(id -u)" -eq 0 && print_usablelogins
        printf "\n"
        test "$(id -u)" -eq 0 && check_for_updates
        printf "\n"
    ;; esac
;; esac
How to use: just copy/paste this code in your .profile and you're set.

I made it portable enough so that it can run unmodified on FreeBSD and also on Ubuntu/Debian Linux. The only difference is that on Linux the script lists the running Docker instances, whereas on FreeBSD I list the VirtualBox VMs (it could be modified to list the bhyve VMs as well). This is just pragmatism: I wish many more people would use FreeBSD, but Linux is everywhere. This script is btw significantly faster on Linux with systemd (no need to parse the running services through the /etc/rc framework - even though I find systemd horrible for many reasons I don't want to debate here).

I hope you'll find this login dashboard useful.

Suggestions and improvements are welcome, but please keep in mind that the goals were :
- Keep it small
- Keep it as POSIX as possible (any non-archaic Bourne shell should be able to swallow it)
- Rely on default tools available on a stock system and avoid heavy interpeters (e.g. Python, Perl...) - sed/awk are okay
- Login time should be kept under a few seconds.

P.S. tips to paypal.me/pmbaty if you want to buy me a coffbeer :)
 
Back
Top