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