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