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