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