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