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