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