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