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