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