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