various 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
2203 # help me focus. opens 2 windows.
2204 focus() {
2205 /p/c/proc/focus/linux-amd64/focus &
2206 watcharb5
2207 kill %%
2208 }
2209
2210
2211 # Display a list of the active window title
2212 # i've been on with 10 second samples going back
2213 # 5 minutes. If I've been on one window for 10 seconds
2214 # or longer, then display the second count.
2215 #
2216 # Press any key to exit.
2217 watcharb5() {
2218 local char ret
2219 killall arbtt-capture &>/dev/null ||:
2220 rm -f ~/.arbtt/capture.log
2221 arbtt-capture --sample-rate=10 &
2222 while true; do
2223 arb5
2224 ret=0
2225 # i first thought to sleep and capture ctrl-c, but it seems we can't
2226 # capture control-c, unless maybe we implement the commands in a
2227 # separate script or maybe add err-cleanup to err. Anyways, this
2228 # method is superior because any single char exits.
2229 read -rsN1 -t 5 char || ret=$?
2230 if (( ret == 142 )) || [[ ! $char ]]; then
2231 # debug
2232 #e ret=$ret char=$char
2233 :
2234 else
2235 killall arbtt-capture ||:
2236 return 0
2237 fi
2238 done
2239
2240 }
2241
2242 arb5() {
2243 local i j l sec blanks line
2244 local -a arbtt_lines
2245 if [[ ! -e ~/.arbtt/capture.log ]]; then
2246 sleep 5
2247 fi
2248 blanks=$(( LINES - 34 ))
2249 for (( i=0; i < blanks; i++ )); do
2250 echo
2251 done
2252
2253 {
2254 i=0
2255 j=0
2256 # https://stackoverflow.com/questions/56486272/how-to-concat-multiple-fields-to-same-line-with-jq
2257 arbtt_lines=$(arbtt-dump -l 30 -t json | \
2258 jq -r '.[] | [ ( .inactive / 1000 | floor ) , ( .windows[] | select (.active == true) |.title) ] | @tsv' | tac)
2259 for line in "${arbtt_lines[@]}"; do
2260 read -r sec l <<<"$line"
2261 if (( j >= LINES )); then
2262 break
2263 fi
2264 if (( i % 6 == 0 && i >= 2 )); then
2265 j=$(( j + 1 ))
2266 echo "## $(( i / 6 + 1 )) ##"
2267 fi
2268 if (( sec > 10 )); then
2269 printf "%3d %s\n" $sec "$l" | sed -r "s/^(.{$COLUMNS}).*/\1/"
2270 else
2271 printf " %s\n" "$l" | sed -r "s/^(.{$COLUMNS}).*/\1/"
2272 fi
2273 i=$(( i + 1 ))
2274 j=$(( j + 1 ))
2275 done
2276 while (( j < 34 && j < LINES )); do
2277 echo
2278 j=$(( j + 1 ))
2279 done
2280 } | tac
2281 }
2282
2283 arbttlog() {
2284 # from the log, show only the currently active window, and the number of
2285 # seconds of input inactivity.
2286 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}' ; }
2287
2288 idea() {
2289 /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
2290 }
2291
2292 ilogs-local() {
2293 d=/var/lib/znc/moddata/log/iank/
2294 for n in freenode libera; do
2295 cd $d/$n
2296 hr
2297 for x in "#$1/"*; do
2298 base=${x##*/}
2299 files=()
2300 for f in $@; do
2301 tmp=\#$f/$base
2302 if [[ -e $tmp ]]; then
2303 files+=(\#$f/$base)
2304 fi
2305 done
2306 sed \"s/^./${base%log}/\" ${files[@]}|sort -n
2307 hr
2308 done
2309 done
2310 }
2311 ilogs() {
2312 sl root@iankelling.org ilogs-local "$@"
2313 }
2314
2315
2316 ilog-local() {
2317 local d chan
2318 chan="$1"
2319 d=/var/lib/znc/moddata/log/iank/
2320 for n in freenode libera; do
2321 if [[ ! -d $d$n/"$chan" ]]; then
2322 continue
2323 fi
2324 cd $d$n/"$chan"
2325 hr
2326 for x in *; do
2327 echo $x; sed "s/^./${x%log}/" $x; hr;
2328 done
2329 done
2330 }
2331 ilog() {
2332 local chan
2333 chan="${1:-#fsfsys}"
2334 # use * instead of -r since that does sorted order
2335 sl root@iankelling.org ilog-local "$chan" | less +G
2336 }
2337
2338 o() {
2339 if type gio &> /dev/null ; then
2340 gio open "$@"
2341 elif type gvfs-open &> /dev/null ; then
2342 gvfs-open "$@"
2343 else
2344 xdg-open "$@"
2345 fi
2346 # another alternative is run-mailcap
2347 }
2348 ccomp xdg-open o
2349
2350 # jfilter() {
2351 # grep -Evi -e "^(\S+\s+){4}(sudo|sshd|cron)\[\S*:" \
2352 # -e "^(\S+\s+){4}systemd\[\S*: (starting|started) (btrfsmaintstop|dynamicipupdate|spamd dns bug fix cronjob|rss2email)\.*$"
2353 # }
2354 # jtail() {
2355 # journalctl -n 10000 -f "$@" | jfilter
2356 # }
2357 # jr() { journalctl "$@" | jfilter | less ; }
2358 # jrf() { journalctl -n 200 -f "$@" | jfilter; }
2359
2360
2361 ## old version for model01. i need to get that firmware working again.
2362 # kff() { # keyboardio firmware flash. you must hold down the tilde key
2363 # pushd /a/opt/Model01-Firmware
2364 # # if we didn't want this yes hack, then remove "shell read" from
2365 # # /a/opt/Kaleidoscope/etc/makefiles/sketch.mk
2366 # yes $'\n' | VERBOSE=1 make flash
2367 # popd
2368 # }
2369
2370
2371 kff() {
2372 pushd /a/opt/Kaleidoscope/examples/Devices/Keyboardio/Model100
2373 make flash
2374 popd
2375 }
2376
2377 wgkey() {
2378 local umask_orig name
2379 if (( $# != 1 )); then
2380 e expected 1 arg >&2
2381 return 1
2382 fi
2383 name=$1
2384 umask_orig=$(umask)
2385 umask 0077
2386 wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key
2387 umask $umask_orig
2388 }
2389
2390 declare -A vpn_ips
2391 vpn_ips[kd]=2
2392 # note: 1, 4, 5 are occupied by mail wireguard
2393 vpn_ips[x3]=8
2394 vpn_ips[sy]=12
2395 vpn_ips[x2]=13
2396 vpn_ips[kw]=27
2397 vpn_ips[bo]=28
2398 vpn_ips[frodo]=34
2399
2400 vpn-ips-update() {
2401 local host ipsuf f files
2402 for host in ${!vpn_ips[@]}; do
2403 ipsuf=${vpn_ips[$host]}
2404 wghole $host $ipsuf
2405 u /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service <<EOF
2406 [Unit]
2407 Description=OpenVPN tunnel for %I
2408 After=syslog.target network-online.target
2409 Wants=network-online.target
2410 Documentation=man:openvpn(8)
2411 Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
2412 Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO
2413 Requires=iptables.service
2414
2415 [Service]
2416 Type=notify
2417 RuntimeDirectory=openvpn-client
2418 RuntimeDirectoryMode=0710
2419 WorkingDirectory=/etc/openvpn/client
2420 ExecStart=/usr/sbin/openvpn --suppress-timestamps --nobind --config /etc/openvpn/client/%i.conf
2421 # todo, try reenabling this from the default openvpn,
2422 # it was disabled so we could do bind mounts as a command,
2423 # but now systemd handles it
2424 #CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE
2425 LimitNPROC=10
2426 # DeviceAllow=/dev/null rw
2427 # DeviceAllow=/dev/net/tun rw
2428
2429 # we use .1 to make this be on a different network than kd, so that we can
2430 # talk to transmission on kd from remote host, and still use this
2431 # vpn.
2432 ExecStartPre=/usr/bin/flock -w 20 /tmp/newns.flock /a/bin/newns/newns -n 10.174.$ipsuf start %i
2433 ExecStartPre=/sbin/iptables-restore /a/bin/distro-setup/transmission-firewall/netns.rules
2434 # allow wireguard network to connect
2435 ExecStartPre=/usr/sbin/ip r add 10.8.0.0/24 via 10.174.$ipsuf.1 dev veth1-client
2436 ExecStopPost=/usr/bin/flock -w 20 /tmp/newns.flock /a/bin/newns/newns stop %i
2437 PrivateNetwork=true
2438 BindReadOnlyPaths=/etc/tr-resolv:/run/systemd/resolve:norbind /etc/basic-nsswitch:/etc/resolved-nsswitch:norbind
2439
2440 [Install]
2441 WantedBy=multi-user.target
2442 EOF
2443 done
2444
2445 {
2446 for host in ${!vpn_ips[@]}; do
2447 ipsuf=${vpn_ips[$host]}
2448 cat <<EOF
2449 local-data-ptr: "10.2.0.$ipsuf $host.b8.nz"
2450 EOF
2451 done
2452 } | u /b/ds/ptr-data
2453
2454 {
2455 for host in ${!vpn_ips[@]}; do
2456 ipsuf=${vpn_ips[$host]}
2457 cat <<EOF
2458 $host A 10.2.0.$ipsuf
2459 ${host}wg A 10.8.0.$ipsuf
2460 ${host}vp A 10.5.5.$ipsuf
2461 ${host}tr A 10.174.$ipsuf.2
2462 EOF
2463 done
2464 } | cedit vpn-ips-update /p/c/machine_specific/vps/bind-initial/db.b8.nz ||:
2465
2466
2467 echo checking for stray files:
2468
2469 initial_dir=$PWD
2470 cd /a/bin/ds/machine_specific
2471 ngset
2472 files=( */filesystem/etc/systemd/system/openvpn-client-tr@.service )
2473 ngreset
2474 cd $initial_dir
2475 for f in "${files[@]}"; do
2476 host=${f%%/*}
2477 if [[ ! ${vpn_ips[$host]} ]]; then
2478 e /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service
2479 fi
2480 done
2481
2482 cd /p/c/machine_specific
2483 ngset
2484 files=( */filesystem/etc/wireguard/wghole.conf )
2485 ngreset
2486 cd $initial_dir
2487 for f in "${files[@]}"; do
2488 host=${f%%/*}
2489 if [[ ! ${vpn_ips[$host]} ]]; then
2490 e /p/c/machine_specific/$host/filesystem/etc/wireguard/wghole.conf
2491 e cedit -s $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf '<<<""'
2492 fi
2493 done
2494 }
2495
2496 # usage host ipsuf [extrahost]
2497 #
2498 # If the keys already exist and you want new ones, remove them:
2499 # rm /p/c/machine_specific/$host/filesystem/etc/wireguard/hole-{priv,pub}.key
2500 #
2501 # extrahost is a host/cidr that is allowed to go be routed through the
2502 # vpn by this host.
2503 wghole() {
2504 if (( $# < 2 || $# > 3 )); then
2505 e expected 2-3 arg of hostname, ip suffix, and extrahost >&2
2506 return 1
2507 fi
2508 local host ipsuf umask_orig vpn_allowed
2509 host=$1
2510 ipsuf=$2
2511 if [[ $3 ]]; then
2512 extrahost=,$3
2513 fi
2514 for vpn_host in ${!vpn_ips[@]}; do
2515 if [[ $vpn_host == "$host" ]]; then
2516 continue
2517 fi
2518 vpn_allowed+=",10.174.${vpn_ips[$vpn_host]}.2/32"
2519 done
2520 mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
2521 (
2522 cd /p/c/machine_specific/$host/filesystem/etc/wireguard
2523 umask_orig=$(umask)
2524 umask 0077
2525 if [[ ! -s hole-priv.key || ! -s hole-pub.key ]]; then
2526 wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
2527 fi
2528 cat >wghole.conf <<EOF
2529 [Interface]
2530 # contents hole-priv.key
2531 PrivateKey = $(cat hole-priv.key)
2532 ListenPort = 1194
2533 Address = 10.8.0.$ipsuf/24
2534 # https://dev.to/tangramvision/what-they-don-t-tell-you-about-setting-up-a-wireguard-vpn-1h2g
2535 # ||: makes the systemd service not fail due to the failed command
2536 PostUp = ping -w10 -c1 10.8.0.1 ||:
2537
2538 [Peer]
2539 # li. called wgmail on that server
2540 PublicKey = CTFsje45qLAU44AbX71Vo+xFJ6rt7Cu6+vdMGyWjBjU=
2541 AllowedIPs = 10.8.0.0/24$vpn_allowed$extrahost
2542 Endpoint = 72.14.176.105:1194
2543 PersistentKeepalive = 25
2544 EOF
2545 umask $umask_orig
2546 # old approach. systemd seems to work fine and cleaner.
2547 rm -f ../network/interfaces.d/wghole
2548 cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
2549 [Peer]
2550 PublicKey = $(cat hole-pub.key)
2551 AllowedIPs = 10.8.0.$ipsuf/32,10.174.${vpn_ips[$host]}.2/32
2552 EOF
2553 )
2554 }
2555
2556
2557 mns() { # mount namespace
2558 ns=$1
2559 shift
2560 s mkdir -p /root/mount_namespaces
2561 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
2562 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
2563 fi
2564 m sudo mount --make-private /root/mount_namespaces
2565 if [[ ! -e /root/mount_namespaces/$ns ]]; then
2566 m sudo touch /root/mount_namespaces/$ns
2567 fi
2568 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
2569 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
2570 fi
2571 m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
2572 }
2573
2574 mnsd() { # mount namespace + systemd namespace
2575 ns=$1
2576 unit=$2
2577 shift 2
2578
2579 s mkdir -p /root/mount_namespaces
2580 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
2581 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
2582 fi
2583 m sudo mount --make-private /root/mount_namespaces
2584 if [[ ! -e /root/mount_namespaces/$ns ]]; then
2585 m sudo touch /root/mount_namespaces/$ns
2586 fi
2587 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
2588 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
2589 fi
2590
2591 pid=$(servicepid $unit)
2592 tmpf=$(mktemp --tmpdir $unit.XXXXXXXXXX)
2593 export -p >$tmpf
2594 printf "%s " "${@@Q}" >>$tmpf
2595 echo >>$tmpf
2596
2597 m sudo nsenter -t $pid -n --mount=/root/mount_namespaces/$ns sudo -u $USER -i bash -c ". $tmpf & sleep 1; rm $tmpf"
2598 }
2599
2600
2601 mnsr() { # mns run
2602 local ns=$1
2603 shift
2604 mns $ns sudo -u iank -E env "PATH=$PATH" "$@"
2605 }
2606
2607 mnsnonetr() {
2608 ns=$1
2609 lomh
2610 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2611 s ip netns add nonet
2612 fi
2613 mns $ns --net=/var/run/netns/nonet /bin/bash
2614 lomh
2615 }
2616
2617 mnsnonet() {
2618 ns=$1
2619 lomh
2620 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2621 s ip netns add nonet
2622 fi
2623 mns $ns --net=/var/run/netns/nonet sudo -E -u iank /bin/bash
2624 lomh
2625 }
2626
2627
2628 lom() {
2629 # l = the loopback device
2630 local l base
2631 # get sudo pass cached right away
2632 if ! sudo -nv 2>/dev/null; then
2633 sudo -v
2634 fi
2635 if [[ $1 == /* ]]; then
2636 base=${1##*/}
2637 fs_file=$1
2638 if mns $base mountpoint -q /mnt/$base; then
2639 return 0
2640 fi
2641 l=$(losetup -j $fs_file | sed -rn 's/^([^ ]+): .*/\1/p' | head -n1 ||:)
2642 if [[ ! $l ]]; then
2643 l=$(sudo losetup -f)
2644 m sudo losetup $l $fs_file
2645 fi
2646 if ! sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2647 if ! m sudo cryptsetup luksOpen $l $base; then
2648 m sudo losetup -d $l
2649 return 1
2650 fi
2651 fi
2652 m sudo mkdir -p /mnt/$base
2653 m mns $base mount /dev/mapper/$base /mnt/$base
2654 m mns $base chown $USER:$USER /mnt/$base
2655 lomh
2656 else
2657 base=$1
2658 if mns $base mountpoint /mnt/$base &>/dev/null; then
2659 m mns $base umount /mnt/$base
2660 fi
2661 if sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2662 if ! m sudo cryptsetup luksClose /dev/mapper/$base; then
2663 echo lom: failed cryptsetup luksClose /dev/mapper/$base
2664 return 1
2665 fi
2666 fi
2667 l=$(losetup -l --noheadings | awk '$6 ~ /\/'$base'$/ {print $1}')
2668 if [[ $l ]]; then
2669 m sudo losetup -d $l
2670 else
2671 echo lom: warning: no loopback device found
2672 fi
2673 fi
2674 }
2675
2676 # mu personality. for original, just run mp. for 2, run mp 2.
2677 # this is partly duplicated in mail-setup
2678 mp() {
2679 local dead=false
2680 for s in {1..5}; do
2681 if ! killall mu; then
2682 dead=true
2683 break
2684 fi
2685 sleep 1
2686 done
2687 if ! $dead; then
2688 echo error: mu not dead
2689 m psg mu
2690 return 1
2691 fi
2692 suf=$1
2693 set -- /m/mucache ~/.cache/mu /m/.mu ~/.config/mu
2694 while (($#)); do
2695 target=$1$suf
2696 f=$2
2697 shift 2
2698 if [[ -e $f && ! -L $f ]]; then
2699 m rm -rf $f
2700 fi
2701 m ln -sf -T $target $f
2702 done
2703 }
2704
2705 # maildir enable
2706 mdenable() {
2707 local md dst ln_path src two
2708
2709 two=false
2710 case $1 in
2711 -2) two=true shift ;;
2712 esac
2713
2714 for md; do
2715 src=
2716 if $two; then
2717 dst=/m/4e2/$md
2718 else
2719 dst=/m/4e/$md
2720 fi
2721
2722 ln_path=/m/md/$md
2723 for d in /m/md/$md /m/4e2/$md; do
2724 if [[ -d $d && ! -L $d ]]; then
2725 src=$d
2726 break
2727 fi
2728 done
2729 if [[ ! $src ]]; then
2730 echo "error: could not find $md" >&2
2731 return 1
2732 fi
2733 m mv -T $src $dst
2734 m ln -sf -T $dst $ln_path
2735 done
2736 }
2737 md2enable() {
2738 mdenable -2 "$@"
2739 }
2740 mddisable() {
2741 local md=$1
2742 dst=/m/md/$md
2743
2744 ### begin copied from mdenable, but different d ###
2745 for d in /m/4e/$md /m/4e2/$md; do
2746 if [[ -d $d && ! -L $d ]]; then
2747 src=$d
2748 break
2749 fi
2750 done
2751 if [[ ! $src ]]; then
2752 echo "error: could not find $md" >&2
2753 return 1
2754 fi
2755 ### end copy from mdenable ###
2756
2757 if [[ -L $dst ]]; then m rm $dst; fi
2758 m mv -T $src $dst
2759 }
2760
2761
2762 mdt() {
2763 markdown "$1" >/tmp/mdtest.html
2764 firefox /tmp/mdtest.html
2765 }
2766
2767 mo() { xset dpms force off; } # monitor off
2768
2769 mpvgpu() {
2770 # seems to be the best gpu decoding on my nvidia 670.
2771 # vlc gets similar or better framerate, but is much darker output on my test movie at least.
2772
2773
2774 case $HOSTNAME in
2775 kd)
2776 echo 0f | sudo tee -a /sys/kernel/debug/dri/0/pstate
2777 ;;
2778 esac
2779 # going back to the default slow clock, and slower fan:
2780 # echo 07 | sudo tee -a /sys/kernel/debug/dri/0/pstate
2781 if [[ $DISPLAY ]]; then
2782 mpv --vo=vdpau --hwdec=auto "$@"
2783 else
2784 # waylandvk seems to work the same
2785 mpv --gpu-context=wayland --hwdec=auto
2786 fi
2787 }
2788
2789 mpvd() {
2790 mpv --profile=d "$@";
2791 }
2792 # mpv all media files in . or $1
2793 mpvm() {
2794 local -a extensions arg
2795 # get page source of https://en.wikipedia.org/w/index.php?title=Video_file_format&action=edit
2796 # into /a/x.log, then
2797 # grep '^| *\.' /a/x.log | sed 's/| *//;s/,//g'
2798
2799 # note: to join them together for a regex, do:
2800 # old=; for e in ${extensions[@]/./}; do if [[ ! $old ]]; then old=$e; continue; fi; echo -n "$old|"; old=$e; done; echo $e
2801 extensions=(
2802 .webm
2803 .mkv
2804 .flv
2805 .flv
2806 .vob
2807 .ogv .ogg
2808 .drc
2809 .gif
2810 .gifv
2811 .mng
2812 .avi
2813 .MTS .M2TS .TS
2814 .mov .qt
2815 .wmv
2816 .yuv
2817 .rm
2818 .rmvb
2819 .viv
2820 .asf
2821 .amv
2822 .mp4 .m4p .m4v
2823 .mpg .mp2 .mpeg .mpe .mpv
2824 .mpg .mpeg .m2v
2825 .m4v
2826 .svi
2827 .3gp
2828 .3g2
2829 .mxf
2830 .roq
2831 .nsv
2832 )
2833 arg=("(" -iname "*${extensions[0]}")
2834 for (( i=1 ; i < ${#extensions[@]}; i++ )); do
2835 arg+=(-o -iname "*${extensions[i]}")
2836 done
2837 arg+=(")")
2838 dir=${1:-.}
2839 # debug:
2840 #find $dir "${arg[@]}" -size +200k
2841 find $dir "${arg[@]}" -size +200k -exec mpv --profile=d '{}' +
2842 }
2843 mpvs() {
2844 mpv --profile=s "$@";
2845 }
2846
2847 myirc() {
2848 if [[ ! $1 ]]; then
2849 set -- fsfsys
2850 fi
2851 local -a d
2852 d=( /var/lib/znc/moddata/log/iank/{freenode,libera} )
2853 # use * instead of -r since that does sorted order
2854 ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\<iank.*' *; done" | cut --complement -c12-16
2855 }
2856
2857
2858 allmyirc() {
2859 local d
2860 d=/var/lib/znc/moddata/log/iank/freenode
2861 ssh root@iankelling.org "cd $d; find . -mtime -60 -type f -exec grep '\<iank.*' {} +" | sed -r 's,^..([^/]*)/(.{11})(.{5})(.{8}).,\2\4 \1,' | sort
2862 }
2863
2864 # The way pidgin logs with xmpp (maybe related to running cheogram too)
2865 # is that there are sometimes duplicates, and sometimes the a log file
2866 # is for a specific day yet logs messages for subsequent days, and the
2867 # only way to realize that is to notice that the timestamps rolled over
2868 # into a new day, you can't see it in isolation. So, basically, pidgin
2869 # logs are really annoying to read a grep of my messages to find the
2870 # date and time I said when I started and stopped working, so I'm trying
2871 # out a new client: profanity.
2872 mypidgin() {
2873 c /p/c/.purple/logs/jabber/iank@fsf.org/office@conference.fsf.org.chat
2874 for x in *.html; do html2text -o ${x%.html}.txt $x; done;
2875 # shellcheck disable=SC2016 # false positive on ${
2876 grep -A1 ') iank:' ./*.txt \
2877 | sed -r 's/^(.{10})[^ ]*\.txt:\(?([^ ]*)[[:space:]](..). iank:/\1_\2_\3/
2878 s/^[^ ]*\.txt-//
2879 /^--$/d
2880 s/^[^ ]*\.txt:\((.{2}).(.{2}).(.{4}) (.{8}) (.{2})\)?/\3-\1-\2_\4_\5/' \
2881 | sed -n 'x;1d;0~2{G;s/\n/ /;p};${x;p}'
2882 }
2883
2884 # my profanity
2885 #
2886 myprof() {
2887 pushd /home/iank/.local/share/profanity/chatlogs/iank_at_fsf.org/rooms/office_at_conference.fsf.org
2888 logs=(*)
2889 logcount=${#logs[@]}
2890 if (( logcount > 15 )); then
2891 i=$(( logcount - 15 ))
2892 else
2893 i=0
2894 fi
2895 # usually do this on monday, sometimes later
2896 if [[ $(date +%A) == Monday ]]; then
2897 min_date=$(date -d 'monday 2 weeks ago' +%s)
2898 else
2899 min_date=$(date -d 'monday 3 weeks ago' +%s)
2900 fi
2901 for (( ; i < logcount; i++ )); do
2902 log=${logs[$i]}
2903 d=$(date -d "$(head -n1 $log|awk '{print $1}')" +%s)
2904 if (( d < min_date )); then
2905 continue
2906 fi
2907 if awk '$3 == "iank:"' $log | sed -r 's/^(.{10}).(.{8})[^ ]+(.*)/\1_\2\3/' | grep .; then
2908 hr
2909 fi
2910 done
2911 popd
2912 }
2913
2914
2915 # Tail all recent prof logs. Copying from profanity has unwanted line breaks
2916 # especially for links.
2917 profr() {
2918 case $HOSTNAME in
2919 kd)
2920 profr-local
2921 ;;
2922 *)
2923 ssh b8.nz profr-local
2924 ;;
2925 esac
2926 }
2927
2928 profr-local() {
2929 local d0 d1
2930 local -a files
2931 d0="$(date +%Y_%m_%d).log"
2932 d1="$(date -d '1 day ago' +%Y_%m_%d).log"
2933 ngset
2934 files=(/d/p/profanity/chatlogs/iank_at_fsf.org/{*,rooms/*}/{$d0,$d1})
2935 ngreset
2936 if (( ${#files[@]} > 0 )); then
2937 cat "${files[@]}" | sort | tail -n 40
2938 fi
2939 }
2940
2941
2942 # Tail pms in the last day, for the case where we restart profanity and
2943 # didn't check for pms beforehand. Assume the most recent logs are on kd.
2944 # If that isn't the case, use prof-recent-local
2945 prof-recent() {
2946 case $HOSTNAME in
2947 kd)
2948 prof-recent-local
2949 ;;
2950 *)
2951 ssh b8.nz prof-recent-local
2952 ;;
2953 esac
2954 }
2955 prof-recent-local() {
2956 local d dates date files f
2957 # consider making the day count passed by parameter. note: this works: $(date -d '2 day ago' +%Y_%m_%d)
2958 dates=("$(date +%Y_%m_%d)" "$(date -d '1 day ago' +%Y_%m_%d)" )
2959 for d in /d/p/profanity/chatlogs/iank_at_fsf.org/!(rooms); do
2960 files=()
2961 for date in ${dates[@]}; do
2962 f=$d/$date.log
2963 if [[ -e $f ]]; then
2964 files+=($f)
2965 fi
2966 done
2967 if (( ${#files[@]} >= 1 )); then
2968 cat ${files[@]} | tail
2969 hr
2970 fi
2971 done
2972 }
2973
2974
2975 # usage: debvm DEBIAN_VERSION RAM_MB
2976 debvm() {
2977 local ver ram fname src
2978 ver=$1
2979 ram=${2:-2024}
2980 # * is because it might have -backports in the name. we only expect 1 expansion
2981 fnames=( debian-$ver-*nocloud-"$(dpkg --print-architecture)".qcow2 )
2982 if (( ${#fnames[@]} >= 2 )); then
2983 echo "error: iank: unexpected multiple files"
2984 return 1
2985 fi
2986 fname="${fnames[0]}"
2987 src=/a/opt/roms/$fname
2988 if [[ ! -f $src ]]; then
2989 echo debvm: not found $src, download from eg: https://cloud.debian.org/images/cloud/buster/latest/
2990 return 1
2991 fi
2992 cp -a $src /t
2993 # note, in fai-revm we do this: not sure why, maybe because of br device
2994 # --graphics spice,listen=0.0.0.0
2995 m s virt-install --osinfo debian11 --rng /dev/urandom -n deb${ver}tmp --import -r $ram --vcpus 2 --disk /t/$fname --graphics spice
2996 # note: to ssh into this machine will require host key generation: ssh-keygen -A
2997
2998 # random: for cvs2git on gnu www, use debian 10. I could use trisquel,
2999 # but happen to want to try out the debian cloud images. the upstream
3000 # requires python2 and hasn't really changed since the version in d10.
3001 #
3002 # apt install cvs2git cvs
3003 # # 7G was not enough
3004 # mount -o mode=1777,nosuid,nodev,size=34G -t tmpfs tmpfs /tmp
3005 # cvs2git --encoding utf_8 --fallback-encoding ascii --dumpfile=dump www-rsync/www |& tee /tmp/l
3006 ## www-rsync is an rsynced copy of the cvsfrom savannah
3007 }
3008
3009 mygajim() {
3010 local time time_sec time_pretty days
3011 days=${1:-16}
3012 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
3013 case $time in
3014 16*) : ;;
3015 *) continue ;;
3016 esac
3017 if ! time_pretty=$(date +%F.%R -d @$time); then
3018 echo bad time: $time
3019 return 1
3020 fi
3021 echo $time_pretty "$l"
3022 time_sec=${time%%.*}
3023 # only look at the last 18 days. generally just use this for timesheet.
3024 if (( time_sec < EPOCHSECONDS - 60 * 60 * 24 * days )); then break; fi
3025 done
3026 }
3027
3028 allmygajim() {
3029 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank'" | less
3030 }
3031
3032 gajlogs() {
3033 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs" | less
3034 }
3035
3036
3037 net-dev-info() {
3038 e "lspci -nnk|gr -iA2 net"
3039 lspci -nnk|gr -iA2 net
3040 hr
3041 e "s lshw -C network"
3042 hr
3043 sudo lshw -C network
3044 }
3045
3046 nk() {
3047 ser stop NetworkManager
3048 ser disable NetworkManager
3049 ser stop NetworkManager-wait-online.service
3050 ser disable NetworkManager-wait-online.service
3051 ser stop dnsmasq
3052 sudo resolvconf -d NetworkManager
3053 # ser start dnsmasq
3054 sudo ifup br0
3055 }
3056 ngo() {
3057 sudo ifdown br0
3058 ser start NetworkManager
3059 sleep 4
3060 sudo nmtui-connect
3061 }
3062
3063 otp() {
3064 oathtool --totp -b "$*" | xclip -selection clipboard
3065 }
3066 j() {
3067 "$@" |& pee "xclip -r -selection clipboard" cat
3068 }
3069
3070 # x copy
3071 xc() {
3072 xclip -r -selection clipboard
3073 }
3074 # echo copy
3075 ec() {
3076 pee "xclip -r -selection clipboard" cat
3077 }
3078
3079 pakaraoke() {
3080 # from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals
3081 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
3082 }
3083
3084 pfind() { #find *$1* in $PATH
3085 [[ $# != 1 ]] && { echo requires 1 argument; return 1; }
3086 local pathArray
3087 IFS=: pathArray=($PATH); unset IFS
3088 find "${pathArray[@]}" -iname "*$1*"
3089 }
3090
3091 pick-trash() {
3092 # trash-restore lists everything that has been trashed at or below CWD
3093 # This picks out files just in CWD, not subdirectories,
3094 # which also match grep $1, usually use $1 for a time string
3095 # which you get from running restore-trash once first
3096 local name x ask
3097 local nth=1
3098 # last condition is to not ask again for ones we skipped
3099 while name="$( echo | restore-trash | gr "$PWD/[^/]\+$" | gr "$1" )" \
3100 && [[ $name ]] && (( $(wc -l <<<"$name") >= nth )); do
3101 name="$(echo "$name" | head -n $nth | tail -n 1 )"
3102 read -r -p "$name [Y/n] " ask
3103 if [[ ! $ask || $ask == [Yy] ]]; then
3104 x=$( echo "$name" | gr -o "^\s*[0-9]*" )
3105 echo $x | restore-trash > /dev/null
3106 elif [[ $ask == [Nn] ]]; then
3107 nth=$((nth+1))
3108 else
3109 return
3110 fi
3111 done
3112 }
3113
3114
3115 pub() {
3116 rld /a/h/_site/ li:/var/www/iankelling.org/html
3117 }
3118
3119
3120 pumpa() {
3121 # fixes the menu bar in xmonad. this won\'t be needed when xmonad
3122 # packages catches up on some changes in future (this is written in
3123 # 4/2017)
3124 #
3125 # geekosaur: so youll want to upgrade to xmonad 0.13 or else use a
3126 # locally modified XMonad.Hooks.ManageDocks that doesnt set the
3127 # work area; turns out it\'s impossible to set correctly if you are
3128 # not a fully EWMH compliant desktop environment
3129 #
3130 # geekosaur: chrome shows one failure mode, qt/kde another, other
3131 # gtk apps a third, ... I came up with a setting that works for me
3132 # locally but apparently doesnt work for others, so we joined the
3133 # other tiling window managers in giving up on setting it at all
3134 #
3135 xprop -root -remove _NET_WORKAREA
3136 command pumpa & r
3137 }
3138
3139 # reviewboard, used at my old job
3140 #rbpipe() { rbt post -o --diff-filename=- "$@"; }
3141 #rbp() { rbt post -o "$@"; }
3142
3143 rebr() {
3144 sudo ifdown br0
3145 sudo ifup br0
3146 }
3147
3148
3149 r2e() { command r2e -d /p/c/rss2email.json -c /p/c/rss2email.cfg "$@"; }
3150 # only run on MAIL_HOST. simpler to keep this on one system.
3151 r2eadd() { # usage: name url
3152 # initial setup of rss2email:
3153 # r2e new r2e@iankelling.org
3154 # that initializes files, and sets default email.
3155 # symlink to the config doesnt work, so I copied it to /p/c
3156 # and then use cli option to specify explicit path.
3157 # Only option changed from default config is to set
3158 # force-from = True
3159 #
3160 # or else for a few feeds, the from address is set by the feed, and
3161 # if I fail delivery, then I send a bounce message to that from
3162 # address, which makes me be a spammer.
3163
3164 r2e add $1 "$2" $1@r2e.iankelling.org
3165 # get up to date and dont send old entries now:
3166 r2e run --no-send $1
3167 }
3168
3169 rspicy() { # usage: HOST DOMAIN
3170 # connect to spice vm remote host. use vspicy for local host
3171 local port
3172 # shellcheck disable=SC2087
3173 port=$(ssh $1<<EOF
3174 sudo virsh dumpxml $2|grep "<graphics.*type='spice'" | \
3175 sed -rn "s/.*port='([0-9]+).*/\1/p"
3176 EOF
3177 )
3178 if [[ $port ]]; then
3179 spicy -h $1 -p $port
3180 else
3181 echo "error: no port found. check that the domain is running."
3182 fi
3183 }
3184
3185
3186 scssl() {
3187 # s gem install scss-lint
3188 pushd /a/opt/thoughtbot-guides
3189 git pull --stat
3190 popd
3191 scss-lint -c /a/opt/thoughtbot-guides/style/sass/.scss-lint.yml "$@"
3192 }
3193
3194 skbrc() {
3195 sk -e 2120,245 /b/ds/brc /b/ds/brc2
3196 }
3197
3198 skaraoke() {
3199 local tmp out
3200 out=${2:-${1%.*}.sh}
3201 tmp=$(mktemp -d)
3202 script -t -c "mpv --no-config --no-resume-playback --no-terminal --no-audio-display '$1'" $tmp/typescript 2>$tmp/timing
3203 # todo, the current sleep seems pretty good, but it
3204 # would be nice to have an empirical measurement, or
3205 # some better wait to sync up.
3206 #
3207 # note: --loop-file=no prevents it from hanging if you have that
3208 # set to inf the mpv config.
3209 # --loop=no prevents it from exit code 3 due to stdin if you
3210 # had it set to inf in mpv config.
3211 #
3212 # args go to mpv, for example --volume=80, 50%
3213 cat >$out <<EOFOUTER
3214 #!/bin/bash
3215 trap "trap - TERM && kill 0" INT TERM ERR; set -e
3216 ( sleep .2; scriptreplay <( cat <<'EOF'
3217 $(cat $tmp/timing)
3218 EOF
3219 ) <( cat <<'EOF'
3220 $(cat $tmp/typescript)
3221 EOF
3222 ))&
3223 base64 -d - <<'EOF'| mpv --loop=no --loop-file=no --no-terminal --no-audio-display "\$@" -
3224 $(base64 "$1")
3225 EOF
3226 kill 0
3227 EOFOUTER
3228 rm -r $tmp
3229 chmod +x $out
3230 }
3231
3232 smeld() { # ssh meld usage host1 host2 file
3233 meld <(ssh $1 cat $3) <(ssh $2 cat $3)
3234 }
3235
3236 spd() {
3237 PATH=/usr/local/spdhackfix:$PATH command spd "$@"
3238 }
3239
3240 spamf() { # spamtest on FILE
3241 local spamcpre spamdpid
3242
3243 if (( $# != 1 )); then
3244 e spamtest error: expected 1 arg, filename >&2
3245 return 1
3246 fi
3247
3248 spamdpid=$(systemctl status spamassassin| sed -n '/^ *Main PID:/s/[^0-9]//gp')
3249 spamcpre="nsenter -t $spamdpid -n -m"
3250 s $spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
3251 }
3252
3253
3254 # mail related
3255 testmail() {
3256 declare -gi _seq; _seq+=1
3257 echo "test body" | m mail -s "test mail from $HOSTNAME, $_seq" "${@:-root@localhost}"
3258 # for testing to send from an external address, you can do for example
3259 # -fian@iank.bid -aFrom:ian@iank.bid web-6fnbs@mail-tester.com
3260 # note in exim, you can retry a deferred message
3261 # s exim -M MSG_ID
3262 # MSG_ID is in /var/log/exim4/mainlog, looks like 1ccdnD-0001nh-EN
3263 }
3264
3265 # to test sieve, use below command. for fsf mail, see offlineimap-sync script
3266 # make modifications, then copy to live file, use -eW to actually modify mailbox
3267 #
3268 # Another option is to use sieve-test SCRIPT MAIL_FILE. note,
3269 # sieve-test doesnt know about envelopes, Im not sure if sieve-filter does.
3270
3271 # sieve with output filter. arg is mailbox, like INBOX.
3272 # This depends on dovecot conf, notably mail_location in /etc/dovecot/conf.d/10-mail.conf
3273
3274 # always run this first, edit the test files, then run the following
3275 testsieve() {
3276 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
3277 }
3278 runsieve() {
3279 c ~/sieve; cp personal{test,}.sieve; cp lists{test,}.sieve; cp personalend{test,}.sieve
3280 sieve-filter -eWv ~/sieve/maintest.sieve ${1:-INBOX} delete &> /tmp/testsieve.log
3281 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
3282 }
3283
3284 # usage:
3285 # alertme SUBJECT
3286 # printf "subject\nbody\n" | alertme
3287 alertme() {
3288 if [[ -t 0 ]]; then
3289 exim -t <<EOF
3290 From: alertme@b8.nz
3291 To: alerts@iankelling.org
3292 Subject: $*
3293 EOF
3294 else
3295 read -r sub
3296 { cat <<EOF
3297 From: alertme@b8.nz
3298 To: alerts@iankelling.org
3299 Subject: $sub
3300
3301 EOF
3302 cat
3303 } | exim -t
3304 fi
3305 }
3306 daylertme() {
3307 if [[ -t 0 ]]; then
3308 exim -t <<EOF
3309 From: alertme@b8.nz
3310 To: daylert@iankelling.org
3311 Subject: $*
3312 EOF
3313 else
3314 read -r sub
3315 { cat <<EOF
3316 From: alertme@b8.nz
3317 To: daylert@iankelling.org
3318 Subject: $sub
3319
3320 EOF
3321 cat
3322 } | exim -t
3323 fi
3324 }
3325
3326 # alert when a page goes live.
3327 alert200() {
3328 local quiet url tmpdir
3329 quiet=false
3330 case $1 in
3331 # dont send a diff of the html. some html is not very readable
3332 -q) quiet=true
3333 shift
3334 ;;
3335 esac
3336 url="$1"
3337 tmpdir="$(mktemp -d)"
3338 cd $tmpdir
3339 while true; do
3340 if wget -q "$url"; then
3341 if $quiet; then
3342 echo | daylert 200
3343 else
3344 alertme $tmpdir
3345 fi
3346 fi
3347 sleep $(( 120 + RANDOM % 300 ))
3348 done
3349 }
3350
3351 # alert on changes to a webpage (just the base page that curl gets)
3352 # usage: weblert URL [SUBJECT...]
3353 weblert() {
3354 local u old new quiet
3355 quiet=false
3356 case $1 in
3357 # dont send a diff of the html. some html is not very readable
3358 -q) quiet=true
3359 shift
3360 ;;
3361 esac
3362 u="$1"
3363 shift
3364 subject="${*:-weblert}"
3365 old=$(curl -s "$u") ||:
3366 while true; do
3367 new=$(curl -s "$u") ||:
3368 if [[ $old && $new ]]; then
3369 if [[ $new != "$old" ]]; then
3370 if $quiet; then
3371 echo | daylertme "$subject"
3372 else
3373 diff <(printf "%s\n" "$old") <(printf "%s\n" "$new") | daylertme "$subject" ||:
3374 fi
3375 fi
3376 old="$new"
3377 fi
3378 sleep $(( 60 + RANDOM % 120 ))
3379 done
3380 }
3381
3382 torshell() {
3383 # per man torsocks
3384 # shellcheck disable=SC1090 # expected
3385 source "$(type -p torsocks)" on
3386 }
3387
3388 eless2() {
3389 less /var/log/exim4/mymain
3390 }
3391
3392
3393 # mail related
3394 testexim() {
3395 # testmail above calls sendmail, which is a link to exim/postfix.
3396 # its docs dont say a way of adding an argument
3397 # to sendmail to turn on debug output. We could make a wrapper, but
3398 # that is a pain. Exim debug args are documented here:
3399 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
3400 #
3401 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
3402 # note, for exim daemon, you can turn on debug options by
3403 # adding -d, etc to COMMONOPTIONS in
3404 # /etc/default/exim4
3405 #
3406 # to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
3407 # exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
3408 #
3409 # -t = get recipient from header
3410 exim -d -t <<EOF
3411 From: ian@iankelling.org
3412 To: submit@b.b8.nz
3413 Subject: testbug1
3414
3415 Package: test
3416 Version:1
3417
3418 This is a test message.
3419 EOF
3420 }
3421
3422 # test bounce exim
3423 testbexim() {
3424 to=$1
3425 exim -d -f '<>' $to <<EOF
3426 From: Mail Delivery System <Mailer-Daemon@gnu.org>
3427 To: $to
3428 Subject: Mail delivery failed: returning message to sender
3429
3430 This message was created automatically by mail delivery software.
3431 EOF
3432
3433 }
3434
3435
3436 # toggle keyboard
3437 tk() {
3438 # based on
3439 # https://askubuntu.com/questions/160945/is-there-a-way-to-disable-a-laptops-internal-keyboard
3440 id=$(xinput --list --id-only 'AT Translated Set 2 keyboard')
3441 if xinput list | grep -F '∼ AT Translated Set 2 keyboard' &>/dev/null; then
3442 echo enabling keyboard
3443 # find the first slave keyboard number, they are all the same in my output.
3444 # if they werent, worst case we would need to save the slave number somewhere
3445 # when it got disabled.
3446 slave=$(xinput list | sed -n 's/.*slave \+keyboard (\([0-9]*\)).*/\1/p' | head -n1)
3447 xinput reattach $id $slave
3448 else
3449 xinput float $id
3450 fi
3451 }
3452
3453 tm() {
3454 # timer in minutes
3455 # --no-config
3456 (sleep "$(calc "$* * 60")" && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
3457 }
3458
3459 ## usage: to connect to my main transmission daemon from a different host, run this
3460 trans-remote-route() {
3461 :
3462 }
3463 trg() { transmission-remote-gtk & r; }
3464 # TODO: this wont work transmission.lan doesnt exist
3465 trc() {
3466 # example, set global upload limit to 100 kilobytes:
3467 # trc -u 100
3468 TR_AUTH=":$(jq -r .profiles[0].password ~/.config/transmission-remote-gtk/config.json)" transmission-remote transmission.lan -ne "$@"
3469 }
3470
3471 trysleep() {
3472 retries="$1"
3473 sleepsecs="$2"
3474 shift 2
3475 for (( i=0; i < retries - 1; i++ )); do
3476 if "$@"; then
3477 return 0
3478 fi
3479 sleep $sleepsecs
3480 done
3481 "$@"
3482 }
3483
3484
3485 tu() {
3486 local s
3487 if [[ -e $1 && ! -w $1 || ! -w $(dirname "$1") ]]; then
3488 s=s;
3489 fi
3490 # full path for using in some initial setup steps
3491 $s /a/exe/teeu "$@"
3492 }
3493
3494 enn() {
3495 local ecmd pid
3496
3497 ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf"
3498 if ip a show veth1-mail &>/dev/null; then
3499 s $ecmd "$@"
3500 return
3501 fi
3502 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q10m -C /etc/exim4/my.conf"|h1)
3503 m s nsenter -t $pid -n -m $ecmd "$@"
3504 }
3505
3506 # get pid of systemd service
3507 servicepid() {
3508 local pid unit dir
3509 unit="$1"
3510 pid=$(systemctl show --property MainPID --value "$unit")
3511 case $pid in
3512 [1-9]*) : ;;
3513 *)
3514
3515 dir=/sys/fs/cgroup/system.slice
3516 if [[ ! -d $dir ]]; then
3517 # t10 and older directory.
3518 dir=/sys/fs/cgroup/systemd/system.slice
3519 fi
3520
3521 # 0 or empty. This file includes the MainPid, so I expect we
3522 # could just get this in the first place, but i don't know if that
3523 # is always the case.
3524 pid=$(head -n1 $dir/${unit%.service}.service/cgroup.procs)
3525 ;;
3526 esac
3527 if [[ $pid ]]; then
3528 printf "%s\n" "$pid"
3529 else
3530 return 1
3531 fi
3532 }
3533
3534 sdnbash() { # systemd namespace bash
3535 local unit pid
3536 if (( $# != 1 )); then
3537 echo $0: error wrong number of args >&2
3538 return 1
3539 fi
3540 unit=$1
3541 pid=$(servicepid $unit)
3542 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash
3543 }
3544
3545 sdnbashroot() { # systemd namespace bash
3546 local unit pid
3547 if (( $# != 1 )); then
3548 echo $0: error wrong number of args >&2
3549 return 1
3550 fi
3551 unit=$1
3552 pid=$(servicepid $unit)
3553 m sudo nsenter -t $pid -n -m bash
3554 }
3555
3556
3557 sdncmd() { # systemd namespace cmd
3558 local unit pid tmpf
3559 if (( $# <= 2 )); then
3560 echo $0: error wrong number of args >&2
3561 return 1
3562 fi
3563 unit=$1
3564 shift
3565 pid=$(servicepid $unit)
3566 tmpf=$(mktemp --tmpdir $unit.XXXXXXXXXX)
3567 export -p >$tmpf
3568 printf "%s " "${@@Q}" >>$tmpf
3569 echo >>$tmpf
3570 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash -c ". $tmpf & rm $tmpf"
3571 }
3572
3573
3574 mailnnbash() {
3575 sdnbash mailnn
3576 }
3577
3578 # we use wireguard now, use mailnnbash.
3579 # mailvpnbash() {
3580 # m sudo nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*mail.conf") -n -m sudo -u $USER -i bash
3581 # }
3582
3583 eximbash() {
3584 local pid
3585 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q10m -C /etc/exim4/my.conf"|h1)
3586 if [[ ! $pid ]]; then
3587 echo "eximbash: failed to find exim pid. systemctl -n 30 status exim4:"
3588 systemctl status exim4
3589 fi
3590 m sudo nsenter -t $pid -n -m
3591 }
3592 spamnn() {
3593 local spamdpid
3594 spamdpid=$(systemctl show --property MainPID --value spamassassin)
3595 m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@"
3596 }
3597 unboundbash() {
3598 m sudo nsenter -t "$(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp')" -n -m sudo -u $USER -i bash
3599 }
3600
3601 nmtc() {
3602 s nmtui-connect "$@"
3603 }
3604
3605 mailnncheck() {
3606 local unit pid ns mailnn
3607 # mailvpn would belong on the list if using openvpn
3608 for unit in mailnn unbound dovecot spamassassin exim4 radicale; do
3609 pid=$(servicepid $unit)
3610 echo debug: unit=$unit pid=$pid
3611 if [[ ! $pid ]]; then
3612 echo failed to find pid for unit=$unit
3613 continue
3614 fi
3615 if ! ns=$(s readlink /proc/$pid/ns/net); then
3616 echo failed to find ns for unit=$unit pid=$pid
3617 continue
3618 fi
3619 if [[ $mailnn ]]; then
3620 if [[ $ns != "$mailnn" ]]; then
3621 echo "$unit ns $ns != $mailnn"
3622 fi
3623 else
3624 mailnn=$ns
3625 fi
3626 done
3627
3628 }
3629
3630
3631 vpncmd() {
3632 m sudo -E env "PATH=$PATH" nsenter -t "$(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf")" -n "$@"
3633 }
3634
3635 vpni() {
3636 vpncmd sudo -u iank env "PATH=$PATH" "$@"
3637 }
3638 vpnbash() {
3639 vpncmd bash
3640 }
3641
3642
3643 vpn() {
3644 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3645 local vpn_service=openvpn-client
3646 else
3647 local vpn_service=openvpn
3648 fi
3649
3650 [[ $1 ]] || { echo need arg; return 1; }
3651 journalctl --unit=$vpn_service@$1 -f -n0 &
3652 # sometimes the journal doesnt open until after the vpn output
3653 # has happened. hoping this fixes that.
3654 sleep 1
3655 sudo systemctl start $vpn_service@$1
3656 # sometimes the ask-password agent does not work and needs a delay.
3657 sleep .5
3658 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779240
3659 # noticed around 8-2017 after update from around stretch release
3660 # on debian testing, even though the bug is much older.
3661 sudo systemd-tty-ask-password-agent
3662 }
3663
3664 fixu() {
3665 local stats
3666 ls -lad /run/user/1000
3667 stats=$(stat -c%a-%g-%u /run/user/1000)
3668 if [[ $stats != 700-1000-1000 ]]; then
3669 m s chmod 700 /run/user/1000; m s chown iank.iank /run/user/1000
3670 fi
3671 }
3672
3673 # unmute
3674 um() {
3675 local sink card
3676 sink=$(pactl get-default-sink)
3677 if [[ $sink != auto_null ]]; then
3678 return
3679 fi
3680
3681 # guessing there is just one with an off profile. otherwise we will
3682 # need some other solution, like storing the card identifier that we
3683 # muted with nap.
3684 card=$(pacmd list-cards | sed -n '/^[[:space:]]*index:/{s/^[[:space:]]*index://;h};/^[[:space:]]*active profile: <off>$/{g;p;q}')
3685 m pacmd set-card-profile "$card" output:analog-stereo
3686
3687 pactl set-sink-mute @DEFAULT_SINK@ false
3688 rm -f /tmp/ianknap
3689 }
3690
3691 nap() {
3692 local sink card
3693 sink=$(pactl get-default-sink)
3694 card="${sink%.*}"
3695 card="${card/output/card}"
3696 m pacmd set-card-profile "$card" off
3697
3698 # clicking on a link in a browser can cause unmute.
3699 # I don't want that. So, use a stronger form of mute
3700 # than this.
3701 #pactl set-sink-mute @DEFAULT_SINK@ true
3702 touch /tmp/ianknap
3703 }
3704
3705
3706 # systemctl is-enabled / status / cat says nothing, instead theres
3707 # some obscure symlink. paths copied from man systemd.unit.
3708 # possibly also usefull, but incomplete, doesnt show units not loaded in memory:
3709 # seru list-dependencies --reverse --all UNIT
3710 sysd-deps() {
3711 local f
3712 local -a dirs search
3713 ngset
3714
3715 case $1 in
3716 u)
3717 search=(
3718 ~/.config/systemd/user.control/*
3719 $XDG_RUNTIME_DIR/systemd/user.control/*
3720 $XDG_RUNTIME_DIR/systemd/transient/*
3721 $XDG_RUNTIME_DIR/systemd/generator.early/*
3722 ~/.config/systemd/user/*
3723 /etc/systemd/user/*
3724 $XDG_RUNTIME_DIR/systemd/user/*
3725 /run/systemd/user/*
3726 $XDG_RUNTIME_DIR/systemd/generator/*
3727 ~/.local/share/systemd/user/*
3728 /usr/lib/systemd/user/*
3729 $XDG_RUNTIME_DIR/systemd/generator.late/*
3730 )
3731 ;;
3732 *)
3733 search=(
3734 /etc/systemd/system.control/*
3735 /run/systemd/system.control/*
3736 /run/systemd/transient/*
3737 /run/systemd/generator.early/*
3738 /etc/systemd/system/*
3739 /etc/systemd/systemd.attached/*
3740 /run/systemd/system/*
3741 /run/systemd/systemd.attached/*
3742 /run/systemd/generator/*
3743 /lib/systemd/system/*
3744 /run/systemd/generator.late/*
3745 )
3746 ;;
3747 esac
3748 for f in "${search[@]}"; do
3749 [[ -d $f ]] || continue
3750 case $f in
3751 *.requires|*.wants)
3752 dirs+=("$f")
3753 ;;
3754 esac
3755 done
3756 # dirs is just so we write out the directory names, ls does it when there is 2 or more dirs.
3757 case ${#dirs[@]} in
3758 1)
3759 echo "${dirs[0]}:"
3760 ll "${dirs[@]}"
3761 ;;
3762 0) : ;;
3763 *)
3764 ll "${dirs[@]}"
3765 ;;
3766 esac
3767 ngreset
3768 }
3769
3770 fixvpndns() {
3771 local link istls
3772 read -r _ link _ istls < <(resolvectl dnsovertls tunfsf)
3773 case $istls in
3774 yes|no) : ;;
3775 *) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;;
3776 esac
3777 s busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no
3778 }
3779
3780 vpnoff() {
3781 [[ $1 ]] || { echo need arg; return 1; }
3782 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3783 local vpn_service=openvpn-client
3784 else
3785 local vpn_service=openvpn
3786 fi
3787 sudo systemctl stop $vpn_service@$1
3788 }
3789 vpnoffc() { # vpn off client
3790 ser stop openvpn-client-tr@client
3791 }
3792 vpnc() {
3793 local unit
3794 unit=openvpn-client-tr@client
3795 sudo -v
3796 if [[ $(systemctl is-active $unit) != active ]]; then
3797 s systemctl start $unit
3798 sleep 1
3799 fi
3800 }
3801
3802
3803 vspicy() { # usage: VIRSH_DOMAIN
3804 # connect to vms made with virt-install
3805 spicy -p "$(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
3806 sed -r "s/.*port='([0-9]+).*/\1/")"
3807 }
3808
3809 wian() {
3810 cat-new-files /m/4e/INBOX/new
3811 }
3812 wakehours() {
3813 local sec
3814 if (( $# != 1 )) ; then
3815 echo wakehours: error: expected 1 arg, got $# >&2
3816 return 1
3817 fi
3818 sec=$(( EPOCHSECONDS - $( date +%s -d $1am ) ))
3819 printf "%d:%02d\n" $(( sec / 60 / 60)) $(( (sec / 60) % 60 ))
3820 }
3821
3822 calvis() { # calendar visualize
3823 install -m 600 /dev/null /tmp/calendar-bytes
3824 while read -r l; do
3825 for char in $l; do
3826 # shellcheck disable=SC2059 # intentional for the hex formatting
3827 printf "\x$(printf "%x" $char)" >>/tmp/calendar-bytes
3828 done
3829 done < <(grep -v '[#-]' /p/calendar-data)
3830 /p/c/proc/calendar/linux-amd64/calendar
3831 }
3832
3833 wtr() { curl wttr.in/boston; }
3834
3835 xevkb() { xev -event keyboard; }
3836
3837 # * misc stuff
3838
3839 vrun() {
3840 printf "running: %s\n" "$*"
3841 "$@"
3842 }
3843
3844 f=/a/f/ansible-configs/files/common/etc/fsf-workstation-bashrc.sh
3845 if [[ -e $f ]]; then
3846 # shellcheck disable=SC1090
3847 source $f
3848 fi
3849
3850 electrum() {
3851 # https://electrum.readthedocs.io/en/latest/tor.html
3852 # https://github.com/spesmilo/electrum-docs/issues/129
3853 s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
3854 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
3855 }
3856 monero() {
3857 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
3858 }
3859
3860
3861 # rg my main files
3862 rgm() {
3863 rg "$@" /p/w.org /a/t.org /a/work.org /b
3864 }
3865
3866 # re all my files more expansively
3867 rem() {
3868 local paths
3869 paths="/p/c /b/"
3870 find $paths -not \( -name .svn -prune -o -name .git -prune \
3871 -o -name .hg -prune -o -name .editor-backups -prune \
3872 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto -- "$*" ||:
3873 rgv -- "$*" $paths /a/t.org /p/w.org /a/work.org ||:
3874 }
3875 reml() { # with limit to 5 matches per file
3876 local paths
3877 paths="/p/c /b"
3878 find $paths -not \( -name .svn -prune -o -name .git -prune \
3879 -o -name .hg -prune -o -name .editor-backups -prune \
3880 -o -name .undo-tree-history -prune \) 2>/dev/null | grep -iP --color=auto -- "$*" ||:
3881 rgv -m 5 -- "$*" $paths /a/t.org /p/w.org /a/work.org ||:
3882 }
3883
3884
3885 # for use in /f/bind
3886 fupzone() {
3887 # shellcheck disable=SC2046 # i want word splitting
3888 ./update-zone $(i s | sed -rn 's/.*db\.(.*)/\1/p')
3889 }
3890
3891 # setup:
3892 # pip3 install linode-cli
3893 # linode-cli
3894 livp9() {
3895 local input ip id tmp
3896 input=$1
3897 if [[ $2 ]]; then
3898 id=$2
3899 ip=$3
3900 else
3901 tmp=$(mktemp)
3902 echo $tmp
3903 linode-cli --json --pretty linodes create --root_pass loxHuceygomGisun | tee $tmp
3904 read -r ip id <<<"$(tail -n+2 $tmp | jq -r '.[0].ipv4[0] , .[0].id')"
3905 for string in $ip $id; do
3906 case $string in
3907 [0-9]*) : ;;
3908 *)
3909 echo "livp9: bad value ip=$ip id=$id input=$input"
3910 return 1
3911 ;;
3912 esac
3913 done
3914 rm $tmp
3915
3916 while true; do
3917 if timeout 4 ssh $ip :; then
3918 break
3919 fi
3920 sleep 3
3921 done
3922 fi
3923 ssh $ip <<EOF
3924 apt-get -qq update
3925 apt-get -qq -y install ffmpeg rsync
3926 mkdir vp9
3927 EOF
3928 m rsync $input $ip:
3929 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
3930 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
3931 rsync $ip:vp9/$input vp9
3932 linode-cli linodes delete $id
3933 }
3934
3935 reset-konsole() {
3936 # we also have a file in /a/c/...konsole...
3937 local f=$HOME/.config/konsolerc
3938 setini DefaultProfile profileian.profile "Desktop Entry" $f
3939 setini Favorites profileian.profile "Favorite Profiles" $f
3940 setini ShowMenuBarByDefault false KonsoleWindow $f
3941 setini TabBarPosition Top TabBar $f
3942 }
3943
3944 reset-sakura() {
3945 while read -r k v; do
3946 # shellcheck disable=SC2154
3947 setini $k $v sakura /a/c/subdir_files/.config/sakura/sakura.conf
3948 done <<'EOF'
3949 colorset1_back rgb(33,37,39)
3950 less_questions true
3951 audible_bell No
3952 visible_bell No
3953 disable_numbered_tabswitch true
3954 scroll_lines 10000000
3955 scrollbar true
3956 EOF
3957 }
3958
3959 # make a page of links found in the files $@. redirect output
3960 linkhtml() {
3961 gr -oh 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)' "$@" | \
3962 rev | sort -u | rev | sed 's,.*,<a href="\0">\0</a><br\>,'
3963 }
3964
3965 reset-xscreensaver() {
3966 # except for spash, i set these by setting gui options in
3967 # xscreensaver-command -demo
3968 # then finding the corresponding option in .xscreensaver
3969 # spash, i happened to notice in .xscreensaver
3970 #
3971 # dpmsOff, monitor doesnt come back on using old free software supported nvidia card
3972 cat > /home/iank/.xscreensaver <<'EOF'
3973 mode: blank
3974 dpmsEnabled: True
3975 dpmsStandby: 0:07:00
3976 dpmsSuspend: 0:08:00
3977 dpmsOff: 0:00:00
3978 timeout: 0:05:00
3979 lock: True
3980 lockTimeout: 0:06:00
3981 splash: False
3982 EOF
3983
3984 }
3985
3986
3987 # very useful, copy directory structure 3 deep. add remove /*/ to change level
3988 # rsync -aivh --exclude '/*/*/*/' -f"+ */" -f"- *" SRC DEST
3989
3990
3991 # * stuff that makes sense to be at the end
3992 if [[ "$SUDOD" ]]; then
3993 # allow failure, for example if we are sudoing into a user with diffferent/lesser permissions.
3994 cd "$SUDOD" ||:
3995 unset SUDOD
3996 elif [[ -d /a ]] && [[ $PWD == "$HOME" ]] && [[ $- == *i* ]]; then
3997 cd /a
3998 OLDPWD=
3999 fi
4000
4001
4002
4003
4004 # for mitmproxy to get a newer python.
4005 # commented until i want to use it because it
4006 # noticably slows bash startup
4007 #
4008
4009 mypyenvinit () {
4010 if [[ $EUID == 0 || ! -e ~/.pyenv/bin ]]; then
4011 echo "error: dont be root. make sure pyenv is installed"
4012 return 1
4013 fi
4014 export PATH="$HOME/.pyenv/bin:$PATH"
4015 eval "$(pyenv init -)"
4016 eval "$(pyenv virtualenv-init -)"
4017 }
4018
4019
4020 export GOPATH=$HOME/go
4021 path-add $GOPATH/bin
4022 path-add /usr/local/go/bin
4023
4024 # I have the git repo and a release. either one should work.
4025 # I have both because I was trying to solve an issue that
4026 # turned out to be unrelated.
4027 # ARDUINO_PATH=/a/opt/Arduino/build/linux/work
4028
4029 ## i should have documented this...
4030 # based on https://github.com/keyboardio/Kaleidoscope
4031 export KALEIDOSCOPE_DIR=/a/opt/Kaleidoscope
4032
4033 # They want to be added to the start, but i think
4034 # that should be avoided unless we really need it.
4035 path-add --end ~/.npm-global
4036
4037
4038 path-add --end $HOME/.cargo/bin
4039
4040 if type -P rg &>/dev/null; then
4041 # --no-messages because of annoying errors on broken symlinks
4042 # -z = search .gz etc files
4043 # -. = search dotfiles
4044 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 $?; }
4045 #fails if not exist. ignore
4046 complete -r rg 2>/dev/null ||:
4047 else
4048 alias rg=grr
4049 fi
4050
4051 # rg with respecting vcs ignore files
4052 rgv() {
4053 ret=0
4054 # -. = search dotfiles
4055 # -z = search zipped files
4056 # -i = case insensitive
4057 # -M = max columns
4058 # --no-messages because of annoying errors on broken symlinks
4059 # --no-ignore-parent because i have /a/.git which ignores almost everything under it.
4060 command rg -. -z --no-messages -i -M 900 --no-ignore-parent -g '!.git' -g '!auto-save-list' -g '!.savehist' "$@" || ret=$?
4061 return $ret
4062 }
4063
4064 amall() {
4065 echo "$(tput setaf 5 2>/dev/null ||:)█ coresite █$(tput sgr0 2>/dev/null||:)"
4066 amfsf "$@"
4067 echo "$(tput setaf 5 2>/dev/null ||:)█ office █$(tput sgr0 2>/dev/null||:)"
4068 amoffice "$@"
4069 }
4070 amallq() { # amall quiet
4071 amfsf "$@"
4072 amoffice "$@"
4073 }
4074 amfsf() {
4075 sedi -r '/alertmanager.url/s/@prom.office/@prom/' ~/.config/amtool/config.yml
4076 amtool "$@"
4077 }
4078 amoffice() {
4079 sedi -r '/alertmanager.url/s/@prom.fsf/@prom.office.fsf/' ~/.config/amtool/config.yml
4080 amtool "$@"
4081 }
4082 amls() {
4083 amall silence query "$@"
4084 }
4085 # amtool silence add
4086 amsa() {
4087 amall silence add "$@"
4088 }
4089 # amtool silence force
4090 amsf() {
4091 amall silence add x!="1"
4092 }
4093 amrmall() {
4094 # note: not sure if quoting of this arg is correct
4095 amfsf silence expire "$(amfsf silence query -q)"
4096 amoffice silence expire "$(amoffice silence query -q)"
4097 }
4098
4099
4100 youtube-dl-update() {
4101 sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
4102 sudo chmod a+rx /usr/local/bin/youtube-dl
4103 }
4104
4105 # https://github.com/yt-dlp/yt-dlp/wiki/Installation
4106 yt-dlp-update() {
4107 sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
4108 sudo chmod a+rx /usr/local/bin/yt-dlp # Make executable
4109 }
4110
4111 mpvyt() {
4112 mpv --ytdl ytdl_path=/usr/local/bin/yt-dlp "$@"
4113 }
4114
4115 # taken from default changes to bashrc and bash_profile
4116 path-add --end --ifexists $HOME/.rvm/bin
4117 # also had ruby bin dir, but moved that to environment.sh
4118 # so its included in overall env
4119
4120
4121 # ya, hacky hardcoded hostnames in 2023. we could do better
4122 hssh-update() {
4123 local -a failed_hosts hosts
4124 case $HOSTNAME in
4125 sy|kd)
4126 hosts=(
4127 kd x3.office.fsf.org syw
4128 )
4129 ;;
4130 x3)
4131 hosts=(
4132 b8.nz sywg.b8.nz
4133 )
4134 ;;
4135 esac
4136 for host in ${hosts[@]}; do
4137 e $host
4138 if ! scp /b/fai/fai/config/files/usr/local/bin/hssh/IANK root@$host:/usr/local/bin/hssh; then
4139 failed_hosts+=($host)
4140 fi
4141 done
4142 if (( ${#failed_hosts[@]} >= 1 )); then
4143 echo failed_hosts=${failed_hosts[*]}
4144 return 1
4145 fi
4146 }
4147
4148
4149 export BASEFILE_DIR=/a/bin/fai-basefiles
4150
4151 #export ANDROID_HOME=/a/opt/android-home
4152 # https://f-droid.org/en/docs/Installing_the_Server_and_Repo_Tools/
4153 #export USE_SDK_WRAPPER=yes
4154 #PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
4155
4156 # didnt get drush working, if I did, this seems like the
4157 # only good thing to include for it.
4158 # Include Drush completion.
4159 # if [ -f "/home/ian/.drush/drush.complete.sh" ] ; then
4160 # source /home/ian/.drush/drush.complete.sh
4161 # fi
4162
4163
4164 # best practice
4165 unset IFS
4166
4167 # https://wiki.archlinux.org/index.php/Xinitrc#Autostart_X_at_login
4168 # i added an extra condition as gentoo xorg guide says depending on
4169 # $DISPLAY is fragile.
4170 if [[ ! $DISPLAY && $XDG_VTNR == 1 ]] && shopt -q login_shell && isarch; then
4171 exec startx
4172 fi
4173
4174
4175 # ensure no bad programs appending to this file will have an affect
4176 return 0