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