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