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