From d67edcdca8795a4bca116aa532d02dda246a6f53 Mon Sep 17 00:00:00 2001
From: Ian Kelling <ian@iankelling.org>
Date: Mon, 15 Apr 2024 10:47:21 -0400
Subject: [PATCH] lots: shellcheck, streaming stuff, fixes

---
 .bash_profile                                 |   4 +-
 beet-data                                     |   1 +
 brc                                           |  53 +++++---
 brc2                                          | 104 ++++++++++++---
 brcrun                                        |   1 +
 check-radicale                                |   2 +-
 check-remote-mailqs                           |   2 +-
 check-stale-alerts                            |   2 +-
 clip-hc                                       |  19 +++
 clip-sad                                      |  18 +++
 clip-up                                       |  25 ++++
 conflink                                      |   7 +-
 dall                                          |   2 +-
 demohost-mount                                |   1 +
 desktop-20-autostart.sh                       |   8 --
 maru-init => disabled/maru-init               |   0
 mastodon-upgrade => disabled/mastodon-upgrade |   0
 offlineimap-sync => disabled/offlineimap-sync |   0
 distro-begin                                  |   4 +-
 distro-end                                    |  75 ++++++++++-
 dsremote                                      |   1 +
 dynamic-ip-update                             |  27 ++--
 exim-nn-iptables                              |   4 +-
 filesystem/etc/nginx/conf.d/rtmp.conf         |  13 --
 .../system/icecast2.service.d/override.conf   |   2 +
 filesystem/usr/local/bin/abrowser             |  29 +++-
 filesystem/usr/local/bin/myupgrade            |   2 +-
 fixvpndns                                     |   2 +-
 gitslink                                      |   2 +-
 i3-auto-layout-toggle                         |  10 ++
 i3-maybe-double-move                          |   9 ++
 i3-mouse-warp                                 |  31 +++++
 i3-pull                                       |  19 +++
 i3-split-maybe                                |  45 +++++++
 i3-sway/common.conf                           | 125 ++++++++++++------
 i3-sway/i3.conf                               |  19 ++-
 iboot                                         |   9 +-
 ic-tmp-setup                                  |   5 +
 input-setup                                   |  11 +-
 ip6tables-exim                                |   2 +-
 iptables-exim                                 |   2 +-
 keyscript-on                                  |   3 +-
 mail-cert-cron                                |   2 +-
 mount-latest-remote                           |   5 +-
 mount-latest-subvol                           |   6 +-
 my-update-info-dir                            |   4 +-
 myi3status                                    |   7 +
 nextcloud-setup                               |   1 +
 obs                                           |  12 ++
 obs-auto-scene-switch-toggle                  |  16 +++
 obs-i3-interlude                              |  14 ++
 obs-i3-monitor                                |  27 ++++
 pkgs                                          |   3 +
 schrootupdate                                 |  16 ++-
 ssh-emacs-setup                               |   3 +-
 subdir_files/.config/mpv/mpv.conf             |   3 +-
 switch-mail-host                              |   2 +-
 system-status                                 |  23 ++--
 trusted-network                               |   4 +-
 vpn-mail-forward                              |   2 +-
 vpn-static-ip                                 |   2 +-
 zboot                                         |   5 +-
 zboot-chroot                                  |   2 +-
 ziva-backup-check                             |   8 +-
 ziva-screen                                   |   2 +-
 65 files changed, 680 insertions(+), 189 deletions(-)
 create mode 100755 clip-hc
 create mode 100755 clip-sad
 create mode 100755 clip-up
 rename maru-init => disabled/maru-init (100%)
 rename mastodon-upgrade => disabled/mastodon-upgrade (100%)
 rename offlineimap-sync => disabled/offlineimap-sync (100%)
 delete mode 100644 filesystem/etc/nginx/conf.d/rtmp.conf
 create mode 100644 filesystem/etc/systemd/system/icecast2.service.d/override.conf
 create mode 100755 i3-auto-layout-toggle
 create mode 100755 i3-maybe-double-move
 create mode 100755 i3-mouse-warp
 create mode 100755 i3-pull
 create mode 100755 i3-split-maybe
 create mode 100755 ic-tmp-setup
 create mode 100755 obs
 create mode 100755 obs-auto-scene-switch-toggle
 create mode 100755 obs-i3-interlude
 create mode 100755 obs-i3-monitor

diff --git a/.bash_profile b/.bash_profile
index ce98e54..ef517f2 100644
--- a/.bash_profile
+++ b/.bash_profile
@@ -1,4 +1,5 @@
-# man bash covers everything comprehensively of course.  i use ~/.bash_profile
+#!/bin/bash
+# info bash covers everything comprehensively of course.  i use ~/.bash_profile
 # to source bashrc, and .profile just echos that the normal bash startup process
 # is not happening.  I don't source bashrc in posix mode based on debian's
 # default, and posix mode is quirky, doesn't seem worth figuring it out This
@@ -34,6 +35,7 @@ HISTCONTROL=ignoredups
 HISTIGNORE='pass *:[ ]*:otp *:oathtool *'
 
 
+# shellcheck source=/a/bin/ds/.bashrc
 [[ -f ~/.bashrc ]] && . ~/.bashrc
 # ensure no bad programs appending to this file will have an affect
 return 0
diff --git a/beet-data b/beet-data
index c030efd..bdfb32d 100644
--- a/beet-data
+++ b/beet-data
@@ -21,6 +21,7 @@ nav_tags=(
   run
 )
 
+
 pl_tags=(
   "${nav_tags[@]}"
   # alternate version of a song we already have which isn't as good
diff --git a/brc b/brc
index 2a81b25..f0b57f8 100644
--- a/brc
+++ b/brc
@@ -347,6 +347,9 @@ if [[ $SOE ]]; then
   fi
 fi
 
+# go exists here
+path-add --ifexists /usr/local/go/bin
+
 
 mysrc() {
   local path dir file
@@ -870,9 +873,21 @@ khfix() {
 a() {
   local x
   x=$(readlink -nf "${1:-$PWD}")
-  # yes, its kinda dumb that xclip/xsel cant do this in one invocation
-  echo -n "$x" | xclip -selection clipboard
-  echo -n "$x" | xclip
+  # yes, its kinda dumb that xclip/xsel cant do this in one invocation.
+  # And, summarizing this:
+  # https://askubuntu.com/questions/705620/xclip-vs-xsel
+  # xclip has a few more options. xclip has a bug in tmux / forwarded x sessions.
+  cbs "$x"
+}
+
+# clipboard a string (into selection & clipboard buffer)
+cbs() {
+  # yes, its kinda dumb that xclip/xsel cant do this in one invocation.
+  # And, summarizing this:
+  # https://askubuntu.com/questions/705620/xclip-vs-xsel
+  # xclip has a few more options. xclip has a bug in tmux / forwarded x sessions.
+  printf "%s" "$*" | xclip -selection clipboard
+  printf "%s" "$*" | xclip
 }
 
 # a1 = awk {print $1}
@@ -1609,8 +1624,9 @@ gl() {
   "$@" &> /a/tmp/gtmp
   g /a/tmp/gtmp
 }
-# g command substitution
+# g command substitution.
 gc() {
+  # shellcheck disable=SC2046 # i want word splitting for this hackery
   g $("$@")
 }
 
@@ -1720,7 +1736,7 @@ go-github-install() {
   file_prefix=$2
   file_suffix=$3
   tmp="${file_prefix##*[[:alnum:]]}"
-  targetf="${file_prefix%$tmp}"
+  targetf="${file_prefix%"$tmp"}"
   echo targetf: $targetf
   github-release-dl "$@"
   files=(./*)
@@ -2429,15 +2445,16 @@ sgu() {
 
 
 sk() {
-  # disable a warning with:
-  # shellcheck disable=SC2206 # reasoning
-
-  # see bash-template/style-guide.md for justifications
-
-  local quotes others
+  # 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 "$@" || return $?
+  shellcheck -W 999 -x -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.
@@ -2823,7 +2840,7 @@ psoff() {
 pson() {
   PROMPT_COMMAND=(prompt-command)
   if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
-    trap 'settitle "$BASH_COMMAND"' DEBUG
+    trap 'auto-window-title "$BASH_COMMAND"' DEBUG
   fi
 }
 
@@ -3125,6 +3142,12 @@ EOF
   done
 }
 
+# note, there is also the tool gron which is meant for this, but
+# this is good enough to not bother installing another tool
+jq-lines() {
+  # https://stackoverflow.com/questions/59700329/how-to-print-path-and-key-values-of-json-file-using-jq
+  jq --stream -r 'select(.[1]|scalars!=null) | "\(.[0]|join(".")): \(.[1]|tojson)"' "$@"
+}
 
 tsr() { # ts run
   "$@" |& ts || return $?
@@ -3284,7 +3307,7 @@ if [[ $- == *i* ]]; then
   fi
 
   # make the titlebar be the last command and the current directory.
-  settitle () {
+  auto-window-title () {
 
 
     # These are some checks to help ensure we dont set the title at
@@ -3309,7 +3332,7 @@ if [[ $- == *i* ]]; then
   # condition from the screen man page i think.
   # note: duplicated in tx()
   if [[ $TERM == *(screen*|xterm*|rxvt*) ]]; then
-    trap 'settitle "$BASH_COMMAND"' DEBUG
+    trap 'auto-window-title "$BASH_COMMAND"' DEBUG
   else
     trap DEBUG
   fi
diff --git a/brc2 b/brc2
index ec5969f..1c57452 100644
--- a/brc2
+++ b/brc2
@@ -463,7 +463,7 @@ glue() {
 
 # usage: see above
 _iki-convert() {
-  local url url_prefix path input err repo_dir dir url_dir url name
+  local url url_prefix path input repo_dir dir url_dir url name
   url_prefix="$1"
   name="${url_prefix%%.*}"
   repo_dir="/f/$name"
@@ -476,8 +476,16 @@ _iki-convert() {
   case $input in
     http*)
       path="$repo_dir/${input##http*://"$url_prefix"/}"
+      # for files like x.jpg, we dont need to convert the extension.
       if [[ $path == */ ]]; then
         path=${path%/}.mdwn
+        # brains adds trailing slash, but without trailing is still
+        # valid. We can't be totally sure whether to add mdwn, but we
+        # can guess based on the existence of the file. We can't be sure
+        # because it could be a file like x.jpg, that we just don't have
+        # in our local repo.
+        elif [[ ! -f $path && -e $path.mdwn ]]; then
+        path=${path}.mdwn
       fi
       j printf "%s\n" "$path"
       ;;
@@ -485,7 +493,9 @@ _iki-convert() {
       path=$(fp "$input")
       url_dir=$(echo "$path" | sed -r "s,^(/a)?$repo_dir/,,")
       url="https://$url_prefix/$url_dir"
-      url="${url%.mdwn}/"
+      if [[ $url == *.mdwn ]]; then
+        url="${url%.mdwn}/"
+      fi
       j echo "$url"
       ;;
   esac
@@ -748,6 +758,7 @@ mpvrpc-percent-pos() {
 # background, this relies on how ps converts newlines in arguments to spaces, and
 # assumes we won't be searching for a command with spaces in its arguments
 rinr() {
+  # shellcheck disable=SC2009 # pgrep has no fixed string option, plus see above.
   if ps h -o args -C "${1##*/}" | grep -Fxqv "$*" &>/dev/null || [[ $? == 141 ]]; then
     "$@"
   fi
@@ -780,7 +791,7 @@ mpvrpc-loadfile() {
     finalpath="$cachedir${path#/i/m}"
     rowir rsync --partial -a --inplace --mkpath "b8.nz:$path" "$finalpath"
     finalnextpath="$cachedir${nextpath#/i/m}"
-    count=$(pgrep -a -f "^rsync --partial -a --inplace --mkpath $cachdir" || [[ $? == 1 ]] )
+    count=$(pgrep -a -f "^rsync --partial -a --inplace --mkpath $cachedir" || [[ $? == 1 ]] )
     # allow us to start 2 rsyncs in the background
     if [[ $count == [01] ]]; then
       rinr rsync --partial -a --inplace --mkpath "b8.nz:$nextpath" "$finalnextpath" &
@@ -1761,7 +1772,8 @@ bindpush() {
   dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
   lipush
   for h in li bk; do
-    m sl $h.b8.nz <<'EOF'
+    e sshing $h
+    ssh $h.b8.nz <<'EOF'
 source ~/.bashrc
 m dnsup
 EOF
@@ -1770,7 +1782,8 @@ EOF
 bindpushb8() {
   lipush
   for h in li bk; do
-    m sl $h <<'EOF'
+    e sshing $h
+    ssh $h.b8.nz <<'EOF'
 source ~/.bashrc
 m dnsb8
 EOF
@@ -2932,6 +2945,9 @@ mpvgpu() {
 mpvd() {
   mpv --profile=d "$@";
 }
+mpva() {
+  mpv --profile=a "$@";
+}
 # mpv all media files in . or $1
 mpvm() {
   local -a extensions arg
@@ -3411,8 +3427,6 @@ spd() {
 }
 
 spamf() { # spamtest on FILE
-  local spamcpre spamdpid
-
   if (( $# != 1 )); then
     e spamtest error: expected 1 arg, filename >&2
     return 1
@@ -4016,12 +4030,6 @@ vrun() {
   "$@"
 }
 
-f=/a/f/ansible-configs/files/common/etc/fsf-workstation-bashrc.sh
-if [[ -e $f ]]; then
-  # shellcheck disable=SC1090
-  source $f
-fi
-
 electrum() {
   # https://electrum.readthedocs.io/en/latest/tor.html
   # https://github.com/spesmilo/electrum-docs/issues/129
@@ -4039,23 +4047,34 @@ rgm() {
 }
 
 # re all my files more expansively
+
 rem() {
   local paths
   paths="/p/c /b/"
   find $paths -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 -- "$*" ||:
-  rgv -- "$*" $paths /a/t.org /p/w.org /a/work.org ||:
+  rgv $local_rgv_args -g "!bash_unpublished" -- "$*" $paths /a/work.org ||:
 }
-reml() { # with limit to 5 matches per file
+reml() { # rem with limit to 5 matches per file
+  local_rgv_args="-m 5"
+  rem "$@"
+}
+
+rep() {
   local paths
-  paths="/p/c /b"
+  paths="/p/c"
   find $paths -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 -- "$*" ||:
-  rgv -m 5 -- "$*" $paths /a/t.org /p/w.org /a/work.org ||:
+  rgv $local_rgv_args -- "$*" $paths /a/t.org /p/w.org ||:
+}
+repl() { # rem with limit to 5 matches per file
+  local local_rgv_args="-m 5"
+  rem "$@"
 }
 
+
 # re on common fsf files
 ref() {
   local paths
@@ -4202,9 +4221,6 @@ mypyenvinit () {
 }
 
 
-export GOPATH=$HOME/go
-path-add $GOPATH/bin
-path-add /usr/local/go/bin
 
 # I have the git repo and a release. either one should work.
 # I have both because I was trying to solve an issue that
@@ -4366,6 +4382,54 @@ ftoc() {
   units "tempF($1)" tempC
 }
 
+# requires dns/firewall setup first
+local-icecast() {
+  web-conf -e ian@iankelling.org -f 8000 - apache2 live.iankelling.org  <<'EOF'
+<Location "/fsf.webm">
+AuthType Basic
+AuthName "basic_auth"
+# created with
+# htpasswd -c icecast-fsf-htpasswd USERNAME
+AuthUserFile "/etc/icecast-fsf-htpasswd"
+Require valid-user
+</Location>
+<Location "/fsf-tech.webm">
+AuthType Basic
+AuthName "basic_auth"
+AuthUserFile "/etc/icecast-fsf-tech-htpasswd"
+Require valid-user
+</Location>
+EOF
+}
+
+# obs screen switching of
+obof() {
+  ls -l /tmp/no-obs-auto-scene-switch
+  touch /tmp/no-obs-auto-scene-switch
+}
+# obs screen switching on
+obon() {
+  ls -l /tmp/no-obs-auto-scene-switch
+  if [[ -e /tmp/no-obs-auto-scene-switch ]]; then
+    rm -f /tmp/no-obs-auto-scene-switch
+  fi
+}
+
+obs-gen-profiles() {
+  local p=/p/c/basic/profiles
+  sed 's/fsf-sysops/fsf-tech/g' $p/fsfsysops/basic.ini >$p/fsftech/basic.ini
+  sed 's/fsf-sysops/fsf/g' $p/fsfsysops/basic.ini >$p/fsf/basic.ini
+}
+
+# terminal clear. like clear, but put the prompt at the bottom,
+# useful for obs streaming the bottom half of a terminal window.
+tclear() {
+  for ((i=i; i<COLUMNS; i++)); do
+    echo
+  done
+}
+
+
 export BASEFILE_DIR=/a/bin/fai-basefiles
 
 #export ANDROID_HOME=/a/opt/android-home
diff --git a/brcrun b/brcrun
index 4ede74e..b8e4c88 100755
--- a/brcrun
+++ b/brcrun
@@ -1,3 +1,4 @@
 #!/bin/bash
+# shellcheck disable=SC1090
 . ~/.bashrc
 "$@"
diff --git a/check-radicale b/check-radicale
index 99599a5..11860a6 100755
--- a/check-radicale
+++ b/check-radicale
@@ -14,7 +14,7 @@ fi
 # zerod out, by picking an amount that we dont expect to go below
 # anytime soon as of 2022.
 
-count=$(find /o/radicale/collections -type f | grep -v cache | wc -l)
+count=$(find /o/radicale/collections -type f | grep -cv cache)
 
 if (( count < 220 )); then
   echo "unexpected file count=$count < 220"
diff --git a/check-remote-mailqs b/check-remote-mailqs
index 047e8b7..eb8e20a 100755
--- a/check-remote-mailqs
+++ b/check-remote-mailqs
@@ -27,7 +27,7 @@ for h in bk je li x3wg kdwg sywg; do
   else
     if [[ -s $statefile ]]; then
       logsec=$(date +%s -d "$(head -n1 $statefile | awk '{print $1,$2}')")
-      case h in
+      case $h in
         frodo)
           hours=200
           ;;
diff --git a/check-stale-alerts b/check-stale-alerts
index f0d8481..cd42122 100755
--- a/check-stale-alerts
+++ b/check-stale-alerts
@@ -4,7 +4,7 @@
 if [[ ! -e /dev/shm/iank-status ]]; then
   exit 0
 fi
-eval $(< /dev/shm/iank-status)
+eval "$(< /dev/shm/iank-status)"
 
 dirs=()
 for d in /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state; do
diff --git a/clip-hc b/clip-hc
new file mode 100755
index 0000000..ed8e738
--- /dev/null
+++ b/clip-hc
@@ -0,0 +1,19 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+cd /a/bin/data/clips/hc
+
+if pgrep mpv; then
+  pkill mpv
+  exit 0
+fi
+
+clip=$(find . -type f -printf '%f\n' | \
+         { if [[ -e /tmp/last-hc ]]; then
+             sed "/^$(cat /tmp/last-hc)\$/d"
+           else
+             cat
+           fi ; } | \
+             shuf | head -n1)
+echo $clip >/tmp/last-hc
+mpv --profile=a $clip
diff --git a/clip-sad b/clip-sad
new file mode 100755
index 0000000..e1ce318
--- /dev/null
+++ b/clip-sad
@@ -0,0 +1,18 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+cd /a/bin/data/clips/sad
+
+if pgrep mpv; then
+  pkill mpv
+  exit 0
+fi
+
+clip=$(find . -type f -printf '%f\n' | \
+         { if [[ -e /tmp/last-sad ]]; then
+             sed "/^$(cat /tmp/last-sad)\$/d"
+           else
+             cat
+           fi ; } | \
+             shuf | head -n1)
+echo $clip >/tmp/last-sad
+mpv --profile=a $clip
diff --git a/clip-up b/clip-up
new file mode 100755
index 0000000..dea5f1d
--- /dev/null
+++ b/clip-up
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+cd /a/bin/data/clips/up
+
+if pgrep mpv; then
+  pkill mpv
+  exit 0
+fi
+
+if [[ ! -s /tmp/last-up ]]; then
+  find . -type f -printf '%f\n' | shuf > /tmp/last-up
+fi
+clip=$(head -n1 /tmp/last-up)
+tail -n+2 /tmp/last-up | sponge /tmp/last-up
+
+# clip=$(ls -1 . | \
+  #          { if [[ -e /tmp/last-up ]]; then
+#              sed "/^$(cat /tmp/last-up)\$/d"
+#            else
+#              cat
+#            fi ; } | \
+  #              shuf | head -n1)
+# echo $clip >/tmp/last-up
+
+mpv --profile=a $clip
diff --git a/conflink b/conflink
index d661e17..5681529 100755
--- a/conflink
+++ b/conflink
@@ -94,7 +94,7 @@ subdir-link-r() {
     local fullpath
     fullpath="$(readlink -f "$path")"
     if [[ -f $path || $(dirname "$fullpath") == "$below" ]]; then
-      m lnf -T "$path" "$HOME/${path#$root/}"
+      m lnf -T "$path" "$HOME/${path#"$root/"}"
     elif [[ -d "$path" ]]; then
       subdir-link-r "$root" "$path"
     fi
@@ -223,6 +223,7 @@ case $user in
       m s chgrp -R bind $f
       m s chmod g+w $f
     fi
+    # shellcheck disable=SC2016 # obviously expected
     s bash -c 'shopt -s nullglob; for f in /etc/bind/*.key /etc/bind/*.private /etc/bind/key.*; do chgrp bind $f; done'
     if [[ -e /etc/caldav-htpasswd ]] && getent group www-data &>/dev/null; then
       s chgrp www-data /etc/caldav-htpasswd
@@ -244,6 +245,10 @@ case $user in
       s rsync -clpgoDiSAX --chmod=Dg-s --chown=bitcoin:bitcoin /p/c/user-specific/bitcoin/settings.json /var/lib/bitcoind
       s rsync -rclpgoDiSAX --chmod=Dg-s --chown=root:bitcoin /p/c/user-specific/bitcoin/bitcoin /etc
     fi
+    # this folder strangely requires ownership as icecast2
+    if [[ -d /etc/icecast2 && -f /p/c/icecast.xml ]]; then
+      m s rsync -rclgoDiSAX --chmod=0644 --chown=root:root /p/c/icecast.xml /etc/icecast2
+    fi
     ##### end special extra stuff #####
 
     if ! $fast; then
diff --git a/dall b/dall
index 3ed8d44..d054f9d 100755
--- a/dall
+++ b/dall
@@ -5,7 +5,7 @@ shopt -s inherit_errexit 2>/dev/null ||: # ignore fail in bash < 4.4
 set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" exit status: $?, PIPESTATUS: ${PIPESTATUS[*]}" >&2' ERR
 
-readonly this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
+readonly this_file; this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
 readonly this_dir="${this_file%/*}"
 cd "$this_dir"
 ./distro-begin
diff --git a/demohost-mount b/demohost-mount
index 90d2b42..91e984f 100755
--- a/demohost-mount
+++ b/demohost-mount
@@ -1,4 +1,5 @@
 #!/bin/bash
+# shellcheck source=/a/bin/ds/.bashrc
 if [[ -s ~/.bashrc ]];then . ~/.bashrc;fi
 
 [[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
diff --git a/desktop-20-autostart.sh b/desktop-20-autostart.sh
index 0a3d139..278da29 100755
--- a/desktop-20-autostart.sh
+++ b/desktop-20-autostart.sh
@@ -16,14 +16,6 @@
 date "+%A, %B %d, %r, %S seconds" > /tmp/desktop-20-autostart-log
 
 
-# first 2 alternatives showed under ubuntu 14.04, second 2 under arch at 11/2015
-if [[ $1 ]]; then
-  right_monitor_rotation=left
-else
-  right_monitor_rotation=normal
-fi
-
-
 if ! xout="$(xrandr)"; then
   # under wayland
   exit 0
diff --git a/maru-init b/disabled/maru-init
similarity index 100%
rename from maru-init
rename to disabled/maru-init
diff --git a/mastodon-upgrade b/disabled/mastodon-upgrade
similarity index 100%
rename from mastodon-upgrade
rename to disabled/mastodon-upgrade
diff --git a/offlineimap-sync b/disabled/offlineimap-sync
similarity index 100%
rename from offlineimap-sync
rename to disabled/offlineimap-sync
diff --git a/distro-begin b/distro-begin
index 4104e00..61d8f8d 100755
--- a/distro-begin
+++ b/distro-begin
@@ -481,6 +481,7 @@ if isarch; then
 fi
 
 #### update all packages
+# shellcheck disable=SC2119 # obvious
 pup
 
 
@@ -708,8 +709,7 @@ if has_monitor; then
 
 
   ###### install X
-  # no recommends due to this bug: https://trisquel.info/en/issues/26525
-  pi --no-install-recommends i3
+  pi i3
 
   ##### install xinput
   case $(distro-name) in
diff --git a/distro-end b/distro-end
index 16fc7a5..e5770d4 100755
--- a/distro-end
+++ b/distro-end
@@ -176,11 +176,13 @@ EOF
       fi
     done
     if $doupdate; then
-      cd $(mktemp -d)
+      tmpdir=$(mktemp -d)
+      cd $tmpdir
       p download debian-archive-keyring
       s dpkg -i debian-archive-keyring
       p update
       cd -
+      rm -rf $tmpdir
     fi
 
     if [[ ! -e /usr/share/debootstrap/scripts/bookworm ]]; then
@@ -756,6 +758,35 @@ esac
 case $distro in
   trisquel|ubuntu)
 
+
+    ## one time setup thing I did
+    # c /a/opt/obs-cmd/
+    # cargo build --release
+    # cp target/release/obs-cmd ../bin
+    #
+    ## in obs, tools -> websocket server settings -> generate/copy password
+    #
+    # note: obs-studio on gnu does not support webrtc, it seems mainly because
+    # libdatachannel is not packaged. If it was, it would just need to do
+    # apt source obs-studio, obs-studio-30.1.1/debian/rules set -DENABLE_WEBRTC=ON
+    #
+    # I did manage to build libdatachannel following its instructions, then make install,
+    # then obs failed due to nvidia. found those options to disable with
+    # rg 'option\(ENABLE' | gr nv, then build obs like so:
+    #
+    # cmake -DLINUX_PORTABLE=ON -DCMAKE_INSTALL_PREFIX="${HOME}/obs-studio-portable" -DENABLE_BROWSER=OFF -DENABLE_AJA=OFF -DENABLE_NEW_MPEGTS_OUTPUT=OFF -DENABLE_WEBRTC=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DENABLE_NVVFX=OFF -DENABLE_NVAFX=OFF -DENABLE_NATIVE_NVENC=OFF  ..
+    #
+    #
+    #
+    # however, I didn't end up trying it out.
+    #
+    # note, in terminal source, i setup a transform so it would show the
+    # bottom 1080p section of the terminal instead of the top if the
+    # screen was bigger. click like 2 times in the preview so the red
+    # lines show up, right click, edit transform (or ctrl-e). bounding
+    # box type: scale to width of bounds. alignment in bounding box:
+    # bottom left. bounding box size 1920 x 1080.
+
     # ppa:obsproject/obs-studio
     if [[ ! -s /etc/apt/sources.list.d/obs.list ]]; then
       # https://blog.zackad.dev/en/2017/08/17/add-ppa-simple-way.html
@@ -767,6 +798,7 @@ EOF
       p update
     fi
     ;;
+
 esac
 
 case $codename_compat in
@@ -2015,9 +2047,39 @@ esac
 # `mpv --cache=no` had about 2.5 sec latency vs 4 seconds.
 # Then I discovered this command which had about .5 sec latency:
 #ffplay -f live_flv -fast -x 1280 -y 720 -fflags nobuffer -flags low_delay -strict experimental -vf "setpts=N/60/TB" -af "asetpts=N/60/TB" -noframedrop -i rtmp://url_here
+## a lot of those args arent needed, here is what I ended up with:
+# #ffplay -f live_flv -fflags nobuffer -flags low_delay -i rtmp://localhost/live
 #
-pi nginx libnginx-mod-rtmp
-
+# A problem with rtmp is that it doesn't support vp8/vp9, requiring the partly patent encumbered h264.
+# Looking at alternative protocols: dash & hls are both high latency, I tested dash with the nginx-rtmp
+# module and got about 5 seconds of latency, web results imply that is normal.
+#
+# Webrtc is what jitsi & bbb use, but an annoying thing is that
+# generally requires a web browser with javascript, or some special
+# client, and afaik, it has a smaller limit on number of clients.
+#
+# Another option is to try rtp/rtsp, there are some servers here:
+# https://en.wikipedia.org/wiki/Real-Time_Streaming_Protocol
+
+
+## reference for setting up rtmp
+# pi nginx libnginx-mod-rtmp
+# cat >/etc/nginx/modules-enabled/rtmp.conf <<'EOF'
+## based on https://opensource.com/article/19/1/basic-live-video-streaming-server#comments
+## and https://github.com/arut/nginx-rtmp-module/wiki/Directives
+
+# rtmp {
+#     allow publish 127.0.0.1;
+#     deny publish all;
+#     server {
+#         listen 1935;
+#         application live {
+#             live on;
+#             record off;
+#         }
+#     }
+# }
+# EOF
 
 ### end live streaming ###
 
@@ -2091,6 +2153,13 @@ m /a/bin/buildscripts/tor-browser
 s ln -sf /a/opt/tor-browser/Browser/start-tor-browser /usr/local/bin
 
 
+case $HOSTNAME in
+  kd)
+    web-conf -p 4500 -f 4533 -e ian@iankelling.org apache2 b8.nz
+    sgo navidrome
+    ;;
+esac
+
 # nfs server
 pi-nostart nfs-kernel-server
 
diff --git a/dsremote b/dsremote
index 2176740..ec8b2d2 100755
--- a/dsremote
+++ b/dsremote
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# shellcheck source=/a/bin/ds/.bashrc
 if [[ -s ~/.bashrc ]];then . ~/.bashrc;fi
 
 set -eE -o pipefail
diff --git a/dynamic-ip-update b/dynamic-ip-update
index c725682..b6dfa09 100755
--- a/dynamic-ip-update
+++ b/dynamic-ip-update
@@ -102,7 +102,7 @@ main() {
       return 0
     fi
     if ip4=$(curl --connect-timeout 10 -s4 https://iankelling.org/cgi/pubip); then
-      if $force || [[ $cur4 && $ip4 && $cur4 != $ip4 ]]; then
+      if $force || [[ $cur4 && $ip4 && $cur4 != "$ip4" ]]; then
         up4=true # update ipv4
       fi
     fi
@@ -120,7 +120,7 @@ main() {
     # we use slaac with privacy extension, so get our less private more permanent address
     mac=$(cat /sys/class/net/$dev/address)
 
-    IFS=: read -a f <<<$mac; set -- ${f[@]}
+    IFS=: read -ra f <<<$mac; set -- ${f[@]}
     ip6=${out6%:*:*:*:*}:$(printf %x $((0x$1 + 2)))$2:$3'ff:fe'$4:$5$6
     # in case we aren't using slaac
     if ! ip a | grep "^ *inet6 $ip6/" &>/dev/null; then
@@ -128,7 +128,7 @@ main() {
     fi
   fi
 
-  if $force || [[ $cur6 != $ip6 ]]; then
+  if $force || [[ $cur6 != "$ip6" ]]; then
     up6=true
   fi
 
@@ -146,14 +146,14 @@ main() {
   # "${SSH_CLIENT%% *}
   # to update bind if needed.
 
-  f=$(mktemp)
-  cat >>$f <<EOF
+  tmpf=$(mktemp)
+  cat >>$tmpf <<EOF
 server iankelling.org
 zone b8.nz
 EOF
 
   if $up4; then
-    cat >>$f <<EOF
+    cat >>$tmpf <<EOF
 update delete $dynhost. A
 update add $dynhost. 300 A $ip4
 update delete $dyndomain. A
@@ -163,31 +163,32 @@ EOF
 
   if $up6; then
     if [[ $ip6 ]]; then
-      cat >>$f <<EOF
+      cat >>$tmpf <<EOF
 update delete $fqdn. AAAA
 update add $fqdn. 60 AAAA $ip6
 EOF
     else
-      cat >>$f <<EOF
+      cat >>$tmpf <<EOF
 update delete $fqdn. AAAA
 EOF
     fi
   fi
 
-  cat >>$f <<EOF
+  cat >>$tmpf <<EOF
 show
 send
 answer
 quit
 EOF
 
-  chronic nsupdate $ip_arg -k /p/c/machine_specific/vps/filesystem/etc/bind/Kb8.nz.*.private <$f || nsupdate_fails=$((nsupdate_fails + 1))
-  sed -i 's/^server .*/server bk.b8.nz/' $f
-  chronic nsupdate $ip_arg -k /p/c/machine_specific/vps/filesystem/etc/bind/Kb8.nz.*.private <$f  || nsupdate_fails=$((nsupdate_fails + 1))
+  chronic nsupdate $ip_arg -k /p/c/machine_specific/vps/filesystem/etc/bind/Kb8.nz.*.private <$tmpf || nsupdate_fails=$((nsupdate_fails + 1))
+  sed -i 's/^server .*/server bk.b8.nz/' $tmpf
+  chronic nsupdate $ip_arg -k /p/c/machine_specific/vps/filesystem/etc/bind/Kb8.nz.*.private <$tmpf  || nsupdate_fails=$((nsupdate_fails + 1))
   if (( nsupdate_fails > nsupdate_fail_limit )); then
     echo error: nsupdate is persistently failing >&2
     exit 1
   fi
+  rm -f $tmpf
 }
 
 loop-main() {
@@ -219,7 +220,7 @@ exit 0
 
 
 #   f=key.b8.nz
-#   cat >$f <<EOF
+#   cat >$tmpf <<EOF
 # key b8.nz. {
 # algorithm HMAC-SHA512;
 # secret "$(awk '$1 == "Key:" {print $2}' Kb8.nz.*.private)";
diff --git a/exim-nn-iptables b/exim-nn-iptables
index 119eaf0..67024c3 100755
--- a/exim-nn-iptables
+++ b/exim-nn-iptables
@@ -17,11 +17,11 @@ trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" exit status: $?, PIPESTATUS: ${P
 # on the systemd automatic restart. Ugh. So, better to use Wants instead
 # and this.
 
-if !/usr/sbin/iptables -C OUTPUT -p tcp -m tcp --dport 25 -o veth1-mail -j REJECT &>/dev/null; then
+if ! /usr/sbin/iptables -C OUTPUT -p tcp -m tcp --dport 25 -o veth1-mail -j REJECT &>/dev/null; then
  /usr/sbin/iptables -I OUTPUT -p tcp -m tcp --dport 25 -o veth1-mail -j REJECT
 fi
 
 
-if !/usr/sbin/ip6tables -C OUTPUT -p tcp -m tcp --dport 25 -o veth1-mail -j REJECT &>/dev/null; then
+if ! /usr/sbin/ip6tables -C OUTPUT -p tcp -m tcp --dport 25 -o veth1-mail -j REJECT &>/dev/null; then
  /usr/sbin/ip6tables -I OUTPUT -p tcp -m tcp --dport 25 -o veth1-mail -j REJECT
 fi
diff --git a/filesystem/etc/nginx/conf.d/rtmp.conf b/filesystem/etc/nginx/conf.d/rtmp.conf
deleted file mode 100644
index 4a47eda..0000000
--- a/filesystem/etc/nginx/conf.d/rtmp.conf
+++ /dev/null
@@ -1,13 +0,0 @@
-# based on https://opensource.com/article/19/1/basic-live-video-streaming-server#comments
-# and https://github.com/arut/nginx-rtmp-module/wiki/Directives
-rtmp {
-    allow publish 127.0.0.1;
-    deny publish all;
-    server {
-        listen 1935;
-        application live {
-            live on;
-            record off;
-        }
-    }
-}
diff --git a/filesystem/etc/systemd/system/icecast2.service.d/override.conf b/filesystem/etc/systemd/system/icecast2.service.d/override.conf
new file mode 100644
index 0000000..26de486
--- /dev/null
+++ b/filesystem/etc/systemd/system/icecast2.service.d/override.conf
@@ -0,0 +1,2 @@
+[Service]
+ExecStartPre=+/b/ds/ic-tmp-setup
diff --git a/filesystem/usr/local/bin/abrowser b/filesystem/usr/local/bin/abrowser
index 365911c..c84a6ae 100755
--- a/filesystem/usr/local/bin/abrowser
+++ b/filesystem/usr/local/bin/abrowser
@@ -16,9 +16,34 @@ PATH=$tmp
 # causes a new browser window to open, even if normally it would open a
 # new tab
 
+
+tmpf=$(mktemp)
+i3-msg -t get_tree | jq -e '.nodes[].nodes[].nodes[].nodes | [.[]] + ( [.[].nodes[]]) | .[] | select(.window_properties.class=="abrowser") | .id' | sort >$tmpf
+
 # prefer abrowser
 if type -P abrowser &>/dev/null; then
-  abrowser "$@"
+  abrowser "$@" &
 else
-  firefox "$@"
+  firefox "$@" &
 fi
+
+# .5 was too fast
+sleep 1
+# debug
+#printf "%s\n" "$*" >> /tmp/a
+if (( $# == 0 )) && ! i3-msg -t get_tree | jq --stream -r 'select(.[1]|scalars!=null) | "\(.[0]|join(".")): \(.[1]|tojson)"'  | grep 'marks.0: "abrowser"$' &>/dev/null; then
+  # explaining this jq nonsense. when the abrowser window starts, it
+  # might be in a vertical split container, and then it is nested down
+  # another level.  the best way I could find to look in both levels was
+  # to get both, then combine them with + (and you have to turn them
+  # into a single array instead of a list of arrays with [.[]], or else
+  # it will add the arrays a bunch of times and give several results.
+  # comm gives us just the new id.
+  id=$(i3-msg -t get_tree | jq -e '.nodes[].nodes[].nodes[].nodes | [.[]] + ( [.[].nodes[]]) | .[] | select(.window_properties.class=="abrowser") | .id' | comm -23 - $tmpf | head -n1)
+  rm -f $tmpf
+  if [[ $id ]]; then
+    i3-msg "[con_id=$id] mark abrowser"
+  fi
+fi
+
+wait
diff --git a/filesystem/usr/local/bin/myupgrade b/filesystem/usr/local/bin/myupgrade
index 7a6c562..f2c5aec 100755
--- a/filesystem/usr/local/bin/myupgrade
+++ b/filesystem/usr/local/bin/myupgrade
@@ -91,7 +91,7 @@ sleep 1
 # isolation instead of as part of bring up and down the whole desktop.
 # But, I'd rather something gets messed up than things not get
 # restarted.
-if ! /sbin/needrestart -p -l &>/dev/null; then
+if ! /sbin/needrestart -p &>/dev/null; then
   if [[ $hn == "$MAIL_HOST" || $hn == kd ]]; then
     # send us an email so we can decide what to do
     needrestart -r l
diff --git a/fixvpndns b/fixvpndns
index a97b2bd..d33797a 100755
--- a/fixvpndns
+++ b/fixvpndns
@@ -16,5 +16,5 @@ if ! resolvectl dnsovertls tunfsf &>/dev/null; then
   # resolvectl dnsovertls tunfsf ||:
   exit 0
 fi
-read _ link _ < <(resolvectl dnsovertls tunfsf)
+read -r _ link _ < <(resolvectl dnsovertls tunfsf)
 busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no
diff --git a/gitslink b/gitslink
index f44f213..0616fe7 100755
--- a/gitslink
+++ b/gitslink
@@ -20,7 +20,7 @@ source /a/bin/lnf/lnf >/dev/null ||:
 
 for x in !(unused|distro-setup|unfinished|queue|bash-template|buildscripts|crons|data|examples|log-quiet); do
   [[ -e $x/.git ]] || continue
-  for y in $x/*; do
+  for y in "$x"/*; do
     f=${y##*/}
     if [[ -x $y && ! -d $y ]]; then
       unset "existing[$f]"
diff --git a/i3-auto-layout-toggle b/i3-auto-layout-toggle
new file mode 100755
index 0000000..e8a464d
--- /dev/null
+++ b/i3-auto-layout-toggle
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+f=/tmp/iank-i3-no-auto
+
+if [[ -e $f ]]; then
+  rm -f $f
+else
+  touch $f
+fi
diff --git a/i3-maybe-double-move b/i3-maybe-double-move
new file mode 100755
index 0000000..3d25c3f
--- /dev/null
+++ b/i3-maybe-double-move
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+direction="$1"
+if i3-msg -t get_tree | jq -e -C '.nodes[].nodes[].nodes[].nodes[] | select((.nodes| length == 1) and (.nodes[0].focused == true))' &>/dev/null; then
+  i3-msg "move $direction; move $direction"
+else
+  i3-msg "move $direction"
+fi
diff --git a/i3-mouse-warp b/i3-mouse-warp
new file mode 100755
index 0000000..99cdacf
--- /dev/null
+++ b/i3-mouse-warp
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+## based on:
+## https://github.com/i3/i3/issues/2971
+
+window=$(xdotool getwindowfocus)
+
+# this brings in variables WIDTH and HEIGHT
+eval "$(xdotool getwindowgeometry --shell $window)"
+
+
+if (( HEIGHT > 100 )); then
+  TX=$(( WIDTH / 2))
+  TY=$(( HEIGHT / 2))
+
+  xdotool mousemove -window $window $TX $TY
+  # iank, original says "Check for height of 100 assumes that anything
+  # less than that means no window", and this condition is for "when I
+  # am navigating to a screen that does not have an open window on it". I don't think
+else
+  rect=$(i3-msg -t get_workspaces | jq -r '.[] | select(.focused==true).rect')
+
+  x=$(jq -r '.x' <<< $rect)
+  y=$(jq -r '.y' <<< $rect)
+  w=$(jq -r '.width' <<< $rect)
+  h=$(jq -r '.height' <<< $rect)
+  TX=$(( x + w / 2))
+  TY=$(( y + h / 2))
+  xdotool mousemove -window $window $TX $TY
+fi
diff --git a/i3-pull b/i3-pull
new file mode 100755
index 0000000..87b7f07
--- /dev/null
+++ b/i3-pull
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# sometimes I want to pull in and sometimes I want to swap.
+
+set -e; . /usr/local/lib/bash-bear; set +e
+
+mark=$1
+h=$(i3-msg -t get_tree | jq -r ".. | select(.focused? == true) | .rect.height")
+
+cur_workspace=$(i3-msg -t get_workspaces | jq -r '.[] | select(.focused? == true) | .name')
+
+
+# 1080 = half the 4k height
+#if [[ $cur_workspace == 1 && $h ]] && (( h <= 1080 )); then
+if [[ $cur_workspace == 1 && $h ]]; then
+  i3-msg "swap container with mark $mark; [con_mark=\"$mark\"] focus"
+else
+  i3-msg '[con_mark="'$mark'"] move workspace current'
+fi
diff --git a/i3-split-maybe b/i3-split-maybe
new file mode 100755
index 0000000..3cedb5c
--- /dev/null
+++ b/i3-split-maybe
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e; . /usr/local/lib/bash-bear; set +e
+
+# We use this along with
+# /a/opt/i3-alternating-layout/alternating_layouts.py to anticipate when
+# we want to split/tab windows. There are 2 options of when to do it:
+# just after a window is created, or just before a window is
+# created. Doing it after a window is created allows you to move a
+# window into the split that only has 1 window, whereas the other way
+# doesn't. For my use cases, I think I don't really want to move it into
+# the split if it is a tabbed split.
+#
+# I have a keybind which disables both, it runs /b/ds/i3-auto-layout-toggle
+
+if [[ -e /tmp/iank-i3-no-auto ]]; then
+  exit 0
+fi
+
+
+tmp=$(mktemp)
+
+i3-msg -t get_workspaces | jq ".[]| select(.focused==true) | .rect | .width" >$tmp
+
+{ read -r screen_width; read -r screen_height; } <$tmp
+
+i3-msg -t get_tree | jq -r ".. | select(.focused? == true).rect | .width, .height" >$tmp
+
+half_w=$(( screen_width / 2 + 100 ))
+half_h=$(( screen_height / 2 + 100 ))
+
+
+{ read -r w; read -r h; } <$tmp
+
+
+if (( screen_width < 1920 )); then
+  # haven't considered this case yet
+  exit 0
+fi
+
+if (( w < half_w && h < half_h )); then
+  i3-msg "split vertical, layout tabbed"
+fi
+
+rm -f $tmp
diff --git a/i3-sway/common.conf b/i3-sway/common.conf
index a658f3b..9e4cedc 100644
--- a/i3-sway/common.conf
+++ b/i3-sway/common.conf
@@ -1,12 +1,25 @@
 ####### DO NOT EDIT LIVE CONFIG. generated from /a/bin/distro-setup/i3-sway/gen #######
 
+# random thoughts: what to do with a window I don't have room for?
+# * I could tabify it
+# * I could split an existing window with it
+# * I could send it away to another workspace,
+# * I could resize it to be very small.
+
+
+# todo: think whether this is useful: https://github.com/tmfink/i3-wk-switch
+# todo: see comment by Jakstern551 here for tip about jumping to windows
+
 # https://i3wm.org/docs/userguide.html#keybindings
 #To get the current mapping of your keys, use xmodmap -pke. To
 #interactively enter a key and see what keysym it is configured to, use
 #xev.
 set $mod Mod4
 
-bindsym $mod+2 exec "pavucontrol"
+# for non-gui apps, use this.
+set $ex exec --no-startup-id
+
+bindsym $mod+2 $ex "/b/ds/i3-split-maybe"; exec "pavucontrol"
 # calling without -no-remote makes this to be the instance that links
 # will open in from other applications.
 bindsym $mod+3 exec "abrowser"
@@ -16,49 +29,59 @@ bindsym $mod+3 exec "abrowser"
 #bindsym $mod+3 exec "abrowser 2>&1 >/tmp/l"
 #bindsym $mod+3 exec "abrowser -no-remote -P sfw"
 bindsym $mod+4 exec "abrowser -no-remote -P firefox-main-profile"
-bindsym $mod+5 exec "/a/bin/ds/laptop-xrandr"
+# todo: figure out a stream delay & way to cut the stream.
+# settings, advanced, stream delay
+bindsym $mod+5 $ex "/a/bin/ds/obs-i3-interlude"
 bindsym $mod+6 exec "/usr/local/bin/start-tor-browser"
-#bindsym $mod+6 exec "/a/bin/redshift.sh"
-# bindsym $mod+equal exec "t s w; t in"
-# bindsym $mod+Home exec "t out"
-# #bindsym $mod+End exec "t s x; t in"
-# bindsym $mod+grave exec "t s lunch; t in; t out -a '45 minutes from now'"
+bindsym $mod+7 $ex "/a/bin/ds/laptop-xrandr"
+#bindsym $mod+6 $ex "/a/bin/redshift.sh"
+# bindsym $mod+equal $ex "t s w; t in"
+# bindsym $mod+Home $ex "t out"
+# #bindsym $mod+End $ex "t s x; t in"
+# bindsym $mod+grave $ex "t s lunch; t in; t out -a '45 minutes from now'"
 
 
 bindsym $mod+1 focus parent
-bindsym $mod+equal exec "dunstctl close-all"
+bindsym $mod+shift+1 focus child
+# undo split: https://github.com/i3/i3/issues/3808
+bindsym $mod+grave floating toggle; floating toggle
+bindsym $mod+equal $ex "dunstctl close-all"
 # move firefox to current workspace.
 # https://i3wm.org/docs/userguide.html#keybindings
 # get class with xprop, example output
 # WM_CLASS(STRING) = "irssi", "URxvt"
 # xprop |& grep WM_CLASS
-bindsym $mod+w [class="abrowser"] move workspace current
+bindsym $mod+w $ex i3-pull abrowser
+bindsym $mod+shift+w fullscreen toggle
 
-bindsym $mod+e fullscreen toggle
-bindsym $mod+r exec "/a/bin/ds/xl"
+bindsym $mod+e $ex i3-pull emacs
+bindsym $mod+shift+e unmark emacs; mark emacs
+bindsym $mod+r $ex "/a/bin/ds/xl"
 # todo, in newer i3, make this toggle split tabbed
-bindsym $mod+t layout toggle split
+bindsym $mod+t layout toggle splith splitv tabbed
 #bindsym $mod+Shift+t move workspace to output up
 bindsym $mod+Shift+t move workspace to output right
-bindsym $mod+g layout tabbed
+# there's a bug about this. it is not logical that there is no "split
+# tabbed", but you accomplish that by doing this.
+bindsym $mod+g split vertical, layout tabbed
+bindsym $mod+shift+g $ex "/b/ds/i3-auto-layout-toggle"
 
 # Use Mouse+$mod to drag floating windows to their wanted position
 floating_modifier $mod
 
-bindsym $mod+u focus left
-bindsym $mod+i focus right
-bindsym $mod+o focus up
-bindsym $mod+p focus down
+bindsym $mod+u focus left; $ex "i3-mouse-warp"
+bindsym $mod+i focus right; $ex "i3-mouse-warp"
+bindsym $mod+o focus up; $ex "i3-mouse-warp"
+bindsym $mod+p focus down; $ex "i3-mouse-warp"
 
-bindsym $mod+Left move left
-bindsym $mod+Right move right
-bindsym $mod+Up move up
-bindsym $mod+Down move down
+bindsym $mod+Left $ex "/a/exe/i3-maybe-double-move left"
+bindsym $mod+Right $ex "i3-maybe-double-move right"
+bindsym $mod+Up $ex "i3-maybe-double-move up"
+bindsym $mod+Down $ex "i3-maybe-double-move down"
 
-# switch to workspace
 bindsym $mod+Shift+a move container to workspace 4
 bindsym $mod+a workspace 4
-# move focused container to workspace
+
 bindsym $mod+Shift+s move container to workspace 3
 bindsym $mod+s workspace 3
 
@@ -78,13 +101,19 @@ bindsym $mod+x workspace 6
 # todo, in newer i3, make this split toggle
 bindsym $mod+v split vertical
 bindsym $mod+Shift+v split horizontal
+#
+## temp for testing, add antying here
+##bindsym $mod+shift+g
+bindsym $mod+b $ex i3-pull term
+bindsym $mod+shift+b unmark term; mark term
+# for use to cleanup extra emacs windows
 # https://faq.i3wm.org/question/7662/reverse-perl-matches-in-criteria-in-i3-config.1.html
 # I found their regex slightly wrong. This is a hacky way to
 # ignore my irc emacs instances, their window titles
 # are irc room names. Another way would be to hack on the
 # window title, or xprop stuff, but I figure I'm switching
 # to wayland soon, lets wait and see how things work there.
-bindsym $mod+b [class="Emacs" title="^(?!#[a-zA-Z][a-zA-Z-]*$)"] move workspace current
+bindsym $mod+shift+6 [class="Emacs" title="^(?!#[a-zA-Z][a-zA-Z-]*$)"] move workspace current
 
 bindsym $mod+c kill
 
@@ -99,32 +128,45 @@ bindsym $mod+8 workspace 9
 bindsym $mod+Shift+9 move container to workspace 10
 bindsym $mod+9 workspace 10
 
-b
-indsym $mod+Shift+m border toggle
+bindsym $mod+Shift+m border toggle
 
 # 65 = space.
-# toggle tiling / floating
-bindcode $mod+Shift+65 floating toggle
+# toggle tiling / floating.
+#
+# The idea here is: when floating a window, make it sticky and 1080p,
+# because the only reason we want to do this is to keep it on screen
+# when doing an obs broacast. When unfloating a window, just act as a
+# normal unfloat. There is a quirk with this: in a layout with 3 windows,
+# 2 stacked, 1 tall, floating and ufloating the tall one will make it
+# another stacked one, but still 1920x1080, you need to move it to the
+# right to get it back into its tall spot. I could automate this,
+# but I'm not bothering right now
+bindcode $mod+65 $ex obs-auto-scene-switch-toggle; floating toggle; sticky enable; resize set 1920 1080; move position 100 ppt 0 ppt
 
 # change focus between tiling / floating windows
-bindcode $mod+65 focus mode_toggle
+bindcode $mod+shift+65 focus mode_toggle
 # Use Mouse+$mod to drag floating windows to their wanted position
 floating_modifier $mod
 
-bindsym $mod+j exec emacsclient -c
-bindsym $mod+k exec konsole
-bindsym $mod+l exec dmenu_run
+bindsym $mod+shift+h $ex clip-hc
+bindsym $mod+j $ex emacsclient -c
+bindsym $mod+shift+j $ex clip-up
+bindsym $mod+k $ex konsole
+bindsym $mod+shift+k $ex mpv --profile=a /a/bin/data/clips/enter-in.flac
+bindsym $mod+l $ex dmenu_run
+bindsym $mod+shift+l $ex mpv --profile=a /a/bin/data/clips/tokyo-eye.flac
+bindsym $mod+shift+semicolon $ex clip-sad
 # note default is 27% on my system76. not sure if these
 # keybinds will screw up other laptop brightness keys.
-bindsym XF86MonBrightnessUp exec brightnessctl s +5%
-bindsym XF86MonBrightnessDown exec brightnessctl s 5%-
+bindsym XF86MonBrightnessUp $ex brightnessctl s +5%
+bindsym XF86MonBrightnessDown $ex brightnessctl s 5%-
 
 # Font for window titles. Will also be used by the bar unless a different font
 # is used in the bar {} block below.
-font pango:monospace 8
+font pango:monospace 7
 
 # todo: only available in newer i3n
-#hide_edge_borders smart
+hide_edge_borders vertical
 
 #exec --no-startup-id /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd
 
@@ -133,10 +175,11 @@ font pango:monospace 8
 
 
 # shortcut to selection widget (primary)
-bindsym $mod+End exec /a/opt/clipster/clipster -sp
+bindsym $mod+End $ex /a/opt/clipster/clipster -sp
 
-# file:///usr/share/doc/i3-wm/userguide.html#_border_style_for_new_windows
-new_window none
+# title bars but no borders. i tried this out a bit
+#default_border normal 0
+default_border pixel 4
 
 # I dont see a way to make processing windows act like normal windows,
 # this does it.
@@ -148,3 +191,7 @@ new_window none
 
 # this is the processing window for my app named focus.
 for_window [class="focus" instance="focus"] floating disable
+
+client.focused          #4c7899 #285577 #ffffff #2e9ef4   #ff4400
+client.focused_inactive #333333 #5f676a #ffffff #484e50   #DBEEF4
+client.unfocused        #333333 #222222 #888888 #292d2e   #B8C8CD
diff --git a/i3-sway/i3.conf b/i3-sway/i3.conf
index c94b51b..aefcee8 100644
--- a/i3-sway/i3.conf
+++ b/i3-sway/i3.conf
@@ -5,6 +5,12 @@ bindsym $mod+Shift+p restart
 
 # need this for kde connect
 bar {
+
+# keep it only on secondary monitor to save space and make for less
+# missing pixes in obs live stream. For docs on this, search "output
+# primary" in the i3 guide.
+output primary
+
 # the builtin prog
 #status_command i3status
 
@@ -18,10 +24,8 @@ font pango:monospace 18
 # i have no need for the tray icons so far
 tray_output primary
 
-# this display is interesting, but I don't use it.
-# if I forget which workspace I'm in, I just tend to
-# toggle between all of them.
-workspace_buttons no
+# I found I didn't need these, but, I'm trying them out again.
+# workspace_buttons no
 }
 
 ## dont want to see this bar for now
@@ -31,6 +35,7 @@ workspace_buttons no
 # workspace_buttons no
 # }
 
-exec copyq
-exec dunst
-exec /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd
+$ex copyq
+$ex dunst
+$ex /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd
+$ex /a/opt/i3-alternating-layout/alternating_layouts.py
diff --git a/iboot b/iboot
index bc84f6c..96ccdb2 100644
--- a/iboot
+++ b/iboot
@@ -1,8 +1,7 @@
 #!/bin/bash
 
 ## in development, meant to be run manually
-
-[[ $EUID == 0 ]] || exec sudo -E "$script" "$@"
+[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
 
 if ! test "$BASH_VERSION"; then echo "error: shell is not bash" >&2; exit 1; fi
 shopt -s inherit_errexit 2>/dev/null ||: # ignore fail in bash < 4.4
@@ -11,8 +10,8 @@ trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" exit status: $?, PIPESTATUS: ${P
 
 set -x
 
-d=(/dev/mapper/crypt_dev*)
-d=${d[0]}
+da=(/dev/mapper/crypt_dev*)
+d=${da[0]}
 
 mount -o subvol=root_trisquelnabia $d /mnt
 
@@ -24,6 +23,6 @@ mount -o bind /dev dev
 mount -o bind /proc proc
 mount -o bind /sys sys
 mkdir -p boot/efi
-mount $(awk '$2 == "/boot/efi" {print $1}' /etc/mtab) boot/efi
+mount "$(awk '$2 == "/boot/efi" {print $1}' /etc/mtab)" boot/efi
 chroot .
 # then run zboot-chroot
diff --git a/ic-tmp-setup b/ic-tmp-setup
new file mode 100755
index 0000000..7ed1d9b
--- /dev/null
+++ b/ic-tmp-setup
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+mkdir -p /t/ic
+chown icecast2:iank /t/ic
+chmod 775 /t/ic
diff --git a/input-setup b/input-setup
index 8085d07..46520b9 100755
--- a/input-setup
+++ b/input-setup
@@ -1,4 +1,5 @@
 #!/bin/bash
+# shellcheck source=/a/bin/ds/.bashrc
 if [[ -s ~/.bashrc ]];then . ~/.bashrc;fi
 set -x
 # Copyright (C) 2016 Ian Kelling
@@ -90,11 +91,11 @@ if set_device_id "Logitech Unifying Device"; then
   xinput --set-prop "$device_id" 'Evdev Middle Button Emulation' 1
 fi
 
-# slow down ploopy trackball, until we recompile firmware
-id=$(xinput list | grep -F 'Ploopy Corporation Trackball Mouse' | sed -rn 's/.*[[:space:]]id=([^[:space:]]*).*/\1/p' ||:)
-if [[ $id ]]; then
-  xinput --set-prop $id  'libinput Accel Speed' -0.9
-fi
+## slow down ploopy trackball, until we recompile firmware
+# id=$(xinput list | grep -F 'Ploopy Corporation Trackball Mouse' | sed -rn 's/.*[[:space:]]id=([^[:space:]]*).*/\1/p' ||:)
+# if [[ $id ]]; then
+#   xinput --set-prop $id  'libinput Accel Speed' -0.9
+# fi
 
 set +x
 exit 0
diff --git a/ip6tables-exim b/ip6tables-exim
index 92ea7a6..276b839 100755
--- a/ip6tables-exim
+++ b/ip6tables-exim
@@ -1,2 +1,2 @@
 #!/bin/bash
-nsenter -t $(systemctl show --property MainPID --value mailnn) -n -m ip6tables "$@"
+nsenter -t "$(systemctl show --property MainPID --value mailnn)" -n -m ip6tables "$@"
diff --git a/iptables-exim b/iptables-exim
index b35a344..1b87af1 100755
--- a/iptables-exim
+++ b/iptables-exim
@@ -1,2 +1,2 @@
 #!/bin/bash
-nsenter -t $(systemctl show --property MainPID --value mailnn) -n -m iptables "$@"
+nsenter -t "$(systemctl show --property MainPID --value mailnn)" -n -m iptables "$@"
diff --git a/keyscript-on b/keyscript-on
index 8f66f7c..98d1548 100755
--- a/keyscript-on
+++ b/keyscript-on
@@ -14,7 +14,6 @@ if [[ $- != *i* ]]; then
   exec &>>/var/log/keyscript-on.log
   echo "$0: starting. $(date)"
 fi
-rootn=1
 
 sed="sed --follow-symlinks"
 
@@ -32,7 +31,7 @@ if [[ $INVOCATION_ID ]]; then
     # exists when /a is unmounted.
     source /dev/shm/iank-status
   fi
-  if [[ $MAIL_HOST && $MAIL_HOST != $HOSTNAME ]]; then
+  if [[ $MAIL_HOST && $MAIL_HOST != "$HOSTNAME" ]]; then
     echo "$0: exiting early: running under systemd as MAIL_HOST"
     exit 0
   fi
diff --git a/mail-cert-cron b/mail-cert-cron
index 99563a9..4202ccb 100755
--- a/mail-cert-cron
+++ b/mail-cert-cron
@@ -13,12 +13,12 @@ esac
 
 f=/a/bin/bash_unpublished/source-state
 if [[ -e $f ]]; then
+  # shellcheck source=/a/bin/bash_unpublished/source-state
   source $f
 fi
 
 case $HOSTNAME in
   $MAIL_HOST|bk)
-    local_mx=mail.iankelling.org
     # ||: is to allow for temporary connection issues.
     rsync "${opt[@]}" -ogtL --chown=root:Debian-exim --chmod=640 \
           root@li.iankelling.org:/etc/letsencrypt/live/mail.iankelling.org/{fullchain.pem,privkey.pem} /etc/exim4 ||:
diff --git a/mount-latest-remote b/mount-latest-remote
index 04ac198..3da3abf 100755
--- a/mount-latest-remote
+++ b/mount-latest-remote
@@ -18,7 +18,8 @@
 
 set -e; . /usr/local/lib/bash-bear; set +e
 
-script_dir=$(dirname $(readlink -f "$BASH_SOURCE"))
+readonly this_file; this_file="$(readlink -f -- "${BASH_SOURCE[0]}")";
+script_dir=${this_file%/*}
 
 if (( ! $# )); then
   echo "mount-latest-remote: error: a host argument"
@@ -49,7 +50,7 @@ if [[ $tg == *:* ]]; then
   rsynctg="[$tg]"
 fi
 # R = relative, t = times, O = omit-dir-times, p = perms
-er rsync -RtOp bin/{mount-latest-subvol,check-subvol-stale} lib/bash-bear "root@$rsynctg:/usr/local" || continue
+er rsync -RtOp bin/{mount-latest-subvol,check-subvol-stale} lib/bash-bear "root@$rsynctg:/usr/local" ||:
 # note: this can hang if we have an old nfs mount.
 er ssh root@$tg timeout -s 9 600 /usr/local/bin/mount-latest-subvol "$@"
 
diff --git a/mount-latest-subvol b/mount-latest-subvol
index dfe65db..af6385e 100644
--- a/mount-latest-subvol
+++ b/mount-latest-subvol
@@ -241,8 +241,8 @@ shopt -s nullglob
 fa=(/mnt/root/btrbk/q.*); f=${fa[0]}
 if [[ -e $f ]]; then
   fstab <<EOF
-$crypt_dev  /q  btrfs  noatime,subvol=q,gid=1000$mopts  0 0
-$crypt_dev  /qd  btrfs  noatime,subvol=qd,gid=1000$mopts  0 0
+$crypt_dev  /q  btrfs  noatime,subvol=q$mopts  0 0
+$crypt_dev  /qd  btrfs  noatime,subvol=qd$mopts  0 0
 /q/p  /p  none  bind$mopts  0 0
 EOF
 fi
@@ -258,7 +258,7 @@ fi
 # fa=(/mnt/root/btrbk/ar.*); f=${fa[0]}
 # if [[ -e $f ]]; then
 #   fstab <<EOF
-# $crypt_dev  /ar  btrfs  noatime,subvol=ar,uid=1000,gid=1000$mopts  0 0
+# $crypt_dev  /ar  btrfs  noatime,subvol=ar,uid=1000$mopts  0 0
 # EOF
 # fi
 
diff --git a/my-update-info-dir b/my-update-info-dir
index f97b598..eeb370d 100755
--- a/my-update-info-dir
+++ b/my-update-info-dir
@@ -12,7 +12,7 @@ for dir in $(emacs --batch --eval '(progn(package-initialize) (dolist (x Info-di
     # this is from /usr/sbin/update-info-dir
     */info)
 
-      find $dir -type f | while read file ; do
+      find $dir -type f | while read -r file ; do
         case $file in
           */dir|*/dir.gz|*/dir.old|*/dir.old.gz|*-[0-9]|*-[0-9].gz|*-[1-9][0-9]|*-[1-9][0-9].gz|*.png|*.jpg)
             # these files are ignored
@@ -28,7 +28,7 @@ for dir in $(emacs --batch --eval '(progn(package-initialize) (dolist (x Info-di
     # ignore relative
     [^/]*) : ;;
     *)
-      for file in $dir/*.info*; do
+      for file in "$dir"/*.info*; do
         echo $file
         sudo install-info "$file" "$INFODIR/dir"
       done
diff --git a/myi3status b/myi3status
index 7183a8f..fbeeabd 100755
--- a/myi3status
+++ b/myi3status
@@ -155,6 +155,13 @@ main() {
     ps_char="=======FOCUS====== $ps_char"
   fi
 
+  if [[ -e /tmp/iank-i3-no-auto ]]; then
+    ps_char="$ps_char I"
+  fi
+  if [[ -e /tmp/no-obs-auto-scene-switch ]]; then
+    ps_char="$ps_char O"
+  fi
+
 
   printf '{ "name":"status", "color":"#ED297D", "full_text": "%s' "$ps_char"
   printf '"},'
diff --git a/nextcloud-setup b/nextcloud-setup
index 5affcd4..3026af7 100755
--- a/nextcloud-setup
+++ b/nextcloud-setup
@@ -21,6 +21,7 @@ i() { # install file
   tmp=$(rsync -ic $tmpdir/"$base" "$dest")
   if [[ $tmp ]]; then
     printf "%s\n" "$tmp"
+    # shellcheck disable=SC2034
     ir=true
     if [[ $dest == /etc/systemd/system/* ]]; then
       touch /var/local/mail-setup-reload
diff --git a/obs b/obs
new file mode 100755
index 0000000..bff119b
--- /dev/null
+++ b/obs
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+
+obs-i3-monitor &
+
+#/usr/bin/sleep 1234 &
+
+# https://obsproject.com/forum/threads/please-safe-mode-or-normal-mode-alert-function-on-off-toggle-add-to-setting.171047/
+/usr/bin/obs --disable-shutdown-check "$@" ||:
+
+kill %%
diff --git a/obs-auto-scene-switch-toggle b/obs-auto-scene-switch-toggle
new file mode 100755
index 0000000..cc067b1
--- /dev/null
+++ b/obs-auto-scene-switch-toggle
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+# the last select is so jq -e will tell us when it isn't found, which is
+# a bit cooler than doing an output comparison... maybe? I'm not sure
+if i3-msg -t get_tree | jq -e '.. | select(.focused? == true) | select(.floating == "user_on")' &>/dev/null; then
+  exit 0
+fi
+
+f=/tmp/no-obs-auto-scene-switch
+
+if [[ -e $f ]]; then
+  rm -f $f
+else
+  touch $f
+fi
diff --git a/obs-i3-interlude b/obs-i3-interlude
new file mode 100755
index 0000000..303dec1
--- /dev/null
+++ b/obs-i3-interlude
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e; . /usr/local/lib/bash-bear; set +e
+
+if [[ -e /tmp/no-obs-auto-scene-switch ]]; then
+  rm -f /tmp/no-obs-auto-scene-switch
+  if [[ -s /tmp/last-obs-i3-mark ]]; then
+    p=$(cat /p/obs-ws-pass)
+    mark=$(cat /tmp/last-obs-i3-mark)
+    obs-cmd -w obsws://localhost:4455/$p scene switch $mark
+  fi
+else
+  touch /tmp/no-obs-auto-scene-switch
+  obs-cmd -w obsws://localhost:4455/$p scene switch interlude
+fi
diff --git a/obs-i3-monitor b/obs-i3-monitor
new file mode 100755
index 0000000..26d17f8
--- /dev/null
+++ b/obs-i3-monitor
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -e; . /usr/local/lib/bash-bear; set +e
+
+try() {
+  "$@" || printf "warning: failed %s\n" "$*"
+}
+
+while true; do
+  while read -r line ; do
+    mark=$(printf "%s\n" "$line" | jq -r 'select (.change == "focus") | .container.marks[0]') ||:
+    if [[ $mark && $mark != null ]]; then
+      echo $mark > /tmp/last-obs-i3-mark
+      if [[ ! -e /tmp/no-obs-auto-scene-switch ]]; then
+        p=$(cat /p/obs-ws-pass)
+        try obs-cmd -w obsws://localhost:4455/$p scene switch $mark
+      fi
+    fi
+    # debugging
+    #printf "%s\n" "$line" | jq
+
+
+    # intentional process substitution to properly exit on kill
+    # ||: avoids error messages
+  done < <(i3-msg -t subscribe -m '[ "window" ]' ||:)
+  sleep 5
+done
diff --git a/pkgs b/pkgs
index f318c4e..345ce58 100644
--- a/pkgs
+++ b/pkgs
@@ -152,6 +152,7 @@ p3=(
   glibc-doc
   goaccess
   gnome-screenshot
+  # color picker
   gpick
   grepmail
   guvcview
@@ -283,6 +284,8 @@ p3=(
   xawtv
   xbacklight
   xdot
+  # needed for some i3 hacks
+  xdotool
   xloadimage
   xprintidle
   xscreensaver
diff --git a/schrootupdate b/schrootupdate
index 182371e..42321c0 100755
--- a/schrootupdate
+++ b/schrootupdate
@@ -5,13 +5,15 @@ trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
 
 [[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
 
-for n in bullseye; do
-  if [[ -e /etc/schroot/chroot.d/$n.conf ]]; then
-    cd /
-    schroot -c $n -- apt-get -y update
-    schroot -c $n -- apt-get -y dist-upgrade --purge --auto-remove
-  fi
-done
+# used to have more than one, leaving commented in case we do
+#for n in bullseye; do
+n=bullseye
+if [[ -e /etc/schroot/chroot.d/$n.conf ]]; then
+  cd /
+  schroot -c $n -- apt-get -y update
+  schroot -c $n -- apt-get -y dist-upgrade --purge --auto-remove
+fi
+#done
 
 # if we haven't upgraded yet
 if [[ ! -d /mnt/boot/debianbullseye_bootstrap ]]; then
diff --git a/ssh-emacs-setup b/ssh-emacs-setup
index 426d098..4fcbc92 100755
--- a/ssh-emacs-setup
+++ b/ssh-emacs-setup
@@ -17,13 +17,14 @@ if [[ $EUID != 0 ]]; then
     sudo "$0"
     exit
 fi
+# shellcheck source=/a/bin/ds/.bashrc
 if [[ -s ~/.bashrc ]];then . ~/.bashrc;fi
 
 
 set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?"' ERR
 
-cd $(dirname $0)
+readonly this_file; this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"; cd ${this_file%/*}
 # get rid of useless motd stuff
 sed -i --follow-symlinks 's/^\s*PrintLastLog .*/PrintLastLog no/' /etc/ssh/sshd_config
 rm -f /etc/update-motd.d/10-help-text /etc/update-motd.d/00-header
diff --git a/subdir_files/.config/mpv/mpv.conf b/subdir_files/.config/mpv/mpv.conf
index 56023b7..9af96a3 100644
--- a/subdir_files/.config/mpv/mpv.conf
+++ b/subdir_files/.config/mpv/mpv.conf
@@ -5,6 +5,7 @@
 loop-file=no
 volume=50
 player-operation-mode=pseudo-gui
+replaygain=track
 
 # use --profile d
 [d]
@@ -19,7 +20,7 @@ shuffle
 
 # audio, especially with beetag
 [a]
-volume=75
+volume=100
 player-operation-mode=cplayer
 audio-display=no
 # dont display any tags
diff --git a/switch-mail-host b/switch-mail-host
index edfc30a..ce1bc86 100644
--- a/switch-mail-host
+++ b/switch-mail-host
@@ -347,7 +347,7 @@ if (( ret )); then
   bang="███████"
   e $bang failed btrbk of /o. restoring old host as primary
   if ! m $old_shell /a/exe/primary-setup localhost; then
-    die due to failed btrbk of /o, we tried to restore old host as primary, but then we failed at that too. To resolve: Fix & rerun switch-mail-host, or fix and rerun primary-setup localhost on old shell so you have a working mail server and then rerun switch-mail-host.
+    die "due to failed btrbk of /o, we tried to restore old host as primary, but then we failed at that too. To resolve: Fix & rerun switch-mail-host, or fix and rerun primary-setup localhost on old shell so you have a working mail server and then rerun switch-mail-host."
   fi
   e finished restoring old host as primary, now exiting $ret due to earlier failed btrbk of /o.
   exit $ret
diff --git a/system-status b/system-status
index dd327bb..f9b0eee 100755
--- a/system-status
+++ b/system-status
@@ -390,17 +390,20 @@ write-status() {
   fi
 
   mprom=/var/lib/prometheus/node-exporter/mailtest-check.prom
-  if grep -qE 'mailtest_check_(unexpected|missing).*[^ ][^0]$' $mprom; then
-    chars+=("MTEST_SPAM")
-  fi
-  mtest_found=false
-  for t in $(grep -E ^mailtest_check_last_usec $mprom | awk '{print $NF}'); do
-    if (( t + 60 * 20 < EPOCHSECONDS )); then
-      mtest_found=true
+  if [[ -s $mprom ]]; then
+    if grep -qE 'mailtest_check_(unexpected|missing).*[^ ][^0]$' $mprom; then
+      chars+=("MTEST_SPAM")
+    fi
+    mtest_found=false
+    # shellcheck disable=SC2013 # these are words
+    for t in $(grep -E ^mailtest_check_last_usec $mprom | awk '{print $NF}'); do
+      if (( t + 60 * 20 < EPOCHSECONDS )); then
+        mtest_found=true
+      fi
+    done
+    if $mtest_found; then
+      chars+=("MTEST_AGE")
     fi
-  done
-  if $mtest_found; then
-    chars+=("MTEST_AGE")
   fi
 
   if [[ ! -e $status_file || -w $status_file ]]; then
diff --git a/trusted-network b/trusted-network
index 825604e..755fb1f 100755
--- a/trusted-network
+++ b/trusted-network
@@ -8,8 +8,6 @@
 
 source /a/bin/bash-bear-trap/bash-bear
 
-readonly this_file=$(readlink -f -- "${BASH_SOURCE[0]}")
-readonly this_dir="${this_file%/*}"
 script_name="${BASH_SOURCE[0]}"
 script_name="${script_name##*/}"
 
@@ -92,7 +90,7 @@ fi
 
 
 # wait for networkmanager to come back
-for f in {1..20}; do
+for ((i=0; i<10; i++)); do
   if read -r _ _ _ _  gateway_if _ < <(ip route get 8.8.8.8); then
     break
   fi
diff --git a/vpn-mail-forward b/vpn-mail-forward
index f6ff168..9331d00 100755
--- a/vpn-mail-forward
+++ b/vpn-mail-forward
@@ -9,7 +9,7 @@ ifname=$1
 shift
 
 # wait up to 10 seconds for the gateway to appear
-for i in in {1..10}; do
+for ((i=0; i<10; i++)); do
   gw=$(/usr/sbin/ip route | sed -rn 's/^default via .* dev (\S+).*/\1/p')
   if [[ $gw ]]; then
     found=true
diff --git a/vpn-static-ip b/vpn-static-ip
index bef7933..8ecce1c 100755
--- a/vpn-static-ip
+++ b/vpn-static-ip
@@ -15,7 +15,7 @@ conf=$1
 main() {
   while read -r host port; do
     while read -r ip; do
-      echo $ip | egrep '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' &>/dev/null || continue
+      echo $ip | grep -E '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' &>/dev/null || continue
       printf "remote %s %s\n" "$ip" "$port" >>$conf
       ret=0
     done < <(timeout -s 9 1 dig +short $host ||:)
diff --git a/zboot b/zboot
index bf9c7c2..082afb5 100755
--- a/zboot
+++ b/zboot
@@ -1,7 +1,6 @@
 #!/bin/bash
 
-script=$(readlink -f -- "$BASH_SOURCE")
-[[ $EUID == 0 ]] || exec sudo -E "$script" "$@"
+[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
 
 set -e; . /usr/local/lib/bash-bear; set +e
 
@@ -33,7 +32,7 @@ mount -o bind /dev dev
 mount -o bind /proc proc
 mount -o bind /sys sys
 mkdir -p boot/efi
-mount $(awk '$2 == "/boot/efi" {print $1}' /etc/mtab) boot/efi
+mount "$(awk '$2 == "/boot/efi" {print $1}' /etc/mtab)" boot/efi
 
 cp /b/ds/zboot-chroot ./root
 
diff --git a/zboot-chroot b/zboot-chroot
index 4411cc4..0a68208 100755
--- a/zboot-chroot
+++ b/zboot-chroot
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-[[ $EUID == 0 ]] || exec sudo -E "$script" "$@"
+[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
 
 if ! test "$BASH_VERSION"; then echo "error: shell is not bash" >&2; exit 1; fi
 shopt -s inherit_errexit 2>/dev/null ||: # ignore fail in bash < 4.4
diff --git a/ziva-backup-check b/ziva-backup-check
index 5c2ed56..b646d21 100755
--- a/ziva-backup-check
+++ b/ziva-backup-check
@@ -10,7 +10,7 @@ pre="${0##*/}:"
 err() { echo "[$(date +'%Y-%m-%d %H:%M:%S%z')]: $pre: $*" >&2; }
 
 ## begin check on syncthing
-if ! systemctl show --no-page syncthing@ziva | sed -n 's/^MainPID=//p' | egrep '^[0-9]+$' &>/dev/null; then
+if ! systemctl show --no-page syncthing@ziva | sed -n 's/^MainPID=//p' | grep -E '^[0-9]+$' &>/dev/null; then
   err no pid for syncthing@ziva. systemctl status:
   systemctl status syncthing@ziva
 fi
@@ -34,16 +34,16 @@ for prefix in root boot; do
     break
   fi
 
-  read last_snap_sec last_snap < <(
+  read -r last_snap_sec last_snap < <(
     for s in ${snaps[@]}; do
       f=${s##*/}
-      unix_time=$(date -d $(sed -r  's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<${f#$vol.}) +%s)
+      unix_time=$(date -d "$(sed -r  's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<${f#"$vol".})" +%s)
       printf "%s %s\n" $unix_time $s # part of the pipeline
     done | sort -r | head -n 1 ||:
   )
   if [[ ! $last_snap ]]; then
     # should not happen.
-    err "could not find latest snapshot for $svp among ${snaps[*]}"
+    err "could not find latest snapshot for $vol among ${snaps[*]}"
     exit 1
   fi
   if (( last_snap_sec < EPOCHSECONDS - age_limit_sec )); then
diff --git a/ziva-screen b/ziva-screen
index 646ae91..ac875c0 100755
--- a/ziva-screen
+++ b/ziva-screen
@@ -20,7 +20,7 @@ cd $dest_dir
 shopt -s nullglob
 jpgs=( 20*jpg )
 if (( ${#jpgs[@]} >= 1 )); then
-  # shellcheck disable=SC2048 # intentional
+  # shellcheck disable=SC2012 # this is much sorter than find|sort
   lastf=$(ls -1 20*jpg  | tail -n1)
 fi
 
-- 
2.30.2