various fixes, a few new features
[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 # expected failure: Error: near line 1: in prepare, table accounts has no column named pinned_mechanism (1)
1296 # the sql needs an update
1297 /a/opt/ceb-tools/ceb2sqlgz Backup/iank@fsf.org.ceb <pas | gunzip | sqlite3 b ||:
1298 rm -r Backup
1299 }
1300
1301 # usage: cheologs [DAYS_LIMIT]
1302 # default days is 100
1303 cheologs() {
1304 local days q
1305 days=${1:-100}
1306 q="
1307 select
1308 datetime(substr(timeSent,0,11), 'unixepoch'),
1309 replace(replace(counterpart,'@fsf.org',''),
1310 '@conference.fsf.org',''),
1311 body
1312 from messages
1313 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
1314 order by timeSent;"
1315 sqlite3 /p/cheogram/b ".mode tabs" "$q" | less
1316 }
1317
1318 mycheologs() {
1319 local days q
1320 days=${1:-16}
1321 # timezone compared to utc. note: this takes the current offset, so if daylight savings change
1322 # happened in the looking back period, this won't account for it.
1323 zone_offset=$(( $( date +%z | sed 's/[^1-9-]*//g' ) * 60 * 60))
1324 case $zone_offset in
1325 -*) : ;;
1326 *) zone_offset="+ $zone_offset"
1327 esac
1328 echo zone_offset=$zone_offset
1329 q="
1330 select
1331 datetime(substr(timeSent,0,11) $zone_offset, 'unixepoch'),
1332 body
1333 from messages
1334 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
1335 and counterpart = 'office@conference.fsf.org/iank'
1336 order by timeSent;"
1337 sqlite3 /p/cheogram/b ".mode tabs" "$q" | sed 's/ /./' | less
1338 }
1339
1340 # version of jdo for my non-root user
1341 jdo() {
1342 # comparison of alternative logging methods:
1343 #
1344 # systemd-run command (what this function does)
1345 #
1346 # If there is a user prompt, the program will detect that it is not
1347 # connected to a terminal and act in a non-interactive way, skipping
1348 # the prompt. This has the benefit that you know exactly how the
1349 # program will act if you want to move it into a service that runs
1350 # automatically.
1351 #
1352 # If run with sudo and command is a shell script which does a sleep,
1353 # it can (sometimes?) output some extra whitespace in front of
1354 # messages, more for each subsequent message. This can be avoided by
1355 # becoming root first.
1356 #
1357 # It logs the command's pid and exit code, which is nice.
1358 #
1359 #
1360 ### command |& ts | tee file.log
1361 #
1362 # If there is a user prompt, like "read -p prompt var", it will hang
1363 # without outputting the prompt.
1364 #
1365 # I've had a few times where ts had an error and I wasn't totally sure
1366 # if it was really the command or ts having the problem.
1367 #
1368 # Sometimes some output will get hidden until you hit enter.
1369 #
1370 #
1371 ### command |& pee cat logger
1372 #
1373 # This seems to work. I need to test more.
1374 #
1375 #
1376 ### command |& logger -s
1377 #
1378 # User prompts get confusingly prefixed to earlier output, and all log
1379 # entries get prefixed with annoying priority level.
1380 #
1381 #
1382 ### systemd-cat
1383 #
1384 # Had a few problems. One major one is that it exited in the middle of
1385 # a command on systemctl daemon-reload
1386 #
1387 # Related commands which can log a whole session: script, sudo, screen
1388 local cmd cmd_name jr_pid ret
1389 ret=0
1390 cmd="$1"
1391 shift
1392 cmd_name=${cmd##*/}
1393 if [[ $cmd != /* ]]; then
1394 cmd=$(type -P "$cmd")
1395 fi
1396 # -q = quiet
1397 journalctl -qn2 -f -u "$cmd_name" &
1398 # Trial and error of time needed to avoid missing initial lines.
1399 # .5 was not reliable. 1 was not reliable. 2 was not reliable
1400 sleep 4
1401 jr_pid=$!
1402 # note, we could have a version that does system --user, but if for example
1403 # it does sudo ssh, that will leave a process around that we can't kill
1404 # and it will leave the unit hanging around in a failed state needing manual
1405 # killing of the process.
1406 s systemd-run --uid "$(id -u)" --gid "$(id -g)" \
1407 -E SSH_AUTH_SOCK=/run/openssh_agent \
1408 --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
1409 # The sleep lets the journal output its last line
1410 # before the prompt comes up.
1411 sleep .5
1412 kill $jr_pid &>/dev/null ||:
1413 unset jr_pid
1414 fg &>/dev/null ||:
1415 # this avoids any err-catch
1416 (( ret == 0 )) || return $ret
1417 }
1418
1419 # service run, and watch the output
1420 srun() {
1421 local unit
1422 ret=0
1423 unit=$1
1424 journalctl -qn2 -f -u $unit &
1425 systemctl start $unit
1426 sleep 2
1427 kill $jr_pid &>/dev/null ||:
1428 unset jr_pid
1429 fg &>/dev/null ||:
1430 }
1431
1432 sm() { # switch mail host
1433 local tmp keyhash
1434 c /
1435 # run latest
1436 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
1437 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
1438 if [[ ! $tmp ]]; then
1439 s ssh-add /root/.ssh/home
1440 fi
1441 install-my-scripts
1442 s jdo switch-mail-host "$@"
1443 return $ret
1444 }
1445 sh2() { # switch host2
1446 local tmp keyhash
1447 c /
1448 # run latest
1449 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
1450 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
1451 if [[ ! $tmp ]]; then
1452 s ssh-add /root/.ssh/home
1453 fi
1454 install-my-scripts
1455 s jdo switch-host2 "$@"
1456 return $ret
1457 }
1458
1459 # shellcheck disable=SC2120
1460 lipush() {
1461 # note, i had --delete-excluded, but that deletes all files in --exclude-from on
1462 # the remote site, which doesn't make sense, so not sure why i had it.
1463 local p a
1464 # excluding emacs for now
1465 #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1466 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1467 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1468 ret=0
1469 for h in li je bk; do
1470 m s rsync "$@" $a ${p[@]} /p/c/machine_specific/$h root@$h.b8.nz:/
1471 ## only li is debian11
1472 #p[0]=/a/opt/emacs-trisuqel10
1473 #p[1]=/a/opt/emacs-trisquel10-nox
1474 done
1475 m s rsync "$@" -ahviSAXPH root@li.b8.nz:/a/h/proposed-comments/ /a/h/proposed-comments || ret=$?
1476 return $ret
1477 }
1478 bkpush() { # no emacs. for running faster.
1479 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1480 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1481 ret=0
1482 m rsync "$@" $a ${p[@]} /p/c/machine_specific/bk root@bk.b8.nz:/ || ret=$?
1483 return $ret
1484 }
1485 jepush() { # no emacs. for running faster.
1486 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
1487 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
1488 ret=0
1489 m rsync "$@" $a ${p[@]} /p/c/machine_specific/je root@je.b8.nz:/ || ret=$?
1490 return $ret
1491 }
1492
1493 bindpush() {
1494 dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
1495 lipush
1496 for h in li bk; do
1497 m sl $h <<'EOF'
1498 source ~/.bashrc
1499 m dnsup
1500 EOF
1501 done
1502 }
1503 bindpushb8() {
1504 lipush
1505 for h in li bk; do
1506 m sl $h <<'EOF'
1507 source ~/.bashrc
1508 m dnsb8
1509 EOF
1510 done
1511 }
1512
1513 dnsup() {
1514 conflink -f
1515 m ser reload named
1516 }
1517 dnsb8() {
1518 local f=/var/lib/bind/db.b8.nz
1519 m ser stop named
1520 m sleep 1
1521 m sudo rm -fv $f.jnl $f.signed.jnl
1522 m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f
1523 m ser restart named
1524 }
1525 dnsecgen() {
1526 # keys generated like this
1527 # because of https://ftp.isc.org/isc/dnssec-guide/dnssec-guide.pdf
1528 # https://blog.apnic.net/2019/05/23/how-to-deploying-dnssec-with-bind-and-ubuntu-server/
1529
1530 # key length is longer than that guide because
1531 # we are using those at fsf and when old key lengths
1532 # become insecure, I want some extra time to update.
1533 # dnsecgen (in brc2)
1534
1535 local zone=$1
1536 dnssec-keygen -a RSASHA256 -b 2048 $zone
1537 dnssec-keygen -f KSK -a RSASHA256 -b 4096 $zone
1538 for f in K"$zone".*.key; do
1539 # eg Kb8.nz.+008+47995.key tag=47995
1540 # in dnsimple, you add the long string from this.
1541 # in gandi, you add the long string from the .key file,
1542 # then see that the digest matches the ds.
1543 echo "tag is the number after DS"
1544 dnssec-dsfromkey -a SHA-256 $f
1545 done
1546 # For b8.nz, we let bind read the keys and sign, and
1547 # right now they have root ownership, so let them
1548 # get group read.
1549 chmod g+r ./*.private
1550 }
1551 dsign() {
1552 # create .signed file
1553 # note: full paths probably not needed.
1554 local arg
1555 for arg; do
1556 local zone=${arg#db.}
1557 local dir=/p/c/machine_specific/vps/filesystem/var/lib/bind
1558 dnssec-signzone -S -e +31536000 -o $zone -K $dir -d $dir $dir/db.$zone
1559 done
1560 }
1561
1562 # set day start for use in other programs.
1563 # expected to do be in a format like 830, or 800 or 1300.
1564 ds() {
1565 echo $1 >/b/data/daystart
1566 }
1567
1568 #### begin bitcoin related things
1569 btc() {
1570 local f=/etc/bitcoin/bitcoin.conf
1571 # importprivkey will timeout if using the default of 15 mins.
1572 # upped it to 1 hour.
1573 bitcoin-cli -rpcclienttimeout=60000 -"$(s grep rpcuser= $f)" -"$(s grep rpcpassword= $f)" "$@"
1574 }
1575 btcusd() { # $1 btc in usd
1576 local price
1577 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1578 printf "$%s\n" "$price"
1579 if [[ $1 ]]; then
1580 printf "$%.2f\n" "$(echo "scale=4; $price * $1"| bc -l)"
1581 fi
1582 }
1583 usdbtc() { # $1 usd in btc
1584 local price
1585 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1586 printf "$%s\n" "$price"
1587 if [[ $1 ]]; then
1588 # 100 mil satoshi / btc. 8 digits after the 1.
1589 printf "%.8f btc\n" "$(echo "scale=10; $1 / $price "| bc -l)"
1590 fi
1591 }
1592 satoshi() { # $1 satoshi in usd
1593 local price
1594 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
1595 price=$(echo "scale=10; $price * 0.00000001"| bc -l)
1596 printf "$%f\n" "$price"
1597 if [[ $1 ]]; then
1598 printf "$%.2f\n" "$(echo "scale=10; $price * $1"| bc -l)"
1599 fi
1600 }
1601 #### end bitcoin related things
1602
1603
1604
1605 cbfstool () { /a/opt/coreboot/build/cbfstool "$@"; }
1606
1607
1608 cgpl()
1609 {
1610 if (($#)); then
1611 cp /a/bin/data/COPYING "$@"
1612 else
1613 cp /a/bin/data/COPYING .
1614 fi
1615 }
1616
1617 capache()
1618 {
1619 if (($#)); then
1620 cp /a/bin/data/LICENSE "$@"
1621 else
1622 cp /a/bin/data/LICENSE .
1623 fi
1624 }
1625
1626 chrome() {
1627 if type -p chromium &>/dev/null; then
1628 cmd=chromium
1629 else
1630 cd /
1631 cmd="schroot -c bullseye chromium"
1632 CHROMIUM_FLAGS='--enable-remote-extensions' $cmd & r
1633 fi
1634 }
1635
1636
1637 # do all tee.
1638 # pipe to this, or just type like a shell
1639 # todo: test this
1640 dat() {
1641 tee >(ssh frodo.b8.nz) >(ssh x2) >(ssh tp.b8.nz) >(ssh kw) >(ssh tp.b8.nz)
1642 }
1643 da() { # do all
1644 local host
1645 for host in x2 kw tp.b8.nz x3.b8.nz frodo.b8.nz; do
1646 ssh $host "$@"
1647 done
1648 }
1649
1650
1651 debian_pick_mirror () {
1652 # netselect-apt finds a fast mirror.
1653 # but we need to replace the mirrors ourselves,
1654 # because it doesnt do that. best it can do is
1655 # output a basic sources file
1656 # here we get the server it found, get the main server we use
1657 # then substitute all instances of one for the other in the sources file
1658 # and backup original to /etc/apt/sources.list-original.
1659 # this is idempotent. the only way to identify debian sources is to
1660 # note the original server, so we put it in a comment so we can
1661 # identify it later.
1662 local file
1663 file=$(mktemp -d)/f # safe way to get file name without creating one
1664 sudo netselect-apt -o "$file" || return 1
1665 url=$(grep ^\\w $file | head -n1 | awk '{print $2}')
1666 sudo cp -f /etc/apt/sources.list /etc/apt/sources.list-original
1667 sudo sed -ri "/http.us.debian.org/ s@( *[^ #]+ +)[^ ]+([^#]+).*@\1$url\2# http.us.debian.org@" /etc/apt/sources.list
1668 sudo apt-get update
1669 }
1670 digme() {
1671 digdiff @ns{1,2}.iankelling.org "$@"
1672 }
1673
1674 tsr() { # ts run
1675 "$@" |& ts || return $?
1676 }
1677
1678 dup() {
1679 local ran_d
1680 ran_d=false
1681 system-status _
1682 case $PS1 in
1683 *[\ \]]D\ *)
1684 pushd /
1685 /b/ds/distro-begin |& ts || return $?
1686 /b/ds/distro-end |& ts || return $?
1687 popd
1688 ran_d=true
1689 ;;&
1690 *[\ \]]DB\ *)
1691 pushd /
1692 /b/ds/distro-begin |& ts || return $?
1693 popd
1694 ran_d=true
1695 ;;
1696 *[\ \]]DE\ *)
1697 pushd /
1698 /b/ds/distro-end |& ts || return $?
1699 popd
1700 ran_d=true
1701 ;;&
1702 *CONFLINK*)
1703 if ! $ran_d; then
1704 conflink
1705 fi
1706 ;;
1707 esac
1708 system-status _
1709 }
1710
1711 envload() { # load environment from a previous: export > file
1712 local file=${1:-$HOME/.${USER}_env}
1713 eval "$(export | sed 's/^declare -x/export -n/')"
1714 while IFS= read -r line; do
1715 # declare -x makes variables local to a function
1716 eval ${line/#declare -x/export}
1717 done < "$file"
1718 }
1719
1720 failfunc() { asdf a b c; }
1721 failfunc2() { failfunc d e f; }
1722
1723 # one that comes with distros is too old for newer devices
1724 fastboot() {
1725 /a/opt/android-platform-tools/fastboot "$@";
1726 }
1727
1728 kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
1729
1730 bat() {
1731 cat /sys/class/power_supply/BAT0/capacity
1732 }
1733
1734 # List of apps to install/update
1735 # Create from existing manually installed apps by doing
1736 # fdroidcl update
1737 # fdroidcl search -i, then manually removing
1738 # automatically installed/preinstalled apps
1739
1740 #
1741 # # my attempt at recovering from boot loop:
1742 # # in that case, boot to recovery (volume up, home button, power, let go of power after samsun logo)
1743 # # then
1744 # mount /dev/block/mmcblk0p12 /data
1745 # cd /data
1746 # find -iname '*appname*'
1747 # rm -rf FOUND_DIRS
1748 # usually good enough to just rm -rf /data/app/APPNAME
1749 #
1750 # currently broken:
1751 # # causes replicant to crash
1752 # org.quantumbadger.redreader
1753 # org.kde.kdeconnect_tp
1754
1755 # not broke, but wont work without gps
1756 #com.zoffcc.applications.zanavi
1757 # not broke, but not using atm
1758 #com.nutomic.syncthingandroid
1759 # # doesn\'t work on replicant
1760 #net.sourceforge.opencamera
1761 #
1762 fdroid_pkgs=(
1763 net.mullvad.mullvadvpn
1764 org.schabi.newpipe
1765 io.github.subhamtyagi.lastlauncher
1766 io.anuke.mindustry
1767 com.biglybt.android.client
1768 de.marmaro.krt.ffupdater
1769 me.ccrama.redditslide
1770 org.fedorahosted.freeotp
1771 at.bitfire.davdroid
1772 com.alaskalinuxuser.justnotes
1773 com.artifex.mupdf.viewer.app
1774 com.danielkim.soundrecorder
1775 com.fsck.k9
1776 com.ichi2.anki
1777 com.jmstudios.redmoon
1778 com.jmstudios.chibe
1779 org.kde.kdeconnect_tp
1780 com.notecryptpro
1781 com.termux
1782 cz.martykan.forecastie
1783 de.danoeh.antennapod
1784 de.blinkt.openvpn
1785 de.marmaro.krt.ffupdater
1786 eu.siacs.conversations
1787 free.rm.skytube.oss
1788 im.vector.alpha # riot
1789 info.papdt.blackblub
1790 me.tripsit.tripmobile
1791 net.gaast.giggity
1792 net.minetest.minetest
1793 net.osmand.plus
1794 org.isoron.uhabits
1795 org.linphone
1796 org.gnu.icecat
1797 org.smssecure.smssecure
1798 org.yaaic
1799 sh.ftp.rocketninelabs.meditationassistant.opensource
1800 )
1801 # https://forum.xda-developers.com/android/software-hacking/wip-selinux-capable-superuser-t3216394
1802 # for maru,
1803 #me.phh.superuser
1804
1805 fdup() {
1806 local -A installed updated
1807 local p
1808 # tried putting this in go buildscript cronjob,
1809 # but it failed with undefined: os.UserCacheDir. I expect its due to
1810 # an environment variable missing, but its easier just to stick it here.
1811 m go get -u mvdan.cc/fdroidcl || return 1
1812 m fdroidcl update
1813 if fdroidcl search -u | grep ^org.fdroid.fdroid; then
1814 fdroidcl install org.fdroid.fdroid
1815 sleep 5
1816 m fdroidcl update
1817 fi
1818 for p in $(fdroidcl search -i| grep -o "^\S\+"); do
1819 installed[$p]=true
1820 done
1821 for p in $(fdroidcl search -u| grep -o "^\S\+"); do
1822 updated[$p]=false
1823 done
1824 for p in ${fdroid_pkgs[@]}; do
1825 if ! ${installed[$p]:-false}; then
1826 m fdroidcl install $p
1827 # sleeps are just me being paranoid since replicant has a history of crashing when certain apps are installed
1828 sleep 5
1829 fi
1830 done
1831 for p in ${!installed[@]}; do
1832 if ! ${updated[$p]:-true}; then
1833 m fdroidcl install $p
1834 sleep 5
1835 fi
1836 done
1837 }
1838
1839 firefox-default-profile() {
1840 local key value section
1841 key=Default
1842 value=1
1843 section=$1
1844 file=/p/c/subdir_files/.mozilla/firefox/profiles.ini
1845 sed -ri "/^ *$key/d" "$file"
1846 sed -ri "/ *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d};/ *\[$section\]/a $key=$value" "$file"
1847 }
1848 fdhome() { #firefox default home profile
1849 firefox-default-profile Profile0
1850 }
1851
1852 fdwork() {
1853 firefox-default-profile Profile4
1854 }
1855
1856 ff() {
1857 if type -P firefox &>/dev/null; then
1858 firefox "$@"
1859 else
1860 iceweasel "$@"
1861 fi
1862 }
1863
1864 fn() {
1865 firefox -P alt "$@" >/dev/null 2>&1
1866 }
1867
1868
1869 fsdiff () {
1870 local missing=false
1871 local dname="${PWD##*/}"
1872 local m="/a/tmp/$dname-missing"
1873 local d="/a/tmp/$dname-diff"
1874 [[ -e $d ]] && rm "$d"
1875 [[ -e $m ]] && rm "$m"
1876 local msize=0
1877 local fsfile
1878 while read -r line; do
1879 fsfile="$1${line#.}"
1880 if [[ -e "$fsfile" ]]; then
1881 md5diff "$line" "$fsfile" && tee -a "/a/tmp/$dname-diff" <<< "$fsfile $line"
1882 else
1883 missing=true
1884 echo "$line" >> "$m"
1885 msize=$((msize + 1))
1886 fi
1887 done < <(find . -type f )
1888 if $missing; then
1889 echo "$m"
1890 (( msize <= 100 )) && cat $m
1891 fi
1892 }
1893 fsdiff-test() {
1894 local tmpd x
1895 # expected output, with different tmp dirs
1896 # /tmp/tmp.HDPbwMqdC9/c/d ./c/d
1897 # /a/tmp/tmp.qLDkYxBYPM-missing
1898 # ./b
1899 tmpd="$(mktemp -d)"
1900 cd "$tmpd"
1901 echo ok > a
1902 echo nok > b
1903 mkdir c
1904 echo ok > c/d
1905 local x
1906 x=$(mktemp -d)
1907 mkdir $x/c
1908 echo different > $x/c/d
1909 echo ok > $x/a
1910 fsdiff $x
1911 rm -r "$x" "$tmpd"
1912 }
1913 rename-test() {
1914 # test whether missing files were renamed, generally for use with fsdiff
1915 # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir
1916 # echos non-renamed files
1917 local x y found
1918 unset sums
1919 for x in "$2"/*; do
1920 { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null
1921 done
1922 while read -r line; do
1923 { missing_sum=$(md5sum < "$line") ; } 2>/dev/null
1924 renamed=false
1925 for x in "${sums[@]}"; do
1926 if [[ $missing_sum == "$x" ]]; then
1927 renamed=true
1928 break
1929 fi
1930 done
1931 $renamed || echo "$line"
1932 done < "$1"
1933 return 0
1934 }
1935
1936 feh() {
1937 # F = fullscren, z = random, Z = auto zoom
1938 command feh -FzZ "$@"
1939 }
1940
1941
1942
1943 fw() {
1944 firefox -P default "$@" >/dev/null 2>&1
1945 }
1946
1947 gitian() {
1948 git config user.email ian@iankelling.org
1949 }
1950
1951 # at least in flidas, things rely on gpg being gpg1
1952 gpg() {
1953 if type -P gpg2 &>/dev/null; then
1954 command gpg2 "$@"
1955 else
1956 command gpg "$@"
1957 fi
1958 }
1959
1960 gse() {
1961 local email=ian@iankelling.org
1962 git send-email --notes "--envelope-sender=<$email>" \
1963 --suppress-cc=self "$@"
1964 }
1965
1966 gup() { /a/f/gnulib/build-aux/gnupload "$@"; }
1967
1968 dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
1969
1970 hstatus() {
1971 # do git status on published repos.
1972 c /a/bin/githtml
1973 for x in *; do
1974 cd "$(readlink -f $x)"/..
1975 status=$(i status -s) || pwd
1976 if [[ $status ]]; then
1977 hr
1978 echo $x
1979 printf "%s\n" "$status"
1980 fi
1981 cd /a/bin/githtml
1982 done
1983 }
1984
1985 # work log
1986 wlog() {
1987 local day i days_back
1988 days_back=${1:-16}
1989 for (( i=0; i<days_back; i++ )); do
1990 day=$( date +%F -d @$((EPOCHSECONDS - 86400*i )) )
1991 date "+%a %b %d" -d @$((EPOCHSECONDS - 86400*i )) | tr '\n' ' '
1992 /a/opt/timetrap/bin/t d -ftotal -s $day -e $day all -m '^w|lunch$'
1993 done
1994 }
1995 to() { t out -a "$@"; }
1996 ti() { t in -a "$@"; }
1997 tl() {
1998 local in_secs
1999 to "$*"
2000 t s lunch
2001 t in -a "$*"
2002 in_secs="$(date -d "${*//[_.]/ }" +%s)"
2003 m t out -a "$(date +%F.%T -d @$(( in_secs + 60*45 )) )"
2004 t s w
2005 }
2006
2007 focus() {
2008 /p/c/proc/focus/linux-amd64/focus &
2009 watcharb5
2010 kill %%
2011 }
2012
2013
2014 watcharb5() {
2015 local char ret
2016 killall arbtt-capture ||:
2017 rm -f ~/.arbtt/capture.log
2018 arbtt-capture --sample-rate=10 &
2019 clear
2020 while true; do
2021 arb5
2022 ret=0
2023 # i first thought to sleep and capture ctrl-c, but it seems we can't
2024 # capture control-c, unless maybe we implement the commands in a
2025 # separate script or maybe add err-cleanup to err. Anyways, this
2026 # method is superior because any single char exits.
2027 read -rsN1 -t 5 char || ret=$?
2028 if (( ret == 142 )) || [[ ! $char ]]; then
2029 # debug
2030 #e ret=$ret char=$char
2031 :
2032 else
2033 killall arbtt-capture ||:
2034 return 0
2035 fi
2036 clear
2037 done
2038
2039 }
2040
2041 arb5() {
2042 local i l sec
2043 i=0
2044 if [[ ! -e ~/.arbtt/capture.log ]]; then
2045 sleep 5
2046 fi
2047 # https://stackoverflow.com/questions/56486272/how-to-concat-multiple-fields-to-same-line-with-jq
2048 arbtt-dump -l 30 -t json | jq -r '.[] | [ ( .inactive / 1000 | floor ) , ( .windows[] | select (.active == true) |.title) ] | @tsv' \
2049 | tac | while read -r sec l; do
2050 if (( i % 6 == 0 && i >= 2 )); then
2051 echo == $(( i / 6 + 1 )) ==
2052 fi
2053 if (( sec > 10 )); then
2054 printf "%3d %s\n" $sec "$l"
2055 else
2056 printf " %s\n" "$l"
2057 fi
2058 i=$(( i + 1 ))
2059 done
2060 }
2061
2062 arbttlog() {
2063 # from the log, show only the currently active window, and the number of
2064 # seconds of input inactivity.
2065 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}' ; }
2066
2067 idea() {
2068 /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
2069 }
2070
2071 ilogs-local() {
2072 cd /var/lib/znc/moddata/log/iank/freenode/
2073 hr
2074 for x in "#$1/"*; do
2075 base=${x##*/}
2076 files=()
2077 for f in $@; do
2078 tmp=\#$f/$base
2079 if [[ -e $tmp ]]; then
2080 files+=(\#$f/$base)
2081 fi
2082 done
2083 sed \"s/^./${base%log}/\" ${files[@]}|sort -n
2084 hr
2085 done
2086 }
2087 ilogs() {
2088 sl root@iankelling.org ilogs-local "$@"
2089 }
2090
2091
2092 ilog-local() {
2093 local d chan
2094 chan="$1"
2095 d=/var/lib/znc/moddata/log/iank/
2096 for n in freenode libera; do
2097 cd $d$n/"$chan" && hr
2098 for x in *; do
2099 echo $x; sed "s/^./${x%log}/" $x; hr;
2100 done
2101 done
2102 }
2103 ilog() {
2104 local chan
2105 chan="${1:-#fsfsys}"
2106 # use * instead of -r since that does sorted order
2107 sl root@iankelling.org ilog-local "$chan" | less +G
2108 }
2109
2110 o() {
2111 if type gio &> /dev/null ; then
2112 gio open "$@"
2113 elif type gvfs-open &> /dev/null ; then
2114 gvfs-open "$@"
2115 else
2116 xdg-open "$@"
2117 fi
2118 # another alternative is run-mailcap
2119 }
2120 ccomp xdg-open o
2121
2122 # jfilter() {
2123 # grep -Evi -e "^(\S+\s+){4}(sudo|sshd|cron)\[\S*:" \
2124 # -e "^(\S+\s+){4}systemd\[\S*: (starting|started) (btrfsmaintstop|dynamicipupdate|spamd dns bug fix cronjob|rss2email)\.*$"
2125 # }
2126 # jtail() {
2127 # journalctl -n 10000 -f "$@" | jfilter
2128 # }
2129 # jr() { journalctl "$@" | jfilter | less ; }
2130 # jrf() { journalctl -n 200 -f "$@" | jfilter; }
2131
2132 jr() { journalctl "$@" ; }
2133 jrf() { journalctl -n 200 -f "$@" ; }
2134
2135
2136 ccomp journalctl jtail jr jrf
2137
2138 ## old version for model01. i need to get that firmware working again.
2139 # kff() { # keyboardio firmware flash. you must hold down the tilde key
2140 # pushd /a/opt/Model01-Firmware
2141 # # if we didn't want this yes hack, then remove "shell read" from
2142 # # /a/opt/Kaleidoscope/etc/makefiles/sketch.mk
2143 # yes $'\n' | VERBOSE=1 make flash
2144 # popd
2145 # }
2146
2147
2148 kff() {
2149 pushd /a/opt/Kaleidoscope/examples/Devices/Keyboardio/Model100
2150 make flash
2151 popd
2152 }
2153
2154 wgkey() {
2155 local umask_orig name
2156 if (( $# != 1 )); then
2157 e expected 1 arg >&2
2158 return 1
2159 fi
2160 name=$1
2161 umask_orig=$(umask)
2162 umask 0077
2163 wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key
2164 umask $umask_orig
2165 }
2166 wghole() {
2167 if (( $# != 2 )); then
2168 e expected 2 arg of hostname, ip suffix >&2
2169 return 1
2170 fi
2171 local host ipsuf umask_orig
2172 host=$1
2173 ipsuf=$2
2174 mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
2175 (
2176 cd /p/c/machine_specific/$host/filesystem/etc/wireguard
2177 umask_orig=$(umask)
2178 umask 0077
2179 wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
2180 cat >wghole.conf <<EOF
2181 [Interface]
2182 # contents hole-priv.key
2183 PrivateKey = $(cat hole-priv.key)
2184 ListenPort = 1194
2185 Address = 10.8.0.$ipsuf/24
2186 # https://dev.to/tangramvision/what-they-don-t-tell-you-about-setting-up-a-wireguard-vpn-1h2g
2187 # ||: makes the systemd service not fail due to the failed command
2188 PostUp = ping -c1 10.8.0.1 ||:
2189
2190 [Peer]
2191 # li. called wgmail on that server
2192 PublicKey = CTFsje45qLAU44AbX71Vo+xFJ6rt7Cu6+vdMGyWjBjU=
2193 AllowedIPs = 10.8.0.0/24
2194 Endpoint = 72.14.176.105:1194
2195 PersistentKeepalive = 25
2196 EOF
2197 umask $umask_orig
2198 # old approach. systemd seems to work fine and cleaner.
2199 rm -f ../network/interfaces.d/wghole
2200 cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
2201 [Peer]
2202 PublicKey = $(cat hole-pub.key)
2203 AllowedIPs = 10.8.0.$ipsuf/32
2204 EOF
2205 )
2206 }
2207
2208
2209 mns() { # mount namespace
2210 ns=$1
2211 shift
2212 s mkdir -p /root/mount_namespaces
2213 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
2214 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
2215 fi
2216 m sudo mount --make-private /root/mount_namespaces
2217 if [[ ! -e /root/mount_namespaces/$ns ]]; then
2218 m sudo touch /root/mount_namespaces/$ns
2219 fi
2220 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
2221 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
2222 fi
2223 m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
2224 }
2225
2226 mnsr() { # mns run
2227 local ns=$1
2228 shift
2229 mns $ns sudo -u iank -E env "PATH=$PATH" "$@"
2230 }
2231
2232 mnsnonet() {
2233 ns=$1
2234 lomh
2235 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
2236 s ip netns add nonet
2237 fi
2238 mns $ns --net=/var/run/netns/nonet sudo -E -u iank /bin/bash
2239 lomh
2240 }
2241
2242
2243 lom() {
2244 # l = the loopback device
2245 local l base
2246 if [[ $1 == /* ]]; then
2247 base=${1##*/}
2248 fs_file=$1
2249 if mns $base mountpoint -q /mnt/$base; then
2250 return 0
2251 fi
2252 l=$(losetup -j $fs_file | sed -rn 's/^([^ ]+): .*/\1/p' | head -n1 ||:)
2253 if [[ ! $l ]]; then
2254 l=$(sudo losetup -f)
2255 m sudo losetup $l $fs_file
2256 fi
2257 if ! sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2258 if ! m sudo cryptsetup luksOpen $l $base; then
2259 m sudo losetup -d $l
2260 return 1
2261 fi
2262 fi
2263 m sudo mkdir -p /mnt/$base
2264 m mns $base mount /dev/mapper/$base /mnt/$base
2265 m mns $base chown $USER:$USER /mnt/$base
2266 lomh
2267 else
2268 base=$1
2269 if mns $base mountpoint /mnt/$base &>/dev/null; then
2270 m mns $base umount /mnt/$base
2271 fi
2272 if sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
2273 if ! m sudo cryptsetup luksClose /dev/mapper/$base; then
2274 echo lom: failed cryptsetup luksClose /dev/mapper/$base
2275 return 1
2276 fi
2277 fi
2278 l=$(losetup -l --noheadings | awk '$6 ~ /\/'$base'$/ {print $1}')
2279 if [[ $l ]]; then
2280 m sudo losetup -d $l
2281 else
2282 echo lom: warning: no loopback device found
2283 fi
2284 fi
2285 }
2286
2287 # mu personality. for original, just run mp. for 2, run mp 2.
2288 # this is partly duplicated in mail-setup
2289 mp() {
2290 local dead=false
2291 for s in {1..5}; do
2292 if ! killall mu; then
2293 dead=true
2294 break
2295 fi
2296 sleep 1
2297 done
2298 if ! $dead; then
2299 echo error: mu not dead
2300 m psg mu
2301 return 1
2302 fi
2303 suf=$1
2304 set -- /m/mucache ~/.cache/mu /m/.mu ~/.config/mu
2305 while (($#)); do
2306 target=$1$suf
2307 f=$2
2308 shift 2
2309 if [[ -e $f && ! -L $f ]]; then
2310 m rm -rf $f
2311 fi
2312 m ln -sf -T $target $f
2313 done
2314 }
2315
2316 # maildir enable
2317 mdenable() {
2318 local md dst ln_path src two
2319
2320 two=false
2321 case $1 in
2322 -2) two=true shift ;;
2323 esac
2324
2325 for md; do
2326 src=
2327 if $two; then
2328 dst=/m/4e2/$md
2329 else
2330 dst=/m/4e/$md
2331 fi
2332
2333 ln_path=/m/md/$md
2334 for d in /m/md/$md /m/4e2/$md; do
2335 if [[ -d $d && ! -L $d ]]; then
2336 src=$d
2337 break
2338 fi
2339 done
2340 if [[ ! $src ]]; then
2341 echo "error: could not find $md" >&2
2342 return 1
2343 fi
2344 m mv -T $src $dst
2345 m ln -sf -T $dst $ln_path
2346 done
2347 }
2348 md2enable() {
2349 mdenable -2 "$@"
2350 }
2351 mddisable() {
2352 local md=$1
2353 dst=/m/md/$md
2354
2355 ### begin copied from mdenable, but different d ###
2356 for d in /m/4e/$md /m/4e2/$md; do
2357 if [[ -d $d && ! -L $d ]]; then
2358 src=$d
2359 break
2360 fi
2361 done
2362 if [[ ! $src ]]; then
2363 echo "error: could not find $md" >&2
2364 return 1
2365 fi
2366 ### end copy from mdenable ###
2367
2368 if [[ -L $dst ]]; then m rm $dst; fi
2369 m mv -T $src $dst
2370 }
2371
2372
2373 mdt() {
2374 markdown "$1" >/tmp/mdtest.html
2375 firefox /tmp/mdtest.html
2376 }
2377
2378 mo() { xset dpms force off; } # monitor off
2379
2380 mpvgpu() {
2381 # seems to be the best gpu decoding on my nvidia 670.
2382 # vlc gets similar or better framerate, but is much darker output on my test movie at least.
2383
2384
2385 case $HOSTNAME in
2386 kd)
2387 echo 0f | sudo tee -a /sys/kernel/debug/dri/0/pstate
2388 ;;
2389 esac
2390 # going back to the default slow clock, and slower fan:
2391 # echo 07 | sudo tee -a /sys/kernel/debug/dri/0/pstate
2392 if [[ $DISPLAY ]]; then
2393 mpv --vo=vdpau --hwdec=auto "$@"
2394 else
2395 # waylandvk seems to work the same
2396 mpv --gpu-context=wayland --hwdec=auto
2397 fi
2398 }
2399
2400 mpvd() {
2401 mpv --profile=d "$@";
2402 }
2403 # mpv all media files in . or $1
2404 mpvm() {
2405 local -a extensions arg
2406 # get page source of https://en.wikipedia.org/w/index.php?title=Video_file_format&action=edit
2407 # into /a/x.log, then
2408 # grep '^| *\.' /a/x.log | sed 's/| *//;s/,//g'
2409
2410 # note: to join them together for a regex, do:
2411 # old=; for e in ${extensions[@]/./}; do if [[ ! $old ]]; then old=$e; continue; fi; echo -n "$old|"; old=$e; done; echo $e
2412 extensions=(
2413 .webm
2414 .mkv
2415 .flv
2416 .flv
2417 .vob
2418 .ogv .ogg
2419 .drc
2420 .gif
2421 .gifv
2422 .mng
2423 .avi
2424 .MTS .M2TS .TS
2425 .mov .qt
2426 .wmv
2427 .yuv
2428 .rm
2429 .rmvb
2430 .viv
2431 .asf
2432 .amv
2433 .mp4 .m4p .m4v
2434 .mpg .mp2 .mpeg .mpe .mpv
2435 .mpg .mpeg .m2v
2436 .m4v
2437 .svi
2438 .3gp
2439 .3g2
2440 .mxf
2441 .roq
2442 .nsv
2443 )
2444 arg=("(" -iname "*${extensions[0]}")
2445 for (( i=1 ; i < ${#extensions[@]}; i++ )); do
2446 arg+=(-o -iname "*${extensions[i]}")
2447 done
2448 arg+=(")")
2449 dir=${1:-.}
2450 # debug:
2451 #find $dir "${arg[@]}" -size +200k
2452 find $dir "${arg[@]}" -size +200k -exec mpv --profile=d '{}' +
2453 }
2454 mpvs() {
2455 mpv --profile=s "$@";
2456 }
2457
2458 myirc() {
2459 if [[ ! $1 ]]; then
2460 set -- fsf-office
2461 fi
2462 local -a d
2463 d=( /var/lib/znc/moddata/log/iank/{freenode,libera} )
2464 # use * instead of -r since that does sorted order
2465 ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\<iank.*' *; done" | cut --complement -c12-16
2466 }
2467 mypidgin() {
2468 c /p/c/.purple/logs/jabber/iank@fsf.org/office@conference.fsf.org.chat
2469 for x in *.html; do html2text -o ${x%.html}.txt $x; done;
2470 # shellcheck disable=SC2016 # false positive on ${
2471 grep -A1 ') iank:' ./*.txt \
2472 | sed -r 's/^(.{10})[^ ]*\.txt:\(?([^ ]*)[[:space:]](..). iank:/\1_\2_\3/
2473 s/^[^ ]*\.txt-//
2474 /^--$/d
2475 s/^[^ ]*\.txt:\((.{2}).(.{2}).(.{4}) (.{8}) (.{2})\)?/\3-\1-\2_\4_\5/' \
2476 | sed -n 'x;1d;0~2{G;s/\n/ /;p};${x;p}'
2477 }
2478 allmyirc() {
2479 local d
2480 d=/var/lib/znc/moddata/log/iank/freenode
2481 ssh root@iankelling.org "cd $d; find . -mtime -60 -type f -exec grep '\<iank.*' {} +" | sed -r 's,^..([^/]*)/(.{11})(.{5})(.{8}).,\2\4 \1,' | sort
2482 }
2483
2484 # usage: debvm DEBIAN_VERSION RAM_MB
2485 debvm() {
2486 local ver ram fname src
2487 ver=$1
2488 ram=${2:-2024}
2489 # * is because it might have -backports in the name. we only expect 1 expansion
2490 fnames=( debian-$ver-*nocloud-"$(dpkg --print-architecture)".qcow2 )
2491 if (( ${#fnames[@]} >= 2 )); then
2492 echo "error: iank: unexpected multiple files"
2493 return 1
2494 fi
2495 fname="${fnames[0]}"
2496 src=/a/opt/roms/$fname
2497 if [[ ! -f $src ]]; then
2498 echo debvm: not found $src, download from eg: https://cloud.debian.org/images/cloud/buster/latest/
2499 return 1
2500 fi
2501 cp -a $src /t
2502 # note, in fai-revm we do this: not sure why, maybe because of br device
2503 # --graphics spice,listen=0.0.0.0
2504 m s virt-install --osinfo debian11 --rng /dev/urandom -n deb${ver}tmp --import -r $ram --vcpus 2 --disk /t/$fname --graphics spice
2505 # note: to ssh into this machine will require host key generation: ssh-keygen -A
2506
2507 # random: for cvs2git on gnu www, use debian 10. I could use trisquel,
2508 # but happen to want to try out the debian cloud images. the upstream
2509 # requires python2 and hasn't really changed since the version in d10.
2510 #
2511 # apt install cvs2git cvs
2512 # # 7G was not enough
2513 # mount -o mode=1777,nosuid,nodev,size=34G -t tmpfs tmpfs /tmp
2514 # cvs2git --encoding utf_8 --fallback-encoding ascii --dumpfile=dump www-rsync/www |& tee /tmp/l
2515 ## www-rsync is an rsynced copy of the cvsfrom savannah
2516 }
2517
2518 mygajim() {
2519 local time time_sec time_pretty days
2520 days=${1:-16}
2521 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
2522 case $time in
2523 16*) : ;;
2524 *) continue ;;
2525 esac
2526 if ! time_pretty=$(date +%F.%R -d @$time); then
2527 echo bad time: $time
2528 return 1
2529 fi
2530 echo $time_pretty "$l"
2531 time_sec=${time%%.*}
2532 # only look at the last 18 days. generally just use this for timesheet.
2533 if (( time_sec < EPOCHSECONDS - 60 * 60 * 24 * days )); then break; fi
2534 done
2535 }
2536
2537 allmygajim() {
2538 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank'" | less
2539 }
2540
2541 gajlogs() {
2542 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs" | less
2543 }
2544
2545
2546 net-dev-info() {
2547 e "lspci -nnk|gr -iA2 net"
2548 lspci -nnk|gr -iA2 net
2549 hr
2550 e "s lshw -C network"
2551 hr
2552 sudo lshw -C network
2553 }
2554
2555 nk() {
2556 ser stop NetworkManager
2557 ser disable NetworkManager
2558 ser stop NetworkManager-wait-online.service
2559 ser disable NetworkManager-wait-online.service
2560 ser stop dnsmasq
2561 sudo resolvconf -d NetworkManager
2562 # ser start dnsmasq
2563 sudo ifup br0
2564 }
2565 ngo() {
2566 sudo ifdown br0
2567 ser start NetworkManager
2568 sleep 4
2569 sudo nmtui-connect
2570 }
2571
2572 otp() {
2573 oathtool --totp -b "$*" | xclip -selection clipboard
2574 }
2575 j() {
2576 "$@" |& pee "xclip -r -selection clipboard"
2577 }
2578
2579
2580 pakaraoke() {
2581 # from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals
2582 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
2583 }
2584
2585 pfind() { #find *$1* in $PATH
2586 [[ $# != 1 ]] && { echo requires 1 argument; return 1; }
2587 local pathArray
2588 IFS=: pathArray=($PATH); unset IFS
2589 find "${pathArray[@]}" -iname "*$1*"
2590 }
2591
2592 pick-trash() {
2593 # trash-restore lists everything that has been trashed at or below CWD
2594 # This picks out files just in CWD, not subdirectories,
2595 # which also match grep $1, usually use $1 for a time string
2596 # which you get from running restore-trash once first
2597 local name x ask
2598 local nth=1
2599 # last condition is to not ask again for ones we skipped
2600 while name="$( echo | restore-trash | gr "$PWD/[^/]\+$" | gr "$1" )" \
2601 && [[ $name ]] && (( $(wc -l <<<"$name") >= nth )); do
2602 name="$(echo "$name" | head -n $nth | tail -n 1 )"
2603 read -r -p "$name [Y/n] " ask
2604 if [[ ! $ask || $ask == [Yy] ]]; then
2605 x=$( echo "$name" | gr -o "^\s*[0-9]*" )
2606 echo $x | restore-trash > /dev/null
2607 elif [[ $ask == [Nn] ]]; then
2608 nth=$((nth+1))
2609 else
2610 return
2611 fi
2612 done
2613 }
2614
2615
2616 pub() {
2617 rld /a/h/_site/ li:/var/www/iankelling.org/html
2618 }
2619
2620
2621 pumpa() {
2622 # fixes the menu bar in xmonad. this won\'t be needed when xmonad
2623 # packages catches up on some changes in future (this is written in
2624 # 4/2017)
2625 #
2626 # geekosaur: so youll want to upgrade to xmonad 0.13 or else use a
2627 # locally modified XMonad.Hooks.ManageDocks that doesnt set the
2628 # work area; turns out it\'s impossible to set correctly if you are
2629 # not a fully EWMH compliant desktop environment
2630 #
2631 # geekosaur: chrome shows one failure mode, qt/kde another, other
2632 # gtk apps a third, ... I came up with a setting that works for me
2633 # locally but apparently doesnt work for others, so we joined the
2634 # other tiling window managers in giving up on setting it at all
2635 #
2636 xprop -root -remove _NET_WORKAREA
2637 command pumpa & r
2638 }
2639
2640 # reviewboard, used at my old job
2641 #rbpipe() { rbt post -o --diff-filename=- "$@"; }
2642 #rbp() { rbt post -o "$@"; }
2643
2644 rebr() {
2645 sudo ifdown br0
2646 sudo ifup br0
2647 }
2648
2649
2650 r2e() { command r2e -d /p/c/rss2email.json -c /p/c/rss2email.cfg "$@"; }
2651 # only run on MAIL_HOST. simpler to keep this on one system.
2652 r2eadd() { # usage: name url
2653 # initial setup of rss2email:
2654 # r2e new r2e@iankelling.org
2655 # that initializes files, and sets default email.
2656 # symlink to the config doesnt work, so I copied it to /p/c
2657 # and then use cli option to specify explicit path.
2658 # Only option changed from default config is to set
2659 # force-from = True
2660 #
2661 # or else for a few feeds, the from address is set by the feed, and
2662 # if I fail delivery, then I send a bounce message to that from
2663 # address, which makes me be a spammer.
2664
2665 r2e add $1 "$2" $1@r2e.iankelling.org
2666 # get up to date and dont send old entries now:
2667 r2e run --no-send $1
2668 }
2669
2670 rspicy() { # usage: HOST DOMAIN
2671 # connect to spice vm remote host. use vspicy for local host
2672 local port
2673 # shellcheck disable=SC2087
2674 port=$(ssh $1<<EOF
2675 sudo virsh dumpxml $2|grep "<graphics.*type='spice'" | \
2676 sed -rn "s/.*port='([0-9]+).*/\1/p"
2677 EOF
2678 )
2679 if [[ $port ]]; then
2680 spicy -h $1 -p $port
2681 else
2682 echo "error: no port found. check that the domain is running."
2683 fi
2684 }
2685
2686
2687 scssl() {
2688 # s gem install scss-lint
2689 pushd /a/opt/thoughtbot-guides
2690 git pull --stat
2691 popd
2692 scss-lint -c /a/opt/thoughtbot-guides/style/sass/.scss-lint.yml "$@"
2693 }
2694
2695 skbrc() {
2696 sk -e 2120,245 /b/ds/brc /b/ds/brc2
2697 }
2698
2699 skaraoke() {
2700 local tmp out
2701 out=${2:-${1%.*}.sh}
2702 tmp=$(mktemp -d)
2703 script -t -c "mpv --no-config --no-resume-playback --no-terminal --no-audio-display '$1'" $tmp/typescript 2>$tmp/timing
2704 # todo, the current sleep seems pretty good, but it
2705 # would be nice to have an empirical measurement, or
2706 # some better wait to sync up.
2707 #
2708 # note: --loop-file=no prevents it from hanging if you have that
2709 # set to inf the mpv config.
2710 # --loop=no prevents it from exit code 3 due to stdin if you
2711 # had it set to inf in mpv config.
2712 #
2713 # args go to mpv, for example --volume=80, 50%
2714 cat >$out <<EOFOUTER
2715 #!/bin/bash
2716 trap "trap - TERM && kill 0" INT TERM ERR; set -e
2717 ( sleep .2; scriptreplay <( cat <<'EOF'
2718 $(cat $tmp/timing)
2719 EOF
2720 ) <( cat <<'EOF'
2721 $(cat $tmp/typescript)
2722 EOF
2723 ))&
2724 base64 -d - <<'EOF'| mpv --loop=no --loop-file=no --no-terminal --no-audio-display "\$@" -
2725 $(base64 "$1")
2726 EOF
2727 kill 0
2728 EOFOUTER
2729 rm -r $tmp
2730 chmod +x $out
2731 }
2732
2733 smeld() { # ssh meld usage host1 host2 file
2734 meld <(ssh $1 cat $3) <(ssh $2 cat $3)
2735 }
2736
2737 spd() {
2738 PATH=/usr/local/spdhackfix:$PATH command spd "$@"
2739 }
2740
2741 spamf() { # spamtest on FILE
2742 local spamcpre spamdpid
2743
2744 if (( $# != 1 )); then
2745 e spamtest error: expected 1 arg, filename >&2
2746 return 1
2747 fi
2748
2749 spamdpid=$(systemctl status spamassassin| sed -n '/^ *Main PID:/s/[^0-9]//gp')
2750 spamcpre="nsenter -t $spamdpid -n -m"
2751 s $spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
2752 }
2753
2754
2755 # mail related
2756 testmail() {
2757 declare -gi _seq; _seq+=1
2758 echo "test body" | m mail -s "test mail from $HOSTNAME, $_seq" "${@:-root@localhost}"
2759 # for testing to send from an external address, you can do for example
2760 # -fian@iank.bid -aFrom:ian@iank.bid web-6fnbs@mail-tester.com
2761 # note in exim, you can retry a deferred message
2762 # s exim -M MSG_ID
2763 # MSG_ID is in /var/log/exim4/mainlog, looks like 1ccdnD-0001nh-EN
2764 }
2765
2766 # to test sieve, use below command. for fsf mail, see offlineimap-sync script
2767 # make modifications, then copy to live file, use -eW to actually modify mailbox
2768 #
2769 # Another option is to use sieve-test SCRIPT MAIL_FILE. note,
2770 # sieve-test doesnt know about envelopes, Im not sure if sieve-filter does.
2771
2772 # sieve with output filter. arg is mailbox, like INBOX.
2773 # This depends on dovecot conf, notably mail_location in /etc/dovecot/conf.d/10-mail.conf
2774
2775 # always run this first, edit the test files, then run the following
2776 testsieve() {
2777 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
2778 }
2779 runsieve() {
2780 c ~/sieve; cp personal{test,}.sieve; cp lists{test,}.sieve; cp personalend{test,}.sieve
2781 sieve-filter -eWv ~/sieve/maintest.sieve ${1:-INBOX} delete &> /tmp/testsieve.log
2782 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
2783 }
2784
2785 # usage:
2786 # alertme SUBJECT
2787 # printf "subject\nbody\n" | alertme
2788 alertme() {
2789 if [[ -t 0 ]]; then
2790 exim -t <<EOF
2791 From: alertme@b8.nz
2792 To: alerts@iankelling.org
2793 Subject: $*
2794 EOF
2795 else
2796 read -r sub
2797 { cat <<EOF
2798 From: alertme@b8.nz
2799 To: alerts@iankelling.org
2800 Subject: $sub
2801
2802 EOF
2803 cat
2804 } | exim -t
2805 fi
2806 }
2807 daylertme() {
2808 if [[ -t 0 ]]; then
2809 exim -t <<EOF
2810 From: alertme@b8.nz
2811 To: daylert@iankelling.org
2812 Subject: $*
2813 EOF
2814 else
2815 read -r sub
2816 { cat <<EOF
2817 From: alertme@b8.nz
2818 To: daylert@iankelling.org
2819 Subject: $sub
2820
2821 EOF
2822 cat
2823 } | exim -t
2824 fi
2825 }
2826
2827 # alert when a page goes live.
2828 alert200() {
2829 local quiet url tmpdir
2830 quiet=false
2831 case $1 in
2832 # dont send a diff of the html. some html is not very readable
2833 -q) quiet=true
2834 shift
2835 ;;
2836 esac
2837 url="$1"
2838 tmpdir="$(mktemp -d)"
2839 cd $tmpdir
2840 while true; do
2841 if wget -q "$url"; then
2842 if $quiet; then
2843 echo | daylert 200
2844 else
2845 alertme $tmpdir
2846 fi
2847 fi
2848 sleep $(( 120 + RANDOM % 300 ))
2849 done
2850 }
2851
2852 # alert on changes to a webpage (just the base page that curl gets)
2853 # usage: weblert URL [SUBJECT...]
2854 weblert() {
2855 local u old new quiet
2856 quiet=false
2857 case $1 in
2858 # dont send a diff of the html. some html is not very readable
2859 -q) quiet=true
2860 shift
2861 ;;
2862 esac
2863 u="$1"
2864 shift
2865 subject="${*:-weblert}"
2866 old=$(curl -s "$u") ||:
2867 while true; do
2868 new=$(curl -s "$u") ||:
2869 if [[ $old && $new ]]; then
2870 if [[ $new != "$old" ]]; then
2871 if $quiet; then
2872 echo | daylertme "$subject"
2873 else
2874 diff <(printf "%s\n" "$old") <(printf "%s\n" "$new") | daylertme "$subject" ||:
2875 fi
2876 fi
2877 old="$new"
2878 fi
2879 sleep $(( 60 + RANDOM % 120 ))
2880 done
2881 }
2882
2883 torshell() {
2884 # per man torsocks
2885 # shellcheck disable=SC1090 # expected
2886 source "$(type -p torsocks)" on
2887 }
2888
2889 eless2() {
2890 less /var/log/exim4/mymain
2891 }
2892
2893
2894 # mail related
2895 testexim() {
2896 # testmail above calls sendmail, which is a link to exim/postfix.
2897 # its docs dont say a way of adding an argument
2898 # to sendmail to turn on debug output. We could make a wrapper, but
2899 # that is a pain. Exim debug args are documented here:
2900 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
2901 #
2902 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
2903 # note, for exim daemon, you can turn on debug options by
2904 # adding -d, etc to COMMONOPTIONS in
2905 # /etc/default/exim4
2906 #
2907 # to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
2908 # exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
2909 #
2910 # -t = get recipient from header
2911 exim -d -t <<EOF
2912 From: root@$(hostname -f)
2913 To: root@$(hostname -f)
2914 Subject: test2
2915
2916 This is a test message.
2917 EOF
2918 }
2919
2920 # test bounce exim
2921 testbexim() {
2922 to=$1
2923 exim -d -f '<>' $to <<EOF
2924 From: Mail Delivery System <Mailer-Daemon@gnu.org>
2925 To: $to
2926 Subject: Mail delivery failed: returning message to sender
2927
2928 This message was created automatically by mail delivery software.
2929 EOF
2930
2931 }
2932
2933
2934 # toggle keyboard
2935 tk() {
2936 # based on
2937 # https://askubuntu.com/questions/160945/is-there-a-way-to-disable-a-laptops-internal-keyboard
2938 id=$(xinput --list --id-only 'AT Translated Set 2 keyboard')
2939 if xinput list | grep -F '∼ AT Translated Set 2 keyboard' &>/dev/null; then
2940 echo enabling keyboard
2941 # find the first slave keyboard number, they are all the same in my output.
2942 # if they werent, worst case we would need to save the slave number somewhere
2943 # when it got disabled.
2944 slave=$(xinput list | sed -n 's/.*slave \+keyboard (\([0-9]*\)).*/\1/p' | head -n1)
2945 xinput reattach $id $slave
2946 else
2947 xinput float $id
2948 fi
2949 }
2950
2951 tm() {
2952 # timer in minutes
2953 # --no-config
2954 (sleep "$(calc "$* * 60")" && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
2955 }
2956
2957 trg() { transmission-remote-gtk & r; }
2958 trc() {
2959 # example, set global upload limit to 100 kilobytes:
2960 # trc -u 100
2961 TR_AUTH=":$(jq -r .profiles[0].password ~/.config/transmission-remote-gtk/config.json)" transmission-remote transmission.lan -ne "$@"
2962 }
2963
2964 trysleep() {
2965 retries="$1"
2966 sleepsecs="$2"
2967 shift 2
2968 for (( i=0; i < retries - 1; i++ )); do
2969 if "$@"; then
2970 return 0
2971 fi
2972 sleep $sleepsecs
2973 done
2974 "$@"
2975 }
2976
2977
2978 tu() {
2979 local s
2980 if [[ -e $1 && ! -w $1 || ! -w $(dirname "$1") ]]; then
2981 s=s;
2982 fi
2983 # full path for using in some initial setup steps
2984 $s /a/exe/teeu "$@"
2985 }
2986
2987 enn() {
2988 local ecmd pid
2989
2990 ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf"
2991 if ip a show veth1-mail &>/dev/null; then
2992 s $ecmd "$@"
2993 return
2994 fi
2995 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1)
2996 m s nsenter -t $pid -n -m $ecmd "$@"
2997 }
2998
2999 # get pid of systemd service
3000 servicepid() {
3001 local pid unit dir
3002 unit="$1"
3003 pid=$(systemctl show --property MainPID --value "$unit")
3004 case $pid in
3005 [1-9]*) : ;;
3006 *)
3007
3008 dir=/sys/fs/cgroup/system.slice
3009 if [[ ! -d $dir ]]; then
3010 # t10 and older directory.
3011 dir=/sys/fs/cgroup/systemd/system.slice
3012 fi
3013
3014 # 0 or empty. This file includes the MainPid, so I expect we
3015 # could just get this in the first place, but i don't know if that
3016 # is always the case.
3017 pid=$(head -n1 $dir/${unit%.service}.service/cgroup.procs)
3018 ;;
3019 esac
3020 if [[ $pid ]]; then
3021 printf "%s\n" "$pid"
3022 else
3023 return 1
3024 fi
3025 }
3026
3027 sdnbash() { # systemd namespace bash
3028 local unit pid
3029 if (( $# != 1 )); then
3030 echo $0: error wrong number of args >&2
3031 return 1
3032 fi
3033 unit=$1
3034 pid=$(servicepid $unit)
3035 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash
3036 }
3037
3038 sdnbashroot() { # systemd namespace bash
3039 local unit pid
3040 if (( $# != 1 )); then
3041 echo $0: error wrong number of args >&2
3042 return 1
3043 fi
3044 unit=$1
3045 pid=$(servicepid $unit)
3046 m sudo nsenter -t $pid -n -m bash
3047 }
3048
3049
3050 sdncmd() { # systemd namespace cmd
3051 local unit pid
3052 if (( $# <= 2 )); then
3053 echo $0: error wrong number of args >&2
3054 return 1
3055 fi
3056 unit=$1
3057 shift
3058 pid=$(servicepid $unit)
3059 m sudo nsenter -t $pid -n -m sudo -u $USER -i "$@"
3060 }
3061
3062
3063 mailnnbash() {
3064 sdnbash mailnn
3065 }
3066
3067 # we use wireguard now, use mailnnbash.
3068 # mailvpnbash() {
3069 # m sudo nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*mail.conf") -n -m sudo -u $USER -i bash
3070 # }
3071
3072 eximbash() {
3073 local pid
3074 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1)
3075 if [[ ! $pid ]]; then
3076 echo "eximbash: failed to find exim pid. systemctl -n 30 status exim4:"
3077 systemctl status exim4
3078 fi
3079 m sudo nsenter -t $pid -n -m
3080 }
3081 spamnn() {
3082 local spamdpid
3083 spamdpid=$(systemctl show --property MainPID --value spamassassin)
3084 m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@"
3085 }
3086 unboundbash() {
3087 m sudo nsenter -t "$(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp')" -n -m sudo -u $USER -i bash
3088 }
3089
3090 nmtc() {
3091 s nmtui-connect "$@"
3092 }
3093
3094 mailnncheck() {
3095 local unit pid ns mailnn
3096 # mailvpn would belong on the list if using openvpn
3097 for unit in mailnn unbound dovecot spamassassin exim4 radicale; do
3098 pid=$(servicepid $unit)
3099 echo debug: unit=$unit pid=$pid
3100 if [[ ! $pid ]]; then
3101 echo failed to find pid for unit=$unit
3102 continue
3103 fi
3104 if ! ns=$(s readlink /proc/$pid/ns/net); then
3105 echo failed to find ns for unit=$unit pid=$pid
3106 continue
3107 fi
3108 if [[ $mailnn ]]; then
3109 if [[ $ns != "$mailnn" ]]; then
3110 echo "$unit ns $ns != $mailnn"
3111 fi
3112 else
3113 mailnn=$ns
3114 fi
3115 done
3116
3117 }
3118
3119
3120 vpncmd() {
3121 m sudo -E env "PATH=$PATH" nsenter -t "$(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf")" -n "$@"
3122 }
3123
3124 vpni() {
3125 vpncmd sudo -u iank env "PATH=$PATH" "$@"
3126 }
3127 vpnbash() {
3128 vpncmd bash
3129 }
3130
3131
3132 vpn() {
3133 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3134 local vpn_service=openvpn-client
3135 else
3136 local vpn_service=openvpn
3137 fi
3138
3139 [[ $1 ]] || { echo need arg; return 1; }
3140 journalctl --unit=$vpn_service@$1 -f -n0 &
3141 # sometimes the journal doesnt open until after the vpn output
3142 # has happened. hoping this fixes that.
3143 sleep 1
3144 sudo systemctl start $vpn_service@$1
3145 # sometimes the ask-password agent does not work and needs a delay.
3146 sleep .5
3147 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779240
3148 # noticed around 8-2017 after update from around stretch release
3149 # on debian testing, even though the bug is much older.
3150 sudo systemd-tty-ask-password-agent
3151 }
3152
3153 fixu() {
3154 local stats
3155 ls -lad /run/user/1000
3156 stats=$(stat -c%a-%g-%u /run/user/1000)
3157 if [[ $stats != 700-1000-1000 ]]; then
3158 m s chmod 700 /run/user/1000; m s chown iank.iank /run/user/1000
3159 fi
3160 }
3161
3162 # unmute
3163 um() {
3164 pactl set-sink-mute @DEFAULT_SINK@ false
3165 rm -f /tmp/ianknap
3166 }
3167 nap() {
3168 pactl set-sink-mute @DEFAULT_SINK@ true
3169 touch /tmp/ianknap
3170 }
3171
3172
3173 # systemctl is-enabled / status / cat says nothing, instead theres
3174 # some obscure symlink. paths copied from man systemd.unit.
3175 # possibly also usefull, but incomplete, doesnt show units not loaded in memory:
3176 # seru list-dependencies --reverse --all UNIT
3177 sysd-deps() {
3178 local f
3179 local -a dirs search
3180 ngset
3181
3182 case $1 in
3183 u)
3184 search=(
3185 ~/.config/systemd/user.control/*
3186 $XDG_RUNTIME_DIR/systemd/user.control/*
3187 $XDG_RUNTIME_DIR/systemd/transient/*
3188 $XDG_RUNTIME_DIR/systemd/generator.early/*
3189 ~/.config/systemd/user/*
3190 /etc/systemd/user/*
3191 $XDG_RUNTIME_DIR/systemd/user/*
3192 /run/systemd/user/*
3193 $XDG_RUNTIME_DIR/systemd/generator/*
3194 ~/.local/share/systemd/user/*
3195 /usr/lib/systemd/user/*
3196 $XDG_RUNTIME_DIR/systemd/generator.late/*
3197 )
3198 ;;
3199 *)
3200 search=(
3201 /etc/systemd/system.control/*
3202 /run/systemd/system.control/*
3203 /run/systemd/transient/*
3204 /run/systemd/generator.early/*
3205 /etc/systemd/system/*
3206 /etc/systemd/systemd.attached/*
3207 /run/systemd/system/*
3208 /run/systemd/systemd.attached/*
3209 /run/systemd/generator/*
3210 /lib/systemd/system/*
3211 /run/systemd/generator.late/*
3212 )
3213 ;;
3214 esac
3215 for f in "${search[@]}"; do
3216 [[ -d $f ]] || continue
3217 case $f in
3218 *.requires|*.wants)
3219 dirs+=("$f")
3220 ;;
3221 esac
3222 done
3223 # dirs is just so we write out the directory names, ls does it when there is 2 or more dirs.
3224 case ${#dirs[@]} in
3225 1)
3226 echo "${dirs[0]}:"
3227 ll "${dirs[@]}"
3228 ;;
3229 0) : ;;
3230 *)
3231 ll "${dirs[@]}"
3232 ;;
3233 esac
3234 ngreset
3235 }
3236
3237 fixvpndns() {
3238 local link istls
3239 read -r _ link _ istls < <(resolvectl dnsovertls tunfsf)
3240 case $istls in
3241 yes|no) : ;;
3242 *) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;;
3243 esac
3244 s busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no
3245 }
3246
3247 vpnoff() {
3248 [[ $1 ]] || { echo need arg; return 1; }
3249 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
3250 local vpn_service=openvpn-client
3251 else
3252 local vpn_service=openvpn
3253 fi
3254 sudo systemctl stop $vpn_service@$1
3255 }
3256 vpnoffc() { # vpn off client
3257 ser stop openvpn-client-tr@client
3258 }
3259 vpnc() {
3260 ser start openvpn-client-tr@client
3261 }
3262
3263
3264 vspicy() { # usage: VIRSH_DOMAIN
3265 # connect to vms made with virt-install
3266 spicy -p "$(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
3267 sed -r "s/.*port='([0-9]+).*/\1/")"
3268 }
3269
3270 wian() {
3271 cat-new-files /m/4e/INBOX/new
3272 }
3273 wakehours() {
3274 local sec
3275 if (( $# != 1 )) ; then
3276 echo wakehours: error: expected 1 arg, got $# >&2
3277 return 1
3278 fi
3279 sec=$(( EPOCHSECONDS - $( date +%s -d $1am ) ))
3280 printf "%d:%02d\n" $(( sec / 60 / 60)) $(( (sec / 60) % 60 ))
3281 }
3282
3283 calvis() { # calendar visualize
3284 install -m 600 /dev/null /tmp/calendar-bytes
3285 while read l; do
3286 for char in $l; do
3287 printf "\x$(printf "%x" $char)" >>/tmp/calendar-bytes
3288 done
3289 done < <(grep -v '[#-]' /p/calendar-data)
3290 /p/c/proc/calendar/linux-amd64/calendar
3291 }
3292
3293 wtr() { curl wttr.in/boston; }
3294
3295 xevkb() { xev -event keyboard; }
3296
3297 # * misc stuff
3298
3299 vrun() {
3300 printf "running: %s\n" "$*"
3301 "$@"
3302 }
3303
3304 f=/a/f/ansible-configs/files/common/etc/fsf-workstation-bashrc.sh
3305 if [[ -e $f ]]; then
3306 # shellcheck disable=SC1090
3307 source $f
3308 fi
3309
3310 electrum() {
3311 # https://electrum.readthedocs.io/en/latest/tor.html
3312 # https://github.com/spesmilo/electrum-docs/issues/129
3313 s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
3314 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
3315 }
3316 monero() {
3317 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
3318 }
3319
3320
3321 # rg my main files
3322 rgm() {
3323 rg "$@" /p/pd.org /p/w.org /a/t.org /a/work.org /b
3324 }
3325
3326 reset-konsole() {
3327 # we also have a file in /a/c/...konsole...
3328 local f=$HOME/.config/konsolerc
3329 setini DefaultProfile profileian.profile "Desktop Entry" $f
3330 setini Favorites profileian.profile "Favorite Profiles" $f
3331 setini ShowMenuBarByDefault false KonsoleWindow $f
3332 setini TabBarPosition Top TabBar $f
3333 }
3334
3335 reset-sakura() {
3336 while read -r k v; do
3337 # shellcheck disable=SC2154
3338 setini $k $v sakura /a/c/subdir_files/.config/sakura/sakura.conf
3339 done <<'EOF'
3340 colorset1_back rgb(33,37,39)
3341 less_questions true
3342 audible_bell No
3343 visible_bell No
3344 disable_numbered_tabswitch true
3345 scroll_lines 10000000
3346 scrollbar true
3347 EOF
3348 }
3349
3350 # make a page of links found in the files $@. redirect output
3351 linkhtml() {
3352 gr -oh 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)' "$@" | \
3353 rev | sort -u | rev | sed 's,.*,<a href="\0">\0</a><br\>,'
3354 }
3355
3356 reset-xscreensaver() {
3357 # except for spash, i set these by setting gui options in
3358 # xscreensaver-command -demo
3359 # then finding the corresponding option in .xscreensaver
3360 # spash, i happened to notice in .xscreensaver
3361 #
3362 # dpmsOff, monitor doesnt come back on using old free software supported nvidia card
3363 cat > /home/iank/.xscreensaver <<'EOF'
3364 mode: blank
3365 dpmsEnabled: True
3366 dpmsStandby: 0:07:00
3367 dpmsSuspend: 0:08:00
3368 dpmsOff: 0:00:00
3369 timeout: 0:05:00
3370 lock: True
3371 lockTimeout: 0:06:00
3372 splash: False
3373 EOF
3374
3375 }
3376
3377
3378 # very useful, copy directory structure 3 deep. add remove /*/ to change level
3379 # rsync -aivh --exclude '/*/*/*/' -f"+ */" -f"- *" SRC DEST
3380
3381
3382 # * stuff that makes sense to be at the end
3383 if [[ "$SUDOD" ]]; then
3384 # allow failure, for example if we are sudoing into a user with diffferent/lesser permissions.
3385 cd "$SUDOD" ||:
3386 unset SUDOD
3387 elif [[ -d /a ]] && [[ $PWD == "$HOME" ]] && [[ $- == *i* ]]; then
3388 cd /a
3389 OLDPWD=
3390 fi
3391
3392
3393
3394
3395 # for mitmproxy to get a newer python.
3396 # commented until i want to use it because it
3397 # noticably slows bash startup
3398 #
3399
3400 mypyenvinit () {
3401 if [[ $EUID == 0 || ! -e ~/.pyenv/bin ]]; then
3402 echo "error: dont be root. make sure pyenv is installed"
3403 return 1
3404 fi
3405 export PATH="$HOME/.pyenv/bin:$PATH"
3406 eval "$(pyenv init -)"
3407 eval "$(pyenv virtualenv-init -)"
3408 }
3409
3410
3411 export GOPATH=$HOME/go
3412 path-add $GOPATH/bin
3413 path-add /usr/local/go/bin
3414
3415 # I have the git repo and a release. either one should work.
3416 # I have both because I was trying to solve an issue that
3417 # turned out to be unrelated.
3418 # ARDUINO_PATH=/a/opt/Arduino/build/linux/work
3419
3420 ## i should have documented this...
3421 # based on https://github.com/keyboardio/Kaleidoscope
3422 export KALEIDOSCOPE_DIR=/a/opt/Kaleidoscope
3423
3424 # They want to be added to the start, but i think
3425 # that should be avoided unless we really need it.
3426 path-add --end ~/.npm-global
3427
3428
3429 path-add --end $HOME/.cargo/bin
3430
3431 if type -P rg &>/dev/null; then
3432 # --no-messages because of annoying errors on broken symlinks
3433 # -z = search .gz etc files
3434 # -. = search dotfilesq
3435 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 $?; }
3436 #fails if not exist. ignore
3437 complete -r rg 2>/dev/null ||:
3438 else
3439 alias rg=grr
3440 fi
3441
3442
3443
3444 # taken from default changes to bashrc and bash_profile
3445 path-add --end --ifexists $HOME/.rvm/bin
3446 # also had ruby bin dir, but moved that to environment.sh
3447 # so its included in overall env
3448
3449
3450 export BASEFILE_DIR=/a/bin/fai-basefiles
3451
3452 #export ANDROID_HOME=/a/opt/android-home
3453 # https://f-droid.org/en/docs/Installing_the_Server_and_Repo_Tools/
3454 #export USE_SDK_WRAPPER=yes
3455 #PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
3456
3457 # didnt get drush working, if I did, this seems like the
3458 # only good thing to include for it.
3459 # Include Drush completion.
3460 # if [ -f "/home/ian/.drush/drush.complete.sh" ] ; then
3461 # source /home/ian/.drush/drush.complete.sh
3462 # fi
3463
3464
3465 # best practice
3466 unset IFS
3467
3468 # https://wiki.archlinux.org/index.php/Xinitrc#Autostart_X_at_login
3469 # i added an extra condition as gentoo xorg guide says depending on
3470 # $DISPLAY is fragile.
3471 if [[ ! $DISPLAY && $XDG_VTNR == 1 ]] && shopt -q login_shell && isarch; then
3472 exec startx
3473 fi
3474
3475
3476 # ensure no bad programs appending to this file will have an affect
3477 return 0