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