X-Git-Url: https://iankelling.org/git/?a=blobdiff_plain;ds=sidebyside;f=brc2;h=3edbae0f26a47871cf1e0d733f570d0b935f8995;hb=HEAD;hp=12586845d49bad0468673dd5d041ae5463830367;hpb=090c49e2a17530c560488270e681d3e23046afc5;p=distro-setup
diff --git a/brc2 b/brc2
index 1258684..32f0065 100644
--- a/brc2
+++ b/brc2
@@ -1,6 +1,25 @@
#!/bin/bash
-# Copyright (C) 2019 Ian Kelling
-# SPDX-License-Identifier: AGPL-3.0-or-later
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to switch
+# its license to GPL.
+
+# Copyright 2024 Ian Kelling
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
# this gets sourced. shebang is just for file mode detection
@@ -9,10 +28,30 @@
if [[ $LESSHISTFILE == - ]]; then
HISTFILE=
c() { cd "$@"; }
-elif [[ $HISTFILE ]]; then
- HISTFILE=$HOME/.bh
+elif [[ $HISTFILE == $HOME/.bash_history ]]; then
+ # 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
+# 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
@@ -43,11 +82,16 @@ export WCDHOME=/a
case $EUID in
0)
# shellcheck disable=SC2034 # used in brc
- SL_SSH_ARGS="-F $HOME/.ssh/confighome"
+ SL_SSH_ARGS="-F /root/.ssh/confighome"
+ dsh() {
+ command dsh -o -F/root/.ssh/confighome -c "$@"
+ }
;;
esac
+
+
# * include files
# generated instead of dynamic for the benefit of shellcheck
@@ -58,19 +102,19 @@ source /a/bin/log-quiet/logq-function
source /a/bin/bash_unpublished/source-semi-priv
source /a/bin/bash_unpublished/source-state
-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
-
+if [[ $HOSTNAME == "$MAIL_HOST" ]]; then
+ export MAIL_HOST_P=t
+else
+ export NOT_MAIL_HOST_P=t
+fi
source /a/bin/ds/beet-data
# * functions
+
+
multimic() {
local i
local -a sources
@@ -79,7 +123,7 @@ multimic() {
m pactl unload-module module-null-sink
m pactl unload-module module-remap-source
- IFS=" " read -r -a sources <<<"$(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p')"
+ IFS=" " read -r -a sources <<<"$(pactl -f json list sources|jq -r '.[].name')"
if (( ! $# )); then
i=0
@@ -126,6 +170,12 @@ zcheck() {
ssh bow rm /tmp/oegu.jpg
feh /t/oegu.jpg
}
+zmon() {
+ while true; do
+ ziva-screen
+ sleep 15
+ done
+}
slemacs() {
local arg rtime v
@@ -147,7 +197,7 @@ slemacs() {
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
@@ -182,6 +232,20 @@ rm-docker-iptables() {
# usage mkschroot [-] distro codename packages
# - means no piping in of sources.list
+#
+# note some useful post mkschroot i've used in the past
+# tu /nocow/schroot/flidas/etc/sudoers </var/spool/exim4/gw/.no-delay-eximids
+ if [[ $EUID == 0 ]]; then
+ chown iank:iank /var/spool/exim4/gw/.no-delay-eximids
+ fi
+}
+edelayon() {
+ echo >/var/spool/exim4/gw/.no-delay-eximids
+ if [[ $EUID == 0 ]]; then
+ chown iank:iank /var/spool/exim4/gw/.no-delay-eximids
+ fi
+}
+
eqgo() {
- enn -M "$(exiqgrep -i -r.\*)"
+ local -a array tmpstr delayon
+ delayon=true
+ if grep -qFx all /var/spool/exim4/gw/.no-delay-eximids; then
+ delayon=false
+ fi
+ if $delayon; then
+ echo all >/var/spool/exim4/gw/.no-delay-eximids
+ fi
+ tmpstr=$(exiqgrep -i -r.\*)
+ mapfile -t array <<<"$tmpstr"
+ enn -M "${array[@]}"
+ if $delayon; then
+ echo >/var/spool/exim4/gw/.no-delay-eximids
+ fi
}
eqgo1() {
- enn -M "$(exipick -i -r.\*|h1)"
+ local eid
+ eid="$(exipick -i -r.\*|h1)"
+ sed -n "/^all$/p;\$a $eid" /var/spool/exim4/gw/.no-delay-eximids
+ enn -M "$eid"
+}
+# exim -M (in namespace and without delay)
+ennm() {
+ local eid
+ for eid; do
+ printf "%s\n" "$eid" >>/var/spool/exim4/gw/.no-delay-eximids
+ done
+ enn -M "$@"
}
@@ -323,11 +424,17 @@ cp-blocked-domains-to-ansible() {
}
-anki() {
- # crashes on adding new cards in t9
- schroot -c buster -- anki
+daycat() {
+ ngset
+ hrcat /m/md/daylert/{cur,new}/*
+ ngreset
+}
+dayclear() {
+ ngset
+ rm -f /m/md/daylert/{cur,new}/*
}
+
acat() {
ngset
hrcat /m/md/alerts/{cur,new}/*
@@ -348,10 +455,11 @@ alerts() {
}
ralerts() { # remote alerts
local ret shell
- # this list is duplicated in check-remote-mailqs
- for h in bk je li frodo kwwg x3wg x2wg kdwg sywg; do
+ local -a active_hosts
+ source /p/c/domain-info
+ for h in ${active_hosts[@]}; do
echo $h:
- shell="ssh $h"
+ shell="ssh $h.b8.nz"
if [[ $HOSTNAME == "${h%wg}" ]]; then
shell=
fi
@@ -366,7 +474,7 @@ ralerts() { # remote alerts
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() {
@@ -387,24 +495,56 @@ astudio() {
/a/opt/android-studio/bin/studio.sh "$@" & r
}
-# convert brains path to url
-# /f/brains/sysadmin/interns/2022/nick_shrader/intro_blog_post.mdwn
-# becomes
-# https://brains.fsf.org/wiki/sysadmin/interns/2022/nick_shrader/intro_blog_post
-iki() {
- local url path
+# Convert brains file path to url and vice versa
+# usage: brains [URL_OR_PATH]
+brains() {
+ _iki-convert brains.fsf.org/wiki "$@"
+}
+glue() {
+ _iki-convert gluestick.office.fsf.org "$@"
+}
+
+# usage: see above
+_iki-convert() {
+ local url url_prefix path input repo_dir dir url_dir url name
+ url_prefix="$1"
+ name="${url_prefix%%.*}"
+ repo_dir="/f/$name"
+ shift
if [[ $1 ]]; then
- path="$*"
+ input="$*"
else
- read -r -p "enter path" path
+ read -r -p "enter path or url"$'\n' input
fi
- url=$(readlink -f "$path")
- url="https://brains.fsf.org/wiki/${url#*brains/}"
- url="${url%.mdwn}"
- echo "$url"
-
+ 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"
+ ;;
+ *)
+ path=$(fp "$input")
+ url_dir=$(echo "$path" | sed -r "s,^(/a)?$repo_dir/,,")
+ url="https://$url_prefix/$url_dir"
+ if [[ $url == *.mdwn ]]; then
+ url="${url%.mdwn}/"
+ fi
+ j echo "$url"
+ ;;
+ esac
}
+
# Generate beet smartplaylists for navidrome.
# for going in the reverse direction, run
# /b/ds/navidrome-playlist-export
@@ -525,7 +665,24 @@ EOF
# beet playlist. use beetag with a playlist name
bpl() {
- eval beetag -r "$@" "${bpla[${@: -1}]}"
+ local playlist playlist_regex
+ case $1 in
+ -h|--help)
+ for playlist in "${!bpla[@]}"; do
+ printf "%s\n" "$playlist"
+ done
+ return 0
+ ;;
+ esac
+
+ playlist="${*: -1}"
+ playlist_regex='[a-z0-9_]'
+ if [[ ! $playlist =~ $playlist_regex ]]; then
+ echo "bpl: error unexpected chars in playlist: $playlist"
+ return 1
+ fi
+ # all but last arg as options
+ eval beetag -r "${*:1:$# - 1}" "${bpla[$playlist]}"
}
complete -W "${!bpla[*]}" bpl
@@ -538,31 +695,156 @@ beetmq() {
sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:)
rm "$tmpf"
- scrolled+=1
+ beetag-nostatus 1
+}
+
+kill-bg-quiet() {
+ # https://stackoverflow.com/a/5722874
+ kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||:
+}
+
+# debug variables
+dv() {
+ for arg; do
+ printf "%s=%s " "$arg" "${!arg}"
+ done
+ echo
}
# Must be called from beetag for variables to be setup
beetag-help() {
+ local -i i j col_total row col button_total row_total remainder_cols remainder_term
+ col_total=4
+ button_total=${#button_map[@]}
+ row_total=$(( button_total / col_total ))
+ remainder_cols=$(( button_total % col_total ))
+ # for debugging
+ #dv button_total row_total remainder_cols
+ beetag-nostatus
# - 3 is just a constant that helps things work in practice.
if [[ $LINES ]] && (( LINES - 3 < scrolled )); then
hr
- for (( i=0; i<${#button_map[@]}; i++)); do
- if (( i % 3 == 2 )); then
- printf "%s %s\n" ${buttons[i]} ${button_map[i]}
+ for (( i=0; i/<- skip
+
+
+y other genres z fg player ' = toggle play 1-5 rate ] repeat1
+; previous _ = delete up/down skip mpv vol,pause,seek
EOF
hr
scrolled=10
fi
}
+# Must be called from beetag for variables to be setup
+beetag-nostatus() {
+ if (( $# )); then
+ scrolled=$(( scrolled + $1 ))
+ fi
+ if $erasable_line; then
+ # https://stackoverflow.com/a/71286261
+ # erase line / delete line in terminal
+ printf '\033[1A\033[K'
+ fi
+ erasable_line=false
+}
+# meant to be called from beetag
+beetag-status() {
+ if $erasable_line; then
+ # https://stackoverflow.com/a/71286261
+ printf '\033[1A\033[K'
+ fi
+ erasable_line=true
+}
+
+# meant to be called from beetag
+mpvrpc() {
+ if jobs -p | grep -q . &>/dev/null; then
+ printf "%s\n" "$*" | socat - /tmp/mpvsock >/dev/null ||:
+ fi
+}
+# meant to be called from beetag
+# o for get output
+mpvrpco() {
+ # note: testing for background jobs will output nothing if we are in a pipeline
+ printf "%s\n" "$*" | socat - /tmp/mpvsock ||:
+}
+
+# meant to be called from beetag
+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() {
+ # 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
+}
+# 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 $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" &
+ 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.
@@ -581,20 +863,27 @@ EOF
# 1-5 rate
# q quit
# ret next
-beetag() {
- local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random
- local do_rare_genres read_wait help line lsout tmp ls_line skip_lookback
- local escape_char escaped_input expected_input skip_input_regex
- local -a pl_tags buttons button_map ids tags rare_genres tmp_tags initial_ls ls_lines
+#
+# 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
+ 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 -a buttons button_map ids tags tmp_tags initial_ls ls_lines paths
local -A button_i
local -i i j volume scrolled id_count line_int skip_start pre_j_count head_count skip_lookback
local -i overflow_lines overflow
-
+ first_play=true
+ erasable_line=false
escape_char=$(printf "\u1b")
scrolled=999 # more than any $LINES
### begin arg processing ###
random=false
+ repeat1=false
+ new_random=false
case $1 in
-r)
random=true
@@ -604,6 +893,10 @@ beetag() {
random=false
shift
;;
+ -x)
+ new_random=true
+ shift
+ ;;
esac
if (( ! $# )); then
echo beetag: error expected a query arg >&2
@@ -611,23 +904,16 @@ 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
read_wait=2
doplay=true
- # because we were destined to run out of single key buttons.
- rare_genres=(
- jazz
- musical
- noise
- skit
- spoken-w
- )
last_genre_i=$(( ${#common_genres[@]} - 1 ))
- buttons=( {a..p} {r..w} 0 {6..9} , . / )
+ buttons=( {a..p} {r..w} {6..8} , . / - "=")
button_map=(${common_genres[@]} ${pl_tags[@]})
fstring=
for tag in "${pl_tags[@]}"; do
@@ -638,37 +924,125 @@ beetag() {
button_i[${buttons[i]}]=$i
done
- # TODO: use shuf --random-source=FILE to save the random sort order
- # for reusing later, so we can pickup where we left off in a playlist.
+ # note: this structure of files is rather haphazard.
+ seed_num=1 # later we might want a few
+ seed_file=seed$seed_num
+ if $random; then
+ pl_state_file=$seed_num
+ else
+ pl_state_file=sorted
+ fi
+ pl_state_dir=/b/data/pl-state
+ if [[ $playlist ]]; then
+ pl_state_dir=$pl_state_dir/$playlist
+ else
+ pl_state_dir=$pl_state_dir/nopl
+ fi
+ pl_state_path=$pl_state_dir/$pl_state_file
+ pl_seed_path=$pl_state_dir/$seed_file
+
+
+ if $new_random || [[ ! -r $pl_seed_path ]]; then
+ mkdir -p $pl_state_dir
+ { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path
+ fi
+ # PijokVipiotOzeph is just a random string for a delimiter
+ # shellcheck disable=SC2016 # false positive
+ fmt='%ifdef{rating,$rating }'"$fstring"'$genre | $title - $artist - $album $length $id PijokVipiotOzeph $path'
# shellcheck disable=SC2016 # obvious reason
- mapfile -t initial_ls < <(beet ls -f '%ifdef{rating,$rating }'"$fstring"'$genre | $artist - $album - $title $length $id' "$@" | { if $random; then sort -R; else cat; fi; } )
- j=0
- # i only care to see the head of the list.
- head_count=$(( LINES - 20 ))
+ tmpstr=$(beet ls -f "$fmt" "$@" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } )
+ mapfile -t initial_ls <<<"$tmpstr"
+ if [[ ! ${initial_ls[0]} ]]; then
+ echo "beetag: error: no result from beet ls $*"
+ return 1
+ fi
+ id_count=${#initial_ls[@]}
for line in "${initial_ls[@]}"; do
- id="${line##* }"
+ path="${line#*PijokVipiotOzeph }"
+ # https://github.com/koalaman/shellcheck/issues/2171
+ # shellcheck disable=SC2190 # bug in shellcheck, looking at paths from an earlier function
+ paths+=("$path")
+ line_no_path="${line% PijokVipiotOzeph*}"
+ id="${line_no_path##* }"
ids+=("$id")
- right_pad="${line%% |*}"
- ls_line="$(printf %-11s "$right_pad")${line#$right_pad}"
+ right_pad="${line_no_path%% |*}"
+ ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}"
ls_lines+=("$ls_line")
- if (( j < head_count )); then
+ i=$(( i+1 ))
+ done
+
+
+
+
+ j=0
+ if [[ $playlist ]]; then
+ if [[ -r $pl_state_path ]]; then
+ j=$(cat $pl_state_path)
+ fi
+ fi
+
+ # i only care to see a smallish portion of the list when starting.
+ head_count=$(( LINES - 20 ))
+ head_start=$(( j - head_count / 2 ))
+ if (( head_start < 0 )); then
+ head_start=0
+ fi
+ for (( i=head_start; i < head_count && i < id_count; i++ )); do
+ ls_line="${ls_lines[$i]}"
+ if (( i == j )); then
+ echo "* $ls_line"
+ else
echo "$ls_line"
fi
- j=$(( j+1 ))
done
- id_count=${#ids[@]}
- for (( j=0; j < id_count; j++ )); do
+ if $doplay; then
+ #{ mpv --profile=a --volume=$volume --idle 2>&1 & } 2>/dev/null
+ mpv --profile=a --volume=$volume --idle &
+ # if we dont sleep, can expect an error like this:
+ # socat[1103381] E connect(5, AF=1 "/tmp/mpvsock", 14): Connection refused
+ sleep .1
+ fi
+
+ while true; do
id=${ids[j]}
+ path="${paths[$j]}"
lsout="${ls_lines[j]}"
tags=( ${lsout%%,*} )
beetag-help
printf "ââ %s\n" "$lsout"
- scrolled+=1
+ beetag-nostatus 1
if $doplay; then
# https://stackoverflow.com/a/7687716
# note: duplicated down below
- { beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
+ #
+ # notes on old method of invoking mpv each time:
+ # https://superuser.com/questions/305933/preventing-bash-from-displaying-done-when-a-background-command-finishes-execut
+ # we can't disown or run in a subshell or set +m because all that
+ # disabled job control from working properly in ways we want.
+ # todo: figure out some kind of answer to this. I think the solution
+ # is that we are waiting in 2 second intervals and checking if the
+ # background job exists. Instead, we should make mpv just idle
+ # when it is done with a song and then send it a command to play a new track.
+ #{ mpv --profile=a --volume=$volume "$path" 2>&1 & } 2>/dev/null
+ # old
+ #{ beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
+
+ # on slow systems, we may need to wait like .3 seconds before mpv
+ # is ready. so impatiently check until it is ready
+ if $first_play; then
+ 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-loadfile "$path" 2>/dev/null
+ break
+ fi
+ sleep .1
+ done
+ else
+ mpvrpc-loadfile "$path"
+ fi
+ erasable_line=false
fi
while true; do
char=
@@ -679,7 +1053,8 @@ beetag() {
# Automatically skip to the next song if this one ends, unless
# we turn off the autoplay.
if (( ret == 142 )) || [[ ! $char ]]; then
- if bg %% &>/dev/null; then
+ if jobs -p | grep -q . &>/dev/null && \
+ [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' | jq .data) == false ]]; then
continue
else
break
@@ -690,13 +1065,10 @@ beetag() {
fi
beetag-help
if [[ $char == $'\n' ]]; then
- # https://stackoverflow.com/a/5722874
- kill %%; wait %% 2>/dev/null ||:
break
fi
case $char in
";")
- kill %%; wait %% 2>/dev/null ||:
j=$(( j - 2 ))
break
;;
@@ -706,44 +1078,52 @@ beetag() {
doplay=false
else
doplay=true
- kill %%; wait %% 2>/dev/null ||:
- { beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
+ mpvrpc-loadfile "$path"
+ erasable_line=false
fi
- scrolled+=1
+ beetag-nostatus 1
continue
;;
_)
- kill %%; wait %% 2>/dev/null ||:
m beet rm --delete --force "id:$id"
- scrolled+=4 # guessing. dont want to test atm
+ beetag-nostatus 4 # guessing. dont want to test atm
break
;;
[1-5])
beetmq "id:$id" rating=$char
continue
;;
- -)
+ 9)
volume=$(( volume - 5 ))
if (( volume < 0 )); then
volume=0
fi
- echo volume=$volume
- scrolled+=1
- continue
- ;;
- q)
- kill %%; wait %% 2>/dev/null ||:
- return
- ;;
- +)
+ ;;&
+ 0)
volume+=5
if (( volume > 130 )); then
volume=130
fi
+ ;;&
+ 0|9)
+ mpvrpc '{ "command": ["set_property", "volume", '$volume'] }'
+ beetag-status
echo volume=$volume
- scrolled+=1
continue
;;
+ ']')
+ if $repeat1; then
+ repeat1=false
+ else
+ repeat1=true
+ fi
+ echo repeat1=$repeat1
+ continue
+ ;;
+ q)
+ kill-bg-quiet
+ return
+ ;;
y)
if $do_rare_genres; then
do_rare_genres=false
@@ -764,7 +1144,7 @@ beetag() {
continue
;;
z)
- scrolled+=3
+ beetag-nostatus 3
# if we ctrl-z, it will put the whole function into sleep. so
# basically, we can't return from a foregrounded mpv like we
# would like to without some strange mechanism I can't think
@@ -776,17 +1156,35 @@ beetag() {
fi
continue
;;
+
+ #
+ " ")
+ # output time if we aren't already paused
+ if [[ $(mpvrpco '{ "command": ["get_property", "pause"] }' | jq .data) == false ]]; then
+ # minutes/seconds
+ #date -d @"$(mpvrpco '{ "command": ["get_property", "playback-time"] }' | jq .data)" +%M:%S ||:
+ beetag-status
+ mpvrpc-percent-pos
+ fi
+ # originally found this solution, which worked fine.
+ #kill -STOP %% &>/dev/null
+ #
+ mpvrpc '{ "command": ["cycle", "pause"] }'
+ continue
+ ;;
"$escape_char")
expected_input=true
read -rsn2 escaped_input
skip_input_regex="^[0-9]+$"
case $escaped_input in
- '[D')
- skip_lookback=5
+ # up char: show all the songs, use less
+ '[A')
skip_start=0
+ skip_lookback=5
if (( j - skip_lookback > skip_start )); then
skip_start=$(( j - skip_lookback ))
fi
+ beetag-nostatus $(( id_count - skip_start - 1 ))
{
line_int=0
for (( i=skip_start; i < id_count; i++ )); do
@@ -798,18 +1196,20 @@ beetag() {
line_int+=1
done
} | less -F
- scrolled+=$(( id_count - skip_start - 1 ))
;;
- '[C')
+ # down char
+ '[B')
# skip forward, but show the last few songs anyways.
skip_start=0
skip_lookback=3
if (( j - skip_lookback > skip_start )); then
skip_start=$(( j - skip_lookback ))
fi
+ beetag-nostatus $(( id_count - skip_start - 1 ))
+
line_int=0
overflow_lines=$LINES
- for (( i=skip_start; i < overflow_lines - 1; i++ )); do
+ for (( i=skip_start; i < overflow_lines - 1 && i < id_count; i++ )); do
ls_line="${ls_lines[i]}"
overflow=$(( ${#ls_line} / ( COLUMNS - 1 ) ))
overflow_lines=$(( overflow_lines - overflow ))
@@ -820,7 +1220,21 @@ beetag() {
echo "$line_int | $ls_line"
line_int+=1
done
- scrolled+=$(( id_count - skip_start - 1 ))
+ ;;
+ # left key
+ '[D')
+ seek_sec=-8
+ ;;&
+ # right key
+ '[C')
+ seek_sec=8
+ ;;&
+ '[C'|'[D')
+ beetag-status
+ mpvrpc-percent-pos
+ erasable_line=true
+ mpvrpc '{ "command": ["seek", "'$seek_sec'"] }'
+ continue
;;
*)
expected_input=false
@@ -828,9 +1242,9 @@ beetag() {
esac
if $expected_input; then
read -r skip_input
- case skip_input in
+ case $skip_input in
q)
- kill %%; wait %% 2>/dev/null ||:
+ kill-bg-quiet
return
;;
esac
@@ -841,7 +1255,6 @@ beetag() {
j=$(( j - 1 ))
fi
fi
- kill %%; wait %% 2>/dev/null ||:
break
fi
;;
@@ -873,6 +1286,16 @@ beetag() {
fi
fi
done
+ if ! $repeat1; then
+ if (( j < id_count - 1 )); then
+ j+=1
+ else
+ j=0
+ fi
+ fi
+ if [[ $playlist ]]; then
+ echo $j >$pl_state_path
+ fi
done
}
@@ -908,6 +1331,8 @@ beet2nav() {
# pull in beets library locally
beetpull() {
+ local sshfs_host sshfs_cmd
+ sshfs_host=b8.nz
if [[ $HOSTNAME == kd ]]; then
return 0
fi
@@ -915,8 +1340,9 @@ beetpull() {
s mkdir /i
s chown iank:iank /i
fi
- if ! mountpoint /i &>/dev/null; then
- m sshfs b8.nz:/i /i
+ sshfs_cmd="sshfs -o ServerAliveInterval=15,reconnect $sshfs_host:/i /i"
+ if ! pgrep -f "^$sshfs_cmd$" >/dev/null; then
+ m $sshfs_cmd
fi
}
@@ -996,6 +1422,109 @@ beegenre() {
rm $tmpf
}
+# prettify the date
+btrbk-date() {
+ local indate
+ indate="$1"
+ shift
+ date +%F_%T%:::z -d "$(sed -r 's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<"$indate")" "$@"
+}
+btrbk-undate() {
+ # fudCaHougfirp is a random string
+ { if [[ $1 ]]; then
+ echo "$1"
+ else
+ cat
+ fi
+ } | sed -r 's/-0([45])( |$)/fudCaHougfirp0\100/;s/_/T/;s/[:-]//g;s/fudCaHougfirp/-/'
+
+}
+btrbk-date-sed() {
+ local line
+ while read -r line; do
+ if [[ $line == *20[0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]-0[45]00* ]]; then
+ pre="${line%%20[0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]-0[45]00*}"
+ post="${line##*20[0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]-0[45]00}"
+ mid="${line:${#pre}:22}"
+ echo "$pre$(btrbk-date "$mid")$post"
+ else
+ echo "$line"
+ fi
+ done
+}
+jrbtrbk() {
+ jr -u btrbk-run -u btrbk -u switch-mail-host "$@"
+}
+
+# internal function
+btrbk-host-debug-show-host() {
+ for f; do
+ snaphost=
+ for host in $remote $alt local; do
+ if line=$(grep -P "\S*$f" /tmp/b/s/$host.log); then
+ if [[ $snaphost ]]; then
+ e error: snaphost=$snaphost, host=$host line="$line"
+ fi
+ if [[ $line == ssh* ]]; then
+ tmp="${line#ssh://}"
+ snaphost="${tmp%%/*}"
+ else
+ snaphost=$host
+ fi
+ fi
+ done
+ echo $snaphost $f | btrbk-date-sed
+ done
+}
+
+# If we get a btrfs receive error like this:
+# ERROR: ... clone: did not find source subvol
+# running this command will help track down the problem.
+# Alter remote= and alt=. When I used it, remote is
+# the host having the error when I push a snapshot.
+# Alt is just the other host that takes snapshots
+# besides the local host.
+btrbk-host-debug() {
+
+ remote=b8.nz
+ alt=sywg.b8.nz
+
+ mkdir -p /tmp/b/s
+ for host in $remote $alt; do
+ h=$(ssh $host hostname)
+ rsync -a /var/log/btrbk $host:/var/log/btrbk /var/log/btrbk/$h
+ grr '\bsnapshot success' /var/log/btrbk/$h >/tmp/b/$h.log
+
+ ## this takes a while, we only want to do it on 1st run
+ # if [[ -s /tmp/b/$host.log ]]; then continue; fi
+ # ssh $host journalctl -u btrbk-run -u btrbk -u switch-mail-host >/tmp/b/$host.log
+ done
+ gr '\bsnapshot success' /var/log/btrbk/*.log >/tmp/b/local.log
+ cd /tmp/b
+ for f in *.log; do
+ gr '\bsnapshot success' $f >s/$f
+ done
+ cd /mnt/root/btrbk
+ localq=(q.*)
+ declare -A localq_a
+ for f in "${localq[@]}"; do
+ localq_a[$f]=t
+ done
+
+ remoteq=()
+ for f in $(ssh $remote "cd /mnt/root/btrbk; echo q.*"); do
+ if [[ ! ${localq_a[$f]} ]]; then
+ remoteq+=($f)
+ fi
+ done
+ btrbk-host-debug-show-host "${localq[@]}"
+ if (( ${#remoteq[@]} >= 1 )); then
+ echo "=== $remote only ===="
+ btrbk-host-debug-show-host ${remoteq[@]}
+ fi
+
+}
+
# note, to check for glue records
# First, find some the .org nameservers:
# dig +trace iankelling.org
@@ -1008,7 +1537,6 @@ beegenre() {
# $ 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 /
@@ -1026,8 +1554,6 @@ bbk() { # btrbk wrapper
return 1
;;
esac
- # run latest
- install-my-scripts
# todo: consider changing this to srun and having the args come
# from a file like /etc/default/btrbk, like is done in exim
s jdo btrbk-run "$@"
@@ -1081,14 +1607,16 @@ scr() {
# tried to use ceb2txt but it failed because of schema
# slightly different than what it expected.
cheogram-get-logs() {
- adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
+ #adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
read -r -p "do cheogram backup on phone, do not enable extra cheogram data. press any key when done"
cd /p/cheogram
rm -rf Backup b
adb pull /storage/emulated/0/Download/Cheogram/Backup
sqlite3 b $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
@@ -1123,108 +1659,16 @@ order by timeSent;"
sqlite3 /p/cheogram/b ".mode tabs" "$q" | sed 's/ /./' | less
}
-# version of jdo for my non-root user
-jdo() {
- # comparison of alternative logging methods:
- #
- # systemd-run command (what this function does)
- #
- # If there is a user prompt, the program will detect that it is not
- # connected to a terminal and act in a non-interactive way, skipping
- # the prompt. This has the benefit that you know exactly how the
- # program will act if you want to move it into a service that runs
- # automatically.
- #
- # If run with sudo and command is a shell script which does a sleep,
- # it can (sometimes?) output some extra whitespace in front of
- # messages, more for each subsequent message. This can be avoided by
- # becoming root first.
- #
- # It logs the command's pid and exit code, which is nice.
- #
- #
- ### command |& ts | tee file.log
- #
- # If there is a user prompt, like "read -p prompt var", it will hang
- # without outputting the prompt.
- #
- # I've had a few times where ts had an error and I wasn't totally sure
- # if it was really the command or ts having the problem.
- #
- # Sometimes some output will get hidden until you hit enter.
- #
- #
- ### command |& pee cat logger
- #
- # This seems to work. I need to test more.
- #
- #
- ### command |& logger -s
- #
- # User prompts get confusingly prefixed to earlier output, and all log
- # entries get prefixed with annoying priority level.
- #
- #
- ### systemd-cat
- #
- # Had a few problems. One major one is that it exited in the middle of
- # a command on systemctl daemon-reload
- #
- # Related commands which can log a whole session: script, sudo, screen
- local cmd cmd_name jr_pid ret
- ret=0
- cmd="$1"
- shift
- cmd_name=${cmd##*/}
- if [[ $cmd != /* ]]; then
- cmd=$(type -P "$cmd")
- fi
- # -q = quiet
- journalctl -qn2 -f -u "$cmd_name" &
- # Trial and error of time needed to avoid missing initial lines.
- # .5 was not reliable. 1 was not reliable. 2 was not reliable
- sleep 4
- jr_pid=$!
- # 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
- # and it will leave the unit hanging around in a failed state needing manual
- # killing of the process.
- s systemd-run --uid "$(id -u)" --gid "$(id -g)" \
- -E SSH_AUTH_SOCK=/run/openssh_agent \
- --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
- # The sleep lets the journal output its last line
- # before the prompt comes up.
- sleep .5
- kill $jr_pid &>/dev/null ||:
- unset jr_pid
- fg &>/dev/null ||:
- # this avoids any err-catch
- (( ret == 0 )) || return $ret
-}
-
-# service run, and watch the output
-srun() {
- local unit
- ret=0
- unit=$1
- journalctl -qn2 -f -u $unit &
- systemctl start $unit
- sleep 2
- kill $jr_pid &>/dev/null ||:
- unset jr_pid
- fg &>/dev/null ||:
-}
sm() { # switch mail host
local tmp keyhash
c /
# run latest
keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
- tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
+ tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"' ||:)
if [[ ! $tmp ]]; then
s ssh-add /root/.ssh/home
fi
- install-my-scripts
s jdo switch-mail-host "$@"
return $ret
}
@@ -1249,7 +1693,15 @@ 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})
- p=(/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}
+ /a/f/ans/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter
+ /a/opt/fpaste
+ /a/opt/bbdb-csv-import
+ /a/opt/spray
+ /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
@@ -1262,14 +1714,19 @@ lipush() {
return $ret
}
bkpush() { # no emacs. for running faster.
- p=(/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}
+ /a/f/ans/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter
+ )
a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
ret=0
m rsync "$@" $a ${p[@]} /p/c/machine_specific/bk root@bk.b8.nz:/ || ret=$?
return $ret
}
jepush() { # no emacs. for running faster.
- p=(/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}
+ /a/f/ans/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter
+ )
+
a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
ret=0
m rsync "$@" $a ${p[@]} /p/c/machine_specific/je root@je.b8.nz:/ || ret=$?
@@ -1280,19 +1737,13 @@ bindpush() {
dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
lipush
for h in li bk; do
- m sl $h <<'EOF'
-source ~/.bashrc
-m dnsup
-EOF
+ m ssh iank@$h.b8.nz dnsup
done
}
bindpushb8() {
lipush
for h in li bk; do
- m sl $h <<'EOF'
-source ~/.bashrc
-m dnsb8
-EOF
+ m ssh $h.b8.nz dnsb8
done
}
@@ -1303,8 +1754,18 @@ dnsup() {
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
}
@@ -1345,6 +1806,21 @@ dsign() {
done
}
+# 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 =~ $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
+ fi
+}
#### begin bitcoin related things
btc() {
@@ -1379,6 +1855,18 @@ satoshi() { # $1 satoshi in usd
printf "$%.2f\n" "$(echo "scale=10; $price * $1"| bc -l)"
fi
}
+
+# Bitcoin holds open the wallet file. this causes problems for a
+# 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.
+# 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)
+#
#### end bitcoin related things
@@ -1404,13 +1892,82 @@ capache()
fi
}
-chrome() {
- if type -p chromium &>/dev/null; then
- cmd=chromium
+
+
+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
+# programs. If a small program grows beyond 300 lines, I plan to change
+# to a recommended GPL license.
+
+# Copyright 2024 Ian Kelling
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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"
+ else
+ {
+ apache-header
+ cat "$file"
+ } | sponge "$file"
+ fi
+ done
+}
+# strip out the apache license from a file.
+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
+}
+
+chro() {
+ if type -p chromium &>/dev/null; then
+ cmd=chromium
else
cd /
- cmd="schroot -c bullseye chromium"
- CHROMIUM_FLAGS='--enable-remote-extensions' $cmd & r
+ cmd="schroot -c bookworm chromium"
+ CHROMIUM_FLAGS='--enable-remote-extensions' $cmd "$@" & r
fi
}
@@ -1452,10 +2009,6 @@ digme() {
digdiff @ns{1,2}.iankelling.org "$@"
}
-tsr() { # ts run
- "$@" |& ts || return $?
-}
-
dup() {
local ran_d
ran_d=false
@@ -1508,7 +2061,7 @@ fastboot() {
kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
-bat() {
+batp() {
cat /sys/class/power_supply/BAT0/capacity
}
@@ -1695,8 +2248,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
- 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
@@ -1716,7 +2269,7 @@ rename-test() {
feh() {
# F = fullscren, z = random, Z = auto zoom
- command feh -FzZ "$@"
+ command feh --auto-rotate -FzZ "$@"
}
@@ -1739,7 +2292,7 @@ gpg() {
}
gse() {
- local email=ian@iankelling.org
+ local email=iank@fsf.org
git send-email --notes "--envelope-sender=<$email>" \
--suppress-cc=self "$@"
}
@@ -1748,8 +2301,8 @@ gup() { /a/f/gnulib/build-aux/gnupload "$@"; }
dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
+# do git status on published repos.
hstatus() {
- # do git status on published repos.
c /a/bin/githtml
for x in *; do
cd "$(readlink -f $x)"/..
@@ -1763,19 +2316,32 @@ hstatus() {
done
}
-# work log
+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
wlog() {
local day i days_back
days_back=${1:-16}
- for (( i=0; i=0; i-- )); do
day=$( date +%F -d @$((EPOCHSECONDS - 86400*i )) )
date "+%a %b %d" -d @$((EPOCHSECONDS - 86400*i )) | tr '\n' ' '
/a/opt/timetrap/bin/t d -ftotal -s $day -e $day all -m '^w|lunch$'
done
}
-to() { t out -a "$@"; }
-ti() { t in -a "$@"; }
-tl() {
+to() { we t out -a "$@"; }
+ti() { we t in -a "$@"; }
+
+_tl() {
local in_secs
to "$*"
t s lunch
@@ -1784,27 +2350,116 @@ tl() {
m t out -a "$(date +%F.%T -d @$(( in_secs + 60*45 )) )"
t s w
}
+tl() {
+ we _tl "$@"
+}
+
+# help me focus. opens 2 windows.
+focus() {
+ /p/c/proc/focus/linux-amd64/focus &
+ watcharb5
+ kill %%
+}
+
+
+# Display a list of the active window title
+# i've been on with 10 second samples going back
+# 5 minutes. If I've been on one window for 10 seconds
+# or longer, then display the second count.
+#
+# Press any key to exit.
+watcharb5() {
+ local char ret
+ killall arbtt-capture &>/dev/null ||:
+ rm -f ~/.arbtt/capture.log
+ arbtt-capture --sample-rate=10 &
+ while true; do
+ arb5
+ ret=0
+ # i first thought to sleep and capture ctrl-c, but it seems we can't
+ # capture control-c, unless maybe we implement the commands in a
+ # separate script or maybe add err-cleanup to err. Anyways, this
+ # method is superior because any single char exits.
+ read -rsN1 -t 5 char || ret=$?
+ if (( ret == 142 )) || [[ ! $char ]]; then
+ # debug
+ #e ret=$ret char=$char
+ :
+ else
+ killall arbtt-capture ||:
+ return 0
+ fi
+ done
+
+}
+
+arb5() {
+ local i j l sec blanks line
+ local -a arbtt_lines
+ if [[ ! -e ~/.arbtt/capture.log ]]; then
+ sleep 5
+ fi
+ blanks=$(( LINES - 34 ))
+ for (( i=0; i < blanks; i++ )); do
+ echo
+ done
+
+ {
+ i=0
+ j=0
+ # https://stackoverflow.com/questions/56486272/how-to-concat-multiple-fields-to-same-line-with-jq
+ arbtt_lines=$(arbtt-dump -l 30 -t json | \
+ jq -r '.[] | [ ( .inactive / 1000 | floor ) , ( .windows[] | select (.active == true) |.title) ] | @tsv' | tac)
+ for line in "${arbtt_lines[@]}"; do
+ read -r sec l <<<"$line"
+ if (( j >= LINES )); then
+ break
+ fi
+ if (( i % 6 == 0 && i >= 2 )); then
+ j=$(( j + 1 ))
+ echo "## $(( i / 6 + 1 )) ##"
+ fi
+ if (( sec > 10 )); then
+ printf "%3d %s\n" $sec "$l" | sed -r "s/^(.{$COLUMNS}).*/\1/"
+ else
+ printf " %s\n" "$l" | sed -r "s/^(.{$COLUMNS}).*/\1/"
+ fi
+ i=$(( i + 1 ))
+ j=$(( j + 1 ))
+ done
+ while (( j < 34 && j < LINES )); do
+ echo
+ j=$(( j + 1 ))
+ done
+ } | tac
+}
-arbttlog() { arbtt-dump "$@" | grep -v '( )\|Current Desktop' | sed -rn '/^[^ ]/{N;s/^(.{21})([0-9]*)[0-9]{3}m.*\(\*/\1\2/;s/^(.{21})[0-9]*.*\(\*/\1/;s/\n//;p}' ; }
+arbttlog() {
+ # from the log, show only the currently active window, and the number of
+ # seconds of input inactivity.
+ arbtt-dump "$@" | grep -v '( )\|Current Desktop' | sed -rn '/^[^ ]/{N;s/^(.{21})([0-9]*)[0-9]{3}m.*\(\*/\1\2/;s/^(.{21})[0-9]*.*\(\*/\1/;s/\n//;p}' ; }
idea() {
/a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
}
ilogs-local() {
- cd /var/lib/znc/moddata/log/iank/freenode/
- hr
- for x in "#$1/"*; do
- base=${x##*/}
- files=()
- for f in $@; do
- tmp=\#$f/$base
- if [[ -e $tmp ]]; then
- files+=(\#$f/$base)
- fi
- done
- sed \"s/^./${base%log}/\" ${files[@]}|sort -n
+ d=/var/lib/znc/moddata/log/iank/
+ for n in freenode libera; do
+ cd $d/$n
hr
+ for x in "#$1/"*; do
+ base=${x##*/}
+ files=()
+ for f in $@; do
+ tmp=\#$f/$base
+ if [[ -e $tmp ]]; then
+ files+=(\#$f/$base)
+ fi
+ done
+ sed \"s/^./${base%log}/\" ${files[@]}|sort -n
+ hr
+ done
done
}
ilogs() {
@@ -1815,19 +2470,33 @@ ilogs() {
ilog-local() {
local d chan
chan="$1"
+ if [[ ! $chan ]]; then
+ err "requires 1 argument, got 0. check ssh interpolation"
+ return 1
+ fi
d=/var/lib/znc/moddata/log/iank/
for n in freenode libera; do
- cd $d$n/"$chan" && hr
+ if [[ ! -d $d$n/"$chan" ]]; then
+ continue
+ fi
+ 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() {
- local chan
+ local chan tmpf
+ tmpf=$(mktemp)
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() {
@@ -1852,11 +2521,6 @@ ccomp xdg-open o
# jr() { journalctl "$@" | jfilter | less ; }
# jrf() { journalctl -n 200 -f "$@" | jfilter; }
-jr() { journalctl "$@" ; }
-jrf() { journalctl -n 200 -f "$@" ; }
-
-
-ccomp journalctl jtail jr jrf
## old version for model01. i need to get that firmware working again.
# kff() { # keyboardio firmware flash. you must hold down the tilde key
@@ -1886,20 +2550,407 @@ wgkey() {
wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key
umask $umask_orig
}
+
+host-info-all() {
+ hiup
+
+ bindpushb8
+ # for wireguard configs
+ ssh iank@li.b8.nz "conflink; ser reload wg-quick@wgmail"
+ wrt-setup
+}
+
+
+
+
+## for updating host info like ip, location, update /p/c/host-info and
+## /p/c/domain-info.
+
+# todo: this is so long that it becomes confusing,
+# try to split it up.
+#
+# To make some changes take effect, run host-info-all.
+# hiup = host info update
+hiup() {
+
+ # incomplete local vars list
+ local usb ip host mac opts
+ local -A vpn_ips host_ips host_macs portfw_ips nonvpn_ips all_ips root_hosts_a
+ 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
+ usb=false
+ for opt in $opts; do
+ case $opt in
+ user=root)
+ root=true
+ ;;
+ vpn)
+ vpn=true
+ ;;
+ esac
+ done
+
+ if [[ $mac == usb ]]; then
+ usb=true
+ fi
+
+
+ all_ips[$host]=$ip
+ if $vpn || $usb; then
+ portfw_ips[$host]=$ip
+ fi
+ if $vpn; then
+ 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/cmc-firewall-data
+
+
+ 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
+ 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
+ u /b/ds/machine_specific/li/filesystem/etc/openvpn/client-config-hole/$host <$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
+
+ for ip in "${!ip_to_hosts[@]}"; do
+ echo "$ip${ip_to_hosts[$ip]}"
+ done | s cedit -e hosts-file-up /etc/hosts
+
+ printf "root@%s\n" ${hosts[@]} >/p/c/subdir_files/.dsh/group/btrbkroot
+ printf "%s\n" ${hosts[@]} >/p/c/subdir_files/.dsh/group/btrbk
+ ### end focus on hosts file update ###
+
+
+ truncate -s0 ~/.ssh/config-dynamic
+ if ! $at_home; then
+ for host in ${!portfw_ips[@]}; do
+ ipsuf=${portfw_ips[$host]}
+ cat <> ~/.ssh/config-dynamic
+ fi
+ cat /p/c/subdir_files/.ssh/config-static ~/.ssh/config-dynamic >~/.ssh/config
+}
+
+# usage host ipsuf [extrahost]
+#
+# This doesn't do all the work, hiup is also needed, as it sets up
+# config on li.
+#
+# extrahost is a host/cidr that is allowed to go be routed through the
+# vpn by this host.
wghole() {
- if (( $# != 2 )); then
- e expected 2 arg of hostname, ip suffix >&2
+ if (( $# < 2 || $# > 3 )); then
+ e expected 2-3 arg of hostname, ip suffix, and extrahost >&2
return 1
fi
- local host ipsuf umask_orig
+ local host ipsuf umask_orig vpn_allowed
host=$1
ipsuf=$2
+ if [[ $3 ]]; then
+ extrahost=,$3
+ fi
+ for vpn_host in ${!vpn_ips[@]}; do
+ if [[ $vpn_host == "$host" ]]; then
+ continue
+ fi
+ vpn_allowed+=",10.174.${vpn_ips[$vpn_host]}.2/32"
+ done
mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
(
cd /p/c/machine_specific/$host/filesystem/etc/wireguard
umask_orig=$(umask)
umask 0077
- wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
+ if [[ ! -s hole-priv.key || ! -s hole-pub.key ]]; then
+ wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
+ fi
cat >wghole.conf </dev/null; then
- m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
+ sudm mkdir -p /root/mount_namespaces
+ if ! sudm mountpoint /root/mount_namespaces >/dev/null; then
+ m sudm mount --bind /root/mount_namespaces /root/mount_namespaces
fi
- m sudo mount --make-private /root/mount_namespaces
- if [[ ! -e /root/mount_namespaces/$ns ]]; then
- m sudo touch /root/mount_namespaces/$ns
+ m sudm mount --make-private /root/mount_namespaces
+ if ! sudm test -e /root/mount_namespaces/$ns; then
+ m sudm touch /root/mount_namespaces/$ns
fi
- if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
- m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
+ if ! sudm mountpoint /root/mount_namespaces/$ns >/dev/null; then
+ m sudm unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
fi
- m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
+
+}
+
+mns() { # mount namespace
+ local ns
+ ns=$1
+ shift
+ mns-setup $ns
+ m sudm -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
+}
+
+mnsd() { # mount namespace + systemd namespace
+ local ns unit
+ ns=$1
+ unit=$2
+ shift 2
+
+ mns-setup $ns
+
+ pid=$(servicepid $unit)
+ env-tmpf "$@"
+ m sudo nsenter -t $pid -n --mount=/root/mount_namespaces/$ns sudo -u $USER -i bash -c ". $tmpf"
}
-mnsr() { # mns run
+
+mnsr() { # mns run (as normal user)
local ns=$1
+ local -a cmd
shift
- mns $ns sudo -u iank -E env "PATH=$PATH" "$@"
+ if [[ $1 ]]; then
+ cmd=("$@")
+ else
+ cmd=(bash)
+ fi
+ mns $ns sudo -u iank -E env "PATH=$PATH" "${cmd[@]}"
+}
+
+mnsnonetroot() {
+ ns=$1
+ lomh
+ if ! s ip netns list | grep -Fx nonet &>/dev/null; then
+ s ip netns add nonet
+ fi
+ mns $ns --net=/var/run/netns/nonet /bin/bash
+ lomh
}
mnsnonet() {
@@ -1966,6 +3070,10 @@ mnsnonet() {
lom() {
# l = the loopback device
local l base
+ # get sudo pass cached right away
+ if ! sudo -nv 2>/dev/null; then
+ sudo -v
+ fi
if [[ $1 == /* ]]; then
base=${1##*/}
fs_file=$1
@@ -2042,7 +3150,7 @@ mdenable() {
two=false
case $1 in
- -2) two=true shift ;;
+ -2) two=true; shift ;;
esac
for md; do
@@ -2123,6 +3231,14 @@ mpvgpu() {
mpvd() {
mpv --profile=d "$@";
}
+mpva() {
+ mpv --profile=a "$@";
+}
+# mpv for testing video quality, dont scale.
+mpvt() {
+ mpv --video-unscaled "$@";
+}
+
# mpv all media files in . or $1
mpvm() {
local -a extensions arg
@@ -2180,13 +3296,29 @@ mpvs() {
myirc() {
if [[ ! $1 ]]; then
- set -- fsf-office
+ set -- fsfsys
fi
local -a d
d=( /var/lib/znc/moddata/log/iank/{freenode,libera} )
# use * instead of -r since that does sorted order
ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\ 32 )); then
+ i=$(( logcount - 32 ))
+ else
+ i=0
+ fi
+ # usually do this on monday, sometimes later
+ if [[ $(date +%A) == Monday ]]; then
+ min_date=$(date -d 'monday 2 weeks ago' +%s)
+ else
+ min_date=$(date -d 'monday 5 weeks ago' +%s)
+ fi
+ for (( ; i < logcount; i++ )); do
+ log_base=${logs[$i]}
+ day_logs=()
+ if [[ -e office_at_conference.fsf.org/$log_base ]]; then
+ day_logs+=(office_at_conference.fsf.org/$log_base)
+ fi
+ if [[ -e sys_at_conference.fsf.org/$log_base ]]; then
+ day_logs+=(office_at_conference.fsf.org/$log_base)
+ fi
+ d=$(date -d "$(head -n1 ${day_logs[0]} |awk '{print $1}')" +%s)
+ if (( d < min_date )); then
+ continue
+ fi
+ if awk '$3 == "iank:"' ${day_logs[@]} | sed -r 's/^(.{10}).(.{8})[^ ]+(.*)/\1_\2\3/' | grep . | sort; then
+ hr
+ fi
+ done
+ popd
+}
+
+
+# Tail all recent prof logs. Copying from profanity has unwanted line breaks
+# especially for links.
+profr() {
+ case $HOSTNAME in
+ kd)
+ profr-local
+ ;;
+ *)
+ ssh b8.nz profr-local
+ ;;
+ esac
+}
+
+profr-local() {
+ local d0 d1
+ local -a files
+ d0="$(date +%Y_%m_%d).log"
+ d1="$(date -d '1 day ago' +%Y_%m_%d).log"
+ ngset
+ files=(/d/p/profanity/chatlogs/iank_at_fsf.org/{*,rooms/*}/{$d0,$d1})
+ ngreset
+ if (( ${#files[@]} > 0 )); then
+ cat "${files[@]}" | sort | tail -n 40
+ fi
+}
+
+
+# Tail pms in the last day, for the case where we restart profanity and
+# didn't check for pms beforehand. Assume the most recent logs are on kd.
+# If that isn't the case, use prof-recent-local
+prof-recent() {
+ case $HOSTNAME in
+ kd)
+ prof-recent-local
+ ;;
+ *)
+ ssh b8.nz prof-recent-local
+ ;;
+ esac
+}
+prof-recent-local() {
+ local d dates date files f
+ # consider making the day count passed by parameter. note: this works: $(date -d '2 day ago' +%Y_%m_%d)
+ dates=("$(date +%Y_%m_%d)" "$(date -d '1 day ago' +%Y_%m_%d)" )
+ for d in /d/p/profanity/chatlogs/iank_at_fsf.org/!(rooms); do
+ files=()
+ for date in ${dates[@]}; do
+ f=$d/$date.log
+ if [[ -e $f ]]; then
+ files+=($f)
+ fi
+ done
+ if (( ${#files[@]} >= 1 )); then
+ cat ${files[@]} | tail
+ hr
+ fi
+ done
+}
+
+prof-sort() {
+ case $HOSTNAME in
+ kd)
+ prof-recent-sort
+ ;;
+ *)
+ ssh b8.nz prof-recent-sort
+ ;;
+ esac
+}
+
+prof-recent-sort() {
+ local d dates date files f
+ # consider making the day count passed by parameter. note: this works: $(date -d '2 day ago' +%Y_%m_%d)
+ dates=("$(date +%Y_%m_%d)" "$(date -d '1 day ago' +%Y_%m_%d)" )
+ files=()
+ for d in /d/p/profanity/chatlogs/iank_at_fsf.org/!(rooms); do
+ for date in ${dates[@]}; do
+ f=$d/$date.log
+ if [[ -e $f ]]; then
+ files+=($f)
+ fi
+ done
+ done
+ for f in "${files[@]}"; do
+ sed "s/\$/ $f/" $f
+ done | sort
}
+
# usage: debvm DEBIAN_VERSION RAM_MB
debvm() {
local ver ram fname src
@@ -2295,10 +3554,19 @@ ngo() {
otp() {
oathtool --totp -b "$*" | xclip -selection clipboard
}
+# run cmd and copy output
j() {
- "$@" |& pee "xclip -r -selection clipboard"
+ "$@" |& pee "xclip -r -selection clipboard" cat
}
+# xorg copy. copy text piped into command, or copy file(s) if given
+xc() {
+ xclip -r -selection clipboard "$@"
+}
+# echo copy
+ec() {
+ pee "xclip -r -selection clipboard" cat
+}
pakaraoke() {
# from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals
@@ -2453,27 +3721,46 @@ EOFOUTER
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)
}
+# 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 "$@"
}
spamf() { # spamtest on FILE
- local spamcpre spamdpid
-
if (( $# != 1 )); then
e spamtest error: expected 1 arg, filename >&2
return 1
fi
+ spamd_ser=spamd
+ if systemctl cat spamassassin &>/dev/null; then
+ spamd_ser=spamassassin
+ fi
+ sdncmdroot $spamd_ser sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
- 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"
-}
+ # rspamc. This has a bit of a problem where it always says hostname not recognized.
+ # I haven't figured out how to fix it, but I know that it expects to get that hostname
+ # from exim, and maybe the only way to do it properly is to actually pass the email
+ # through exim and tell exim the host.
+ #sdncmd rspamd rspamc "$1"
+
+ # if we made rspamc listen on other addresses, we could do
+ #rspamc -h 10.173.8.2:11334 "$1"
+}
# mail related
testmail() {
@@ -2610,31 +3897,35 @@ torshell() {
}
eless2() {
- less /var/log/exim4/mymain
+ less /var/log/exim4/nondmain
}
-# mail related
+### mail related
+
+# testmail above calls sendmail, which is a link to exim/postfix.
+# its docs dont say a way of adding an argument
+# to sendmail to turn on debug output. We could make a wrapper, but
+# that is a pain. Exim debug args are documented here:
+# http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
+#
+# http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
+# note, for exim daemon, you can turn on debug options by
+# adding -d, etc to COMMONOPTIONS in
+# /etc/default/exim4
+#
+# to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
+# exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
+#
+# -t = get recipient from header
testexim() {
- # testmail above calls sendmail, which is a link to exim/postfix.
- # its docs dont say a way of adding an argument
- # to sendmail to turn on debug output. We could make a wrapper, but
- # that is a pain. Exim debug args are documented here:
- # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
- #
- # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
- # note, for exim daemon, you can turn on debug options by
- # adding -d, etc to COMMONOPTIONS in
- # /etc/default/exim4
- #
- # to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
- # exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
- #
- # -t = get recipient from header
exim -d -t < /dev/null 2>&1 &
}
+## usage: to connect to my main transmission daemon from a different host, run this
+trans-remote-route() {
+ :
+}
trg() { transmission-remote-gtk & r; }
+# TODO: this wont work transmission.lan doesnt exist
trc() {
# example, set global upload limit to 100 kilobytes:
# trc -u 100
@@ -2707,16 +4003,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"
+ ecmd="/usr/sbin/exim4 -C /etc/exim4/nn-mainlog.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 -q30m -C /etc/exim4/my.conf"|h1)
- m s nsenter -t $pid -n -m $ecmd "$@"
}
# get pid of systemd service
@@ -2758,7 +4054,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
@@ -2770,21 +4066,66 @@ sdnbashroot() { # systemd namespace bash
}
-sdncmd() { # systemd namespace cmd
+# systemd namespace cmd
+# usage: UNIT CMD...
+sdncmd() {
+ local unit pid tmpf
+ if (( $# <= 1 )); then
+ echo $0: error wrong number of args >&2
+ return 1
+ fi
+ unit=$1
+ shift
+ pid=$(servicepid $unit)
+ env-tmpf "$@"
+ m sudo nsenter -t $pid -n -m sudo -u $USER -i bash -c ". $tmpf"
+}
+
+sdncmdroot() { # systemd namespace root command
local unit pid
- if (( $# <= 2 )); then
+ 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 "$@"
+}
+
+## create tempfile script which runs command under same env then deletes itself.
+# background: i can't remember the exact reason i started exporting, but it does keep the
+# environment vars perfectly accurate, whereas sudo -E does not quite. Although,
+# we could just set those explicity, PATH is the main one. It also
+# seems less secure since another process could modify the temp file.
+env-tmpf() {
+ tmpf=$(mktemp --tmpdir $unit.XXXXXXXXXX)
+ {
+ export -p
+ printf "%s " "${@@Q}"
+ echo
+ echo "rm -f $tmpf"
+ } >$tmpf
+}
+
+# systemd network namespace (not mount) cmd
+# usage: UNIT CMD...
+sdnncmd() {
+ local unit pid tmpf
+ if (( $# <= 1 )); 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 sudo -u $USER -i "$@"
+ env-tmpf "$@"
+ m sudo nsenter -t $pid -n sudo -u $USER -i bash -c ". $tmpf"
}
mailnnbash() {
- sdnbash mailnn
+ sdnbashroot mailnn
}
# we use wireguard now, use mailnnbash.
@@ -2793,31 +4134,36 @@ mailnnbash() {
# }
eximbash() {
- local pid
- pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -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
- spamdpid=$(systemctl show --property MainPID --value spamassassin)
+ spamd_ser=spamd
+ if systemctl cat spamassassin &>/dev/null; then
+ spamd_ser=spamassassin
+ fi
+ spamdpid=$(systemctl show --property MainPID --value $spamd_ser)
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() {
s nmtui-connect "$@"
}
+# check exim and others network namespace
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
- 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
@@ -2841,14 +4187,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
}
@@ -2860,9 +4205,8 @@ vpn() {
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.
+ journalctl --since=now --unit=$vpn_service@$1 -f -n0 &
+ # might be able to go lower than 1
sleep 1
sudo systemctl start $vpn_service@$1
# sometimes the ask-password agent does not work and needs a delay.
@@ -2882,6 +4226,40 @@ fixu() {
fi
}
+# unmute desktop output
+um() {
+ local sink card
+ sink=$(pactl get-default-sink)
+ if [[ $sink == auto_null ]]; then
+ # this assumes there is just one with an off profile. otherwise we will
+ # need to do something else like storing the card name we care
+ # about somewhere. the nap function picks a card, it could help.
+ card=$(pactl -f json list cards|jq '.[] | select(.active_profile? == "off")|.index')
+ if [[ $card ]]; then
+ m pactl set-card-profile "$card" output:analog-stereo
+ else
+ echo "iank note: no muted card found. still unmuting sink"
+ fi
+ fi
+ m pactl set-sink-mute @DEFAULT_SINK@ false
+ rm -f /tmp/ianknap
+}
+
+nap() {
+ local sink card
+ sink=$(pactl get-default-sink)
+ card="${sink%.*}"
+ card="${card/output/card}"
+ m pactl set-card-profile "$card" off
+
+ # clicking on a link in a browser can cause unmute.
+ # I don't want that. So, use a stronger form of mute
+ # than this.
+ #pactl set-sink-mute @DEFAULT_SINK@ true
+ touch /tmp/ianknap
+}
+
+
# systemctl is-enabled / status / cat says nothing, instead theres
# some obscure symlink. paths copied from man systemd.unit.
# possibly also usefull, but incomplete, doesnt show units not loaded in memory:
@@ -2969,7 +4347,13 @@ vpnoffc() { # vpn off client
ser stop openvpn-client-tr@client
}
vpnc() {
- ser start openvpn-client-tr@client
+ local unit
+ unit=openvpn-client-tr@client
+ sudo -v
+ if [[ $(systemctl is-active $unit) != active ]]; then
+ s systemctl start $unit
+ sleep 1
+ fi
}
@@ -2982,6 +4366,26 @@ vspicy() { # usage: VIRSH_DOMAIN
wian() {
cat-new-files /m/4e/INBOX/new
}
+wakehours() {
+ local sec
+ if (( $# != 1 )) ; then
+ echo wakehours: error: expected 1 arg, got $# >&2
+ return 1
+ fi
+ sec=$(( EPOCHSECONDS - $( date +%s -d $1am ) ))
+ printf "%d:%02d\n" $(( sec / 60 / 60)) $(( (sec / 60) % 60 ))
+}
+
+calvis() { # calendar visualize
+ install -m 600 /dev/null /tmp/calendar-bytes
+ while read -r l; do
+ for char in $l; do
+ # shellcheck disable=SC2059 # intentional for the hex formatting
+ printf "\x$(printf "%x" $char)" >>/tmp/calendar-bytes
+ done
+ done < <(grep -v '[#-]' /p/calendar-data)
+ /p/c/proc/calendar/linux-amd64/calendar
+}
wtr() { curl wttr.in/boston; }
@@ -2994,26 +4398,142 @@ vrun() {
"$@"
}
-f=/a/f/ansible-configs/files/common/etc/fsf-workstation-bashrc.sh
-if [[ -e $f ]]; then
- # shellcheck disable=SC1090
- source $f
-fi
-
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
- 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
}
+# grep + find
+gef() {
+ faf | grep -E "$@" ||:
+ rgv "$@"
+}
+
# rg my main files
rgm() {
- rg "$@" /p/pd.org /p/w.org /a/t.org /a/work.org /b
+ rg "$@" /p/w.org /a/t.org /a/work.org /b
+}
+
+# re all my files more expansively.
+# usage [-OPT...] regex space combined
+rem() {
+ local paths
+ local -a opts
+ if [[ ! $1 ]]; then
+ echo rem: missing argument >&2
+ return 1
+ fi
+ for arg; do
+ if [[ $arg == -* ]]; then
+ opts+=("$1")
+ shift
+ else
+ break
+ fi
+ done
+ paths="/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" "${opts[@]}" -- "$*" $paths /a/work.org ||:
+}
+reml() { # rem with limit to 5 matches per file
+ local_rgv_args="-m 5"
+ rem "$@"
+}
+
+rep() {
+ local paths
+ 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 $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
+ 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() {
+ # shellcheck disable=SC2046 # i want word splitting
+ ./update-zone $(i s | sed -rn 's/.*db\.(.*)/\1/p')
+}
+
+# setup:
+# pip3 install linode-cli
+# linode-cli
+livp9() {
+ local input ip id tmp
+ input=$1
+ if [[ $2 ]]; then
+ id=$2
+ ip=$3
+ else
+ tmp=$(mktemp)
+ echo $tmp
+ linode-cli --json --pretty linodes create --root_pass loxHuceygomGisun | tee $tmp
+ read -r ip id <<<"$(tail -n+2 $tmp | jq -r '.[0].ipv4[0] , .[0].id')"
+ for string in $ip $id; do
+ case $string in
+ [0-9]*) : ;;
+ *)
+ echo "livp9: bad value ip=$ip id=$id input=$input"
+ return 1
+ ;;
+ esac
+ done
+ rm $tmp
+
+ while true; do
+ if timeout 4 ssh $ip :; then
+ break
+ fi
+ sleep 3
+ done
+ fi
+ ssh $ip </dev/null; then
# --no-messages because of annoying errors on broken symlinks
# -z = search .gz etc files
- # -. = search dotfilesq
- rg() { command rg -. -z --no-messages -L -i -M 900 --no-ignore-parent --no-ignore-vcs -g '!.git' -g '!auto-save-list' -g '!.savehist' "$@" || return $?; }
+ # -. = search dotfiles
+ # -n --no-heading: show files and line numbers together allowing for clicking
+ rg() {
+ local path_arg
+ if [[ ${#@} == 1 ]]; then
+ path_arg=.
+ fi
+
+ command rg -. -z --no-messages -Lin --no-heading -M 900 --no-ignore-parent --no-ignore-vcs -g '!.git' -g '!auto-save-list' -g '!.savehist' "$@" $path_arg || return $?
+ }
#fails if not exist. ignore
complete -r rg 2>/dev/null ||:
else
alias rg=grr
fi
+# rg with respecting vcs ignore files
+rgv() {
+ local path_arg ret=0
+ if [[ ${#@} == 1 ]]; then
+ path_arg=.
+ fi
+ # settings that are turned off for pipes, keep them on.
+ # Found by searching for "terminal" in --help
+ # --heading
+ # -n
+ #
+ # -. = search dotfiles
+ # -z = search zipped files
+ # -i = case insensitive
+ # -M = max columns
+ # -n --no-heading: show files and line numbers together allowing for clicking
+ # --no-messages because of annoying errors on broken symlinks
+ # --no-ignore-parent because i have /a/.git which ignores almost everything under it.
+ command rg -n --no-heading -. -z --no-messages -i -M 900 --no-ignore-parent -g '!.git' -g '!auto-save-list' -g '!.savehist' "$@" $path_arg || ret=$?
+ return $ret
+}
+
+amall() {
+ echo "$(tput setaf 5 2>/dev/null ||:)â coresite â$(tput sgr0 2>/dev/null||:)"
+ amfsf "$@"
+ echo "$(tput setaf 5 2>/dev/null ||:)â office â$(tput sgr0 2>/dev/null||:)"
+ amoffice "$@"
+}
+amallq() { # amall quiet
+ amfsf "$@"
+ amoffice "$@"
+}
+amfsf() {
+ sedi -r '/alertmanager.url/s/@prom.office/@prom/' ~/.config/amtool/config.yml
+ amtool "$@"
+}
+amoffice() {
+ sedi -r '/alertmanager.url/s/@prom.fsf/@prom.office.fsf/' ~/.config/amtool/config.yml
+ amtool "$@"
+}
+amls() {
+ amall silence query "$@"
+}
+# amtool silence add
+amsa() {
+ amall silence add "$@"
+}
+# amtool silence force
+amsf() {
+ amall silence add x!="1"
+}
+amrmall() {
+ # note: not sure if quoting of this arg is correct
+ amfsf silence expire "$(amfsf silence query -q)"
+ amoffice silence expire "$(amoffice silence query -q)"
+}
+
+
+youtube-dl-update() {
+ sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
+ sudo chmod a+rx /usr/local/bin/youtube-dl
+}
+
+# https://github.com/yt-dlp/yt-dlp/wiki/Installation
+yt-dlp-update() {
+ sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
+ sudo chmod a+rx /usr/local/bin/yt-dlp # Make executable
+}
+mpvyt() {
+ mpv --ytdl ytdl_path=/usr/local/bin/yt-dlp "$@"
+}
# taken from default changes to bashrc and bash_profile
path-add --end --ifexists $HOME/.rvm/bin
@@ -3140,6 +4736,314 @@ path-add --end --ifexists $HOME/.rvm/bin
# so its included in overall env
+# ya, hacky hardcoded hostnames in 2023. we could do better
+hssh-update() {
+ local -a failed_hosts hosts
+ case $HOSTNAME in
+ sy|so|kd)
+ hosts=(
+ kd.b8.nz x3.office.fsf.org sy so x2.b8.nz
+ )
+ ;;
+ x3)
+ hosts=(
+ b8.nz sywg.b8.nz sowg.b8.nz
+ )
+ ;;
+ esac
+ for host in ${hosts[@]}; do
+ e $host
+ if ! scp /b/fai/fai/config/files/usr/local/bin/hssh/IANK root@$host:/usr/local/bin/hssh; then
+ failed_hosts+=($host)
+ fi
+ done
+ if (( ${#failed_hosts[@]} >= 1 )); then
+ echo failed_hosts=${failed_hosts[*]}
+ return 1
+ fi
+}
+
+noi3bar() {
+ touch /tmp/noi3bar
+}
+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
+}
+
+ctof() {
+ units "tempC($1)" tempF
+}
+
+ftoc() {
+ units "tempF($1)" tempC
+}
+
+# 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 <
+AuthType Basic
+AuthName "basic_auth"
+# created with
+# htpasswd -c icecast-fsf-htpasswd USERNAME
+AuthUserFile "/etc/icecast-fsf-htpasswd"
+Require valid-user
+
+
+AuthType Basic
+AuthName "basic_auth"
+AuthUserFile "/etc/icecast-fsf-tech-htpasswd"
+Require valid-user
+
+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() {
+ if [[ -d /var/icecast ]]; then
+ find /var/icecast -type f -delete
+ fi
+ ssh li.b8.nz find /var/icecast -type f -delete
+}
+
+
+# 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=0; i&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
+}
+
+# decrease filesize without losing any noticeable quality. inspired from
+# https://gist.github.com/BlueSwordM/86dfcb6ab38a93a524472a0cbe4c4100
+ffsencode() {
+ in="$1"
+ out="$2"
+ ffmpeg -i "$in" -c:v libsvtav1 -crf 60 -preset 6 -g 60 -svtav1-params tune=0:enable-overlays=1:scd=1:scm=1 -pix_fmt yuv420p10le -c:a copy "$out"
+}
+
+localai() {
+ schroot -c bookworm
+}
+
+spdfx() {
+ spdx -f ~/.spd/spd/spd_data_financial.gpg "$@"
+}
+
+# note: if no prompt and no error, that means we found a single pass and
+# put it i the clipboard.
+spdx() {
+ local out i input pw file
+ if [[ $1 == -f ]]; then
+ file="$2"
+ shift 2
+ else
+ file=~/.spd/spd/spd_data.gpg
+ fi
+
+ out=$(gpg -q -d "$file" 2>/dev/null | gr "$@")
+ if [[ $out == *$'\n'* ]]; then
+ i=0
+ while read -r line; do
+ printf "$i %s\n" "$line" | awk -F'\t' '{print $1,$2}'
+ i=$(( i + 1 ))
+ done <<<"$out"
+ read -r input
+ i=0
+ while read -r line; do
+ if [[ $input == "$i" ]]; then
+ out="$line"
+ break
+ fi
+ i=$(( i + 1 ))
+ done <<<"$out"
+ fi
+ pw=$(printf "$i %s\n" "$out" | awk -F'\t' '{print $3}')
+ ( { printf "%s" "$pw" | xclip -selection clipboard && sleep 15 && echo " " | xclip -selection clipboard; } & )
+}
+
+ffmain() {
+ xdg-settings set default-web-browser ffmain.desktop
+}
+ffdefault() {
+ xdg-settings set default-web-browser firefox.desktop
+}
+
+snap-last() {
+ ls -lad /mnt/o/btrbk/o.* | tail -n2
+ for sub in a q; do
+ ls -lad /mnt/root/btrbk/$sub.* | tail -n2
+ done
+ }
+
export BASEFILE_DIR=/a/bin/fai-basefiles
#export ANDROID_HOME=/a/opt/android-home