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