distro specific fixes
[distro-setup] / brc
diff --git a/brc b/brc
index f0b57f8cc1558918fdd34ff410da566854024207..222e0f85c5c908c5507ad3dfdc1c0791275e9252 100644 (file)
--- a/brc
+++ b/brc
@@ -1,6 +1,25 @@
 #!/bin/bash
-# Copyright (C) 2019 Ian Kelling
-# SPDX-License-Identifier: AGPL-3.0-or-later
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to switch
+# its license to GPL.
+
+# Copyright 2024 Ian Kelling
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 # this gets sourced. shebang is just for file mode detection
 
 # Use source ~/.bashrc instead of doing bash -l when running a script
@@ -225,34 +244,12 @@ if [[ $- == *i* ]]; then
       use_color=true
     fi
 
-    if [[ $KONSOLE_PROFILE_NAME ]]; then
-      TERM=xterm-256color
-    fi
-
-    if [[ $TERM == alacritty && ! -e /usr/share/terminfo/a/alacritty ]]; then
-      # todo: we should try installing the alacritty terminfo if it is not found
-      # https://github.com/alacritty/alacritty/issues/2838
-      TERM=xterm-256color
-    fi
-
-    # copying from the alacritty example above,
-    if [[ $TERM == xterm-kitty ]]; then
-      if [[ ! -e /usr/share/terminfo/x/xterm-kitty ]]; then
-        TERM=xterm-256color
-      else
-        if [[ -e /a/opt/kitty/shell-integration/bash/kitty.bash ]]; then
-          KITTY_SHELL_INTEGRATION=t
-          source /a/opt/kitty/shell-integration/bash/kitty.bash
-        fi
-      fi
-    fi
-
     # todo: not sure this works in sakura
     #stty werase undef
     #bind "\C-w": kill-region
     # sakura == xterm-256color
     # konsole == xterm
-    if [[ $TERM != xterm-kitty && $TERM == xterm* ]]; then
+    if [[ $TERM == xterm* ]]; then
       # control + arrow keys. for other terminals, see http://unix.stackexchange.com/questions/10806/how-to-change-previous-next-word-shortcut-in-bash
       bind '"\e[1;5C": shell-forward-word' 2>/dev/null
       bind '"\e[1;5D": shell-backward-word' 2>/dev/null
@@ -272,15 +269,6 @@ 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
@@ -296,6 +284,7 @@ export PROFILE_TASKS_TASK_OUTPUT_LIMIT=100
 export LESS=RXij12
 export SYSTEMD_LESS=$LESS
 
+
 export NNN_COLORS=2136
 
 export SL_FILES_DIR=/b/ds/sl/.iank
@@ -326,9 +315,6 @@ export SL_INFO_DIR=/p/sshinfo
 if [[ -s $bashrc_dir/path-add-function ]]; then
   source $bashrc_dir/path-add-function
   if [[ $SSH_CLIENT ]]; 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
   fi
 fi
@@ -368,6 +354,7 @@ mysrc() {
 
 mysrc /a/bin/small-misc-bash/ll-function
 mysrc /a/bin/distro-functions/src/package-manager-abstractions
+mysrc /a/bin/fai/fai/config/distro-install-common/bash-misc-funcs
 
 # things to remember:
 # ALT-C - cd into the selected directory
@@ -696,10 +683,10 @@ jdo() {
   fi
   # -q = quiet
   journalctl -qn2 -f -u "$cmd_name" &
+  jr_pid=$!
   # 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.
@@ -781,6 +768,50 @@ EOF
   done
 }
 
+screenrtp() {
+
+  local ip port xoffset
+  read -r ip port xoffset <<<"$@"
+
+  setxenv
+
+  if [[ ! $port ]]; then
+    port=9999
+  fi
+
+  while true; do
+    # By default, plugged in screen goes to the right side, so we need an
+    # offset that is the same as the laptop's x resolution. If we are in
+    # mirror mode, then we don't need an offset.
+    if [[ ! $xoffset ]]; then
+      xoffset=0
+      laptop_x=$(xrandr | awk '$1 == "LVDS-1" {print $4}' | sed 's/x.*//') || { sleep 1; continue; }
+      total_x=$(xdpyinfo| awk '$1 == "dimensions:" {print $2}' | sed 's/x.*//') || { sleep 1; continue; }
+      screen2_res=$(xrandr | awk '$2 == "connected" && $1 != "LVDS-1" { print $3 }' | sed 's/+.*//')
+      if (( laptop_x < total_x )); then
+        xoffset=$laptop_x
+      fi
+    fi
+
+    m ffmpeg -probesize 50M -thread_queue_size 50 \
+      -video_size $screen2_res -f x11grab -framerate 30 -i :0.0+$xoffset.0 \
+      -vcodec libx264 -g 1 -tune zerolatency -preset ultrafast -pix_fmt yuv420p -x264-params repeat-headers=1 \
+      -f rtp_mpegts rtp://$ip:$port ||:
+
+
+    sleep 1
+  done
+}
+
+setxenv() {
+  if [[ ! $DISPLAY ]]; then
+    export DISPLAY=:0.0
+  fi
+  if [[ ! $XAUTHORITY ]]; then
+    export XAUTHORITY=$HOME/.Xauthority
+  fi
+}
+
 #### end fsf section
 
 
@@ -822,25 +853,40 @@ fpst() { # file paste
 }
 
 _khfix-common() {
-  local host ip port file key tmp
-  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" ||: )
+  local host ip port file key tmp ssh_host alias
+  ssh_host=$1
+  {
+    read -r host ip port
+    read -r alias;
+    # note ":graph:" is needed or else we get a trailing \r out of ssh,
+    # dunno why.  web search says terminals add \r, so I tried adding -T
+    # to turn off psuedo terminal, but it didnt help.
+  } < <(timeout -s 9 2 ssh -TN -oBatchMode=yes -oControlMaster=no -oControlPath=/ -v $ssh_host |&
+        sed -rn "s/debug1: Connecting to ([^ ]+) \[([^\]*)] port ([0-9]+).*/\1 \2 \3/p;
+s/^debug1: using hostkeyalias: ([[:graph:]]*).*/\1/p" ||: )
   file=$(readlink -f ~/.ssh/known_hosts)
   if [[ ! $ip ]]; then
     echo "khfix: ssh failed"
     return 1
   fi
+  ip_entry=$ip
+  host_entry=$host
+  if [[ $alias ]]; then
+    host_entry="$alias"
+  fi
   if [[ $port != 22 ]]; then
     ip_entry="[$ip]:$port"
-    host_entry="[$host]:$port"
-  else
-    ip_entry=$ip
-    host_entry=$host
+    if [[ ! $alias ]]; then
+      host_entry="[$host]:$port"
+    fi
   fi
-  if [[ $host != "$ip" ]]; then
+  if [[ $host_entry != "$ip_entry" ]]; then
     tmp=$(mktemp)
     ssh-keygen -F "$host_entry" -f $file >$tmp || [[ $? == 1 ]] # 1 when it doesnt exist in the file
     if [[ -s $tmp ]]; then
       key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp)
+    else
+      echo "khfix WARNING: did not find host entry:$host_entry in known_hosts"
     fi
     rm $tmp
     if [[ $key ]]; then
@@ -852,21 +898,22 @@ _khfix-common() {
   ssh-keygen -F "$ip_entry" -f $file >$tmp || [[ $? == 1 ]]
   if [[ -s $tmp ]]; then
     key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp)
+  else
+    echo "khfix WARNING: did not find ip entry:$ip_entry in known_hosts"
   fi
   rm $tmp
   if [[ $key ]]; then
     grep -Fv "$key" "$file" | sponge "$file"
   fi
-  ll ~/.ssh/known_hosts
 }
-khfix-r() { # known hosts fix + root
+khfix-r() { # known hosts fix without syncing to root user
   _khfix-common "$@" || return 1
   ssh $1 :
-  rootsshsync
 }
 khfix() {
   _khfix-common "$@" || return 1
   ssh $1 :
+  rootsshsync
 }
 
 # copy path into clipboard
@@ -994,10 +1041,13 @@ caf() {
        -o -name .hg -prune -o -name .editor-backups -prune \
        -o -name .undo-tree-history -prune \) -printf '%h\0%d\0%p\n' | sort -t '\0' -n \
     | awk -F '\0' '{print $3}' 2>/dev/null | while read -r file; do
-    hr
-    printf "%s\n" "$file"
-    hr
-    cat "$file"
+    hr "$file"
+    v "$file"
+    # if the file is nonempty and the last char is nonempty, it is not
+    # newline terminated.
+    if [[ -s "$file" && "$(tail -c 1 "$file")" ]]; then
+      echo
+    fi
   done
 }
 ccomp cat cf caf
@@ -1272,9 +1322,13 @@ etailm() {
   tail -F /var/log/exim4/mainlog -n 200 "$@"
 }
 etail2() {
-  tail -F /var/log/exim4/mymain -n 200 "$@"
+  tail -F /var/log/exim4/nondmain -n 200 "$@"
 }
-ccomp tail etail etail2
+# shortcut for tail -F
+ta() {
+  tail -F "$@"
+}
+ccomp tail etail etail2 ta
 
 # ran into this online, trying it out
 detach() {
@@ -1382,7 +1436,12 @@ 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'
+  local -a tail_arg
+  tail_arg=(-n500)
+  if [[ $1 ]]; then
+    tail_arg=($@)
+  fi
+  tail "${tail_arg[@]}" -F /var/log/exim4/mainlog | sed -rn '/testignore|jtuttle|eximbackup/!s/^[^ ]+ ([^ ]+) [^ ]+ [^ ]+ <= ([^ ]+).*T="(.*)" from (<[^ ]+> .*$)/\1 \4\n \3/p'
 }
 
 
@@ -1397,6 +1456,7 @@ fa() {
   done < <(find "$@" -print0);
 }
 
+# shellcheck disable=SC2120
 faf() { # find all files. use -L to follow symlinks
   find "$@" -not \( -name .svn -prune -o -name .git -prune \
        -o -name .hg -prune -o -name .editor-backups -prune \
@@ -1560,53 +1620,6 @@ and works in older versions of git which did not have that."
   echo "${p%%/.git}"
 }
 
-g() {
-
-  local args gdb=false
-
-  if [[ $EMACSDIR ]]; then
-    path-add "$EMACSDIR/lib-src" "$EMACSDIR/src"
-  fi
-
-  if [[ $DISPLAY ]]; then
-    args=-n
-  fi
-
-  if (( $# == 0 )); then
-    args+=" -c"
-  fi
-  # duplicate -c, but oh well
-  if ! pgrep -u $EUID emacsclient; then
-    if (( $# == 0 )) && type -p gdb &>/dev/null; then
-      gdb=true
-    else
-      args+=" -c"
-    fi
-  fi
-  if [[ $EMACSDIR ]]; then
-
-    # todo: we don't have to alter HOME since emacs 29+, we can set
-    # user-emacs-directory with the flag --init-directory
-
-    # Alter the path here, otherwise the nfs mount gets triggered on the
-    # first path lookup when emacs is not being used.
-    # shellcheck disable=SC2098 disable=SC2097 # false positive
-    PATH="$EMACSDIR/lib-src:$EMACSDIR/src:$PATH" EHOME=$HOME HOME=$EMACSDIR m emacsclient -a "" $args "$@"
-  else
-    if $gdb; then
-      # due to a bug, we cant debug from the start unless we get a new gdb
-      # https://sourceware.org/bugzilla/show_bug.cgi?id=24454
-      # m gdb -ex="set follow-fork-mode child" -ex=r -ex=quit --args emacs --daemon
-      m emacsclient -a "" $args "$@"
-      sleep 1
-      cd "/a/opt/emacs-$(distro-name)$(distro-num)"
-      s gdb -p "$(pgrep -f 'emacs --daemon')" -ex c
-      cd -
-    else
-      m emacsclient -a "" $args "$@"
-    fi
-  fi
-}
 
 # g pipe. like: cmd | emacs. save cmd output to tmp file, then edit.
 gp() {
@@ -1652,9 +1665,9 @@ grr() { # grep recursive
   # Don't return 1 on nonmatch because this is meant to be
   # interactive, not in a conditional.
   if [[ ${#@} == 1 ]]; then
-    grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" . || [[ $? == 1 ]]
+    grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -rniIP --color=auto "$@" . || [[ $? == 1 ]]
   else
-    grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -riIP --color=auto "$@" || [[ $? == 1 ]]
+    grep --exclude-dir='*.emacs.d' --exclude-dir='*.git' -rniIP --color=auto "$@" || [[ $? == 1 ]]
   fi
 }
 ccomp grep gr grr
@@ -1674,10 +1687,21 @@ re() {
 
 # horizontal row. used to break up output
 hr() {
-  local blocks
-  # 180 is long enough.
-  blocks=██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
-  printf "%s\n" "$(tput setaf 5 2>/dev/null ||:)${blocks:0:${COLUMNS:-180}}$(tput sgr0 2>/dev/null||:)"
+  local start end end_count arg
+  # 180 is long enough. 5 for start.
+  start=█████ end=█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+  end_count=$(( ${COLUMNS:-180} - 5 ))
+  arg="$*"
+  if [[ $arg ]]; then
+    end_count=$(( end_count - 2 - ${#arg} ))
+    start="$start $arg "
+  fi
+  if (( end_count >= 1 )); then
+    end=${end:0:$end_count}
+  else
+    end=
+  fi
+  printf "%s\n" "$(tput setaf 5 2>/dev/null ||:)$start$end$(tput sgr0 2>/dev/null||:)"
 }
 # highlight
 hl() {
@@ -1990,6 +2014,8 @@ ksu() { # history search unique
   grep -P --binary-files=text "$@" ${HISTFILE:-~/.bash_history}  | uniq || [[ $? == 1 ]];
 }
 
+# remove lines from history matching $1
+#
 # 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.
@@ -2052,9 +2078,9 @@ nags() {
   /usr/bin/nagstamon &
 }
 
-# profanity screen
+# profanity tmux
 profsrc() {
-  screen -RD -S profanity
+  screen -L profanity a
 }
 
 # i dont want to wait for konsole to exit...
@@ -2119,7 +2145,7 @@ pkx() { # package extract
   c "$(mktemp -d)"
   pkg=$1
   # shellcheck disable=SC2012
-  cached=$(ls -t /var/cache/apt/archives/${pkg}_* | tail -n1 2>/dev/null) ||:
+  cached=$(ls -t /var/cache/apt/archives/${pkg}_* 2>/dev/null | tail -n1 2>/dev/null) ||:
   if [[ $cached ]]; then
     m cp $cached .
   else
@@ -2161,7 +2187,7 @@ grep ps and output in a nice format"
   fi
   x=$(ps -eF)
   # final grep is because some commands tend to have a lot of trailing spaces
-  y=$(echo "$x" | grep -iP "$@" | grep -o '.*[^ ]') ||:
+  y=$(echo "$x" | sed -r 's,//[^[:space:]:@/]+:[^[:space:]:@/]+@,//REDACTED_URL_USER@PASS/,g' | grep -iP "$@" | grep -o '.*[^ ]') ||:
   if [[ $y ]]; then
     echo "$x" | head -n 1 || [[ $? == 141 ]]
     echo "$y"
@@ -2391,6 +2417,19 @@ serstat() {
   systemctl -n 40 status "$@"
 }
 
+# assume last arg is a service and we want to tail its log.
+serj() {
+  local service jr_pid ret
+  ret=0
+  service="${*: -1}"
+  journalctl -qn2 -f -u "$service" &
+  sleep 3
+  s systemctl "$@" || ret=$?
+  sleep .5
+  kill %%
+  (( ret == 0 )) || return $ret
+}
+
 seru() { systemctl --user "$@"; }
 # like restart, but do nothing if its not already started
 srestart() {
@@ -2443,19 +2482,24 @@ sgu() {
   systemctl list-unit-files | rg "$@"
 }
 
+# check whether we generally want to do sk on the file
+sk-p() {
+  [[ ! -L $f ]] && istext "$1" && [[ $(head -n1 "$1" 2>/dev/null) == '#!/bin/bash'* ]]
+}
 
 sk() {
   # see https://savannah.gnu.org/maintenance/fsf/bash-style-guide/ for justifications
   local quotes others ret
   quotes=2048,2068,2086,2206,2254
-  others=2029,2032,2033,2054,2164,
-  shellcheck -W 999 -x -e $quotes,$others "$@" || ret=$?
+  others=2029,2032,2033,2054,2164
+  shellcheck -x -W 999 -e $quotes,$others "$@" || ret=$?
   if (( ret >= 1 )); then
     echo "A template comment to disable is now in clipboard. eg: # shellcheck disable=SC2206 # reason"
     cbs "# shellcheck disable=SC"
     return $ret
   fi
 }
+
 # sk with quotes. For checking scripts that we expect to take untrusted
 # input in order to verify we quoted vars.
 skq() {
@@ -2464,15 +2508,41 @@ skq() {
   shellcheck -W 999 -x -e $others "$@" || return $?
 }
 
-skgit() {
+# sk on all modified & new files in current git repo. must git add for new files.
+skmodified() {
   local f
-  for f in $(i s | awk '$1 == "modified:" {print $2}'); do
-    if istext "$f" && [[ $(head -n1 "$f" 2>/dev/null) == '#!/bin/bash'* ]]; then
+  for f in $(i s | awk '$1 == "modified:" {print $2}; $1 == "new" {print $3}'); do
+    if sk-p "$f"; then
       sk $f ||:
     fi
   done
 }
 
+
+# sk on all the files in current git repo
+skgit() {
+  local f toplevel orig_dir tmp
+  local -a ls_files sk_files
+  toplevel=$(git rev-parse --show-toplevel)
+  if [[ $PWD != "$toplevel" ]]; then
+    orig_dir=$PWD
+    cd $toplevel
+  fi
+  # tracked & untracked files
+  tmp=$(git ls-files && git ls-files --others --exclude-standard)
+  mapfile -t ls_files <<<"$tmp"
+  for f in "${ls_files[@]}"; do
+    if sk-p "$f"; then
+      sk_files+=("$f")
+    fi
+  done
+  sk "${sk_files[@]}"
+  if [[ $orig_dir ]]; then
+    cd $orig_dir
+  fi
+}
+
+
 # sl: ssh, but firsh rsync our bashrc and related files to a special
 # directory on the remote host if needed.
 
@@ -2706,8 +2776,15 @@ sl() {
 slr() {
   sl --rsync "$@"
 }
-sss() { # ssh solo
-  sl -oControlMaster=no -oControlPath=/ "$@"
+
+
+# ssh solo
+#
+# WARNING: If you are trying to use -i, remember that keys added to
+# agent previously will still be tried. Use ssh-add -D to remove all
+# keys from the agent.
+sss() {
+  ssh -oControlMaster=no -oControlPath=/ "$@"
 }
 # kill off old shared socket then ssh
 ssk() {
@@ -2717,11 +2794,7 @@ ssk() {
 ccomp ssh sl slr sss ssk
 # plain ssh
 ssh() {
-  if [[ $TERM == alacritty || $TERM == xterm-kitty ]]; then
-    TERM=xterm-256color LC_USEBASHRC=t command ssh "$@"
-  else
-    LC_USEBASHRC=t command ssh "$@"
-  fi
+  LC_USEBASHRC=t command ssh "$@"
 }
 
 
@@ -2834,9 +2907,19 @@ psoff() {
   # however, DEBUG is not inherited, so we need to run it outside a function.
   # And we want to run set -x afterwards to avoid spam, so we cram everything
   # in here, and then it will run after this function is done.
-  # # set as array to satisfy shellcheck, but it is equivalent to setting it as non-array
-  PROMPT_COMMAND=('trap DEBUG; unset PROMPT_COMMAND; PS1="\w \$ "')
+  # shellcheck disable=SC2178 # intentional
+  PROMPT_COMMAND='trap DEBUG; unset PROMPT_COMMAND; PS1=" \w \$ "'
+}
+
+pskde() {
+  # shellcheck disable=SC2178 # intentional
+  PROMPT_COMMAND='trap DEBUG; unset PROMPT_COMMAND'
+  PS1='\[\e]133;L\a\]\[\e]133;D;$?\]\[\e]133;A\a\]\w \$ \[\e]133;B\a\]' ;
+  PS2='\[\e]133;A\a\]'$PS2'\[\e]133;B\a\]' ;
+  PS0='\[\e]133;C\a\]'
+
 }
+
 pson() {
   PROMPT_COMMAND=(prompt-command)
   if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
@@ -2880,34 +2963,8 @@ nonet() {
 }
 
 m() { printf "%s\n" "$*";  "$@"; }
+m2() { printf "%s\n" "$*" >&2;  "$@"; }
 
-# update file. note: duplicated in mail-setup.
-# updates $ur u result to true or false
-# updates $reload to true if file updated is in /etc/systemd/system
-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
-  # shellcheck disable=SC2034 # see comment at top of function
-  ur=false # u result
-  tmpdir="$(mktemp -d)"
-  cat >$tmpdir/"$base"
-  tmp=$(rsync -ic $tmpdir/"$base" "$dest")
-  if [[ $tmp ]]; then
-    printf "%s\n" "$tmp"
-    # shellcheck disable=SC2034 # see comment at top of function
-    ur=true
-    if [[ $dest == /etc/systemd/system/* ]]; then
-      # shellcheck disable=SC2034 # see comment at top of function
-      reload=true
-    fi
-  fi
-  rm -rf $tmpdir
-}
 
 
 uptime() {
@@ -3032,7 +3089,7 @@ spark()
 
   for n in $numbers
   do
-    _spark_echo -n ${ticks[$(( (((n-min)<<8)/f) ))]}
+    _spark_echo -n ${ticks[$(( ((n-min)<<8)/f ))]}
   done
   _spark_echo
 }
@@ -3097,8 +3154,9 @@ n() {
 }
 
 catnew() {
-  local dir file
+  local dir file _
   dir="$1"
+  # shellcheck disable=SC2030
   inotifywait -m "$dir" -e create -e moved_to | while read -r _ _ file; do
     hr
     cat "$dir/$file"
@@ -3162,9 +3220,13 @@ if $use_color && type -p tput &>/dev/null; then
   # https://github.com/trapd00r/LS_COLORS
   # I would like if there was something similar for light.
 
+  # https://www.bigsoft.co.uk/blog/2008/04/11/configuring-ls_colors
+  # change the hard to read turqouise.
+  # defaults dircolors --print-database.
+
   # the default bold green is too light.
   # this explains the codes: https://gist.github.com/thomd/7667642
-  export LS_COLORS=ex=1
+  export LS_COLORS="ex=1:ln=00;31"
 
   term_bold="$(tput bold)"
   term_red="$(tput setaf 1)"
@@ -3218,29 +3280,46 @@ if [[ $- == *i* ]]; then
   # so I've thrown a bunch of things at the wall to speed it up.
   prompt-command() {
     local return=$? # this MUST COME FIRST
+
+
+    # all usable colors:
+    # black
+    # green         nonzero exit (pri 1)
+    # purple        default
+    # purple bold
+    # red           pwd different owner & group & not writable (pri 2)
+    # red bold      pwd different owner & group & writable (pri 2)
+    # yellow
+
     local ps_char ps_color
     unset IFS
 
     if [[ $HISTFILE ]]; then
       history -a # save history
+      if [[ -e $HOME/.iank-stream-on ]]; then
+        if [[ $HISTFILE == $HOME/.bh ]]; then
+          ps_char="HISTP "
+        fi
+      elif [[ $HISTFILE == /a/bin/data/stream_hist ]]; then
+        ps_char="HISTS "
+      fi
     fi
 
-    case $return in
-      0) ps_color="$term_purple"
-         ps_char='\$'
-         ;;
-      *) ps_color="$term_green"
-         ps_char="$return \\$"
-         ;;
-    esac
+    ps_color="$term_purple"
+    ps_char="$ps_char"'\$'
     if [[ ! -O . ]]; then # not owner
       if [[ -w . ]]; then # writable
        ps_color="$term_bold$term_red"
       else
-       ps_color="$term_bold$term_green"
+       ps_color="$term_red"
       fi
     fi
 
+    if [[ $return != 0 ]]; then
+      ps_color="$term_green"
+      ps_char="$return \\$"
+    fi
+
     # faster than sourceing the file im guessing
     if [[ -e /dev/shm/iank-status && ! -e /tmp/quiet-status ]]; then
       eval "$(< /dev/shm/iank-status)"
@@ -3250,7 +3329,7 @@ if [[ $- == *i* ]]; then
     fi
     jobs_char=
     if [[ $(jobs -p) ]]; then
-      jobs_char='j\j '
+      jobs_char="$(jobs -p)"'j\j '
     fi
 
 
@@ -3283,6 +3362,8 @@ if [[ $- == *i* ]]; then
     fi
     PS1="${PS1%"${PS1#*[wW]}"} $jobs_char$psudo\[$ps_color\]$ps_char\[$term_nocolor\] "
 
+
+
     # copy of what is automatically added by guix.
     # adds [env] to PS1 if GUIX_ENVIRONMENT is set and PS1 contains '$';
     if [ -n "$GUIX_ENVIRONMENT" ]; then
@@ -3295,6 +3376,16 @@ if [[ $- == *i* ]]; then
     # set titlebar. instead, using more advanced
     # titelbar below
     #echo -ne "$_title_escape $HOSTNAME ${PWD/#$HOME/~}  \007"
+
+    # version 211203 does not have this feature.
+    if [[ $KONSOLE_VERSION && $KONSOLE_VERSION == [3456789]* || $KONSOLE_VERSION == 2[2456789]* ]]; then
+      # from konsole, output via ctrl-alt-]
+      if [[ ! $PS1 =~ 133 ]] ; then
+        PS1='\[\e]133;L\a\]\[\e]133;D;$?\]\[\e]133;A\a\]'$PS1'\[\e]133;B\a\]' ;
+        PS2='\[\e]133;A\a\]'$PS2'\[\e]133;B\a\]' ;
+        PS0='\[\e]133;C\a\]' ; fi
+    fi
+
   }
   PROMPT_COMMAND=(prompt-command)
 
@@ -3339,6 +3430,78 @@ if [[ $- == *i* ]]; then
 
 fi
 
+
+lp22viewers() {
+  v=0
+  roomv=(0 0)
+  rooms=(jupiter saturn)
+  for ip in 209.51.188.25 live.fsf.org; do
+    out=$(curl -sS --insecure https://$ip/)
+    for i in 0 1 2; do
+      room=${rooms[i]}
+      while read -r n; do
+        v=$((v+n))
+        # shellcheck disable=SC2004 # false positive
+        roomv[$i]=$(( ${roomv[$i]} + n ))
+      done < <(printf "%s\n" "$out"  | grep -Po "$room.*?current[^0-9]*[0-9]*" | grep -o '[0-9]*$' )
+    done
+  done
+  printf "total: %s " $v
+  for i in 0 1; do
+    room=${rooms[i]}
+    printf "$room: %s " "${roomv[$i]}"
+  done
+  echo
+}
+
+arpflush() {
+  local default_route_dev
+  default_route_dev=$(ip r show default | sed 's/.*dev \([^ ]*\).*/\1/' | head -n1)
+  m s ip n flush dev "$default_route_dev"
+}
+
+dsh() {
+  command dsh -c "$@"
+}
+
+# cat or bat with color if we have it
+v() {
+  if type -t batcat >/dev/null; then
+    # note: another useful useful style is "header"
+    batcat --color always --style plain --theme Coldark-Cold -P "$@"
+  else
+    cat "$@"
+  fi
+}
+
+# Combine files $@ into a single file with comments between them which
+# allow splitting them back with fsplit.
+#
+# Assumes file names do not have newlines in them.
+fcomb() {
+  local f comment out
+  # generated with cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c8
+  comment='# jvvuyUsq '
+  out=~/fcomb
+  rm -f $out
+  for f; do
+    echo "$comment$f" >>$out
+    cat "$f" >>$out
+  done
+}
+fsplit() {
+  local f fin line fin_lines
+  fin=~/fcomb
+  line=1
+  fin_lines=$(wc -l "$fin" | awk '{print $1}')
+  comment='# jvvuyUsq '
+  while (( line <= fin_lines )); do
+    f=$(sed -n "${line}s/^$comment//p" "$fin")
+    sed -n "$line,/^$comment/{/^$comment/d;p}" "$fin" >"$f"
+    line=$(( line + 1 + $(wc -l "$f" | awk '{print $1}') ))
+  done
+}
+
 # * stuff that makes sense to be at the end