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