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