X-Git-Url: https://git.xandkar.net/?a=blobdiff_plain;f=home%2Flib%2Flogin_functions.sh;h=c393938ccdbf784c6077b294e453a71e6d517eb5;hb=9492242e5dc9999cafafb7492077a7cc8f1ba6e3;hp=01e0ffa63d71c1de5b0de37c1b813c8b1e899be3;hpb=773a0061dccdd6763fbff37335038aa636960add;p=khome.git diff --git a/home/lib/login_functions.sh b/home/lib/login_functions.sh index 01e0ffa..c393938 100644 --- a/home/lib/login_functions.sh +++ b/home/lib/login_functions.sh @@ -1,10 +1,138 @@ -# +#! /bin/bash + +## open : string -> unit +## +## Fork xdg-open so we don't block current terminal session when opening +## things like pdf files. For example: +## +## open book.pdf +## +open() { + (xdg-open "$1" &) & +} + +## notify_done : unit -> unit +notify_done() { + local -r _status_code="$?" + local -r _program="$1" + local _timestamp + _timestamp="$(timestamp)" + local -r _msg="$_timestamp [$_program] done " + if [[ "$_status_code" -eq 0 ]] + then + notify-send -u normal "$_msg OK: $_status_code" + else + notify-send -u critical "$_msg ERROR: $_status_code" + fi +} + +## p : string -> unit +p() { + local -r usage='Usage: p [FILE] NAME' + local name + local file + + case "$#" in + 0) + echo "$usage" >&2 + return 1;; + 1) + file=~/._p/p + name="$1";; + 2) + file="$1" + name="$2";; + *) + echo "$usage" >&2 + return 1;; + esac + + awk \ + -v _s="$name" \ + ' + BEGIN {_s = tolower(_s)} + + # TODO fzf/dmenu select instead of searching: + /^[a-zA-Z]/ && tolower($1) ~ _s && NF >= 2 { + n++ + s = $1 + p = $NF + if (NF == 2) { + e = "" + u = "" + } else if (NF == 3) { + e = $2 + u = "" + } else { + e = $2 + u = $3 + } # TODO What would NF > 4 mean? + + printf("%d [O] s:\"%s\", e:\"%s\", u:\"%s\"\n", n, s, e, u) > "/dev/stderr" + if (match(u, "@")) { + tmp = e + e = u + u = tmp + printf("%d [C] s:\"%s\", e:\"%s\", u:\"%s\"\n", n, s, e, u) > "/dev/stderr" + } + ps[n] = p + } + + END { + printf "%s", ps[n] # XXX Intentionally avoiding newline in the result. + if (n == 1) { + exit 0 + } else if (n == 0) { + printf "[ERROR] Found nothing.\n" > "/dev/stderr" + exit 1 + } else if (n > 1) { + # TODO fzf-select which of the records the user (ahem, me) wants. + printf "[WARNING] Found more than one record. Selecting the last one.\n" > "/dev/stderr" + exit 0 + } + } + ' \ + "$file" \ + | xsel -i -b -t 30000 +} + +dl() { + local -r timestamp="$(date --iso-8601=ns)" + local -r dir="$HOME"/dl/adhoc/"$timestamp" + local -r url_file_path="${dir}/url" + + mkdir -p "$dir" + touch "$url_file_path" + cd "$dir" +} + +## web search +## ws : string -> unit +ws() { + local line search_string0 search_string + + search_string0="$*" + case "$search_string0" in + '') + while read -r line; do + search_string="${search_string} ${line}" + done;; + *) + search_string="$search_string0";; + esac + + firefox --search "$search_string" +} + +## dictionary +## d : string -> string list d() { local -r word=$(fzf < /usr/share/dict/words) dict "$word" } +## shell_activity_report : (mon | dow) -> string list shell_activity_report() { # TODO: optional concrete number output # TODO: optional combinations of granularities: hour, weekday, month, year @@ -102,6 +230,7 @@ shell_activity_report() { }' } +## top_commands : unit -> (command:string * count:number * bar:string) list top_commands() { history \ | awk ' @@ -136,58 +265,35 @@ top_commands() { | column -t } -# Top Disk-Using directories -# TODO: Consider using numfmt instead of awk +## Top Disk-Using directories +## tdu : path-string -> (size:number * directory:path-string) list tdu() { - du "$1" \ - | awk ' - { - size = $1 - path = $0 - sub("^" $1 "\t+", "", path) - paths[path] = size - if (size > max) - max = size - } + local -r root_path="$1" - END { - for (path in paths) { - size = paths[path] - pct = 100 * (size / max) - gb = size / 1024 / 1024 - printf("%6.2f %3d%% %s\n", gb, pct, path) - } - } - ' \ - | sort -r -n -k 1 \ + du -h "$root_path" \ + | sort -r -h -k 1 \ | head -50 \ | tac # A slight optimization: head can exit before traversing the full input. } -# Top Disk-Using Files +## Top Disk-Using Files +## tduf : path-string list -> (size:number * file:path-string) list tduf() { - find "$1" -type f -printf '%s\t%p\0' \ + find "$@" -type f -printf '%s\t%p\0' \ | sort -z -n -k 1 \ | tail -z -n 50 \ - | gawk -v RS='\0' ' - { - size = $1 - path = $0 - sub("^" $1 "\t+", "", path) - gb = size / 1024 / 1024 / 1024 - printf("%f\t%s\n", gb, path) - }' + | numfmt -z --to=iec \ + | tr '\0' '\n' } # Most-recently modified file system objects +## recent : ?(path-string list) -> path-string list recent() { # NOTES: - # - intentionally not quoting the parameters, so that some can be ignored - # if not passed, rather than be passed to find as an empty string; # - %T+ is a GNU extension; # - gawk is able to split records on \0, while awk cannot. - find $@ -printf '%T@ %T+ %p\0' \ + find "$@" -printf '%T@ %T+ %p\0' \ | tee >(gawk -v RS='\0' 'END { printf("[INFO] Total found: %d\n", NR); }') \ | sort -z -k 1 -n -r \ | head -n "$(stty size | awk 'NR == 1 {print $1 - 5}')" -z \ @@ -200,23 +306,28 @@ recent() { }' } +## recent_dirs : ?(path-string list) -> path-string list recent_dirs() { - recent "$1" -type d + recent "$@" -type d } +## recent_files : ?(path-string list) -> path-string list recent_files() { - recent "$1" -type f + recent "$@" -type f } +## pa_def_sink : unit -> string pa_def_sink() { pactl info | awk '/^Default Sink:/ {print $3}' } +## void_pkgs : ?(string) -> json void_pkgs() { curl "https://xq-api.voidlinux.org/v1/query/x86_64?q=$1" | jq '.data' } -# Colorful man +## Colorful man +## man : string -> string man() { # mb: begin blink # md: begin bold @@ -237,15 +348,20 @@ man() { command man "$@" } -# new experiment +## new experiment +## x : string list -> unit x() { cd "$(~/bin/x $@)" || kill -INT $$ } +## ocaml repl +## hump : unit -> unit hump() { ledit -l "$(stty size | awk '{print $2}')" ocaml $@ } +## search howtos +## howto : unit -> string howto() { cat "$(find ~/arc/doc/HOWTOs -mindepth 1 -maxdepth 1 | sort | fzf)" } @@ -255,14 +371,15 @@ _yt() { local -r uri="$2" local -r opts="$3" - local -r id=$(youtube-dlc --get-id "$uri") - local -r title=$(youtube-dlc --get-title "$uri" | sed 's/[^A-Za-z0-9._-]/_/g') + local -r yt=youtube-dl + local -r id=$("$yt" --get-id "$uri") + local -r title=$("$yt" --get-title "$uri" | sed 's/[^А-Яа-яA-Za-z0-9._-]/_/g') local -r dir="${base_dir}/${title}--${id}" mkdir -p "$dir" cd "$dir" || kill -INT $$ echo "$uri" > 'uri' - youtube-dlc $opts -c --write-description --write-info-json "$uri" + "$yt" $opts -c --write-all-thumbnails --write-description --write-info-json "$uri" } yt_audio() { @@ -290,7 +407,7 @@ gh_clone() { mkdir -p "$gh_dir" cd "$gh_dir" || kill -INT $$ gh_fetch_repos "$gh_user_type" "$gh_user_name" \ - | jq --raw-output '.[] | select(.fork | not) | .git_url' \ + | jq --raw-output '.[] | select(.fork | not) | .clone_url' \ | parallel -j 25 \ git clone {} } @@ -311,6 +428,59 @@ gh_clone_repo() { git clone "$1" } +bar() { + local -r len="${1:-79}" # 1st arg or 79. + local -r char="${2:--}" # 2nd arg or a dash. + for _ in {1.."$len"}; do + printf '%c' "$char"; + done +} + +daily_todo_file_template() { +cat << EOF +=============================================================================== +$(date '+%F %A') +=============================================================================== + +------------------------------------------------------------------------------- +TODAY +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +CURRENT +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +BLOCKED +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +BACKLOG +------------------------------------------------------------------------------- +EOF +} + +today() { + local date + date="$(date +%F)" + local -r dir="$DIR_TODO/daily" + local -r file="$dir/$date.txt" + + mkdir -p "$dir" + if [ ! -f "$file" ] + then + daily_todo_file_template > "$file" + fi + cd "$DIR_TODO" && "$EDITOR" $EDITOR_ARGS "$file" +} + +todo() { + cd "$DIR_TODO" && "$EDITOR" TODO +} + work_log_template() { cat << EOF $(date '+%F %A') @@ -348,15 +518,6 @@ note() { vim -c 'set spell' "$DIR_NOTES/$(date +'%Y_%m_%d--%H_%M_%S%z')--$1.md" } -weather() { - local _weather_location - case "$1" in - '') _weather_location="$WEATHER_LOCATION";; - *) _weather_location="$1" - esac - curl "http://wttr.in/$_weather_location?format=v2" -} - _bt_devs_infos() { # grep's defintion of a line does not include \r, wile awk's does and # which bluetoothctl outputs @@ -497,11 +658,54 @@ status() { echo 'accounting' - printf '%stmux\n%ssessions %d, clients %d\n' \ - "$indent_unit" \ - "${indent_unit}${indent_unit}" \ - "$(tmux list-sessions 2> /dev/null | wc -l)" \ - "$(tmux list-clients 2> /dev/null | wc -l)" + # TODO Bring back seesion and client listing, but per server/socket. + printf '%stmux\n' "$indent_unit" + ps -eo comm,cmd \ + | awk ' + # Expecting lines like: + # "tmux: server tmux -L pistactl new-session -d -s pistactl" + # "tmux: client tmux -L foo" + # "tmux: client tmux -Lbar" + # "tmux: client tmux" + # "tmux: server tmux -L foo -S bar" <-- -S takes precedence + /^tmux:/ { + # XXX This of course assumes pervasive usage of -L + # TODO Handle -S + role=$2 + + split($0, sides_of_S, "-S") + split(sides_of_S[2], words_right_of_S, FS) + + split($0, sides_of_L, "-L") + split(sides_of_L[2], words_right_of_L, FS) + + if (words_right_of_S[1]) { + sock = "path." words_right_of_S[1] + } else if (words_right_of_L[1]) { + sock = "name." words_right_of_L[1] + } else { + sock = "default" + } + + roles[role]++ + socks[sock]++ + count[role, sock]++ + } + + END { + for (sock in socks) { + clients = count["client", sock] + printf "%s ", sock + if (clients) { + printf "<-> %d", clients + } + printf "\n" + } + printf "\n" + }' \ + | sort \ + | column -t \ + | indent "${indent_unit}${indent_unit}" echo @@ -536,13 +740,11 @@ status() { echo printf '%smem by proc\n' "$indent_unit" - ps -eo rss,cmd \ + ps -eo rss,comm \ | awk -v total="$(free | awk '$1 == "Mem:" {print $2; exit}')" ' NR > 1 { rss = $1 - cmd = $2 - n = split(cmd, path, "/") # _may_ be a path - proc = path[n] + proc = $2 by_proc[proc] += rss } @@ -556,14 +758,29 @@ status() { echo + local _dir temp_input label_file label + printf '%sthermal\n' "$indent_unit" - for _dir in /sys/class/thermal/thermal_zone* - do - printf '%s %.2f C\n' \ - $(cat "$_dir"/type) \ - $(( $(cat "$_dir"/temp) / 1000 )) + for _dir in /sys/class/hwmon/hwmon*; do + cat "$_dir"/name + find "$_dir"/ -name 'temp*_input' \ + | while read -r temp_input; do + label_file=${temp_input//_input/_label} + if [ -f "$label_file" ]; then + label=$(< "$label_file") + else + label='' + fi + awk -v label="$label" '{ + if (label) + label = sprintf(" (%s)", label) + printf("%.2f°C%s\n", $1 / 1000, label) + }' \ + "$temp_input" + done \ + | sort \ + | indent "$indent_unit" done \ - | column -t \ | indent "${indent_unit}${indent_unit}" echo 'net' @@ -619,43 +836,46 @@ status() { echo "${indent_unit}-->" + # TODO Populate pid->cmd dict from `ps -eo pid,comm` and lookup progs there + # since netstat -p output comes out truncated. + sudo -n netstat -tulnp \ | awk -v indent="${indent_unit}${indent_unit}" ' NR > 2 && ((/^tcp/ && proc = $7) || (/^udp/ && proc = $6)) { - protocol = $1 - addr = $4 - port = a[split(addr, a, ":")] - name = p[split(proc, p, "/")] - names[name] = 1 - protocols[protocol] = 1 - if (!seen[protocol, name, port]++) - ports[protocol, name, ++seen[protocol, name]] = port - } - - END { - for (protocol in protocols) { - printf "%s%s\t", indent, toupper(protocol) - for (name in names) { - if (n = seen[protocol, name]) { - sep = "" - printf "%s:", name - for (i = 1; i <= n; i++) { - printf "%s%d", sep, ports[protocol, name, i] - sep = "," - } - printf " " - } - } - printf "\n" - } - }' + protocol = $1 + addr = $4 + port = a[split(addr, a, ":")] + name = p[split(proc, p, "/")] + names[name] = 1 + protocols[protocol] = 1 + if (!seen[protocol, name, port]++) + ports[protocol, name, ++seen[protocol, name]] = port + } + + END { + for (protocol in protocols) { + printf "%s%s\t", indent, toupper(protocol) + for (name in names) { + if (n = seen[protocol, name]) { + sep = "" + printf "%s:", name + for (i = 1; i <= n; i++) { + printf "%s%d", sep, ports[protocol, name, i] + sep = "," + } + printf " " + } + } + printf "\n" + } + }' echo "${indent_unit}<->" - printf '%sTCP: ' "${indent_unit}${indent_unit}" + printf '%sTCP\t' "${indent_unit}${indent_unit}" sudo -n netstat -tnp \ | awk 'NR > 2 && $6 == "ESTABLISHED" {print $7}' \ - | awk -F/ '{print $2}' \ + | awk '{sub("^[0-9]+/", ""); print}' \ | sort -u \ | xargs \ | column -t @@ -663,19 +883,75 @@ status() { # TODO: iptables summary } -ssh_invalid_attempts_from() { +ssh_invalid_by_addr() { awk ' /: Invalid user/ && $5 ~ /^sshd/ { - u=$8 addr=$10 == "port" ? $9 : $10 max++ - curr[addr]++ + by_addr[addr]++ + } + + END { + for (addr in by_addr) + if ((c = by_addr[addr]) > 1) + printf "%d %d %s\n", c, max, addr + } + ' \ + /var/log/auth.log \ + /var/log/auth.log.1 \ + | sort -n -k 1 \ + | bar_gauge -v width="$(stty size | awk '{print $2}')" -v num=1 -v ch_right=' ' -v ch_left=' ' -v ch_blank=' ' \ + | column -t +} + +ssh_invalid_by_day() { + awk ' + BEGIN { + m["Jan"] = "01" + m["Feb"] = "02" + m["Mar"] = "03" + m["Apr"] = "04" + m["May"] = "05" + m["Jun"] = "06" + m["Jul"] = "07" + m["Aug"] = "08" + m["Sep"] = "09" + m["Oct"] = "10" + m["Nov"] = "11" + m["Dec"] = "12" + } + + /: Invalid user/ && $5 ~ /^sshd/ { + day = m[$1] "-" $2 + max++ + by_day[day]++ + } + + END { + for (day in by_day) + if ((c = by_day[day]) > 1) + printf "%d %d %s\n", c, max, day + } + ' \ + /var/log/auth.log \ + /var/log/auth.log.1 \ + | sort -k 3 \ + | bar_gauge -v width="$(stty size | awk '{print $2}')" -v num=1 -v ch_right=' ' -v ch_left=' ' -v ch_blank=' ' \ + | column -t +} + +ssh_invalid_by_user() { + awk ' + /: Invalid user/ && $5 ~ /^sshd/ { + user=$8 + max++ + by_user[user]++ } END { - for (addr in curr) - if ((c = curr[addr]) > 1) - print c, max, addr + for (user in by_user) + if ((c = by_user[user]) > 1) + printf "%d %d %s\n", c, max, user } ' \ /var/log/auth.log \