mostly fixes, a few improvements
authorIan Kelling <ian@iankelling.org>
Sat, 9 Mar 2024 13:26:51 +0000 (08:26 -0500)
committerIan Kelling <ian@iankelling.org>
Sat, 9 Mar 2024 13:26:51 +0000 (08:26 -0500)
25 files changed:
.bashrc
brc
brc2
brcrun [new file with mode: 0755]
btrbk-run
distro-end
filesystem/etc/cron.d/ian
filesystem/etc/profile.d/environment.sh
filesystem/etc/systemd/resolved.conf.d/zziank.conf
filesystem/etc/systemd/system/nzbget.service [new file with mode: 0644]
filesystem/usr/local/bin/prof-irc
laptop-xrandr
machine_specific/kd/filesystem/etc/btrbk/rust.conf
mail-setup
mailclean
mount-latest-subvol
my-update-info-dir
pkgs
subdir_files/.config/gh/config.yml [new file with mode: 0644]
subdir_files/.config/gh/hosts.yml [new file with mode: 0644]
subdir_files/sieve/lists.sieve
subdir_files/sieve/liststest.sieve
switch-mail-host
trusted-network
unsaved-buffers

diff --git a/.bashrc b/.bashrc
index c1dbd77fc6ab7a7182e3462c86b8c1baa31f1d16..12b60b8b29b6e0cefbb153de6c1bc1b16285d376 100644 (file)
--- a/.bashrc
+++ b/.bashrc
@@ -31,6 +31,9 @@ HISTCONTROL=ignoredups
 # but meh. dunno why, but just " *" does glob expansion, so use [ ] to avoid it.
 HISTIGNORE='pass *:otp *:oathtool *:histrm *'
 
+# note: duplicated in /a/bin/ds/filesystem/etc/profile.d/environment.sh
+umask 022
+
 
 
 #### if (in
diff --git a/brc b/brc
index a47a6fa6cb6d99ef82ae7eb6be6742e868958bf8..2a81b25fa369f557d373f431d3c56400f61e9707 100644 (file)
--- a/brc
+++ b/brc
@@ -128,19 +128,79 @@ fi
 
 export SSH_CONFIG_FILE_OVERRIDE=/root/.ssh/confighome
 
+
+
 # emacs has a different default search path than the info command. This
-# adds the info defaults to emacs, but not the reverse, because I dun
+# adds the info defaults to emacs. This is commented because after
+# various upgrades this is no longer a problem: for the directories that
+# exist on my system, emacs already includes the ones that info
+# searches.
+#
+# but not the reverse, because I dun
 # care much about the cli. The search path is only on the cli if you run
 # "info xxx", or in emacs if you run '(info xxx)', so not that
-# important, but might as well fix it.
+# important and i don't bother fixing it.
+
+# # info info says this path is what was compiled, and its not documented
+# # anywhere. Through source grepping, i found it in files.h of the info
+# # source in trisquel flidas.
+# #
+# # Trailing : means for emacs to add its own stuff on to the end.
+# #
+# # A problem with this is that directories which are not readable breaks info. And of course, this hard coding is not nice.
+# # I removed PATH from the start, because I've never seen an info file in PATH. And removed ".", because I can just specify the full file name in that case.
+# #
+# # https://raw.githubusercontent.com/debian-tex/texinfo/master/info/filesys.h
+# #
+
+# # note: to split up the var like this, do:
+# # IFS=:; printf '%s\n' $INFOPATH
+
+# dirs=(
+#   /usr/local/info
+#   /usr/info
+#   /usr/local/lib/info
+#   /usr/lib/info
+#   /usr/local/gnu/info
+#   /usr/local/gnu/lib/info
+#   /usr/gnu/info
+#   /usr/gnu/lib/info
+#   /opt/gnu/info
+#   /usr/share/info
+#   /usr/share/lib/info
+#   /usr/local/share/info
+#   /usr/local/share/lib/info
+#   /usr/gnu/lib/emacs/info
+#   /usr/local/gnu/lib/emacs/info
+#   /usr/local/lib/emacs/info
+#   /usr/local/emacs/info
+# )
+
+# for d in ${dirs[@]}; do
+#   if [[ -r $d ]]; then
+#     INFOPATH="$d:$INFOPATH"
+#   fi
+# done
+# unset d dirs
+
+
+# note: guix bash config does this automatically.
+if [[ $INFOPATH != *: ]]; then
+  INFOPATH="$INFOPATH:"
+fi
 
-# info info says this path is what was compiled, and its not documented
-# anywhere. Through source grepping, i found it in filesys.h of the info
-# source in trisquel flidas.
+# info parameter expansion
+#
+# info cheat sheet:
+# H: see keybinds
+# / search, {, }: next/prev match
+# ctrl/alt-v scroll forward/backward within this node
+# l: go to previous node
 #
-# Traling : means for emacs to add its own stuff on to the end.
+info-pe() {
+  info bash 'Basic Shell Features' 'Shell Expansions' 'Shell Parameter Expansion'
+}
 
-export INFOPATH=$PATH:/usr/local/info:/usr/info:/usr/local/lib/info:/usr/lib/info:/usr/local/gnu/info:/usr/local/gnu/lib/info:/usr/gnu/info:/usr/gnu/lib/info:/opt/gnu/info:/usr/share/info:/usr/share/lib/info:/usr/local/share/info:/usr/local/share/lib/info:/usr/gnu/lib/emacs/info:/usr/local/gnu/lib/emacs/info:/usr/local/lib/emacs/info:/usr/local/emacs/info:.:
 
 # for openwrt system that has no stty, this is easier than
 # guarding every time i use it.
@@ -648,6 +708,76 @@ jdo() {
   (( ret == 0 )) || return $ret
 }
 ccomp time jdo
+
+# standard date as used in logs
+datelog() {
+  date +%Y-%m-%d "$@"
+}
+
+# date in log appropriate format
+dtl() {
+  date "+%F %T" "$@"
+}
+
+# ts formatted
+tsf() {
+  command ts "%F %T" "$@"
+}
+
+# ts log. log command to log file.
+# usage: tsl LOG_PATH_PREFIX COMMAND...
+# example: tsl /root/command
+# log file will be like /root/command-2024-02-10.log
+tsl() {
+  local log_prefix log_path appending ret
+  if (( $# < 2 )); then
+    echo "tsl: error: expected >= 2 arguments, got $#" >&2
+    return 1
+  fi
+  log_prefix="$1"
+  if [[ $log_prefix == */* && ! -d ${log_prefix%*/} ]]; then
+    echo "tsl: error: expected directory at ${log_prefix%*/}" >&2
+    return 1
+  fi
+  log_path=$log_prefix-$(date +%Y-%m-%d).log
+  appending=false
+  if [[ -s $log_path ]]; then
+    appending=true
+  fi
+  shift
+  printf "%s\n" "CWD: $PWD, log: $log_path, running $*" | ts "%F %T" | tee -a "$log_path"
+  ret=0
+  "$@" |& ts "%F %T" | tee -a "$log_path" || ret=$?
+  printf "%s\n" "exit code $ret from command: $*" | ts "%F %T" | tee -a "$log_path"
+  if $appending; then
+    printf "%s\n" "note: this log file contains logs before those of previous command" | ts "%F %T" | tee -a "$log_path"
+  fi
+}
+
+disk-info() {
+  local cmds cmd
+  mapfile -t cmds <<'EOF'
+tail -n +1 /proc/mdstat /etc/mdadm/mdadm.conf /etc/fstab /etc/crypttab
+lsblk
+blkid
+ls -la /dev/disk/by-id
+EOF
+
+  for cmd in "${cmds[@]}"; do
+    cat <<EOF
+### $cmd
+
+\`\`\`
+EOF
+    $cmd
+    cat <<'EOF'
+
+```
+
+EOF
+  done
+}
+
 #### end fsf section
 
 
@@ -688,8 +818,8 @@ fpst() { # file paste
   cp "$my_f_tempdir"/* "$target"
 }
 
-_khfix_common() {
-  local host ip port file key
+_khfix-common() {
+  local host ip port file key tmp
   read -r host ip port < <(timeout -s 9 2 ssh -oBatchMode=yes -oControlMaster=no -oControlPath=/ -v $1 |& sed -rn "s/debug1: Connecting to ([^ ]+) \[([^\]*)] port ([0-9]+).*/\1 \2 \3/p" ||: )
   file=$(readlink -f ~/.ssh/known_hosts)
   if [[ ! $ip ]]; then
@@ -704,25 +834,36 @@ _khfix_common() {
     host_entry=$host
   fi
   if [[ $host != "$ip" ]]; then
-    key=$(ssh-keygen -F "$host_entry" -f $file | sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/')
+    tmp=$(mktemp)
+    ssh-keygen -F "$host_entry" -f $file >$tmp || [[ $? == 1 ]] # 1 when it doesnt exist in the file
+    if [[ -s $tmp ]]; then
+      key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp)
+    fi
+    rm $tmp
     if [[ $key ]]; then
       grep -Fv "$key" "$file" | sponge "$file"
     fi
+    key=
   fi
-  key=$(ssh-keygen -F "$ip_entry" -f $file | sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/')
+  tmp=$(mktemp)
+  ssh-keygen -F "$ip_entry" -f $file >$tmp || [[ $? == 1 ]]
+  if [[ -s $tmp ]]; then
+    key=$(sed -r 's/^.*([^ ]+ +[^ ]+) *$/\1/' $tmp)
+  fi
+  rm $tmp
   if [[ $key ]]; then
     grep -Fv "$key" "$file" | sponge "$file"
   fi
   ll ~/.ssh/known_hosts
-  rootsshsync
 }
-khfix() { # known hosts fix
-  _khfix_common "$@" || return 1
+khfix-r() { # known hosts fix + root
+  _khfix-common "$@" || return 1
   ssh $1 :
+  rootsshsync
 }
-khcopy() {
-  _khfix_common "$@"
-  ssh-copy-id $1
+khfix() {
+  _khfix-common "$@" || return 1
+  ssh $1 :
 }
 
 # copy path into clipboard
@@ -1540,7 +1681,86 @@ hlm() { hl "$*";  "$@"; }
 
 hrcat() { local f; for f; do [[ -f $f ]] || continue; hr; echo "$f"; cat "$f"; done }
 
+# example usage:
+# github-release-dl restic/restic restic_ _linux_amd64.bz2
+# gets a url like:
+# https://github.com/restic/restic/releases/download/v0.16.3/restic_0.16.3_linux_amd64.bz2
+github-release-dl() {
+  local github_path file_prefix file_suffix latest_prefix version redir_path
+  github_path=$1
+  file_prefix=$2
+  file_suffix=$3
+  if (( $# != 3 )); then
+    echo "$0: error, expected 3 arguments" >&2
+    return 1
+  fi
+  redir_path="https://github.com/$github_path/releases/latest/download/"
+  latest_prefix=$(curl -s -I "$redir_path" | awk 'tolower($1) == "location:" {print $2}')
+  # it has a trailing /r at the end. just kill any whitespace.
+  latest_prefix="${latest_prefix//[$'\t\r\n ']}"
+  if [[ ! $latest_prefix ]]; then
+    echo "failed to find latest path. Tried to find case insensitive 'location:' in the curl output:"
+    m curl -s -I "$redir_path"
+    return 1
+  fi
+  version="${latest_prefix##*/}"
+  version="${version#v}"
+  m wget -- "$latest_prefix/$file_prefix$version$file_suffix"
+}
+
+# examples.
+# go-github-install restic/restic restic_ _linux_amd64.bz2
+# go-github-install restic/rest-server rest-server_ _linux_amd64.tar.gz
+
+# common pattern among go binaries on github
+go-github-install() {
+  local tmpd targetf tmp files src
+  tmpd=$(mktemp -d)
+  cd $tmpd
+  file_prefix=$2
+  file_suffix=$3
+  tmp="${file_prefix##*[[:alnum:]]}"
+  targetf="${file_prefix%$tmp}"
+  echo targetf: $targetf
+  github-release-dl "$@"
+  files=(./*)
+  case $file_suffix in
+    *.bz2)
+      bunzip2 -- ./*
+      ;;
+    *.tar.gz|*.tgz)
+      tar -vxzf ./*
+      ;;
+  esac
+  rm -f -- "${files[@]}"
+  files=(./*)
+  # Here we detect and handle 2 cases: either we extracted a single
+  # binary which we have to rename or a folder with a binary named
+  # $targetf in it which is all we care about.
+  if (( ${#files[@]} == 1 )) && [[ -f ${files[0]} ]]; then
+    chmod +x ./*
+    mv -- ./* /usr/local/bin/$targetf
+  else
+    files=(./*/$targetf)
+    if [[ -f $targetf ]]; then
+      src=$targetf
+    elif [[ -f ${files[0]} ]]; then
+      src="${files[0]}"
+    fi
+    chmod +x "$src"
+    mv -- "$src" /usr/local/bin
+  fi
+  cd - >/dev/null
+  rm -rf $tmpd
+}
 
+## 2024: I'm using gh instead of hub, but leaving this just in case.
+## I tried the github cli tool (gh) and it seems easier than
+## I remember hub.
+##
+## hub predated github's 2020 official cli tool gh.
+## more info at
+## https://raw.githubusercontent.com/cli/cli/trunk/docs/gh-vs-hub.md
 # get latest hub and run it
 # main command to use:
 # hub pull-request --no-edit
@@ -2069,6 +2289,16 @@ sedi() {
 
 
 
+# todo: test variable assignment with newlines here.
+# https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash
+
+# beware that it only works on the assumption that any special
+# characters in the input string are intended to be escaped, not to work
+# as special chacters.
+shellescape() {
+  LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'
+}
+
 rmstrips() {
   ssh fencepost head -n 300 /gd/gnuorg/EventAndTravelInfo/rms-current-trips.txt | less
 }
@@ -2697,14 +2927,19 @@ vmunshare() {
 }
 
 myiwscan() {
-  # find input, copy to pattern space, when we find the first field, print the copy in different order without newlines.
-  # instead of using labels, we could just match a line and group, eg: /signal:/,{s/signal:(.*)/\1/h}
-  sudo iw dev wls1 scan | sed -rn  "
+  local i
+  interfaces=$(iw dev | awk '$1 == "Interface" {print $2}')
+  for i in $interfaces; do
+    echo "myiwscan: considering $i"
+    # find input, copy to pattern space, when we find the first field, print the copy in different order without newlines.
+    # instead of using labels, we could just match a line and group, eg: /signal:/,{s/signal:(.*)/\1/h}
+    sudo iw dev $i scan | sed -rn  "
 s/^\Wcapability: (.*)/\1/;Ta;h;b
 :a;s/^\Wsignal: -([^.]+).*/\1/;Tb;H;b
 # padded to min width of 20
 :b;s/\WSSID: (.*)/\1                    /;T;s/^(.{20}(.*[^ ])?) */\1/;H;g;s/(.*)\n(.*)\n(.*)/\2 \3  \1/gp;b
 "|sort -r
+  done
 }
 
 # Run script by copying it to a temporary location first,
@@ -2858,30 +3093,44 @@ cm() {
 }
 
 
-disk-info() {
-  local cmds cmd
-  mapfile -t cmds <<'EOF'
-tail -n +1 /proc/mdstat /etc/mdadm/mdadm.conf /etc/fstab /etc/crypttab
-lsblk
-blkid
-ls -la /dev/disk/by-id
-EOF
+fsf-sv-header() {
+  local f
+  local -a f_maybe
+  if ! type -p sponge &>/dev/null; then
+    echo "$0: error: missing dependency: sudo apt install moreutils" >&2
+    return 1
+  fi
 
-  for cmd in "${cmds[@]}"; do
-    cat <<EOF
-### $cmd
+  for f; do
+    echo "adding header to $f"
+    if [[ -s $f ]]; then
+      f_maybe=("$f")
+    else
+      f_maybe=()
+    fi
+    cat - "${f_maybe[@]}" <<EOF | sponge "$f"
+The following is the GNU All-permissive License as recommended in
+<https://www.gnu.org/licenses/license-recommendations.en.html>
 
-\`\`\`
-EOF
-    $cmd
-    cat <<'EOF'
+Copyright (C) $(date +%Y) Free Software Foundation <sysadmin@fsf.org>
 
-```
+Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.  This file is offered as-is,
+without any warranty.
+
+Contributions are welcome. See <https://savannah.gnu.org/maintenance/fsf/>.
 
 EOF
   done
 }
 
+
+tsr() { # ts run
+  "$@" |& ts || return $?
+}
+
+
 # * misc stuff
 
 
diff --git a/brc2 b/brc2
index 5f0a5ff45f5ee11430b1d232c6fed78473686b2a..e7d50e3f06b7d37d083a1e393e54bc22fa05bb5c 100644 (file)
--- a/brc2
+++ b/brc2
@@ -729,6 +729,59 @@ mpvrpc-percent-pos() {
   mpvrpco '{ "command": ["get_property", "percent-pos"] }' | jq .data | sed 's/\..*/%/' 2>/dev/null ||:
 }
 
+# run if not running.
+#
+# Note: this does not work with shell scripts as they are normally
+# invoked, because the ps output has the interpreter at the start.
+# A workaround is to invoke the command in that format, or we could
+# do various other workarounds.
+#
+# 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() {
+  if ps h -o args -C "${1##*/}" | grep -Fxqv "$*" &>/dev/null || [[ $? == 141 ]]; then
+    "$@"
+  fi
+}
+# variation of above: run or wait if running
+rowir() {
+  local pid
+  pid=$(ps h -o 'pid,args' -C "${1##*/}" | sed -r 's/^[[:space:]]*([0-9]+)[[:space:]](.*)/\1\n\2/' | grep -B1 -Fx "$*" | head -n1 ||: )
+  if [[ $pid ]]; then
+    # https://unix.stackexchange.com/questions/427115/listen-for-exit-of-process-given-pid
+    tail --pid="$pid" -f /dev/null
+  else
+    "$@"
+  fi
+}
+
+mpvrpc-loadfile() {
+  local path nextpath cachedir finalpath nextpath count
+  cachedir=$HOME/.iank-music-cache
+  path="$1"
+  nextpath="$2"
+
+  # note: logic duplicated in beetpull
+  local remote_p=true
+  if [[ $HOSTNAME == kd ]]; then
+    remote_p=false
+  fi
+
+  if $remote_p; then
+    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 ]] )
+    # allow us to start 2 rsyncs in the background
+    if [[ $count == [01] ]]; then
+      rinr rsync --partial -a --inplace --mkpath "b8.nz:$nextpath" "$finalnextpath" &
+    fi
+  else
+    finalpath="$path"
+  fi
+  mpvrpc '{ "command": ["loadfile", "'"$finalpath"'"] }'
+}
+
 # tag with beets.
 # usage: beetag [-r] [-s] QUERY
 # it lists the query, reads an input char for tagging one by one.
@@ -787,7 +840,8 @@ beetag()  {
   fi
   ### end arg processing ###
 
-  beetpull
+  # note: I used to do beetpull here, but mpv + ssfs on slowish
+  # connection leads to bad/buggy result.
 
   do_rare_genres=false
   volume=70
@@ -916,13 +970,13 @@ beetag()  {
         first_play=false
         for (( i=0; i<20; i++ )); do
           if [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' 2>/dev/null | jq .data) == true ]]; then
-            mpvrpc '{ "command": ["loadfile", "'"$path"'"] }' 2>/dev/null
+            mpvrpc-loadfile "$path" 2>/dev/null
             break
           fi
           sleep .1
         done
       else
-        mpvrpc '{ "command": ["loadfile", "'"$path"'"] }'
+        mpvrpc-loadfile "$path"
       fi
       erasable_line=false
     fi
@@ -960,7 +1014,7 @@ beetag()  {
             doplay=false
           else
             doplay=true
-            mpvrpc '{ "command": ["loadfile", "'"$path"'"] }'
+            mpvrpc-loadfile "$path"
             erasable_line=false
           fi
           beetag-nostatus 1
@@ -1808,9 +1862,13 @@ satoshi() { # $1 satoshi in usd
 }
 
 # Bitcoin holds open the wallet file. this causes problems for a
-# secondary computer running bitcoin and receiving a backup. So, as a
-# workaround, I intend to manually enable the wallet when I want to use
-# it and leave it disabled otherwise.
+# secondary computer running bitcoin and receiving a backup (as of
+# 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.
+#
+# 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
@@ -1856,9 +1914,13 @@ walletoff() {
       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
@@ -1939,10 +2001,6 @@ digme() {
   digdiff @ns{1,2}.iankelling.org "$@"
 }
 
-tsr() { # ts run
-  "$@" |& ts || return $?
-}
-
 dup() {
   local ran_d
   ran_d=false
@@ -2471,6 +2529,7 @@ vpn_ips[x2]=13
 vpn_ips[kw]=27
 vpn_ips[bo]=28
 vpn_ips[frodo]=34
+vpn_ips[s23b]=49
 
 vpn-ips-update() {
   local host ipsuf f files
@@ -3172,7 +3231,7 @@ j() {
   "$@" |& pee "xclip -r -selection clipboard" cat
 }
 
-# x copy
+# xorg copy. copy text piped into command
 xc() {
   xclip -r -selection clipboard
 }
@@ -3349,10 +3408,7 @@ spamf() { # spamtest on FILE
     e spamtest error: expected 1 arg, filename >&2
     return 1
   fi
-
-  spamdpid=$(systemctl status spamassassin| sed -n '/^ *Main PID:/s/[^0-9]//gp')
-  spamcpre="nsenter -t $spamdpid -n -m"
-  s $spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
+  sdncmdroot spamassassin sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
 }
 
 
@@ -3596,16 +3652,16 @@ tu() {
   $s /a/exe/teeu "$@"
 }
 
+# execute exim in its namespace. Useful args like -Mrm
 enn() {
   local ecmd pid
 
   ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf"
   if ip a show veth1-mail &>/dev/null; then
     s $ecmd "$@"
-    return
+  else
+    sdncmdroot exim4 $ecmd "$@"
   fi
-  pid=$(pgrep -f "/usr/sbin/exim4 -bd -q10m -C /etc/exim4/my.conf"|h1)
-  m s nsenter -t $pid -n -m $ecmd "$@"
 }
 
 # get pid of systemd service
@@ -3647,7 +3703,7 @@ sdnbash() { # systemd namespace bash
   m sudo nsenter -t $pid -n -m sudo -u $USER -i bash
 }
 
-sdnbashroot() { # systemd namespace bash
+sdnbashroot() { # systemd namespace bash as root
   local unit pid
   if (( $# != 1 )); then
     echo $0: error wrong number of args >&2
@@ -3659,9 +3715,11 @@ sdnbashroot() { # systemd namespace bash
 }
 
 
-sdncmd() { # systemd namespace cmd
+# systemd namespace cmd
+# usage: UNIT CMD...
+sdncmd() {
   local unit pid tmpf
-  if (( $# <= 2 )); then
+  if (( $# <= 1 )); then
     echo $0: error wrong number of args >&2
     return 1
   fi
@@ -3675,6 +3733,18 @@ sdncmd() { # systemd namespace cmd
   m sudo nsenter -t $pid -n -m sudo -u $USER -i bash -c ". $tmpf & rm $tmpf"
 }
 
+sdncmdroot() { # systemd namespace root command
+  local unit pid
+  if (( $# < 2 )); then
+    echo $0: error wrong number of args >&2
+    return 1
+  fi
+  unit=$1
+  shift
+  pid=$(servicepid $unit)
+  m sudo nsenter -t $pid -n -m "$@"
+}
+
 
 mailnnbash() {
   sdnbash mailnn
@@ -3686,13 +3756,7 @@ mailnnbash() {
 # }
 
 eximbash() {
-  local pid
-  pid=$(pgrep -f "/usr/sbin/exim4 -bd -q10m -C /etc/exim4/my.conf"|h1)
-  if [[ ! $pid ]]; then
-    echo "eximbash: failed to find exim pid. systemctl -n 30 status exim4:"
-    systemctl status exim4
-  fi
-  m sudo nsenter -t $pid -n -m
+  sdnbashroot exim4
 }
 spamnn() {
   local spamdpid
@@ -3700,7 +3764,7 @@ spamnn() {
   m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@"
 }
 unboundbash() {
-  m sudo nsenter -t "$(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp')" -n -m sudo -u $USER -i bash
+  sdnbashroot unbound
 }
 
 nmtc() {
@@ -3734,14 +3798,13 @@ mailnncheck() {
 
 
 vpncmd() {
-  m sudo -E env "PATH=$PATH" nsenter -t "$(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf")" -n "$@"
+  sdncmd openvpn-client-tr@client.service "$@"
 }
-
 vpni() {
-  vpncmd sudo -u iank env "PATH=$PATH" "$@"
+  sdncmd openvpn-client-tr@client.service bash
 }
 vpnbash() {
-  vpncmd bash
+  sdncmdroot openvpn-client-tr@client.service bash
 }
 
 
@@ -3779,17 +3842,15 @@ fixu() {
 um() {
   local sink card
   sink=$(pactl get-default-sink)
-  if [[ $sink != auto_null ]]; then
-    return
+  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}')
+    m pacmd set-card-profile "$card" output:analog-stereo
   fi
 
-  # 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}')
-  m pacmd set-card-profile "$card" output:analog-stereo
-
-  pactl set-sink-mute @DEFAULT_SINK@ false
+  m pactl set-sink-mute @DEFAULT_SINK@ false
   rm -f /tmp/ianknap
 }
 
@@ -3986,6 +4047,16 @@ reml() { # with limit to 5 matches per file
   rgv -m 5 -- "$*" $paths /a/t.org /p/w.org /a/work.org ||:
 }
 
+# re on common fsf files
+ref() {
+  local paths
+  paths="/f/gluestick /f/brains /f/s /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 -- "$*" $paths /a/work.org ||:
+  }
+
 
 # for use in /f/bind
 fupzone() {
@@ -4262,6 +4333,21 @@ i3bar() {
   rm -fv /tmp/noi3bar
 }
 
+# example:
+# <#part type="image/jpeg" filename="/home/iank/2023-12-24-ski-trip.jpg" disposition=attachment> <#/part>
+#
+attach-txt() {
+  local f
+  for f; do
+    if [[ ! -s $f ]]; then
+      e "error: empty or non-existent file $f"
+      return 1
+    fi
+  done
+  for f; do
+    echo '<#part type="image/jpeg" filename="'"$(rl "$f")"'" disposition=attachment> <#/part>'
+  done | ec
+}
 
 export BASEFILE_DIR=/a/bin/fai-basefiles
 
diff --git a/brcrun b/brcrun
new file mode 100755 (executable)
index 0000000..4ede74e
--- /dev/null
+++ b/brcrun
@@ -0,0 +1,3 @@
+#!/bin/bash
+. ~/.bashrc
+"$@"
index 5ce774b7e83c84270ad228479e5ba7b0f1901c40..34b23a8af6a552d79672b55a0dc3b55b5cdc8186 100644 (file)
--- a/btrbk-run
+++ b/btrbk-run
@@ -100,6 +100,8 @@ add-x3-target() {
   elif ping -q -c1 -w1 $h.b8.nz &>/dev/null; then
     # in case we took it home
     targets+=(x3.b8.nz)
+  elif ping -q -c1 -w1 ${h}w.b8.nz &>/dev/null; then
+    targets+=(x3w.b8.nz)
   else
     targets+=(x3wg.b8.nz)
   fi
@@ -333,6 +335,7 @@ if [[ /a/opt/btrbk/btrbk -nt /usr/bin/btrbk ]]; then
   fi
   cd /a/opt/btrbk
   m make install
+  cd /
 fi
 
 # TODO: i wonder if there should be an option to send to the default
@@ -400,17 +403,17 @@ else
             prospective_mps+=(/o)
           fi
           if [[ $source_host == "$HOST2" ]]; then
-            prospective_mps+=(/a /ar /qr /qd /q)
+            prospective_mps+=(/a /qr /qd /q)
           fi
         else
           if [[ $HOSTNAME == "$MAIL_HOST" ]]; then
             prospective_mps+=(/o)
           fi
           if [[ $HOSTNAME == "$HOST2" ]]; then
-            prospective_mps+=(/a /ar /qr /qd /q)
+            prospective_mps+=(/a /qr /qd /q)
           fi
           if $kd_spread; then
-            prospective_mps=(/a /ar /o /qr /qd /q)
+            prospective_mps=(/a /o /qr /qd /q)
           fi
         fi
         # note: put q last just in case its specific retention options were to
index b7a164e27a8323118e830f16a5b311da1a9b76c8..721b2c1a19de981337f4291b8a6264dde88cbf70 100755 (executable)
@@ -2198,6 +2198,30 @@ esac
 
 ### end bitcoin
 
+### begin gh ####
+
+# from https://raw.githubusercontent.com/cli/cli/trunk/docs/install_linux.md
+# One time setup afterwards:
+# gh auth login
+#
+# When it gets to the page where it asks to authorize github, the button
+# is grayed out. You can just open browser dev tools, inspect the
+# button, remove disabled="", then click it and it works.
+#
+# Auth token gets saved into /p/c/subdir_files/.local/share/keyrings/
+#
+# initial config goes to /home/iank/.config/gh
+curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
+  && sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
+  && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
+  && sudo apt update \
+  && sudo apt install gh -y
+
+### end gh ####
+
+# remove trisquel banner. it is cool but takes up too much space.
+sudo rm -f /etc/update-motd.d/01-banner
+
 case $HOSTNAME in
   kw|x3)
     sd /etc/cups/client.conf <<'EOF'
index 7c24b4af79e898b4ca3e6169e1cb7ddf3d6a5866..9e9ca2fab2a427b4d03feab6bf78c2d26df8ff77 100644 (file)
@@ -1,3 +1,12 @@
+# field          allowed values
+# -----          --------------
+# minute         0–59
+# hour           0–23
+# day of month   1–31
+# month          1–12 (or names, see below)
+# day of week    0–7 (0 or 7 is Sun, or use names)
+
+
 # default is /bin/sh
 SHELL=/bin/bash
 # default is /usr/bin:/bin
@@ -17,3 +26,9 @@ MAILTO=root
 4 20 * * 5   iank /usr/local/bin/check-lets-encrypt-ssl-settings
 4 21 * * 5   iank /b/ds/auto-commit-changes /a /p
 4 24 * * 5   iank failmail /b/ds/eggdrop-upgrade
+# avoid dnssec expirations. This is a hack, what we should
+# do instead is something like, sign only if expiration is
+# coming soon, and send an email notication, because this
+# push a version that is in the midst of editing. Whatever,
+# I rarely edit them.
+0 16 5 * *   iank brcrun bindpush
index 6191cb7eaf3c1958c9d832668f6cbc60a583f530..8463ea13dcea3a0ce104f2fc46477ce7475c011a 100644 (file)
@@ -1,4 +1,8 @@
 #!/bin/sh
+
+# Exports here get inherited by X, that is the only reason to do things
+# here. However, they do not get sent with sl().
+
 if [ -f $HOME/path-add-function ]; then
   . $HOME/path-add-function
   path-add /usr/sbin /usr/local/sbin /a/exe /a/opt/bin
@@ -155,6 +159,8 @@ fi
 # group, so if you copy files there with exact perms, that is probably
 # not what you want. I don't use a system like that, so I don't
 # care.
+#
+# Note: duplicated in .bashrc
 umask 022
 # this is how we could test for non-system user
 
index bce09667c0f2843fe7a194d1eceecf8fcd7ad229..323c4060c5669c85e111e75dac13523be777216a 100644 (file)
@@ -4,4 +4,3 @@
 # in case.
 LLMNR=no
 MulticastDNS=no
-Domains=fsf.org gnu.org
diff --git a/filesystem/etc/systemd/system/nzbget.service b/filesystem/etc/systemd/system/nzbget.service
new file mode 100644 (file)
index 0000000..06487c1
--- /dev/null
@@ -0,0 +1,17 @@
+[Unit]
+Description=NZBGet Daemon
+Documentation=http://nzbget.net/Documentation
+After=network.target
+
+[Service]
+User=iank
+Group=iank
+Type=forking
+ExecStart=/usr/bin/nzbget -D
+ExecStop=/usr/bin/nzbget -Q
+ExecReload=/usr/bin/nzbget -O
+KillMode=process
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
index 31c2823c3001bb8f9c4f9d52c8026d958286c4af..c9ffaac3cf5cef3e472ba243ff58a58797f65f89 100755 (executable)
@@ -1,5 +1,10 @@
 #!/bin/bash
 
+# man profanity says:
+# ALT+c  Run external editor (see profanity-editor(1)) for current input line.
+# note: this is NOT in the online manual list of commands, that has got to
+# be a bug.
+
 # This, along with changes to emacs init file and prof-remote allows us
 # to press alt-c in profanity to send the current text to #fsfsys.
 exec emacsclient -s profanity -a "" "$@"
index a28600c1280e708fa75eefd01c442157a1cbd66e..af21928b789f1ef4cd95089463966e4c7cc849a9 100755 (executable)
@@ -8,6 +8,8 @@ output=DP1
 if xrandr | grep -q "^$output disconnected" &>/dev/null; then
   xrandr --auto
 else
+  xrandr --output $output --off
+  sleep 2
   xrandr --output $output --right-of eDP1 --mode 3840x2160
 
   for i in 1 2 4 5 6 7 8 9 10; do
index c7ca32ab78524137763e2479a0d9896e5dca04c9..28032b16460b39a886ecd2ddb8cc6ddb9c34d9af 100644 (file)
@@ -18,3 +18,7 @@ volume /mnt/r7
 subvolume d
 target send-receive /mnt/rust1/btrbk
 target send-receive /mnt/rust2/btrbk
+
+subvolume ar
+target send-receive /mnt/rust1/btrbk
+target send-receive /mnt/rust2/btrbk
index 776f3e68aefd6d4b929e40f2cf04c57b09aab081..8f2a5c5f973fbecee46c5a113b34fbba73ae9d55 100755 (executable)
@@ -2037,7 +2037,7 @@ EOF
 ssl = required
 # this is the same as the certbot list, i check changes in /a/bin/ds/filesystem/usr/local/bin/check-lets-encrypt-ssl-settings
 ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
-ssl_protocols = TLSv1.2
+ssl_min_protocol = TLSv1.2
 ssl_prefer_server_ciphers = no
 
 protocol lmtp {
index e79794a5a6fe236e38750258583f248ed188f976..417f44549bf7ca39cdbf12019173e87e0b1a43da 100755 (executable)
--- a/mailclean
+++ b/mailclean
@@ -65,6 +65,6 @@ archive() {
 }
 
 cd /m/md
-archive 800 ./!(*archive|Drafts|Sent|INBOX)/*(cur|new) ./l/!(*archive)/*(cur|new)
+archive 800 ./!(*archive|board|Drafts|Sent|INBOX)/*(cur|new) ./l/!(*archive)/*(cur|new)
 archive 60 ./{sysadmin,rtcc,fsfcc,fsfmembers}/{cur,new}
 archive 30 ./Junk/{cur,new}
index bb93939290a11f1d4da248901f3d33576f2992b3..dfe65db1f1d15be994c9dc995b83d0d9ee3177bb 100644 (file)
@@ -254,12 +254,13 @@ $crypt_dev  /qr  btrfs  noatime,subvol=qr$mopts  0 0
 EOF
 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
-EOF
-fi
+# not syncing ar at the moment
+# 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
+# EOF
+# fi
 
 
 fa=(/mnt/o/btrbk/o.*); f=${fa[0]}
index 92235ea56a0a2284f66890645772dca7f275f624..f97b5985da7f3583fc98dc85d5793dd056845005 100755 (executable)
@@ -4,13 +4,14 @@
 INFODIR=/usr/share/info
 sudo rm -f "$INFODIR/dir"
 
-for dir in $(emacs --batch --eval '(progn(package-initialize) (dolist (x Info-directory-list) (message x)))' |& sort -u); do
+shopt -s nullglob
+
+for dir in $(emacs --batch --eval '(progn(package-initialize) (dolist (x Info-directory-list) (message x)))' |& sed 's,/$,,' | sort -u); do
 
   case ${dir%/} in
     # this is from /usr/sbin/update-info-dir
     */info)
 
-      echo $dir
       find $dir -type f | while read 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)
@@ -28,7 +29,7 @@ for dir in $(emacs --batch --eval '(progn(package-initialize) (dolist (x Info-di
     [^/]*) : ;;
     *)
       for file in $dir/*.info*; do
-        #echo $file
+        echo $file
         sudo install-info "$file" "$INFODIR/dir"
       done
       ;;
diff --git a/pkgs b/pkgs
index 6a6b962286f7e326aaed7508b130848407b4c8f9..f318c4e4bff64672a3a9c965731f2429dee7cc78 100644 (file)
--- a/pkgs
+++ b/pkgs
@@ -155,6 +155,7 @@ p3=(
   gpick
   grepmail
   guvcview
+  gwenview
   # for my / office hp printers
   hplip
   html-xml-utils
@@ -230,9 +231,12 @@ p3=(
   pixz
   profanity
   pry
+  # https://wiki.archlinux.org/title/bluetooth
+  pulseaudio-module-bluetooth
   pv
   python3-doc
   qemu-user-static
+  qimgv
   qrencode
   readline-doc
   rename
@@ -268,6 +272,7 @@ p3=(
   transmission-remote-gtk
   trash-cli
   tty-clock
+  units
   uuid-runtime
   vlc
   wamerican-huge
diff --git a/subdir_files/.config/gh/config.yml b/subdir_files/.config/gh/config.yml
new file mode 100644 (file)
index 0000000..241fb97
--- /dev/null
@@ -0,0 +1,17 @@
+# The current version of the config schema
+version: 1
+# What protocol to use when performing git operations. Supported values: ssh, https
+git_protocol: https
+# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment.
+editor:
+# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
+prompt: enabled
+# A pager program to send command output to, e.g. "less". Set the value to "cat" to disable the pager.
+pager:
+# Aliases allow you to create nicknames for gh commands
+aliases:
+    co: pr checkout
+# The path to a unix socket through which send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport.
+http_unix_socket:
+# What web browser gh should use when opening URLs. If blank, will refer to environment.
+browser:
diff --git a/subdir_files/.config/gh/hosts.yml b/subdir_files/.config/gh/hosts.yml
new file mode 100644 (file)
index 0000000..cf2f603
--- /dev/null
@@ -0,0 +1,5 @@
+github.com:
+    git_protocol: ssh
+    users:
+        ian-kelling:
+    user: ian-kelling
index f6bed70d572dc9e4f10d81b48bf52df0a81a0394..62a412c7833664aa49142d90c18f33f7cf8d9227 100644 (file)
@@ -105,7 +105,9 @@ if anyof (
   header :contains "list-id" "<linux-raid.vger.kernel.org>",
   header :contains "list-id" "<mailop.mailop.org>",
   header :contains "list-id" "<gcc.gcc.gnu.org>",
+  header :contains "list-id" "<union.gnu.org>",
   header :contains "list-id" "<lilypond-devel.gnu.org>",
+  header :contains "list-id" "<libc-alpha.sourceware.org>",
   header :contains "list-id" "<xmonad.haskell.org>") {
   if header :regex "list-id" "<([a-z_0-9-]+)[.@]" {
     set :lower "listname" "${1}";
index f6bed70d572dc9e4f10d81b48bf52df0a81a0394..62a412c7833664aa49142d90c18f33f7cf8d9227 100644 (file)
@@ -105,7 +105,9 @@ if anyof (
   header :contains "list-id" "<linux-raid.vger.kernel.org>",
   header :contains "list-id" "<mailop.mailop.org>",
   header :contains "list-id" "<gcc.gcc.gnu.org>",
+  header :contains "list-id" "<union.gnu.org>",
   header :contains "list-id" "<lilypond-devel.gnu.org>",
+  header :contains "list-id" "<libc-alpha.sourceware.org>",
   header :contains "list-id" "<xmonad.haskell.org>") {
   if header :regex "list-id" "<([a-z_0-9-]+)[.@]" {
     set :lower "listname" "${1}";
index f1cb21272e6a057b41d5e49554b765fc1e98af90..edfc30ac827e7a5ca78443c2c23a4ef44b77c792 100644 (file)
@@ -64,7 +64,7 @@ host2_only=false
 force=false
 force_arg=
 pull_reexec=false
-mp_args="-m /o,/a,/ar,/q,/qd,/qr"
+mp_args="-m /o,/a,/q,/qd,/qr"
 check_installed=false
 orig_args=("$@")
 if ! temp=$(getopt -l check-installed,force,pull-reexec,help afioh "$@"); then
@@ -166,7 +166,7 @@ case $direction in
         /usr/local/{bin/{unsaved-buffers{,.el},switch-mail-host},lib/bash-bear}
       )
       m scp -F $HOME/.ssh/confighome \
-        ${files@/#/root@$old_host:} $tmpd
+        ${files[@]/#/root@$old_host:} $tmpd
       diff=false
       for f in ${files[@]}; do
         if ! diff -q $tmpd/${f##*/} $f; then
@@ -214,7 +214,7 @@ esac
 if $mail_only; then
   mp_args="-m /o"
 elif $host2_only; then
-  mp_args="-m /a,/ar,/q,/qd,/qr"
+  mp_args="-m /a,/q,/qd,/qr"
 fi
 
 if ! $force; then
@@ -344,15 +344,18 @@ fi
 e Running main btrbk
 m btrbk-run -v --fast $bbk_args $force_arg $incremental_arg -m /o || ret=$?
 if (( ret )); then
-  bang="$(printf "$(tput setaf 5)█$(tput sgr0)%.0s" 1 2 3 4 5 6 7)"
+  bang="███████"
   e $bang failed btrbk of /o. restoring old host as primary
-  m $old_shell /a/exe/primary-setup localhost
+  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.
+  fi
+  e finished restoring old host as primary, now exiting $ret due to earlier failed btrbk of /o.
   exit $ret
 fi
 
 # new system is usable at this point
 blocks=██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
-printf "%s\n" "$(tput setaf 5 2>/dev/null ||:)${blocks:0:${COLUMNS:-180}}$(tput sgr0 2>/dev/null||:)"
+printf "%s\n" "${blocks:0:${COLUMNS:-100}}"
 
 # once I accidentally accepted incoming mail on old host. I used this script to copy over that mail:
 #
index c0ed8a5094fcdadd153b291e4461dbf7251b37d2..825604e8421e21698b066860f0fb8dfe3807b471 100755 (executable)
@@ -57,13 +57,17 @@ if $trust; then
     fi
   fi
 
-  rm -fv /etc/systemd/resolved.conf.d/untrusted-network.conf
+  # https://github.com/jonathanio/update-systemd-resolved
+  # suggests this will help prevent leakage into a vpn interface
+  cat >/etc/systemd/resolved.conf.d/untrusted-network.conf <<EOF
+Domains=~.
+EOF
 else  #untrusted
   # https://wiki.archlinux.org/index.php/Systemd-resolved#Manually
   cat >/etc/systemd/resolved.conf.d/untrusted-network.conf <<EOF
 [Resolve]
 DNS=${servers[@]}
-Domains=b8.nz
+Domains=~. b8.nz
 DNSOverTLS=yes
 EOF
 
index 095f3ad94492c2c42cb809e25482e2d90c0bbddf..c94ca8ba3b48243ec918a58d4054e7adaee5e4b7 100644 (file)
@@ -4,7 +4,7 @@ set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
 if pgrep -G iank -u iank -f '^emacs --daemon$' &>/dev/null; then
   elisp=$(cat /usr/local/bin/unsaved-buffers.el)
-  emacsout=$(sudo -u iank env XDG_RUNTIME_DIR=/run/user/1000 emacsclient --eval "$elisp")
+  emacsout=$(sudo -u iank env XDG_RUNTIME_DIR=/run/user/1000 emacsclient --eval "$elisp" ||:)
   bufs=$(printf "%s\n" "$emacsout"|sed '/^"nil"$/d;s/^"(/E: /;s/)"$//')
   if [[ $bufs ]]; then
     echo "error: on $HOSTNAME, unsaved emacs files: $bufs" >&2