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