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