more beets stuff
[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 source /a/bin/log-quiet/logq-function
62
63 # not used
64 # if [[ -s /a/opt/alacritty/extra/completions/alacritty.bash ]]; then
65 # source /a/opt/alacritty/extra/completions/alacritty.bash
66 # fi
67
68
69 source /a/bin/ds/beet-data
70
71
72 # * functions
73
74 multimic() {
75 local i
76 local -a sources
77
78 m pactl unload-module module-loopback
79 m pactl unload-module module-null-sink
80 m pactl unload-module module-remap-source
81
82 IFS=" " read -r -a sources <<<"$(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p')"
83
84 if (( ! $# )); then
85 i=0
86 for s in ${sources[@]}; do
87 e $i $s
88 i=$(( i+1 ))
89 done
90 read -r l
91 set -- $l
92 fi
93 m pactl load-module module-null-sink sink_name=ianinput sink_properties=device.description=ianinputs
94 for i; do
95 m pactl load-module module-loopback source=${sources[i]} sink_dont_move=true sink=ianinput
96 done
97 pactl load-module module-remap-source source_name=iancombine master=ianinput.monitor source_properties=device.description=iancombine
98 }
99
100 # h ssh test
101 # For testing restrictive ssh.
102 hstest() {
103 install-my-scripts
104 d=$(mktemp -d)
105 sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config
106 s command ssh -F $d/config -i /q/root/h "$@"
107 }
108
109 # h rsync test
110 # For testing restrictive rsync
111 hrtest() { #
112 install-my-scripts
113 d=$(mktemp -d)
114 sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config
115 s rsync -e "ssh -F $d/config -i /q/root/h" "$@"
116 }
117
118 # rsync as root and avoid the default restrictive h key & config.
119 rootrsync() {
120 s rsync -e "ssh -F /root/.ssh/confighome" "$@"
121 }
122
123 zcheck() {
124 ssh bow DISPLAY=:0 scrot /tmp/oegu.jpg
125 scp bow:/tmp/oegu.jpg /t
126 ssh bow rm /tmp/oegu.jpg
127 feh /t/oegu.jpg
128 }
129
130 slemacs() {
131 local arg rtime v
132 arg="$1"
133 remote="$2"
134 if [[ $arg == [89]0Etiona* ]]; then
135 v=${arg::1}
136 rtime=${arg#*Etiona} # remote time
137 if [[ ! $rtime ]]; then
138 rtime=0
139 fi
140 dir=/a/opt/emacs-trisquel${v}-nox/.iank
141 ltime=$(stat -c%Y $dir/e/e/.emacs.d/init.el)
142 if (( ltime > rtime )); then
143 m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" $dir "$remote":/home/iank
144 fi
145 fi
146 }
147
148 sle() { # sl emacs
149 local f=/home/iank/.emacs.d/init.el
150 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 "$@"
151 }
152 ccomp ssh sle
153
154 # Run this manually after .emacs.d changes. Otherwise, to check if
155 # files changed with find takes 90ms. sl normally only adds 25ms. We
156 # could cut it down to 10ms if we put things on a btrfs filesystem and
157 # looked for changes there, or used some inotify thing, but that seems
158 # like too much work.
159 egh() { # emacs gnuhope
160 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank lists2d.fsf.org:.ianktrisquel_9
161 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank lists2d.fsf.org:/home/iank
162 }
163 ekw() {
164 local shell="bash -s"
165 if [[ $HOSTNAME != kw ]]; then
166 shell="ssh kw.office.fsf.org"
167 bbk -m /a -t kw
168 fi
169 $shell <<'EOF'
170 sudo mkdir /root/.ianktrisquel_9
171 sudo rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank /root/.ianktrisquel_9
172 rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank /home/iank
173 EOF
174 }
175
176 rm-docker-iptables() {
177 s iptables -S | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables $l; done
178 s iptables -S -t nat | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables -t nat $l; done
179 s iptables -S | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables $l; done
180 s iptables -S -t nat | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables -t nat $l; done
181 }
182
183 # usage mkschroot [-] distro codename packages
184 # - means no piping in of sources.list
185 mkschroot() {
186 local sources force repo n distro
187 force=false
188 while [[ $1 == -* ]]; do
189 case $1 in
190 -f) force=true; shift ;;
191 -s)
192 sources="$2"
193 if [[ ! -s $sources ]]; then
194 echo mkschroot: error: sources file $sources does not exist or is empty
195 return 1
196 fi
197 shift 2
198 ;;
199 esac
200 done
201 distro=$1
202 shift
203 case $distro in
204 trisquel)
205 repo=http://mirror.fsf.org/trisquel/
206 ;;
207 ubuntu)
208 repo=http://archive.ubuntu.com/ubuntu/
209 ;;
210 debian)
211 repo=http://deb.debian.org/debian/
212 ;;
213 esac
214 n=$1
215
216 shift
217 if ! $force && schroot -l | grep -xFq chroot:$n; then
218 echo "$0: $n schroot already installed, skipping"
219 return 0
220 fi
221 apps=($@)
222 d=/nocow/schroot/$n
223 sd /etc/schroot/chroot.d/$n.conf <<EOF
224 [$n]
225 description=$n
226 type=directory
227 directory=$d
228 profile=desktop
229 preserve-environment=true
230 users=$USER,user2
231 EOF
232 cd
233 if [[ ! -e $d/bin ]]; then
234 sudo mkdir -p $d
235 # resolvconf otherwise schroot fails with
236 # cp: not writing through dangling symlink '/var/run/schroot/mount/flidas-7a2362e0-81b3-4848-92c1-610203ef5976/etc/resolv.conf'
237 sudo debootstrap --exclude=resolvconf $n $d $repo
238 fi
239 if [[ $sources ]]; then
240 sudo install -m 644 $sources $d/etc/apt/sources.list
241 fi
242 sudo chroot $d apt-get update
243 sudo DEBIAN_FRONTEND=noninteractive chroot $d apt-get -y dist-upgrade --purge --auto-remove
244 sudo cp -P {,$d}/etc/localtime
245 if (( ${#apps[@]} )); then
246 sudo DEBIAN_FRONTEND=noninteractive schroot -c $n -- apt-get install --allow-unauthenticated -y ${apps[@]}
247 fi
248 }
249
250
251 # note: this is incomplete and untested.
252 # https://wiki.archlinux.org/index.php/Install_Arch_Linux_from_existing_Linux#Creating_a_chroot
253 mkarchchroot() {
254 local tarball mirror
255 mirror=https://mirrors.edge.kernel.org/archlinux/iso/latest/
256 tarball=$(curl -s $mirror | sed -nr 's/.*"(archlinux-bootstrap-.*-x86_64.tar.gz)".*/\1/p')
257 wget -O /tmp/arch.tar.gz https://mirrors.edge.kernel.org/archlinux/iso/latest/$tarball
258 s mkdir -p /nocow/schroot/arch
259 cd _/nocow/schroot/arch
260 s sed -i '/## United States/,/^$/s,^#,,' etc/pacman.d/mirrorlist
261 # error: could not determine cachedir mount point /var/cache/pacman/pkg
262 s sed -i /^CheckSpace/d etc/pacman.conf
263 chroot . /bin/bash -s <<'EOF'
264 pacman-key --init
265 pacman-key --populate archlinux
266 pacman -Syyu
267 EOF
268 # example of building an aur package:
269 # pacman -Sy base-devel wget
270 # useradd -m iank
271 # f=$target/etc/sudoers
272 # line='iank ALL=(ALL) NOPASSWD: ALL'
273 # if [[ ! -e $f ]] || ! grep -xF "$line" $f; then
274 # echo "$line" >> $f
275 # fi
276 # su iank
277 # wget https://aur.archlinux.org/cgit/aur.git/snapshot/anbox-image-gapps.tar.gz
278 # tar xzf anbox-image-gapps.tar.gz
279 # cd anbox-image-gapps
280 # makepkg -s
281 }
282
283
284 # clock back in to timetrack from last entry
285 tback() {
286 sqlite3 /p/.timetrap.db "update entries set end = NULL where id = (select max(id) from entries);"
287 }
288
289 # sshfs example:
290 # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
291
292 eqgo() {
293 enn -M "$(exiqgrep -i -r.\*)"
294 }
295 eqgo1() {
296 enn -M "$(exipick -i -r.\*|h1)"
297 }
298
299
300 gnupload(){
301 /a/f/gnulib/build-aux/gnupload "$@"
302 }
303
304 abrowserrmcompat() {
305 local f
306 ngset
307 f=(/p/c/firefox*/compatibility.ini)
308 if (( ${#f[@]} )); then
309 rm ${f[@]}
310 fi
311 ngreset
312 }
313
314 checkre() {
315 s checkrestart -b /a/bin/ds/checkrestart-blacklist -pv
316 }
317
318 cp-blocked-domains-to-brains() {
319 cp /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn
320 }
321 cp-blocked-domains-to-ansible() {
322 cp /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains
323 }
324
325
326 anki() {
327 # crashes on adding new cards in t9
328 schroot -c buster -- anki
329 }
330
331 acat() {
332 ngset
333 hrcat /m/md/alerts/{cur,new}/*
334 ngreset
335 hr; echo bk; hr
336 ssh bk.b8.nz "shopt -s nullglob; hrcat /m/md/INBOX/new/* /m/md/INBOX/cur/*"
337 }
338 aclear() {
339 ngset
340 rm -f /m/md/alerts/{cur,new}/*
341 ngreset
342 ssh bk.b8.nz "shopt -s nullglob; rm -f /m/md/INBOX/new/* /m/md/INBOX/cur/*"
343 system-status _
344 }
345
346 alerts() {
347 find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f
348 }
349 ralerts() { # remote alerts
350 local ret shell
351 # this list is duplicated in check-remote-mailqs
352 for h in bk je li frodo kwwg x3wg x2wg kdwg sywg; do
353 echo $h:
354 shell="ssh $h"
355 if [[ $HOSTNAME == "${h%wg}" ]]; then
356 shell=
357 fi
358 ret=0
359 $shell find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f || ret=$?
360 if (( ret )); then
361 echo ret:$ret
362 fi
363 done
364 }
365
366 ap() {
367 # pushd in case current directory has an ansible.cfg file
368 pushd /a/xans >/dev/null
369 ansible-playbook -v -l ${1:- $(hostname -f)} site.yml
370 popd >/dev/null
371 }
372 aw() {
373 pushd /a/work/ans >/dev/null
374 time ansible-playbook -i inventory adhoc.yml "$@"
375 popd >/dev/null
376 }
377 ad() {
378 pushd /a/bin/distro-setup/a >/dev/null
379 ansible-playbook site.yml "$@"
380 popd >/dev/null
381 }
382
383 astudio() {
384 # googling android emulator libGL error: failed to load driver: r600
385 # lead to http://stackoverflow.com/a/36625175/14456
386 export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1
387 /a/opt/android-studio/bin/studio.sh "$@" & r
388 }
389
390 # convert brains path to url
391 # /f/brains/sysadmin/interns/2022/nick_shrader/intro_blog_post.mdwn
392 # becomes
393 # https://brains.fsf.org/wiki/sysadmin/interns/2022/nick_shrader/intro_blog_post
394 iki() {
395 local url path
396 if [[ $1 ]]; then
397 path="$*"
398 else
399 read -r -p "enter path" path
400 fi
401 url=$(readlink -f "$path")
402 url="https://brains.fsf.org/wiki/${url#*brains/}"
403 url="${url%.mdwn}"
404 echo "$url"
405
406 }
407
408 # Generate beet smartplaylists for navidrome.
409 # for going in the reverse direction, run
410 # /b/ds/navidrome-playlist-export
411 beetsmartplaylists() {
412 install -m 0700 -d /tmp/ianbeetstmp
413 beet splupdate
414 # kill off any playlists we deleted. they will still need manual
415 # killing from a navidrome client.
416 rm -rf /i/converted/beetsmartplaylists
417 mkdir -p /i/converted/beetsmartplaylists
418 for f in /tmp/ianbeetstmp/*; do
419 sed 's,^/i/m,/i/converted,;s,\.flac$,.mp3,' "$f" >"/i/converted/beetsmartplaylists/${f##*/}"
420 rm "$f"
421 done
422 rmdir /tmp/ianbeetstmp
423 }
424
425 # internal function for beetrating, in case we need to ssh
426 beetrating-stdin() {
427 local tmp rating path cpath sqlpath userid
428 # plucked this from the db. im the only user.
429 userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634
430 while read -r rating path; do
431 cpath="/i/converted${path#/i/m}" # converted path
432 case $cpath in
433 *.flac)
434 cpath="${cpath%.*}.mp3"
435 ;;
436 esac
437 if [[ ! -e $cpath ]]; then
438 echo "beetraing: error: this should not happen, path does not exist: $cpath"
439 return 1
440 fi
441 sqlpath="${cpath//\'/\'\'}"
442 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';")
443 if [[ $old_rating ]]; then
444 if [[ $old_rating != "$rating" ]]; then
445 echo "setting rating $old_rating -> $rating $cpath"
446 # https://stackoverflow.com/a/50317320
447 # we got a timeout error once. arbitrarily chose 15 seconds.
448 sqlite3 /i/navidrome/navidrome.db ".timeout 15000" "
449 update annotation set rating = $rating
450 where item_id in (
451 select media_file.id from annotation inner join media_file on annotation.item_id = media_file.id
452 where media_file.path = '$sqlpath' and annotation.item_type = 'media_file' );"
453 fi
454 else
455 echo "setting rating $rating $cpath"
456 # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0
457 # https://www.sqlite.org/lang_insert.html
458 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';"
459 fi
460 done
461 }
462
463 # Export beets ratings into navidrome
464 beetrating() {
465 local ssh_prefix
466 if [[ $HOSTNAME != kd ]]; then
467 ssh_prefix="ssh b8.nz"
468 fi
469 # shellcheck disable=SC2016 # obvious reason
470 beet ls -f '$rating $path' $nav_convert_query | $ssh_prefix beetrating-stdin
471 }
472
473 # Do transcoding and hardlinking of audio files for navidrome.
474 beetconvert() {
475 local tmpf
476 tmpf="$(mktemp)"
477 # a bunch of effort to ignore output we dont care about...
478 sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
479 beet -c $tmpf convert -y $nav_convert_query > >(grep -vFx 'ignore_this' ||:) 2> >(grep -v '^convert: Skipping' ||:)
480 rm "$tmpf"
481 }
482 # This deletes files in the converted directory which should no longer
483 # be there due to a rename of the unconverted file.
484 beetconvert-rm-extras() {
485 local l tmpf
486 local -A paths
487 tmpf="$(mktemp)"
488 # shellcheck disable=SC2016 # obvious reason
489 beet ls -f '$path' $nav_convert_query >"$tmpf"
490 ## begin removal of files that are leftover from previous conversion,
491 # eg, previously rated > 1, now rated 1.
492 while read -r l; do
493 convertedpath="/i/converted${l#/i/m}"
494 case $convertedpath in
495 *.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
496 esac
497 paths[$convertedpath]=t
498 done <"$tmpf"
499
500 find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \) -name '*.mp3' -o -name '*.m4a' >"$tmpf"
501 while read -r l; do
502 if [[ ! ${paths[$l]} ]]; then
503 rm -v "$l"
504 fi
505 # note: the pruning is duplicative of filtering on name, but whatever.
506 done <"$tmpf"
507 rm "$tmpf"
508 }
509
510 beets-gen-playlists() {
511 local i str
512 local -a query_array query_str
513 for i in "${!bpla[@]}"; do
514 query_str=()
515 eval "query_array=(${bpla[$i]})"
516 for str in "${query_array[@]}"; do
517 query_str+=("\"$str\"")
518 done
519 cat <<EOF
520 - name: $i.m3u
521 query: '${query_str[@]}'
522 EOF
523 done
524 }
525
526 # beet playlist. use beetag with a playlist name
527 bpl() {
528 local playlist playlist_regex
529 playlist="${*: -1}"
530 playlist_regex='[a-z0-9_]'
531 if [[ ! $playlist =~ $playlist_regex ]]; then
532 echo "bpl: error unexpected chars in playlist: $playlist"
533 return 1
534 fi
535 # all but last arg as options
536 eval beetag -r "${*:1:$# - 1}" "${bpla[$playlist]}"
537 }
538 complete -W "${!bpla[*]}" bpl
539
540
541 # beet modify quietly
542 beetmq() {
543 local tmpf
544 tmpf="$(mktemp)"
545 # a bunch of effort to ignore output we dont care about...
546 sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
547 beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:)
548 rm "$tmpf"
549 beetag-nostatus 1
550 }
551
552 kill-bg-quiet() {
553 # https://stackoverflow.com/a/5722874
554 kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||:
555 }
556
557 # debug variables
558 dv() {
559 for arg; do
560 printf "%s=%s " "$arg" "${!arg}"
561 done
562 echo
563 }
564
565 # Must be called from beetag for variables to be setup
566 beetag-help() {
567 local -i i j col_total row col button_total row_total remainder_cols remainder_term
568 col_total=4
569 button_total=${#button_map[@]}
570 row_total=$(( button_total / col_total ))
571 remainder_cols=$(( button_total % col_total ))
572 # for debugging
573 #dv button_total row_total remainder_cols
574 beetag-nostatus
575 # - 3 is just a constant that helps things work in practice.
576 if [[ $LINES ]] && (( LINES - 3 < scrolled )); then
577 hr
578 for (( i=0; i<button_total; i++)); do
579 row=$(( i / col_total ))
580 col=$(( i % col_total ))
581 remainder_term=$remainder_cols
582 if (( col < remainder_term )); then
583 remainder_term=$col
584 fi
585 j=$(( col * row_total + row + remainder_term ))
586 # avoid double newline when we have exactly row * col buttons
587 if (( i == button_total - 1 )); then
588 printf "%s %s" ${buttons[j]} ${button_map[j]}
589 elif (( i % col_total == col_total -1 )); then
590 printf "%s %s\n" ${buttons[j]} ${button_map[j]}
591 else
592 printf "%s %-15s" ${buttons[j]} ${button_map[j]}
593 fi
594 done
595 cat <<'EOF'
596
597
598 y other genres z fg player ' = toggle play 1-5 rate
599 ; previous _ = delete up/down skip mpv vol,pause,seek
600 EOF
601 hr
602 scrolled=10
603 fi
604 }
605
606 # Must be called from beetag for variables to be setup
607 beetag-nostatus() {
608 if (( $# )); then
609 scrolled=$(( scrolled + $1 ))
610 fi
611 if $erasable_line; then
612 # https://stackoverflow.com/a/71286261
613 printf '\033[1A\033[K'
614 fi
615 erasable_line=false
616 }
617 # meant to be called from beetag
618 beetag-status() {
619 if $erasable_line; then
620 # https://stackoverflow.com/a/71286261
621 printf '\033[1A\033[K'
622 fi
623 erasable_line=true
624 }
625
626 # meant to be called from beetag
627 mpvrpc() {
628 if jobs -p | grep -q . &>/dev/null; then
629 printf "%s\n" "$*" | socat - /tmp/mpvsock >/dev/null ||:
630 fi
631 }
632 # meant to be called from beetag
633 # o for get output
634 mpvrpco() {
635 # note: testing for background jobs will output nothing if we are in a pipeline
636 printf "%s\n" "$*" | socat - /tmp/mpvsock ||:
637 }
638
639 # meant to be called from beetag
640 mpvrpc-percent-pos() {
641 mpvrpco '{ "command": ["get_property", "percent-pos"] }' | jq .data | sed 's/\..*/%/' 2>/dev/null ||:
642 }
643
644 # tag with beets.
645 # usage: beetag [-r] [-s] QUERY
646 # it lists the query, reads an input char for tagging one by one.
647 #
648 # note, you may want to change the play command for doing rapid taging
649 # by immediately jumping forward into the song. this is set in the beets
650 # config yaml.
651 #
652 # (available buttons: ` \ ) ] [ and non-printing chars, see
653 # https://stackoverflow.com/questions/10679188/casing-arrow-keys-in-bash
654 #
655 #
656 # note: after foregrounding the player, must quit it to get back. can't ctrl-c.
657 #
658 # keys I dont need help to remember:
659 # 1-5 rate
660 # q quit
661 # ret next
662 beetag() {
663 local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random path
664 local do_rare_genres read_wait help line lsout tmp ls_line skip_lookback
665 local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec
666 local pl_state_path pl_state_dir pl_state_file
667 local new_random pl_seed_path seed_num seed_file
668 local -a pl_tags buttons button_map ids tags tmp_tags initial_ls ls_lines paths
669 local -A button_i
670 local -i i j volume scrolled id_count line_int skip_start pre_j_count head_count skip_lookback
671 local -i overflow_lines overflow
672
673 erasable_line=false
674 escape_char=$(printf "\u1b")
675 scrolled=999 # more than any $LINES
676 ### begin arg processing ###
677 random=false
678 new_random=false
679 case $1 in
680 -r)
681 random=true
682 shift
683 ;;
684 -s)
685 random=false
686 shift
687 ;;
688 -x)
689 new_random=true
690 shift
691 ;;
692 esac
693 if (( ! $# )); then
694 echo beetag: error expected a query arg >&2
695 return 1
696 fi
697 ### end arg processing ###
698
699 beetpull
700
701 do_rare_genres=false
702 volume=70
703 read_wait=2
704 doplay=true
705
706 last_genre_i=$(( ${#common_genres[@]} - 1 ))
707 buttons=( {a..p} {r..w} {6..8} , . / - "=")
708 button_map=(${common_genres[@]} ${pl_tags[@]})
709 fstring=
710 for tag in "${pl_tags[@]}"; do
711 fstring+="%ifdef{$tag,$tag }"
712 done
713
714 for (( i=0; i<${#buttons[@]}; i++ )); do
715 button_i[${buttons[i]}]=$i
716 done
717
718 # note: this structure of files is rather haphazard.
719 seed_num=1 # later we might want a few
720 seed_file=seed$seed_num
721 if $random; then
722 pl_state_file=$seed_num
723 else
724 pl_state_file=sorted
725 fi
726 pl_state_dir=/i/info/pl-state
727 if [[ $playlist ]]; then
728 pl_state_dir=$pl_state_dir/nopl
729 else
730 pl_state_dir=$pl_state_dir/$playlist
731 fi
732 pl_state_path=$pl_state_dir/$pl_state_file
733 pl_seed_path=$pl_state_dir/$seed_file
734
735
736 if $new_random || [[ ! -r $pl_seed_path ]]; then
737 mkdir -p $pl_state_dir
738 { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path
739 fi
740
741
742
743 # PijokVipiotOzeph is just a random string for a delimiter
744 # shellcheck disable=SC2016 # obvious reason
745 mapfile -t initial_ls < <(beet ls -f '%ifdef{rating,$rating }'"$fstring"'$genre | $artist - $album - $title $length $id PijokVipiotOzeph $path' "$@" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } )
746 id_count=${#initial_ls[@]}
747 for line in "${initial_ls[@]}"; do
748 path="${line#*PijokVipiotOzeph }"
749 # https://github.com/koalaman/shellcheck/issues/2171
750 # shellcheck disable=SC2190 # bug in shellcheck, looking at paths from an earlier function
751 paths+=("$path")
752 line_no_path="${line% PijokVipiotOzeph*}"
753 id="${line_no_path##* }"
754 ids+=("$id")
755 right_pad="${line_no_path%% |*}"
756 ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}"
757 ls_lines+=("$ls_line")
758 i=$(( i+1 ))
759 done
760
761
762
763
764 j=0
765 if [[ $playlist ]]; then
766 if [[ -r $pl_state_path ]]; then
767 j=$(cat $pl_state_path)
768 fi
769 fi
770
771 # i only care to see a smallish portion of the list when starting.
772 head_count=$(( LINES - 20 ))
773 head_start=$(( j - head_count / 2 ))
774 if (( head_start < 0 )); then
775 head_start=0
776 fi
777 for (( i=head_start; i < head_count && i < id_count; i++ )); do
778 ls_line="${ls_lines[$i]}"
779 if (( i == j )); then
780 echo "* $ls_line"
781 else
782 echo "$ls_line"
783 fi
784 done
785
786 while true; do
787 id=${ids[j]}
788 path="${paths[$j]}"
789 lsout="${ls_lines[j]}"
790 tags=( ${lsout%%,*} )
791 beetag-help
792 printf "██ %s\n" "$lsout"
793 beetag-nostatus 1
794 if $doplay; then
795 # https://stackoverflow.com/a/7687716
796 # note: duplicated down below
797 # https://superuser.com/questions/305933/preventing-bash-from-displaying-done-when-a-background-command-finishes-execut
798 # we can't disown or run in a subshell or set +m because all that
799 # disabled job control from working properly in ways we want.
800 # todo: figure out some kind of answer to this. I think the solution
801 # is that we are waiting in 2 second intervals and checking if the
802 # background job exists. Instead, we should make mpv just idle
803 # when it is done with a song and then send it a command to play a new track.
804 { mpv --profile=a --volume=$volume "$path" 2>&1 & } 2>/dev/null
805 erasable_line=false
806 fi
807 while true; do
808 char=
809 if $doplay; then
810 ret=0
811 read -rsN1 -t $read_wait char || ret=$?
812 read_wait=2
813 # Automatically skip to the next song if this one ends, unless
814 # we turn off the autoplay.
815 if (( ret == 142 )) || [[ ! $char ]]; then
816 if jobs -p | grep -q . &>/dev/null; then
817 continue
818 else
819 break
820 fi
821 fi
822 else
823 read -rsN1 char
824 fi
825 beetag-help
826 if [[ $char == $'\n' ]]; then
827 # https://stackoverflow.com/a/5722874
828 kill-bg-quiet
829 break
830 fi
831 case $char in
832 ";")
833 kill-bg-quiet
834 j=$(( j - 2 ))
835 break
836 ;;
837 "'")
838 if $doplay; then
839 echo "play toggled off"
840 doplay=false
841 else
842 doplay=true
843 kill-bg-quiet
844 { mpv --profile=a --volume=$volume "$path" 2>&1 & } 2>/dev/null
845 #{ beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
846 erasable_line=false
847 fi
848 beetag-nostatus 1
849 continue
850 ;;
851 _)
852 kill-bg-quiet
853 m beet rm --delete --force "id:$id"
854 beetag-nostatus 4 # guessing. dont want to test atm
855 break
856 ;;
857 [1-5])
858 beetmq "id:$id" rating=$char
859 continue
860 ;;
861 9)
862 volume=$(( volume - 5 ))
863 if (( volume < 0 )); then
864 volume=0
865 fi
866 ;;&
867 0)
868 volume+=5
869 if (( volume > 130 )); then
870 volume=130
871 fi
872 ;;&
873 0|9)
874 mpvrpc '{ "command": ["set_property", "volume", '$volume'] }'
875 beetag-status
876 echo volume=$volume
877 continue
878 ;;
879 q)
880 kill-bg-quiet
881 return
882 ;;
883 y)
884 if $do_rare_genres; then
885 do_rare_genres=false
886 button_map=(${common_genres[@]} ${pl_tags[@]})
887 last_genre_i=$(( ${#rare_genres[@]} - 1 ))
888 else
889 do_rare_genres=true
890 button_map=(${rare_genres[@]} ${pl_tags[@]})
891 last_genre_i=$(( ${#rare_genres[@]} - 1 ))
892 fi
893 local -A button_i
894 for (( i=0; i<${#buttons[@]}; i++ )); do
895 button_i[${buttons[i]}]=$i
896 done
897 for (( i=0; i<${#button_map[@]}; i++ )); do
898 echo ${buttons[i]} ${button_map[i]}
899 done
900 continue
901 ;;
902 z)
903 beetag-nostatus 3
904 # if we ctrl-z, it will put the whole function into sleep. so
905 # basically, we can't return from a foregrounded mpv like we
906 # would like to without some strange mechanism I can't think
907 # of. So, instead, detect ctrl-c and wait a while for prompt
908 # input. One idea would be to use a music player like mpd where
909 # we can send it messages.
910 if ! fg; then
911 read_wait=10
912 fi
913 continue
914 ;;
915
916 #
917 " ")
918 # output time if we aren't already paused
919 if [[ $(mpvrpco '{ "command": ["get_property", "pause"] }' | jq .data) == false ]]; then
920 # minutes/seconds
921 #date -d @"$(mpvrpco '{ "command": ["get_property", "playback-time"] }' | jq .data)" +%M:%S ||:
922 beetag-status
923 mpvrpc-percent-pos
924 fi
925 # originally found this solution, which worked fine.
926 #kill -STOP %% &>/dev/null
927 #
928 mpvrpc '{ "command": ["cycle", "pause"] }'
929 continue
930 ;;
931 "$escape_char")
932 expected_input=true
933 read -rsn2 escaped_input
934 skip_input_regex="^[0-9]+$"
935 case $escaped_input in
936 # up char
937 '[A')
938 skip_start=0
939 skip_lookback=5
940 if (( j - skip_lookback > skip_start )); then
941 skip_start=$(( j - skip_lookback ))
942 fi
943 beetag-nostatus $(( id_count - skip_start - 1 ))
944 {
945 line_int=0
946 for (( i=skip_start; i < id_count; i++ )); do
947 if (( i == j )); then
948 echo " * ${ls_lines[i]}"
949 continue
950 fi
951 echo "$line_int | ${ls_lines[i]}"
952 line_int+=1
953 done
954 } | less -F
955 ;;
956 # down char
957 '[B')
958 # skip forward, but show the last few songs anyways.
959 skip_start=0
960 skip_lookback=3
961 if (( j - skip_lookback > skip_start )); then
962 skip_start=$(( j - skip_lookback ))
963 fi
964 beetag-nostatus $(( id_count - skip_start - 1 ))
965
966 line_int=0
967 overflow_lines=$LINES
968 for (( i=skip_start; i < overflow_lines - 1 && i < id_count; i++ )); do
969 ls_line="${ls_lines[i]}"
970 overflow=$(( ${#ls_line} / ( COLUMNS - 1 ) ))
971 overflow_lines=$(( overflow_lines - overflow ))
972 if (( i == j )); then
973 echo " * $ls_line"
974 continue
975 fi
976 echo "$line_int | $ls_line"
977 line_int+=1
978 done
979 ;;
980 # left key
981 '[D')
982 seek_sec=-8
983 ;;&
984 # right key
985 '[C')
986 seek_sec=8
987 ;;&
988 '[C'|'[D')
989 beetag-status
990 mpvrpc-percent-pos
991 erasable_line=true
992 mpvrpc '{ "command": ["seek", "'$seek_sec'"] }'
993 continue
994 ;;
995 *)
996 expected_input=false
997 ;;
998 esac
999 if $expected_input; then
1000 read -r skip_input
1001 case $skip_input in
1002 q)
1003 kill-bg-quiet
1004 return
1005 ;;
1006 esac
1007 if [[ $skip_input =~ $skip_input_regex ]]; then
1008 pre_j_count=$(( j - skip_start ))
1009 j=$(( j + skip_input - pre_j_count ))
1010 if (( skip_input < pre_j_count )); then
1011 j=$(( j - 1 ))
1012 fi
1013 fi
1014 kill-bg-quiet
1015 break
1016 fi
1017 ;;
1018 esac
1019 char_i=${button_i[$char]}
1020 new_item=${button_map[$char_i]}
1021 if [[ ! $char_i || ! $new_item ]]; then
1022 echo "error: no mapping of input: $char found, try again"
1023 continue
1024 fi
1025 if (( char_i <= last_genre_i )); then
1026 m beetmq "id:$id" genre=$new_item
1027 else
1028 remove=false
1029 tmp_tags=()
1030 for tag in ${tags[@]}; do
1031 if [[ $new_item == "$tag" ]]; then
1032 remove=true
1033 else
1034 tmp_tags+=("$tag")
1035 fi
1036 done
1037 if $remove; then
1038 tags=("${tags[@]}")
1039 m beetmq "id:$id" "$new_item!"
1040 else
1041 tags+=("$new_item")
1042 m beetmq "id:$id" $new_item=t
1043 fi
1044 fi
1045 done
1046 if (( j < id_count - 1 )); then
1047 j+=1
1048 else
1049 j=0
1050 fi
1051 if [[ $playlist ]]; then
1052 echo $j >$pl_state_path
1053 fi
1054 done
1055 }
1056
1057 # usage: FILE|ALBUM_DIR [GENRE]
1058 beetadd() {
1059 local import_path genre_arg single_track_arg
1060 import_path="$1"
1061 if [[ ! -e $import_path ]]; then
1062 echo "beetadd error: path does not exist"
1063 fi
1064 if [[ $2 ]]; then
1065 genre_arg="--set genre=$2"
1066 fi
1067 if [[ -f $import_path ]]; then
1068 single_track_arg=-s
1069 fi
1070 beet import --set totag=t $single_track_arg $genre_arg "$import_path"
1071 beetag totag:t
1072 beet modify -y totag:t "totag!"
1073 }
1074
1075 # update navidrome music data after doing beets tagging
1076 beet2nav() {
1077 m beetpull
1078 m beetconvert
1079 m beetrating
1080 # this function would naturally just be part of beetconvert,
1081 # but we want beetrating to happen sooner so that our ssh auth dialog
1082 # happens earlier. Currently 17 seconds for that.
1083 m beetconvert-rm-extras
1084 m beetsmartplaylists
1085 }
1086
1087 # pull in beets library locally
1088 beetpull() {
1089 if [[ $HOSTNAME == kd ]]; then
1090 return 0
1091 fi
1092 if [[ ! -e /i ]]; then
1093 s mkdir /i
1094 s chown iank:iank /i
1095 fi
1096 if ! mountpoint /i &>/dev/null; then
1097 m sshfs b8.nz:/i /i
1098 fi
1099 }
1100
1101 # remove all playlists in navidrome, for when I make big
1102 # playlist name changes and just want to scrap everything.
1103 nav-rm-plists() {
1104 local tmpf id
1105 tmpf=$(mktemp)
1106 if [[ $HOSTNAME != kd ]]; then
1107 echo "error: run on kd"
1108 return 1
1109 fi
1110 sqlite3 /i/navidrome/navidrome.db "select id from playlist" >$tmpf
1111 while read -r id; do
1112
1113 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"
1114 done <$tmpf
1115 rm $tmpf
1116 }
1117
1118 # escape regex.
1119 #
1120 # This is not perfect but generally good enough. It escapes all
1121 # metachars listed man 3 pcrepattern.
1122 er() {
1123 sed 's/[]\\^$.[|()?*+{}]/[&]/g; s/\^/\\^/g' <<<"$*"
1124 }
1125
1126 # usage beegenre QUERY
1127 #
1128 # beet set genre for QUERY based on existing artist most used genre on
1129 #
1130 # inverse of query for each artist found in QUERY. If query starts with
1131 # "artist:" it is used as the artist instead of each artist in QUERY.
1132 #
1133 beegenre() {
1134 local count artist artregex genre singleartist tmpf tmpf2
1135 local -a artists genres
1136 singleartist=false
1137 case $1 in
1138 artist:*)
1139 singleartist=true
1140 artist="$1"
1141 shift
1142 ;;
1143 esac
1144 tmpf=$(mktemp)
1145 tmpf2=$(mktemp)
1146 if $singleartist; then
1147 # shellcheck disable=SC2016 # obvious reason
1148 beet ls -f '$genre' "$artist" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1 >$tmpf
1149 read -r count genre <$tmpf ||:
1150 beet modify "$artist" "$@" genre=$genre
1151 else
1152 # shellcheck disable=SC2016 # obvious reason
1153 beet ls -f '$artist' "$@" | sort -u >$tmpf
1154 while read -r artist; do
1155 artregex=$(er "$artist")
1156 # shellcheck disable=SC2016 # obvious reason
1157 beet ls -f '$genre' "artist::^$artregex$" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1 >$tmpf2
1158 read -r count genre <$tmpf2 || continue
1159 if [[ $count ]]; then
1160 artists+=("$artregex")
1161 genres+=("$genre")
1162 echo "beet modify -y $* \"artist::^$artist$\" genre=$genre # $count"
1163 fi
1164 done <$tmpf
1165 read -r -N 1 -s -p "Y/n " char
1166 case $char in
1167 [Yy$'\n'])
1168 for (( i=0; i<${#artists[@]}; i++ )); do
1169 beet modify -y "$@" "artist::^${artists[i]}$" genre=${genre[i]}
1170 done
1171 ;;
1172 esac
1173 fi
1174 rm $tmpf
1175 }
1176
1177 # note, to check for glue records
1178 # First, find some the .org nameservers:
1179 # dig +trace iankelling.org
1180 # then, query one:
1181 # dig ns1.iankelling.org @b0.org.afilias-nst.org.
1182
1183 # Now, compare for a domain that does have glue records setup (note the A
1184 # and AAAA records in ADDITIONAL SECTION, those are glue records like the
1185 # one I'm asking for):
1186
1187 # $ dig ns1.gnu.org @b0.org.afilias-nst.org.
1188
1189 # todo: make sm pull/push use systemd instead of the journal cat command
1190 bbk() { # btrbk wrapper
1191 local ret=0
1192 c /
1193 local active=true
1194 systemctl is-active btrbk.timer || active=false
1195 if $active; then
1196 ser stop btrbk.timer
1197 fi
1198 btrbk_is_active=$(systemctl is-active btrbk.service ||:)
1199 case $btrbk_is_active in
1200 inactive|failed) : ;;
1201 *)
1202 echo "bbk: error: systemctl is-active btrbk.service output: $btrbk_is_active"
1203 if $active; then ser start btrbk.timer; fi
1204 return 1
1205 ;;
1206 esac
1207 # run latest
1208 install-my-scripts
1209 # todo: consider changing this to srun and having the args come
1210 # from a file like /etc/default/btrbk, like is done in exim
1211 s jdo btrbk-run "$@"
1212 if $active; then
1213 if (( ret )); then
1214 echo bbk: WARNING: btrbk.timer not restarted due to failure
1215 else
1216 ser start btrbk.timer
1217 fi
1218 fi
1219 return $ret
1220 }
1221
1222 faimon() {
1223 fai-monitor | pee cat "fai-monitor-gui -"
1224 }
1225
1226 bfg() { java -jar /a/opt/bfg-1.12.14.jar "$@"; }
1227
1228 bigclock() {
1229 xclock -digital -update 1 -face 'arial black-80:bold'
1230 }
1231
1232 nnn() { /a/opt/nnn -H "$@"; }
1233
1234 locat() { # log-once cat
1235 local files
1236 ngset
1237 files=(/var/local/cron-errors/* /home/iank/cron-errors/* /sysd-mail-once-state/*)
1238 case ${#files[@]} in
1239 0) : ;;
1240 1)
1241 echo ${files[0]}
1242 head ${files[0]}
1243 ;;
1244 *)
1245 head ${files[@]}
1246 ;;
1247 esac
1248 ngreset
1249 }
1250
1251 scr() {
1252 screen -RD "$@"
1253 }
1254
1255 # usage: first get an adb shell on the phone.
1256 #
1257 # just followed instructions in readme at
1258 # https://github.com/Yuubi-san/ceb-tools
1259 # tried to use ceb2txt but it failed because of schema
1260 # slightly different than what it expected.
1261 cheogram-get-logs() {
1262 adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
1263 read -r -p "do cheogram backup on phone, do not enable extra cheogram data. press any key when done"
1264 cd /p/cheogram
1265 rm -rf Backup b
1266 adb pull /storage/emulated/0/Download/Cheogram/Backup
1267 sqlite3 b </a/opt/ceb-tools/schema.sql
1268 echo "note: the next step took 39 seconds last time i measured"
1269 /a/opt/ceb-tools/ceb2sqlgz Backup/iank@fsf.org.ceb <pas | gunzip | sqlite3 b
1270 rm -r Backup
1271 }
1272
1273 # usage: cheologs [DAYS_LIMIT]
1274 # default days is 100
1275 cheologs() {
1276 local days q
1277 days=${1:-100}
1278 q="
1279 select
1280 datetime(substr(timeSent,0,11), 'unixepoch'),
1281 replace(replace(counterpart,'@fsf.org',''),
1282 '@conference.fsf.org',''),
1283 body
1284 from messages
1285 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
1286 order by timeSent;"
1287 sqlite3 /p/cheogram/b ".mode tabs" "$q" | less
1288 }
1289
1290 mycheologs() {
1291 local days q
1292 days=${1:-16}
1293 q="
1294 select
1295 datetime(substr(timeSent,0,11), 'unixepoch'),
1296 body
1297 from messages
1298 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
1299 and counterpart = 'office@conference.fsf.org/iank'
1300 order by timeSent;"
1301 sqlite3 /p/cheogram/b ".mode tabs" "$q" | sed 's/ /./' | less
1302 }
1303
1304 # version of jdo for my non-root user
1305 jdo() {
1306 # comparison of alternative logging methods:
1307 #
1308 # systemd-run command (what this function does)
1309 #
1310 # If there is a user prompt, the program will detect that it is not
1311 # connected to a terminal and act in a non-interactive way, skipping
1312 # the prompt. This has the benefit that you know exactly how the
1313 # program will act if you want to move it into a service that runs
1314 # automatically.
1315 #
1316 # If run with sudo and command is a shell script which does a sleep,
1317 # it can (sometimes?) output some extra whitespace in front of
1318 # messages, more for each subsequent message. This can be avoided by
1319 # becoming root first.
1320 #
1321 # It logs the command's pid and exit code, which is nice.
1322 #
1323 #
1324 ### command |& ts | tee file.log
1325 #
1326 # If there is a user prompt, like "read -p prompt var", it will hang
1327 # without outputting the prompt.
1328 #
1329 # I've had a few times where ts had an error and I wasn't totally sure
1330 # if it was really the command or ts having the problem.
1331 #
1332 # Sometimes some output will get hidden until you hit enter.
1333 #
1334 #
1335 ### command |& pee cat logger
1336 #
1337 # This seems to work. I need to test more.
1338 #
1339 #
1340 ### command |& logger -s
1341 #
1342 # User prompts get confusingly prefixed to earlier output, and all log
1343 # entries get prefixed with annoying priority level.
1344 #
1345 #
1346 ### systemd-cat
1347 #
1348 # Had a few problems. One major one is that it exited in the middle of
1349 # a command on systemctl daemon-reload
1350 #
1351 # Related commands which can log a whole session: script, sudo, screen
1352 local cmd cmd_name jr_pid ret
1353 ret=0
1354 cmd="$1"
1355 shift
1356 cmd_name=${cmd##*/}
1357 if [[ $cmd != /* ]]; then
1358 cmd=$(type -P "$cmd")
1359 fi
1360 # -q = quiet
1361 journalctl -qn2 -f -u "$cmd_name" &
1362 # Trial and error of time needed to avoid missing initial lines.
1363 # .5 was not reliable. 1 was not reliable. 2 was not reliable
1364 sleep 4
1365 jr_pid=$!
1366 # note, we could have a version that does system --user, but if for example
1367 # it does sudo ssh, that will leave a process around that we can't kill
1368 # and it will leave the unit hanging around in a failed state needing manual
1369 # killing of the process.
1370 s systemd-run --uid "$(id -u)" --gid "$(id -g)" \
1371 -E SSH_AUTH_SOCK=/run/openssh_agent \
1372 --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
1373 # The sleep lets the journal output its last line
1374 # before the prompt comes up.
1375 sleep .5
1376 kill $jr_pid &>/dev/null ||:
1377 unset jr_pid
1378 fg &>/dev/null ||:
1379 # this avoids any err-catch
1380 (( ret == 0 )) || return $ret
1381 }
1382
1383 # service run, and watch the output
1384 srun() {
1385 local unit
1386 ret=0
1387 unit=$1
1388 journalctl -qn2 -f -u $unit &
1389 systemctl start $unit
1390 sleep 2
1391 kill $jr_pid &>/dev/null ||:
1392 unset jr_pid
1393 fg &>/dev/null ||:
1394 }
1395
1396 sm() { # switch mail host
1397 local tmp keyhash
1398 c /
1399 # run latest
1400 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
1401 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
1402 if [[ ! $tmp ]]; then
1403 s ssh-add /root/.ssh/home
1404 fi
1405 install-my-scripts
1406 s jdo switch-mail-host "$@"
1407 return $ret
1408 }
1409 sh2() { # switch host2
1410 local tmp keyhash
1411 c /
1412 # run latest
1413 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
1414 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
1415 if [[ ! $tmp ]]; then
1416 s ssh-add /root/.ssh/home
1417 fi
1418 install-my-scripts
1419 s jdo switch-host2 "$@"
1420 return $ret
1421 }
1422
1423 # shellcheck disable=SC2120
1424 lipush() {
1425 # note, i had --delete-excluded, but that deletes all files in --exclude-from on
1426 # the remote site, which doesn't make sense, so not sure why i had it.
1427 local p a
1428 # excluding emacs for now
1429 #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1430 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1431 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1432 ret=0
1433 for h in li je bk; do
1434 m s rsync "$@" $a ${p[@]} /p/c/machine_specific/$h root@$h.b8.nz:/
1435 ## only li is debian11
1436 #p[0]=/a/opt/emacs-trisuqel10
1437 #p[1]=/a/opt/emacs-trisquel10-nox
1438 done
1439 m s rsync "$@" -ahviSAXPH root@li.b8.nz:/a/h/proposed-comments/ /a/h/proposed-comments || ret=$?
1440 return $ret
1441 }
1442 bkpush() { # no emacs. for running faster.
1443 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1444 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1445 ret=0
1446 m rsync "$@" $a ${p[@]} /p/c/machine_specific/bk root@bk.b8.nz:/ || ret=$?
1447 return $ret
1448 }
1449 jepush() { # no emacs. for running faster.
1450 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1451 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1452 ret=0
1453 m rsync "$@" $a ${p[@]} /p/c/machine_specific/je root@je.b8.nz:/ || ret=$?
1454 return $ret
1455 }
1456
1457 bindpush() {
1458 dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
1459 lipush
1460 for h in li bk; do
1461 m sl $h <<'EOF'
1462 source ~/.bashrc
1463 m dnsup
1464 EOF
1465 done
1466 }
1467 bindpushb8() {
1468 lipush
1469 for h in li bk; do
1470 m sl $h <<'EOF'
1471 source ~/.bashrc
1472 m dnsb8
1473 EOF
1474 done
1475 }
1476
1477 dnsup() {
1478 conflink -f
1479 m ser reload named
1480 }
1481 dnsb8() {
1482 local f=/var/lib/bind/db.b8.nz
1483 m ser stop named
1484 m sleep 1
1485 m sudo rm -fv $f.jnl $f.signed.jnl
1486 m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f
1487 m ser restart named
1488 }
1489 dnsecgen() {
1490 # keys generated like this
1491 # because of https://ftp.isc.org/isc/dnssec-guide/dnssec-guide.pdf
1492 # https://blog.apnic.net/2019/05/23/how-to-deploying-dnssec-with-bind-and-ubuntu-server/
1493
1494 # key length is longer than that guide because
1495 # we are using those at fsf and when old key lengths
1496 # become insecure, I want some extra time to update.
1497 # dnsecgen (in brc2)
1498
1499 local zone=$1
1500 dnssec-keygen -a RSASHA256 -b 2048 $zone
1501 dnssec-keygen -f KSK -a RSASHA256 -b 4096 $zone
1502 for f in K"$zone".*.key; do
1503 # eg Kb8.nz.+008+47995.key tag=47995
1504 # in dnsimple, you add the long string from this.
1505 # in gandi, you add the long string from the .key file,
1506 # then see that the digest matches the ds.
1507 echo "tag is the number after DS"
1508 dnssec-dsfromkey -a SHA-256 $f
1509 done
1510 # For b8.nz, we let bind read the keys and sign, and
1511 # right now they have root ownership, so let them
1512 # get group read.
1513 chmod g+r ./*.private
1514 }
1515 dsign() {
1516 # create .signed file
1517 # note: full paths probably not needed.
1518 local arg
1519 for arg; do
1520 local zone=${arg#db.}
1521 local dir=/p/c/machine_specific/vps/filesystem/var/lib/bind
1522 dnssec-signzone -S -e +31536000 -o $zone -K $dir -d $dir $dir/db.$zone
1523 done
1524 }
1525
1526
1527 #### begin bitcoin related things
1528 btc() {
1529 local f=/etc/bitcoin/bitcoin.conf
1530 # importprivkey will timeout if using the default of 15 mins.
1531 # upped it to 1 hour.
1532 bitcoin-cli -rpcclienttimeout=60000 -"$(s grep rpcuser= $f)" -"$(s grep rpcpassword= $f)" "$@"
1533 }
1534 btcusd() { # $1 btc in usd
1535 local price
1536 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1537 printf "$%s\n" "$price"
1538 if [[ $1 ]]; then
1539 printf "$%.2f\n" "$(echo "scale=4; $price * $1"| bc -l)"
1540 fi
1541 }
1542 usdbtc() { # $1 usd in btc
1543 local price
1544 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1545 printf "$%s\n" "$price"
1546 if [[ $1 ]]; then
1547 # 100 mil satoshi / btc. 8 digits after the 1.
1548 printf "%.8f btc\n" "$(echo "scale=10; $1 / $price "| bc -l)"
1549 fi
1550 }
1551 satoshi() { # $1 satoshi in usd
1552 local price
1553 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1554 price=$(echo "scale=10; $price * 0.00000001"| bc -l)
1555 printf "$%f\n" "$price"
1556 if [[ $1 ]]; then
1557 printf "$%.2f\n" "$(echo "scale=10; $price * $1"| bc -l)"
1558 fi
1559 }
1560 #### end bitcoin related things
1561
1562
1563
1564 cbfstool () { /a/opt/coreboot/build/cbfstool "$@"; }
1565
1566
1567 cgpl()
1568 {
1569 if (($#)); then
1570 cp /a/bin/data/COPYING "$@"
1571 else
1572 cp /a/bin/data/COPYING .
1573 fi
1574 }
1575
1576 capache()
1577 {
1578 if (($#)); then
1579 cp /a/bin/data/LICENSE "$@"
1580 else
1581 cp /a/bin/data/LICENSE .
1582 fi
1583 }
1584
1585 chrome() {
1586 if type -p chromium &>/dev/null; then
1587 cmd=chromium
1588 else
1589 cd /
1590 cmd="schroot -c bullseye chromium"
1591 CHROMIUM_FLAGS='--enable-remote-extensions' $cmd & r
1592 fi
1593 }
1594
1595
1596 # do all tee.
1597 # pipe to this, or just type like a shell
1598 # todo: test this
1599 dat() {
1600 tee >(ssh frodo.b8.nz) >(ssh x2) >(ssh tp.b8.nz) >(ssh kw) >(ssh tp.b8.nz)
1601 }
1602 da() { # do all
1603 local host
1604 for host in x2 kw tp.b8.nz x3.b8.nz frodo.b8.nz; do
1605 ssh $host "$@"
1606 done
1607 }
1608
1609
1610 debian_pick_mirror () {
1611 # netselect-apt finds a fast mirror.
1612 # but we need to replace the mirrors ourselves,
1613 # because it doesnt do that. best it can do is
1614 # output a basic sources file
1615 # here we get the server it found, get the main server we use
1616 # then substitute all instances of one for the other in the sources file
1617 # and backup original to /etc/apt/sources.list-original.
1618 # this is idempotent. the only way to identify debian sources is to
1619 # note the original server, so we put it in a comment so we can
1620 # identify it later.
1621 local file
1622 file=$(mktemp -d)/f # safe way to get file name without creating one
1623 sudo netselect-apt -o "$file" || return 1
1624 url=$(grep ^\\w $file | head -n1 | awk '{print $2}')
1625 sudo cp -f /etc/apt/sources.list /etc/apt/sources.list-original
1626 sudo sed -ri "/http.us.debian.org/ s@( *[^ #]+ +)[^ ]+([^#]+).*@\1$url\2# http.us.debian.org@" /etc/apt/sources.list
1627 sudo apt-get update
1628 }
1629 digme() {
1630 digdiff @ns{1,2}.iankelling.org "$@"
1631 }
1632
1633 tsr() { # ts run
1634 "$@" |& ts || return $?
1635 }
1636
1637 dup() {
1638 local ran_d
1639 ran_d=false
1640 system-status _
1641 case $PS1 in
1642 *[\ \]]D\ *)
1643 pushd /
1644 /b/ds/distro-begin |& ts || return $?
1645 /b/ds/distro-end |& ts || return $?
1646 popd
1647 ran_d=true
1648 ;;&
1649 *[\ \]]DB\ *)
1650 pushd /
1651 /b/ds/distro-begin |& ts || return $?
1652 popd
1653 ran_d=true
1654 ;;
1655 *[\ \]]DE\ *)
1656 pushd /
1657 /b/ds/distro-end |& ts || return $?
1658 popd
1659 ran_d=true
1660 ;;&
1661 *CONFLINK*)
1662 if ! $ran_d; then
1663 conflink
1664 fi
1665 ;;
1666 esac
1667 system-status _
1668 }
1669
1670 envload() { # load environment from a previous: export > file
1671 local file=${1:-$HOME/.${USER}_env}
1672 eval "$(export | sed 's/^declare -x/export -n/')"
1673 while IFS= read -r line; do
1674 # declare -x makes variables local to a function
1675 eval ${line/#declare -x/export}
1676 done < "$file"
1677 }
1678
1679 failfunc() { asdf a b c; }
1680 failfunc2() { failfunc d e f; }
1681
1682 # one that comes with distros is too old for newer devices
1683 fastboot() {
1684 /a/opt/android-platform-tools/fastboot "$@";
1685 }
1686
1687 kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
1688
1689 bat() {
1690 cat /sys/class/power_supply/BAT0/capacity
1691 }
1692
1693 # List of apps to install/update
1694 # Create from existing manually installed apps by doing
1695 # fdroidcl update
1696 # fdroidcl search -i, then manually removing
1697 # automatically installed/preinstalled apps
1698
1699 #
1700 # # my attempt at recovering from boot loop:
1701 # # in that case, boot to recovery (volume up, home button, power, let go of power after samsun logo)
1702 # # then
1703 # mount /dev/block/mmcblk0p12 /data
1704 # cd /data
1705 # find -iname '*appname*'
1706 # rm -rf FOUND_DIRS
1707 # usually good enough to just rm -rf /data/app/APPNAME
1708 #
1709 # currently broken:
1710 # # causes replicant to crash
1711 # org.quantumbadger.redreader
1712 # org.kde.kdeconnect_tp
1713
1714 # not broke, but wont work without gps
1715 #com.zoffcc.applications.zanavi
1716 # not broke, but not using atm
1717 #com.nutomic.syncthingandroid
1718 # # doesn\'t work on replicant
1719 #net.sourceforge.opencamera
1720 #
1721 fdroid_pkgs=(
1722 net.mullvad.mullvadvpn
1723 org.schabi.newpipe
1724 io.github.subhamtyagi.lastlauncher
1725 io.anuke.mindustry
1726 com.biglybt.android.client
1727 de.marmaro.krt.ffupdater
1728 me.ccrama.redditslide
1729 org.fedorahosted.freeotp
1730 at.bitfire.davdroid
1731 com.alaskalinuxuser.justnotes
1732 com.artifex.mupdf.viewer.app
1733 com.danielkim.soundrecorder
1734 com.fsck.k9
1735 com.ichi2.anki
1736 com.jmstudios.redmoon
1737 com.jmstudios.chibe
1738 org.kde.kdeconnect_tp
1739 com.notecryptpro
1740 com.termux
1741 cz.martykan.forecastie
1742 de.danoeh.antennapod
1743 de.blinkt.openvpn
1744 de.marmaro.krt.ffupdater
1745 eu.siacs.conversations
1746 free.rm.skytube.oss
1747 im.vector.alpha # riot
1748 info.papdt.blackblub
1749 me.tripsit.tripmobile
1750 net.gaast.giggity
1751 net.minetest.minetest
1752 net.osmand.plus
1753 org.isoron.uhabits
1754 org.linphone
1755 org.gnu.icecat
1756 org.smssecure.smssecure
1757 org.yaaic
1758 sh.ftp.rocketninelabs.meditationassistant.opensource
1759 )
1760 # https://forum.xda-developers.com/android/software-hacking/wip-selinux-capable-superuser-t3216394
1761 # for maru,
1762 #me.phh.superuser
1763
1764 fdup() {
1765 local -A installed updated
1766 local p
1767 # tried putting this in go buildscript cronjob,
1768 # but it failed with undefined: os.UserCacheDir. I expect its due to
1769 # an environment variable missing, but its easier just to stick it here.
1770 m go get -u mvdan.cc/fdroidcl || return 1
1771 m fdroidcl update
1772 if fdroidcl search -u | grep ^org.fdroid.fdroid; then
1773 fdroidcl install org.fdroid.fdroid
1774 sleep 5
1775 m fdroidcl update
1776 fi
1777 for p in $(fdroidcl search -i| grep -o "^\S\+"); do
1778 installed[$p]=true
1779 done
1780 for p in $(fdroidcl search -u| grep -o "^\S\+"); do
1781 updated[$p]=false
1782 done
1783 for p in ${fdroid_pkgs[@]}; do
1784 if ! ${installed[$p]:-false}; then
1785 m fdroidcl install $p
1786 # sleeps are just me being paranoid since replicant has a history of crashing when certain apps are installed
1787 sleep 5
1788 fi
1789 done
1790 for p in ${!installed[@]}; do
1791 if ! ${updated[$p]:-true}; then
1792 m fdroidcl install $p
1793 sleep 5
1794 fi
1795 done
1796 }
1797
1798 firefox-default-profile() {
1799 local key value section
1800 key=Default
1801 value=1
1802 section=$1
1803 file=/p/c/subdir_files/.mozilla/firefox/profiles.ini
1804 sed -ri "/^ *$key/d" "$file"
1805 sed -ri "/ *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d};/ *\[$section\]/a $key=$value" "$file"
1806 }
1807 fdhome() { #firefox default home profile
1808 firefox-default-profile Profile0
1809 }
1810
1811 fdwork() {
1812 firefox-default-profile Profile4
1813 }
1814
1815 ff() {
1816 if type -P firefox &>/dev/null; then
1817 firefox "$@"
1818 else
1819 iceweasel "$@"
1820 fi
1821 }
1822
1823 fn() {
1824 firefox -P alt "$@" >/dev/null 2>&1
1825 }
1826
1827
1828 fsdiff () {
1829 local missing=false
1830 local dname="${PWD##*/}"
1831 local m="/a/tmp/$dname-missing"
1832 local d="/a/tmp/$dname-diff"
1833 [[ -e $d ]] && rm "$d"
1834 [[ -e $m ]] && rm "$m"
1835 local msize=0
1836 local fsfile
1837 while read -r line; do
1838 fsfile="$1${line#.}"
1839 if [[ -e "$fsfile" ]]; then
1840 md5diff "$line" "$fsfile" && tee -a "/a/tmp/$dname-diff" <<< "$fsfile $line"
1841 else
1842 missing=true
1843 echo "$line" >> "$m"
1844 msize=$((msize + 1))
1845 fi
1846 done < <(find . -type f )
1847 if $missing; then
1848 echo "$m"
1849 (( msize <= 100 )) && cat $m
1850 fi
1851 }
1852 fsdiff-test() {
1853 local tmpd x
1854 # expected output, with different tmp dirs
1855 # /tmp/tmp.HDPbwMqdC9/c/d ./c/d
1856 # /a/tmp/tmp.qLDkYxBYPM-missing
1857 # ./b
1858 tmpd="$(mktemp -d)"
1859 cd "$tmpd"
1860 echo ok > a
1861 echo nok > b
1862 mkdir c
1863 echo ok > c/d
1864 local x
1865 x=$(mktemp -d)
1866 mkdir $x/c
1867 echo different > $x/c/d
1868 echo ok > $x/a
1869 fsdiff $x
1870 rm -r "$x" "$tmpd"
1871 }
1872 rename-test() {
1873 # test whether missing files were renamed, generally for use with fsdiff
1874 # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir
1875 # echos non-renamed files
1876 local x y found
1877 unset sums
1878 for x in "$2"/*; do
1879 { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null
1880 done
1881 while read -r line; do
1882 { missing_sum=$(md5sum < "$line") ; } 2>/dev/null
1883 renamed=false
1884 for x in "${sums[@]}"; do
1885 if [[ $missing_sum == "$x" ]]; then
1886 renamed=true
1887 break
1888 fi
1889 done
1890 $renamed || echo "$line"
1891 done < "$1"
1892 return 0
1893 }
1894
1895 feh() {
1896 # F = fullscren, z = random, Z = auto zoom
1897 command feh -FzZ "$@"
1898 }
1899
1900
1901
1902 fw() {
1903 firefox -P default "$@" >/dev/null 2>&1
1904 }
1905
1906 gitian() {
1907 git config user.email ian@iankelling.org
1908 }
1909
1910 # at least in flidas, things rely on gpg being gpg1
1911 gpg() {
1912 if type -P gpg2 &>/dev/null; then
1913 command gpg2 "$@"
1914 else
1915 command gpg "$@"
1916 fi
1917 }
1918
1919 gse() {
1920 local email=ian@iankelling.org
1921 git send-email --notes "--envelope-sender=<$email>" \
1922 --suppress-cc=self "$@"
1923 }
1924
1925 gup() { /a/f/gnulib/build-aux/gnupload "$@"; }
1926
1927 dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
1928
1929 hstatus() {
1930 # do git status on published repos.
1931 c /a/bin/githtml
1932 for x in *; do
1933 cd "$(readlink -f $x)"/..
1934 status=$(i status -s) || pwd
1935 if [[ $status ]]; then
1936 hr
1937 echo $x
1938 printf "%s\n" "$status"
1939 fi
1940 cd /a/bin/githtml
1941 done
1942 }
1943
1944 # work log
1945 wlog() {
1946 local day i days_back
1947 days_back=${1:-16}
1948 for (( i=0; i<days_back; i++ )); do
1949 day=$( date +%F -d @$((EPOCHSECONDS - 86400*i )) )
1950 date "+%a %b %d" -d @$((EPOCHSECONDS - 86400*i )) | tr '\n' ' '
1951 /a/opt/timetrap/bin/t d -ftotal -s $day -e $day all -m '^w|lunch$'
1952 done
1953 }
1954 to() { t out -a "$@"; }
1955 ti() { t in -a "$@"; }
1956 tl() {
1957 local in_secs
1958 to "$*"
1959 t s lunch
1960 t in -a "$*"
1961 in_secs="$(date -d "${*//[_.]/ }" +%s)"
1962 m t out -a "$(date +%F.%T -d @$(( in_secs + 60*45 )) )"
1963 t s w
1964 }
1965
1966 arbttlog() { arbtt-dump "$@" | grep -v '( )\|Current Desktop' | sed -rn '/^[^ ]/{N;s/^(.{21})([0-9]*)[0-9]{3}m.*\(\*/\1\2/;s/^(.{21})[0-9]*.*\(\*/\1/;s/\n//;p}' ; }
1967
1968 idea() {
1969 /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
1970 }
1971
1972 ilogs-local() {
1973 cd /var/lib/znc/moddata/log/iank/freenode/
1974 hr
1975 for x in "#$1/"*; do
1976 base=${x##*/}
1977 files=()
1978 for f in $@; do
1979 tmp=\#$f/$base
1980 if [[ -e $tmp ]]; then
1981 files+=(\#$f/$base)
1982 fi
1983 done
1984 sed \"s/^./${base%log}/\" ${files[@]}|sort -n
1985 hr
1986 done
1987 }
1988 ilogs() {
1989 sl root@iankelling.org ilogs-local "$@"
1990 }
1991
1992
1993 ilog-local() {
1994 local d chan
1995 chan="$1"
1996 d=/var/lib/znc/moddata/log/iank/
1997 for n in freenode libera; do
1998 cd $d$n/"$chan" && hr
1999 for x in *; do
2000 echo $x; sed "s/^./${x%log}/" $x; hr;
2001 done
2002 done
2003 }
2004 ilog() {
2005 local chan
2006 chan="${1:-#fsfsys}"
2007 # use * instead of -r since that does sorted order
2008 sl root@iankelling.org ilog-local "$chan" | less +G
2009 }
2010
2011 o() {
2012 if type gio &> /dev/null ; then
2013 gio open "$@"
2014 elif type gvfs-open &> /dev/null ; then
2015 gvfs-open "$@"
2016 else
2017 xdg-open "$@"
2018 fi
2019 # another alternative is run-mailcap
2020 }
2021 ccomp xdg-open o
2022
2023 # jfilter() {
2024 # grep -Evi -e "^(\S+\s+){4}(sudo|sshd|cron)\[\S*:" \
2025 # -e "^(\S+\s+){4}systemd\[\S*: (starting|started) (btrfsmaintstop|dynamicipupdate|spamd dns bug fix cronjob|rss2email)\.*$"
2026 # }
2027 # jtail() {
2028 # journalctl -n 10000 -f "$@" | jfilter
2029 # }
2030 # jr() { journalctl "$@" | jfilter | less ; }
2031 # jrf() { journalctl -n 200 -f "$@" | jfilter; }
2032
2033 jr() { journalctl "$@" ; }
2034 jrf() { journalctl -n 200 -f "$@" ; }
2035
2036
2037 ccomp journalctl jtail jr jrf
2038
2039 ## old version for model01. i need to get that firmware working again.
2040 # kff() { # keyboardio firmware flash. you must hold down the tilde key
2041 # pushd /a/opt/Model01-Firmware
2042 # # if we didn't want this yes hack, then remove "shell read" from
2043 # # /a/opt/Kaleidoscope/etc/makefiles/sketch.mk
2044 # yes $'\n' | VERBOSE=1 make flash
2045 # popd
2046 # }
2047
2048
2049 kff() {
2050 pushd /a/opt/Kaleidoscope/examples/Devices/Keyboardio/Model100
2051 make flash
2052 popd
2053 }
2054
2055 wgkey() {
2056 local umask_orig name
2057 if (( $# != 1 )); then
2058 e expected 1 arg >&2
2059 return 1
2060 fi
2061 name=$1
2062 umask_orig=$(umask)
2063 umask 0077
2064 wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key
2065 umask $umask_orig
2066 }
2067 wghole() {
2068 if (( $# != 2 )); then
2069 e expected 2 arg of hostname, ip suffix >&2
2070 return 1
2071 fi
2072 local host ipsuf umask_orig
2073 host=$1
2074 ipsuf=$2
2075 mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
2076 (
2077 cd /p/c/machine_specific/$host/filesystem/etc/wireguard
2078 umask_orig=$(umask)
2079 umask 0077
2080 wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
2081 cat >wghole.conf <<EOF
2082 [Interface]
2083 # contents hole-priv.key
2084 PrivateKey = $(cat hole-priv.key)
2085 ListenPort = 1194
2086 Address = 10.8.0.$ipsuf/24
2087 # https://dev.to/tangramvision/what-they-don-t-tell-you-about-setting-up-a-wireguard-vpn-1h2g
2088 # ||: makes the systemd service not fail due to the failed command
2089 PostUp = ping -c1 10.8.0.1 ||:
2090
2091 [Peer]
2092 # li. called wgmail on that server
2093 PublicKey = CTFsje45qLAU44AbX71Vo+xFJ6rt7Cu6+vdMGyWjBjU=
2094 AllowedIPs = 10.8.0.0/24
2095 Endpoint = 72.14.176.105:1194
2096 PersistentKeepalive = 25
2097 EOF
2098 umask $umask_orig
2099 # old approach. systemd seems to work fine and cleaner.
2100 rm -f ../network/interfaces.d/wghole
2101 cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
2102 [Peer]
2103 PublicKey = $(cat hole-pub.key)
2104 AllowedIPs = 10.8.0.$ipsuf/32
2105 EOF
2106 )
2107 }
2108
2109
2110 mns() { # mount namespace
2111 ns=$1
2112 shift
2113 s mkdir -p /root/mount_namespaces
2114 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
2115 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
2116 fi
2117 m sudo mount --make-private /root/mount_namespaces
2118 if [[ ! -e /root/mount_namespaces/$ns ]]; then
2119 m sudo touch /root/mount_namespaces/$ns
2120 fi
2121 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
2122 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
2123 fi
2124 m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
2125 }
2126
2127 mnsr() { # mns run
2128 local ns=$1
2129 shift
2130 mns $ns sudo -u iank -E env "PATH=$PATH" "$@"
2131 }
2132
2133 mnsnonet() {
2134 ns=$1
2135 lomh
2136 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2137 s ip netns add nonet
2138 fi
2139 mns $ns --net=/var/run/netns/nonet sudo -E -u iank /bin/bash
2140 lomh
2141 }
2142
2143
2144 lom() {
2145 # l = the loopback device
2146 local l base
2147 if [[ $1 == /* ]]; then
2148 base=${1##*/}
2149 fs_file=$1
2150 if mns $base mountpoint -q /mnt/$base; then
2151 return 0
2152 fi
2153 l=$(losetup -j $fs_file | sed -rn 's/^([^ ]+): .*/\1/p' | head -n1 ||:)
2154 if [[ ! $l ]]; then
2155 l=$(sudo losetup -f)
2156 m sudo losetup $l $fs_file
2157 fi
2158 if ! sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2159 if ! m sudo cryptsetup luksOpen $l $base; then
2160 m sudo losetup -d $l
2161 return 1
2162 fi
2163 fi
2164 m sudo mkdir -p /mnt/$base
2165 m mns $base mount /dev/mapper/$base /mnt/$base
2166 m mns $base chown $USER:$USER /mnt/$base
2167 lomh
2168 else
2169 base=$1
2170 if mns $base mountpoint /mnt/$base &>/dev/null; then
2171 m mns $base umount /mnt/$base
2172 fi
2173 if sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2174 if ! m sudo cryptsetup luksClose /dev/mapper/$base; then
2175 echo lom: failed cryptsetup luksClose /dev/mapper/$base
2176 return 1
2177 fi
2178 fi
2179 l=$(losetup -l --noheadings | awk '$6 ~ /\/'$base'$/ {print $1}')
2180 if [[ $l ]]; then
2181 m sudo losetup -d $l
2182 else
2183 echo lom: warning: no loopback device found
2184 fi
2185 fi
2186 }
2187
2188 # mu personality. for original, just run mp. for 2, run mp 2.
2189 # this is partly duplicated in mail-setup
2190 mp() {
2191 local dead=false
2192 for s in {1..5}; do
2193 if ! killall mu; then
2194 dead=true
2195 break
2196 fi
2197 sleep 1
2198 done
2199 if ! $dead; then
2200 echo error: mu not dead
2201 m psg mu
2202 return 1
2203 fi
2204 suf=$1
2205 set -- /m/mucache ~/.cache/mu /m/.mu ~/.config/mu
2206 while (($#)); do
2207 target=$1$suf
2208 f=$2
2209 shift 2
2210 if [[ -e $f && ! -L $f ]]; then
2211 m rm -rf $f
2212 fi
2213 m ln -sf -T $target $f
2214 done
2215 }
2216
2217 # maildir enable
2218 mdenable() {
2219 local md dst ln_path src two
2220
2221 two=false
2222 case $1 in
2223 -2) two=true shift ;;
2224 esac
2225
2226 for md; do
2227 src=
2228 if $two; then
2229 dst=/m/4e2/$md
2230 else
2231 dst=/m/4e/$md
2232 fi
2233
2234 ln_path=/m/md/$md
2235 for d in /m/md/$md /m/4e2/$md; do
2236 if [[ -d $d && ! -L $d ]]; then
2237 src=$d
2238 break
2239 fi
2240 done
2241 if [[ ! $src ]]; then
2242 echo "error: could not find $md" >&2
2243 return 1
2244 fi
2245 m mv -T $src $dst
2246 m ln -sf -T $dst $ln_path
2247 done
2248 }
2249 md2enable() {
2250 mdenable -2 "$@"
2251 }
2252 mddisable() {
2253 local md=$1
2254 dst=/m/md/$md
2255
2256 ### begin copied from mdenable, but different d ###
2257 for d in /m/4e/$md /m/4e2/$md; do
2258 if [[ -d $d && ! -L $d ]]; then
2259 src=$d
2260 break
2261 fi
2262 done
2263 if [[ ! $src ]]; then
2264 echo "error: could not find $md" >&2
2265 return 1
2266 fi
2267 ### end copy from mdenable ###
2268
2269 if [[ -L $dst ]]; then m rm $dst; fi
2270 m mv -T $src $dst
2271 }
2272
2273
2274 mdt() {
2275 markdown "$1" >/tmp/mdtest.html
2276 firefox /tmp/mdtest.html
2277 }
2278
2279 mo() { xset dpms force off; } # monitor off
2280
2281 mpvgpu() {
2282 # seems to be the best gpu decoding on my nvidia 670.
2283 # vlc gets similar or better framerate, but is much darker output on my test movie at least.
2284
2285
2286 case $HOSTNAME in
2287 kd)
2288 echo 0f | sudo tee -a /sys/kernel/debug/dri/0/pstate
2289 ;;
2290 esac
2291 # going back to the default slow clock, and slower fan:
2292 # echo 07 | sudo tee -a /sys/kernel/debug/dri/0/pstate
2293 if [[ $DISPLAY ]]; then
2294 mpv --vo=vdpau --hwdec=auto "$@"
2295 else
2296 # waylandvk seems to work the same
2297 mpv --gpu-context=wayland --hwdec=auto
2298 fi
2299 }
2300
2301 mpvd() {
2302 mpv --profile=d "$@";
2303 }
2304 # mpv all media files in . or $1
2305 mpvm() {
2306 local -a extensions arg
2307 # get page source of https://en.wikipedia.org/w/index.php?title=Video_file_format&action=edit
2308 # into /a/x.log, then
2309 # grep '^| *\.' /a/x.log | sed 's/| *//;s/,//g'
2310
2311 # note: to join them together for a regex, do:
2312 # old=; for e in ${extensions[@]/./}; do if [[ ! $old ]]; then old=$e; continue; fi; echo -n "$old|"; old=$e; done; echo $e
2313 extensions=(
2314 .webm
2315 .mkv
2316 .flv
2317 .flv
2318 .vob
2319 .ogv .ogg
2320 .drc
2321 .gif
2322 .gifv
2323 .mng
2324 .avi
2325 .MTS .M2TS .TS
2326 .mov .qt
2327 .wmv
2328 .yuv
2329 .rm
2330 .rmvb
2331 .viv
2332 .asf
2333 .amv
2334 .mp4 .m4p .m4v
2335 .mpg .mp2 .mpeg .mpe .mpv
2336 .mpg .mpeg .m2v
2337 .m4v
2338 .svi
2339 .3gp
2340 .3g2
2341 .mxf
2342 .roq
2343 .nsv
2344 )
2345 arg=("(" -iname "*${extensions[0]}")
2346 for (( i=1 ; i < ${#extensions[@]}; i++ )); do
2347 arg+=(-o -iname "*${extensions[i]}")
2348 done
2349 arg+=(")")
2350 dir=${1:-.}
2351 # debug:
2352 #find $dir "${arg[@]}" -size +200k
2353 find $dir "${arg[@]}" -size +200k -exec mpv --profile=d '{}' +
2354 }
2355 mpvs() {
2356 mpv --profile=s "$@";
2357 }
2358
2359 myirc() {
2360 if [[ ! $1 ]]; then
2361 set -- fsf-office
2362 fi
2363 local -a d
2364 d=( /var/lib/znc/moddata/log/iank/{freenode,libera} )
2365 # use * instead of -r since that does sorted order
2366 ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\<iank.*' *; done" | cut --complement -c12-16
2367 }
2368 mypidgin() {
2369 c /p/c/.purple/logs/jabber/iank@fsf.org/office@conference.fsf.org.chat
2370 for x in *.html; do html2text -o ${x%.html}.txt $x; done;
2371 # shellcheck disable=SC2016 # false positive on ${
2372 grep -A1 ') iank:' ./*.txt \
2373 | sed -r 's/^(.{10})[^ ]*\.txt:\(?([^ ]*)[[:space:]](..). iank:/\1_\2_\3/
2374 s/^[^ ]*\.txt-//
2375 /^--$/d
2376 s/^[^ ]*\.txt:\((.{2}).(.{2}).(.{4}) (.{8}) (.{2})\)?/\3-\1-\2_\4_\5/' \
2377 | sed -n 'x;1d;0~2{G;s/\n/ /;p};${x;p}'
2378 }
2379 allmyirc() {
2380 local d
2381 d=/var/lib/znc/moddata/log/iank/freenode
2382 ssh root@iankelling.org "cd $d; find . -mtime -60 -type f -exec grep '\<iank.*' {} +" | sed -r 's,^..([^/]*)/(.{11})(.{5})(.{8}).,\2\4 \1,' | sort
2383 }
2384
2385 # usage: debvm DEBIAN_VERSION RAM_MB
2386 debvm() {
2387 local ver ram fname src
2388 ver=$1
2389 ram=${2:-2024}
2390 # * is because it might have -backports in the name. we only expect 1 expansion
2391 fnames=( debian-$ver-*nocloud-"$(dpkg --print-architecture)".qcow2 )
2392 if (( ${#fnames[@]} >= 2 )); then
2393 echo "error: iank: unexpected multiple files"
2394 return 1
2395 fi
2396 fname="${fnames[0]}"
2397 src=/a/opt/roms/$fname
2398 if [[ ! -f $src ]]; then
2399 echo debvm: not found $src, download from eg: https://cloud.debian.org/images/cloud/buster/latest/
2400 return 1
2401 fi
2402 cp -a $src /t
2403 # note, in fai-revm we do this: not sure why, maybe because of br device
2404 # --graphics spice,listen=0.0.0.0
2405 m s virt-install --osinfo debian11 --rng /dev/urandom -n deb${ver}tmp --import -r $ram --vcpus 2 --disk /t/$fname --graphics spice
2406 # note: to ssh into this machine will require host key generation: ssh-keygen -A
2407
2408 # random: for cvs2git on gnu www, use debian 10. I could use trisquel,
2409 # but happen to want to try out the debian cloud images. the upstream
2410 # requires python2 and hasn't really changed since the version in d10.
2411 #
2412 # apt install cvs2git cvs
2413 # # 7G was not enough
2414 # mount -o mode=1777,nosuid,nodev,size=34G -t tmpfs tmpfs /tmp
2415 # cvs2git --encoding utf_8 --fallback-encoding ascii --dumpfile=dump www-rsync/www |& tee /tmp/l
2416 ## www-rsync is an rsynced copy of the cvsfrom savannah
2417 }
2418
2419 mygajim() {
2420 local time time_sec time_pretty days
2421 days=${1:-16}
2422 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
2423 case $time in
2424 16*) : ;;
2425 *) continue ;;
2426 esac
2427 if ! time_pretty=$(date +%F.%R -d @$time); then
2428 echo bad time: $time
2429 return 1
2430 fi
2431 echo $time_pretty "$l"
2432 time_sec=${time%%.*}
2433 # only look at the last 18 days. generally just use this for timesheet.
2434 if (( time_sec < EPOCHSECONDS - 60 * 60 * 24 * days )); then break; fi
2435 done
2436 }
2437
2438 allmygajim() {
2439 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank'" | less
2440 }
2441
2442 gajlogs() {
2443 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs" | less
2444 }
2445
2446
2447 net-dev-info() {
2448 e "lspci -nnk|gr -iA2 net"
2449 lspci -nnk|gr -iA2 net
2450 hr
2451 e "s lshw -C network"
2452 hr
2453 sudo lshw -C network
2454 }
2455
2456 nk() {
2457 ser stop NetworkManager
2458 ser disable NetworkManager
2459 ser stop NetworkManager-wait-online.service
2460 ser disable NetworkManager-wait-online.service
2461 ser stop dnsmasq
2462 sudo resolvconf -d NetworkManager
2463 # ser start dnsmasq
2464 sudo ifup br0
2465 }
2466 ngo() {
2467 sudo ifdown br0
2468 ser start NetworkManager
2469 sleep 4
2470 sudo nmtui-connect
2471 }
2472
2473 otp() {
2474 oathtool --totp -b "$*" | xclip -selection clipboard
2475 }
2476 j() {
2477 "$@" |& pee "xclip -r -selection clipboard"
2478 }
2479
2480
2481 pakaraoke() {
2482 # from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals
2483 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
2484 }
2485
2486 pfind() { #find *$1* in $PATH
2487 [[ $# != 1 ]] && { echo requires 1 argument; return 1; }
2488 local pathArray
2489 IFS=: pathArray=($PATH); unset IFS
2490 find "${pathArray[@]}" -iname "*$1*"
2491 }
2492
2493 pick-trash() {
2494 # trash-restore lists everything that has been trashed at or below CWD
2495 # This picks out files just in CWD, not subdirectories,
2496 # which also match grep $1, usually use $1 for a time string
2497 # which you get from running restore-trash once first
2498 local name x ask
2499 local nth=1
2500 # last condition is to not ask again for ones we skipped
2501 while name="$( echo | restore-trash | gr "$PWD/[^/]\+$" | gr "$1" )" \
2502 && [[ $name ]] && (( $(wc -l <<<"$name") >= nth )); do
2503 name="$(echo "$name" | head -n $nth | tail -n 1 )"
2504 read -r -p "$name [Y/n] " ask
2505 if [[ ! $ask || $ask == [Yy] ]]; then
2506 x=$( echo "$name" | gr -o "^\s*[0-9]*" )
2507 echo $x | restore-trash > /dev/null
2508 elif [[ $ask == [Nn] ]]; then
2509 nth=$((nth+1))
2510 else
2511 return
2512 fi
2513 done
2514 }
2515
2516
2517 pub() {
2518 rld /a/h/_site/ li:/var/www/iankelling.org/html
2519 }
2520
2521
2522 pumpa() {
2523 # fixes the menu bar in xmonad. this won\'t be needed when xmonad
2524 # packages catches up on some changes in future (this is written in
2525 # 4/2017)
2526 #
2527 # geekosaur: so youll want to upgrade to xmonad 0.13 or else use a
2528 # locally modified XMonad.Hooks.ManageDocks that doesnt set the
2529 # work area; turns out it\'s impossible to set correctly if you are
2530 # not a fully EWMH compliant desktop environment
2531 #
2532 # geekosaur: chrome shows one failure mode, qt/kde another, other
2533 # gtk apps a third, ... I came up with a setting that works for me
2534 # locally but apparently doesnt work for others, so we joined the
2535 # other tiling window managers in giving up on setting it at all
2536 #
2537 xprop -root -remove _NET_WORKAREA
2538 command pumpa & r
2539 }
2540
2541 # reviewboard, used at my old job
2542 #rbpipe() { rbt post -o --diff-filename=- "$@"; }
2543 #rbp() { rbt post -o "$@"; }
2544
2545 rebr() {
2546 sudo ifdown br0
2547 sudo ifup br0
2548 }
2549
2550
2551 r2e() { command r2e -d /p/c/rss2email.json -c /p/c/rss2email.cfg "$@"; }
2552 # only run on MAIL_HOST. simpler to keep this on one system.
2553 r2eadd() { # usage: name url
2554 # initial setup of rss2email:
2555 # r2e new r2e@iankelling.org
2556 # that initializes files, and sets default email.
2557 # symlink to the config doesnt work, so I copied it to /p/c
2558 # and then use cli option to specify explicit path.
2559 # Only option changed from default config is to set
2560 # force-from = True
2561 #
2562 # or else for a few feeds, the from address is set by the feed, and
2563 # if I fail delivery, then I send a bounce message to that from
2564 # address, which makes me be a spammer.
2565
2566 r2e add $1 "$2" $1@r2e.iankelling.org
2567 # get up to date and dont send old entries now:
2568 r2e run --no-send $1
2569 }
2570
2571 rspicy() { # usage: HOST DOMAIN
2572 # connect to spice vm remote host. use vspicy for local host
2573 local port
2574 # shellcheck disable=SC2087
2575 port=$(ssh $1<<EOF
2576 sudo virsh dumpxml $2|grep "<graphics.*type='spice'" | \
2577 sed -rn "s/.*port='([0-9]+).*/\1/p"
2578 EOF
2579 )
2580 if [[ $port ]]; then
2581 spicy -h $1 -p $port
2582 else
2583 echo "error: no port found. check that the domain is running."
2584 fi
2585 }
2586
2587
2588 scssl() {
2589 # s gem install scss-lint
2590 pushd /a/opt/thoughtbot-guides
2591 git pull --stat
2592 popd
2593 scss-lint -c /a/opt/thoughtbot-guides/style/sass/.scss-lint.yml "$@"
2594 }
2595
2596 skbrc() {
2597 sk -e 2120,245 /b/ds/brc /b/ds/brc2
2598 }
2599
2600 skaraoke() {
2601 local tmp out
2602 out=${2:-${1%.*}.sh}
2603 tmp=$(mktemp -d)
2604 script -t -c "mpv --no-config --no-resume-playback --no-terminal --no-audio-display '$1'" $tmp/typescript 2>$tmp/timing
2605 # todo, the current sleep seems pretty good, but it
2606 # would be nice to have an empirical measurement, or
2607 # some better wait to sync up.
2608 #
2609 # note: --loop-file=no prevents it from hanging if you have that
2610 # set to inf the mpv config.
2611 # --loop=no prevents it from exit code 3 due to stdin if you
2612 # had it set to inf in mpv config.
2613 #
2614 # args go to mpv, for example --volume=80, 50%
2615 cat >$out <<EOFOUTER
2616 #!/bin/bash
2617 trap "trap - TERM && kill 0" INT TERM ERR; set -e
2618 ( sleep .2; scriptreplay <( cat <<'EOF'
2619 $(cat $tmp/timing)
2620 EOF
2621 ) <( cat <<'EOF'
2622 $(cat $tmp/typescript)
2623 EOF
2624 ))&
2625 base64 -d - <<'EOF'| mpv --loop=no --loop-file=no --no-terminal --no-audio-display "\$@" -
2626 $(base64 "$1")
2627 EOF
2628 kill 0
2629 EOFOUTER
2630 rm -r $tmp
2631 chmod +x $out
2632 }
2633
2634 smeld() { # ssh meld usage host1 host2 file
2635 meld <(ssh $1 cat $3) <(ssh $2 cat $3)
2636 }
2637
2638 spd() {
2639 PATH=/usr/local/spdhackfix:$PATH command spd "$@"
2640 }
2641
2642 spamf() { # spamtest on FILE
2643 local spamcpre spamdpid
2644
2645 if (( $# != 1 )); then
2646 e spamtest error: expected 1 arg, filename >&2
2647 return 1
2648 fi
2649
2650 spamdpid=$(systemctl status spamassassin| sed -n '/^ *Main PID:/s/[^0-9]//gp')
2651 spamcpre="nsenter -t $spamdpid -n -m"
2652 s $spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
2653 }
2654
2655
2656 # mail related
2657 testmail() {
2658 declare -gi _seq; _seq+=1
2659 echo "test body" | m mail -s "test mail from $HOSTNAME, $_seq" "${@:-root@localhost}"
2660 # for testing to send from an external address, you can do for example
2661 # -fian@iank.bid -aFrom:ian@iank.bid web-6fnbs@mail-tester.com
2662 # note in exim, you can retry a deferred message
2663 # s exim -M MSG_ID
2664 # MSG_ID is in /var/log/exim4/mainlog, looks like 1ccdnD-0001nh-EN
2665 }
2666
2667 # to test sieve, use below command. for fsf mail, see offlineimap-sync script
2668 # make modifications, then copy to live file, use -eW to actually modify mailbox
2669 #
2670 # Another option is to use sieve-test SCRIPT MAIL_FILE. note,
2671 # sieve-test doesnt know about envelopes, Im not sure if sieve-filter does.
2672
2673 # sieve with output filter. arg is mailbox, like INBOX.
2674 # This depends on dovecot conf, notably mail_location in /etc/dovecot/conf.d/10-mail.conf
2675
2676 # always run this first, edit the test files, then run the following
2677 testsieve() {
2678 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
2679 }
2680 runsieve() {
2681 c ~/sieve; cp personal{test,}.sieve; cp lists{test,}.sieve; cp personalend{test,}.sieve
2682 sieve-filter -eWv ~/sieve/maintest.sieve ${1:-INBOX} delete &> /tmp/testsieve.log
2683 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
2684 }
2685
2686 # usage:
2687 # alertme SUBJECT
2688 # printf "subject\nbody\n" | alertme
2689 alertme() {
2690 if [[ -t 0 ]]; then
2691 exim -t <<EOF
2692 From: alertme@b8.nz
2693 To: alerts@iankelling.org
2694 Subject: $*
2695 EOF
2696 else
2697 read -r sub
2698 { cat <<EOF
2699 From: alertme@b8.nz
2700 To: alerts@iankelling.org
2701 Subject: $sub
2702
2703 EOF
2704 cat
2705 } | exim -t
2706 fi
2707 }
2708 daylertme() {
2709 if [[ -t 0 ]]; then
2710 exim -t <<EOF
2711 From: alertme@b8.nz
2712 To: daylert@iankelling.org
2713 Subject: $*
2714 EOF
2715 else
2716 read -r sub
2717 { cat <<EOF
2718 From: alertme@b8.nz
2719 To: daylert@iankelling.org
2720 Subject: $sub
2721
2722 EOF
2723 cat
2724 } | exim -t
2725 fi
2726 }
2727
2728 # alert when a page goes live.
2729 alert200() {
2730 local quiet url tmpdir
2731 quiet=false
2732 case $1 in
2733 # dont send a diff of the html. some html is not very readable
2734 -q) quiet=true
2735 shift
2736 ;;
2737 esac
2738 url="$1"
2739 tmpdir="$(mktemp -d)"
2740 cd $tmpdir
2741 while true; do
2742 if wget -q "$url"; then
2743 if $quiet; then
2744 echo | daylert 200
2745 else
2746 alertme $tmpdir
2747 fi
2748 fi
2749 sleep $(( 120 + RANDOM % 300 ))
2750 done
2751 }
2752
2753 # alert on changes to a webpage (just the base page that curl gets)
2754 # usage: weblert URL [SUBJECT...]
2755 weblert() {
2756 local u old new quiet
2757 quiet=false
2758 case $1 in
2759 # dont send a diff of the html. some html is not very readable
2760 -q) quiet=true
2761 shift
2762 ;;
2763 esac
2764 u="$1"
2765 shift
2766 subject="${*:-weblert}"
2767 old=$(curl -s "$u") ||:
2768 while true; do
2769 new=$(curl -s "$u") ||:
2770 if [[ $old && $new ]]; then
2771 if [[ $new != "$old" ]]; then
2772 if $quiet; then
2773 echo | daylertme "$subject"
2774 else
2775 diff <(printf "%s\n" "$old") <(printf "%s\n" "$new") | daylertme "$subject" ||:
2776 fi
2777 fi
2778 old="$new"
2779 fi
2780 sleep $(( 60 + RANDOM % 120 ))
2781 done
2782 }
2783
2784 torshell() {
2785 # per man torsocks
2786 # shellcheck disable=SC1090 # expected
2787 source "$(type -p torsocks)" on
2788 }
2789
2790 eless2() {
2791 less /var/log/exim4/mymain
2792 }
2793
2794
2795 # mail related
2796 testexim() {
2797 # testmail above calls sendmail, which is a link to exim/postfix.
2798 # its docs dont say a way of adding an argument
2799 # to sendmail to turn on debug output. We could make a wrapper, but
2800 # that is a pain. Exim debug args are documented here:
2801 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
2802 #
2803 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
2804 # note, for exim daemon, you can turn on debug options by
2805 # adding -d, etc to COMMONOPTIONS in
2806 # /etc/default/exim4
2807 #
2808 # to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
2809 # exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
2810 #
2811 # -t = get recipient from header
2812 exim -d -t <<EOF
2813 From: root@$(hostname -f)
2814 To: root@$(hostname -f)
2815 Subject: test2
2816
2817 This is a test message.
2818 EOF
2819 }
2820
2821 # test bounce exim
2822 testbexim() {
2823 to=$1
2824 exim -d -f '<>' $to <<EOF
2825 From: Mail Delivery System <Mailer-Daemon@gnu.org>
2826 To: $to
2827 Subject: Mail delivery failed: returning message to sender
2828
2829 This message was created automatically by mail delivery software.
2830 EOF
2831
2832 }
2833
2834
2835 # toggle keyboard
2836 tk() {
2837 # based on
2838 # https://askubuntu.com/questions/160945/is-there-a-way-to-disable-a-laptops-internal-keyboard
2839 id=$(xinput --list --id-only 'AT Translated Set 2 keyboard')
2840 if xinput list | grep -F '∼ AT Translated Set 2 keyboard' &>/dev/null; then
2841 echo enabling keyboard
2842 # find the first slave keyboard number, they are all the same in my output.
2843 # if they werent, worst case we would need to save the slave number somewhere
2844 # when it got disabled.
2845 slave=$(xinput list | sed -n 's/.*slave \+keyboard (\([0-9]*\)).*/\1/p' | head -n1)
2846 xinput reattach $id $slave
2847 else
2848 xinput float $id
2849 fi
2850 }
2851
2852 tm() {
2853 # timer in minutes
2854 # --no-config
2855 (sleep "$(calc "$* * 60")" && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
2856 }
2857
2858 trg() { transmission-remote-gtk & r; }
2859 trc() {
2860 # example, set global upload limit to 100 kilobytes:
2861 # trc -u 100
2862 TR_AUTH=":$(jq -r .profiles[0].password ~/.config/transmission-remote-gtk/config.json)" transmission-remote transmission.lan -ne "$@"
2863 }
2864
2865 trysleep() {
2866 retries="$1"
2867 sleepsecs="$2"
2868 shift 2
2869 for (( i=0; i < retries - 1; i++ )); do
2870 if "$@"; then
2871 return 0
2872 fi
2873 sleep $sleepsecs
2874 done
2875 "$@"
2876 }
2877
2878
2879 tu() {
2880 local s
2881 if [[ -e $1 && ! -w $1 || ! -w $(dirname "$1") ]]; then
2882 s=s;
2883 fi
2884 # full path for using in some initial setup steps
2885 $s /a/exe/teeu "$@"
2886 }
2887
2888 enn() {
2889 local ecmd pid
2890
2891 ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf"
2892 if ip a show veth1-mail &>/dev/null; then
2893 s $ecmd "$@"
2894 return
2895 fi
2896 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1)
2897 m s nsenter -t $pid -n -m $ecmd "$@"
2898 }
2899
2900 # get pid of systemd service
2901 servicepid() {
2902 local pid unit dir
2903 unit="$1"
2904 pid=$(systemctl show --property MainPID --value "$unit")
2905 case $pid in
2906 [1-9]*) : ;;
2907 *)
2908
2909 dir=/sys/fs/cgroup/system.slice
2910 if [[ ! -d $dir ]]; then
2911 # t10 and older directory.
2912 dir=/sys/fs/cgroup/systemd/system.slice
2913 fi
2914
2915 # 0 or empty. This file includes the MainPid, so I expect we
2916 # could just get this in the first place, but i don't know if that
2917 # is always the case.
2918 pid=$(head -n1 $dir/${unit%.service}.service/cgroup.procs)
2919 ;;
2920 esac
2921 if [[ $pid ]]; then
2922 printf "%s\n" "$pid"
2923 else
2924 return 1
2925 fi
2926 }
2927
2928 sdnbash() { # systemd namespace bash
2929 local unit pid
2930 if (( $# != 1 )); then
2931 echo $0: error wrong number of args >&2
2932 return 1
2933 fi
2934 unit=$1
2935 pid=$(servicepid $unit)
2936 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash
2937 }
2938
2939 sdnbashroot() { # systemd namespace bash
2940 local unit pid
2941 if (( $# != 1 )); then
2942 echo $0: error wrong number of args >&2
2943 return 1
2944 fi
2945 unit=$1
2946 pid=$(servicepid $unit)
2947 m sudo nsenter -t $pid -n -m bash
2948 }
2949
2950
2951 sdncmd() { # systemd namespace cmd
2952 local unit pid
2953 if (( $# <= 2 )); then
2954 echo $0: error wrong number of args >&2
2955 return 1
2956 fi
2957 unit=$1
2958 shift
2959 pid=$(servicepid $unit)
2960 m sudo nsenter -t $pid -n -m sudo -u $USER -i "$@"
2961 }
2962
2963
2964 mailnnbash() {
2965 sdnbash mailnn
2966 }
2967
2968 # we use wireguard now, use mailnnbash.
2969 # mailvpnbash() {
2970 # m sudo nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*mail.conf") -n -m sudo -u $USER -i bash
2971 # }
2972
2973 eximbash() {
2974 local pid
2975 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1)
2976 if [[ ! $pid ]]; then
2977 echo "eximbash: failed to find exim pid. systemctl -n 30 status exim4:"
2978 systemctl status exim4
2979 fi
2980 m sudo nsenter -t $pid -n -m
2981 }
2982 spamnn() {
2983 local spamdpid
2984 spamdpid=$(systemctl show --property MainPID --value spamassassin)
2985 m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@"
2986 }
2987 unboundbash() {
2988 m sudo nsenter -t "$(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp')" -n -m sudo -u $USER -i bash
2989 }
2990
2991 nmtc() {
2992 s nmtui-connect "$@"
2993 }
2994
2995 mailnncheck() {
2996 local unit pid ns mailnn
2997 # mailvpn would belong on the list if using openvpn
2998 for unit in mailnn unbound dovecot spamassassin exim4 radicale; do
2999 pid=$(servicepid $unit)
3000 echo debug: unit=$unit pid=$pid
3001 if [[ ! $pid ]]; then
3002 echo failed to find pid for unit=$unit
3003 continue
3004 fi
3005 if ! ns=$(s readlink /proc/$pid/ns/net); then
3006 echo failed to find ns for unit=$unit pid=$pid
3007 continue
3008 fi
3009 if [[ $mailnn ]]; then
3010 if [[ $ns != "$mailnn" ]]; then
3011 echo "$unit ns $ns != $mailnn"
3012 fi
3013 else
3014 mailnn=$ns
3015 fi
3016 done
3017
3018 }
3019
3020
3021 vpncmd() {
3022 m sudo -E env "PATH=$PATH" nsenter -t "$(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf")" -n "$@"
3023 }
3024
3025 vpni() {
3026 vpncmd sudo -u iank env "PATH=$PATH" "$@"
3027 }
3028 vpnbash() {
3029 vpncmd bash
3030 }
3031
3032
3033 vpn() {
3034 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3035 local vpn_service=openvpn-client
3036 else
3037 local vpn_service=openvpn
3038 fi
3039
3040 [[ $1 ]] || { echo need arg; return 1; }
3041 journalctl --unit=$vpn_service@$1 -f -n0 &
3042 # sometimes the journal doesnt open until after the vpn output
3043 # has happened. hoping this fixes that.
3044 sleep 1
3045 sudo systemctl start $vpn_service@$1
3046 # sometimes the ask-password agent does not work and needs a delay.
3047 sleep .5
3048 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779240
3049 # noticed around 8-2017 after update from around stretch release
3050 # on debian testing, even though the bug is much older.
3051 sudo systemd-tty-ask-password-agent
3052 }
3053
3054 fixu() {
3055 local stats
3056 ls -lad /run/user/1000
3057 stats=$(stat -c%a-%g-%u /run/user/1000)
3058 if [[ $stats != 700-1000-1000 ]]; then
3059 m s chmod 700 /run/user/1000; m s chown iank.iank /run/user/1000
3060 fi
3061 }
3062
3063 # systemctl is-enabled / status / cat says nothing, instead theres
3064 # some obscure symlink. paths copied from man systemd.unit.
3065 # possibly also usefull, but incomplete, doesnt show units not loaded in memory:
3066 # seru list-dependencies --reverse --all UNIT
3067 sysd-deps() {
3068 local f
3069 local -a dirs search
3070 ngset
3071
3072 case $1 in
3073 u)
3074 search=(
3075 ~/.config/systemd/user.control/*
3076 $XDG_RUNTIME_DIR/systemd/user.control/*
3077 $XDG_RUNTIME_DIR/systemd/transient/*
3078 $XDG_RUNTIME_DIR/systemd/generator.early/*
3079 ~/.config/systemd/user/*
3080 /etc/systemd/user/*
3081 $XDG_RUNTIME_DIR/systemd/user/*
3082 /run/systemd/user/*
3083 $XDG_RUNTIME_DIR/systemd/generator/*
3084 ~/.local/share/systemd/user/*
3085 /usr/lib/systemd/user/*
3086 $XDG_RUNTIME_DIR/systemd/generator.late/*
3087 )
3088 ;;
3089 *)
3090 search=(
3091 /etc/systemd/system.control/*
3092 /run/systemd/system.control/*
3093 /run/systemd/transient/*
3094 /run/systemd/generator.early/*
3095 /etc/systemd/system/*
3096 /etc/systemd/systemd.attached/*
3097 /run/systemd/system/*
3098 /run/systemd/systemd.attached/*
3099 /run/systemd/generator/*
3100 /lib/systemd/system/*
3101 /run/systemd/generator.late/*
3102 )
3103 ;;
3104 esac
3105 for f in "${search[@]}"; do
3106 [[ -d $f ]] || continue
3107 case $f in
3108 *.requires|*.wants)
3109 dirs+=("$f")
3110 ;;
3111 esac
3112 done
3113 # dirs is just so we write out the directory names, ls does it when there is 2 or more dirs.
3114 case ${#dirs[@]} in
3115 1)
3116 echo "${dirs[0]}:"
3117 ll "${dirs[@]}"
3118 ;;
3119 0) : ;;
3120 *)
3121 ll "${dirs[@]}"
3122 ;;
3123 esac
3124 ngreset
3125 }
3126
3127 fixvpndns() {
3128 local link istls
3129 read -r _ link _ istls < <(resolvectl dnsovertls tunfsf)
3130 case $istls in
3131 yes|no) : ;;
3132 *) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;;
3133 esac
3134 s busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no
3135 }
3136
3137 vpnoff() {
3138 [[ $1 ]] || { echo need arg; return 1; }
3139 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3140 local vpn_service=openvpn-client
3141 else
3142 local vpn_service=openvpn
3143 fi
3144 sudo systemctl stop $vpn_service@$1
3145 }
3146 vpnoffc() { # vpn off client
3147 ser stop openvpn-client-tr@client
3148 }
3149 vpnc() {
3150 ser start openvpn-client-tr@client
3151 }
3152
3153
3154 vspicy() { # usage: VIRSH_DOMAIN
3155 # connect to vms made with virt-install
3156 spicy -p "$(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
3157 sed -r "s/.*port='([0-9]+).*/\1/")"
3158 }
3159
3160 wian() {
3161 cat-new-files /m/4e/INBOX/new
3162 }
3163
3164 wtr() { curl wttr.in/boston; }
3165
3166 xevkb() { xev -event keyboard; }
3167
3168 # * misc stuff
3169
3170 vrun() {
3171 printf "running: %s\n" "$*"
3172 "$@"
3173 }
3174
3175 f=/a/f/ansible-configs/files/common/etc/fsf-workstation-bashrc.sh
3176 if [[ -e $f ]]; then
3177 # shellcheck disable=SC1090
3178 source $f
3179 fi
3180
3181 electrum() {
3182 # https://electrum.readthedocs.io/en/latest/tor.html
3183 # https://github.com/spesmilo/electrum-docs/issues/129
3184 s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
3185 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
3186 }
3187 monero() {
3188 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
3189 }
3190
3191
3192 # rg my main files
3193 rgm() {
3194 rg "$@" /p/pd.org /p/w.org /a/t.org /a/work.org /b
3195 }
3196
3197 reset-konsole() {
3198 # we also have a file in /a/c/...konsole...
3199 local f=$HOME/.config/konsolerc
3200 setini DefaultProfile profileian.profile "Desktop Entry" $f
3201 setini Favorites profileian.profile "Favorite Profiles" $f
3202 setini ShowMenuBarByDefault false KonsoleWindow $f
3203 setini TabBarPosition Top TabBar $f
3204 }
3205
3206 reset-sakura() {
3207 while read -r k v; do
3208 # shellcheck disable=SC2154
3209 setini $k $v sakura /a/c/subdir_files/.config/sakura/sakura.conf
3210 done <<'EOF'
3211 colorset1_back rgb(33,37,39)
3212 less_questions true
3213 audible_bell No
3214 visible_bell No
3215 disable_numbered_tabswitch true
3216 scroll_lines 10000000
3217 scrollbar true
3218 EOF
3219 }
3220
3221 # make a page of links found in the files $@. redirect output
3222 linkhtml() {
3223 gr -oh 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)' "$@" | \
3224 rev | sort -u | rev | sed 's,.*,<a href="\0">\0</a><br\>,'
3225 }
3226
3227 reset-xscreensaver() {
3228 # except for spash, i set these by setting gui options in
3229 # xscreensaver-command -demo
3230 # then finding the corresponding option in .xscreensaver
3231 # spash, i happened to notice in .xscreensaver
3232 #
3233 # dpmsOff, monitor doesnt come back on using old free software supported nvidia card
3234 cat > /home/iank/.xscreensaver <<'EOF'
3235 mode: blank
3236 dpmsEnabled: True
3237 dpmsStandby: 0:07:00
3238 dpmsSuspend: 0:08:00
3239 dpmsOff: 0:00:00
3240 timeout: 0:05:00
3241 lock: True
3242 lockTimeout: 0:06:00
3243 splash: False
3244 EOF
3245
3246 }
3247
3248
3249 # very useful, copy directory structure 3 deep. add remove /*/ to change level
3250 # rsync -aivh --exclude '/*/*/*/' -f"+ */" -f"- *" SRC DEST
3251
3252
3253 # * stuff that makes sense to be at the end
3254 if [[ "$SUDOD" ]]; then
3255 # allow failure, for example if we are sudoing into a user with diffferent/lesser permissions.
3256 cd "$SUDOD" ||:
3257 unset SUDOD
3258 elif [[ -d /a ]] && [[ $PWD == "$HOME" ]] && [[ $- == *i* ]]; then
3259 cd /a
3260 OLDPWD=
3261 fi
3262
3263
3264
3265
3266 # for mitmproxy to get a newer python.
3267 # commented until i want to use it because it
3268 # noticably slows bash startup
3269 #
3270
3271 mypyenvinit () {
3272 if [[ $EUID == 0 || ! -e ~/.pyenv/bin ]]; then
3273 echo "error: dont be root. make sure pyenv is installed"
3274 return 1
3275 fi
3276 export PATH="$HOME/.pyenv/bin:$PATH"
3277 eval "$(pyenv init -)"
3278 eval "$(pyenv virtualenv-init -)"
3279 }
3280
3281
3282 export GOPATH=$HOME/go
3283 path-add $GOPATH/bin
3284 path-add /usr/local/go/bin
3285
3286 # I have the git repo and a release. either one should work.
3287 # I have both because I was trying to solve an issue that
3288 # turned out to be unrelated.
3289 # ARDUINO_PATH=/a/opt/Arduino/build/linux/work
3290
3291 ## i should have documented this...
3292 # based on https://github.com/keyboardio/Kaleidoscope
3293 export KALEIDOSCOPE_DIR=/a/opt/Kaleidoscope
3294
3295 # They want to be added to the start, but i think
3296 # that should be avoided unless we really need it.
3297 path-add --end ~/.npm-global
3298
3299
3300 path-add --end $HOME/.cargo/bin
3301
3302 if type -P rg &>/dev/null; then
3303 # --no-messages because of annoying errors on broken symlinks
3304 # -z = search .gz etc files
3305 # -. = search dotfilesq
3306 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 $?; }
3307 #fails if not exist. ignore
3308 complete -r rg 2>/dev/null ||:
3309 else
3310 alias rg=grr
3311 fi
3312
3313
3314
3315 # taken from default changes to bashrc and bash_profile
3316 path-add --end --ifexists $HOME/.rvm/bin
3317 # also had ruby bin dir, but moved that to environment.sh
3318 # so its included in overall env
3319
3320
3321 export BASEFILE_DIR=/a/bin/fai-basefiles
3322
3323 #export ANDROID_HOME=/a/opt/android-home
3324 # https://f-droid.org/en/docs/Installing_the_Server_and_Repo_Tools/
3325 #export USE_SDK_WRAPPER=yes
3326 #PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
3327
3328 # didnt get drush working, if I did, this seems like the
3329 # only good thing to include for it.
3330 # Include Drush completion.
3331 # if [ -f "/home/ian/.drush/drush.complete.sh" ] ; then
3332 # source /home/ian/.drush/drush.complete.sh
3333 # fi
3334
3335
3336 # best practice
3337 unset IFS
3338
3339 # https://wiki.archlinux.org/index.php/Xinitrc#Autostart_X_at_login
3340 # i added an extra condition as gentoo xorg guide says depending on
3341 # $DISPLAY is fragile.
3342 if [[ ! $DISPLAY && $XDG_VTNR == 1 ]] && shopt -q login_shell && isarch; then
3343 exec startx
3344 fi
3345
3346
3347 # ensure no bad programs appending to this file will have an affect
3348 return 0