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