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