X-Git-Url: https://git.xandkar.net/?a=blobdiff_plain;f=home%2Flib%2Flogin_functions.sh;h=c393938ccdbf784c6077b294e453a71e6d517eb5;hb=9492242e5dc9999cafafb7492077a7cc8f1ba6e3;hp=d5bd5dd1041afd15596ac565901f47d9e0ae8a8b;hpb=887d4f6d94e0b7d06b9c13bb1721bc2568aca648;p=khome.git diff --git a/home/lib/login_functions.sh b/home/lib/login_functions.sh index d5bd5dd..c393938 100644 --- a/home/lib/login_functions.sh +++ b/home/lib/login_functions.sh @@ -1,26 +1,72 @@ -# +#! /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="$1" \ - ' - BEGIN {_s = tolower(_s)} - - /^[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? + -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, "@")) { @@ -29,12 +75,35 @@ p() { 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 +} - printf "%s", p # XXX Intentionally avoiding newline in the result. - } - ' \ - ~/._p/p \ - | 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 @@ -197,32 +266,12 @@ top_commands() { } ## Top Disk-Using directories -## TODO: Consider using numfmt instead of awk ## tdu : path-string -> (size:number * directory:path-string) list tdu() { local -r root_path="$1" - du "$root_path" \ - | awk ' - { - size = $1 - path = $0 - sub("^" $1 "\t+", "", path) - paths[path] = size - if (size > max) - max = size - } - - 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. @@ -234,14 +283,8 @@ tduf() { 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 @@ -328,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() { @@ -363,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 {} } @@ -384,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') @@ -561,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 @@ -696,36 +836,39 @@ 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}<->" @@ -763,32 +906,32 @@ ssh_invalid_by_addr() { 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 - } + 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 \