X-Git-Url: https://iankelling.org/git/?a=blobdiff_plain;f=brc;h=84837358700987d5510d9d36e2c555d9ffd25e70;hb=f5fe5af7aec9c23412c9c349e21c42f6c2208582;hp=0b89b663c9173b0f0aedb0c7cc87420bd247ce89;hpb=7b47d6a266340223e78317cfe0570868f45a4cad;p=distro-setup diff --git a/brc b/brc index 0b89b66..8483735 100644 --- a/brc +++ b/brc @@ -141,6 +141,7 @@ if ! type -p stty >/dev/null; then fi +use_color=false if [[ $- == *i* ]]; then # for readline-complete.el if [[ $LC_INSIDE_EMACS ]]; then @@ -151,6 +152,11 @@ if [[ $- == *i* ]]; then bind '"\C-i": self-insert' else + + if [[ $TERM != dumb ]] && test -t 1; then + use_color=true + fi + if [[ $KONSOLE_PROFILE_NAME ]]; then TERM=xterm-256color fi @@ -198,6 +204,15 @@ if [[ $- == *i* ]]; then fi +case $TERM in + # fixup broken backspace in chroots + xterm-kitty|alacritty) + chroot() { + TERM=xterm-256color command chroot "$@" + } + ;; +esac + export BC_LINE_LENGTH=0 # ansible option @@ -209,6 +224,7 @@ export PROFILE_TASKS_TASK_OUTPUT_LIMIT=100 # i for insensitive. the rest from # X means dont remove the current screenworth of output upon exit # R means to show colors n things +# a useful flag is -F aka --quit-if-one-screen export LESS=RXij12 export SYSTEMD_LESS=$LESS @@ -223,8 +239,7 @@ export SL_INFO_DIR=/p/sshinfo if [[ -s $bashrc_dir/path-add-function ]]; then source $bashrc_dir/path-add-function if [[ $SSH_CLIENT ]]; then - # [[ -d /home/iank/.iank/e/e ]] mounts it unnecessarily, so use this. - if grep -qF /home/iank/.iank/e/e /etc/auto.iank /etc/exports &>/dev/null; then + if grep -qF /home/iank/.iank/e/e /etc/exports &>/dev/null; then export EMACSDIR=/home/iank/.iank/e/e fi path-add $bashrc_dir @@ -245,10 +260,7 @@ if [[ $SOE ]]; then fi fi -# based on readme.debian. dunno if this will break on other distros. -if [[ -s /usr/share/wcd/wcd-include.sh ]]; then - source /usr/share/wcd/wcd-include.sh -fi + mysrc() { @@ -267,11 +279,33 @@ mysrc() { mysrc /a/bin/small-misc-bash/ll-function mysrc /a/bin/distro-functions/src/package-manager-abstractions +# things to remember: +# ALT-C - cd into the selected directory +# CTRL-T - Paste the selected file path into the command line +# +# good guide to some of its basic features is the readme file +# https://github.com/junegunn/fzf + +# if [[ -s /usr/share/doc/fzf/examples/key-bindings.bash ]]; then +# source /usr/share/doc/fzf/examples/key-bindings.bash +# fi # * functions -ccomp() { # copy completion - local src=$1 - local c + +### begin FSF section ### + +# Comments before functions are meant to be good useful +# documentation. If they fail at that, please improve them or send Ian a +# note. + +## copy bash completion +# Usage: ORIGINAL_COMMAND TARGET_COMMAND... +# +# It copies how the bash completion works from one command to other +# commands. +ccomp() { + local c src + src=$1 shift if ! c=$(complete -p $src 2>/dev/null); then _completion_loader $src &>/dev/null ||: @@ -283,6 +317,277 @@ ccomp() { # copy completion eval $c $* } +## directory history tracking and navigation. +# +# cd becomes a function, also aliased to c. b to go back, f to go +# forward, cl to list recent directories and choose one. +# +# The finer details you may want to skip: +# +# We also define bl to print the list of back and forward directories. +# +# We keep 2 stacks, forward and back. Unlike with a web browser, the +# forward stack is not erased when going somewhere new. +# +# Recent directories are stored in ~/.cdirs. +# +declare -a _dir_forward _dir_back +c() { + # normally, the top of _dir_back is our current dir. if it isn't, + # put it on there, except we don't want to do that when we + # just launched a shell + if [[ $OLDPWD ]]; then + if (( ${#_dir_back[@]} == 0 )) || [[ ${_dir_back[-1]} != "$PWD" ]]; then + _dir_back+=("$PWD") + fi + fi + command cd "$@" + if (( ${#_dir_back[@]} == 0 )) || [[ ${_dir_back[-1]} != "$PWD" ]]; then + _dir_back+=("$PWD") + fi + echo "$PWD" >> ~/.cdirs +} +ccomp cd c + +# back +b() { + local top_back + if (( ${#_dir_back[@]} == 0 )); then + echo "nothing left to go back to" >&2 + return 0 + fi + top_back="${_dir_back[-1]}" + + if [[ $top_back == "$PWD" ]] && (( ${#_dir_back[@]} == 1 )); then + echo "already on last back entry" >&2 + return 0 + fi + + + if [[ $top_back == "$PWD" ]]; then + # add to dirf if not already there + if (( ${#_dir_forward[@]} == 0 )) || [[ ${_dir_forward[-1]} != "$top_back" ]]; then + _dir_forward+=("$top_back") + fi + unset "_dir_back[-1]" + command cd "${_dir_back[-1]}" + else + if (( ${#_dir_forward[@]} == 0 )) || [[ ${_dir_forward[-1]} != "$PWD" ]]; then + _dir_forward+=("$PWD") + fi + command cd "$top_back" + fi + + # Interesting feature, not sure I want it. + # give us a peek at what is next in the list + # if (( ${#_dir_back[@]} >= 2 )); then + # printf "%s\n" "${_dir_back[-2]}" + # fi + # + + # c/b/f Implementation notes: + # + # The top of the back is $PWD + # as long as the last directory change was due to c,b,or cl. + # + # Example of stack changes: + # + # a b c (d) + ## back + # a b (c) + # d + #back + #a (b) + #d c + #back + #(a) + #d c b + #forward + #a (b) + #d c + # + # a b c + ## back + # a b + # (c) + ## forward + +} +# forward +f() { + local top_forward + if (( ${#_dir_forward[@]} == 0 )); then + echo "no forward dir left" >&2 + return 0 + fi + top_forward="${_dir_forward[-1]}" + unset "_dir_forward[-1]" + c "$top_forward" + + # give us a peek at what is next in the list + # if (( ${#_dir_forward[@]} )); then + # printf "%s\n" "${_dir_forward[-1]}" + # fi +} +# cd list +cl() { + local i line input start + local -A buttondirs alines + local -a buttons dirs lines + buttons=( {a..z} {2..9} ) + if [[ ! -s ~/.cdirs ]]; then + echo nothing in ~/.cdirs + return 0 + fi + + i=0 + + mapfile -t lines <~/.cdirs + start=$(( ${#lines[@]} - 1 )) + + # we have ~33 buttons as of this writing, so lets + # prune down the history every once in a while. + if (( start > 500 )); then + tac ~/.cdirs | awk '!seen[$0]++' | head -n 200 | tac | sponge ~/.cdirs || [[ $? == 141 ]] + fi + + for (( j=$start; j >= 0; j-- )); do + line="${lines[$j]}" + if [[ ! $line || ${alines[$line]} || ! -d "$line" || $line == "$PWD" || line == "$HOME" ]]; then + continue + fi + alines[$line]=t + buttondirs[${buttons[i]}]="$line" + printf "%s %s\n" ${buttons[i]} "$line" + if (( i == ${#buttons[@]} - 1 )); then + break + fi + i=$(( i + 1 )) + done + + if (( i == 0 )); then + echo "no dirs in ~/.cdirs" + return 0 + fi + read -r -N 1 input + if [[ $input != $'\n' ]]; then + c "${buttondirs[$input]}" + fi +} +# back list +bl() { + local start i j max + max=10 + start=$(( ${#_dir_back[@]} - 1 )) + + # cleanup possible repeating of pwd + if (( start >= 0 )) && [[ ${_dir_back[$start]} == "$PWD" ]]; then + start=$(( start - 1 )) + fi + j=1 + if (( start >= 0 )); then + for (( i=$start; i >= 0 ; i-- )); do + printf "%s %s\n" $j ${_dir_back[i]} + j=$(( j + 1 )) + if (( j >= max )); then + break + fi + done + fi + + max=10 + start=$(( ${#_dir_forward[@]} - 1 )) + + # cleanup possible repeating of pwd + if (( start >= 0 )) && [[ ${_dir_forward[$start]} == "$PWD" ]]; then + start=$(( start - 1 )) + fi + if (( start < 0 )); then + return 0 + fi + echo -- + j=1 + for (( i=$start; i >= 0 ; i-- )); do + printf "%s %s\n" $j ${_dir_forward[i]} + j=$(( j + 1 )) + if (( j >= max )); then + break + fi + done +} + +# pee do. run args as a command with output copied to syslog. +# +# Usage: pd [-t TAG] COMMAND... +# +# -t TAG Override the tag in the syslog. The default is COMMAND with +# any path part is removed, eg. for /bin/cat the tag is cat. +# +# You can view the log via "journalctl -t TAG" +pd() { + local tag ret + ret=0 + tag=${1##*/} + case $1 in + -t) tag="$2"; shift 2 ;; + esac + echo "PWD=$PWD command: $*" | logger -t $tag + "$@" |& pee cat "logger -t $tag" || ret=$? + echo "exited with status=$ret" | pee cat "logger -t $tag" + # this avoids any err-catch + (( $ret == 0 )) || return $ret +} +ccomp time pd + +# jdo = journal do. Run command as transient systemd service, tailing +# its output in the journal until it completes. +# +# Usage: jdo COMMAND... +# +# Compared to pd: commands recognize this is a non-interactive shell. +# The service is unaffected if our ssh connection dies, no need to run +# in screen or tmux. +# +# Note: The last few lines of any existing entries for a unit by that +# name will be output first, and there will be a few second delay at the +# start of the command, and a second or so at the end. +# +# Note: Functions and aliases obviously won't work, we resolve the +# command to a file. +# +# Note: requires running as root. +jdo() { + local cmd cmd_name jr_pid ret + ret=0 + cmd="$1" + shift + if [[ $EUID != 0 ]]; then + echo "jdo: error: rerun as root" + return 1 + fi + cmd_name=${cmd##*/} + if [[ $cmd != /* ]]; then + cmd=$(type -P "$cmd") + fi + # -q = quiet + journalctl -qn2 -f -u "$cmd_name" & + # Trial and error of time needed to avoid missing initial lines. + # .5 was not reliable. 1 was not reliable. 2 was not reliable + sleep 4 + jr_pid=$! + systemd-run --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$? + # The sleep lets the journal output its last line + # before the prompt comes up. + sleep .5 + kill $jr_pid &>/dev/null ||: + unset jr_pid + fg &>/dev/null ||: + # this avoids any err-catch + (( $ret == 0 )) || return $ret +} +ccomp time jdo +#### end fsf section + ..() { c ..; } ...() { c ../..; } @@ -322,8 +627,9 @@ fpst() { # file paste } _khfix_common() { - local host ip port + local host ip port file key read -r host ip port < <(timeout -s 9 2 ssh -oBatchMode=yes -oControlMaster=no -oControlPath=/ -v $1 |& sed -rn "s/debug1: Connecting to ([^ ]+) \[([^\]*)] port ([0-9]+).*/\1 \2 \3/p" ||: ) + file=$(readlink -f ~/.ssh/known_hosts) if [[ ! $ip ]]; then echo "khfix: ssh failed" return 1 @@ -335,11 +641,17 @@ _khfix_common() { ip_entry=$ip host_entry=$host fi + tmpfile=$(mktemp) if [[ $host != $ip ]]; then - m ssh-keygen -R "$host_entry" -f $(readlink -f ~/.ssh/known_hosts) - ll ~/.ssh/known_hosts + key=$(ssh-keygen -F "$host_entry" -f $file | sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/') + if [[ $key ]]; then + grep -Fv "$key" "$file" | sponge "$file" + fi + fi + key=$(ssh-keygen -F "$ip_entry" -f $file | sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/') + if [[ $key ]]; then + grep -Fv "$key" "$file" | sponge "$file" fi - m ssh-keygen -R "$ip_entry" -f $(readlink -f ~/.ssh/known_hosts) ll ~/.ssh/known_hosts rootsshsync } @@ -352,6 +664,14 @@ khcopy() { ssh-copy-id $1 } +# ya, hacky hardcoded hostnames in 2023. we could do better +hssh-update() { + for host in kd x3.office.fsf.org syw; do + e $host + scp /b/fai/fai/config/files/usr/local/bin/hssh/IANK root@$host:/usr/local/bin/hssh + done +} + a() { local x x=$(readlink -nf "${1:-$PWD}") @@ -370,24 +690,79 @@ for num in {1..9}; do done -b() { - # backwards - c - +hexipv4() { + printf '%d.%d.%d.%d\n' $(echo $1 | sed 's/../0x& /g') +} + +vp9() { + local f out outdir in fname origdir skip1 + origdir="$PWD" + outdir=vp9 + skip1=false + while [[ $1 == -* ]]; do + case $1 in + # if we got interrupted after 1st phase + -2) + skip1=true + shift + ;; + --out) + outdir=$2 + shift 2 + ;; + esac + done + m mkdir -p $outdir + # first pass only uses about 1 cpu, so run in parallel + for f; do + { + fname="${f##*/f}" + if [[ $f == /* ]]; then + in="$f" + else + in=$origdir/$f + fi + out="$origdir/$outdir/$fname" + mkdir -p /tmp/vp9/$fname + cd /tmp/vp9/$fname + if ! $skip1 && [[ ! -s ffmpeg2pass-0.log ]]; then + # -nostdin or else wait causes ffmpeg to go into stopped state. dunno why, random stackoverflow answer. + m ffmpeg -nostdin -hide_banner -loglevel error -i $in -g 192 -vcodec libvpx-vp9 -vf scale=-1:720 -max_muxing_queue_size 9999 -b:v 750K -pass 1 -an -f null /dev/null + fi + if [[ -e $out ]]; then rm -f $out; fi + m ffmpeg -nostdin -hide_banner -loglevel error -y -i $in -g 192 -vcodec libvpx-vp9 -tile-rows 2 -vf scale=-1:720 -max_muxing_queue_size 9999 -b:v 750K -pass 2 -c:a libvorbis -qscale:a 5 $out + } & + done + wait -f + cd "$origdir" +} + +utcl() { # utc 24 hour time to local hour 24 hour time + echo "print( ($1 $(date +%z | sed -r 's/..$//;s/^(-?)0*/\1/')) % 24)"|python3 +} + +bwm() { + s bwm-ng -T avg -d +} + + +# for running in a fai rescue. iank specific. +kdrescue() { + d=vgata-Samsung_SSD_850_EVO_2TB_S2RLNX0J502123D + for f in $d vgata-Samsung_SSD_870_QVO_8TB_S5VUNG0N900656V; do + cryptsetup luksOpen --key-file /p /dev/$f/root crypt-$f-root + cryptsetup luksOpen --key-file /p /dev/$f/o crypt-$f-o + done + mount -o subvol=root_trisquelaramo /dev/mapper/crypt-$d-root /mnt + mount -o subvol=a /dev/mapper/crypt-$d-root /mnt/a + mount -o subvol=o /dev/mapper/crypt-$d-o /mnt/o + mount -o subvol=boot_trisquelaramo /dev/sda2 /mnt/boot + cd /mnt + chrbind } -# c. better cd -if type -p wcd &>/dev/null; then - if [[ $LC_INSIDE_EMACS ]]; then - c() { wcd -c -z 50 -o "$@"; } - else - # lets see what the fancy terminal does from time to time - c() { wcd -c -z 50 "$@"; } - fi -else - c() { cd "$@"; } -fi -ccomp cd c + c4() { c /var/log/exim4; } @@ -434,7 +809,17 @@ chrbind() { for d in dev proc sys dev/pts; do [[ -d $d ]] if ! mountpoint $d &>/dev/null; then - s mount -o bind /$d $d + m s mount -o bind /$d $d + fi + done +} +chumount() { + local d + # dev/pts needed for pacman signature check + for d in dev/pts dev proc sys; do + [[ -d $d ]] + if mountpoint $d &>/dev/null; then + m s umount $d fi done } @@ -483,8 +868,8 @@ cdiff() { cat-new-files() { local start=$SECONDS local dir="$1" - inotifywait -m "$dir" -e create -e moved_to | - # shellcheck disable=SC2030 + # shellcheck disable=SC2030 + inotifywait -m "$dir" -e create -e moved_to | \ while read -r filedir _ file; do cat "$filedir$file" hr @@ -494,6 +879,10 @@ cat-new-files() { } +chownme() { + s chown -R $USER:$USER "$@" +} + # shellcheck disable=SC2032 chown() { # makes it so chown -R symlink affects the symlink and its target. @@ -510,14 +899,16 @@ cim() { git commit -m "$*" } -cl() { - # choose recent directory. cl = cd list - c = -} d() { builtin bg "$@"; } ccomp bg d +# f would be more natural, but i already am using it for something +z() { builtin fg "$@"; } +ccomp fg z + +x() { builtin kill %%; } + dc() { diff --strip-trailing-cr -w "$@" # diff content } @@ -531,6 +922,39 @@ despace() { done } +# df progress +# usage: dfp MOUNTPOINT [SECOND_INTERVAL] +# SECOND_INTERVAL defaults to 90 +dfp() { + # mp = mountpoint + local a b mp interval + mp=$1 + interval=${2:-90} + if [[ ! $mp ]]; then + echo "dfp: error, missing 1st arg" >&2 + return 1 + fi + while true; do + a=$(df --output=used $mp | tail -n1) + sleep $interval + b=$(df --output=used $mp | tail -n1) + printf "used mib: %'d mib/min: %s\n" $(( b /1000 )) $(( (b-a) / (interval * 1000 / 60 ) )) + done +} + +# get ipv4 ip from HOST. or if it is already a number, return that +hostip() { + local host="$1" + case $host in + [0-9:]) + echo "$host" + ;; + *) + getent ahostsv4 "$host" | awk '{ print $1 }' | head -n1 + ;; + esac +} + dig() { command dig +nostats +nocmd "$@" } @@ -565,10 +989,18 @@ digdiff() { diff -u /tmp/digdiff <(digsort $s2 "$@") } +# date in a format i like reading dt() { date "+%A, %B %d, %r" "$@" } -ccomp date dt +dtr() { + date -R "$@" +} +# date with all digits in a format i like +dtd() { + date +%F_%T% "$@" +} +ccomp date dt dtr dtd dus() { # du, sorted, default arg of du -sh ${@:-*} | sort -h @@ -576,7 +1008,7 @@ dus() { # du, sorted, default arg of ccomp du dus -e() { echo "$@"; } +e() { printf "%s\n" "$*"; } # echo args ea() { @@ -588,14 +1020,19 @@ ea() { printf "%s" "${arg}" |& hexdump -C done } -# echo vars. print var including escapes, etc + +# echo variables. print var including escapes, etc, like xxd for variable ev() { if (( ! $# )); then echo no args fi for arg; do - printf "%qEOL\n" "${!arg}" - printf "%s" "${!arg}" |& hexdump -C + if [[ -v $arg ]]; then + printf "%qEOL\n" "${!arg}" + printf "%s" "${!arg}" |& hexdump -C + else + echo arg $arg is unset + fi done } @@ -606,9 +1043,23 @@ ediff() { # mail related etail() { + ngset + tail -F /var/log/exim4/mainlog /var/log/exim4/*main /var/log/exim4/paniclog /var/log/exim4/*panic -n 200 "$@" + ngreset +} +etailm() { tail -F /var/log/exim4/mainlog -n 200 "$@" } -ccomp tail etail +etail2() { + tail -F /var/log/exim4/mymain -n 200 "$@" +} +ccomp tail etail etail2 + + +showkeys() { + ssh "$@" cat .ssh/authorized_keys{,2} +} + # print exim old pids eoldpids() { @@ -668,14 +1119,18 @@ eless() { } ccomp less eless eqcat() { - exiqgrep -i -o 60 | while read -r i; do + exiqgrep -ir.\* -o 60 | while read -r i; do hlm exim -Mvc $i echo hlm exigrep $i /var/log/exim4/mainlog | cat ||: done } eqrmf() { - exiqgrep -i | xargs exim -Mrm + # other ways to get the list of message ids: + # exim -bp | awk 'NF == 4 {print $3}' + # # this is slower 160ms, vs 60. + # exipick -i + exiqgrep -ir.\* | xargs exim -Mrm } econfdevnew() { @@ -690,14 +1145,23 @@ econfdev() { update-exim4.conf -d /tmp/edev/etc/exim4 -o /tmp/edev/e.conf } +# exim grep in +# show important information about incoming mail in the exim log +egrin() { + sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p' <${1:-/var/log/exim4/mainlog} +} - -# shellcheck disable=SC2032 -f() { - # cd forward - c + +# 2nd line is message-id: +egrinid() { + sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).* id=([^ ]+) T="(.*)" from (<[^ ]+> .*$)/\1 \5\n \3\n \4/p' <${1:-/var/log/exim4/mainlog} +} +etailin() { + tail -F /var/log/exim4/mainlog | sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p' } + + + fa() { # find array. make an array of file names found by find into $x # argument: find arguments @@ -713,19 +1177,78 @@ faf() { # find all files. use -L to follow symlinks -o -name .undo-tree-history -prune \) -type f 2>/dev/null } -# todo: id like to do maybe a daily or hourly cronjob to -# check that my history file size is increasing. Ive had it -# inexplicably truncated in the past. -histrm() { - history -n - history | awk -v IGNORECASE=1 '{ a=$1; sub(/^( *[^ ]+){4} */, "") }; /'"$*"'/' - read -p "press anything but contrl-c to delete" - for entry in $(history | awk -v IGNORECASE=1 '{ a=$1; sub(/^( *[^ ]+){4} */, "") }; /'"$*"'/ { print a }' | tac); do - history -d $entry +# usage ffconcat FILES_TO_CONCAT OUTPUT_FILE +ffconcat() { + local tmpf + tmpf=$(mktemp) + printf "file '%s'\n" "$1" >$tmpf + while (( $# > 1 )); do + shift + printf "file '%s'\n" "$1" >>$tmpf done - history -w + # https://trac.ffmpeg.org/wiki/Concatenate + ffmpeg -f concat -safe 0 -i $tmpf -c copy "$1" + rm $tmpf +} +ffremux() { + local tmpf tmpd + if (( $# == 0 )); then + echo ffremux error expected args >&2 + return 1 + fi + tmpd=$(mktemp -d) + for f; do + tmpf=$tmpd/"${f##*/}" + ffmpeg -i "$f" -c:v copy -c:a copy $tmpf + cat $tmpf >"$f" + done + rm -r $tmpd +} + + + +# absolute path of file/dir without resolving symlinks. +# +# Most of the time, I want this where I would normally use readlink. +# This is what realpath -s does in most cases, but sometimes it +# actually resolves symlinks, at least when they are in /. +# +# Note, if run on a dir, if the final component is relative, it won't +# resolve that. Use the below fpd for that. +# +# note: we could make a variation of this which +# assigns to a variable name using eval, so that we don't have to do +# x=$(fp somepath), which might save subshell overhead and look nice, +# but I'm not going to bother. +fp() { + local initial_oldpwd initial_pwd dir base + initial_oldpwd="$OLDPWD" + initial_pwd="$PWD" + if [[ $1 == */* ]]; then + dir="${1%/*}" + base="/${1##*/}" + # CDPATH because having it set will cause cd to possibly print output + CDPATH= cd "$dir" + printf "%s%s\n" "$PWD" "$base" + CDPATH= cd "$initial_pwd" + OLDPWD="$initial_oldpwd" + else + printf "%s/%s\n" "$PWD" "$1" + fi +} +# full path of directory without resolving symlinks +fpd() { + local initial_oldpwd initial_pwd dir + initial_oldpwd="$OLDPWD" + initial_pwd="$PWD" + dir="$1" + CDPATH= cd "$dir" + printf "%s%s\n" "$PWD" "$base" + cd "$initial_pwd" + OLDPWD="$initial_oldpwd" } + # mail related frozen() { rm -rf /tmp/frozen @@ -889,6 +1412,16 @@ ccomp grep gr grr rg() { grr "$@"; } ccomp grep rg +# recursive everything. search for files/dirs and lines. rs = easy chars to press +re() { + local query + query="$1" + find "$@" -not \( -name .svn -prune -o -name .git -prune \ + -o -name .hg -prune -o -name .editor-backups -prune \ + -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto "$query" + grr -m 5 "$@" +} + hr() { # horizontal row. used to break up output printf "$(tput setaf 5 2>/dev/null ||:)█$(tput sgr0 2>/dev/null||:)%.0s" $(eval echo "{1..${COLUMNS:-60}}") echo @@ -922,9 +1455,13 @@ hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; d # On first use, you input username/pass and it gets an oath token so you dont have to repeat # it\'s at ~/.config/hub hub() { - local up uptar updir p - p=/github/hub/releases/ - up=https://github.com/$(curl -s https://github.com$p| grep -o $p'download/[^/]*/hub-linux-amd64[^"]*' | head -n1) + local up uptar updir p v + # example https://github.com/github/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz + up=$(wget -q -O- https://api.github.com/repos/github/hub/releases/latest | jq -r .assets[].browser_download_url | grep linux-amd64) + re='[[:space:]]' + if [[ ! $up || $up == $re ]]; then + echo "failed to get good update url. got: $up" + fi uptar=${up##*/} updir=${uptar%.tgz} if [[ ! -e /a/opt/$updir ]]; then @@ -952,14 +1489,43 @@ hub() { i() { git "$@"; } ccomp git i +# git status: +# cvs -qn update + +# git checkout FILE +# cvs update -C FILE + +# git pull +# cvs up[date] + +# potentially useful command translation +# https://fling.seas.upenn.edu/~giesen/dynamic/wordpress/equivalent-commands-for-git-svn-and-cvs/ + +# importing cvs repo into git using git-cvs package: +# /f/www $ /usr/lib/git-core/git-cvsimport -C /f/www-git + ic() { # fast commit all git commit -am "$*" } +ipp() { + git pull + git push +} ifn() { - # insensitive find + local glob + glob="$1" + shift + find -L "$@" -not \( -name .svn -prune -o -name .git -prune \ + -o -name .hg -prune -o -name .editor-backups -prune \ + -o -name .undo-tree-history -prune \) -iname "*$glob*" 2>/dev/null +} + +ifh() { + # insensitive find here. args are combined into the search string. + # -L = follow symlinks find -L . -not \( -name .svn -prune -o -name .git -prune \ -o -name .hg -prune -o -name .editor-backups -prune \ -o -name .undo-tree-history -prune \) -iname "*$**" 2>/dev/null @@ -982,6 +1548,10 @@ istext() { grep -Il "" "$@" &>/dev/null } +pst() { + pstree -apnA +} + jtail() { journalctl -n 10000 -f "$@" } @@ -991,6 +1561,8 @@ jru() { journalctl -u exim4 _SYSTEMD_INVOCATION_ID=$(systemctl show -p InvocationID --value $1) } + + l() { if [[ $PWD == /[iap] ]]; then command ls -A --color=auto -I lost+found "$@" @@ -1050,7 +1622,7 @@ low() { # make filenames lowercase, remove bad chars fi f="${arg##*/}" new="${f,,}" # downcase - new="${new//[^[:alnum:]._-]/_}" # sub bad chars + new="${new//[^a-zA-Z0-9._-]/_}" # sub bad chars new="${new#"${new%%[[:alnum:]]*}"}" # remove leading/trailing non-alnum new="${new%"${new##*[[:alnum:]]}"}" # remove bad underscores, like __ and _._ @@ -1074,10 +1646,28 @@ lower() { # make first letter of filenames lowercase. k() { # history search grep -iP --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | tail -n 80 || [[ $? == 1 ]]; } -ks() { # history search +ks() { # history search with context + # args are an extended regex used by sed + history | sed -nr "h;s/^\s*(\S+\s+){4}//;/$*/{g;p}" | tail -n 80 || [[ $? == 1 ]]; +} +ksu() { # history search unique grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history} | uniq || [[ $? == 1 ]]; } -ccomp grep k ks + +# todo: id like to do maybe a daily or hourly cronjob to +# check that my history file size is increasing. Ive had it +# inexplicably truncated in the past. +histrm() { + history -n + HISTTIMEFORMAT= history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/' + read -p "press anything but contrl-c to delete" + for entry in $(HISTTIMEFORMAT= history | awk -v IGNORECASE=1 '{ a=$1; sub(/^ *[^ ]+ */, "") }; /'"$*"'/ { print a }' | tac); do + history -d $entry + done + history -w +} + +ccomp grep k ks ksu histrm make-targets() { @@ -1094,6 +1684,15 @@ ccomp mkdir mkc mkct() { mkc $(mktemp -d) } +# mkdir the last arg, cp the rest into it +mkcp() { + mkdir -p "${@: -1}" + cp "${@:1:$#-1}" "${@: -1}" +} +mkmv() { + mkdir -p "${@: -1}" + mv "${@:1:$#-1}" "${@: -1}" +} mkt() { # mkdir and touch file local path="$1" @@ -1113,14 +1712,48 @@ nags() { } nmt() { - s nmtui-connect "$@" + # cant use s because sudo -i doesnt work for passwordless sudo command + case $EUID in + 0) + sudo nmtui-connect "$@" + ;; + *) + nmtui-connect "$@" + ;; + esac +} + + +ngset() { + if shopt nullglob >/dev/null; then + ngreset=false + else + shopt -s nullglob + ngreset=true + fi +} +ngreset() { + if $ngreset; then + shopt -u nullglob + fi } nopanic() { # shellcheck disable=SC2024 - sudo tee -a /var/log/exim4/paniclog-archive /dev/null || [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then # this will fail is dnsmasq is failed hr; m ser status dnsmasq | cat || : @@ -1274,7 +1913,7 @@ resolvcat() { grep '^ *hosts:' /etc/nsswitch.conf if systemctl is-enabled systemd-resolved &>/dev/null || [[ $(systemctl is-active systemd-resolved ||:) != inactive ]]; then hr; m ser status systemd-resolved | cat || : - hr; m systemd-resolve --status | cat + hr; m resolvectl status | cat fi } @@ -1303,6 +1942,15 @@ rmstrips() { ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less } +urun () { + umask $1 + shift + "$@" +} +sudo () { + command sudo "$@" || return $? + DID_SUDO=true +} s() { # background # I use a function because otherwise we cant use in a script, @@ -1327,7 +1975,9 @@ sb() { # sudo bash -c local SUDOD="$PWD" sudo -i bash -c "$@" } -ccomp sudo s sb +# secret sudo +se() { s urun 0077 "$@"; } +ccomp sudo s sb se safe_rename() { # warn and dont rename if file exists. # mv -n exists, but it\'s silent @@ -1345,7 +1995,6 @@ safe_rename() { # warn and dont rename if file exists. } - sd() { sudo dd status=none of="$1" } @@ -1361,6 +2010,10 @@ ser() { s service $2 $1 fi } +serstat() { + systemctl -n 40 status "$@" +} + seru() { systemctl --user "$@"; } # like restart, but do nothing if its not already started srestart() { @@ -1415,16 +2068,17 @@ sgu() { sk() { - # 2029: "unescaped, this expands on the client side." yes, I know how ssh works - # 2164: "Use 'cd ... || exit' or 'cd ... || return' in case cd fails." i have automatic error handling - # 2086: unquoted $var - # 2046: unquoted $(cmd) - # 2068: Double quote array expansions to avoid re-splitting elements. - # 2119: Functions with optional args get bad warnings when none are passed. - # 2033: too many false positives for thing that will never work, passing shell function to find. - # i had -x as an arg, but debian testing(stretch) doesn\'t support it - shellcheck -x -e 2029,2164,2086,2046,2068,2119,2033 "$@" || return $? - # had this before. not sure what it is 2119 + + + # disable a warning with: + # shellcheck disable=SC2206 # reasoning + + # see bash-template/style-guide.md for justifications + + local quotes others + quotes=2048,2068,2086,2206 + others=2029,2033,2054,2164 + shellcheck -W 999 -x -e $quotes,$others "$@" || return $? } @@ -1570,7 +2224,6 @@ sl() { return 1 fi - now=$(date +%s) dorsync=false haveinfo=false tmpa=($SL_INFO_DIR/???????????"$remote") @@ -1628,7 +2281,7 @@ sl() { RSYNC_RSH="ssh ${args[*]}" m rsync -rptL --delete $sl_rsync_args $SL_FILES_DIR "$remote": fi if $dorsync || ! $haveinfo; then - sshinfo=$SL_INFO_DIR/$now$type"$remote" + sshinfo=$SL_INFO_DIR/$EPOCHSECONDS$type"$remote" [[ -e $SL_INFO_DIR ]] || mkdir -p $SL_INFO_DIR printf "%s\n" "$extra_info" >$sshinfo chmod 666 $sshinfo @@ -1793,6 +2446,13 @@ pson() { fi } +# prometheus node curl +pnodecurl() { + local host + host=${1:-127.0.0.1} + s curl --cert-type PEM --cert /etc/prometheus/ssl/prometheus_cert.pem --key /etc/prometheus/ssl/prometheus_key.pem --cacert /etc/prometheus/ssl/prom_node_cert.pem --resolve prom_node:9100:$host -v https://prom_node:9100/metrics +} + tx() { # toggle set -x, and the prompt so it doesnt spam if [[ $- == *x* ]]; then set +x @@ -1814,9 +2474,39 @@ psnetns() { if [[ $x ]]; then echo "$x"; else echo $l; fi; done } +nonet() { + if ! s ip netns list | grep -Fx nonet &>/dev/null; then + s ip netns add nonet + fi + sudo -E env /sbin/ip netns exec nonet sudo -E -u iank /bin/bash +} m() { printf "%s\n" "$*"; "$@"; } +# update file. note: duplicated in mail-setup +u() { + local tmp tmpdir dest="$1" + local base="${dest##*/}" + local dir="${dest%/*}" + if [[ $dir != "$base" ]]; then + # dest has a directory component + mkdir -p "$dir" + fi + ur=false # u result + tmpdir=$(mktemp -d) + cat >$tmpdir/"$base" + tmp=$(rsync -ic $tmpdir/"$base" "$dest") + if [[ $tmp ]]; then + printf "%s\n" "$tmp" + ur=true + if [[ $dest == /etc/systemd/system/* ]]; then + reload=true + fi + fi + rm -rf $tmpdir +} + + uptime() { if type -p uprecords &>/dev/null; then uprecords -B @@ -1861,28 +2551,90 @@ s/^\Wcapability: (.*)/\1/;Ta;h;b "|sort -r } -# * misc stuff +# Run script by copying it to a temporary location first, +# and changing directory, so we don't have any open +# directories or files that could cause problems when +# remounting. +zr() { + local tmp + tmp=$(type -p "$1") + if [[ $tmp ]]; then + cd $(mktemp -d) + cp -a "$tmp" . + shift + ./"${tmp##*/}" "$@" + else + "$@" + fi +} -# temporary variables to test colorization -# some copied from gentoo /etc/bash/bashrc, -use_color=false -# dircolors --print-database uses its own built-in database -# instead of using /etc/DIR_COLORS. Try to use the external file -# first to take advantage of user additions. -safe_term=${TERM//[^[:alnum:]]/?} # sanitize TERM -match_lhs="" -[[ -f ~/.dir_colors ]] && match_lhs="${match_lhs}$(<~/.dir_colors)" -[[ -f /etc/DIR_COLORS ]] && match_lhs="${match_lhs}$(/dev/null \ - && match_lhs=$(dircolors --print-database) -# test if our $TERM is in the TERM values in dircolor -[[ $'\n'${match_lhs} == *$'\n'"TERM "${safe_term}* ]] && use_color=true - - -if ${use_color} && [[ $- == *i* ]]; then +# * spark +# spark 1 5 22 13 53 +# # => ▁▁▃▂▇ + +# The MIT License +# Copyright (c) Zach Holman, https://zachholman.com +# https://github.com/holman/spark + +# As of 2022-10-28, I reviewed github forks that had several newer +# commits, none had anything interesting. I did a little refactoring +# mostly to fix emacs indent bug. +# Generates sparklines. +_spark_echo() +{ + if [ "X$1" = "X-n" ]; then + shift + printf "%s" "$*" + else + printf "%s\n" "$*" + fi +} + + +spark() +{ + local f tc + local n numbers= + + # find min/max values + local min=0xffffffff max=0 + + for n in ${@//,/ } + do + # on Linux (or with bash4) we could use `printf %.0f $n` here to + # round the number but that doesn't work on OS X (bash3) nor does + # `awk '{printf "%.0f",$1}' <<< $n` work, so just cut it off + n=${n%.*} + (( n < min )) && min=$n + (( n > max )) && max=$n + numbers=$numbers${numbers:+ }$n + done + + # print ticks + local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █) + + # use a high tick if data is constant + (( min == max )) && ticks=(▅ ▆) + + tc=${#ticks[@]} + f=$(( ( (max-min) <<8)/( tc - 1) )) + (( f < 1 )) && f=1 + + for n in $numbers + do + _spark_echo -n ${ticks[$(( ((($n-$min)<<8)/$f) ))]} + done + _spark_echo +} + +pdfwc() { local f; for f; do echo "$f" $(pdfinfo "$f" | awk '/^Pages:/ {print $2}'); done } + +# * misc stuff + + +if $use_color && type -p tput &>/dev/null; then term_bold="$(tput bold)" term_red="$(tput setaf 1)" term_green="$(tput setaf 2)" @@ -1894,7 +2646,6 @@ if ${use_color} && [[ $- == *i* ]]; then # term_underl="$(tput smul)" # term_blue="$(tput setaf 4)" # term_cyan="$(tput setaf 6)" - fi # Try to keep environment pollution down, EPA loves us. unset safe_term match_lhs use_color @@ -1904,6 +2655,16 @@ unset safe_term match_lhs use_color if [[ $- == *i* ]]; then + + case $HOSTNAME in + bk|je|li) + if [[ $EUID == 1000 ]]; then + system-status _ ||: + fi + ;; + esac + + # this needs to come before next ps1 stuff # this stuff needs bash 4, feb 2009, # old enough to no longer condition on $BASH_VERSION anymore @@ -1932,23 +2693,11 @@ if [[ $- == *i* ]]; then history -a # save history fi - # assigned in brc2 - # shellcheck disable=SC1303 - if [[ $jr_pid ]]; then - if [[ -e /proc/$jr_pid ]]; then - kill $jr_pid - fi - unset jr_pid - fi - case $return in 0) ps_color="$term_purple" ps_char='\$' ;; - 1) ps_color="$term_green" - ps_char="$return \\$" - ;; - *) ps_color="$term_yellow" + *) ps_color="$term_green" ps_char="$return \\$" ;; esac @@ -1964,19 +2713,23 @@ if [[ $- == *i* ]]; then if [[ -e /dev/shm/iank-status && ! -e /tmp/quiet-status ]]; then eval $(< /dev/shm/iank-status) fi - if [[ ! $SSH_CLIENT && $MAIL_HOST != "$HOSTNAME" ]]; then + if [[ $MAIL_HOST && $MAIL_HOST != "$HOSTNAME" ]]; then ps_char="@ $ps_char" fi + jobs_char= + if [[ $(jobs -p) ]]; then + jobs_char='j\j ' + fi # We could test if sudo is active with sudo -nv # but then we get an email and log of lots of failed sudo commands. # We could turn those off, but seems better not to. if [[ $EUID != 0 ]] && [[ $DID_SUDO ]]; then - ps_char="SUDO $ps_char" + psudo="\[$term_bold$term_red\]s\[$term_nocolor\] " fi if [[ ! $HISTFILE ]]; then ps_char="NOHIST $ps_char" fi - PS1="${PS1%"${PS1#*[wW]}"} \[$ps_color\]$ps_char\[$term_nocolor\] " + PS1="${PS1%"${PS1#*[wW]}"} $jobs_char$psudo\[$ps_color\]$ps_char\[$term_nocolor\] " # set titlebar. instead, using more advanced # titelbar below @@ -1992,18 +2745,22 @@ if [[ $- == *i* ]]; then _title_escape="\033]0;" fi + # make the titlebar be the last command and the current directory. settitle () { - # this makes it so we show the current command if - # one is running, otherwise, show nothing - if [[ $1 == prompt-command ]]; then + + # These are some checks to help ensure we dont set the title at + # times that the debug trap is running other than the case we + # want. Some of them might not be needed. + if (( ${#FUNCNAME[@]} != 1 || ${#BASH_ARGC[@]} != 2 || $BASH_SUBSHELL != 0 )); then return 0 fi - if (( ${#BASH_ARGC[@]} == 1 && BASH_SUBSHELL == 0 )); then - echo -ne "$_title_escape ${PWD/#$HOME/~} " - printf "%s" "$*" - echo -ne "\007" + if [[ $1 == prompt-command ]]; then + return 0 fi + echo -ne "$_title_escape ${PWD/#$HOME/~} " + printf "%s" "$*" + echo -ne "\007" } # note, this wont work: