icecast improvements
[distro-setup] / brc2
diff --git a/brc2 b/brc2
index 3f953c1dc8c001b4bc110b9ebfc554efbb352066..104907e67c76f2d4df1f737ff986a3300d9db381 100644 (file)
--- a/brc2
+++ b/brc2
@@ -29,9 +29,29 @@ if [[ $LESSHISTFILE == - ]]; then
   HISTFILE=
   c() { cd "$@"; }
 elif [[ $HISTFILE ]]; then
   HISTFILE=
   c() { cd "$@"; }
 elif [[ $HISTFILE ]]; then
-  HISTFILE=$HOME/.bh
+  # use an alternate history file when we are streaming.
+  if [[ -e $HOME/.iank-stream-on ]]; then
+    HISTFILE=/a/bin/data/stream_hist
+  else
+    HISTFILE=$HOME/.bh
+  fi
 fi
 
 fi
 
+# history personal
+hip() {
+  history -c
+  HISTFILE=$HOME/.bh
+  history -r
+}
+
+# history for streaming
+his() {
+  history -c
+  HISTFILE=/a/bin/data/stream_hist
+  history -r
+}
+
+
 source /a/bin/distro-setup/path-add-function
 path-add /a/exe
 # add this with absolute paths as needed for better security
 source /a/bin/distro-setup/path-add-function
 path-add /a/exe
 # add this with absolute paths as needed for better security
@@ -83,15 +103,6 @@ else
   export NOT_MAIL_HOST_P=t
 fi
 
   export NOT_MAIL_HOST_P=t
 fi
 
-
-source /a/bin/log-quiet/logq-function
-
-# not used
-# if [[ -s /a/opt/alacritty/extra/completions/alacritty.bash ]]; then
-#   source /a/opt/alacritty/extra/completions/alacritty.bash
-# fi
-
-
 source /a/bin/ds/beet-data
 
 
 source /a/bin/ds/beet-data
 
 
@@ -181,7 +192,7 @@ slemacs() {
 
 sle() { # sl emacs
   local f=/home/iank/.emacs.d/init.el
 
 sle() { # sl emacs
   local f=/home/iank/.emacs.d/init.el
-  sl --sl-test-cmd ". /etc/os-release ; printf %s \${VERSION//[^a-zA-Z0-9]/}; test -e $f && stat -c%Y $f" --sl-test-hook slemacs "$@"
+  sl --sl-test-cmd "sed -rn '/^VERSION=/{s/^.*=//;s/[^[:alnum:]]//gp}' /etc/os-release; test -e $f && stat -c%Y $f" --sl-test-hook slemacs "$@"
 }
 ccomp ssh sle
 
 }
 ccomp ssh sle
 
@@ -338,10 +349,16 @@ tback() {
 # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
 
 edelayoff() {
 # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
 
 edelayoff() {
-  echo all >/etc/exim4/no-delay-eximids
+  echo all >/var/spool/exim4/gw/.no-delay-eximids
+  if [[ $EUID == 0 ]]; then
+    chown iank:iank /var/spool/exim4/gw/.no-delay-eximids
+  fi
 }
 edelayon() {
 }
 edelayon() {
-  echo >/etc/exim4/no-delay-eximids
+  echo >/var/spool/exim4/gw/.no-delay-eximids
+  if [[ $EUID == 0 ]]; then
+    chown iank:iank /var/spool/exim4/gw/.no-delay-eximids
+  fi
 }
 
 eqgo() {
 }
 
 eqgo() {
@@ -450,7 +467,7 @@ ralerts() { # remote alerts
 ap() {
   # pushd in case current directory has an ansible.cfg file
   pushd /a/xans >/dev/null
 ap() {
   # pushd in case current directory has an ansible.cfg file
   pushd /a/xans >/dev/null
-  ansible-playbook -v -l ${1:- $(hostname -f)} site.yml
+  ansible-playbook -v -i ${1:- $(hostname -f)}, site.yml
   popd >/dev/null
 }
 aw() {
   popd >/dev/null
 }
 aw() {
@@ -840,9 +857,10 @@ mpvrpc-loadfile() {
 # q quit
 # ret next
 #
 # q quit
 # ret next
 #
+# todo: enter should also unpause
 beetag()  {
   local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random path
 beetag()  {
   local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random path
-  local do_rare_genres read_wait help line lsout tmp ls_line skip_lookback
+  local do_rare_genres read_wait line lsout tmp ls_line skip_lookback
   local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec
   local pl_state_path pl_state_dir pl_state_file tmpstr
   local new_random pl_seed_path seed_num seed_file fmt first_play repeat1
   local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec
   local pl_state_path pl_state_dir pl_state_file tmpstr
   local new_random pl_seed_path seed_num seed_file fmt first_play repeat1
@@ -907,7 +925,7 @@ beetag()  {
   else
     pl_state_file=sorted
   fi
   else
     pl_state_file=sorted
   fi
-  pl_state_dir=/i/info/pl-state
+  pl_state_dir=/b/data/pl-state
   if [[ $playlist ]]; then
     pl_state_dir=$pl_state_dir/$playlist
   else
   if [[ $playlist ]]; then
     pl_state_dir=$pl_state_dir/$playlist
   else
@@ -1512,7 +1530,6 @@ btrbk-host-debug() {
 
 # $ dig ns1.gnu.org @b0.org.afilias-nst.org.
 
 
 # $ dig ns1.gnu.org @b0.org.afilias-nst.org.
 
-# todo: make sm pull/push use systemd instead of the journal cat command
 bbk() { # btrbk wrapper
   local ret=0
   c /
 bbk() { # btrbk wrapper
   local ret=0
   c /
@@ -1691,11 +1708,9 @@ jdo() {
   if [[ $cmd != /* ]]; then
     cmd=$(type -P "$cmd")
   fi
   if [[ $cmd != /* ]]; then
     cmd=$(type -P "$cmd")
   fi
+  #note date format for since is date '+%F %T'
   # -q = quiet
   # -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
+  journalctl --since=now -qn2 -f -u "$cmd_name" &
   jr_pid=$!
   # note, we could have a version that does system --user, but if for example
   # it does sudo ssh, that will leave a process around that we can't kill
   jr_pid=$!
   # note, we could have a version that does system --user, but if for example
   # it does sudo ssh, that will leave a process around that we can't kill
@@ -1760,7 +1775,13 @@ lipush() {
   local p a
   # excluding emacs for now
   #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
   local p a
   # excluding emacs for now
   #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
-  p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter /a/opt/fpaste)
+  p=(
+    /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts}
+    /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter
+    /a/opt/fpaste
+    /p/c/user-specific/www-data/icecast-fsf{,-tech}-htpasswd
+    /p/c/icecast.xml
+  )
   a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
   ret=0
   for h in li je bk; do
   a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
   ret=0
   for h in li je bk; do
@@ -1791,21 +1812,13 @@ bindpush() {
   dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
   lipush
   for h in li bk; do
   dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
   lipush
   for h in li bk; do
-    e sshing $h
-    ssh $h.b8.nz <<'EOF'
-source ~/.bashrc
-m dnsup
-EOF
+    m ssh iank@$h.b8.nz dnsup
   done
 }
 bindpushb8() {
   lipush
   for h in li bk; do
   done
 }
 bindpushb8() {
   lipush
   for h in li bk; do
-    e sshing $h
-    ssh $h.b8.nz <<'EOF'
-source ~/.bashrc
-m dnsb8
-EOF
+    m ssh $h.b8.nz dnsb8
   done
 }
 
   done
 }
 
@@ -1816,8 +1829,18 @@ dnsup() {
 dnsb8() {
   local f=/var/lib/bind/db.b8.nz
   m ser stop named
 dnsb8() {
   local f=/var/lib/bind/db.b8.nz
   m ser stop named
-  m sleep 1
-  m sudo rm -fv $f.jnl $f.signed.jnl
+  # jbk is like a temp file. dunno if removing it helps
+
+  i=0
+  while pgrep '^named$' &>/dev/null; do
+    sleep .5
+    i=$(( i + 1 ))
+    if (( i > 100 )); then
+      echo "dnsb8: error: timeout waiting for named to exit"
+      return 1
+    fi
+  done
+  m sudo rm -fv $f.jnl $f.signed.jnl $f.jbk
   m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f
   m ser restart named
 }
   m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f
   m ser restart named
 }
@@ -1861,7 +1884,13 @@ dsign() {
 # set day start for use in other programs.
 # expected to do be in a format like 830, or 800 or 1300.
 ds() {
 # set day start for use in other programs.
 # expected to do be in a format like 830, or 800 or 1300.
 ds() {
+  local regex
+  regex='[0-9]?[0-9]?[0-9][0-9]'
   if [[ $1 ]]; then
   if [[ $1 ]]; then
+    if [[ ! $1 =~ $regex ]]; then
+      echo "ds: error. expected \$1 to match $regex, got \$1: $1"
+      return 1
+    fi
     echo $1 >/b/data/daystart
   else
     cat /b/data/daystart
     echo $1 >/b/data/daystart
   else
     cat /b/data/daystart
@@ -1907,68 +1936,12 @@ satoshi() { # $1 satoshi in usd
 # 2023). However, in 2024-02, I ran a backup where a receiving machine
 # had the wallet enabled and there was no error, so I don't know if this
 # is still an issue or likely it is an inconsistent behavior.
 # 2023). However, in 2024-02, I ran a backup where a receiving machine
 # had the wallet enabled and there was no error, so I don't know if this
 # is still an issue or likely it is an inconsistent behavior.
+# Note: a pruned node won't allow for a wallet to be added, super lame
+# so i'm just not running a bitcoin node for now.
+# Error: Prune: last wallet synchronisation goes beyond pruned data. You
+# need to -reindex (download the whole blockchain again in case of
+# pruned node)
 #
 #
-# As a workaround, this function is for enabling the wallet when I want
-# to use it and leave it disabled otherwise.
-walleton() {
-  local active
-  active=false
-  no_on=true
-  if [[ ! $(readlink -f /var/lib/bitcoind/wallets) == /q/wallets ]]; then
-    if systemctl --quiet is-active bitcoind; then
-      if [[ -e /tmp/no-bitcoinon ]]; then
-        no_on=true
-      else
-        if [[ $EUID == 0 ]]; then
-          m install -T -o iank -g iank /dev/null /tmp/no-bitcoinon
-        else
-          m touch /tmp/no-bitcoinon
-        fi
-      fi
-      active=true
-      m ser stop bitcoind
-    fi
-    m s ln -s /q/wallets /var/lib/bitcoind
-    sudo chown -h bitcoin:bitcoin /var/lib/bitcoind/wallets
-    if $active; then
-      m ser start bitcoind
-      if ! $no_on; then
-        m rm /tmp/no-bitcoinon
-      fi
-    fi
-  fi
-}
-walletoff() {
-  local active
-  active=false
-  no_on=true
-  if [[ $(readlink -f /var/lib/bitcoind/wallets) == /q/wallets ]]; then
-    if systemctl --quiet is-active bitcoind; then
-      if [[ -e /tmp/no-bitcoinon ]]; then
-        no_on=true
-      else
-        if [[ $EUID == 0 ]]; then
-          m install -T -o iank -g iank /dev/null /tmp/no-bitcoinon
-        else
-          m touch /tmp/no-bitcoinon
-        fi
-      fi
-      active=true
-      m ser stop bitcoind
-    else
-      echo note: bitcoind not active
-    fi
-    m rm /var/lib/bitcoind/wallets
-    if $active; then
-      # note, starting bitcoin always fails, but it actually
-      # succeeds. But this is strangely not consistent.
-      m ser start bitcoind
-      if ! $no_on; then
-        m rm /tmp/no-bitcoinon
-      fi
-    fi
-  fi
-}
 #### end bitcoin related things
 
 
 #### end bitcoin related things
 
 
@@ -1996,22 +1969,16 @@ capache()
 
 
 
 
 
 
-
-
-apache-apply() {
-  for file; do
-    if head -n1 "$file"| grep -E '^#!/bin/bash\b' &>/dev/null; then
-      {
-        head -n1 "$file"
-        # First paragraph is to avoid people being confused about why a
-        # file is apache licensed.
-        cat <<'EOF'
+apache-header() {
+  # First paragraph is to avoid people being confused about why a
+  # file is apache licensed.
+  cat <<'EOF'
 # 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
 # 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.
+# programs. If a small program grows beyond 300 lines, I plan to change
+# to a recommended GPL license.
 
 # Copyright 2024 Ian Kelling
 
 
 # Copyright 2024 Ian Kelling
 
@@ -2028,14 +1995,46 @@ apache-apply() {
 # limitations under the License.
 
 EOF
 # limitations under the License.
 
 EOF
+
+}
+
+# apply apache to git tracked bash files + README, except files with A?GPL3 header.
+apache-apply-repo() {
+  for f in $(git ls-files); do
+    [[ -L $f || ! -f $f ]] && continue
+    if [[ $f != README ]]; then
+      if ! grep -n '^#!/bin/bash' $f | grep ^1: &>/dev/null; then continue; fi
+      if  head -n 10 $f | grep 'it under the terms of the GNU General Public License as published by' &>/dev/null; then continue; fi
+    fi
+    apache-apply $f
+  done
+}
+
+apache-apply() {
+  for file; do
+    if [[ ! -e $file ]]; then
+      echo '#!/bin/bash' >$file
+      chmod +x $file
+    fi
+    if head -n1 "$file"| grep -E '^#!/' &>/dev/null; then
+      {
+        head -n1 "$file"
+        apache-header
         tail -n+2 "$file"
       } | sponge "$file"
         tail -n+2 "$file"
       } | sponge "$file"
+    else
+      {
+        apache-header
+        cat "$file"
+      } | sponge "$file"
     fi
   done
 }
     fi
   done
 }
+# strip out the apache license from a file.
 apache-strip() {
 apache-strip() {
+  # shellcheck disable=SC2044 # meh
   for f in $(find . -type f -maxdepth 1); do if head -n1 "$f"| grep -E '^#!/bin/bash\b' &>/dev/null; then { head -n 20 $f | tac | sed '/^# limitations under the License.$/,/^# Copyright.*Ian Kelling$/d' | tac; tail -n+21 $f; } |sponge $f; fi ; done
   for f in $(find . -type f -maxdepth 1); do if head -n1 "$f"| grep -E '^#!/bin/bash\b' &>/dev/null; then { head -n 20 $f | tac | sed '/^# limitations under the License.$/,/^# Copyright.*Ian Kelling$/d' | tac; tail -n+21 $f; } |sponge $f; fi ; done
-  }
+}
 
 chrome() {
   if type -p chromium &>/dev/null; then
 
 chrome() {
   if type -p chromium &>/dev/null; then
@@ -2137,7 +2136,7 @@ fastboot() {
 
 kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
 
 
 kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
 
-bat() {
+batp() {
   cat /sys/class/power_supply/BAT0/capacity
 }
 
   cat /sys/class/power_supply/BAT0/capacity
 }
 
@@ -2324,8 +2323,8 @@ rename-test() {
   # test whether missing files were renamed, generally for use with fsdiff
   # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir
   # echos non-renamed files
   # test whether missing files were renamed, generally for use with fsdiff
   # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir
   # echos non-renamed files
-  local x y found
-  unset sums
+  local x line found renamed
+  local -a sums
   for x in "$2"/*; do
     { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null
   done
   for x in "$2"/*; do
     { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null
   done
@@ -2377,8 +2376,8 @@ gup() { /a/f/gnulib/build-aux/gnupload "$@"; }
 
 dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
 
 
 dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
 
+# do git status on published repos.
 hstatus() {
 hstatus() {
-  # do git status on published repos.
   c /a/bin/githtml
   for x in *; do
     cd "$(readlink -f $x)"/..
   c /a/bin/githtml
   for x in *; do
     cd "$(readlink -f $x)"/..
@@ -2392,6 +2391,16 @@ hstatus() {
   done
 }
 
   done
 }
 
+hsk() {
+  local x
+  c /a/bin/githtml
+  for x in *; do
+    cd "$(readlink -f $x)"/..
+    skgit
+    cd /a/bin/githtml
+  done
+}
+
 ## work log
 #
 # note: database location is specified in ~/.timetrap.yml, currently /p/.timetrap.db
 ## work log
 #
 # note: database location is specified in ~/.timetrap.yml, currently /p/.timetrap.db
@@ -2541,15 +2550,21 @@ ilog-local() {
     cd $d$n/"$chan"
     hr
     for x in *; do
     cd $d$n/"$chan"
     hr
     for x in *; do
-      echo $x; sed "s/^./${x%log}/" $x; hr;
+      # *** are parts and joins and such, and they make reading hard.
+      # I probably will want to see them sometimes, just have to
+      # remove that part.
+      echo $x; sed "s/^./${x%log}/;/\*\*\*/d" $x; hr;
     done
   done
 }
 ilog() {
     done
   done
 }
 ilog() {
-  local chan
+  local chan tmpf
+  tmpf=$(mktemp)
   chan="${1:-#fsfsys}"
   # use * instead of -r since that does sorted order
   chan="${1:-#fsfsys}"
   # use * instead of -r since that does sorted order
-  sl root@iankelling.org ilog-local "$chan" | less +G
+  sl root@li.b8.nz ilog-local "$chan" > $tmpf
+  less +G $tmpf
+  rm -f $tmpf
 }
 
 o() {
 }
 
 o() {
@@ -2604,22 +2619,148 @@ wgkey() {
   umask $umask_orig
 }
 
   umask $umask_orig
 }
 
-declare -A vpn_ips
-vpn_ips[kd]=2
-# note: 1, 4, 5 are occupied by mail wireguard
-vpn_ips[x3]=8
-vpn_ips[sy]=12
-vpn_ips[x2]=13
-vpn_ips[kw]=27
-vpn_ips[bo]=28
-vpn_ips[frodo]=34
-vpn_ips[s23b]=49
+host-info-all() {
+  host-info-update
+
+  bindpushb8
+  # for wireguard configs
+  ssh iank@li.b8.nz conflink
+  wrt-setup
+}
+
+
+
+
+
+## for updating host info like ip, location, update /p/c/host-info and
+## host_info below. the host_info array should probably be in its own
+## file that gets sourced so that it can be more easily updated.
+
+# todo: this is so long that it becomes confusing,
+# try to split it up.
+#
+# To make some changes take effect, run host-info-all.
+host-info-update() {
+
+  local -A vpn_ips host_ips host_macs portfw_ips nonvpn_ips all_ips
+  local -a root_hosts nonroot_hosts
+
+  # the hosts with no mac
+  root_hosts=( bk je li b8.nz )
+  for h in ${root_hosts[@]}; do
+    root_hosts+=(${h}ex)
+  done
+  root_hosts+=(cmc)
+
+  while read -r ip host mac opts; do
+    if [[ $ip == *#*  || ! $host ]]; then continue; fi
+
+    # opt parsing
+    vpn=false
+    root=false
+    for opt in $opts; do
+      case $opt in
+        user=root)
+          root=true
+          ;;
+        vpn)
+          vpn=true
+          ;;
+      esac
+    done
+
+    all_ips[$host]=$ip
+    if $vpn; then
+      portfw_ips[$host]=$ip
+      vpn_ips[$host]=$ip
+    else
+      nonvpn_ips[$host]=$ip
+    fi
+    if $root; then
+      # note: the reason we have b8.nz suffix here but not for non_root
+      # hosts is that it is for the User part, the IdentityFile part is
+      # redundant to *.b8.nz. Also note ${host}i, we only setup those for vpn hosts, but there is no harm in overspecifying here.
+      root_hosts+=($host ${host}i $host.b8.nz ${host}i.b8.nz)
+      # shellcheck disable=SC2004 # false positive
+      root_hosts_a[$host]=t  # a for associative array
+    else
+      nonroot_hosts+=($host ${host}i)
+    fi
+    host_ips[$host]=$ip
+    if [[ $mac ]]; then
+      host_macs[$host]=$mac
+    fi
+
+  done </p/c/host-info
+
+  {
+    cat <<EOF
+Host ${nonroot_hosts[@]}
+User iank
+IdentityFile ~/.ssh/home
+
+Host ${root_hosts[@]}
+IdentityFile ~/.ssh/home
+
+EOF
+    for host in ${!vpn_ips[@]}; do
+      ipsuf=${vpn_ips[$host]}
+      cat <<EOF
+Host ${host}i ${host}i.b8.nz
+Port $((2200 + ipsuf))
+EOF
+    done
+
+    # convenience of one auth key entry
+    for host in ${!all_ips[@]}; do
+      cat <<EOF
+Host $host ${host}i $host.b8.nz ${host}i.b8.nz
+HostKeyAlias $host.b8.nz
+EOF
+    done
+  } | cedit -e /p/c/subdir_files/.ssh/config-static
+
+  {
+    # hack to please emacs parser
+    here_begin="cat <<EOF"
+    echo "$here_begin"
+    for host in ${!vpn_ips[@]}; do
+      ipsuf=${vpn_ips[$host]}
+      i_port=$(( 2200 + ipsuf ))
+      cat <<EOF
+config redirect
+option name ssh$host
+option src              wan
+option src_dport        $i_port
+option dest_port        22
+option dest_ip          \$l.$ipsuf
+option dest             lan
+config rule
+option src              wan
+option target           ACCEPT
+option dest_port        $i_port
+EOF
+    done
+    echo "EOF"
+  } >/p/c/cmc-firewall-data
+
 
 
-vpn-ips-update() {
   local host ipsuf f files
   local host ipsuf f files
+
+  # shellcheck disable=SC2016 # shellcheck doesnt know this is sed
+  sedi '/edits below here are made automatically/,$d' /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf
   for host in ${!vpn_ips[@]}; do
   for host in ${!vpn_ips[@]}; do
+    if [[ ${root_hosts_a[$host]} ]]; then
+      # root machines dont actually need vpn, but
+      # the classification still helps with other
+      # configurations.
+      continue
+    fi
     ipsuf=${vpn_ips[$host]}
     wghole $host $ipsuf
     ipsuf=${vpn_ips[$host]}
     wghole $host $ipsuf
+    u /b/ds/machine_specific/li/filesystem/etc/openvpn/client-config-hole/$host <<EOF
+ifconfig-push 10.5.5.${vpn_ips[$host]} 255.255.255.0
+EOF
     u /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service <<EOF
 [Unit]
 Description=OpenVPN tunnel for %I
     u /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service <<EOF
 [Unit]
 Description=OpenVPN tunnel for %I
@@ -2661,15 +2802,41 @@ EOF
   done
 
   {
   done
 
   {
-    for host in ${!vpn_ips[@]}; do
-      ipsuf=${vpn_ips[$host]}
-      cat <<EOF
-local-data-ptr: "10.2.0.$ipsuf $host.b8.nz"
-EOF
+    echo "cat <<EOF"
+    for host in ${!host_ips[@]}; do
+      ipsuf=${host_ips[$host]}
+      # shellcheck disable=SC2016 # intentional
+      echo 'local-data-ptr: "$l.'$ipsuf $host.b8.nz'"'
     done
     done
-  } | u /b/ds/ptr-data
+    echo "EOF"
+  } | u /p/c/ptr-data
 
   {
 
   {
+    echo "cat <<EOF"
+    for host in ${!host_macs[@]}; do
+      ipsuf=${host_ips[$host]}
+      echo "dhcp-host=${host_macs[$host]},set:$host,\$l.$ipsuf,$host"
+    done
+    echo "EOF"
+  } | u /p/c/dnsmasq-data
+
+
+  b8_ip=$(dig +short b8.nz @iankelling.org | tail -1)
+  # if our dynamic ip updates broke, set manually, eg:
+  #b8_ip=72.74.193.xxx
+  if [[ ! $b8_ip ]]; then
+    echo "$0: error: got empty b8.nz ip. returning 1"
+    return 1
+  fi
+  {
+    cat <<EOF
+@      A       $b8_ip
+i      A       $b8_ip
+EOF
+    for host in ${!nonvpn_ips[@]}; do
+      ipsuf=${nonvpn_ips[$host]}
+      echo "$host      A       10.2.0.$ipsuf"
+    done
     for host in ${!vpn_ips[@]}; do
       ipsuf=${vpn_ips[$host]}
       cat <<EOF
     for host in ${!vpn_ips[@]}; do
       ipsuf=${vpn_ips[$host]}
       cat <<EOF
@@ -2677,38 +2844,132 @@ $host  A       10.2.0.$ipsuf
 ${host}wg      A       10.8.0.$ipsuf
 ${host}vp      A       10.5.5.$ipsuf
 ${host}tr      A       10.174.$ipsuf.2
 ${host}wg      A       10.8.0.$ipsuf
 ${host}vp      A       10.5.5.$ipsuf
 ${host}tr      A       10.174.$ipsuf.2
+${host}i       CNAME   i.b8.nz.
 EOF
     done
 EOF
     done
-  } | cedit vpn-ips-update /p/c/machine_specific/vps/bind-initial/db.b8.nz ||:
+  } | cedit -e vpn-ips-update /p/c/machine_specific/vps/bind-initial/db.b8.nz
 
 
   echo checking for stray files:
 
 
 
   echo checking for stray files:
 
-  initial_dir=$PWD
-  cd /a/bin/ds/machine_specific
-  ngset
-  files=( */filesystem/etc/systemd/system/openvpn-client-tr@.service )
-  ngreset
-  cd $initial_dir
+  initial_dir="$PWD"
+  while read -r dir path; do
+    cd $dir
+    ngset
+    files=( */$path )
+    ngreset
+    cd "$initial_dir"
+    for f in "${files[@]}"; do
+      host=${f%%/*}
+      if [[ ! ${vpn_ips[$host]} ]]; then
+        e rm $dir/$f
+      fi
+    done
+  done <<'EOF'
+/a/bin/ds/machine_specific filesystem/etc/systemd/system/openvpn-client-tr@.service
+/p/c/machine_specific filesystem/etc/wireguard/wghole.conf
+EOF
+
+  files=( /b/ds/machine_specific/li/filesystem/etc/openvpn/client-config-hole/* )
   for f in "${files[@]}"; do
   for f in "${files[@]}"; do
-    host=${f%%/*}
+    host=${f##*/}
     if [[ ! ${vpn_ips[$host]} ]]; then
     if [[ ! ${vpn_ips[$host]} ]]; then
-      e /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service
+      e rm $f
+      e ssh root@li.b8.nz rm -f $f
     fi
   done
 
     fi
   done
 
-  cd /p/c/machine_specific
-  ngset
-  files=( */filesystem/etc/wireguard/wghole.conf )
-  ngreset
-  cd $initial_dir
-  for f in "${files[@]}"; do
-    host=${f%%/*}
-    if [[ ! ${vpn_ips[$host]} ]]; then
-      e /p/c/machine_specific/$host/filesystem/etc/wireguard/wghole.conf
-      e cedit -s $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf '<<<""'
+  tmpf=$(mktemp)
+  {
+    printf "%s" "Host * "
+    sed -n '/^Host /h;/^IdentityFile .*\/home/{g;s/^Host//;s/ / !/gp}' /p/c/subdir_files/.ssh/config-static | tr '\n' ' ' \
+      | sed -r 's/ *$/\n/'
+    echo "IdentityFile ~/.ssh/work"
+  } >$tmpf
+  cedit -e work-identity /p/c/subdir_files/.ssh/config-static <$tmpf
+  rm -f $tmpf
+
+  ### begin focus on hosts file update ###
+  #
+  # This started as its own function, but it actually
+  # needed to alter the ssh config, so combined it.
+  #
+  # background: This is finally doing dynamic ip resolution via the hosts
+  # file. I considered detecting where each host was dynamically or
+  # something, but ultimately decided to mostly avoid that, other than
+  # detecting the status of the current machine I'm on.  I want to be able
+  # to move it around without having to manually type much of anything.
+  local -a host_domain_suffix hosts
+  local -A ip_to_hosts
+  local suf ip i host at_home suf_from_here
+
+  source /p/c/domain-info
+
+  at_home=false
+  if ip n | grep -q "10.2.0.1 .* b4:75:0e:fd:06:4a"; then
+    at_home=true
+  fi
+
+  for i in ${host_domain_suffix[@]}; do
+    if [[ $i == *.* ]]; then
+      suf=$i
+      continue
+    fi
+    hosts+=($i)
+    if [[ $i == "$HOSTNAME" ]]; then
+      unset "portfw_ips[$i]"
+      continue
+    fi
+
+    suf_from_here=$suf
+    if ! $at_home && [[ $suf == .b8.nz || $suf == [wc].b8.nz ]]; then
+      suf_from_here=i.b8.nz
+    else
+      unset "portfw_ips[$i]"
+    fi
+
+    # note this might be outdated until we do a dns push
+    ip=$(dig +short "$i$suf_from_here" @iankelling.org | tail -n1) ||:
+    if [[ ! $ip ]]; then
+      if [[ $suf == .office.fsf.org ]]; then
+        suf_from_here=wg.b8.nz
+        ip=$(getent ahostsv4 "$i$suf_from_here" | awk '{ print $1 }' | head -n1) ||:
+      fi
+    fi
+    if [[ $ip ]]; then
+      ip_to_hosts[$ip]+=" $i"
+    else
+      echo error: failed to get ip of "$i$suf_from_here"
     fi
   done
     fi
   done
+
+  for ip in "${!ip_to_hosts[@]}"; do
+    echo "$ip${ip_to_hosts[$ip]}"
+  done | s cedit -e hosts-file-up /etc/hosts
+  for host in ${hosts[@]}; do
+    echo $host
+  done >/p/c/subdir_files/.dsh/group/btrbk
+  ### end focus on hosts file update ###
+
+
+  # note: note sure if this is a great way to check.
+  # todo: think about it
+
+  if $at_home; then
+    # possible that in the future we want to create
+    # a dynamic file here, and then we can move the cat
+    # command above out of the conditional
+    rsync -a /p/c/subdir_files/.ssh/config-static ~/.ssh/config
+  else
+    for host in ${!portfw_ips[@]}; do
+      ipsuf=${portfw_ips[$host]}
+      cat <<EOF
+Host ${host}
+Port $((2200 + ipsuf))
+EOF
+    done > ~/.ssh/config-dynamic
+    cat /p/c/subdir_files/.ssh/config-static ~/.ssh/config-dynamic  >~/.ssh/config
+  fi
 }
 
 # usage host ipsuf [extrahost]
 }
 
 # usage host ipsuf [extrahost]
@@ -2926,7 +3187,7 @@ mdenable() {
 
   two=false
   case $1 in
 
   two=false
   case $1 in
-    -2) two=true shift ;;
+    -2) two=true; shift ;;
   esac
 
   for md; do
   esac
 
   for md; do
@@ -3108,8 +3369,8 @@ myprof() {
   pushd /home/iank/.local/share/profanity/chatlogs/iank_at_fsf.org/rooms/office_at_conference.fsf.org
   logs=(*)
   logcount=${#logs[@]}
   pushd /home/iank/.local/share/profanity/chatlogs/iank_at_fsf.org/rooms/office_at_conference.fsf.org
   logs=(*)
   logcount=${#logs[@]}
-  if (( logcount > 15 )); then
-    i=$(( logcount - 15 ))
+  if (( logcount > 16 )); then
+    i=$(( logcount - 16 ))
   else
     i=0
   fi
   else
     i=0
   fi
@@ -3480,10 +3741,21 @@ EOFOUTER
   chmod +x $out
 }
 
   chmod +x $out
 }
 
-smeld() { # ssh meld usage host1 host2 file
+# ssh meld. usage: host1 host2 file
+smeld() {
   meld <(ssh $1 cat $3) <(ssh $2 cat $3)
 }
 
   meld <(ssh $1 cat $3) <(ssh $2 cat $3)
 }
 
+# remote file meld
+# usage: host file1 file2
+rmeld() {
+  local tmpdir
+  tmpdir=$(mktemp -d)
+  scp "$1:$2" "$1:$3" $tmpdir
+  meld "$tmpdir/${2##*/}" "$tmpdir/${3##*/}"
+}
+
+
 spd() {
   PATH=/usr/local/spdhackfix:$PATH command spd "$@"
 }
 spd() {
   PATH=/usr/local/spdhackfix:$PATH command spd "$@"
 }
@@ -3856,10 +4128,17 @@ nmtc() {
   s nmtui-connect "$@"
 }
 
   s nmtui-connect "$@"
 }
 
+# check exim and others network namespace
 mailnncheck() {
 mailnncheck() {
-  local unit pid ns mailnn
+  local unit pid ns mailnn spamd_ser
+
+  spamd_ser=spamd
+  if systemctl cat spamassassin &>/dev/null; then
+    spamd_ser=spamassassin
+  fi
+
   # mailvpn would belong on the list if using openvpn
   # mailvpn would belong on the list if using openvpn
-  for unit in mailnn unbound dovecot spamassassin exim4 radicale; do
+  for unit in mailnn unbound dovecot $spamd_ser exim4 radicale; do
     pid=$(servicepid $unit)
     echo debug: unit=$unit pid=$pid
     if [[ ! $pid ]]; then
     pid=$(servicepid $unit)
     echo debug: unit=$unit pid=$pid
     if [[ ! $pid ]]; then
@@ -3901,10 +4180,7 @@ vpn() {
   fi
 
   [[ $1 ]] || { echo need arg; return 1; }
   fi
 
   [[ $1 ]] || { echo need arg; return 1; }
-  journalctl --unit=$vpn_service@$1 -f -n0 &
-  # sometimes the journal doesnt open until after the vpn output
-  # has happened. hoping this fixes that.
-  sleep 1
+  journalctl --since=now --unit=$vpn_service@$1 -f -n0 &
   sudo systemctl start $vpn_service@$1
   # sometimes the ask-password agent does not work and needs a delay.
   sleep .5
   sudo systemctl start $vpn_service@$1
   # sometimes the ask-password agent does not work and needs a delay.
   sleep .5
@@ -3923,15 +4199,17 @@ fixu() {
   fi
 }
 
   fi
 }
 
-# unmute
+# unmute desktop output
 um() {
 um() {
-  local sink card
+  local sink card sedcmd
   sink=$(pactl get-default-sink)
   if [[ $sink == auto_null ]]; then
     # guessing there is just one with an off profile. otherwise we will
     # need some other solution, like storing the card identifier that we
   sink=$(pactl get-default-sink)
   if [[ $sink == auto_null ]]; then
     # guessing there is just one with an off profile. otherwise we will
     # need some other solution, like storing the card identifier that we
-    # muted with nap.
-    card=$(pacmd list-cards | sed -n '/^[[:space:]]*index:/{s/^[[:space:]]*index://;h};/^[[:space:]]*active profile: <off>$/{g;p;q}')
+    # muted with nap. Or, we could so some hakery with
+    # pactl -f json.
+    sedcmd='/^[[:space:]]*index:/{s/^[[:space:]]*index://;h};/^[[:space:]]*active profile: <off>$/{g;p;q}'
+    card=$(pacmd list-cards | sed -n "$sedcmd")
     m pacmd set-card-profile "$card" output:analog-stereo
   fi
 
     m pacmd set-card-profile "$card" output:analog-stereo
   fi
 
@@ -4093,30 +4371,58 @@ vrun() {
 }
 
 electrum() {
 }
 
 electrum() {
+  # Running the appimage said fuse was not available, but try
+  # running the appimage with --appimage-extract, which worked.
+  # It seems there is no need to backup the wallet, it can be restored
+  # via the seed onto any computer that needs it.
+  /a/opt/electrum/squashfs-root/AppRun "$@"
+
+
+  # This was an old way I ran electrum over tor, and seems like I
+  # imported a bitcoin core wallet.
+  #
   # https://electrum.readthedocs.io/en/latest/tor.html
   # https://github.com/spesmilo/electrum-docs/issues/129
   # https://electrum.readthedocs.io/en/latest/tor.html
   # https://github.com/spesmilo/electrum-docs/issues/129
-  s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
-  sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
+  # s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
+  # sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
+
 }
 }
+
+
 monero() {
   sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
 }
 
 
 monero() {
   sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
 }
 
 
+# grep + find
+gef() {
+  faf | grep -E "$@" ||:
+  rgv "$@"
+}
+
 # rg my main files
 rgm() {
   rg "$@" /p/w.org /a/t.org /a/work.org /b
 }
 
 # rg my main files
 rgm() {
   rg "$@" /p/w.org /a/t.org /a/work.org /b
 }
 
-# re all my files more expansively
-
+# re all my files more expansively.
+# usage [-OPT...] regex space combined
 rem() {
   local paths
 rem() {
   local paths
+  local -a opts
+  for arg; do
+    if [[ $arg == -* ]]; then
+      opts+=("$1")
+      shift
+    else
+      break
+    fi
+  done
   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 -- "$*" ||:
   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 $local_rgv_args -g "!bash_unpublished" -- "$*" $paths /a/work.org ||:
+  rgv $local_rgv_args -g "!bash_unpublished" "${opts[@]}" -- "$*" $paths /a/work.org ||:
 }
 reml() { # rem with limit to 5 matches per file
   local_rgv_args="-m 5"
 }
 reml() { # rem with limit to 5 matches per file
   local_rgv_args="-m 5"
@@ -4392,7 +4698,7 @@ hssh-update() {
   case $HOSTNAME in
     sy|kd)
       hosts=(
   case $HOSTNAME in
     sy|kd)
       hosts=(
-        kd x3.office.fsf.org syw
+        kd.b8.nz x3.office.fsf.org syw x2.b8.nz
       )
       ;;
     x3)
       )
       ;;
     x3)
@@ -4444,8 +4750,24 @@ ftoc() {
   units "tempF($1)" tempC
 }
 
   units "tempF($1)" tempC
 }
 
-# requires dns/firewall setup first
-local-icecast() {
+# local icecast
+localic() {
+  local mod=false
+  cedit live /p/c/machine_specific/vps/filesystem/var/lib/bind/db.iankelling.org <<'EOF' || mod=true
+live   CNAME   b8.nz.
+EOF
+  if $mod; then
+    ip=$(ip r show default | sed -r 's/.*src ([^ ]*).*/\1/' | head -n1)
+    if [[ ! $ip ]] && timeout 1 ping -c 1 $ip; then
+      echo "error: failed to get ip: $ip" >&2
+      exit 1
+    fi
+    cat >/p/c/cmc-firewall-data-http <<EOF
+http_ip=$ip
+EOF
+    bindpush
+    wrt-setup
+  fi
   web-conf -e ian@iankelling.org -f 8000 - apache2 live.iankelling.org  <<'EOF'
 <Location "/fsf.webm">
 AuthType Basic
   web-conf -e ian@iankelling.org -f 8000 - apache2 live.iankelling.org  <<'EOF'
 <Location "/fsf.webm">
 AuthType Basic
@@ -4462,8 +4784,23 @@ AuthUserFile "/etc/icecast-fsf-tech-htpasswd"
 Require valid-user
 </Location>
 EOF
 Require valid-user
 </Location>
 EOF
+  s cat /etc/letsencrypt/live/live.iankelling.org/{fullchain,privkey}.pem | s dd of=/etc/icecast2/fullchainpluskey.pem
+  ser start icecast2
+}
+# li icecast
+liic() {
+  cedit live /p/c/machine_specific/vps/filesystem/var/lib/bind/db.iankelling.org <<'EOF' || bindpush
+live   A       72.14.176.105
+       AAAA    2600:3c00::f03c:91ff:fe6d:baf8
+EOF
+}
+# icecast rm -r
+icrmr() {
+  find /var/icecast -type f -delete
+  ssh li.b8.nz find /var/icecast -type f -delete
 }
 
 }
 
+
 # obs screen switching of
 obof() {
   ls -l /tmp/no-obs-auto-scene-switch
 # obs screen switching of
 obof() {
   ls -l /tmp/no-obs-auto-scene-switch
@@ -4486,11 +4823,115 @@ obs-gen-profiles() {
 # terminal clear. like clear, but put the prompt at the bottom,
 # useful for obs streaming the bottom half of a terminal window.
 tclear() {
 # 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
+  for ((i=0; i<COLUMNS; i++)); do
     echo
   done
 }
 
     echo
   done
 }
 
+opensslcertinfo() {
+  openssl x509 -txt -in "$@"
+}
+
+# dsh on btrbk hosts
+dsb() {
+  :
+}
+
+# dsh a file and run it
+dsa() {
+  local ret file
+  if ! parallel -j 10 scp x {}:/tmp <~/.dsh/group/btrbk; then
+    echo parallel scp failed. dsa returning $ret
+  fi
+  dsh -g btrbk
+}
+
+# temporary
+zmqsend() {
+  /nocow/t/ffmpeg-release/ffmpeg-7.0.1/tools/zmqsend "$@"
+}
+
+ffg() { /nocow/t/ffmpeg-release/ffmpeg-7.0.1/tools/graph2dot -o /tmp/g.tmp && dot -Tpng /tmp/g.tmp -o /tmp/g.png && feh /tmp/g.png; }
+
+firefox-hide-tabs() {
+
+  # without this, make tabs smaller by setting browser.uidensity 1 in about:config
+
+  profiledir=$1
+  [[ $1 ]] || return 1
+  # Related: the sidebery extension is useful.
+
+  # This is from
+  # https://raw.githubusercontent.com/MrOtherGuy/firefox-csshacks/master/chrome/hide_tabs_toolbar.css
+
+  ainsl $profiledir/chrome/userChrome.css '#TabsToolbar{ visibility: collapse !important }'
+
+}
+
+# kill lease on cmc
+klease() {
+  local tmpdir ret out
+  ret=0
+  out=$(ssh cmc dnsmasq-end-lease "$1" 2>&1) || ret=1
+  printf "%s\n" "$out"
+  if [[ $out == *"try diffing"* ]]; then
+    tmpdir=$(mktemp -d)
+    m scp cmc:/tmp/dhcp.leases cmc:/tmp/dhcp.leases.iank $tmpdir
+    m diff $tmpdir/dhcp.leases $tmpdir/dhcp.leases.iank ||:
+    rm -rf $tmpdir
+  fi
+  return $ret
+}
+
+# ffs and switch the bash history on this terminal.
+# disabled because I don't really need this and
+# the history switching is annoying for debugging.
+#
+# ffs() {
+#   local last
+#   last="${*: -1}"
+#   if [[ $last && $last != -* && $last != sysops ]]; then
+#     his
+#   fi
+#   command ffs "$@"
+# }
+
+i3gen() {
+  /b/ds/i3-sway/gen
+}
+
+
+# insensitive find plus edit
+ife() {
+  local tmps found_count i char file
+  local -a found_files
+  local -A button_file
+  tmps=$(ifn "$@")
+  mapfile -t found_files <<<"$tmps"
+  found_count=${#found_files[@]}
+  if (( ${#found_files[@]} == 1 )); then
+    m g ${found_files[0]}
+  else
+    i=0
+    for button in {a..z}; do
+      button_file[$button]="${found_files[$i]}"
+      echo $button: ${found_files[$i]}
+      i=$(( i + 1 ))
+      if (( i >= found_count )); then
+        break
+      fi
+    done
+    read -rsN1 -t 5 char ||:
+    file="${button_file[$char]}"
+
+    if [[ $file ]]; then
+      g "$file"
+    else
+      echo "no selection"
+    fi
+  fi
+}
+
 
 export BASEFILE_DIR=/a/bin/fai-basefiles
 
 
 export BASEFILE_DIR=/a/bin/fai-basefiles