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