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