Files
profile/profile.d/info.sh
T

458 lines
16 KiB
Bash

#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# Common helpers (helpers/common.sh)
# shellcheck source=../helpers/common.sh
declare -F _profile_load_helpers >/dev/null || . "$(dirname -- "${BASH_SOURCE[0]}")/../helpers/common.sh"
load_conf info
# ------------------------------------------------------------------------------
# Show profile version
# Usage: ver
ver()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'ver' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"ver --help\" to display usage."
return 1
fi
# Restore the getopt-normalised (shell-quoted) argument list
local -a _pargs=()
_getopt_split _pargs "$PARSED"
set -- "${_pargs[@]}"
while true; do
case "$1" in
-h|--help)
printf "ver: Display the current profile version.\nUsage: ver\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"ver --help\" to display usage."
return 1
;;
esac
done
[[ -z ${PROFVERSION:-} ]] && \
disp W "No version defined. Profile is probably badly installed." && \
return 1
disp "Profile version $PROFVERSION."
}
export -f ver
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display weather for the given city (or the default one)
# Usage: meteo [city1 city2 ...]
meteo()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'meteo' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"meteo --help\" to display usage."
return 1
fi
# Restore the getopt-normalised (shell-quoted) argument list
local -a _pargs=()
_getopt_split _pargs "$PARSED"
set -- "${_pargs[@]}"
while true; do
case "$1" in
-h|--help)
printf "meteo: Fetch weather data.\n"
printf "Usage: meteo [city1 city2 ...]\n"
printf "If no city is provided, the default city from configuration will be used.\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"meteo --help\" to display usage."
return 1
;;
esac
done
local cities=("$@")
local city="" encoded=""
[[ $# -eq 0 ]] && cities=("${METEO_DEFAULT_CITY:-}")
if [[ ${#cities[@]} -eq 0 || -z "${cities[0]}" ]]; then
disp E "No city given and METEO_DEFAULT_CITY is not set. Use 'meteo <city>'."
return 1
fi
for city in "${cities[@]}"; do
encoded=$(urlencode "$city")
dwl "https://wttr.in/$encoded" || \
disp E "Failed to fetch weather data for $city."
done
}
export -f meteo
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display a system information block using only /proc and /sys — no external tools.
# Usage: pinfo
pinfo()
{
local _row
_row() {
printf " %b%-12s%b %b%s%b\n" "$_lbl" "$1" "$_rst" "$_val" "$2" "$_rst"
}
local PARSED
PARSED=$(getopt -o h --long help -n 'pinfo' -- "$@")
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"pinfo --help\" to display usage."
return 1
fi
# Restore the getopt-normalised (shell-quoted) argument list
local -a _pargs=()
_getopt_split _pargs "$PARSED"
set -- "${_pargs[@]}"
while true; do
case "$1" in
-h|--help)
printf "pinfo: Display system information from /proc and /sys (no external tools required).\n"
printf "Usage: pinfo\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"pinfo --help\" to display usage."
return 1
;;
esac
done
# --- Hostname ---
local hostname_str
if [[ -r /proc/sys/kernel/hostname ]]; then
read -r hostname_str < /proc/sys/kernel/hostname
else
hostname_str="${HOSTNAME:-unknown}"
fi
# --- OS release (security: parsed line by line, never sourced) ---
# /usr/lib/os-release is the documented fallback location. Values may be
# wrapped in double or single quotes.
local os_name="Unknown" os_version="" _osr_file=""
if [[ -r /etc/os-release ]]; then
_osr_file=/etc/os-release
elif [[ -r /usr/lib/os-release ]]; then
_osr_file=/usr/lib/os-release
fi
if [[ -n $_osr_file ]]; then
local _osr_key _osr_val
while IFS='=' read -r _osr_key _osr_val; do
_osr_val="${_osr_val//\"/}"
_osr_val="${_osr_val//\'/}"
case "$_osr_key" in
NAME) os_name="$_osr_val" ;;
VERSION_ID) os_version="$_osr_val" ;;
esac
done < "$_osr_file"
fi
# --- Kernel release ---
local kernel_str="unknown"
[[ -r /proc/sys/kernel/osrelease ]] && read -r kernel_str < /proc/sys/kernel/osrelease
# --- Architecture (set by bash at startup from the ELF interpreter) ---
local arch_str="${HOSTTYPE:-unknown}"
# --- Uptime (seconds from /proc/uptime) ---
local uptime_str="unknown"
if [[ -r /proc/uptime ]]; then
local _upraw
read -r _upraw _ < /proc/uptime
local _upsec="${_upraw%%.*}"
local _days=$(( _upsec / 86400 ))
local _hours=$(( (_upsec % 86400) / 3600 ))
local _mins=$(( (_upsec % 3600) / 60 ))
local _secs=$(( _upsec % 60 ))
uptime_str=""
(( _days > 0 )) && uptime_str+="${_days}d "
(( _hours > 0 || _days > 0 )) && uptime_str+="${_hours}h "
uptime_str+="${_mins}m ${_secs}s"
fi
# --- Load average ---
local load_str="unknown"
if [[ -r /proc/loadavg ]]; then
local _l1 _l5 _l15
read -r _l1 _l5 _l15 _ < /proc/loadavg
load_str="${_l1} ${_l5} ${_l15} (1/5/15 min)"
fi
# --- CPU model and logical/physical core count (pure bash) ---
local cpu_model="unknown" cpu_threads=0
local -A _seen_cores=()
local _cur_phys="" _cpu_line
if [[ -r /proc/cpuinfo ]]; then
while IFS= read -r _cpu_line; do
case "$_cpu_line" in
"model name"*|"Model name"*|"Hardware"*)
[[ "$cpu_model" == "unknown" ]] && cpu_model="${_cpu_line#*: }"
;;
"processor"*)
cpu_threads=$(( cpu_threads + 1 ))
;;
"physical id"*)
_cur_phys="${_cpu_line#*: }"
;;
"core id"*)
_seen_cores["${_cur_phys}:${_cpu_line#*: }"]=1
;;
esac
done < /proc/cpuinfo
fi
local cpu_cores="${#_seen_cores[@]}"
(( cpu_cores == 0 )) && cpu_cores=$cpu_threads
# --- Memory (pure bash, /proc/meminfo, values in kB) ---
# MemAvailable only exists since Linux 3.14; on older kernels it is
# estimated as free + buffers + cached.
local mem_total=0 mem_available=-1 mem_free=0 mem_buffers=0 mem_cached=0
local swap_total=0 swap_free=0
if [[ -r /proc/meminfo ]]; then
local _mkey _mval _munit
while read -r _mkey _mval _munit; do
case "${_mkey%:}" in
MemTotal) mem_total="$_mval" ;;
MemAvailable) mem_available="$_mval" ;;
MemFree) mem_free="$_mval" ;;
Buffers) mem_buffers="$_mval" ;;
Cached) mem_cached="$_mval" ;;
SwapTotal) swap_total="$_mval" ;;
SwapFree) swap_free="$_mval" ;;
esac
done < /proc/meminfo
fi
(( mem_available < 0 )) && mem_available=$(( mem_free + mem_buffers + mem_cached ))
local mem_total_mib=$(( mem_total / 1024 ))
local mem_used_mib=$(( (mem_total - mem_available) / 1024 ))
local swap_total_mib=$(( swap_total / 1024 ))
local swap_used_mib=$(( (swap_total - swap_free) / 1024 ))
# --- Process count (glob over numeric /proc entries) ---
local proc_count=0 _pdir
for _pdir in /proc/[0-9]*/; do
[[ -d "$_pdir" ]] && proc_count=$(( proc_count + 1 ))
done
# --- Shell and terminal ---
local shell_str="${BASH:-bash} ${BASH_VERSION%\(*}"
local term_str="${TERM:-unknown}"
# --- GPU (no external tools required; sysfs first, then /proc/bus/pci) ---
local -a gpu_list=()
local _gdir _gname _uev_key _uev_val _uev_driver _uev_pci
# Preferred: sysfs drm — each card has a device/uevent file readable
# without root, giving the PCI id and the kernel driver in use. A label
# file (rarely provided by drivers) wins when present.
for _gdir in /sys/class/drm/card[0-9]*/device; do
[[ -d "$_gdir" ]] || continue
# Connector entries (card0-eDP-1, card0-HDMI-A-1, ...) also carry a
# "device" link to the same GPU: keep the plain "cardN" entries only.
_gname="${_gdir%/device}"
[[ "${_gname##*/}" =~ ^card[0-9]+$ ]] || continue
_gname=""
_uev_driver="" _uev_pci=""
if [[ -r "$_gdir/uevent" ]]; then
while IFS='=' read -r _uev_key _uev_val; do
case "$_uev_key" in
DRIVER) _uev_driver="$_uev_val" ;;
PCI_ID) _uev_pci="$_uev_val" ;;
esac
done < "$_gdir/uevent"
[[ -n "$_uev_pci" ]] && _gname="PCI ${_uev_pci}"
[[ -n "$_uev_driver" ]] && _gname+=" (${_uev_driver})"
fi
if [[ -r "$_gdir/label" ]]; then
read -r _gname < "$_gdir/label"
elif [[ -r "$_gdir/../label" ]]; then
read -r _gname < "$_gdir/../label"
fi
[[ -z "$_gname" ]] && _gname="unknown GPU"
gpu_list+=("$_gname")
done
# Fallback: /proc/bus/pci/devices. It holds no human-readable name: the
# tab-separated fields are the bus address, vendor+device ids (8 hex
# digits), IRQ, the base addresses and sizes, then the kernel driver name
# as the last field when one is bound. Only well-known GPU drivers are
# reported, as vendor ids alone also match every other device of a vendor.
if (( ${#gpu_list[@]} == 0 )) && [[ -r /proc/bus/pci/devices ]]; then
local -a _pci=()
local _pci_id _pci_drv
while IFS=$'\t' read -r -a _pci; do
_pci_drv="${_pci[-1]// /}"
case "$_pci_drv" in
i915|xe|amdgpu|radeon|nouveau|nvidia|virtio-pci|vmwgfx|qxl|bochs-drm|cirrus|mgag200|ast)
_pci_id="${_pci[1]}"
gpu_list+=("PCI ${_pci_id:0:4}:${_pci_id:4:4} (${_pci_drv})")
;;
esac
done < /proc/bus/pci/devices
fi
# --- Render ---
# Colours are dropped under NO_COLOR, like everywhere else in the profile.
local _lbl="${BIWhite:-}" _val="${ICyan:-}" _rst="${DEFAULTCOL:-}"
if [[ -n ${NO_COLOR:-} ]]; then
_lbl="" _val="" _rst=""
fi
printf "\n"
_row "Hostname" "$hostname_str"
_row "OS" "${os_name}${os_version:+ $os_version}"
_row "Kernel" "$kernel_str"
_row "Arch" "$arch_str"
_row "Uptime" "$uptime_str"
_row "Load avg" "$load_str"
_row "CPU" "$cpu_model"
_row "Cores" "${cpu_cores} physical, ${cpu_threads} logical"
if (( ${#gpu_list[@]} > 0 )); then
local _gi
for _gi in "${!gpu_list[@]}"; do
local _gpu_lbl="GPU"
(( ${#gpu_list[@]} > 1 )) && _gpu_lbl="GPU $(( _gi + 1 ))"
_row "$_gpu_lbl" "${gpu_list[$_gi]}"
done
fi
_row "Memory" "${mem_used_mib} MiB used / ${mem_total_mib} MiB total"
if (( swap_total > 0 )); then
_row "Swap" "${swap_used_mib} MiB used / ${swap_total_mib} MiB total"
fi
_row "Processes" "$proc_count"
_row "Shell" "$shell_str"
_row "Terminal" "$term_str"
printf "\n"
unset -f _row
}
export -f pinfo
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display system general information
# Usage: showinfo
showinfo()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'showinfo' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1
fi
# Restore the getopt-normalised (shell-quoted) argument list
local -a _pargs=()
_getopt_split _pargs "$PARSED"
set -- "${_pargs[@]}"
while true; do
case "$1" in
-h|--help)
printf "showinfo: Display system information (hostname, kernel, uptime and fetch output when available).\n"
printf "Usage: showinfo\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1
;;
esac
done
local hostname_str figopt=()
# /proc first: the hostname binary is missing from minimal installations
if [[ -r /proc/sys/kernel/hostname ]]; then
read -r hostname_str < /proc/sys/kernel/hostname
else
hostname_str="${HOSTNAME:-$(hostname 2>/dev/null)}"
fi
printf "\n"
if command -v figlet >/dev/null 2>&1; then
[[ -s /usr/share/figlet/ansi_shadow.flf ]] && \
figopt=(-f ansi_shadow)
figlet -k "${figopt[@]}" "$hostname_str"
else
printf "%s\n" "$hostname_str"
fi
printf "\n"
if command -v fastfetch >/dev/null 2>&1; then
fastfetch
elif command -v neofetch >/dev/null 2>&1; then
neofetch
else
pinfo "$@"
fi
}
export -f showinfo
# ------------------------------------------------------------------------------
# EOF