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