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