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