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