lots: shellcheck, streaming stuff, fixes
[distro-setup] / brc2
1 #!/bin/bash
2 # Copyright (C) 2019 Ian Kelling
3 # SPDX-License-Identifier: AGPL-3.0-or-later
4 # this gets sourced. shebang is just for file mode detection
5
6
7 # * settings
8
9 if [[ $LESSHISTFILE == - ]]; then
10 HISTFILE=
11 c() { cd "$@"; }
12 elif [[ $HISTFILE ]]; then
13 HISTFILE=$HOME/.bh
14 fi
15
16 source /a/bin/distro-setup/path-add-function
17 path-add /a/exe
18 # add this with absolute paths as needed for better security
19 #path-add --end /path/to/node_modules/.bin
20 ## for yarn, etc
21 #path-add --end /usr/lib/node_modules/corepack/shims/
22
23 # pip3 --user things go here:
24 path-add --end ~/.local/bin
25 path-add --ifexists --end /a/work/libremanage
26 path-add --ifexists --end /a/opt/adt-bundle*/tools /a/opt/adt-bundle*/platform-tools
27 path-add --ifexists --end /a/opt/scancode-toolkit-3.10.
28 path-add --ifexists --end /p/bin
29
30 case $HOSTNAME in
31 sy|bo)
32 # https://askubuntu.com/questions/1254544/vlc-crashes-when-opening-any-file-ubuntu-20-04
33 if grep -qE '^VERSION_CODENAME="(nabia|focal)"' /etc/os-release &>/dev/null; then
34 export MESA_LOADER_DRIVER_OVERRIDE=i965
35 fi
36 ;;
37 esac
38
39
40 export WCDHOME=/a
41
42
43 case $EUID in
44 0)
45 # shellcheck disable=SC2034 # used in brc
46 SL_SSH_ARGS="-F $HOME/.ssh/confighome"
47 ;;
48 esac
49
50
51 # * include files
52
53 # generated instead of dynamic for the benefit of shellcheck
54 #for x in /a/bin/distro-functions/src/* /a/bin/!(githtml)/*-function?(s); do echo source $x ; done
55 source /a/bin/distro-functions/src/identify-distros
56 source /a/bin/log-quiet/logq-function
57 # for x in /a/bin/bash_unpublished/source-!(.#*); do echo source $x; done
58 source /a/bin/bash_unpublished/source-semi-priv
59 source /a/bin/bash_unpublished/source-state
60
61 if [[ $HOSTNAME == "$MAIL_HOST" ]]; then
62 export MAIL_HOST_P=t
63 else
64 export NOT_MAIL_HOST_P=t
65 fi
66
67
68 source /a/bin/log-quiet/logq-function
69
70 # not used
71 # if [[ -s /a/opt/alacritty/extra/completions/alacritty.bash ]]; then
72 # source /a/opt/alacritty/extra/completions/alacritty.bash
73 # fi
74
75
76 source /a/bin/ds/beet-data
77
78
79 # * functions
80
81
82
83 multimic() {
84 local i
85 local -a sources
86
87 m pactl unload-module module-loopback
88 m pactl unload-module module-null-sink
89 m pactl unload-module module-remap-source
90
91 IFS=" " read -r -a sources <<<"$(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p')"
92
93 if (( ! $# )); then
94 i=0
95 for s in ${sources[@]}; do
96 e $i $s
97 i=$(( i+1 ))
98 done
99 read -r l
100 set -- $l
101 fi
102 m pactl load-module module-null-sink sink_name=ianinput sink_properties=device.description=ianinputs
103 for i; do
104 m pactl load-module module-loopback source=${sources[i]} sink_dont_move=true sink=ianinput
105 done
106 pactl load-module module-remap-source source_name=iancombine master=ianinput.monitor source_properties=device.description=iancombine
107 }
108
109 # h ssh test
110 # For testing restrictive ssh.
111 hstest() {
112 install-my-scripts
113 d=$(mktemp -d)
114 sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config
115 s command ssh -F $d/config -i /q/root/h "$@"
116 }
117
118 # h rsync test
119 # For testing restrictive rsync
120 hrtest() { #
121 install-my-scripts
122 d=$(mktemp -d)
123 sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config
124 s rsync -e "ssh -F $d/config -i /q/root/h" "$@"
125 }
126
127 # rsync as root and avoid the default restrictive h key & config.
128 rootrsync() {
129 s rsync -e "ssh -F /root/.ssh/confighome" "$@"
130 }
131
132 zcheck() {
133 ssh bow DISPLAY=:0 scrot /tmp/oegu.jpg
134 scp bow:/tmp/oegu.jpg /t
135 ssh bow rm /tmp/oegu.jpg
136 feh /t/oegu.jpg
137 }
138 zmon() {
139 while true; do
140 ziva-screen
141 sleep 15
142 done
143 }
144
145 slemacs() {
146 local arg rtime v
147 arg="$1"
148 remote="$2"
149 if [[ $arg == [89]0Etiona* ]]; then
150 v=${arg::1}
151 rtime=${arg#*Etiona} # remote time
152 if [[ ! $rtime ]]; then
153 rtime=0
154 fi
155 dir=/a/opt/emacs-trisquel${v}-nox/.iank
156 ltime=$(stat -c%Y $dir/e/e/.emacs.d/init.el)
157 if (( ltime > rtime )); then
158 m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" $dir "$remote":/home/iank
159 fi
160 fi
161 }
162
163 sle() { # sl emacs
164 local f=/home/iank/.emacs.d/init.el
165 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 "$@"
166 }
167 ccomp ssh sle
168
169 # Run this manually after .emacs.d changes. Otherwise, to check if
170 # files changed with find takes 90ms. sl normally only adds 25ms. We
171 # could cut it down to 10ms if we put things on a btrfs filesystem and
172 # looked for changes there, or used some inotify thing, but that seems
173 # like too much work.
174 egh() { # emacs gnuhope
175 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank lists2d.fsf.org:.ianktrisquel_9
176 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank lists2d.fsf.org:/home/iank
177 }
178 ekw() {
179 local shell="bash -s"
180 if [[ $HOSTNAME != kw ]]; then
181 shell="ssh kw.office.fsf.org"
182 bbk -m /a -t kw
183 fi
184 $shell <<'EOF'
185 sudo mkdir /root/.ianktrisquel_9
186 sudo rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank /root/.ianktrisquel_9
187 rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank /home/iank
188 EOF
189 }
190
191 rm-docker-iptables() {
192 s iptables -S | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables $l; done
193 s iptables -S -t nat | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables -t nat $l; done
194 s iptables -S | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables $l; done
195 s iptables -S -t nat | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables -t nat $l; done
196 }
197
198 # usage mkschroot [-] distro codename packages
199 # - means no piping in of sources.list
200 #
201 # note some useful post mkschroot i've used in the past
202 # tu /nocow/schroot/flidas/etc/sudoers <<EOF
203 # $USER ALL=(ALL) NOPASSWD: ALL
204 # Defaults env_keep += SUDOD
205 # Defaults always_set_home
206 # Defaults !umask
207 # EOF
208 # sd /nocow/schroot/flidas//etc/locale.gen <<'EOF'
209 # en_US.UTF-8 UTF-8
210 # EOF
211 # s schroot -c flidas locale-gen
212 # s schroot -c flidas update-locale LANG=en_US.UTF-8
213
214 mkschroot() {
215 local sources force repo n distro
216 force=false
217 while [[ $1 == -* ]]; do
218 case $1 in
219 -f) force=true; shift ;;
220 -s)
221 sources="$2"
222 if [[ ! -s $sources ]]; then
223 echo mkschroot: error: sources file $sources does not exist or is empty
224 return 1
225 fi
226 shift 2
227 ;;
228 esac
229 done
230 distro=$1
231 shift
232 case $distro in
233 trisquel)
234 repo=http://mirror.fsf.org/trisquel/
235 ;;
236 ubuntu)
237 repo=http://archive.ubuntu.com/ubuntu/
238 ;;
239 debian)
240 repo=http://deb.debian.org/debian/
241 ;;
242 esac
243 n=$1
244
245 shift
246 if ! $force && schroot -l | grep -xFq chroot:$n; then
247 echo "$0: $n schroot already installed, skipping"
248 return 0
249 fi
250 apps=($@)
251 d=/nocow/schroot/$n
252 sd /etc/schroot/chroot.d/$n.conf <<EOF
253 [$n]
254 description=$n
255 type=directory
256 directory=$d
257 profile=desktop
258 preserve-environment=true
259 users=$USER,user2
260 EOF
261 cd
262 if [[ ! -e $d/bin ]]; then
263 sudo mkdir -p $d
264 # resolvconf otherwise schroot fails with
265 # cp: not writing through dangling symlink '/var/run/schroot/mount/flidas-7a2362e0-81b3-4848-92c1-610203ef5976/etc/resolv.conf'
266 sudo debootstrap --exclude=resolvconf $n $d $repo
267 fi
268 if [[ $sources ]]; then
269 sudo install -m 644 $sources $d/etc/apt/sources.list
270 fi
271 sudo chroot $d apt-get update
272 sudo DEBIAN_FRONTEND=noninteractive chroot $d apt-get -y dist-upgrade --purge --auto-remove
273 sudo cp -P {,$d}/etc/localtime
274 if (( ${#apps[@]} )); then
275 sudo DEBIAN_FRONTEND=noninteractive schroot -c $n -- apt-get install --allow-unauthenticated -y ${apps[@]}
276 fi
277 }
278
279
280 # note: this is incomplete and untested.
281 # https://wiki.archlinux.org/index.php/Install_Arch_Linux_from_existing_Linux#Creating_a_chroot
282 mkarchchroot() {
283 local tarball mirror
284 mirror=https://mirrors.edge.kernel.org/archlinux/iso/latest/
285 tarball=$(curl -s $mirror | sed -nr 's/.*"(archlinux-bootstrap-.*-x86_64.tar.gz)".*/\1/p')
286 wget -O /tmp/arch.tar.gz https://mirrors.edge.kernel.org/archlinux/iso/latest/$tarball
287 s mkdir -p /nocow/schroot/arch
288 cd _/nocow/schroot/arch
289 s sed -i '/## United States/,/^$/s,^#,,' etc/pacman.d/mirrorlist
290 # error: could not determine cachedir mount point /var/cache/pacman/pkg
291 s sed -i /^CheckSpace/d etc/pacman.conf
292 chroot . /bin/bash -s <<'EOF'
293 pacman-key --init
294 pacman-key --populate archlinux
295 pacman -Syyu
296 EOF
297 # example of building an aur package:
298 # pacman -Sy base-devel wget
299 # useradd -m iank
300 # f=$target/etc/sudoers
301 # line='iank ALL=(ALL) NOPASSWD: ALL'
302 # if [[ ! -e $f ]] || ! grep -xF "$line" $f; then
303 # echo "$line" >> $f
304 # fi
305 # su iank
306 # wget https://aur.archlinux.org/cgit/aur.git/snapshot/anbox-image-gapps.tar.gz
307 # tar xzf anbox-image-gapps.tar.gz
308 # cd anbox-image-gapps
309 # makepkg -s
310 }
311
312
313 # clock back in to timetrack from last entry
314 tback() {
315 sqlite3 /p/.timetrap.db "update entries set end = NULL where id = (select max(id) from entries);"
316 }
317
318 # sshfs example:
319 # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
320
321 edelayoff() {
322 echo all >/etc/exim4/no-delay-eximids
323 }
324 edelayon() {
325 echo >/etc/exim4/no-delay-eximids
326 }
327
328 eqgo() {
329 local -a array tmpstr delayon
330 delayon=true
331 if grep -qFx all /etc/exim4/no-delay-eximids; then
332 delayon=false
333 fi
334 if $delayon; then
335 echo all >/etc/exim4/no-delay-eximids
336 fi
337 tmpstr=$(exiqgrep -i -r.\*)
338 mapfile -t array <<<"$tmpstr"
339 enn -M "${array[@]}"
340 if $delayon; then
341 echo >/etc/exim4/no-delay-eximids
342 fi
343 }
344 eqgo1() {
345 local eid
346 eid="$(exipick -i -r.\*|h1)"
347 sed -n "/^all$/p;\$a $eid" /etc/exim4/no-delay-eximids
348 enn -M "$eid"
349 }
350 ennm() {
351 local eid
352 for eid; do
353 printf "%s\n" "$eid" >>/etc/exim4/no-delay-eximids
354 done
355 enn -M "$@"
356 }
357
358
359 gnupload(){
360 /a/f/gnulib/build-aux/gnupload "$@"
361 }
362
363 abrowserrmcompat() {
364 local f
365 ngset
366 f=(/p/c/firefox*/compatibility.ini)
367 if (( ${#f[@]} )); then
368 rm ${f[@]}
369 fi
370 ngreset
371 }
372
373 checkre() {
374 s checkrestart -b /a/bin/ds/checkrestart-blacklist -pv
375 }
376
377 cp-blocked-domains-to-brains() {
378 cp /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn
379 }
380 cp-blocked-domains-to-ansible() {
381 cp /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains
382 }
383
384
385 daycat() {
386 ngset
387 hrcat /m/md/daylert/{cur,new}/*
388 ngreset
389 }
390 dayclear() {
391 ngset
392 rm -f /m/md/daylert/{cur,new}/*
393 }
394
395
396 acat() {
397 ngset
398 hrcat /m/md/alerts/{cur,new}/*
399 ngreset
400 hr; echo bk; hr
401 ssh bk.b8.nz "shopt -s nullglob; hrcat /m/md/INBOX/new/* /m/md/INBOX/cur/*"
402 }
403 aclear() {
404 ngset
405 rm -f /m/md/alerts/{cur,new}/*
406 ngreset
407 ssh bk.b8.nz "shopt -s nullglob; rm -f /m/md/INBOX/new/* /m/md/INBOX/cur/*"
408 system-status _
409 }
410
411 alerts() {
412 find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f
413 }
414 ralerts() { # remote alerts
415 local ret shell
416 # this list is duplicated in check-remote-mailqs
417 for h in bk je li frodo x3wg kdwg sywg; do
418 echo $h:
419 shell="ssh $h"
420 if [[ $HOSTNAME == "${h%wg}" ]]; then
421 shell=
422 fi
423 ret=0
424 $shell find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f || ret=$?
425 if (( ret )); then
426 echo ret:$ret
427 fi
428 done
429 }
430
431 ap() {
432 # pushd in case current directory has an ansible.cfg file
433 pushd /a/xans >/dev/null
434 ansible-playbook -v -l ${1:- $(hostname -f)} site.yml
435 popd >/dev/null
436 }
437 aw() {
438 pushd /a/work/ans >/dev/null
439 time ansible-playbook -i inventory adhoc.yml "$@"
440 popd >/dev/null
441 }
442 ad() {
443 pushd /a/bin/distro-setup/a >/dev/null
444 ansible-playbook site.yml "$@"
445 popd >/dev/null
446 }
447
448 astudio() {
449 # googling android emulator libGL error: failed to load driver: r600
450 # lead to http://stackoverflow.com/a/36625175/14456
451 export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1
452 /a/opt/android-studio/bin/studio.sh "$@" & r
453 }
454
455 # Convert brains file path to url and vice versa
456 # usage: brains [URL_OR_PATH]
457 brains() {
458 _iki-convert brains.fsf.org/wiki "$@"
459 }
460 glue() {
461 _iki-convert gluestick.office.fsf.org "$@"
462 }
463
464 # usage: see above
465 _iki-convert() {
466 local url url_prefix path input repo_dir dir url_dir url name
467 url_prefix="$1"
468 name="${url_prefix%%.*}"
469 repo_dir="/f/$name"
470 shift
471 if [[ $1 ]]; then
472 input="$*"
473 else
474 read -r -p "enter path or url"$'\n' input
475 fi
476 case $input in
477 http*)
478 path="$repo_dir/${input##http*://"$url_prefix"/}"
479 # for files like x.jpg, we dont need to convert the extension.
480 if [[ $path == */ ]]; then
481 path=${path%/}.mdwn
482 # brains adds trailing slash, but without trailing is still
483 # valid. We can't be totally sure whether to add mdwn, but we
484 # can guess based on the existence of the file. We can't be sure
485 # because it could be a file like x.jpg, that we just don't have
486 # in our local repo.
487 elif [[ ! -f $path && -e $path.mdwn ]]; then
488 path=${path}.mdwn
489 fi
490 j printf "%s\n" "$path"
491 ;;
492 *)
493 path=$(fp "$input")
494 url_dir=$(echo "$path" | sed -r "s,^(/a)?$repo_dir/,,")
495 url="https://$url_prefix/$url_dir"
496 if [[ $url == *.mdwn ]]; then
497 url="${url%.mdwn}/"
498 fi
499 j echo "$url"
500 ;;
501 esac
502 }
503
504
505 # Generate beet smartplaylists for navidrome.
506 # for going in the reverse direction, run
507 # /b/ds/navidrome-playlist-export
508 beetsmartplaylists() {
509 install -m 0700 -d /tmp/ianbeetstmp
510 beet splupdate
511 # kill off any playlists we deleted. they will still need manual
512 # killing from a navidrome client.
513 rm -rf /i/converted/beetsmartplaylists
514 mkdir -p /i/converted/beetsmartplaylists
515 for f in /tmp/ianbeetstmp/*; do
516 sed 's,^/i/m,/i/converted,;s,\.flac$,.mp3,' "$f" >"/i/converted/beetsmartplaylists/${f##*/}"
517 rm "$f"
518 done
519 rmdir /tmp/ianbeetstmp
520 }
521
522 # internal function for beetrating, in case we need to ssh
523 beetrating-stdin() {
524 local tmp rating path cpath sqlpath userid
525 # plucked this from the db. im the only user.
526 userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634
527 while read -r rating path; do
528 cpath="/i/converted${path#/i/m}" # converted path
529 case $cpath in
530 *.flac)
531 cpath="${cpath%.*}.mp3"
532 ;;
533 esac
534 if [[ ! -e $cpath ]]; then
535 echo "beetraing: error: this should not happen, path does not exist: $cpath"
536 return 1
537 fi
538 sqlpath="${cpath//\'/\'\'}"
539 old_rating=$(sqlite3 /i/navidrome/navidrome.db "select rating from annotation inner join media_file on item_id = id where path = '$sqlpath' and item_type = 'media_file';")
540 if [[ $old_rating ]]; then
541 if [[ $old_rating != "$rating" ]]; then
542 echo "setting rating $old_rating -> $rating $cpath"
543 # https://stackoverflow.com/a/50317320
544 # we got a timeout error once. arbitrarily chose 15 seconds.
545 sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "
546 update annotation set rating = $rating
547 where item_id in (
548 select media_file.id from annotation inner join media_file on annotation.item_id = media_file.id
549 where media_file.path = '$sqlpath' and annotation.item_type = 'media_file' );"
550 fi
551 else
552 echo "setting rating $rating $cpath"
553 # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0
554 # https://www.sqlite.org/lang_insert.html
555 sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "insert into annotation select '$(uuidgen)', '$userid', id, 'media_file', 0, NULL, $rating, 0, NULL from media_file where path = '$sqlpath';"
556 fi
557 done
558 }
559
560 # Export beets ratings into navidrome
561 beetrating() {
562 local ssh_prefix
563 if [[ $HOSTNAME != kd ]]; then
564 ssh_prefix="ssh b8.nz"
565 fi
566 # shellcheck disable=SC2016 # obvious reason
567 beet ls -f '$rating $path' $nav_convert_query | $ssh_prefix beetrating-stdin
568 }
569
570 # Do transcoding and hardlinking of audio files for navidrome.
571 beetconvert() {
572 local tmpf
573 tmpf="$(mktemp)"
574 # a bunch of effort to ignore output we dont care about...
575 sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
576 beet -c $tmpf convert -y $nav_convert_query > >(grep -vFx 'ignore_this' ||:) 2> >(grep -v '^convert: Skipping' ||:)
577 rm "$tmpf"
578 }
579 # This deletes files in the converted directory which should no longer
580 # be there due to a rename of the unconverted file.
581 beetconvert-rm-extras() {
582 local l tmpf
583 local -A paths
584 tmpf="$(mktemp)"
585 # shellcheck disable=SC2016 # obvious reason
586 beet ls -f '$path' $nav_convert_query >"$tmpf"
587 ## begin removal of files that are leftover from previous conversion,
588 # eg, previously rated > 1, now rated 1.
589 while read -r l; do
590 convertedpath="/i/converted${l#/i/m}"
591 case $convertedpath in
592 *.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
593 esac
594 paths[$convertedpath]=t
595 done <"$tmpf"
596
597 find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \) -name '*.mp3' -o -name '*.m4a' >"$tmpf"
598 while read -r l; do
599 if [[ ! ${paths[$l]} ]]; then
600 rm -v "$l"
601 fi
602 # note: the pruning is duplicative of filtering on name, but whatever.
603 done <"$tmpf"
604 rm "$tmpf"
605 }
606
607 beets-gen-playlists() {
608 local i str
609 local -a query_array query_str
610 for i in "${!bpla[@]}"; do
611 query_str=()
612 eval "query_array=(${bpla[$i]})"
613 for str in "${query_array[@]}"; do
614 query_str+=("\"$str\"")
615 done
616 cat <<EOF
617 - name: $i.m3u
618 query: '${query_str[@]}'
619 EOF
620 done
621 }
622
623 # beet playlist. use beetag with a playlist name
624 bpl() {
625 local playlist playlist_regex
626 case $1 in
627 -h|--help)
628 for playlist in "${!bpla[@]}"; do
629 printf "%s\n" "$playlist"
630 done
631 return 0
632 ;;
633 esac
634
635 playlist="${*: -1}"
636 playlist_regex='[a-z0-9_]'
637 if [[ ! $playlist =~ $playlist_regex ]]; then
638 echo "bpl: error unexpected chars in playlist: $playlist"
639 return 1
640 fi
641 # all but last arg as options
642 eval beetag -r "${*:1:$# - 1}" "${bpla[$playlist]}"
643 }
644 complete -W "${!bpla[*]}" bpl
645
646
647 # beet modify quietly
648 beetmq() {
649 local tmpf
650 tmpf="$(mktemp)"
651 # a bunch of effort to ignore output we dont care about...
652 sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
653 beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:)
654 rm "$tmpf"
655 beetag-nostatus 1
656 }
657
658 kill-bg-quiet() {
659 # https://stackoverflow.com/a/5722874
660 kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||:
661 }
662
663 # debug variables
664 dv() {
665 for arg; do
666 printf "%s=%s " "$arg" "${!arg}"
667 done
668 echo
669 }
670
671 # Must be called from beetag for variables to be setup
672 beetag-help() {
673 local -i i j col_total row col button_total row_total remainder_cols remainder_term
674 col_total=4
675 button_total=${#button_map[@]}
676 row_total=$(( button_total / col_total ))
677 remainder_cols=$(( button_total % col_total ))
678 # for debugging
679 #dv button_total row_total remainder_cols
680 beetag-nostatus
681 # - 3 is just a constant that helps things work in practice.
682 if [[ $LINES ]] && (( LINES - 3 < scrolled )); then
683 hr
684 for (( i=0; i<button_total; i++)); do
685 row=$(( i / col_total ))
686 col=$(( i % col_total ))
687 remainder_term=$remainder_cols
688 if (( col < remainder_term )); then
689 remainder_term=$col
690 fi
691 j=$(( col * row_total + row + remainder_term ))
692 # avoid double newline when we have exactly row * col buttons
693 if (( i == button_total - 1 )); then
694 printf "%s %s" ${buttons[j]} ${button_map[j]}
695 elif (( i % col_total == col_total -1 )); then
696 printf "%s %s\n" ${buttons[j]} ${button_map[j]}
697 else
698 printf "%s %-15s" ${buttons[j]} ${button_map[j]}
699 fi
700 done
701 cat <<'EOF'
702
703
704 y other genres z fg player ' = toggle play 1-5 rate ] repeat1
705 ; previous _ = delete up/down skip mpv vol,pause,seek
706 EOF
707 hr
708 scrolled=10
709 fi
710 }
711
712 # Must be called from beetag for variables to be setup
713 beetag-nostatus() {
714 if (( $# )); then
715 scrolled=$(( scrolled + $1 ))
716 fi
717 if $erasable_line; then
718 # https://stackoverflow.com/a/71286261
719 # erase line / delete line in terminal
720 printf '\033[1A\033[K'
721 fi
722 erasable_line=false
723 }
724 # meant to be called from beetag
725 beetag-status() {
726 if $erasable_line; then
727 # https://stackoverflow.com/a/71286261
728 printf '\033[1A\033[K'
729 fi
730 erasable_line=true
731 }
732
733 # meant to be called from beetag
734 mpvrpc() {
735 if jobs -p | grep -q . &>/dev/null; then
736 printf "%s\n" "$*" | socat - /tmp/mpvsock >/dev/null ||:
737 fi
738 }
739 # meant to be called from beetag
740 # o for get output
741 mpvrpco() {
742 # note: testing for background jobs will output nothing if we are in a pipeline
743 printf "%s\n" "$*" | socat - /tmp/mpvsock ||:
744 }
745
746 # meant to be called from beetag
747 mpvrpc-percent-pos() {
748 mpvrpco '{ "command": ["get_property", "percent-pos"] }' | jq .data | sed 's/\..*/%/' 2>/dev/null ||:
749 }
750
751 # run if not running.
752 #
753 # Note: this does not work with shell scripts as they are normally
754 # invoked, because the ps output has the interpreter at the start.
755 # A workaround is to invoke the command in that format, or we could
756 # do various other workarounds.
757 #
758 # background, this relies on how ps converts newlines in arguments to spaces, and
759 # assumes we won't be searching for a command with spaces in its arguments
760 rinr() {
761 # shellcheck disable=SC2009 # pgrep has no fixed string option, plus see above.
762 if ps h -o args -C "${1##*/}" | grep -Fxqv "$*" &>/dev/null || [[ $? == 141 ]]; then
763 "$@"
764 fi
765 }
766 # variation of above: run or wait if running
767 rowir() {
768 local pid
769 pid=$(ps h -o 'pid,args' -C "${1##*/}" | sed -r 's/^[[:space:]]*([0-9]+)[[:space:]](.*)/\1\n\2/' | grep -B1 -Fx "$*" | head -n1 ||: )
770 if [[ $pid ]]; then
771 # https://unix.stackexchange.com/questions/427115/listen-for-exit-of-process-given-pid
772 tail --pid="$pid" -f /dev/null
773 else
774 "$@"
775 fi
776 }
777
778 mpvrpc-loadfile() {
779 local path nextpath cachedir finalpath nextpath count
780 cachedir=$HOME/.iank-music-cache
781 path="$1"
782 nextpath="$2"
783
784 # note: logic duplicated in beetpull
785 local remote_p=true
786 if [[ $HOSTNAME == kd ]]; then
787 remote_p=false
788 fi
789
790 if $remote_p; then
791 finalpath="$cachedir${path#/i/m}"
792 rowir rsync --partial -a --inplace --mkpath "b8.nz:$path" "$finalpath"
793 finalnextpath="$cachedir${nextpath#/i/m}"
794 count=$(pgrep -a -f "^rsync --partial -a --inplace --mkpath $cachedir" || [[ $? == 1 ]] )
795 # allow us to start 2 rsyncs in the background
796 if [[ $count == [01] ]]; then
797 rinr rsync --partial -a --inplace --mkpath "b8.nz:$nextpath" "$finalnextpath" &
798 fi
799 else
800 finalpath="$path"
801 fi
802 mpvrpc '{ "command": ["loadfile", "'"$finalpath"'"] }'
803 }
804
805 # tag with beets.
806 # usage: beetag [-r] [-s] QUERY
807 # it lists the query, reads an input char for tagging one by one.
808 #
809 # note, you may want to change the play command for doing rapid taging
810 # by immediately jumping forward into the song. this is set in the beets
811 # config yaml.
812 #
813 # (available buttons: ` \ ) ] [ and non-printing chars, see
814 # https://stackoverflow.com/questions/10679188/casing-arrow-keys-in-bash
815 #
816 #
817 # note: after foregrounding the player, must quit it to get back. can't ctrl-c.
818 #
819 # keys I dont need help to remember:
820 # 1-5 rate
821 # q quit
822 # ret next
823 #
824 beetag() {
825 local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random path
826 local do_rare_genres read_wait help line lsout tmp ls_line skip_lookback
827 local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec
828 local pl_state_path pl_state_dir pl_state_file tmpstr
829 local new_random pl_seed_path seed_num seed_file fmt first_play repeat1
830 local -a buttons button_map ids tags tmp_tags initial_ls ls_lines paths
831 local -A button_i
832 local -i i j volume scrolled id_count line_int skip_start pre_j_count head_count skip_lookback
833 local -i overflow_lines overflow
834
835 first_play=true
836 erasable_line=false
837 escape_char=$(printf "\u1b")
838 scrolled=999 # more than any $LINES
839 ### begin arg processing ###
840 random=false
841 repeat1=false
842 new_random=false
843 case $1 in
844 -r)
845 random=true
846 shift
847 ;;
848 -s)
849 random=false
850 shift
851 ;;
852 -x)
853 new_random=true
854 shift
855 ;;
856 esac
857 if (( ! $# )); then
858 echo beetag: error expected a query arg >&2
859 return 1
860 fi
861 ### end arg processing ###
862
863 # note: I used to do beetpull here, but mpv + ssfs on slowish
864 # connection leads to bad/buggy result.
865
866 do_rare_genres=false
867 volume=70
868 read_wait=2
869 doplay=true
870
871 last_genre_i=$(( ${#common_genres[@]} - 1 ))
872 buttons=( {a..p} {r..w} {6..8} , . / - "=")
873 button_map=(${common_genres[@]} ${pl_tags[@]})
874 fstring=
875 for tag in "${pl_tags[@]}"; do
876 fstring+="%ifdef{$tag,$tag }"
877 done
878
879 for (( i=0; i<${#buttons[@]}; i++ )); do
880 button_i[${buttons[i]}]=$i
881 done
882
883 # note: this structure of files is rather haphazard.
884 seed_num=1 # later we might want a few
885 seed_file=seed$seed_num
886 if $random; then
887 pl_state_file=$seed_num
888 else
889 pl_state_file=sorted
890 fi
891 pl_state_dir=/i/info/pl-state
892 if [[ $playlist ]]; then
893 pl_state_dir=$pl_state_dir/$playlist
894 else
895 pl_state_dir=$pl_state_dir/nopl
896 fi
897 pl_state_path=$pl_state_dir/$pl_state_file
898 pl_seed_path=$pl_state_dir/$seed_file
899
900
901 if $new_random || [[ ! -r $pl_seed_path ]]; then
902 mkdir -p $pl_state_dir
903 { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path
904 fi
905
906 # PijokVipiotOzeph is just a random string for a delimiter
907 # shellcheck disable=SC2016 # false positive
908 fmt='%ifdef{rating,$rating }'"$fstring"'$genre | $title - $artist - $album $length $id PijokVipiotOzeph $path'
909 # shellcheck disable=SC2016 # obvious reason
910 tmpstr=$(beet ls -f "$fmt" "$@" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } )
911 mapfile -t initial_ls <<<"$tmpstr"
912 if [[ ! ${initial_ls[0]} ]]; then
913 echo "beetag: error: no result from beet ls $*"
914 return 1
915 fi
916 id_count=${#initial_ls[@]}
917 for line in "${initial_ls[@]}"; do
918 path="${line#*PijokVipiotOzeph }"
919 # https://github.com/koalaman/shellcheck/issues/2171
920 # shellcheck disable=SC2190 # bug in shellcheck, looking at paths from an earlier function
921 paths+=("$path")
922 line_no_path="${line% PijokVipiotOzeph*}"
923 id="${line_no_path##* }"
924 ids+=("$id")
925 right_pad="${line_no_path%% |*}"
926 ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}"
927 ls_lines+=("$ls_line")
928 i=$(( i+1 ))
929 done
930
931
932
933
934 j=0
935 if [[ $playlist ]]; then
936 if [[ -r $pl_state_path ]]; then
937 j=$(cat $pl_state_path)
938 fi
939 fi
940
941 # i only care to see a smallish portion of the list when starting.
942 head_count=$(( LINES - 20 ))
943 head_start=$(( j - head_count / 2 ))
944 if (( head_start < 0 )); then
945 head_start=0
946 fi
947 for (( i=head_start; i < head_count && i < id_count; i++ )); do
948 ls_line="${ls_lines[$i]}"
949 if (( i == j )); then
950 echo "* $ls_line"
951 else
952 echo "$ls_line"
953 fi
954 done
955 if $doplay; then
956 #{ mpv --profile=a --volume=$volume --idle 2>&1 & } 2>/dev/null
957 mpv --profile=a --volume=$volume --idle &
958 # if we dont sleep, can expect an error like this:
959 # socat[1103381] E connect(5, AF=1 "/tmp/mpvsock", 14): Connection refused
960 sleep .1
961 fi
962
963 while true; do
964 id=${ids[j]}
965 path="${paths[$j]}"
966 lsout="${ls_lines[j]}"
967 tags=( ${lsout%%,*} )
968 beetag-help
969 printf "██ %s\n" "$lsout"
970 beetag-nostatus 1
971 if $doplay; then
972 # https://stackoverflow.com/a/7687716
973 # note: duplicated down below
974 #
975 # notes on old method of invoking mpv each time:
976 # https://superuser.com/questions/305933/preventing-bash-from-displaying-done-when-a-background-command-finishes-execut
977 # we can't disown or run in a subshell or set +m because all that
978 # disabled job control from working properly in ways we want.
979 # todo: figure out some kind of answer to this. I think the solution
980 # is that we are waiting in 2 second intervals and checking if the
981 # background job exists. Instead, we should make mpv just idle
982 # when it is done with a song and then send it a command to play a new track.
983 #{ mpv --profile=a --volume=$volume "$path" 2>&1 & } 2>/dev/null
984 # old
985 #{ beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
986
987 # on slow systems, we may need to wait like .3 seconds before mpv
988 # is ready. so impatiently check until it is ready
989 if $first_play; then
990 first_play=false
991 for (( i=0; i<20; i++ )); do
992 if [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' 2>/dev/null | jq .data) == true ]]; then
993 mpvrpc-loadfile "$path" 2>/dev/null
994 break
995 fi
996 sleep .1
997 done
998 else
999 mpvrpc-loadfile "$path"
1000 fi
1001 erasable_line=false
1002 fi
1003 while true; do
1004 char=
1005 if $doplay; then
1006 ret=0
1007 read -rsN1 -t $read_wait char || ret=$?
1008 read_wait=2
1009 # Automatically skip to the next song if this one ends, unless
1010 # we turn off the autoplay.
1011 if (( ret == 142 )) || [[ ! $char ]]; then
1012 if jobs -p | grep -q . &>/dev/null && \
1013 [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' | jq .data) == false ]]; then
1014 continue
1015 else
1016 break
1017 fi
1018 fi
1019 else
1020 read -rsN1 char
1021 fi
1022 beetag-help
1023 if [[ $char == $'\n' ]]; then
1024 break
1025 fi
1026 case $char in
1027 ";")
1028 j=$(( j - 2 ))
1029 break
1030 ;;
1031 "'")
1032 if $doplay; then
1033 echo "play toggled off"
1034 doplay=false
1035 else
1036 doplay=true
1037 mpvrpc-loadfile "$path"
1038 erasable_line=false
1039 fi
1040 beetag-nostatus 1
1041 continue
1042 ;;
1043 _)
1044 m beet rm --delete --force "id:$id"
1045 beetag-nostatus 4 # guessing. dont want to test atm
1046 break
1047 ;;
1048 [1-5])
1049 beetmq "id:$id" rating=$char
1050 continue
1051 ;;
1052 9)
1053 volume=$(( volume - 5 ))
1054 if (( volume < 0 )); then
1055 volume=0
1056 fi
1057 ;;&
1058 0)
1059 volume+=5
1060 if (( volume > 130 )); then
1061 volume=130
1062 fi
1063 ;;&
1064 0|9)
1065 mpvrpc '{ "command": ["set_property", "volume", '$volume'] }'
1066 beetag-status
1067 echo volume=$volume
1068 continue
1069 ;;
1070 ']')
1071 if $repeat1; then
1072 repeat1=false
1073 else
1074 repeat1=true
1075 fi
1076 echo repeat1=$repeat1
1077 continue
1078 ;;
1079 q)
1080 kill-bg-quiet
1081 return
1082 ;;
1083 y)
1084 if $do_rare_genres; then
1085 do_rare_genres=false
1086 button_map=(${common_genres[@]} ${pl_tags[@]})
1087 last_genre_i=$(( ${#rare_genres[@]} - 1 ))
1088 else
1089 do_rare_genres=true
1090 button_map=(${rare_genres[@]} ${pl_tags[@]})
1091 last_genre_i=$(( ${#rare_genres[@]} - 1 ))
1092 fi
1093 local -A button_i
1094 for (( i=0; i<${#buttons[@]}; i++ )); do
1095 button_i[${buttons[i]}]=$i
1096 done
1097 for (( i=0; i<${#button_map[@]}; i++ )); do
1098 echo ${buttons[i]} ${button_map[i]}
1099 done
1100 continue
1101 ;;
1102 z)
1103 beetag-nostatus 3
1104 # if we ctrl-z, it will put the whole function into sleep. so
1105 # basically, we can't return from a foregrounded mpv like we
1106 # would like to without some strange mechanism I can't think
1107 # of. So, instead, detect ctrl-c and wait a while for prompt
1108 # input. One idea would be to use a music player like mpd where
1109 # we can send it messages.
1110 if ! fg; then
1111 read_wait=10
1112 fi
1113 continue
1114 ;;
1115
1116 #
1117 " ")
1118 # output time if we aren't already paused
1119 if [[ $(mpvrpco '{ "command": ["get_property", "pause"] }' | jq .data) == false ]]; then
1120 # minutes/seconds
1121 #date -d @"$(mpvrpco '{ "command": ["get_property", "playback-time"] }' | jq .data)" +%M:%S ||:
1122 beetag-status
1123 mpvrpc-percent-pos
1124 fi
1125 # originally found this solution, which worked fine.
1126 #kill -STOP %% &>/dev/null
1127 #
1128 mpvrpc '{ "command": ["cycle", "pause"] }'
1129 continue
1130 ;;
1131 "$escape_char")
1132 expected_input=true
1133 read -rsn2 escaped_input
1134 skip_input_regex="^[0-9]+$"
1135 case $escaped_input in
1136 # up char: show all the songs, use less
1137 '[A')
1138 skip_start=0
1139 skip_lookback=5
1140 if (( j - skip_lookback > skip_start )); then
1141 skip_start=$(( j - skip_lookback ))
1142 fi
1143 beetag-nostatus $(( id_count - skip_start - 1 ))
1144 {
1145 line_int=0
1146 for (( i=skip_start; i < id_count; i++ )); do
1147 if (( i == j )); then
1148 echo " * ${ls_lines[i]}"
1149 continue
1150 fi
1151 echo "$line_int | ${ls_lines[i]}"
1152 line_int+=1
1153 done
1154 } | less -F
1155 ;;
1156 # down char
1157 '[B')
1158 # skip forward, but show the last few songs anyways.
1159 skip_start=0
1160 skip_lookback=3
1161 if (( j - skip_lookback > skip_start )); then
1162 skip_start=$(( j - skip_lookback ))
1163 fi
1164 beetag-nostatus $(( id_count - skip_start - 1 ))
1165
1166 line_int=0
1167 overflow_lines=$LINES
1168 for (( i=skip_start; i < overflow_lines - 1 && i < id_count; i++ )); do
1169 ls_line="${ls_lines[i]}"
1170 overflow=$(( ${#ls_line} / ( COLUMNS - 1 ) ))
1171 overflow_lines=$(( overflow_lines - overflow ))
1172 if (( i == j )); then
1173 echo " * $ls_line"
1174 continue
1175 fi
1176 echo "$line_int | $ls_line"
1177 line_int+=1
1178 done
1179 ;;
1180 # left key
1181 '[D')
1182 seek_sec=-8
1183 ;;&
1184 # right key
1185 '[C')
1186 seek_sec=8
1187 ;;&
1188 '[C'|'[D')
1189 beetag-status
1190 mpvrpc-percent-pos
1191 erasable_line=true
1192 mpvrpc '{ "command": ["seek", "'$seek_sec'"] }'
1193 continue
1194 ;;
1195 *)
1196 expected_input=false
1197 ;;
1198 esac
1199 if $expected_input; then
1200 read -r skip_input
1201 case $skip_input in
1202 q)
1203 kill-bg-quiet
1204 return
1205 ;;
1206 esac
1207 if [[ $skip_input =~ $skip_input_regex ]]; then
1208 pre_j_count=$(( j - skip_start ))
1209 j=$(( j + skip_input - pre_j_count ))
1210 if (( skip_input < pre_j_count )); then
1211 j=$(( j - 1 ))
1212 fi
1213 fi
1214 break
1215 fi
1216 ;;
1217 esac
1218 char_i=${button_i[$char]}
1219 new_item=${button_map[$char_i]}
1220 if [[ ! $char_i || ! $new_item ]]; then
1221 echo "error: no mapping of input: $char found, try again"
1222 continue
1223 fi
1224 if (( char_i <= last_genre_i )); then
1225 m beetmq "id:$id" genre=$new_item
1226 else
1227 remove=false
1228 tmp_tags=()
1229 for tag in ${tags[@]}; do
1230 if [[ $new_item == "$tag" ]]; then
1231 remove=true
1232 else
1233 tmp_tags+=("$tag")
1234 fi
1235 done
1236 if $remove; then
1237 tags=("${tags[@]}")
1238 m beetmq "id:$id" "$new_item!"
1239 else
1240 tags+=("$new_item")
1241 m beetmq "id:$id" $new_item=t
1242 fi
1243 fi
1244 done
1245 if ! $repeat1; then
1246 if (( j < id_count - 1 )); then
1247 j+=1
1248 else
1249 j=0
1250 fi
1251 fi
1252 if [[ $playlist ]]; then
1253 echo $j >$pl_state_path
1254 fi
1255 done
1256 }
1257
1258 # usage: FILE|ALBUM_DIR [GENRE]
1259 beetadd() {
1260 local import_path genre_arg single_track_arg
1261 import_path="$1"
1262 if [[ ! -e $import_path ]]; then
1263 echo "beetadd error: path does not exist"
1264 fi
1265 if [[ $2 ]]; then
1266 genre_arg="--set genre=$2"
1267 fi
1268 if [[ -f $import_path ]]; then
1269 single_track_arg=-s
1270 fi
1271 beet import --set totag=t $single_track_arg $genre_arg "$import_path"
1272 beetag totag:t
1273 beet modify -y totag:t "totag!"
1274 }
1275
1276 # update navidrome music data after doing beets tagging
1277 beet2nav() {
1278 m beetpull
1279 m beetconvert
1280 m beetrating
1281 # this function would naturally just be part of beetconvert,
1282 # but we want beetrating to happen sooner so that our ssh auth dialog
1283 # happens earlier. Currently 17 seconds for that.
1284 m beetconvert-rm-extras
1285 m beetsmartplaylists
1286 }
1287
1288 # pull in beets library locally
1289 beetpull() {
1290 local sshfs_host sshfs_cmd
1291 sshfs_host=b8.nz
1292 if [[ $HOSTNAME == kd ]]; then
1293 return 0
1294 fi
1295 if [[ ! -e /i ]]; then
1296 s mkdir /i
1297 s chown iank:iank /i
1298 fi
1299 sshfs_cmd="sshfs -o ServerAliveInterval=15,reconnect $sshfs_host:/i /i"
1300 if ! pgrep -f "^$sshfs_cmd$" >/dev/null; then
1301 m $sshfs_cmd
1302 fi
1303 }
1304
1305 # remove all playlists in navidrome, for when I make big
1306 # playlist name changes and just want to scrap everything.
1307 nav-rm-plists() {
1308 local tmpf id
1309 tmpf=$(mktemp)
1310 if [[ $HOSTNAME != kd ]]; then
1311 echo "error: run on kd"
1312 return 1
1313 fi
1314 sqlite3 /i/navidrome/navidrome.db "select id from playlist" >$tmpf
1315 while read -r id; do
1316
1317 curl --http1.1 --user "iank:$navidrome_pw" "https://b8.nz/rest/deletePlaylist.view?u=iank&s=sb219dvv7egnoe4i47k75cli0m&t=1c8f5575cd0fdf03deb971187c9c88b1&v=1.2.0&c=DSub&id=$id"
1318 done <$tmpf
1319 rm $tmpf
1320 }
1321
1322 # escape regex.
1323 #
1324 # This is not perfect but generally good enough. It escapes all
1325 # metachars listed man 3 pcrepattern.
1326 er() {
1327 sed 's/[]\\^$.[|()?*+{}]/[&]/g; s/\^/\\^/g' <<<"$*"
1328 }
1329
1330 # usage beegenre QUERY
1331 #
1332 # beet set genre for QUERY based on existing artist most used genre on
1333 #
1334 # inverse of query for each artist found in QUERY. If query starts with
1335 # "artist:" it is used as the artist instead of each artist in QUERY.
1336 #
1337 beegenre() {
1338 local count artist artregex genre singleartist tmpf tmpf2
1339 local -a artists genres
1340 singleartist=false
1341 case $1 in
1342 artist:*)
1343 singleartist=true
1344 artist="$1"
1345 shift
1346 ;;
1347 esac
1348 tmpf=$(mktemp)
1349 tmpf2=$(mktemp)
1350 if $singleartist; then
1351 # shellcheck disable=SC2016 # obvious reason
1352 beet ls -f '$genre' "$artist" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1 >$tmpf
1353 read -r count genre <$tmpf ||:
1354 beet modify "$artist" "$@" genre=$genre
1355 else
1356 # shellcheck disable=SC2016 # obvious reason
1357 beet ls -f '$artist' "$@" | sort -u >$tmpf
1358 while read -r artist; do
1359 artregex=$(er "$artist")
1360 # shellcheck disable=SC2016 # obvious reason
1361 beet ls -f '$genre' "artist::^$artregex$" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1 >$tmpf2
1362 read -r count genre <$tmpf2 || continue
1363 if [[ $count ]]; then
1364 artists+=("$artregex")
1365 genres+=("$genre")
1366 echo "beet modify -y $* \"artist::^$artist$\" genre=$genre # $count"
1367 fi
1368 done <$tmpf
1369 read -r -N 1 -s -p "Y/n " char
1370 case $char in
1371 [Yy$'\n'])
1372 for (( i=0; i<${#artists[@]}; i++ )); do
1373 beet modify -y "$@" "artist::^${artists[i]}$" genre=${genre[i]}
1374 done
1375 ;;
1376 esac
1377 fi
1378 rm $tmpf
1379 }
1380
1381 # prettify the date
1382 btrbk-date() {
1383 local indate
1384 indate="$1"
1385 shift
1386 date +%F_%T%:::z -d "$(sed -r 's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<"$indate")" "$@"
1387 }
1388 btrbk-undate() {
1389 # fudCaHougfirp is a random string
1390 { if [[ $1 ]]; then
1391 echo "$1"
1392 else
1393 cat
1394 fi
1395 } | sed -r 's/-0([45])( |$)/fudCaHougfirp0\100/;s/_/T/;s/[:-]//g;s/fudCaHougfirp/-/'
1396
1397 }
1398 btrbk-date-sed() {
1399 local line
1400 while read -r line; do
1401 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
1402 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*}"
1403 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}"
1404 mid="${line:${#pre}:22}"
1405 echo "$pre$(btrbk-date "$mid")$post"
1406 else
1407 echo "$line"
1408 fi
1409 done
1410 }
1411 jrbtrbk() {
1412 jr -u btrbk-run -u btrbk -u switch-mail-host "$@"
1413 }
1414
1415 # internal function
1416 btrbk-host-debug-show-host() {
1417 for f; do
1418 snaphost=
1419 for host in $remote $alt local; do
1420 if line=$(grep -P "\S*$f" /tmp/b/s/$host.log); then
1421 if [[ $snaphost ]]; then
1422 e error: snaphost=$snaphost, host=$host line="$line"
1423 fi
1424 if [[ $line == ssh* ]]; then
1425 tmp="${line#ssh://}"
1426 snaphost="${tmp%%/*}"
1427 else
1428 snaphost=$host
1429 fi
1430 fi
1431 done
1432 echo $snaphost $f | btrbk-date-sed
1433 done
1434 }
1435
1436 # If we get a btrfs receive error like this:
1437 # ERROR: ... clone: did not find source subvol
1438 # running this command will help track down the problem.
1439 # Alter remote= and alt=. When I used it, remote is
1440 # the host having the error when I push a snapshot.
1441 # Alt is just the other host that takes snapshots
1442 # besides the local host.
1443 btrbk-host-debug() {
1444
1445 remote=b8.nz
1446 alt=sywg.b8.nz
1447
1448 mkdir -p /tmp/b/s
1449 for host in $remote $alt; do
1450 h=$(ssh $host hostname)
1451 rsync -a /var/log/btrbk $host:/var/log/btrbk /var/log/btrbk/$h
1452 grr '\bsnapshot success' /var/log/btrbk/$h >/tmp/b/$h.log
1453
1454 ## this takes a while, we only want to do it on 1st run
1455 # if [[ -s /tmp/b/$host.log ]]; then continue; fi
1456 # ssh $host journalctl -u btrbk-run -u btrbk -u switch-mail-host >/tmp/b/$host.log
1457 done
1458 gr '\bsnapshot success' /var/log/btrbk/*.log >/tmp/b/local.log
1459 cd /tmp/b
1460 for f in *.log; do
1461 gr '\bsnapshot success' $f >s/$f
1462 done
1463 cd /mnt/root/btrbk
1464 localq=(q.*)
1465 declare -A localq_a
1466 for f in "${localq[@]}"; do
1467 localq_a[$f]=t
1468 done
1469
1470 remoteq=()
1471 for f in $(ssh $remote "cd /mnt/root/btrbk; echo q.*"); do
1472 if [[ ! ${localq_a[$f]} ]]; then
1473 remoteq+=($f)
1474 fi
1475 done
1476 btrbk-host-debug-show-host "${localq[@]}"
1477 if (( ${#remoteq[@]} >= 1 )); then
1478 echo "=== $remote only ===="
1479 btrbk-host-debug-show-host ${remoteq[@]}
1480 fi
1481
1482 }
1483
1484 # note, to check for glue records
1485 # First, find some the .org nameservers:
1486 # dig +trace iankelling.org
1487 # then, query one:
1488 # dig ns1.iankelling.org @b0.org.afilias-nst.org.
1489
1490 # Now, compare for a domain that does have glue records setup (note the A
1491 # and AAAA records in ADDITIONAL SECTION, those are glue records like the
1492 # one I'm asking for):
1493
1494 # $ dig ns1.gnu.org @b0.org.afilias-nst.org.
1495
1496 # todo: make sm pull/push use systemd instead of the journal cat command
1497 bbk() { # btrbk wrapper
1498 local ret=0
1499 c /
1500 local active=true
1501 systemctl is-active btrbk.timer || active=false
1502 if $active; then
1503 ser stop btrbk.timer
1504 fi
1505 btrbk_is_active=$(systemctl is-active btrbk.service ||:)
1506 case $btrbk_is_active in
1507 inactive|failed) : ;;
1508 *)
1509 echo "bbk: error: systemctl is-active btrbk.service output: $btrbk_is_active"
1510 if $active; then ser start btrbk.timer; fi
1511 return 1
1512 ;;
1513 esac
1514 # todo: consider changing this to srun and having the args come
1515 # from a file like /etc/default/btrbk, like is done in exim
1516 s jdo btrbk-run "$@"
1517 if $active; then
1518 if (( ret )); then
1519 echo bbk: WARNING: btrbk.timer not restarted due to failure
1520 else
1521 ser start btrbk.timer
1522 fi
1523 fi
1524 return $ret
1525 }
1526
1527 faimon() {
1528 fai-monitor | pee cat "fai-monitor-gui -"
1529 }
1530
1531 bfg() { java -jar /a/opt/bfg-1.12.14.jar "$@"; }
1532
1533 bigclock() {
1534 xclock -digital -update 1 -face 'arial black-80:bold'
1535 }
1536
1537 nnn() { /a/opt/nnn -H "$@"; }
1538
1539 locat() { # log-once cat
1540 local files
1541 ngset
1542 files=(/var/local/cron-errors/* /home/iank/cron-errors/* /sysd-mail-once-state/*)
1543 case ${#files[@]} in
1544 0) : ;;
1545 1)
1546 echo ${files[0]}
1547 head ${files[0]}
1548 ;;
1549 *)
1550 head ${files[@]}
1551 ;;
1552 esac
1553 ngreset
1554 }
1555
1556 scr() {
1557 screen -RD "$@"
1558 }
1559
1560 # usage: first get an adb shell on the phone.
1561 #
1562 # just followed instructions in readme at
1563 # https://github.com/Yuubi-san/ceb-tools
1564 # tried to use ceb2txt but it failed because of schema
1565 # slightly different than what it expected.
1566 cheogram-get-logs() {
1567 #adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
1568 read -r -p "do cheogram backup on phone, do not enable extra cheogram data. press any key when done"
1569 cd /p/cheogram
1570 rm -rf Backup b
1571 adb pull /storage/emulated/0/Download/Cheogram/Backup
1572 sqlite3 b </a/opt/ceb-tools/schema.sql
1573 echo "note: the next step took 39 seconds last time i measured"
1574 # expected failure: Error: near line 1: in prepare, table accounts has no column named pinned_mechanism (1)
1575 # the sql needs an update
1576 /a/opt/ceb-tools/ceb2sqlgz Backup/iank@fsf.org.ceb <pas | gunzip | sqlite3 b ||:
1577 rm -r Backup
1578 }
1579
1580 # usage: cheologs [DAYS_LIMIT]
1581 # default days is 100
1582 cheologs() {
1583 local days q
1584 days=${1:-100}
1585 q="
1586 select
1587 datetime(substr(timeSent,0,11), 'unixepoch'),
1588 replace(replace(counterpart,'@fsf.org',''),
1589 '@conference.fsf.org',''),
1590 body
1591 from messages
1592 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
1593 order by timeSent;"
1594 sqlite3 /p/cheogram/b ".mode tabs" "$q" | less
1595 }
1596
1597 mycheologs() {
1598 local days q
1599 days=${1:-16}
1600 # timezone compared to utc. note: this takes the current offset, so if daylight savings change
1601 # happened in the looking back period, this won't account for it.
1602 zone_offset=$(( $( date +%z | sed 's/[^1-9-]*//g' ) * 60 * 60))
1603 case $zone_offset in
1604 -*) : ;;
1605 *) zone_offset="+ $zone_offset"
1606 esac
1607 echo zone_offset=$zone_offset
1608 q="
1609 select
1610 datetime(substr(timeSent,0,11) $zone_offset, 'unixepoch'),
1611 body
1612 from messages
1613 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
1614 and counterpart = 'office@conference.fsf.org/iank'
1615 order by timeSent;"
1616 sqlite3 /p/cheogram/b ".mode tabs" "$q" | sed 's/ /./' | less
1617 }
1618
1619 # version of jdo for my non-root user
1620 jdo() {
1621 # comparison of alternative logging methods:
1622 #
1623 # systemd-run command (what this function does)
1624 #
1625 # If there is a user prompt, the program will detect that it is not
1626 # connected to a terminal and act in a non-interactive way, skipping
1627 # the prompt. This has the benefit that you know exactly how the
1628 # program will act if you want to move it into a service that runs
1629 # automatically.
1630 #
1631 # If run with sudo and command is a shell script which does a sleep,
1632 # it can (sometimes?) output some extra whitespace in front of
1633 # messages, more for each subsequent message. This can be avoided by
1634 # becoming root first.
1635 #
1636 # It logs the command's pid and exit code, which is nice.
1637 #
1638 #
1639 ### command |& ts | tee file.log
1640 #
1641 # If there is a user prompt, like "read -p prompt var", it will hang
1642 # without outputting the prompt.
1643 #
1644 # I've had a few times where ts had an error and I wasn't totally sure
1645 # if it was really the command or ts having the problem.
1646 #
1647 # Sometimes some output will get hidden until you hit enter.
1648 #
1649 #
1650 ### command |& pee cat logger
1651 #
1652 # This seems to work. I need to test more.
1653 #
1654 #
1655 ### command |& logger -s
1656 #
1657 # User prompts get confusingly prefixed to earlier output, and all log
1658 # entries get prefixed with annoying priority level.
1659 #
1660 #
1661 ### systemd-cat
1662 #
1663 # Had a few problems. One major one is that it exited in the middle of
1664 # a command on systemctl daemon-reload
1665 #
1666 # Related commands which can log a whole session: script, sudo, screen
1667 local cmd cmd_name jr_pid ret
1668 ret=0
1669 cmd="$1"
1670 shift
1671 cmd_name=${cmd##*/}
1672 if [[ $cmd != /* ]]; then
1673 cmd=$(type -P "$cmd")
1674 fi
1675 # -q = quiet
1676 journalctl -qn2 -f -u "$cmd_name" &
1677 # Trial and error of time needed to avoid missing initial lines.
1678 # .5 was not reliable. 1 was not reliable. 2 was not reliable
1679 sleep 4
1680 jr_pid=$!
1681 # note, we could have a version that does system --user, but if for example
1682 # it does sudo ssh, that will leave a process around that we can't kill
1683 # and it will leave the unit hanging around in a failed state needing manual
1684 # killing of the process.
1685 s systemd-run --uid "$(id -u)" --gid "$(id -g)" \
1686 -E SSH_AUTH_SOCK=/run/openssh_agent \
1687 --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
1688 # The sleep lets the journal output its last line
1689 # before the prompt comes up.
1690 sleep .5
1691 kill $jr_pid &>/dev/null ||:
1692 unset jr_pid
1693 fg &>/dev/null ||:
1694 # this avoids any err-catch
1695 (( ret == 0 )) || return $ret
1696 }
1697
1698 # service run, and watch the output
1699 srun() {
1700 local unit
1701 ret=0
1702 unit=$1
1703 journalctl -qn2 -f -u $unit &
1704 systemctl start $unit
1705 sleep 2
1706 kill $jr_pid &>/dev/null ||:
1707 unset jr_pid
1708 fg &>/dev/null ||:
1709 }
1710
1711 sm() { # switch mail host
1712 local tmp keyhash
1713 c /
1714 # run latest
1715 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
1716 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"' ||:)
1717 if [[ ! $tmp ]]; then
1718 s ssh-add /root/.ssh/home
1719 fi
1720 s jdo switch-mail-host "$@"
1721 return $ret
1722 }
1723 sh2() { # switch host2
1724 local tmp keyhash
1725 c /
1726 # run latest
1727 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
1728 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
1729 if [[ ! $tmp ]]; then
1730 s ssh-add /root/.ssh/home
1731 fi
1732 install-my-scripts
1733 s jdo switch-host2 "$@"
1734 return $ret
1735 }
1736
1737 # shellcheck disable=SC2120
1738 lipush() {
1739 # note, i had --delete-excluded, but that deletes all files in --exclude-from on
1740 # the remote site, which doesn't make sense, so not sure why i had it.
1741 local p a
1742 # excluding emacs for now
1743 #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1744 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter /a/opt/fpaste)
1745 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1746 ret=0
1747 for h in li je bk; do
1748 m s rsync "$@" $a ${p[@]} /p/c/machine_specific/$h root@$h.b8.nz:/
1749 ## only li is debian11
1750 #p[0]=/a/opt/emacs-trisuqel10
1751 #p[1]=/a/opt/emacs-trisquel10-nox
1752 done
1753 m s rsync "$@" -ahviSAXPH root@li.b8.nz:/a/h/proposed-comments/ /a/h/proposed-comments || ret=$?
1754 return $ret
1755 }
1756 bkpush() { # no emacs. for running faster.
1757 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter)
1758 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1759 ret=0
1760 m rsync "$@" $a ${p[@]} /p/c/machine_specific/bk root@bk.b8.nz:/ || ret=$?
1761 return $ret
1762 }
1763 jepush() { # no emacs. for running faster.
1764 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter)
1765 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1766 ret=0
1767 m rsync "$@" $a ${p[@]} /p/c/machine_specific/je root@je.b8.nz:/ || ret=$?
1768 return $ret
1769 }
1770
1771 bindpush() {
1772 dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
1773 lipush
1774 for h in li bk; do
1775 e sshing $h
1776 ssh $h.b8.nz <<'EOF'
1777 source ~/.bashrc
1778 m dnsup
1779 EOF
1780 done
1781 }
1782 bindpushb8() {
1783 lipush
1784 for h in li bk; do
1785 e sshing $h
1786 ssh $h.b8.nz <<'EOF'
1787 source ~/.bashrc
1788 m dnsb8
1789 EOF
1790 done
1791 }
1792
1793 dnsup() {
1794 conflink -f
1795 m ser reload named
1796 }
1797 dnsb8() {
1798 local f=/var/lib/bind/db.b8.nz
1799 m ser stop named
1800 m sleep 1
1801 m sudo rm -fv $f.jnl $f.signed.jnl
1802 m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f
1803 m ser restart named
1804 }
1805 dnsecgen() {
1806 # keys generated like this
1807 # because of https://ftp.isc.org/isc/dnssec-guide/dnssec-guide.pdf
1808 # https://blog.apnic.net/2019/05/23/how-to-deploying-dnssec-with-bind-and-ubuntu-server/
1809
1810 # key length is longer than that guide because
1811 # we are using those at fsf and when old key lengths
1812 # become insecure, I want some extra time to update.
1813 # dnsecgen (in brc2)
1814
1815 local zone=$1
1816 dnssec-keygen -a RSASHA256 -b 2048 $zone
1817 dnssec-keygen -f KSK -a RSASHA256 -b 4096 $zone
1818 for f in K"$zone".*.key; do
1819 # eg Kb8.nz.+008+47995.key tag=47995
1820 # in dnsimple, you add the long string from this.
1821 # in gandi, you add the long string from the .key file,
1822 # then see that the digest matches the ds.
1823 echo "tag is the number after DS"
1824 dnssec-dsfromkey -a SHA-256 $f
1825 done
1826 # For b8.nz, we let bind read the keys and sign, and
1827 # right now they have root ownership, so let them
1828 # get group read.
1829 chmod g+r ./*.private
1830 }
1831 dsign() {
1832 # create .signed file
1833 # note: full paths probably not needed.
1834 local arg
1835 for arg; do
1836 local zone=${arg#db.}
1837 local dir=/p/c/machine_specific/vps/filesystem/var/lib/bind
1838 dnssec-signzone -S -e +31536000 -o $zone -K $dir -d $dir $dir/db.$zone
1839 done
1840 }
1841
1842 # set day start for use in other programs.
1843 # expected to do be in a format like 830, or 800 or 1300.
1844 ds() {
1845 if [[ $1 ]]; then
1846 echo $1 >/b/data/daystart
1847 else
1848 cat /b/data/daystart
1849 fi
1850 }
1851
1852 #### begin bitcoin related things
1853 btc() {
1854 local f=/etc/bitcoin/bitcoin.conf
1855 # importprivkey will timeout if using the default of 15 mins.
1856 # upped it to 1 hour.
1857 bitcoin-cli -rpcclienttimeout=60000 -"$(s grep rpcuser= $f)" -"$(s grep rpcpassword= $f)" "$@"
1858 }
1859 btcusd() { # $1 btc in usd
1860 local price
1861 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1862 printf "$%s\n" "$price"
1863 if [[ $1 ]]; then
1864 printf "$%.2f\n" "$(echo "scale=4; $price * $1"| bc -l)"
1865 fi
1866 }
1867 usdbtc() { # $1 usd in btc
1868 local price
1869 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1870 printf "$%s\n" "$price"
1871 if [[ $1 ]]; then
1872 # 100 mil satoshi / btc. 8 digits after the 1.
1873 printf "%.8f btc\n" "$(echo "scale=10; $1 / $price "| bc -l)"
1874 fi
1875 }
1876 satoshi() { # $1 satoshi in usd
1877 local price
1878 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1879 price=$(echo "scale=10; $price * 0.00000001"| bc -l)
1880 printf "$%f\n" "$price"
1881 if [[ $1 ]]; then
1882 printf "$%.2f\n" "$(echo "scale=10; $price * $1"| bc -l)"
1883 fi
1884 }
1885
1886 # Bitcoin holds open the wallet file. this causes problems for a
1887 # secondary computer running bitcoin and receiving a backup (as of
1888 # 2023). However, in 2024-02, I ran a backup where a receiving machine
1889 # had the wallet enabled and there was no error, so I don't know if this
1890 # is still an issue or likely it is an inconsistent behavior.
1891 #
1892 # As a workaround, this function is for enabling the wallet when I want
1893 # to use it and leave it disabled otherwise.
1894 walleton() {
1895 local active
1896 active=false
1897 no_on=true
1898 if [[ ! $(readlink -f /var/lib/bitcoind/wallets) == /q/wallets ]]; then
1899 if systemctl --quiet is-active bitcoind; then
1900 if [[ -e /tmp/no-bitcoinon ]]; then
1901 no_on=true
1902 else
1903 if [[ $EUID == 0 ]]; then
1904 m install -T -o iank -g iank /dev/null /tmp/no-bitcoinon
1905 else
1906 m touch /tmp/no-bitcoinon
1907 fi
1908 fi
1909 active=true
1910 m ser stop bitcoind
1911 fi
1912 m s ln -s /q/wallets /var/lib/bitcoind
1913 sudo chown -h bitcoin:bitcoin /var/lib/bitcoind/wallets
1914 if $active; then
1915 m ser start bitcoind
1916 if ! $no_on; then
1917 m rm /tmp/no-bitcoinon
1918 fi
1919 fi
1920 fi
1921 }
1922 walletoff() {
1923 local active
1924 active=false
1925 no_on=true
1926 if [[ $(readlink -f /var/lib/bitcoind/wallets) == /q/wallets ]]; then
1927 if systemctl --quiet is-active bitcoind; then
1928 if [[ -e /tmp/no-bitcoinon ]]; then
1929 no_on=true
1930 else
1931 if [[ $EUID == 0 ]]; then
1932 m install -T -o iank -g iank /dev/null /tmp/no-bitcoinon
1933 else
1934 m touch /tmp/no-bitcoinon
1935 fi
1936 fi
1937 active=true
1938 m ser stop bitcoind
1939 else
1940 echo note: bitcoind not active
1941 fi
1942 m rm /var/lib/bitcoind/wallets
1943 if $active; then
1944 # note, starting bitcoin always fails, but it actually
1945 # succeeds. But this is strangely not consistent.
1946 m ser start bitcoind
1947 if ! $no_on; then
1948 m rm /tmp/no-bitcoinon
1949 fi
1950 fi
1951 fi
1952 }
1953 #### end bitcoin related things
1954
1955
1956
1957 cbfstool () { /a/opt/coreboot/build/cbfstool "$@"; }
1958
1959
1960 cgpl()
1961 {
1962 if (($#)); then
1963 cp /a/bin/data/COPYING "$@"
1964 else
1965 cp /a/bin/data/COPYING .
1966 fi
1967 }
1968
1969 capache()
1970 {
1971 if (($#)); then
1972 cp /a/bin/data/LICENSE "$@"
1973 else
1974 cp /a/bin/data/LICENSE .
1975 fi
1976 }
1977
1978 chrome() {
1979 if type -p chromium &>/dev/null; then
1980 cmd=chromium
1981 else
1982 cd /
1983 cmd="schroot -c bullseye chromium"
1984 CHROMIUM_FLAGS='--enable-remote-extensions' $cmd & r
1985 fi
1986 }
1987
1988
1989 # do all tee.
1990 # pipe to this, or just type like a shell
1991 # todo: test this
1992 dat() {
1993 tee >(ssh frodo.b8.nz) >(ssh x2) >(ssh tp.b8.nz) >(ssh kw) >(ssh tp.b8.nz)
1994 }
1995 da() { # do all
1996 local host
1997 for host in x2 kw tp.b8.nz x3.b8.nz frodo.b8.nz; do
1998 ssh $host "$@"
1999 done
2000 }
2001
2002
2003 debian_pick_mirror () {
2004 # netselect-apt finds a fast mirror.
2005 # but we need to replace the mirrors ourselves,
2006 # because it doesnt do that. best it can do is
2007 # output a basic sources file
2008 # here we get the server it found, get the main server we use
2009 # then substitute all instances of one for the other in the sources file
2010 # and backup original to /etc/apt/sources.list-original.
2011 # this is idempotent. the only way to identify debian sources is to
2012 # note the original server, so we put it in a comment so we can
2013 # identify it later.
2014 local file
2015 file=$(mktemp -d)/f # safe way to get file name without creating one
2016 sudo netselect-apt -o "$file" || return 1
2017 url=$(grep ^\\w $file | head -n1 | awk '{print $2}')
2018 sudo cp -f /etc/apt/sources.list /etc/apt/sources.list-original
2019 sudo sed -ri "/http.us.debian.org/ s@( *[^ #]+ +)[^ ]+([^#]+).*@\1$url\2# http.us.debian.org@" /etc/apt/sources.list
2020 sudo apt-get update
2021 }
2022 digme() {
2023 digdiff @ns{1,2}.iankelling.org "$@"
2024 }
2025
2026 dup() {
2027 local ran_d
2028 ran_d=false
2029 system-status _
2030 case $PS1 in
2031 *[\ \]]D\ *)
2032 pushd /
2033 /b/ds/distro-begin |& ts || return $?
2034 /b/ds/distro-end |& ts || return $?
2035 popd
2036 ran_d=true
2037 ;;&
2038 *[\ \]]DB\ *)
2039 pushd /
2040 /b/ds/distro-begin |& ts || return $?
2041 popd
2042 ran_d=true
2043 ;;
2044 *[\ \]]DE\ *)
2045 pushd /
2046 /b/ds/distro-end |& ts || return $?
2047 popd
2048 ran_d=true
2049 ;;&
2050 *CONFLINK*)
2051 if ! $ran_d; then
2052 conflink
2053 fi
2054 ;;
2055 esac
2056 system-status _
2057 }
2058
2059 envload() { # load environment from a previous: export > file
2060 local file=${1:-$HOME/.${USER}_env}
2061 eval "$(export | sed 's/^declare -x/export -n/')"
2062 while IFS= read -r line; do
2063 # declare -x makes variables local to a function
2064 eval ${line/#declare -x/export}
2065 done < "$file"
2066 }
2067
2068 failfunc() { asdf a b c; }
2069 failfunc2() { failfunc d e f; }
2070
2071 # one that comes with distros is too old for newer devices
2072 fastboot() {
2073 /a/opt/android-platform-tools/fastboot "$@";
2074 }
2075
2076 kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
2077
2078 bat() {
2079 cat /sys/class/power_supply/BAT0/capacity
2080 }
2081
2082 # List of apps to install/update
2083 # Create from existing manually installed apps by doing
2084 # fdroidcl update
2085 # fdroidcl search -i, then manually removing
2086 # automatically installed/preinstalled apps
2087
2088 #
2089 # # my attempt at recovering from boot loop:
2090 # # in that case, boot to recovery (volume up, home button, power, let go of power after samsun logo)
2091 # # then
2092 # mount /dev/block/mmcblk0p12 /data
2093 # cd /data
2094 # find -iname '*appname*'
2095 # rm -rf FOUND_DIRS
2096 # usually good enough to just rm -rf /data/app/APPNAME
2097 #
2098 # currently broken:
2099 # # causes replicant to crash
2100 # org.quantumbadger.redreader
2101 # org.kde.kdeconnect_tp
2102
2103 # not broke, but wont work without gps
2104 #com.zoffcc.applications.zanavi
2105 # not broke, but not using atm
2106 #com.nutomic.syncthingandroid
2107 # # doesn\'t work on replicant
2108 #net.sourceforge.opencamera
2109 #
2110 fdroid_pkgs=(
2111 net.mullvad.mullvadvpn
2112 org.schabi.newpipe
2113 io.github.subhamtyagi.lastlauncher
2114 io.anuke.mindustry
2115 com.biglybt.android.client
2116 de.marmaro.krt.ffupdater
2117 me.ccrama.redditslide
2118 org.fedorahosted.freeotp
2119 at.bitfire.davdroid
2120 com.alaskalinuxuser.justnotes
2121 com.artifex.mupdf.viewer.app
2122 com.danielkim.soundrecorder
2123 com.fsck.k9
2124 com.ichi2.anki
2125 com.jmstudios.redmoon
2126 com.jmstudios.chibe
2127 org.kde.kdeconnect_tp
2128 com.notecryptpro
2129 com.termux
2130 cz.martykan.forecastie
2131 de.danoeh.antennapod
2132 de.blinkt.openvpn
2133 de.marmaro.krt.ffupdater
2134 eu.siacs.conversations
2135 free.rm.skytube.oss
2136 im.vector.alpha # riot
2137 info.papdt.blackblub
2138 me.tripsit.tripmobile
2139 net.gaast.giggity
2140 net.minetest.minetest
2141 net.osmand.plus
2142 org.isoron.uhabits
2143 org.linphone
2144 org.gnu.icecat
2145 org.smssecure.smssecure
2146 org.yaaic
2147 sh.ftp.rocketninelabs.meditationassistant.opensource
2148 )
2149 # https://forum.xda-developers.com/android/software-hacking/wip-selinux-capable-superuser-t3216394
2150 # for maru,
2151 #me.phh.superuser
2152
2153 fdup() {
2154 local -A installed updated
2155 local p
2156 # tried putting this in go buildscript cronjob,
2157 # but it failed with undefined: os.UserCacheDir. I expect its due to
2158 # an environment variable missing, but its easier just to stick it here.
2159 m go get -u mvdan.cc/fdroidcl || return 1
2160 m fdroidcl update
2161 if fdroidcl search -u | grep ^org.fdroid.fdroid; then
2162 fdroidcl install org.fdroid.fdroid
2163 sleep 5
2164 m fdroidcl update
2165 fi
2166 for p in $(fdroidcl search -i| grep -o "^\S\+"); do
2167 installed[$p]=true
2168 done
2169 for p in $(fdroidcl search -u| grep -o "^\S\+"); do
2170 updated[$p]=false
2171 done
2172 for p in ${fdroid_pkgs[@]}; do
2173 if ! ${installed[$p]:-false}; then
2174 m fdroidcl install $p
2175 # sleeps are just me being paranoid since replicant has a history of crashing when certain apps are installed
2176 sleep 5
2177 fi
2178 done
2179 for p in ${!installed[@]}; do
2180 if ! ${updated[$p]:-true}; then
2181 m fdroidcl install $p
2182 sleep 5
2183 fi
2184 done
2185 }
2186
2187 firefox-default-profile() {
2188 local key value section
2189 key=Default
2190 value=1
2191 section=$1
2192 file=/p/c/subdir_files/.mozilla/firefox/profiles.ini
2193 sed -ri "/^ *$key/d" "$file"
2194 sed -ri "/ *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d};/ *\[$section\]/a $key=$value" "$file"
2195 }
2196 fdhome() { #firefox default home profile
2197 firefox-default-profile Profile0
2198 }
2199
2200 fdwork() {
2201 firefox-default-profile Profile4
2202 }
2203
2204 ff() {
2205 if type -P firefox &>/dev/null; then
2206 firefox "$@"
2207 else
2208 iceweasel "$@"
2209 fi
2210 }
2211
2212 fn() {
2213 firefox -P alt "$@" >/dev/null 2>&1
2214 }
2215
2216
2217 fsdiff () {
2218 local missing=false
2219 local dname="${PWD##*/}"
2220 local m="/a/tmp/$dname-missing"
2221 local d="/a/tmp/$dname-diff"
2222 [[ -e $d ]] && rm "$d"
2223 [[ -e $m ]] && rm "$m"
2224 local msize=0
2225 local fsfile
2226 while read -r line; do
2227 fsfile="$1${line#.}"
2228 if [[ -e "$fsfile" ]]; then
2229 md5diff "$line" "$fsfile" && tee -a "/a/tmp/$dname-diff" <<< "$fsfile $line"
2230 else
2231 missing=true
2232 echo "$line" >> "$m"
2233 msize=$((msize + 1))
2234 fi
2235 done < <(find . -type f )
2236 if $missing; then
2237 echo "$m"
2238 (( msize <= 100 )) && cat $m
2239 fi
2240 }
2241 fsdiff-test() {
2242 local tmpd x
2243 # expected output, with different tmp dirs
2244 # /tmp/tmp.HDPbwMqdC9/c/d ./c/d
2245 # /a/tmp/tmp.qLDkYxBYPM-missing
2246 # ./b
2247 tmpd="$(mktemp -d)"
2248 cd "$tmpd"
2249 echo ok > a
2250 echo nok > b
2251 mkdir c
2252 echo ok > c/d
2253 local x
2254 x=$(mktemp -d)
2255 mkdir $x/c
2256 echo different > $x/c/d
2257 echo ok > $x/a
2258 fsdiff $x
2259 rm -r "$x" "$tmpd"
2260 }
2261 rename-test() {
2262 # test whether missing files were renamed, generally for use with fsdiff
2263 # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir
2264 # echos non-renamed files
2265 local x y found
2266 unset sums
2267 for x in "$2"/*; do
2268 { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null
2269 done
2270 while read -r line; do
2271 { missing_sum=$(md5sum < "$line") ; } 2>/dev/null
2272 renamed=false
2273 for x in "${sums[@]}"; do
2274 if [[ $missing_sum == "$x" ]]; then
2275 renamed=true
2276 break
2277 fi
2278 done
2279 $renamed || echo "$line"
2280 done < "$1"
2281 return 0
2282 }
2283
2284 feh() {
2285 # F = fullscren, z = random, Z = auto zoom
2286 command feh --auto-rotate -FzZ "$@"
2287 }
2288
2289
2290
2291 fw() {
2292 firefox -P default "$@" >/dev/null 2>&1
2293 }
2294
2295 gitian() {
2296 git config user.email ian@iankelling.org
2297 }
2298
2299 # at least in flidas, things rely on gpg being gpg1
2300 gpg() {
2301 if type -P gpg2 &>/dev/null; then
2302 command gpg2 "$@"
2303 else
2304 command gpg "$@"
2305 fi
2306 }
2307
2308 gse() {
2309 local email=iank@fsf.org
2310 git send-email --notes "--envelope-sender=<$email>" \
2311 --suppress-cc=self "$@"
2312 }
2313
2314 gup() { /a/f/gnulib/build-aux/gnupload "$@"; }
2315
2316 dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
2317
2318 hstatus() {
2319 # do git status on published repos.
2320 c /a/bin/githtml
2321 for x in *; do
2322 cd "$(readlink -f $x)"/..
2323 status=$(i status -s) || pwd
2324 if [[ $status ]]; then
2325 hr
2326 echo $x
2327 printf "%s\n" "$status"
2328 fi
2329 cd /a/bin/githtml
2330 done
2331 }
2332
2333 ## work log
2334 #
2335 # note: database location is specified in ~/.timetrap.yml, currently /p/.timetrap.db
2336 wlog() {
2337 local day i days_back
2338 days_back=${1:-16}
2339 for (( i=days_back; i>=0; i-- )); do
2340 day=$( date +%F -d @$((EPOCHSECONDS - 86400*i )) )
2341 date "+%a %b %d" -d @$((EPOCHSECONDS - 86400*i )) | tr '\n' ' '
2342 /a/opt/timetrap/bin/t d -ftotal -s $day -e $day all -m '^w|lunch$'
2343 done
2344 }
2345 to() { t out -a "$@"; }
2346 ti() { t in -a "$@"; }
2347 tl() {
2348 local in_secs
2349 to "$*"
2350 t s lunch
2351 t in -a "$*"
2352 in_secs="$(date -d "${*//[_.]/ }" +%s)"
2353 m t out -a "$(date +%F.%T -d @$(( in_secs + 60*45 )) )"
2354 t s w
2355 }
2356
2357
2358 # help me focus. opens 2 windows.
2359 focus() {
2360 /p/c/proc/focus/linux-amd64/focus &
2361 watcharb5
2362 kill %%
2363 }
2364
2365
2366 # Display a list of the active window title
2367 # i've been on with 10 second samples going back
2368 # 5 minutes. If I've been on one window for 10 seconds
2369 # or longer, then display the second count.
2370 #
2371 # Press any key to exit.
2372 watcharb5() {
2373 local char ret
2374 killall arbtt-capture &>/dev/null ||:
2375 rm -f ~/.arbtt/capture.log
2376 arbtt-capture --sample-rate=10 &
2377 while true; do
2378 arb5
2379 ret=0
2380 # i first thought to sleep and capture ctrl-c, but it seems we can't
2381 # capture control-c, unless maybe we implement the commands in a
2382 # separate script or maybe add err-cleanup to err. Anyways, this
2383 # method is superior because any single char exits.
2384 read -rsN1 -t 5 char || ret=$?
2385 if (( ret == 142 )) || [[ ! $char ]]; then
2386 # debug
2387 #e ret=$ret char=$char
2388 :
2389 else
2390 killall arbtt-capture ||:
2391 return 0
2392 fi
2393 done
2394
2395 }
2396
2397 arb5() {
2398 local i j l sec blanks line
2399 local -a arbtt_lines
2400 if [[ ! -e ~/.arbtt/capture.log ]]; then
2401 sleep 5
2402 fi
2403 blanks=$(( LINES - 34 ))
2404 for (( i=0; i < blanks; i++ )); do
2405 echo
2406 done
2407
2408 {
2409 i=0
2410 j=0
2411 # https://stackoverflow.com/questions/56486272/how-to-concat-multiple-fields-to-same-line-with-jq
2412 arbtt_lines=$(arbtt-dump -l 30 -t json | \
2413 jq -r '.[] | [ ( .inactive / 1000 | floor ) , ( .windows[] | select (.active == true) |.title) ] | @tsv' | tac)
2414 for line in "${arbtt_lines[@]}"; do
2415 read -r sec l <<<"$line"
2416 if (( j >= LINES )); then
2417 break
2418 fi
2419 if (( i % 6 == 0 && i >= 2 )); then
2420 j=$(( j + 1 ))
2421 echo "## $(( i / 6 + 1 )) ##"
2422 fi
2423 if (( sec > 10 )); then
2424 printf "%3d %s\n" $sec "$l" | sed -r "s/^(.{$COLUMNS}).*/\1/"
2425 else
2426 printf " %s\n" "$l" | sed -r "s/^(.{$COLUMNS}).*/\1/"
2427 fi
2428 i=$(( i + 1 ))
2429 j=$(( j + 1 ))
2430 done
2431 while (( j < 34 && j < LINES )); do
2432 echo
2433 j=$(( j + 1 ))
2434 done
2435 } | tac
2436 }
2437
2438 arbttlog() {
2439 # from the log, show only the currently active window, and the number of
2440 # seconds of input inactivity.
2441 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}' ; }
2442
2443 idea() {
2444 /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
2445 }
2446
2447 ilogs-local() {
2448 d=/var/lib/znc/moddata/log/iank/
2449 for n in freenode libera; do
2450 cd $d/$n
2451 hr
2452 for x in "#$1/"*; do
2453 base=${x##*/}
2454 files=()
2455 for f in $@; do
2456 tmp=\#$f/$base
2457 if [[ -e $tmp ]]; then
2458 files+=(\#$f/$base)
2459 fi
2460 done
2461 sed \"s/^./${base%log}/\" ${files[@]}|sort -n
2462 hr
2463 done
2464 done
2465 }
2466 ilogs() {
2467 sl root@iankelling.org ilogs-local "$@"
2468 }
2469
2470
2471 ilog-local() {
2472 local d chan
2473 chan="$1"
2474 d=/var/lib/znc/moddata/log/iank/
2475 for n in freenode libera; do
2476 if [[ ! -d $d$n/"$chan" ]]; then
2477 continue
2478 fi
2479 cd $d$n/"$chan"
2480 hr
2481 for x in *; do
2482 echo $x; sed "s/^./${x%log}/" $x; hr;
2483 done
2484 done
2485 }
2486 ilog() {
2487 local chan
2488 chan="${1:-#fsfsys}"
2489 # use * instead of -r since that does sorted order
2490 sl root@iankelling.org ilog-local "$chan" | less +G
2491 }
2492
2493 o() {
2494 if type gio &> /dev/null ; then
2495 gio open "$@"
2496 elif type gvfs-open &> /dev/null ; then
2497 gvfs-open "$@"
2498 else
2499 xdg-open "$@"
2500 fi
2501 # another alternative is run-mailcap
2502 }
2503 ccomp xdg-open o
2504
2505 # jfilter() {
2506 # grep -Evi -e "^(\S+\s+){4}(sudo|sshd|cron)\[\S*:" \
2507 # -e "^(\S+\s+){4}systemd\[\S*: (starting|started) (btrfsmaintstop|dynamicipupdate|spamd dns bug fix cronjob|rss2email)\.*$"
2508 # }
2509 # jtail() {
2510 # journalctl -n 10000 -f "$@" | jfilter
2511 # }
2512 # jr() { journalctl "$@" | jfilter | less ; }
2513 # jrf() { journalctl -n 200 -f "$@" | jfilter; }
2514
2515
2516 ## old version for model01. i need to get that firmware working again.
2517 # kff() { # keyboardio firmware flash. you must hold down the tilde key
2518 # pushd /a/opt/Model01-Firmware
2519 # # if we didn't want this yes hack, then remove "shell read" from
2520 # # /a/opt/Kaleidoscope/etc/makefiles/sketch.mk
2521 # yes $'\n' | VERBOSE=1 make flash
2522 # popd
2523 # }
2524
2525
2526 kff() {
2527 pushd /a/opt/Kaleidoscope/examples/Devices/Keyboardio/Model100
2528 make flash
2529 popd
2530 }
2531
2532 wgkey() {
2533 local umask_orig name
2534 if (( $# != 1 )); then
2535 e expected 1 arg >&2
2536 return 1
2537 fi
2538 name=$1
2539 umask_orig=$(umask)
2540 umask 0077
2541 wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key
2542 umask $umask_orig
2543 }
2544
2545 declare -A vpn_ips
2546 vpn_ips[kd]=2
2547 # note: 1, 4, 5 are occupied by mail wireguard
2548 vpn_ips[x3]=8
2549 vpn_ips[sy]=12
2550 vpn_ips[x2]=13
2551 vpn_ips[kw]=27
2552 vpn_ips[bo]=28
2553 vpn_ips[frodo]=34
2554 vpn_ips[s23b]=49
2555
2556 vpn-ips-update() {
2557 local host ipsuf f files
2558 for host in ${!vpn_ips[@]}; do
2559 ipsuf=${vpn_ips[$host]}
2560 wghole $host $ipsuf
2561 u /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service <<EOF
2562 [Unit]
2563 Description=OpenVPN tunnel for %I
2564 After=syslog.target network-online.target
2565 Wants=network-online.target
2566 Documentation=man:openvpn(8)
2567 Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
2568 Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO
2569 Requires=iptables.service
2570
2571 [Service]
2572 Type=notify
2573 RuntimeDirectory=openvpn-client
2574 RuntimeDirectoryMode=0710
2575 WorkingDirectory=/etc/openvpn/client
2576 ExecStart=/usr/sbin/openvpn --suppress-timestamps --nobind --config /etc/openvpn/client/%i.conf
2577 # todo, try reenabling this from the default openvpn,
2578 # it was disabled so we could do bind mounts as a command,
2579 # but now systemd handles it
2580 #CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE
2581 LimitNPROC=10
2582 # DeviceAllow=/dev/null rw
2583 # DeviceAllow=/dev/net/tun rw
2584
2585 # we use .1 to make this be on a different network than kd, so that we can
2586 # talk to transmission on kd from remote host, and still use this
2587 # vpn.
2588 ExecStartPre=/usr/bin/flock -w 20 /tmp/newns.flock /a/bin/newns/newns -n 10.174.$ipsuf start %i
2589 ExecStartPre=/sbin/iptables-restore /a/bin/distro-setup/transmission-firewall/netns.rules
2590 # allow wireguard network to connect
2591 ExecStartPre=/usr/sbin/ip r add 10.8.0.0/24 via 10.174.$ipsuf.1 dev veth1-client
2592 ExecStopPost=/usr/bin/flock -w 20 /tmp/newns.flock /a/bin/newns/newns stop %i
2593 PrivateNetwork=true
2594 BindReadOnlyPaths=/etc/tr-resolv:/run/systemd/resolve:norbind /etc/basic-nsswitch:/etc/resolved-nsswitch:norbind
2595
2596 [Install]
2597 WantedBy=multi-user.target
2598 EOF
2599 done
2600
2601 {
2602 for host in ${!vpn_ips[@]}; do
2603 ipsuf=${vpn_ips[$host]}
2604 cat <<EOF
2605 local-data-ptr: "10.2.0.$ipsuf $host.b8.nz"
2606 EOF
2607 done
2608 } | u /b/ds/ptr-data
2609
2610 {
2611 for host in ${!vpn_ips[@]}; do
2612 ipsuf=${vpn_ips[$host]}
2613 cat <<EOF
2614 $host A 10.2.0.$ipsuf
2615 ${host}wg A 10.8.0.$ipsuf
2616 ${host}vp A 10.5.5.$ipsuf
2617 ${host}tr A 10.174.$ipsuf.2
2618 EOF
2619 done
2620 } | cedit vpn-ips-update /p/c/machine_specific/vps/bind-initial/db.b8.nz ||:
2621
2622
2623 echo checking for stray files:
2624
2625 initial_dir=$PWD
2626 cd /a/bin/ds/machine_specific
2627 ngset
2628 files=( */filesystem/etc/systemd/system/openvpn-client-tr@.service )
2629 ngreset
2630 cd $initial_dir
2631 for f in "${files[@]}"; do
2632 host=${f%%/*}
2633 if [[ ! ${vpn_ips[$host]} ]]; then
2634 e /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service
2635 fi
2636 done
2637
2638 cd /p/c/machine_specific
2639 ngset
2640 files=( */filesystem/etc/wireguard/wghole.conf )
2641 ngreset
2642 cd $initial_dir
2643 for f in "${files[@]}"; do
2644 host=${f%%/*}
2645 if [[ ! ${vpn_ips[$host]} ]]; then
2646 e /p/c/machine_specific/$host/filesystem/etc/wireguard/wghole.conf
2647 e cedit -s $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf '<<<""'
2648 fi
2649 done
2650 }
2651
2652 # usage host ipsuf [extrahost]
2653 #
2654 # If the keys already exist and you want new ones, remove them:
2655 # rm /p/c/machine_specific/$host/filesystem/etc/wireguard/hole-{priv,pub}.key
2656 #
2657 # extrahost is a host/cidr that is allowed to go be routed through the
2658 # vpn by this host.
2659 wghole() {
2660 if (( $# < 2 || $# > 3 )); then
2661 e expected 2-3 arg of hostname, ip suffix, and extrahost >&2
2662 return 1
2663 fi
2664 local host ipsuf umask_orig vpn_allowed
2665 host=$1
2666 ipsuf=$2
2667 if [[ $3 ]]; then
2668 extrahost=,$3
2669 fi
2670 for vpn_host in ${!vpn_ips[@]}; do
2671 if [[ $vpn_host == "$host" ]]; then
2672 continue
2673 fi
2674 vpn_allowed+=",10.174.${vpn_ips[$vpn_host]}.2/32"
2675 done
2676 mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
2677 (
2678 cd /p/c/machine_specific/$host/filesystem/etc/wireguard
2679 umask_orig=$(umask)
2680 umask 0077
2681 if [[ ! -s hole-priv.key || ! -s hole-pub.key ]]; then
2682 wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
2683 fi
2684 cat >wghole.conf <<EOF
2685 [Interface]
2686 # contents hole-priv.key
2687 PrivateKey = $(cat hole-priv.key)
2688 ListenPort = 1194
2689 Address = 10.8.0.$ipsuf/24
2690 # https://dev.to/tangramvision/what-they-don-t-tell-you-about-setting-up-a-wireguard-vpn-1h2g
2691 # ||: makes the systemd service not fail due to the failed command
2692 PostUp = ping -w10 -c1 10.8.0.1 ||:
2693
2694 [Peer]
2695 # li. called wgmail on that server
2696 PublicKey = CTFsje45qLAU44AbX71Vo+xFJ6rt7Cu6+vdMGyWjBjU=
2697 AllowedIPs = 10.8.0.0/24$vpn_allowed$extrahost
2698 Endpoint = 72.14.176.105:1194
2699 PersistentKeepalive = 25
2700 EOF
2701 umask $umask_orig
2702 # old approach. systemd seems to work fine and cleaner.
2703 rm -f ../network/interfaces.d/wghole
2704 cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
2705 [Peer]
2706 PublicKey = $(cat hole-pub.key)
2707 AllowedIPs = 10.8.0.$ipsuf/32,10.174.${vpn_ips[$host]}.2/32
2708 EOF
2709 )
2710 }
2711
2712
2713 mns() { # mount namespace
2714 ns=$1
2715 shift
2716 s mkdir -p /root/mount_namespaces
2717 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
2718 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
2719 fi
2720 m sudo mount --make-private /root/mount_namespaces
2721 if [[ ! -e /root/mount_namespaces/$ns ]]; then
2722 m sudo touch /root/mount_namespaces/$ns
2723 fi
2724 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
2725 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
2726 fi
2727 m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
2728 }
2729
2730 mnsd() { # mount namespace + systemd namespace
2731 ns=$1
2732 unit=$2
2733 shift 2
2734
2735 s mkdir -p /root/mount_namespaces
2736 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
2737 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
2738 fi
2739 m sudo mount --make-private /root/mount_namespaces
2740 if [[ ! -e /root/mount_namespaces/$ns ]]; then
2741 m sudo touch /root/mount_namespaces/$ns
2742 fi
2743 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
2744 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
2745 fi
2746
2747 pid=$(servicepid $unit)
2748 tmpf=$(mktemp --tmpdir $unit.XXXXXXXXXX)
2749 export -p >$tmpf
2750 printf "%s " "${@@Q}" >>$tmpf
2751 echo >>$tmpf
2752
2753 m sudo nsenter -t $pid -n --mount=/root/mount_namespaces/$ns sudo -u $USER -i bash -c ". $tmpf & sleep 1; rm $tmpf"
2754 }
2755
2756
2757 mnsr() { # mns run
2758 local ns=$1
2759 shift
2760 mns $ns sudo -u iank -E env "PATH=$PATH" "$@"
2761 }
2762
2763 mnsnonetr() {
2764 ns=$1
2765 lomh
2766 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2767 s ip netns add nonet
2768 fi
2769 mns $ns --net=/var/run/netns/nonet /bin/bash
2770 lomh
2771 }
2772
2773 mnsnonet() {
2774 ns=$1
2775 lomh
2776 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2777 s ip netns add nonet
2778 fi
2779 mns $ns --net=/var/run/netns/nonet sudo -E -u iank /bin/bash
2780 lomh
2781 }
2782
2783
2784 lom() {
2785 # l = the loopback device
2786 local l base
2787 # get sudo pass cached right away
2788 if ! sudo -nv 2>/dev/null; then
2789 sudo -v
2790 fi
2791 if [[ $1 == /* ]]; then
2792 base=${1##*/}
2793 fs_file=$1
2794 if mns $base mountpoint -q /mnt/$base; then
2795 return 0
2796 fi
2797 l=$(losetup -j $fs_file | sed -rn 's/^([^ ]+): .*/\1/p' | head -n1 ||:)
2798 if [[ ! $l ]]; then
2799 l=$(sudo losetup -f)
2800 m sudo losetup $l $fs_file
2801 fi
2802 if ! sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2803 if ! m sudo cryptsetup luksOpen $l $base; then
2804 m sudo losetup -d $l
2805 return 1
2806 fi
2807 fi
2808 m sudo mkdir -p /mnt/$base
2809 m mns $base mount /dev/mapper/$base /mnt/$base
2810 m mns $base chown $USER:$USER /mnt/$base
2811 lomh
2812 else
2813 base=$1
2814 if mns $base mountpoint /mnt/$base &>/dev/null; then
2815 m mns $base umount /mnt/$base
2816 fi
2817 if sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2818 if ! m sudo cryptsetup luksClose /dev/mapper/$base; then
2819 echo lom: failed cryptsetup luksClose /dev/mapper/$base
2820 return 1
2821 fi
2822 fi
2823 l=$(losetup -l --noheadings | awk '$6 ~ /\/'$base'$/ {print $1}')
2824 if [[ $l ]]; then
2825 m sudo losetup -d $l
2826 else
2827 echo lom: warning: no loopback device found
2828 fi
2829 fi
2830 }
2831
2832 # mu personality. for original, just run mp. for 2, run mp 2.
2833 # this is partly duplicated in mail-setup
2834 mp() {
2835 local dead=false
2836 for s in {1..5}; do
2837 if ! killall mu; then
2838 dead=true
2839 break
2840 fi
2841 sleep 1
2842 done
2843 if ! $dead; then
2844 echo error: mu not dead
2845 m psg mu
2846 return 1
2847 fi
2848 suf=$1
2849 set -- /m/mucache ~/.cache/mu /m/.mu ~/.config/mu
2850 while (($#)); do
2851 target=$1$suf
2852 f=$2
2853 shift 2
2854 if [[ -e $f && ! -L $f ]]; then
2855 m rm -rf $f
2856 fi
2857 m ln -sf -T $target $f
2858 done
2859 }
2860
2861 # maildir enable
2862 mdenable() {
2863 local md dst ln_path src two
2864
2865 two=false
2866 case $1 in
2867 -2) two=true shift ;;
2868 esac
2869
2870 for md; do
2871 src=
2872 if $two; then
2873 dst=/m/4e2/$md
2874 else
2875 dst=/m/4e/$md
2876 fi
2877
2878 ln_path=/m/md/$md
2879 for d in /m/md/$md /m/4e2/$md; do
2880 if [[ -d $d && ! -L $d ]]; then
2881 src=$d
2882 break
2883 fi
2884 done
2885 if [[ ! $src ]]; then
2886 echo "error: could not find $md" >&2
2887 return 1
2888 fi
2889 m mv -T $src $dst
2890 m ln -sf -T $dst $ln_path
2891 done
2892 }
2893 md2enable() {
2894 mdenable -2 "$@"
2895 }
2896 mddisable() {
2897 local md=$1
2898 dst=/m/md/$md
2899
2900 ### begin copied from mdenable, but different d ###
2901 for d in /m/4e/$md /m/4e2/$md; do
2902 if [[ -d $d && ! -L $d ]]; then
2903 src=$d
2904 break
2905 fi
2906 done
2907 if [[ ! $src ]]; then
2908 echo "error: could not find $md" >&2
2909 return 1
2910 fi
2911 ### end copy from mdenable ###
2912
2913 if [[ -L $dst ]]; then m rm $dst; fi
2914 m mv -T $src $dst
2915 }
2916
2917
2918 mdt() {
2919 markdown "$1" >/tmp/mdtest.html
2920 firefox /tmp/mdtest.html
2921 }
2922
2923 mo() { xset dpms force off; } # monitor off
2924
2925 mpvgpu() {
2926 # seems to be the best gpu decoding on my nvidia 670.
2927 # vlc gets similar or better framerate, but is much darker output on my test movie at least.
2928
2929
2930 case $HOSTNAME in
2931 kd)
2932 echo 0f | sudo tee -a /sys/kernel/debug/dri/0/pstate
2933 ;;
2934 esac
2935 # going back to the default slow clock, and slower fan:
2936 # echo 07 | sudo tee -a /sys/kernel/debug/dri/0/pstate
2937 if [[ $DISPLAY ]]; then
2938 mpv --vo=vdpau --hwdec=auto "$@"
2939 else
2940 # waylandvk seems to work the same
2941 mpv --gpu-context=wayland --hwdec=auto
2942 fi
2943 }
2944
2945 mpvd() {
2946 mpv --profile=d "$@";
2947 }
2948 mpva() {
2949 mpv --profile=a "$@";
2950 }
2951 # mpv all media files in . or $1
2952 mpvm() {
2953 local -a extensions arg
2954 # get page source of https://en.wikipedia.org/w/index.php?title=Video_file_format&action=edit
2955 # into /a/x.log, then
2956 # grep '^| *\.' /a/x.log | sed 's/| *//;s/,//g'
2957
2958 # note: to join them together for a regex, do:
2959 # old=; for e in ${extensions[@]/./}; do if [[ ! $old ]]; then old=$e; continue; fi; echo -n "$old|"; old=$e; done; echo $e
2960 extensions=(
2961 .webm
2962 .mkv
2963 .flv
2964 .flv
2965 .vob
2966 .ogv .ogg
2967 .drc
2968 .gif
2969 .gifv
2970 .mng
2971 .avi
2972 .MTS .M2TS .TS
2973 .mov .qt
2974 .wmv
2975 .yuv
2976 .rm
2977 .rmvb
2978 .viv
2979 .asf
2980 .amv
2981 .mp4 .m4p .m4v
2982 .mpg .mp2 .mpeg .mpe .mpv
2983 .mpg .mpeg .m2v
2984 .m4v
2985 .svi
2986 .3gp
2987 .3g2
2988 .mxf
2989 .roq
2990 .nsv
2991 )
2992 arg=("(" -iname "*${extensions[0]}")
2993 for (( i=1 ; i < ${#extensions[@]}; i++ )); do
2994 arg+=(-o -iname "*${extensions[i]}")
2995 done
2996 arg+=(")")
2997 dir=${1:-.}
2998 # debug:
2999 #find $dir "${arg[@]}" -size +200k
3000 find $dir "${arg[@]}" -size +200k -exec mpv --profile=d '{}' +
3001 }
3002 mpvs() {
3003 mpv --profile=s "$@";
3004 }
3005
3006 myirc() {
3007 if [[ ! $1 ]]; then
3008 set -- fsfsys
3009 fi
3010 local -a d
3011 d=( /var/lib/znc/moddata/log/iank/{freenode,libera} )
3012 # use * instead of -r since that does sorted order
3013 ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\<iank.*' *; done" | cut --complement -c12-16
3014 }
3015
3016
3017 allmyirc() {
3018 local d
3019 d=/var/lib/znc/moddata/log/iank/freenode
3020 ssh root@iankelling.org "cd $d; find . -mtime -60 -type f -exec grep '\<iank.*' {} +" | sed -r 's,^..([^/]*)/(.{11})(.{5})(.{8}).,\2\4 \1,' | sort
3021 }
3022
3023 # The way pidgin logs with xmpp (maybe related to running cheogram too)
3024 # is that there are sometimes duplicates, and sometimes the a log file
3025 # is for a specific day yet logs messages for subsequent days, and the
3026 # only way to realize that is to notice that the timestamps rolled over
3027 # into a new day, you can't see it in isolation. So, basically, pidgin
3028 # logs are really annoying to read a grep of my messages to find the
3029 # date and time I said when I started and stopped working, so I'm trying
3030 # out a new client: profanity.
3031 mypidgin() {
3032 c /p/c/.purple/logs/jabber/iank@fsf.org/office@conference.fsf.org.chat
3033 for x in *.html; do html2text -o ${x%.html}.txt $x; done;
3034 # shellcheck disable=SC2016 # false positive on ${
3035 grep -A1 ') iank:' ./*.txt \
3036 | sed -r 's/^(.{10})[^ ]*\.txt:\(?([^ ]*)[[:space:]](..). iank:/\1_\2_\3/
3037 s/^[^ ]*\.txt-//
3038 /^--$/d
3039 s/^[^ ]*\.txt:\((.{2}).(.{2}).(.{4}) (.{8}) (.{2})\)?/\3-\1-\2_\4_\5/' \
3040 | sed -n 'x;1d;0~2{G;s/\n/ /;p};${x;p}'
3041 }
3042
3043 # my profanity
3044 #
3045 myprof() {
3046 pushd /home/iank/.local/share/profanity/chatlogs/iank_at_fsf.org/rooms/office_at_conference.fsf.org
3047 logs=(*)
3048 logcount=${#logs[@]}
3049 if (( logcount > 15 )); then
3050 i=$(( logcount - 15 ))
3051 else
3052 i=0
3053 fi
3054 # usually do this on monday, sometimes later
3055 if [[ $(date +%A) == Monday ]]; then
3056 min_date=$(date -d 'monday 2 weeks ago' +%s)
3057 else
3058 min_date=$(date -d 'monday 3 weeks ago' +%s)
3059 fi
3060 for (( ; i < logcount; i++ )); do
3061 log=${logs[$i]}
3062 d=$(date -d "$(head -n1 $log|awk '{print $1}')" +%s)
3063 if (( d < min_date )); then
3064 continue
3065 fi
3066 if awk '$3 == "iank:"' $log | sed -r 's/^(.{10}).(.{8})[^ ]+(.*)/\1_\2\3/' | grep .; then
3067 hr
3068 fi
3069 done
3070 popd
3071 }
3072
3073
3074 # Tail all recent prof logs. Copying from profanity has unwanted line breaks
3075 # especially for links.
3076 profr() {
3077 case $HOSTNAME in
3078 kd)
3079 profr-local
3080 ;;
3081 *)
3082 ssh b8.nz profr-local
3083 ;;
3084 esac
3085 }
3086
3087 profr-local() {
3088 local d0 d1
3089 local -a files
3090 d0="$(date +%Y_%m_%d).log"
3091 d1="$(date -d '1 day ago' +%Y_%m_%d).log"
3092 ngset
3093 files=(/d/p/profanity/chatlogs/iank_at_fsf.org/{*,rooms/*}/{$d0,$d1})
3094 ngreset
3095 if (( ${#files[@]} > 0 )); then
3096 cat "${files[@]}" | sort | tail -n 40
3097 fi
3098 }
3099
3100
3101 # Tail pms in the last day, for the case where we restart profanity and
3102 # didn't check for pms beforehand. Assume the most recent logs are on kd.
3103 # If that isn't the case, use prof-recent-local
3104 prof-recent() {
3105 case $HOSTNAME in
3106 kd)
3107 prof-recent-local
3108 ;;
3109 *)
3110 ssh b8.nz prof-recent-local
3111 ;;
3112 esac
3113 }
3114 prof-recent-local() {
3115 local d dates date files f
3116 # consider making the day count passed by parameter. note: this works: $(date -d '2 day ago' +%Y_%m_%d)
3117 dates=("$(date +%Y_%m_%d)" "$(date -d '1 day ago' +%Y_%m_%d)" )
3118 for d in /d/p/profanity/chatlogs/iank_at_fsf.org/!(rooms); do
3119 files=()
3120 for date in ${dates[@]}; do
3121 f=$d/$date.log
3122 if [[ -e $f ]]; then
3123 files+=($f)
3124 fi
3125 done
3126 if (( ${#files[@]} >= 1 )); then
3127 cat ${files[@]} | tail
3128 hr
3129 fi
3130 done
3131 }
3132
3133 prof-sort() {
3134 case $HOSTNAME in
3135 kd)
3136 prof-recent-sort
3137 ;;
3138 *)
3139 ssh b8.nz prof-recent-sort
3140 ;;
3141 esac
3142 }
3143
3144 prof-recent-sort() {
3145 local d dates date files f
3146 # consider making the day count passed by parameter. note: this works: $(date -d '2 day ago' +%Y_%m_%d)
3147 dates=("$(date +%Y_%m_%d)" "$(date -d '1 day ago' +%Y_%m_%d)" )
3148 files=()
3149 for d in /d/p/profanity/chatlogs/iank_at_fsf.org/!(rooms); do
3150 for date in ${dates[@]}; do
3151 f=$d/$date.log
3152 if [[ -e $f ]]; then
3153 files+=($f)
3154 fi
3155 done
3156 done
3157 for f in "${files[@]}"; do
3158 sed "s/\$/ $f/" $f
3159 done | sort
3160 }
3161
3162
3163 # usage: debvm DEBIAN_VERSION RAM_MB
3164 debvm() {
3165 local ver ram fname src
3166 ver=$1
3167 ram=${2:-2024}
3168 # * is because it might have -backports in the name. we only expect 1 expansion
3169 fnames=( debian-$ver-*nocloud-"$(dpkg --print-architecture)".qcow2 )
3170 if (( ${#fnames[@]} >= 2 )); then
3171 echo "error: iank: unexpected multiple files"
3172 return 1
3173 fi
3174 fname="${fnames[0]}"
3175 src=/a/opt/roms/$fname
3176 if [[ ! -f $src ]]; then
3177 echo debvm: not found $src, download from eg: https://cloud.debian.org/images/cloud/buster/latest/
3178 return 1
3179 fi
3180 cp -a $src /t
3181 # note, in fai-revm we do this: not sure why, maybe because of br device
3182 # --graphics spice,listen=0.0.0.0
3183 m s virt-install --osinfo debian11 --rng /dev/urandom -n deb${ver}tmp --import -r $ram --vcpus 2 --disk /t/$fname --graphics spice
3184 # note: to ssh into this machine will require host key generation: ssh-keygen -A
3185
3186 # random: for cvs2git on gnu www, use debian 10. I could use trisquel,
3187 # but happen to want to try out the debian cloud images. the upstream
3188 # requires python2 and hasn't really changed since the version in d10.
3189 #
3190 # apt install cvs2git cvs
3191 # # 7G was not enough
3192 # mount -o mode=1777,nosuid,nodev,size=34G -t tmpfs tmpfs /tmp
3193 # cvs2git --encoding utf_8 --fallback-encoding ascii --dumpfile=dump www-rsync/www |& tee /tmp/l
3194 ## www-rsync is an rsynced copy of the cvsfrom savannah
3195 }
3196
3197 mygajim() {
3198 local time time_sec time_pretty days
3199 days=${1:-16}
3200 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank' and jid_id = 17;" | while read -r time l; do
3201 case $time in
3202 16*) : ;;
3203 *) continue ;;
3204 esac
3205 if ! time_pretty=$(date +%F.%R -d @$time); then
3206 echo bad time: $time
3207 return 1
3208 fi
3209 echo $time_pretty "$l"
3210 time_sec=${time%%.*}
3211 # only look at the last 18 days. generally just use this for timesheet.
3212 if (( time_sec < EPOCHSECONDS - 60 * 60 * 24 * days )); then break; fi
3213 done
3214 }
3215
3216 allmygajim() {
3217 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank'" | less
3218 }
3219
3220 gajlogs() {
3221 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs" | less
3222 }
3223
3224
3225 net-dev-info() {
3226 e "lspci -nnk|gr -iA2 net"
3227 lspci -nnk|gr -iA2 net
3228 hr
3229 e "s lshw -C network"
3230 hr
3231 sudo lshw -C network
3232 }
3233
3234 nk() {
3235 ser stop NetworkManager
3236 ser disable NetworkManager
3237 ser stop NetworkManager-wait-online.service
3238 ser disable NetworkManager-wait-online.service
3239 ser stop dnsmasq
3240 sudo resolvconf -d NetworkManager
3241 # ser start dnsmasq
3242 sudo ifup br0
3243 }
3244 ngo() {
3245 sudo ifdown br0
3246 ser start NetworkManager
3247 sleep 4
3248 sudo nmtui-connect
3249 }
3250
3251 otp() {
3252 oathtool --totp -b "$*" | xclip -selection clipboard
3253 }
3254 # run cmd and copy output
3255 j() {
3256 "$@" |& pee "xclip -r -selection clipboard" cat
3257 }
3258
3259 # xorg copy. copy text piped into command
3260 xc() {
3261 xclip -r -selection clipboard
3262 }
3263 # echo copy
3264 ec() {
3265 pee "xclip -r -selection clipboard" cat
3266 }
3267
3268 pakaraoke() {
3269 # from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals
3270 pactl load-module module-ladspa-sink sink_name=Karaoke master=alsa_output.usb-Audioengine_Audioengine_D1-00.analog-stereo plugin=karaoke_1409 label=karaoke control=-30
3271 }
3272
3273 pfind() { #find *$1* in $PATH
3274 [[ $# != 1 ]] && { echo requires 1 argument; return 1; }
3275 local pathArray
3276 IFS=: pathArray=($PATH); unset IFS
3277 find "${pathArray[@]}" -iname "*$1*"
3278 }
3279
3280 pick-trash() {
3281 # trash-restore lists everything that has been trashed at or below CWD
3282 # This picks out files just in CWD, not subdirectories,
3283 # which also match grep $1, usually use $1 for a time string
3284 # which you get from running restore-trash once first
3285 local name x ask
3286 local nth=1
3287 # last condition is to not ask again for ones we skipped
3288 while name="$( echo | restore-trash | gr "$PWD/[^/]\+$" | gr "$1" )" \
3289 && [[ $name ]] && (( $(wc -l <<<"$name") >= nth )); do
3290 name="$(echo "$name" | head -n $nth | tail -n 1 )"
3291 read -r -p "$name [Y/n] " ask
3292 if [[ ! $ask || $ask == [Yy] ]]; then
3293 x=$( echo "$name" | gr -o "^\s*[0-9]*" )
3294 echo $x | restore-trash > /dev/null
3295 elif [[ $ask == [Nn] ]]; then
3296 nth=$((nth+1))
3297 else
3298 return
3299 fi
3300 done
3301 }
3302
3303
3304 pub() {
3305 rld /a/h/_site/ li:/var/www/iankelling.org/html
3306 }
3307
3308
3309 pumpa() {
3310 # fixes the menu bar in xmonad. this won\'t be needed when xmonad
3311 # packages catches up on some changes in future (this is written in
3312 # 4/2017)
3313 #
3314 # geekosaur: so youll want to upgrade to xmonad 0.13 or else use a
3315 # locally modified XMonad.Hooks.ManageDocks that doesnt set the
3316 # work area; turns out it\'s impossible to set correctly if you are
3317 # not a fully EWMH compliant desktop environment
3318 #
3319 # geekosaur: chrome shows one failure mode, qt/kde another, other
3320 # gtk apps a third, ... I came up with a setting that works for me
3321 # locally but apparently doesnt work for others, so we joined the
3322 # other tiling window managers in giving up on setting it at all
3323 #
3324 xprop -root -remove _NET_WORKAREA
3325 command pumpa & r
3326 }
3327
3328 # reviewboard, used at my old job
3329 #rbpipe() { rbt post -o --diff-filename=- "$@"; }
3330 #rbp() { rbt post -o "$@"; }
3331
3332 rebr() {
3333 sudo ifdown br0
3334 sudo ifup br0
3335 }
3336
3337
3338 r2e() { command r2e -d /p/c/rss2email.json -c /p/c/rss2email.cfg "$@"; }
3339 # only run on MAIL_HOST. simpler to keep this on one system.
3340 r2eadd() { # usage: name url
3341 # initial setup of rss2email:
3342 # r2e new r2e@iankelling.org
3343 # that initializes files, and sets default email.
3344 # symlink to the config doesnt work, so I copied it to /p/c
3345 # and then use cli option to specify explicit path.
3346 # Only option changed from default config is to set
3347 # force-from = True
3348 #
3349 # or else for a few feeds, the from address is set by the feed, and
3350 # if I fail delivery, then I send a bounce message to that from
3351 # address, which makes me be a spammer.
3352
3353 r2e add $1 "$2" $1@r2e.iankelling.org
3354 # get up to date and dont send old entries now:
3355 r2e run --no-send $1
3356 }
3357
3358 rspicy() { # usage: HOST DOMAIN
3359 # connect to spice vm remote host. use vspicy for local host
3360 local port
3361 # shellcheck disable=SC2087
3362 port=$(ssh $1<<EOF
3363 sudo virsh dumpxml $2|grep "<graphics.*type='spice'" | \
3364 sed -rn "s/.*port='([0-9]+).*/\1/p"
3365 EOF
3366 )
3367 if [[ $port ]]; then
3368 spicy -h $1 -p $port
3369 else
3370 echo "error: no port found. check that the domain is running."
3371 fi
3372 }
3373
3374
3375 scssl() {
3376 # s gem install scss-lint
3377 pushd /a/opt/thoughtbot-guides
3378 git pull --stat
3379 popd
3380 scss-lint -c /a/opt/thoughtbot-guides/style/sass/.scss-lint.yml "$@"
3381 }
3382
3383 skbrc() {
3384 sk -e 2120,245 /b/ds/brc /b/ds/brc2
3385 }
3386
3387 skaraoke() {
3388 local tmp out
3389 out=${2:-${1%.*}.sh}
3390 tmp=$(mktemp -d)
3391 script -t -c "mpv --no-config --no-resume-playback --no-terminal --no-audio-display '$1'" $tmp/typescript 2>$tmp/timing
3392 # todo, the current sleep seems pretty good, but it
3393 # would be nice to have an empirical measurement, or
3394 # some better wait to sync up.
3395 #
3396 # note: --loop-file=no prevents it from hanging if you have that
3397 # set to inf the mpv config.
3398 # --loop=no prevents it from exit code 3 due to stdin if you
3399 # had it set to inf in mpv config.
3400 #
3401 # args go to mpv, for example --volume=80, 50%
3402 cat >$out <<EOFOUTER
3403 #!/bin/bash
3404 trap "trap - TERM && kill 0" INT TERM ERR; set -e
3405 ( sleep .2; scriptreplay <( cat <<'EOF'
3406 $(cat $tmp/timing)
3407 EOF
3408 ) <( cat <<'EOF'
3409 $(cat $tmp/typescript)
3410 EOF
3411 ))&
3412 base64 -d - <<'EOF'| mpv --loop=no --loop-file=no --no-terminal --no-audio-display "\$@" -
3413 $(base64 "$1")
3414 EOF
3415 kill 0
3416 EOFOUTER
3417 rm -r $tmp
3418 chmod +x $out
3419 }
3420
3421 smeld() { # ssh meld usage host1 host2 file
3422 meld <(ssh $1 cat $3) <(ssh $2 cat $3)
3423 }
3424
3425 spd() {
3426 PATH=/usr/local/spdhackfix:$PATH command spd "$@"
3427 }
3428
3429 spamf() { # spamtest on FILE
3430 if (( $# != 1 )); then
3431 e spamtest error: expected 1 arg, filename >&2
3432 return 1
3433 fi
3434 sdncmdroot spamassassin sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
3435 }
3436
3437
3438 # mail related
3439 testmail() {
3440 declare -gi _seq; _seq+=1
3441 echo "test body" | m mail -s "test mail from $HOSTNAME, $_seq" "${@:-root@localhost}"
3442 # for testing to send from an external address, you can do for example
3443 # -fian@iank.bid -aFrom:ian@iank.bid web-6fnbs@mail-tester.com
3444 # note in exim, you can retry a deferred message
3445 # s exim -M MSG_ID
3446 # MSG_ID is in /var/log/exim4/mainlog, looks like 1ccdnD-0001nh-EN
3447 }
3448
3449 # to test sieve, use below command. for fsf mail, see offlineimap-sync script
3450 # make modifications, then copy to live file, use -eW to actually modify mailbox
3451 #
3452 # Another option is to use sieve-test SCRIPT MAIL_FILE. note,
3453 # sieve-test doesnt know about envelopes, Im not sure if sieve-filter does.
3454
3455 # sieve with output filter. arg is mailbox, like INBOX.
3456 # This depends on dovecot conf, notably mail_location in /etc/dovecot/conf.d/10-mail.conf
3457
3458 # always run this first, edit the test files, then run the following
3459 testsieve() {
3460 sieve-filter ~/sieve/maintest.sieve ${1:-INBOX} delete 2> >(head; tail) >/tmp/testsieve.log && sed -rn '/^Performed actions:/,/^[^ ]/{/^ /p}' /tmp/testsieve.log | sort | uniq -c
3461 }
3462 runsieve() {
3463 c ~/sieve; cp personal{test,}.sieve; cp lists{test,}.sieve; cp personalend{test,}.sieve
3464 sieve-filter -eWv ~/sieve/maintest.sieve ${1:-INBOX} delete &> /tmp/testsieve.log
3465 sed -r '/^info: filtering:/{h;d};/^info: msgid=$/N;/^info: msgid=.*left message in mailbox [^ ]+$/d;/^info: msgid=/{H;g};/^info: message kept in source mailbox.$/d' /tmp/testsieve.log
3466 }
3467
3468 # usage:
3469 # alertme SUBJECT
3470 # printf "subject\nbody\n" | alertme
3471 alertme() {
3472 if [[ -t 0 ]]; then
3473 exim -t <<EOF
3474 From: alertme@b8.nz
3475 To: alerts@iankelling.org
3476 Subject: $*
3477 EOF
3478 else
3479 read -r sub
3480 { cat <<EOF
3481 From: alertme@b8.nz
3482 To: alerts@iankelling.org
3483 Subject: $sub
3484
3485 EOF
3486 cat
3487 } | exim -t
3488 fi
3489 }
3490 daylertme() {
3491 if [[ -t 0 ]]; then
3492 exim -t <<EOF
3493 From: alertme@b8.nz
3494 To: daylert@iankelling.org
3495 Subject: $*
3496 EOF
3497 else
3498 read -r sub
3499 { cat <<EOF
3500 From: alertme@b8.nz
3501 To: daylert@iankelling.org
3502 Subject: $sub
3503
3504 EOF
3505 cat
3506 } | exim -t
3507 fi
3508 }
3509
3510 # alert when a page goes live.
3511 alert200() {
3512 local quiet url tmpdir
3513 quiet=false
3514 case $1 in
3515 # dont send a diff of the html. some html is not very readable
3516 -q) quiet=true
3517 shift
3518 ;;
3519 esac
3520 url="$1"
3521 tmpdir="$(mktemp -d)"
3522 cd $tmpdir
3523 while true; do
3524 if wget -q "$url"; then
3525 if $quiet; then
3526 echo | daylert 200
3527 else
3528 alertme $tmpdir
3529 fi
3530 fi
3531 sleep $(( 120 + RANDOM % 300 ))
3532 done
3533 }
3534
3535 # alert on changes to a webpage (just the base page that curl gets)
3536 # usage: weblert URL [SUBJECT...]
3537 weblert() {
3538 local u old new quiet
3539 quiet=false
3540 case $1 in
3541 # dont send a diff of the html. some html is not very readable
3542 -q) quiet=true
3543 shift
3544 ;;
3545 esac
3546 u="$1"
3547 shift
3548 subject="${*:-weblert}"
3549 old=$(curl -s "$u") ||:
3550 while true; do
3551 new=$(curl -s "$u") ||:
3552 if [[ $old && $new ]]; then
3553 if [[ $new != "$old" ]]; then
3554 if $quiet; then
3555 echo | daylertme "$subject"
3556 else
3557 diff <(printf "%s\n" "$old") <(printf "%s\n" "$new") | daylertme "$subject" ||:
3558 fi
3559 fi
3560 old="$new"
3561 fi
3562 sleep $(( 60 + RANDOM % 120 ))
3563 done
3564 }
3565
3566 torshell() {
3567 # per man torsocks
3568 # shellcheck disable=SC1090 # expected
3569 source "$(type -p torsocks)" on
3570 }
3571
3572 eless2() {
3573 less /var/log/exim4/mymain
3574 }
3575
3576
3577 # mail related
3578 testexim() {
3579 # testmail above calls sendmail, which is a link to exim/postfix.
3580 # its docs dont say a way of adding an argument
3581 # to sendmail to turn on debug output. We could make a wrapper, but
3582 # that is a pain. Exim debug args are documented here:
3583 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
3584 #
3585 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
3586 # note, for exim daemon, you can turn on debug options by
3587 # adding -d, etc to COMMONOPTIONS in
3588 # /etc/default/exim4
3589 #
3590 # to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
3591 # exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
3592 #
3593 # -t = get recipient from header
3594 exim -d -t <<EOF
3595 From: ian@iankelling.org
3596 To: submit@b.b8.nz
3597 Subject: testbug1
3598
3599 Package: test
3600 Version:1
3601
3602 This is a test message.
3603 EOF
3604 }
3605
3606 # test bounce exim
3607 testbexim() {
3608 to=$1
3609 exim -d -f '<>' $to <<EOF
3610 From: Mail Delivery System <Mailer-Daemon@gnu.org>
3611 To: $to
3612 Subject: Mail delivery failed: returning message to sender
3613
3614 This message was created automatically by mail delivery software.
3615 EOF
3616
3617 }
3618
3619
3620 # toggle keyboard
3621 tk() {
3622 # based on
3623 # https://askubuntu.com/questions/160945/is-there-a-way-to-disable-a-laptops-internal-keyboard
3624 id=$(xinput --list --id-only 'AT Translated Set 2 keyboard')
3625 if xinput list | grep -F '∼ AT Translated Set 2 keyboard' &>/dev/null; then
3626 echo enabling keyboard
3627 # find the first slave keyboard number, they are all the same in my output.
3628 # if they werent, worst case we would need to save the slave number somewhere
3629 # when it got disabled.
3630 slave=$(xinput list | sed -n 's/.*slave \+keyboard (\([0-9]*\)).*/\1/p' | head -n1)
3631 xinput reattach $id $slave
3632 else
3633 xinput float $id
3634 fi
3635 }
3636
3637 tm() {
3638 # timer in minutes
3639 # --no-config
3640 (sleep "$(calc "$* * 60")" && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
3641 }
3642
3643 ## usage: to connect to my main transmission daemon from a different host, run this
3644 trans-remote-route() {
3645 :
3646 }
3647 trg() { transmission-remote-gtk & r; }
3648 # TODO: this wont work transmission.lan doesnt exist
3649 trc() {
3650 # example, set global upload limit to 100 kilobytes:
3651 # trc -u 100
3652 TR_AUTH=":$(jq -r .profiles[0].password ~/.config/transmission-remote-gtk/config.json)" transmission-remote transmission.lan -ne "$@"
3653 }
3654
3655 trysleep() {
3656 retries="$1"
3657 sleepsecs="$2"
3658 shift 2
3659 for (( i=0; i < retries - 1; i++ )); do
3660 if "$@"; then
3661 return 0
3662 fi
3663 sleep $sleepsecs
3664 done
3665 "$@"
3666 }
3667
3668
3669 tu() {
3670 local s
3671 if [[ -e $1 && ! -w $1 || ! -w $(dirname "$1") ]]; then
3672 s=s;
3673 fi
3674 # full path for using in some initial setup steps
3675 $s /a/exe/teeu "$@"
3676 }
3677
3678 # execute exim in its namespace. Useful args like -Mrm
3679 enn() {
3680 local ecmd pid
3681
3682 ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf"
3683 if ip a show veth1-mail &>/dev/null; then
3684 s $ecmd "$@"
3685 else
3686 sdncmdroot exim4 $ecmd "$@"
3687 fi
3688 }
3689
3690 # get pid of systemd service
3691 servicepid() {
3692 local pid unit dir
3693 unit="$1"
3694 pid=$(systemctl show --property MainPID --value "$unit")
3695 case $pid in
3696 [1-9]*) : ;;
3697 *)
3698
3699 dir=/sys/fs/cgroup/system.slice
3700 if [[ ! -d $dir ]]; then
3701 # t10 and older directory.
3702 dir=/sys/fs/cgroup/systemd/system.slice
3703 fi
3704
3705 # 0 or empty. This file includes the MainPid, so I expect we
3706 # could just get this in the first place, but i don't know if that
3707 # is always the case.
3708 pid=$(head -n1 $dir/${unit%.service}.service/cgroup.procs)
3709 ;;
3710 esac
3711 if [[ $pid ]]; then
3712 printf "%s\n" "$pid"
3713 else
3714 return 1
3715 fi
3716 }
3717
3718 sdnbash() { # systemd namespace bash
3719 local unit pid
3720 if (( $# != 1 )); then
3721 echo $0: error wrong number of args >&2
3722 return 1
3723 fi
3724 unit=$1
3725 pid=$(servicepid $unit)
3726 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash
3727 }
3728
3729 sdnbashroot() { # systemd namespace bash as root
3730 local unit pid
3731 if (( $# != 1 )); then
3732 echo $0: error wrong number of args >&2
3733 return 1
3734 fi
3735 unit=$1
3736 pid=$(servicepid $unit)
3737 m sudo nsenter -t $pid -n -m bash
3738 }
3739
3740
3741 # systemd namespace cmd
3742 # usage: UNIT CMD...
3743 sdncmd() {
3744 local unit pid tmpf
3745 if (( $# <= 1 )); then
3746 echo $0: error wrong number of args >&2
3747 return 1
3748 fi
3749 unit=$1
3750 shift
3751 pid=$(servicepid $unit)
3752 tmpf=$(mktemp --tmpdir $unit.XXXXXXXXXX)
3753 export -p >$tmpf
3754 printf "%s " "${@@Q}" >>$tmpf
3755 echo >>$tmpf
3756 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash -c ". $tmpf & rm $tmpf"
3757 }
3758
3759 sdncmdroot() { # systemd namespace root command
3760 local unit pid
3761 if (( $# < 2 )); then
3762 echo $0: error wrong number of args >&2
3763 return 1
3764 fi
3765 unit=$1
3766 shift
3767 pid=$(servicepid $unit)
3768 m sudo nsenter -t $pid -n -m "$@"
3769 }
3770
3771
3772 mailnnbash() {
3773 sdnbash mailnn
3774 }
3775
3776 # we use wireguard now, use mailnnbash.
3777 # mailvpnbash() {
3778 # m sudo nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*mail.conf") -n -m sudo -u $USER -i bash
3779 # }
3780
3781 eximbash() {
3782 sdnbashroot exim4
3783 }
3784 spamnn() {
3785 local spamdpid
3786 spamdpid=$(systemctl show --property MainPID --value spamassassin)
3787 m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@"
3788 }
3789 unboundbash() {
3790 sdnbashroot unbound
3791 }
3792
3793 nmtc() {
3794 s nmtui-connect "$@"
3795 }
3796
3797 mailnncheck() {
3798 local unit pid ns mailnn
3799 # mailvpn would belong on the list if using openvpn
3800 for unit in mailnn unbound dovecot spamassassin exim4 radicale; do
3801 pid=$(servicepid $unit)
3802 echo debug: unit=$unit pid=$pid
3803 if [[ ! $pid ]]; then
3804 echo failed to find pid for unit=$unit
3805 continue
3806 fi
3807 if ! ns=$(s readlink /proc/$pid/ns/net); then
3808 echo failed to find ns for unit=$unit pid=$pid
3809 continue
3810 fi
3811 if [[ $mailnn ]]; then
3812 if [[ $ns != "$mailnn" ]]; then
3813 echo "$unit ns $ns != $mailnn"
3814 fi
3815 else
3816 mailnn=$ns
3817 fi
3818 done
3819
3820 }
3821
3822
3823 vpncmd() {
3824 sdncmd openvpn-client-tr@client.service "$@"
3825 }
3826 vpni() {
3827 sdncmd openvpn-client-tr@client.service bash
3828 }
3829 vpnbash() {
3830 sdncmdroot openvpn-client-tr@client.service bash
3831 }
3832
3833
3834 vpn() {
3835 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3836 local vpn_service=openvpn-client
3837 else
3838 local vpn_service=openvpn
3839 fi
3840
3841 [[ $1 ]] || { echo need arg; return 1; }
3842 journalctl --unit=$vpn_service@$1 -f -n0 &
3843 # sometimes the journal doesnt open until after the vpn output
3844 # has happened. hoping this fixes that.
3845 sleep 1
3846 sudo systemctl start $vpn_service@$1
3847 # sometimes the ask-password agent does not work and needs a delay.
3848 sleep .5
3849 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779240
3850 # noticed around 8-2017 after update from around stretch release
3851 # on debian testing, even though the bug is much older.
3852 sudo systemd-tty-ask-password-agent
3853 }
3854
3855 fixu() {
3856 local stats
3857 ls -lad /run/user/1000
3858 stats=$(stat -c%a-%g-%u /run/user/1000)
3859 if [[ $stats != 700-1000-1000 ]]; then
3860 m s chmod 700 /run/user/1000; m s chown iank.iank /run/user/1000
3861 fi
3862 }
3863
3864 # unmute
3865 um() {
3866 local sink card
3867 sink=$(pactl get-default-sink)
3868 if [[ $sink == auto_null ]]; then
3869 # guessing there is just one with an off profile. otherwise we will
3870 # need some other solution, like storing the card identifier that we
3871 # muted with nap.
3872 card=$(pacmd list-cards | sed -n '/^[[:space:]]*index:/{s/^[[:space:]]*index://;h};/^[[:space:]]*active profile: <off>$/{g;p;q}')
3873 m pacmd set-card-profile "$card" output:analog-stereo
3874 fi
3875
3876 m pactl set-sink-mute @DEFAULT_SINK@ false
3877 rm -f /tmp/ianknap
3878 }
3879
3880 nap() {
3881 local sink card
3882 sink=$(pactl get-default-sink)
3883 card="${sink%.*}"
3884 card="${card/output/card}"
3885 m pacmd set-card-profile "$card" off
3886
3887 # clicking on a link in a browser can cause unmute.
3888 # I don't want that. So, use a stronger form of mute
3889 # than this.
3890 #pactl set-sink-mute @DEFAULT_SINK@ true
3891 touch /tmp/ianknap
3892 }
3893
3894
3895 # systemctl is-enabled / status / cat says nothing, instead theres
3896 # some obscure symlink. paths copied from man systemd.unit.
3897 # possibly also usefull, but incomplete, doesnt show units not loaded in memory:
3898 # seru list-dependencies --reverse --all UNIT
3899 sysd-deps() {
3900 local f
3901 local -a dirs search
3902 ngset
3903
3904 case $1 in
3905 u)
3906 search=(
3907 ~/.config/systemd/user.control/*
3908 $XDG_RUNTIME_DIR/systemd/user.control/*
3909 $XDG_RUNTIME_DIR/systemd/transient/*
3910 $XDG_RUNTIME_DIR/systemd/generator.early/*
3911 ~/.config/systemd/user/*
3912 /etc/systemd/user/*
3913 $XDG_RUNTIME_DIR/systemd/user/*
3914 /run/systemd/user/*
3915 $XDG_RUNTIME_DIR/systemd/generator/*
3916 ~/.local/share/systemd/user/*
3917 /usr/lib/systemd/user/*
3918 $XDG_RUNTIME_DIR/systemd/generator.late/*
3919 )
3920 ;;
3921 *)
3922 search=(
3923 /etc/systemd/system.control/*
3924 /run/systemd/system.control/*
3925 /run/systemd/transient/*
3926 /run/systemd/generator.early/*
3927 /etc/systemd/system/*
3928 /etc/systemd/systemd.attached/*
3929 /run/systemd/system/*
3930 /run/systemd/systemd.attached/*
3931 /run/systemd/generator/*
3932 /lib/systemd/system/*
3933 /run/systemd/generator.late/*
3934 )
3935 ;;
3936 esac
3937 for f in "${search[@]}"; do
3938 [[ -d $f ]] || continue
3939 case $f in
3940 *.requires|*.wants)
3941 dirs+=("$f")
3942 ;;
3943 esac
3944 done
3945 # dirs is just so we write out the directory names, ls does it when there is 2 or more dirs.
3946 case ${#dirs[@]} in
3947 1)
3948 echo "${dirs[0]}:"
3949 ll "${dirs[@]}"
3950 ;;
3951 0) : ;;
3952 *)
3953 ll "${dirs[@]}"
3954 ;;
3955 esac
3956 ngreset
3957 }
3958
3959 fixvpndns() {
3960 local link istls
3961 read -r _ link _ istls < <(resolvectl dnsovertls tunfsf)
3962 case $istls in
3963 yes|no) : ;;
3964 *) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;;
3965 esac
3966 s busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no
3967 }
3968
3969 vpnoff() {
3970 [[ $1 ]] || { echo need arg; return 1; }
3971 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3972 local vpn_service=openvpn-client
3973 else
3974 local vpn_service=openvpn
3975 fi
3976 sudo systemctl stop $vpn_service@$1
3977 }
3978 vpnoffc() { # vpn off client
3979 ser stop openvpn-client-tr@client
3980 }
3981 vpnc() {
3982 local unit
3983 unit=openvpn-client-tr@client
3984 sudo -v
3985 if [[ $(systemctl is-active $unit) != active ]]; then
3986 s systemctl start $unit
3987 sleep 1
3988 fi
3989 }
3990
3991
3992 vspicy() { # usage: VIRSH_DOMAIN
3993 # connect to vms made with virt-install
3994 spicy -p "$(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
3995 sed -r "s/.*port='([0-9]+).*/\1/")"
3996 }
3997
3998 wian() {
3999 cat-new-files /m/4e/INBOX/new
4000 }
4001 wakehours() {
4002 local sec
4003 if (( $# != 1 )) ; then
4004 echo wakehours: error: expected 1 arg, got $# >&2
4005 return 1
4006 fi
4007 sec=$(( EPOCHSECONDS - $( date +%s -d $1am ) ))
4008 printf "%d:%02d\n" $(( sec / 60 / 60)) $(( (sec / 60) % 60 ))
4009 }
4010
4011 calvis() { # calendar visualize
4012 install -m 600 /dev/null /tmp/calendar-bytes
4013 while read -r l; do
4014 for char in $l; do
4015 # shellcheck disable=SC2059 # intentional for the hex formatting
4016 printf "\x$(printf "%x" $char)" >>/tmp/calendar-bytes
4017 done
4018 done < <(grep -v '[#-]' /p/calendar-data)
4019 /p/c/proc/calendar/linux-amd64/calendar
4020 }
4021
4022 wtr() { curl wttr.in/boston; }
4023
4024 xevkb() { xev -event keyboard; }
4025
4026 # * misc stuff
4027
4028 vrun() {
4029 printf "running: %s\n" "$*"
4030 "$@"
4031 }
4032
4033 electrum() {
4034 # https://electrum.readthedocs.io/en/latest/tor.html
4035 # https://github.com/spesmilo/electrum-docs/issues/129
4036 s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
4037 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
4038 }
4039 monero() {
4040 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
4041 }
4042
4043
4044 # rg my main files
4045 rgm() {
4046 rg "$@" /p/w.org /a/t.org /a/work.org /b
4047 }
4048
4049 # re all my files more expansively
4050
4051 rem() {
4052 local paths
4053 paths="/p/c /b/"
4054 find $paths -not \( -name .svn -prune -o -name .git -prune \
4055 -o -name .hg -prune -o -name .editor-backups -prune \
4056 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto -- "$*" ||:
4057 rgv $local_rgv_args -g "!bash_unpublished" -- "$*" $paths /a/work.org ||:
4058 }
4059 reml() { # rem with limit to 5 matches per file
4060 local_rgv_args="-m 5"
4061 rem "$@"
4062 }
4063
4064 rep() {
4065 local paths
4066 paths="/p/c"
4067 find $paths -not \( -name .svn -prune -o -name .git -prune \
4068 -o -name .hg -prune -o -name .editor-backups -prune \
4069 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto -- "$*" ||:
4070 rgv $local_rgv_args -- "$*" $paths /a/t.org /p/w.org ||:
4071 }
4072 repl() { # rem with limit to 5 matches per file
4073 local local_rgv_args="-m 5"
4074 rem "$@"
4075 }
4076
4077
4078 # re on common fsf files
4079 ref() {
4080 local paths
4081 paths="/f/gluestick /f/brains /f/s /c"
4082 find $paths -not \( -name .svn -prune -o -name .git -prune \
4083 -o -name .hg -prune -o -name .editor-backups -prune \
4084 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto -- "$*" ||:
4085 rgv -- "$*" $paths /a/work.org ||:
4086 }
4087
4088
4089 # for use in /f/bind
4090 fupzone() {
4091 # shellcheck disable=SC2046 # i want word splitting
4092 ./update-zone $(i s | sed -rn 's/.*db\.(.*)/\1/p')
4093 }
4094
4095 # setup:
4096 # pip3 install linode-cli
4097 # linode-cli
4098 livp9() {
4099 local input ip id tmp
4100 input=$1
4101 if [[ $2 ]]; then
4102 id=$2
4103 ip=$3
4104 else
4105 tmp=$(mktemp)
4106 echo $tmp
4107 linode-cli --json --pretty linodes create --root_pass loxHuceygomGisun | tee $tmp
4108 read -r ip id <<<"$(tail -n+2 $tmp | jq -r '.[0].ipv4[0] , .[0].id')"
4109 for string in $ip $id; do
4110 case $string in
4111 [0-9]*) : ;;
4112 *)
4113 echo "livp9: bad value ip=$ip id=$id input=$input"
4114 return 1
4115 ;;
4116 esac
4117 done
4118 rm $tmp
4119
4120 while true; do
4121 if timeout 4 ssh $ip :; then
4122 break
4123 fi
4124 sleep 3
4125 done
4126 fi
4127 ssh $ip <<EOF
4128 apt-get -qq update
4129 apt-get -qq -y install ffmpeg rsync
4130 mkdir vp9
4131 EOF
4132 m rsync $input $ip:
4133 m ssh $ip ffmpeg -nostdin -hide_banner -loglevel error -i $input -g 192 -vcodec libvpx-vp9 -vf scale=-1:720 -max_muxing_queue_size 9999 -b:v 750K -pass 1 -an -f null /dev/null
4134 m ssh $ip ffmpeg -nostdin -hide_banner -loglevel error -y -i $input -g 192 -vcodec libvpx-vp9 -tile-rows 2 -vf scale=-1:720 -max_muxing_queue_size 9999 -b:v 750K -pass 2 -c:a libvorbis -qscale:a 5 vp9/$input
4135 rsync $ip:vp9/$input vp9
4136 linode-cli linodes delete $id
4137 }
4138
4139 reset-konsole() {
4140 # we also have a file in /a/c/...konsole...
4141 local f=$HOME/.config/konsolerc
4142 setini DefaultProfile profileian.profile "Desktop Entry" $f
4143 setini Favorites profileian.profile "Favorite Profiles" $f
4144 setini ShowMenuBarByDefault false KonsoleWindow $f
4145 setini TabBarPosition Top TabBar $f
4146 }
4147
4148 reset-sakura() {
4149 while read -r k v; do
4150 # shellcheck disable=SC2154
4151 setini $k $v sakura /a/c/subdir_files/.config/sakura/sakura.conf
4152 done <<'EOF'
4153 colorset1_back rgb(33,37,39)
4154 less_questions true
4155 audible_bell No
4156 visible_bell No
4157 disable_numbered_tabswitch true
4158 scroll_lines 10000000
4159 scrollbar true
4160 EOF
4161 }
4162
4163 # make a page of links found in the files $@. redirect output
4164 linkhtml() {
4165 gr -oh 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)' "$@" | \
4166 rev | sort -u | rev | sed 's,.*,<a href="\0">\0</a><br\>,'
4167 }
4168
4169 reset-xscreensaver() {
4170 # except for spash, i set these by setting gui options in
4171 # xscreensaver-command -demo
4172 # then finding the corresponding option in .xscreensaver
4173 # spash, i happened to notice in .xscreensaver
4174 #
4175 # dpmsOff, monitor doesnt come back on using old free software supported nvidia card
4176 cat > /home/iank/.xscreensaver <<'EOF'
4177 mode: blank
4178 dpmsEnabled: True
4179 dpmsStandby: 0:07:00
4180 dpmsSuspend: 0:08:00
4181 dpmsOff: 0:00:00
4182 timeout: 0:05:00
4183 lock: True
4184 lockTimeout: 0:06:00
4185 splash: False
4186 EOF
4187
4188 }
4189
4190
4191 # very useful, copy directory structure 3 deep. add remove /*/ to change level
4192 # rsync -aivh --exclude '/*/*/*/' -f"+ */" -f"- *" SRC DEST
4193
4194
4195 # * stuff that makes sense to be at the end
4196 if [[ "$SUDOD" ]]; then
4197 # allow failure, for example if we are sudoing into a user with diffferent/lesser permissions.
4198 cd "$SUDOD" ||:
4199 unset SUDOD
4200 elif [[ -d /a ]] && [[ $PWD == "$HOME" ]] && [[ $- == *i* ]]; then
4201 cd /a
4202 OLDPWD=
4203 fi
4204
4205
4206
4207
4208 # for mitmproxy to get a newer python.
4209 # commented until i want to use it because it
4210 # noticably slows bash startup
4211 #
4212
4213 mypyenvinit () {
4214 if [[ $EUID == 0 || ! -e ~/.pyenv/bin ]]; then
4215 echo "error: dont be root. make sure pyenv is installed"
4216 return 1
4217 fi
4218 export PATH="$HOME/.pyenv/bin:$PATH"
4219 eval "$(pyenv init -)"
4220 eval "$(pyenv virtualenv-init -)"
4221 }
4222
4223
4224
4225 # I have the git repo and a release. either one should work.
4226 # I have both because I was trying to solve an issue that
4227 # turned out to be unrelated.
4228 # ARDUINO_PATH=/a/opt/Arduino/build/linux/work
4229
4230 ## i should have documented this...
4231 # based on https://github.com/keyboardio/Kaleidoscope
4232 export KALEIDOSCOPE_DIR=/a/opt/Kaleidoscope
4233
4234 # They want to be added to the start, but i think
4235 # that should be avoided unless we really need it.
4236 path-add --end ~/.npm-global
4237
4238
4239 path-add --end $HOME/.cargo/bin
4240
4241 if type -P rg &>/dev/null; then
4242 # --no-messages because of annoying errors on broken symlinks
4243 # -z = search .gz etc files
4244 # -. = search dotfiles
4245 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 $?; }
4246 #fails if not exist. ignore
4247 complete -r rg 2>/dev/null ||:
4248 else
4249 alias rg=grr
4250 fi
4251
4252 # rg with respecting vcs ignore files
4253 rgv() {
4254 ret=0
4255 # settings that are turned off for pipes, keep them on.
4256 # Found by searching for "terminal" in --help
4257 # --heading
4258 # -n
4259 #
4260 # -. = search dotfiles
4261 # -z = search zipped files
4262 # -i = case insensitive
4263 # -M = max columns
4264 # --no-messages because of annoying errors on broken symlinks
4265 # --no-ignore-parent because i have /a/.git which ignores almost everything under it.
4266 command rg -n --heading -. -z --no-messages -i -M 900 --no-ignore-parent -g '!.git' -g '!auto-save-list' -g '!.savehist' "$@" || ret=$?
4267 return $ret
4268 }
4269
4270 amall() {
4271 echo "$(tput setaf 5 2>/dev/null ||:)█ coresite █$(tput sgr0 2>/dev/null||:)"
4272 amfsf "$@"
4273 echo "$(tput setaf 5 2>/dev/null ||:)█ office █$(tput sgr0 2>/dev/null||:)"
4274 amoffice "$@"
4275 }
4276 amallq() { # amall quiet
4277 amfsf "$@"
4278 amoffice "$@"
4279 }
4280 amfsf() {
4281 sedi -r '/alertmanager.url/s/@prom.office/@prom/' ~/.config/amtool/config.yml
4282 amtool "$@"
4283 }
4284 amoffice() {
4285 sedi -r '/alertmanager.url/s/@prom.fsf/@prom.office.fsf/' ~/.config/amtool/config.yml
4286 amtool "$@"
4287 }
4288 amls() {
4289 amall silence query "$@"
4290 }
4291 # amtool silence add
4292 amsa() {
4293 amall silence add "$@"
4294 }
4295 # amtool silence force
4296 amsf() {
4297 amall silence add x!="1"
4298 }
4299 amrmall() {
4300 # note: not sure if quoting of this arg is correct
4301 amfsf silence expire "$(amfsf silence query -q)"
4302 amoffice silence expire "$(amoffice silence query -q)"
4303 }
4304
4305
4306 youtube-dl-update() {
4307 sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
4308 sudo chmod a+rx /usr/local/bin/youtube-dl
4309 }
4310
4311 # https://github.com/yt-dlp/yt-dlp/wiki/Installation
4312 yt-dlp-update() {
4313 sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
4314 sudo chmod a+rx /usr/local/bin/yt-dlp # Make executable
4315 }
4316
4317 mpvyt() {
4318 mpv --ytdl ytdl_path=/usr/local/bin/yt-dlp "$@"
4319 }
4320
4321 # taken from default changes to bashrc and bash_profile
4322 path-add --end --ifexists $HOME/.rvm/bin
4323 # also had ruby bin dir, but moved that to environment.sh
4324 # so its included in overall env
4325
4326
4327 # ya, hacky hardcoded hostnames in 2023. we could do better
4328 hssh-update() {
4329 local -a failed_hosts hosts
4330 case $HOSTNAME in
4331 sy|kd)
4332 hosts=(
4333 kd x3.office.fsf.org syw
4334 )
4335 ;;
4336 x3)
4337 hosts=(
4338 b8.nz sywg.b8.nz
4339 )
4340 ;;
4341 esac
4342 for host in ${hosts[@]}; do
4343 e $host
4344 if ! scp /b/fai/fai/config/files/usr/local/bin/hssh/IANK root@$host:/usr/local/bin/hssh; then
4345 failed_hosts+=($host)
4346 fi
4347 done
4348 if (( ${#failed_hosts[@]} >= 1 )); then
4349 echo failed_hosts=${failed_hosts[*]}
4350 return 1
4351 fi
4352 }
4353
4354 noi3bar() {
4355 touch /tmp/noi3bar
4356 }
4357 i3bar() {
4358 rm -fv /tmp/noi3bar
4359 }
4360
4361 # example:
4362 # <#part type="image/jpeg" filename="/home/iank/2023-12-24-ski-trip.jpg" disposition=attachment> <#/part>
4363 #
4364 attach-txt() {
4365 local f
4366 for f; do
4367 if [[ ! -s $f ]]; then
4368 e "error: empty or non-existent file $f"
4369 return 1
4370 fi
4371 done
4372 for f; do
4373 echo '<#part type="image/jpeg" filename="'"$(rl "$f")"'" disposition=attachment> <#/part>'
4374 done | ec
4375 }
4376
4377 ctof() {
4378 units "tempC($1)" tempF
4379 }
4380
4381 ftoc() {
4382 units "tempF($1)" tempC
4383 }
4384
4385 # requires dns/firewall setup first
4386 local-icecast() {
4387 web-conf -e ian@iankelling.org -f 8000 - apache2 live.iankelling.org <<'EOF'
4388 <Location "/fsf.webm">
4389 AuthType Basic
4390 AuthName "basic_auth"
4391 # created with
4392 # htpasswd -c icecast-fsf-htpasswd USERNAME
4393 AuthUserFile "/etc/icecast-fsf-htpasswd"
4394 Require valid-user
4395 </Location>
4396 <Location "/fsf-tech.webm">
4397 AuthType Basic
4398 AuthName "basic_auth"
4399 AuthUserFile "/etc/icecast-fsf-tech-htpasswd"
4400 Require valid-user
4401 </Location>
4402 EOF
4403 }
4404
4405 # obs screen switching of
4406 obof() {
4407 ls -l /tmp/no-obs-auto-scene-switch
4408 touch /tmp/no-obs-auto-scene-switch
4409 }
4410 # obs screen switching on
4411 obon() {
4412 ls -l /tmp/no-obs-auto-scene-switch
4413 if [[ -e /tmp/no-obs-auto-scene-switch ]]; then
4414 rm -f /tmp/no-obs-auto-scene-switch
4415 fi
4416 }
4417
4418 obs-gen-profiles() {
4419 local p=/p/c/basic/profiles
4420 sed 's/fsf-sysops/fsf-tech/g' $p/fsfsysops/basic.ini >$p/fsftech/basic.ini
4421 sed 's/fsf-sysops/fsf/g' $p/fsfsysops/basic.ini >$p/fsf/basic.ini
4422 }
4423
4424 # terminal clear. like clear, but put the prompt at the bottom,
4425 # useful for obs streaming the bottom half of a terminal window.
4426 tclear() {
4427 for ((i=i; i<COLUMNS; i++)); do
4428 echo
4429 done
4430 }
4431
4432
4433 export BASEFILE_DIR=/a/bin/fai-basefiles
4434
4435 #export ANDROID_HOME=/a/opt/android-home
4436 # https://f-droid.org/en/docs/Installing_the_Server_and_Repo_Tools/
4437 #export USE_SDK_WRAPPER=yes
4438 #PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
4439
4440 # didnt get drush working, if I did, this seems like the
4441 # only good thing to include for it.
4442 # Include Drush completion.
4443 # if [ -f "/home/ian/.drush/drush.complete.sh" ] ; then
4444 # source /home/ian/.drush/drush.complete.sh
4445 # fi
4446
4447
4448 # best practice
4449 unset IFS
4450
4451 # https://wiki.archlinux.org/index.php/Xinitrc#Autostart_X_at_login
4452 # i added an extra condition as gentoo xorg guide says depending on
4453 # $DISPLAY is fragile.
4454 if [[ ! $DISPLAY && $XDG_VTNR == 1 ]] && shopt -q login_shell && isarch; then
4455 exec startx
4456 fi
4457
4458
4459 # ensure no bad programs appending to this file will have an affect
4460 return 0