lots of fixes, new music stuff
[distro-setup] / brc2
1 #!/bin/bash
2 # Copyright (C) 2019 Ian Kelling
3 # SPDX-License-Identifier: AGPL-3.0-or-later
4 # this gets sourced. shebang is just for file mode detection
5
6
7 # * settings
8
9 if [[ $LESSHISTFILE == - ]]; then
10 HISTFILE=
11 c() { cd "$@"; }
12 elif [[ $HISTFILE ]]; then
13 HISTFILE=$HOME/.bh
14 fi
15
16 source /a/bin/distro-setup/path-add-function
17 path-add /a/exe
18 # add this with absolute paths as needed for better security
19 #path-add --end /path/to/node_modules/.bin
20 ## for yarn, etc
21 #path-add --end /usr/lib/node_modules/corepack/shims/
22
23 # pip3 --user things go here:
24 path-add --end ~/.local/bin
25 path-add --ifexists --end /a/work/libremanage
26 path-add --ifexists --end /a/opt/adt-bundle*/tools /a/opt/adt-bundle*/platform-tools
27 path-add --ifexists --end /a/opt/scancode-toolkit-3.10.
28 path-add --ifexists --end /p/bin
29
30 case $HOSTNAME in
31 sy|bo)
32 # https://askubuntu.com/questions/1254544/vlc-crashes-when-opening-any-file-ubuntu-20-04
33 if grep -qE '^VERSION_CODENAME="(nabia|focal)"' /etc/os-release &>/dev/null; then
34 export MESA_LOADER_DRIVER_OVERRIDE=i965
35 fi
36 ;;
37 esac
38
39
40 export WCDHOME=/a
41
42
43 case $EUID in
44 0)
45 SL_SSH_ARGS="-F $HOME/.ssh/confighome"
46 ;;
47 esac
48
49
50 # * include files
51
52 # generated instead of dynamic for the benefit of shellcheck
53 #for x in /a/bin/distro-functions/src/* /a/bin/!(githtml)/*-function?(s); do echo source $x ; done
54 source /a/bin/distro-functions/src/identify-distros
55 source /a/bin/log-quiet/logq-function
56 # for x in /a/bin/bash_unpublished/source-!(.#*); do echo source $x; done
57 source /a/bin/bash_unpublished/source-semi-priv
58 source /a/bin/bash_unpublished/source-state
59
60 source /a/bin/log-quiet/logq-function
61 if [[ -s /a/opt/alacritty/extra/completions/alacritty.bash ]]; then
62 source /a/opt/alacritty/extra/completions/alacritty.bash
63 fi
64
65
66 # * functions
67
68 multimic() {
69 local i
70 local -a sources
71
72 m pactl unload-module module-loopback
73 m pactl unload-module module-null-sink
74 m pactl unload-module module-remap-source
75
76 sources=($(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p'))
77
78 if (( ! $# )); then
79 i=0
80 for s in ${sources[@]}; do
81 e $i $s
82 i=$(( i+1 ))
83 done
84 read -r l
85 set -- $l
86 fi
87 m pactl load-module module-null-sink sink_name=ianinput sink_properties=device.description=ianinputs
88 for i; do
89 m pactl load-module module-loopback source=${sources[i]} sink_dont_move=true sink=ianinput
90 done
91 pactl load-module module-remap-source source_name=iancombine master=ianinput.monitor source_properties=device.description=iancombine
92 }
93
94 # h ssh test
95 # For testing restrictive ssh.
96 hstest() {
97 install-my-scripts
98 d=$(mktemp -d)
99 sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config
100 s command ssh -F $d/config -i /q/root/h "$@"
101 }
102
103 # h rsync test
104 # For testing restrictive rsync
105 hrtest() { #
106 install-my-scripts
107 d=$(mktemp -d)
108 sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config
109 s rsync -e "ssh -F $d/config -i /q/root/h" "$@"
110 }
111
112 # rsync as root and avoid the default restrictive h key & config.
113 rootrsync() {
114 s rsync -e "ssh -F /root/.ssh/confighome" "$@"
115 }
116
117 zcheck() {
118 ssh bow DISPLAY=:0 scrot /tmp/oegu.jpg
119 scp bow:/tmp/oegu.jpg /t
120 ssh bow rm /tmp/oegu.jpg
121 feh /t/oegu.jpg
122 }
123
124 slemacs() {
125 local arg rtime v
126 arg="$1"
127 remote="$2"
128 if [[ $arg == [89]0Etiona* ]]; then
129 v=${arg::1}
130 rtime=${arg#*Etiona} # remote time
131 if [[ ! $rtime ]]; then
132 rtime=0
133 fi
134 dir=/a/opt/emacs-trisquel${v}-nox/.iank
135 ltime=$(stat -c%Y $dir/e/e/.emacs.d/init.el)
136 if (( ltime > rtime )); then
137 m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" $dir "$remote":/home/iank
138 fi
139 fi
140 }
141
142 sle() { # sl emacs
143 local f=/home/iank/.emacs.d/init.el
144 sl --sl-test-cmd ". /etc/os-release ; printf %s \${VERSION//[^a-zA-Z0-9]/}; test -e $f && stat -c%Y $f" --sl-test-hook slemacs "$@"
145 }
146 ccomp ssh sle
147
148 # Run this manually after .emacs.d changes. Otherwise, to check if
149 # files changed with find takes 90ms. sl normally only adds 25ms. We
150 # could cut it down to 10ms if we put things on a btrfs filesystem and
151 # looked for changes there, or used some inotify thing, but that seems
152 # like too much work.
153 egh() { # emacs gnuhope
154 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank lists2d.fsf.org:.ianktrisquel_9
155 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank lists2d.fsf.org:/home/iank
156 }
157 ekw() {
158 local shell="bash -s"
159 if [[ $HOSTNAME != kw ]]; then
160 shell="ssh kw.office.fsf.org"
161 bbk -m /a -t kw
162 fi
163 $shell <<'EOF'
164 sudo mkdir /root/.ianktrisquel_9
165 sudo rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank /root/.ianktrisquel_9
166 rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank /home/iank
167 EOF
168 }
169
170 rm-docker-iptables() {
171 s iptables -S | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables $l; done
172 s iptables -S -t nat | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables -t nat $l; done
173 s iptables -S | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables $l; done
174 s iptables -S -t nat | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables -t nat $l; done
175 }
176
177 # usage mkschroot [-] distro codename packages
178 # - means no piping in of sources.list
179 mkschroot() {
180 local force=false
181 while [[ $1 == -* ]]; do
182 case $1 in
183 -f) force=true; shift ;;
184 -s)
185 sources="$2"
186 if [[ ! -s $sources ]]; then
187 echo mkschroot: error: sources file $sources does not exist or is empty
188 return 1
189 fi
190 shift 2
191 ;;
192 esac
193 done
194 distro=$1
195 shift
196 case $distro in
197 trisquel)
198 repo=http://mirror.fsf.org/trisquel/
199 ;;
200 ubuntu)
201 repo=http://archive.ubuntu.com/ubuntu/
202 ;;
203 debian)
204 repo=http://deb.debian.org/debian/
205 ;;
206 esac
207 n=$1
208
209 shift
210 if ! $force && schroot -l | grep -xFq chroot:$n; then
211 echo "$0: $n schroot already installed, skipping"
212 return 0
213 fi
214 apps=($@)
215 d=/nocow/schroot/$n
216 sd /etc/schroot/chroot.d/$n.conf <<EOF
217 [$n]
218 description=$n
219 type=directory
220 directory=$d
221 profile=desktop
222 preserve-environment=true
223 users=$USER,user2
224 EOF
225 cd
226 if [[ ! -e $d/bin ]]; then
227 sudo mkdir -p $d
228 # resolvconf otherwise schroot fails with
229 # cp: not writing through dangling symlink '/var/run/schroot/mount/flidas-7a2362e0-81b3-4848-92c1-610203ef5976/etc/resolv.conf'
230 sudo debootstrap --exclude=resolvconf $n $d $repo
231 fi
232 if [[ $sources ]]; then
233 sudo install -m 644 $sources $d/etc/apt/sources.list
234 fi
235 sudo chroot $d apt-get update
236 sudo DEBIAN_FRONTEND=noninteractive chroot $d apt-get -y dist-upgrade --purge --auto-remove
237 sudo cp -P {,$d}/etc/localtime
238 if (( ${#apps[@]} )); then
239 sudo DEBIAN_FRONTEND=noninteractive schroot -c $n -- apt-get install --allow-unauthenticated -y ${apps[@]}
240 fi
241 }
242
243
244 # note: this is incomplete and untested.
245 # https://wiki.archlinux.org/index.php/Install_Arch_Linux_from_existing_Linux#Creating_a_chroot
246 mkarchchroot() {
247 local tarball mirror
248 mirror=https://mirrors.edge.kernel.org/archlinux/iso/latest/
249 tarball=$(curl -s $mirror | sed -nr 's/.*"(archlinux-bootstrap-.*-x86_64.tar.gz)".*/\1/p')
250 wget -O /tmp/arch.tar.gz https://mirrors.edge.kernel.org/archlinux/iso/latest/$tarball
251 s mkdir -p /nocow/schroot/arch
252 cd _/nocow/schroot/arch
253 s sed -i '/## United States/,/^$/s,^#,,' etc/pacman.d/mirrorlist
254 # error: could not determine cachedir mount point /var/cache/pacman/pkg
255 s sed -i /^CheckSpace/d etc/pacman.conf
256 chroot . /bin/bash -s <<'EOF'
257 pacman-key --init
258 pacman-key --populate archlinux
259 pacman -Syyu
260 EOF
261 # example of building an aur package:
262 # pacman -Sy base-devel wget
263 # useradd -m iank
264 # f=$target/etc/sudoers
265 # line='iank ALL=(ALL) NOPASSWD: ALL'
266 # if [[ ! -e $f ]] || ! grep -xF "$line" $f; then
267 # echo "$line" >> $f
268 # fi
269 # su iank
270 # wget https://aur.archlinux.org/cgit/aur.git/snapshot/anbox-image-gapps.tar.gz
271 # tar xzf anbox-image-gapps.tar.gz
272 # cd anbox-image-gapps
273 # makepkg -s
274 }
275
276
277 # clock back in to timetrack from last entry
278 tback() {
279 sqlite3 /p/.timetrap.db "update entries set end = NULL where id = (select max(id) from entries);"
280 }
281
282 # sshfs example:
283 # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
284
285 eqgo() {
286 enn -M $(exiqgrep -i -r.\*)
287 }
288 eqgo1() {
289 enn -M $(exipick -i -r.\*|h1)
290 }
291
292
293 gnupload(){
294 /a/f/gnulib/build-aux/gnupload "$@"
295 }
296
297 abrowserrmcompat() {
298 local f
299 ngset
300 f=(/p/c/firefox*/compatibility.ini)
301 if (( ${#f[@]} )); then
302 rm ${f[@]}
303 fi
304 ngreset
305 }
306
307 checkre() {
308 s checkrestart -b /a/bin/ds/checkrestart-blacklist -pv
309 }
310
311 cp-blocked-domains-to-brains() {
312 cp /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn
313 }
314 cp-blocked-domains-to-ansible() {
315 cp /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains
316 }
317
318
319 anki() {
320 # crashes on adding new cards in t9
321 schroot -c buster -- anki
322 }
323
324 acat() {
325 ngset
326 hrcat /m/md/alerts/{cur,new}/*
327 ngreset
328 hr; echo bk; hr
329 ssh bk.b8.nz "shopt -s nullglob; hrcat /m/md/INBOX/new/* /m/md/INBOX/cur/*"
330 }
331 aclear() {
332 ngset
333 rm -f /m/md/alerts/{cur,new}/*
334 ngreset
335 ssh bk.b8.nz "shopt -s nullglob; rm -f /m/md/INBOX/new/* /m/md/INBOX/cur/*"
336 system-status _
337 }
338
339 alerts() {
340 find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f
341 }
342 ralerts() { # remote alerts
343 local ret shell
344 # this list is duplicated in check-remote-mailqs
345 for h in bk je li frodo kwwg x3wg x2wg kdwg sywg; do
346 echo $h:
347 shell="ssh $h"
348 if [[ $HOSTNAME == "${h%wg}" ]]; then
349 shell=
350 fi
351 ret=0
352 $shell find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f || ret=$?
353 if (( ret )); then
354 echo ret:$ret
355 fi
356 done
357 }
358
359 ap() {
360 # pushd in case current directory has an ansible.cfg file
361 pushd /a/xans >/dev/null
362 ansible-playbook -v -l ${1:- $(hostname -f)} site.yml
363 popd >/dev/null
364 }
365 aw() {
366 pushd /a/work/ans >/dev/null
367 time ansible-playbook -i inventory adhoc.yml "$@"
368 popd >/dev/null
369 }
370 ad() {
371 pushd /a/bin/distro-setup/a >/dev/null
372 ansible-playbook site.yml "$@"
373 popd >/dev/null
374 }
375
376 astudio() {
377 # googling android emulator libGL error: failed to load driver: r600
378 # lead to http://stackoverflow.com/a/36625175/14456
379 export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1
380 /a/opt/android-studio/bin/studio.sh "$@" &r;
381 }
382
383
384 iki() {
385 local url path
386 if [[ $1 ]]; then
387 path="$*"
388 else
389 read -r -p "enter path" path
390 fi
391 url=$(readlink -f "$path")
392 url="https://brains.fsf.org/wiki/${url#*brains/}"
393 url="${url%.mdwn}"
394 echo "$url"
395 # /f/brains/sysadmin/interns/2022/nick_shrader/intro_blog_post.mdwn
396 # becomes
397 # https://brains.fsf.org/wiki/sysadmin/interns/2022/nick_shrader/intro_blog_post
398
399 }
400
401 # Generate beet smartplaylists for navidrome.
402 # for going in the reverse direction, run
403 # /b/ds/navidrome-playlist-export
404 beetsmartplaylists() {
405 install -m 0700 -d /tmp/ianbeetstmp
406 beet splupdate
407 # kill off any playlists we deleted. they will still need manual
408 # killing from a navidrome client.
409 rm -rf /i/converted/beetsmartplaylists
410 mkdir -p /i/converted/beetsmartplaylists
411 for f in /tmp/ianbeetstmp/*; do
412 sed 's,^/i/m,/i/converted,;s,\.flac$,.mp3,' "$f" >"/i/converted/beetsmartplaylists/${f##*/}"
413 rm "$f"
414 done
415 rmdir /tmp/ianbeetstmp
416 }
417
418 # Export beets ratings into navidrome
419 beetrating() {
420 local tmp tmpfile myuser userid rating path cpath sqlpath
421 # plucked this from the db. im the only user.
422 userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634
423 tmpfile=$(mktemp)
424 beet ls -f '$rating $path' ^genre:spoken-w ^genre:skit rating:2..5 >$tmpfile
425 while read -r rating path; do
426 tmp="/i/converted${path#/i/m}"
427 cpath="${tmp%.*}.mp3" # converted path
428 sqlpath="${cpath//\'/\'\'}"
429 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';")
430 if [[ $old_rating ]]; then
431 if [[ $old_rating != $rating ]]; then
432 # https://stackoverflow.com/a/50317320
433 m sqlite3 /i/navidrome/navidrome.db "
434 update annotation set rating = $rating
435 where item_id in (
436 select media_file.id from annotation inner join media_file on annotation.item_id = media_file.id
437 where media_file.path = '$sqlpath' and annotation.item_type = 'media_file' );"
438 fi
439 else
440 # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0
441 # https://www.sqlite.org/lang_insert.html
442 m sqlite3 /i/navidrome/navidrome.db "insert into annotation select '$(uuidgen)', '$userid', id, 'media_file', 0, NULL, $rating, 0, NULL from media_file where path = '$sqlpath';"
443 fi
444 #sqlite3 /i/navidrome/navidrome.db "select path from annotation inner join media_file on item_id = id where rating = $r;"
445 done <$tmpfile
446 }
447
448 # Do transcoding and hardlinking of audio files for navidrome.
449 #
450 # Deletes files in the converted directory which should no longer
451 # be there due to a rename of the unconverted file.
452 beetconvert() {
453 # directs to avoid printing every file
454 beet convert -y ^genre:spoken-w ^genre:skit ^rating:1 >/dev/null 2> >(grep -v '^convert: Skipping' ||:)
455 local l
456 local -A paths
457 while read -r l; do
458 convertedpath="/i/converted${l#/i/m}"
459 case $convertedpath in
460 *.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
461 esac
462 paths[$convertedpath]=t
463 done < <(beet ls -f '$path' ^genre:spoken-w ^genre:skit ^rating:1)
464 while read -r l; do
465 if [[ ! ${paths[$l]} ]]; then
466 rm -v "$l"
467 fi
468 done < <(find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \))
469 }
470
471 # tag with beets.
472 # usage: beetag QUERY
473 # it lists the query, reads an input char for tagging one by one
474 # 1-5 = set rating
475 # a-z+ = set genre/playlist.
476 # enter = next song
477 # , = play song
478 beetag() {
479 if (( ! $# )); then
480 echo beetag: error expected a query arg >&2
481 return 1
482 fi
483 local last_genre_i fstring tag id char new_item char_i genre tag remove
484 local -a genres pl_tags buttons button_map ids tags
485 local -A button_i
486 genres=(
487 ambient
488 avant
489 blues
490 classical
491 country
492 # like power glove
493 dark-wave
494 hardcore
495 instrumental
496 jazz
497 latin
498 metal
499 musical
500 # mq = mac quale. similar to the mr robot soundtracks.
501 # slow, foreboding. usually electronic.
502 mq
503 noise
504 pop
505 rap
506 rock
507 skit
508 spoken-w
509 techno
510 world
511 )
512 pl_tags=(
513 expl
514 love
515 pump1
516 pumprap
517 rend
518 run
519 sad
520 )
521 last_genre_i=$(( ${#genres[@]} - 1 ))
522 buttons=( {a..z} 0 {6..9} )
523 button_map=(${genres[@]} ${pl_tags[@]})
524 fstring=
525 for tag in "${pl_tags[@]}"; do
526 fstring+="%ifdef{$tag,$tag }"
527 done
528
529 for (( i=0; i<${#buttons[@]}; i++ )); do
530 button_i[${buttons[i]}]=$i
531 done
532 beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $artist - $album - $title' "$@"
533 hr
534 mapfile -t ids < <(beet ls -f '$id' "$@")
535 for id in "${ids[@]}"; do
536 lsout="$(beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $id $artist - $album - $title' "id:$id")"
537 tags=( ${lsout%%,*} )
538 printf "%s\n" "$lsout"
539 for (( i=0; i<${#button_map[@]}; i++ )); do
540 echo ${buttons[i]} ${button_map[i]}
541 done
542 while true; do
543 read -r -N 1 -s char
544 if [[ $char == $'\n' ]]; then
545 break
546 fi
547 case $char in
548 ,)
549 beet play "id:$id"
550 continue
551 ;;
552 [1-5])
553 beet modify -y "id:$id" rating=$char
554 continue
555 ;;
556 esac
557 char_i=${button_i[$char]}
558 new_item=${button_map[$char_i]}
559 if [[ ! $char_i || ! $new_item ]]; then
560 echo "error: no mapping of input found, try again"
561 continue
562 fi
563 if (( char_i <= last_genre_i )); then
564 m beet modify -y "id:$id" genre=$new_item
565 else
566 remove=false
567 for tag in ${tags[@]}; do
568 if [[ $new_item == "$tag" ]]; then
569 remove=true
570 break
571 fi
572 done
573 if $remove; then
574 m beet modify -y "id:$id" "$new_item!"
575 else
576 m beet modify -y "id:$id" $new_item=t
577 fi
578 fi
579 done
580 done
581
582 # sadpop
583 #
584 # rending:
585 # two dollar guitar: speed
586 # black heard procession
587 # strong enough sheryl crow
588 #
589 #
590 }
591
592 # escape regex.
593 #
594 # This is not perfect but generally good enough. It escapes all
595 # metachars listed man 3 pcrepattern.
596 er() {
597 sed 's/[]\\^$.[|()?*+{}]/[&]/g; s/\^/\\^/g' <<<"$*"
598 }
599
600 # usage beegenre QUERY
601 #
602 # beet set genre for QUERY based on existing artist most used genre on
603 #
604 # inverse of query for each artist found in QUERY. If query starts with
605 # "artist:" it is used as the artist instead of each artist in QUERY.
606 #
607 beegenre() {
608 local artist artregex genre term singleartist
609 local -a artists genres terms
610 singleartist=false
611 case $1 in
612 artist:*)
613 singleartist=true
614 artist="$term"
615 ;;
616 esac
617 if $singleartist; then
618 read count genre < <(beet ls -f '$genre' "$artist" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1) ||:
619 beet modify "$artist" "$@" genre=$genre
620 else
621 while read -r artist; do
622 artregex=$(er "$artist")
623 read count genre < <(beet ls -f '$genre' "artist::^$artregex$" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1) || continue
624 if [[ $count ]]; then
625 artists+=("$artregex")
626 genres+=("$genre")
627 echo "beet modify -y $@ \"artist::^$artist$\" genre=$genre # $count"
628 fi
629 done < <(beet ls -f '$artist' "$@" | sort -u)
630 read -r -N 1 -s -p "Y/n " char
631 case $char in
632 [Yy$'\n'])
633 for (( i=0; i<${#artists[@]}; i++ )); do
634 beet modify -y "$@" "artist::^${artists[i]}$" genre=${genre[i]}
635 done
636 ;;
637 esac
638 fi
639 }
640
641 # note, to check for glue records
642 # First, find some the .org nameservers:
643 # dig +trace iankelling.org
644 # then, query one:
645 # dig ns1.iankelling.org @b0.org.afilias-nst.org.
646
647 # Now, compare for a domain that does have glue records setup (note the A
648 # and AAAA records in ADDITIONAL SECTION, those are glue records like the
649 # one I'm asking for):
650
651 # $ dig ns1.gnu.org @b0.org.afilias-nst.org.
652
653 # todo: make sm pull/push use systemd instead of the journal cat command
654 bbk() { # btrbk wrapper
655 local ret=0
656 c /
657 local active=true
658 systemctl is-active btrbk.timer || active=false
659 if $active; then
660 ser stop btrbk.timer
661 fi
662 btrbk_is_active=$(systemctl is-active btrbk.service ||:)
663 case $btrbk_is_active in
664 inactive|failed) : ;;
665 *)
666 echo "bbk: error: systemctl is-active btrbk.service output: $btrbk_is_active"
667 if $active; then ser start btrbk.timer; fi
668 return 1
669 ;;
670 esac
671 # run latest
672 install-my-scripts
673 # todo: consider changing this to srun and having the args come
674 # from a file like /etc/default/btrbk, like is done in exim
675 s jdo btrbk-run "$@"
676 if $active; then
677 if (( ret )); then
678 echo bbk: WARNING: btrbk.timer not restarted due to failure
679 else
680 ser start btrbk.timer
681 fi
682 fi
683 return $ret
684 }
685
686 faimon() {
687 fai-monitor | pee cat "fai-monitor-gui -"
688 }
689
690 bfg() { java -jar /a/opt/bfg-1.12.14.jar "$@"; }
691
692 bigclock() {
693 xclock -digital -update 1 -face 'arial black-80:bold'
694 }
695
696 nnn() { /a/opt/nnn -H "$@"; }
697
698 locat() { # log-once cat
699 local files
700 ngset
701 files=(/var/local/cron-errors/* /home/iank/cron-errors/* /sysd-mail-once-state/*)
702 case ${#files[@]} in
703 0) : ;;
704 1)
705 echo ${files[0]}
706 head ${files[0]}
707 ;;
708 *)
709 head ${files[@]}
710 ;;
711 esac
712 ngreset
713 }
714
715 scr() {
716 screen -RD "$@"
717 }
718
719
720 # version of jdo for my non-root user
721 jdo() {
722 # comparison of alternative logging methods:
723 #
724 # systemd-run command (what this function does)
725 #
726 # If there is a user prompt, the program will detect that it is not
727 # connected to a terminal and act in a non-interactive way, skipping
728 # the prompt. This has the benefit that you know exactly how the
729 # program will act if you want to move it into a service that runs
730 # automatically.
731 #
732 # If run with sudo and command is a shell script which does a sleep,
733 # it can (sometimes?) output some extra whitespace in front of
734 # messages, more for each subsequent message. This can be avoided by
735 # becoming root first.
736 #
737 # It logs the command's pid and exit code, which is nice.
738 #
739 #
740 ### command |& ts | tee file.log
741 #
742 # If there is a user prompt, like "read -p prompt var", it will hang
743 # without outputting the prompt.
744 #
745 # I've had a few times where ts had an error and I wasn't totally sure
746 # if it was really the command or ts having the problem.
747 #
748 # Sometimes some output will get hidden until you hit enter.
749 #
750 #
751 ### command |& pee cat logger
752 #
753 # This seems to work. I need to test more.
754 #
755 #
756 ### command |& logger -s
757 #
758 # User prompts get confusingly prefixed to earlier output, and all log
759 # entries get prefixed with annoying priority level.
760 #
761 #
762 ### systemd-cat
763 #
764 # Had a few problems. One major one is that it exited in the middle of
765 # a command on systemctl daemon-reload
766 #
767 # Related commands which can log a whole session: script, sudo, screen
768 local cmd cmd_name jr_pid ret
769 ret=0
770 cmd="$1"
771 shift
772 cmd_name=${cmd##*/}
773 if [[ $cmd != /* ]]; then
774 cmd=$(type -P "$cmd")
775 fi
776 # -q = quiet
777 journalctl -qn2 -f -u "$cmd_name" &
778 # Trial and error of time needed to avoid missing initial lines.
779 # .5 was not reliable. 1 was not reliable. 2 was not reliable
780 sleep 4
781 jr_pid=$!
782 # note, we could have a version that does system --user, but if for example
783 # it does sudo ssh, that will leave a process around that we can't kill
784 # and it will leave the unit hanging around in a failed state needing manual
785 # killing of the process.
786 s systemd-run --uid $(id -u) --gid $(id -g) \
787 -E SSH_AUTH_SOCK=/run/openssh_agent \
788 --unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
789 # The sleep lets the journal output its last line
790 # before the prompt comes up.
791 sleep .5
792 kill $jr_pid &>/dev/null ||:
793 unset jr_pid
794 fg &>/dev/null ||:
795 # this avoids any err-catch
796 (( $ret == 0 )) || return $ret
797 }
798
799 # service run, and watch the output
800 srun() {
801 local unit
802 ret=0
803 unit=$1
804 journalctl -qn2 -f -u $unit &
805 systemctl start $unit
806 sleep 2
807 kill $jr_pid &>/dev/null ||:
808 unset jr_pid
809 fg &>/dev/null ||:
810 }
811
812 sm() { # switch mail host
813 local tmp keyhash
814 c /
815 # run latest
816 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
817 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
818 if [[ ! $tmp ]]; then
819 s ssh-add /root/.ssh/home
820 fi
821 install-my-scripts
822 s jdo switch-mail-host "$@"
823 return $ret
824 }
825 sh2() { # switch host2
826 local tmp keyhash
827 c /
828 # run latest
829 keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}')
830 tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"')
831 if [[ ! $tmp ]]; then
832 s ssh-add /root/.ssh/home
833 fi
834 install-my-scripts
835 s jdo switch-host2 "$@"
836 return $ret
837 }
838
839 # shellcheck disable=SC2120
840 lipush() {
841 # note, i had --delete-excluded, but that deletes all files in --exclude-from on
842 # the remote site, which doesn't make sense, so not sure why i had it.
843 local p a
844 # excluding emacs for now
845 #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
846 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
847 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
848 ret=0
849 for h in li je bk; do
850 m s rsync "$@" $a ${p[@]} /p/c/machine_specific/$h root@$h.b8.nz:/
851 ## only li is debian11
852 #p[0]=/a/opt/emacs-trisuqel10
853 #p[1]=/a/opt/emacs-trisquel10-nox
854 done
855 m s rsync "$@" -ahviSAXPH root@li.b8.nz:/a/h/proposed-comments/ /a/h/proposed-comments || ret=$?
856 return $ret
857 }
858 bkpush() { # no emacs. for running faster.
859 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
860 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
861 ret=0
862 m rsync "$@" $a ${p[@]} /p/c/machine_specific/bk root@bk.b8.nz:/ || ret=$?
863 return $ret
864 }
865 jepush() { # no emacs. for running faster.
866 p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts})
867 a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes"
868 ret=0
869 m rsync "$@" $a ${p[@]} /p/c/machine_specific/je root@je.b8.nz:/ || ret=$?
870 return $ret
871 }
872
873 bindpush() {
874 dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja
875 lipush
876 for h in li bk; do
877 m sl $h <<'EOF'
878 source ~/.bashrc
879 m dnsup
880 EOF
881 done
882 }
883 bindpushb8() {
884 lipush
885 for h in li bk; do
886 m sl $h <<'EOF'
887 source ~/.bashrc
888 m dnsb8
889 EOF
890 done
891 }
892
893 dnsup() {
894 conflink -f
895 m ser reload named
896 }
897 dnsb8() {
898 local f=/var/lib/bind/db.b8.nz
899 m ser stop named
900 m sleep 1
901 m sudo rm -fv $f.jnl $f.signed.jnl
902 m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f
903 m ser restart named
904 }
905 dnsecgen() {
906 # keys generated like this
907 # because of https://ftp.isc.org/isc/dnssec-guide/dnssec-guide.pdf
908 # https://blog.apnic.net/2019/05/23/how-to-deploying-dnssec-with-bind-and-ubuntu-server/
909
910 # key length is longer than that guide because
911 # we are using those at fsf and when old key lengths
912 # become insecure, I want some extra time to update.
913 # dnsecgen (in brc2)
914
915 local zone=$1
916 dnssec-keygen -a RSASHA256 -b 2048 $zone
917 dnssec-keygen -f KSK -a RSASHA256 -b 4096 $zone
918 for f in K$zone.*.key; do
919 # eg Kb8.nz.+008+47995.key tag=47995
920 # in dnsimple, you add the long string from this.
921 # in gandi, you add the long string from the .key file,
922 # then see that the digest matches the ds.
923 echo "tag is the number after DS"
924 dnssec-dsfromkey -a SHA-256 $f
925 done
926 # For b8.nz, we let bind read the keys and sign, and
927 # right now they have root ownership, so let them
928 # get group read.
929 chmod g+r *.private
930 }
931 dsign() {
932 # create .signed file
933 # note: full paths probably not needed.
934 local arg
935 for arg; do
936 local zone=${arg#db.}
937 local dir=/p/c/machine_specific/vps/filesystem/var/lib/bind
938 dnssec-signzone -S -e +31536000 -o $zone -K $dir -d $dir $dir/db.$zone
939 done
940 }
941
942
943 #### begin bitcoin related things
944 btc() {
945 local f=/etc/bitcoin/bitcoin.conf
946 # importprivkey will timeout if using the default of 15 mins.
947 # upped it to 1 hour.
948 bitcoin-cli -rpcclienttimeout=60000 -$(s grep rpcuser= $f) -$(s grep rpcpassword= $f) "$@"
949 }
950 btcusd() { # $1 btc in usd
951 local price
952 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
953 printf "$%s\n" "$price"
954 if [[ $1 ]]; then
955 printf "$%.2f\n" "$(echo "scale=4; $price * $1"| bc -l)"
956 fi
957 }
958 usdbtc() { # $1 usd in btc
959 local price
960 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
961 printf "$%s\n" "$price"
962 if [[ $1 ]]; then
963 # 100 mil satoshi / btc. 8 digits after the 1.
964 printf "%.8f btc\n" "$(echo "scale=10; $1 / $price "| bc -l)"
965 fi
966 }
967 satoshi() { # $1 satoshi in usd
968 local price
969 price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)"
970 price=$(echo "scale=10; $price * 0.00000001"| bc -l)
971 printf "$%f\n" "$price"
972 if [[ $1 ]]; then
973 printf "$%.2f\n" "$(echo "scale=10; $price * $1"| bc -l)"
974 fi
975 }
976 #### end bitcoin related things
977
978
979
980 cbfstool () { /a/opt/coreboot/build/cbfstool "$@"; }
981
982
983 cgpl()
984 {
985 if (($#)); then
986 cp /a/bin/data/COPYING "$@"
987 else
988 cp /a/bin/data/COPYING .
989 fi
990 }
991
992 capache()
993 {
994 if (($#)); then
995 cp /a/bin/data/LICENSE "$@"
996 else
997 cp /a/bin/data/LICENSE .
998 fi
999 }
1000
1001 chrome() {
1002 if type -p chromium &>/dev/null; then
1003 cmd=chromium
1004 else
1005 cd /
1006 cmd="schroot -c bullseye chromium"
1007 CHROMIUM_FLAGS='--enable-remote-extensions' $cmd &r
1008 fi
1009 }
1010
1011
1012 # do all tee.
1013 # pipe to this, or just type like a shell
1014 # todo: test this
1015 dat() {
1016 tee >(ssh frodo.b8.nz) >(ssh x2) >(ssh tp.b8.nz) >(ssh kw) >(ssh tp.b8.nz)
1017 }
1018 da() { # do all
1019 local host
1020 for host in x2 kw tp.b8.nz x3.b8.nz frodo.b8.nz; do
1021 ssh $host "$@"
1022 done
1023 }
1024
1025
1026 debian_pick_mirror () {
1027 # netselect-apt finds a fast mirror.
1028 # but we need to replace the mirrors ourselves,
1029 # because it doesnt do that. best it can do is
1030 # output a basic sources file
1031 # here we get the server it found, get the main server we use
1032 # then substitute all instances of one for the other in the sources file
1033 # and backup original to /etc/apt/sources.list-original.
1034 # this is idempotent. the only way to identify debian sources is to
1035 # note the original server, so we put it in a comment so we can
1036 # identify it later.
1037 local file
1038 file=$(mktemp -d)/f # safe way to get file name without creating one
1039 sudo netselect-apt -o "$file" || return 1
1040 url=$(grep ^\\w $file | head -n1 | awk '{print $2}')
1041 sudo cp -f /etc/apt/sources.list /etc/apt/sources.list-original
1042 sudo sed -ri "/http.us.debian.org/ s@( *[^ #]+ +)[^ ]+([^#]+).*@\1$url\2# http.us.debian.org@" /etc/apt/sources.list
1043 sudo apt-get update
1044 }
1045 digme() {
1046 digdiff @ns{1,2}.iankelling.org "$@"
1047 }
1048
1049 tsr() { # ts run
1050 "$@" |& ts || return $?
1051 }
1052
1053 dup() {
1054 local ran_d
1055 ran_d=false
1056 system-status _
1057 case $PS1 in
1058 *[\ \]]D\ *)
1059 pushd /
1060 /b/ds/distro-begin |& ts || return $?
1061 /b/ds/distro-end |& ts || return $?
1062 popd
1063 ran_d=true
1064 ;;&
1065 *[\ \]]DB\ *)
1066 pushd /
1067 /b/ds/distro-begin |& ts || return $?
1068 popd
1069 ran_d=true
1070 ;;
1071 *[\ \]]DE\ *)
1072 pushd /
1073 /b/ds/distro-end |& ts || return $?
1074 popd
1075 ran_d=true
1076 ;;&
1077 *CONFLINK*)
1078 if ! $ran_d; then
1079 conflink
1080 fi
1081 ;;
1082 esac
1083 system-status _
1084 }
1085
1086 envload() { # load environment from a previous: export > file
1087 local file=${1:-$HOME/.${USER}_env}
1088 eval "$(export | sed 's/^declare -x/export -n/')"
1089 while IFS= read -r line; do
1090 # declare -x makes variables local to a function
1091 eval ${line/#declare -x/export}
1092 done < "$file"
1093 }
1094
1095 failfunc() { asdf a b c; }
1096 failfunc2() { failfunc d e f; }
1097
1098 # one that comes with distros is too old for newer devices
1099 fastboot() {
1100 /a/opt/android-platform-tools/fastboot "$@";
1101 }
1102
1103 kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; }
1104
1105 bat() {
1106 cat /sys/class/power_supply/BAT0/capacity
1107 }
1108
1109 # List of apps to install/update
1110 # Create from existing manually installed apps by doing
1111 # fdroidcl update
1112 # fdroidcl search -i, then manually removing
1113 # automatically installed/preinstalled apps
1114
1115 #
1116 # # my attempt at recovering from boot loop:
1117 # # in that case, boot to recovery (volume up, home button, power, let go of power after samsun logo)
1118 # # then
1119 # mount /dev/block/mmcblk0p12 /data
1120 # cd /data
1121 # find -iname '*appname*'
1122 # rm -rf FOUND_DIRS
1123 # usually good enough to just rm -rf /data/app/APPNAME
1124 #
1125 # currently broken:
1126 # # causes replicant to crash
1127 # org.quantumbadger.redreader
1128 # org.kde.kdeconnect_tp
1129
1130 # not broke, but wont work without gps
1131 #com.zoffcc.applications.zanavi
1132 # not broke, but not using atm
1133 #com.nutomic.syncthingandroid
1134 # # doesn\'t work on replicant
1135 #net.sourceforge.opencamera
1136 #
1137 fdroid_pkgs=(
1138 net.mullvad.mullvadvpn
1139 org.schabi.newpipe
1140 io.github.subhamtyagi.lastlauncher
1141 io.anuke.mindustry
1142 com.biglybt.android.client
1143 de.marmaro.krt.ffupdater
1144 me.ccrama.redditslide
1145 org.fedorahosted.freeotp
1146 at.bitfire.davdroid
1147 com.alaskalinuxuser.justnotes
1148 com.artifex.mupdf.viewer.app
1149 com.danielkim.soundrecorder
1150 com.fsck.k9
1151 com.ichi2.anki
1152 com.jmstudios.redmoon
1153 com.jmstudios.chibe
1154 org.kde.kdeconnect_tp
1155 com.notecryptpro
1156 com.termux
1157 cz.martykan.forecastie
1158 de.danoeh.antennapod
1159 de.blinkt.openvpn
1160 de.marmaro.krt.ffupdater
1161 eu.siacs.conversations
1162 free.rm.skytube.oss
1163 im.vector.alpha # riot
1164 info.papdt.blackblub
1165 me.tripsit.tripmobile
1166 net.gaast.giggity
1167 net.minetest.minetest
1168 net.osmand.plus
1169 org.isoron.uhabits
1170 org.linphone
1171 org.gnu.icecat
1172 org.smssecure.smssecure
1173 org.yaaic
1174 sh.ftp.rocketninelabs.meditationassistant.opensource
1175 )
1176 # https://forum.xda-developers.com/android/software-hacking/wip-selinux-capable-superuser-t3216394
1177 # for maru,
1178 #me.phh.superuser
1179
1180 fdup() {
1181 local -A installed updated
1182 local p
1183 # tried putting this in go buildscript cronjob,
1184 # but it failed with undefined: os.UserCacheDir. I expect its due to
1185 # an environment variable missing, but its easier just to stick it here.
1186 m go get -u mvdan.cc/fdroidcl || return 1
1187 m fdroidcl update
1188 if fdroidcl search -u | grep ^org.fdroid.fdroid; then
1189 fdroidcl install org.fdroid.fdroid
1190 sleep 5
1191 m fdroidcl update
1192 fi
1193 for p in $(fdroidcl search -i| grep -o "^\S\+"); do
1194 installed[$p]=true
1195 done
1196 for p in $(fdroidcl search -u| grep -o "^\S\+"); do
1197 updated[$p]=false
1198 done
1199 for p in ${fdroid_pkgs[@]}; do
1200 if ! ${installed[$p]:-false}; then
1201 m fdroidcl install $p
1202 # sleeps are just me being paranoid since replicant has a history of crashing when certain apps are installed
1203 sleep 5
1204 fi
1205 done
1206 for p in ${!installed[@]}; do
1207 if ! ${updated[$p]:-true}; then
1208 m fdroidcl install $p
1209 sleep 5
1210 fi
1211 done
1212 }
1213
1214 firefox-default-profile() {
1215 key=Default value=1 section=$1
1216 file=/p/c/subdir_files/.mozilla/firefox/profiles.ini
1217 sed -ri "/^ *$key/d" "$file"
1218 sed -ri "/ *\[$section\]/,/^ *\[[^]]+\]/{/^\s*$key[[:space:]=]/d};/ *\[$section\]/a $key=$value" "$file"
1219 }
1220 fdhome() { #firefox default home profile
1221 firefox-default-profile Profile0
1222 }
1223
1224 fdwork() {
1225 firefox-default-profile Profile4
1226 }
1227
1228 ff() {
1229 if type -P firefox &>/dev/null; then
1230 firefox "$@"
1231 else
1232 iceweasel "$@"
1233 fi
1234 }
1235
1236 fn() {
1237 firefox -P alt "$@" >/dev/null 2>&1
1238 }
1239
1240
1241 fsdiff () {
1242 local missing=false
1243 local dname="${PWD##*/}"
1244 local m="/a/tmp/$dname-missing"
1245 local d="/a/tmp/$dname-diff"
1246 [[ -e $d ]] && rm "$d"
1247 [[ -e $m ]] && rm "$m"
1248 local msize=0
1249 local fsfile
1250 while read -r line; do
1251 fsfile="$1${line#.}"
1252 if [[ -e "$fsfile" ]]; then
1253 md5diff "$line" "$fsfile" && tee -a "/a/tmp/$dname-diff" <<< "$fsfile $line"
1254 else
1255 missing=true
1256 echo "$line" >> "$m"
1257 msize=$((msize + 1))
1258 fi
1259 done < <(find . -type f )
1260 if $missing; then
1261 echo "$m"
1262 (( msize <= 100 )) && cat $m
1263 fi
1264 }
1265 fsdiff-test() {
1266 # expected output, with different tmp dirs
1267 # /tmp/tmp.HDPbwMqdC9/c/d ./c/d
1268 # /a/tmp/tmp.qLDkYxBYPM-missing
1269 # ./b
1270 cd $(mktemp -d)
1271 echo ok > a
1272 echo nok > b
1273 mkdir c
1274 echo ok > c/d
1275 local x
1276 x=$(mktemp -d)
1277 mkdir $x/c
1278 echo different > $x/c/d
1279 echo ok > $x/a
1280 fsdiff $x
1281 }
1282 rename-test() {
1283 # test whether missing files were renamed, generally for use with fsdiff
1284 # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir
1285 # echos non-renamed files
1286 local x y found
1287 unset sums
1288 for x in "$2"/*; do
1289 { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null
1290 done
1291 while read -r line; do
1292 { missing_sum=$(md5sum < "$line") ; } 2>/dev/null
1293 renamed=false
1294 for x in "${sums[@]}"; do
1295 if [[ $missing_sum == "$x" ]]; then
1296 renamed=true
1297 break
1298 fi
1299 done
1300 $renamed || echo "$line"
1301 done < "$1"
1302 return 0
1303 }
1304
1305 feh() {
1306 # F = fullscren, z = random, Z = auto zoom
1307 command feh -FzZ "$@"
1308 }
1309
1310
1311
1312 fw() {
1313 firefox -P default "$@" >/dev/null 2>&1
1314 }
1315
1316 gitian() {
1317 git config user.email ian@iankelling.org
1318 }
1319
1320 # at least in flidas, things rely on gpg being gpg1
1321 gpg() {
1322 if type -P gpg2 &>/dev/null; then
1323 command gpg2 "$@"
1324 else
1325 command gpg "$@"
1326 fi
1327 }
1328
1329 gse() {
1330 local email=ian@iankelling.org
1331 git send-email --notes "--envelope-sender=<$email>" \
1332 --suppress-cc=self "$@"
1333 }
1334
1335 gup() { /a/f/gnulib/build-aux/gnupload "$@"; }
1336
1337 dejagnu() { /a/opt/dejagnu/dejagnu "$@"; }
1338
1339 hstatus() {
1340 # do git status on published repos.
1341 c /a/bin/githtml
1342 for x in *; do
1343 cd $(readlink -f $x)/..
1344 status=$(i status -s) || pwd
1345 if [[ $status ]]; then
1346 hr
1347 echo $x
1348 printf "%s\n" "$status"
1349 fi
1350 cd /a/bin/githtml
1351 done
1352 }
1353
1354 # work log
1355 wlog() {
1356 local day now i days_back
1357 days_back=${1:-16}
1358 for (( i=0; i<days_back; i++ )); do
1359 day=$( date +%F -d @$((EPOCHSECONDS - 86400*i )) )
1360 date "+%a %b %d" -d @$((EPOCHSECONDS - 86400*i )) | tr '\n' ' '
1361 /a/opt/timetrap/bin/t d -ftotal -s $day -e $day all -m '^w|lunch$'
1362 done
1363 }
1364 to() { t out -a "$@"; }
1365 ti() { t in -a "$@"; }
1366 tl() {
1367 to "$*"
1368 t s lunch
1369 t in -a "$*"
1370 m t out -a $(date +%F.%T -d @$(( $(date -d "$(echo $*|sed 's/[_.]/ /g')" +%s) + 60*45 )) )
1371 t s w
1372 }
1373
1374 arbttlog() { 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}' ; }
1375
1376 idea() {
1377 /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" &r
1378 }
1379
1380 ilogs() {
1381 ssh root@iankelling.org "cd /var/lib/znc/moddata/log/iank/freenode/ && hr && for x in \#$1/*; do base=\${x##*/}; files=(); for f in $@; do tmp=\#\$f/\$base; if [[ -e \$tmp ]]; then files+=(\#\$f/\$base); fi; done; sed \"s/^./\${base%log}/\" \${files[@]}|sort -n; hr; done"
1382 }
1383
1384 ilog() {
1385 chan=${1:-#fsfsys}
1386 # use * instead of -r since that does sorted order
1387 ssh root@iankelling.org "for n in freenode libera; do cd /var/lib/znc/moddata/log/iank/\$n/$chan && hr && for x in *; do echo \$x; sed \"s/^./\${x%log}/\" \$x; hr; done; done" | less +G
1388 }
1389
1390 o() {
1391 if type gio &> /dev/null ; then
1392 gio open "$@"
1393 elif type gvfs-open &> /dev/null ; then
1394 gvfs-open "$@"
1395 else
1396 xdg-open "$@"
1397 fi
1398 # another alternative is run-mailcap
1399 }
1400 ccomp xdg-open o
1401
1402 # jfilter() {
1403 # grep -Evi -e "^(\S+\s+){4}(sudo|sshd|cron)\[\S*:" \
1404 # -e "^(\S+\s+){4}systemd\[\S*: (starting|started) (btrfsmaintstop|dynamicipupdate|spamd dns bug fix cronjob|rss2email)\.*$"
1405 # }
1406 # jtail() {
1407 # journalctl -n 10000 -f "$@" | jfilter
1408 # }
1409 # jr() { journalctl "$@" | jfilter | less ; }
1410 # jrf() { journalctl -n 200 -f "$@" | jfilter; }
1411
1412 jr() { journalctl "$@" ; }
1413 jrf() { journalctl -n 200 -f "$@" ; }
1414
1415
1416 ccomp journalctl jtail jr jrf
1417
1418 kff() { # keyboardio firmware flash. you must hold down the tilde key
1419 pushd /a/opt/Model01-Firmware
1420 # if we didn't want this yes hack, then remove "shell read" from
1421 # /a/opt/Kaleidoscope/etc/makefiles/sketch.mk
1422 yes $'\n' | VERBOSE=1 make flash
1423 popd
1424 }
1425
1426 wgkey() {
1427 local umask_orig name
1428 if (( $# != 1 )); then
1429 e expected 1 arg >&2
1430 return 1
1431 fi
1432 name=$1
1433 umask_orig=$(umask)
1434 umask 0077
1435 wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key
1436 umask $umask_orig
1437 }
1438 wghole() {
1439 if (( $# != 2 )); then
1440 e expected 2 arg of hostname, ip suffix >&2
1441 return 1
1442 fi
1443 local host ipsuf umask_orig
1444 host=$1
1445 ipsuf=$2
1446 mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
1447 cd /p/c/machine_specific/$host/filesystem/etc/wireguard
1448 umask_orig=$(umask)
1449 umask 0077
1450 wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
1451 cat >wghole.conf <<EOF
1452 [Interface]
1453 # contents hole-priv.key
1454 PrivateKey = $(cat hole-priv.key)
1455 ListenPort = 1194
1456 Address = 10.8.0.$ipsuf/24
1457 # https://dev.to/tangramvision/what-they-don-t-tell-you-about-setting-up-a-wireguard-vpn-1h2g
1458 # ||: makes the systemd service not fail due to the failed command
1459 PostUp = ping -c1 10.8.0.1 ||:
1460
1461 [Peer]
1462 # li. called wgmail on that server
1463 PublicKey = CTFsje45qLAU44AbX71Vo+xFJ6rt7Cu6+vdMGyWjBjU=
1464 AllowedIPs = 10.8.0.0/24
1465 Endpoint = 72.14.176.105:1194
1466 PersistentKeepalive = 25
1467 EOF
1468 umask $umask_orig
1469 # old approach. systemd seems to work fine and cleaner.
1470 rm -f ../network/interfaces.d/wghole
1471 cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
1472 [Peer]
1473 PublicKey = $(cat hole-pub.key)
1474 AllowedIPs = 10.8.0.$ipsuf/32
1475 EOF
1476 cd - >/dev/null
1477 }
1478
1479
1480 mns() { # mount namespace
1481 ns=$1
1482 shift
1483 s mkdir -p /root/mount_namespaces
1484 if ! sudo mountpoint /root/mount_namespaces >/dev/null; then
1485 m sudo mount --bind /root/mount_namespaces /root/mount_namespaces
1486 fi
1487 m sudo mount --make-private /root/mount_namespaces
1488 if [[ ! -e /root/mount_namespaces/$ns ]]; then
1489 m sudo touch /root/mount_namespaces/$ns
1490 fi
1491 if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then
1492 m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true
1493 fi
1494 m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@"
1495 }
1496
1497 mnsr() { # mns run
1498 local ns=$1
1499 shift
1500 mns $ns sudo -u iank -E env "PATH=$PATH" "$@"
1501 }
1502
1503 mnsnonet() {
1504 ns=$1
1505 lomh
1506 if ! s ip netns list | grep -Fx nonet &>/dev/null; then
1507 s ip netns add nonet
1508 fi
1509 mns $ns --net=/var/run/netns/nonet sudo -E -u iank /bin/bash
1510 lomh
1511 }
1512
1513
1514 lom() {
1515 # l = the loopback device
1516 local l base
1517 if [[ $1 == /* ]]; then
1518 base=${1##*/}
1519 fs_file=$1
1520 if mns $base mountpoint -q /mnt/$base; then
1521 return 0
1522 fi
1523 l=$(losetup -j $fs_file | sed -rn 's/^([^ ]+): .*/\1/p' | head -n1 ||:)
1524 if [[ ! $l ]]; then
1525 l=$(sudo losetup -f)
1526 m sudo losetup $l $fs_file
1527 fi
1528 if ! sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
1529 if ! m sudo cryptsetup luksOpen $l $base; then
1530 m sudo losetup -d $l
1531 return 1
1532 fi
1533 fi
1534 m sudo mkdir -p /mnt/$base
1535 m mns $base mount /dev/mapper/$base /mnt/$base
1536 m mns $base chown $USER:$USER /mnt/$base
1537 lomh
1538 else
1539 base=$1
1540 if mns $base mountpoint /mnt/$base &>/dev/null; then
1541 m mns $base umount /mnt/$base
1542 fi
1543 if sudo cryptsetup status /dev/mapper/$base &>/dev/null; then
1544 if ! m sudo cryptsetup luksClose /dev/mapper/$base; then
1545 echo lom: failed cryptsetup luksClose /dev/mapper/$base
1546 return 1
1547 fi
1548 fi
1549 l=$(losetup -l --noheadings | awk '$6 ~ /\/'$base'$/ {print $1}')
1550 if [[ $l ]]; then
1551 m sudo losetup -d $l
1552 else
1553 echo lom: warning: no loopback device found
1554 fi
1555 fi
1556 }
1557
1558 # mu personality. for original, just run mp. for 2, run mp 2.
1559 # this is partly duplicated in mail-setup
1560 mp() {
1561 local dead=false
1562 for s in {1..5}; do
1563 if ! killall mu; then
1564 dead=true
1565 break
1566 fi
1567 sleep 1
1568 done
1569 if ! $dead; then
1570 echo error: mu not dead
1571 m psg mu
1572 return 1
1573 fi
1574 suf=$1
1575 set -- /m/mucache ~/.cache/mu /m/.mu ~/.config/mu
1576 while (($#)); do
1577 target=$1$suf
1578 f=$2
1579 shift 2
1580 if [[ -e $f && ! -L $f ]]; then
1581 m rm -rf $f
1582 fi
1583 m ln -sf -T $target $f
1584 done
1585 }
1586
1587 # maildir enable
1588 mdenable() {
1589 local md dst ln_path src two
1590
1591 two=false
1592 case $1 in
1593 -2) two=true shift ;;
1594 esac
1595
1596 for md; do
1597 src=
1598 if $two; then
1599 dst=/m/4e2/$md
1600 else
1601 dst=/m/4e/$md
1602 fi
1603
1604 ln_path=/m/md/$md
1605 for d in /m/md/$md /m/4e2/$md; do
1606 if [[ -d $d && ! -L $d ]]; then
1607 src=$d
1608 break
1609 fi
1610 done
1611 if [[ ! $src ]]; then
1612 echo "error: could not find $md" >&2
1613 return 1
1614 fi
1615 m mv -T $src $dst
1616 m ln -sf -T $dst $ln_path
1617 done
1618 }
1619 md2enable() {
1620 mdenable -2 "$@"
1621 }
1622 mddisable() {
1623 local md=$1
1624 dst=/m/md/$md
1625
1626 ### begin copied from mdenable, but different d ###
1627 for d in /m/4e/$md /m/4e2/$md; do
1628 if [[ -d $d && ! -L $d ]]; then
1629 src=$d
1630 break
1631 fi
1632 done
1633 if [[ ! $src ]]; then
1634 echo "error: could not find $md" >&2
1635 return 1
1636 fi
1637 ### end copy from mdenable ###
1638
1639 if [[ -L $dst ]]; then m rm $dst; fi
1640 m mv -T $src $dst
1641 }
1642
1643
1644 mdt() {
1645 markdown "$1" >/tmp/mdtest.html
1646 firefox /tmp/mdtest.html
1647 }
1648
1649 mo() { xset dpms force off; } # monitor off
1650
1651 mpvgpu() {
1652 # seems to be the best gpu decoding on my nvidia 670.
1653 # vlc gets similar or better framerate, but is much darker output on my test movie at least.
1654
1655
1656 case $HOSTNAME in
1657 kd)
1658 echo 0f | sudo tee -a /sys/kernel/debug/dri/0/pstate
1659 ;;
1660 esac
1661 # going back to the default slow clock, and slower fan:
1662 # echo 07 | sudo tee -a /sys/kernel/debug/dri/0/pstate
1663 if [[ $DISPLAY ]]; then
1664 mpv --vo=vdpau --hwdec=auto "$@"
1665 else
1666 # waylandvk seems to work the same
1667 mpv --gpu-context=wayland --hwdec=auto
1668 fi
1669 }
1670
1671 mpvd() {
1672 mpv --profile=d "$@";
1673 }
1674 # mpv all media files in . or $1
1675 mpvm() {
1676 local -a extensions arg
1677 # get page source of https://en.wikipedia.org/w/index.php?title=Video_file_format&action=edit
1678 # into /a/x.log, then
1679 # grep '^| *\.' /a/x.log | sed 's/| *//;s/,//g'
1680
1681 # note: to join them together for a regex, do:
1682 # old=; for e in ${extensions[@]/./}; do if [[ ! $old ]]; then old=$e; continue; fi; echo -n "$old|"; old=$e; done; echo $e
1683 extensions=(
1684 .webm
1685 .mkv
1686 .flv
1687 .flv
1688 .vob
1689 .ogv .ogg
1690 .drc
1691 .gif
1692 .gifv
1693 .mng
1694 .avi
1695 .MTS .M2TS .TS
1696 .mov .qt
1697 .wmv
1698 .yuv
1699 .rm
1700 .rmvb
1701 .viv
1702 .asf
1703 .amv
1704 .mp4 .m4p .m4v
1705 .mpg .mp2 .mpeg .mpe .mpv
1706 .mpg .mpeg .m2v
1707 .m4v
1708 .svi
1709 .3gp
1710 .3g2
1711 .mxf
1712 .roq
1713 .nsv
1714 )
1715 arg=("(" -iname "*${extensions[0]}")
1716 for (( i=1 ; i < ${#extensions[@]}; i++ )); do
1717 arg+=(-o -iname "*${extensions[i]}")
1718 done
1719 arg+=(")")
1720 dir=${1:-.}
1721 # debug:
1722 #find $dir "${arg[@]}" -size +200k
1723 find $dir "${arg[@]}" -size +200k -exec mpv --profile=d '{}' +
1724 }
1725 mpvs() {
1726 mpv --profile=s "$@";
1727 }
1728
1729 myirc() {
1730 if [[ ! $1 ]]; then
1731 set -- fsf-office
1732 fi
1733 local d1 d2
1734 d=( /var/lib/znc/moddata/log/iank/{freenode,libera} )
1735 # use * instead of -r since that does sorted order
1736 ssh root@iankelling.org "for f in ${d[@]}; do cd \$f/#$1; grep '\<iank.*' *; done" | cut --complement -c12-16
1737 }
1738 mypidgin() {
1739 c /p/c/.purple/logs/jabber/iank@fsf.org/office@conference.fsf.org.chat
1740 for x in *.html; do html2text -o ${x%.html}.txt $x; done;
1741 grep -A1 ') iank:' *.txt | sed -r 's/^(.{10})[^ ]*\.txt:\(?([^ ]*)[[:space:]](..). iank:/\1_\2_\3/;s/^[^ ]*\.txt-//;/^--$/d;s/^[^ ]*\.txt:\((.{2}).(.{2}).(.{4}) (.{8}) (.{2})\)?/\3-\1-\2_\4_\5/' | sed -n 'x;1d;0~2{G;s/\n/ /;p};${x;p}'
1742 }
1743 allmyirc() {
1744 local d
1745 d=/var/lib/znc/moddata/log/iank/freenode
1746 ssh root@iankelling.org "cd $d; find . -mtime -60 -type f -exec grep '\<iank.*' {} +" | sed -r 's,^..([^/]*)/(.{11})(.{5})(.{8}).,\2\4 \1,' | sort
1747 }
1748
1749 # usage: debvm DEBIAN_VERSION RAM_MB
1750 debvm() {
1751 local ver ram fname src
1752 ver=$1
1753 ram=${2:-2024}
1754 # * is because it might have -backports in the name
1755 fname=debian-$ver-*nocloud-$(dpkg --print-architecture).qcow2
1756 src=/a/opt/roms/$fname
1757 if [[ ! -f $src ]]; then
1758 echo debvm: not found $src, download from eg: https://cloud.debian.org/images/cloud/buster/latest/
1759 return 1
1760 fi
1761 cp -a $src /t
1762 # note, in fai-revm we do this: not sure why, maybe because of br device
1763 # --graphics spice,listen=0.0.0.0
1764 m s virt-install --osinfo debian11 --rng /dev/urandom -n deb${ver}tmp --import -r $ram --vcpus 2 --disk /t/$fname --graphics spice
1765 # note: to ssh into this machine will require host key generation: ssh-keygen -A
1766
1767 # random: for cvs2git on gnu www, use debian 10. I could use trisquel,
1768 # but happen to want to try out the debian cloud images. the upstream
1769 # requires python2 and hasn't really changed since the version in d10.
1770 #
1771 # apt install cvs2git cvs
1772 # # 7G was not enough
1773 # mount -o mode=1777,nosuid,nodev,size=34G -t tmpfs tmpfs /tmp
1774 # cvs2git --encoding utf_8 --fallback-encoding ascii --dumpfile=dump www-rsync/www |& tee /tmp/l
1775 ## www-rsync is an rsynced copy of the cvsfrom savannah
1776 }
1777
1778 mygajim() {
1779 local time time_sec time_pretty days
1780 days=${1:-16}
1781 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
1782 case $time in
1783 16*) : ;;
1784 *) continue ;;
1785 esac
1786 if ! time_pretty=$(date +%F.%R -d @$time); then
1787 echo bad time: $time
1788 return 1
1789 fi
1790 echo $time_pretty "$l"
1791 time_sec=${time%%.*}
1792 # only look at the last 18 days. generally just use this for timesheet.
1793 if (( time_sec < EPOCHSECONDS - 60 * 60 * 24 * days )); then break; fi
1794 done
1795 }
1796
1797 allmygajim() {
1798 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank'" | less
1799 }
1800
1801 gajlogs() {
1802 sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs" | less
1803 }
1804
1805
1806 net-dev-info() {
1807 e "lspci -nnk|gr -iA2 net"
1808 lspci -nnk|gr -iA2 net
1809 hr
1810 e "s lshw -C network"
1811 hr
1812 sudo lshw -C network
1813 }
1814
1815 nk() {
1816 ser stop NetworkManager
1817 ser disable NetworkManager
1818 ser stop NetworkManager-wait-online.service
1819 ser disable NetworkManager-wait-online.service
1820 ser stop dnsmasq
1821 sudo resolvconf -d NetworkManager
1822 # ser start dnsmasq
1823 sudo ifup br0
1824 }
1825 ngo() {
1826 sudo ifdown br0
1827 ser start NetworkManager
1828 sleep 4
1829 sudo nmtui-connect
1830 }
1831
1832 otp() {
1833 oathtool --totp -b "$*" | xclip -selection clipboard
1834 }
1835 j() {
1836 "$@" |& pee "xclip -r -selection clipboard"
1837 }
1838
1839
1840 pakaraoke() {
1841 # from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals
1842 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
1843 }
1844
1845 pfind() { #find *$1* in $PATH
1846 [[ $# != 1 ]] && { echo requires 1 argument; return 1; }
1847 local pathArray
1848 IFS=: pathArray=($PATH); unset IFS
1849 find "${pathArray[@]}" -iname "*$1*"
1850 }
1851
1852 pick-trash() {
1853 # trash-restore lists everything that has been trashed at or below CWD
1854 # This picks out files just in CWD, not subdirectories,
1855 # which also match grep $1, usually use $1 for a time string
1856 # which you get from running restore-trash once first
1857 local name x ask
1858 local nth=1
1859 # last condition is to not ask again for ones we skipped
1860 while name="$( echo | restore-trash | gr "$PWD/[^/]\+$" | gr "$1" )" \
1861 && [[ $name ]] && (( $(wc -l <<<"$name") >= nth )); do
1862 name="$(echo "$name" | head -n $nth | tail -n 1 )"
1863 read -r -p "$name [Y/n] " ask
1864 if [[ ! $ask || $ask == [Yy] ]]; then
1865 x=$( echo "$name" | gr -o "^\s*[0-9]*" )
1866 echo $x | restore-trash > /dev/null
1867 elif [[ $ask == [Nn] ]]; then
1868 nth=$((nth+1))
1869 else
1870 return
1871 fi
1872 done
1873 }
1874
1875
1876 pub() {
1877 rld /a/h/_site/ li:/var/www/iankelling.org/html
1878 }
1879
1880
1881 pumpa() {
1882 # fixes the menu bar in xmonad. this won\'t be needed when xmonad
1883 # packages catches up on some changes in future (this is written in
1884 # 4/2017)
1885 #
1886 # geekosaur: so youll want to upgrade to xmonad 0.13 or else use a
1887 # locally modified XMonad.Hooks.ManageDocks that doesnt set the
1888 # work area; turns out it\'s impossible to set correctly if you are
1889 # not a fully EWMH compliant desktop environment
1890 #
1891 # geekosaur: chrome shows one failure mode, qt/kde another, other
1892 # gtk apps a third, ... I came up with a setting that works for me
1893 # locally but apparently doesnt work for others, so we joined the
1894 # other tiling window managers in giving up on setting it at all
1895 #
1896 xprop -root -remove _NET_WORKAREA
1897 command pumpa & r
1898 }
1899
1900 # reviewboard, used at my old job
1901 #rbpipe() { rbt post -o --diff-filename=- "$@"; }
1902 #rbp() { rbt post -o "$@"; }
1903
1904 rebr() {
1905 sudo ifdown br0
1906 sudo ifup br0
1907 }
1908
1909
1910 r2e() { command r2e -d /p/c/rss2email.json -c /p/c/rss2email.cfg "$@"; }
1911 # only run on MAIL_HOST. simpler to keep this on one system.
1912 r2eadd() { # usage: name url
1913 # initial setup of rss2email:
1914 # r2e new r2e@iankelling.org
1915 # that initializes files, and sets default email.
1916 # symlink to the config doesnt work, so I copied it to /p/c
1917 # and then use cli option to specify explicit path.
1918 # Only option changed from default config is to set
1919 # force-from = True
1920 #
1921 # or else for a few feeds, the from address is set by the feed, and
1922 # if I fail delivery, then I send a bounce message to that from
1923 # address, which makes me be a spammer.
1924
1925 r2e add $1 "$2" $1@r2e.iankelling.org
1926 # get up to date and dont send old entries now:
1927 r2e run --no-send $1
1928 }
1929
1930 rspicy() { # usage: HOST DOMAIN
1931 # connect to spice vm remote host. use vspicy for local host
1932 local port
1933 # shellcheck disable=SC2087
1934 port=$(ssh $1<<EOF
1935 sudo virsh dumpxml $2|grep "<graphics.*type='spice'" | \
1936 sed -rn "s/.*port='([0-9]+).*/\1/p"
1937 EOF
1938 )
1939 if [[ $port ]]; then
1940 spicy -h $1 -p $port
1941 else
1942 echo "error: no port found. check that the domain is running."
1943 fi
1944 }
1945
1946
1947 scssl() {
1948 # s gem install scss-lint
1949 pushd /a/opt/thoughtbot-guides
1950 git pull --stat
1951 popd
1952 scss-lint -c /a/opt/thoughtbot-guides/style/sass/.scss-lint.yml "$@"
1953 }
1954
1955 skbrc() {
1956 sk -e 2120,245 /b/ds/brc /b/ds/brc2
1957 }
1958
1959 skaraoke() {
1960 local tmp out
1961 out=${2:-${1%.*}.sh}
1962 tmp=$(mktemp -d)
1963 script -t -c "mpv --no-config --no-resume-playback --no-terminal --no-audio-display '$1'" $tmp/typescript 2>$tmp/timing
1964 # todo, the current sleep seems pretty good, but it
1965 # would be nice to have an empirical measurement, or
1966 # some better wait to sync up.
1967 #
1968 # note: --loop-file=no prevents it from hanging if you have that
1969 # set to inf the mpv config.
1970 # --loop=no prevents it from exit code 3 due to stdin if you
1971 # had it set to inf in mpv config.
1972 #
1973 # args go to mpv, for example --volume=80, 50%
1974 cat >$out <<EOFOUTER
1975 #!/bin/bash
1976 trap "trap - TERM && kill 0" INT TERM ERR; set -e
1977 ( sleep .2; scriptreplay <( cat <<'EOF'
1978 $(cat $tmp/timing)
1979 EOF
1980 ) <( cat <<'EOF'
1981 $(cat $tmp/typescript)
1982 EOF
1983 ))&
1984 base64 -d - <<'EOF'| mpv --loop=no --loop-file=no --no-terminal --no-audio-display "\$@" -
1985 $(base64 "$1")
1986 EOF
1987 kill 0
1988 EOFOUTER
1989 rm -r $tmp
1990 chmod +x $out
1991 }
1992
1993 smeld() { # ssh meld usage host1 host2 file
1994 meld <(ssh $1 cat $3) <(ssh $2 cat $3)
1995 }
1996
1997 spd() {
1998 PATH=/usr/local/spdhackfix:$PATH command spd "$@"
1999 }
2000
2001 spamf() { # spamtest on FILE
2002 local spamcpre spamdpid
2003
2004 if (( $# != 1 )); then
2005 e spamtest error: expected 1 arg, filename >&2
2006 return 1
2007 fi
2008
2009 spamdpid=$(systemctl status spamassassin| sed -n '/^ *Main PID:/s/[^0-9]//gp')
2010 spamcpre="nsenter -t $spamdpid -n -m"
2011 s $spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1"
2012 }
2013
2014
2015 # mail related
2016 testmail() {
2017 declare -gi _seq; _seq+=1
2018 echo "test body" | m mail -s "test mail from $HOSTNAME, $_seq" "${@:-root@localhost}"
2019 # for testing to send from an external address, you can do for example
2020 # -fian@iank.bid -aFrom:ian@iank.bid web-6fnbs@mail-tester.com
2021 # note in exim, you can retry a deferred message
2022 # s exim -M MSG_ID
2023 # MSG_ID is in /var/log/exim4/mainlog, looks like 1ccdnD-0001nh-EN
2024 }
2025
2026 # to test sieve, use below command. for fsf mail, see offlineimap-sync script
2027 # make modifications, then copy to live file, use -eW to actually modify mailbox
2028 #
2029 # Another option is to use sieve-test SCRIPT MAIL_FILE. note,
2030 # sieve-test doesnt know about envelopes, Im not sure if sieve-filter does.
2031
2032 # sieve with output filter. arg is mailbox, like INBOX.
2033 # This depends on dovecot conf, notably mail_location in /etc/dovecot/conf.d/10-mail.conf
2034
2035 # always run this first, edit the test files, then run the following
2036 testsieve() {
2037 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
2038 }
2039 runsieve() {
2040 c ~/sieve; cp personal{test,}.sieve; cp lists{test,}.sieve; cp personalend{test,}.sieve
2041 sieve-filter -eWv ~/sieve/maintest.sieve ${1:-INBOX} delete &> /tmp/testsieve.log
2042 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
2043 }
2044
2045 # usage:
2046 # alertme SUBJECT
2047 # printf "subject\nbody\n" | alertme
2048 alertme() {
2049 if [[ -t 0 ]]; then
2050 exim -t <<EOF
2051 From: alertme@b8.nz
2052 To: alerts@iankelling.org
2053 Subject: $*
2054 EOF
2055 else
2056 read sub
2057 { cat <<EOF
2058 From: alertme@b8.nz
2059 To: alerts@iankelling.org
2060 Subject: $sub
2061
2062 EOF
2063 cat
2064 } | exim -t
2065 fi
2066 }
2067 daylertme() {
2068 if [[ -t 0 ]]; then
2069 exim -t <<EOF
2070 From: alertme@b8.nz
2071 To: daylert@iankelling.org
2072 Subject: $*
2073 EOF
2074 else
2075 read sub
2076 { cat <<EOF
2077 From: alertme@b8.nz
2078 To: daylert@iankelling.org
2079 Subject: $sub
2080
2081 EOF
2082 cat
2083 } | exim -t
2084 fi
2085 }
2086
2087 # alert when a page goes live.
2088 alert200() {
2089 local quiet url tmpdir
2090 quiet=false
2091 case $1 in
2092 # dont send a diff of the html. some html is not very readable
2093 -q) quiet=true
2094 shift
2095 ;;
2096 esac
2097 url="$1"
2098 tmpdir="$(mktemp -d)"
2099 cd $tmpdir
2100 while true; do
2101 if wget -q "$url"; then
2102 if $quiet; then
2103 echo | daylert 200
2104 else
2105 alertme $tmpdir
2106 fi
2107 fi
2108 sleep $(( 120 + RANDOM % 300 ))
2109 done
2110 }
2111
2112 # alert on changes to a webpage (just the base page that curl gets)
2113 # usage: weblert URL [SUBJECT...]
2114 weblert() {
2115 local u old new quiet
2116 quiet=false
2117 case $1 in
2118 # dont send a diff of the html. some html is not very readable
2119 -q) quiet=true
2120 shift
2121 ;;
2122 esac
2123 u="$1"
2124 shift
2125 subject="${*:-weblert}"
2126 old=$(curl -s "$u") ||:
2127 while true; do
2128 new=$(curl -s "$u") ||:
2129 if [[ $old && $new ]]; then
2130 if [[ $new != "$old" ]]; then
2131 if $quiet; then
2132 echo | daylertme "$subject"
2133 else
2134 diff <(printf "%s\n" "$old") <(printf "%s\n" "$new") | daylertme "$subject" ||:
2135 fi
2136 fi
2137 old="$new"
2138 fi
2139 sleep $(( 60 + RANDOM % 120 ))
2140 done
2141 }
2142
2143 torshell() {
2144 # per man torsocks
2145 source `type -p torsocks` on
2146 }
2147
2148 eless2() {
2149 less /var/log/exim4/mymain
2150 }
2151
2152
2153 # mail related
2154 testexim() {
2155 # testmail above calls sendmail, which is a link to exim/postfix.
2156 # its docs dont say a way of adding an argument
2157 # to sendmail to turn on debug output. We could make a wrapper, but
2158 # that is a pain. Exim debug args are documented here:
2159 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
2160 #
2161 # http://www.exim.org/exim-html-current/doc/html/spec_html/ch-building_and_installing_exim.html
2162 # note, for exim daemon, you can turn on debug options by
2163 # adding -d, etc to COMMONOPTIONS in
2164 # /etc/default/exim4
2165 #
2166 # to specify recipients other than those in to, cc, bcc, you can use the cli args, eg:
2167 # exim -t 'test@zroe.org, t2@zroe.org' <<'EOF'
2168 #
2169 # -t = get recipient from header
2170 exim -d -t <<EOF
2171 From: root@$(hostname -f)
2172 To: root@$(hostname -f)
2173 Subject: test2
2174
2175 This is a test message.
2176 EOF
2177 }
2178
2179 # test bounce exim
2180 testbexim() {
2181 to=$1
2182 exim -d -f '<>' $to <<EOF
2183 From: Mail Delivery System <Mailer-Daemon@gnu.org>
2184 To: $to
2185 Subject: Mail delivery failed: returning message to sender
2186
2187 This message was created automatically by mail delivery software.
2188 EOF
2189
2190 }
2191
2192
2193 # toggle keyboard
2194 tk() {
2195 # based on
2196 # https://askubuntu.com/questions/160945/is-there-a-way-to-disable-a-laptops-internal-keyboard
2197 id=$(xinput --list --id-only 'AT Translated Set 2 keyboard')
2198 if xinput list | grep -F '∼ AT Translated Set 2 keyboard' &>/dev/null; then
2199 echo enabling keyboard
2200 # find the first slave keyboard number, they are all the same in my output.
2201 # if they werent, worst case we would need to save the slave number somewhere
2202 # when it got disabled.
2203 slave=$(xinput list | sed -n 's/.*slave \+keyboard (\([0-9]*\)).*/\1/p' | head -n1)
2204 xinput reattach $id $slave
2205 else
2206 xinput float $id
2207 fi
2208 }
2209
2210 tm() {
2211 # timer in minutes
2212 # --no-config
2213 (sleep $(calc "$* * 60") && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
2214 }
2215
2216 trg() { transmission-remote-gtk & r; }
2217 trc() {
2218 # example, set global upload limit to 100 kilobytes:
2219 # trc -u 100
2220 TR_AUTH=":$(jq -r .profiles[0].password ~/.config/transmission-remote-gtk/config.json)" transmission-remote transmission.lan -ne "$@"
2221 }
2222
2223 trysleep() {
2224 retries="$1"
2225 sleepsecs="$2"
2226 shift 2
2227 for (( i=0; i < retries - 1; i++ )); do
2228 if "$@"; then
2229 return 0
2230 fi
2231 sleep $sleepsecs
2232 done
2233 "$@"
2234 }
2235
2236
2237 tu() {
2238 local s
2239 if [[ -e $1 && ! -w $1 || ! -w $(dirname "$1") ]]; then
2240 s=s;
2241 fi
2242 # full path for using in some initial setup steps
2243 $s /a/exe/teeu "$@"
2244 }
2245
2246 enn() {
2247 local ecmd pid
2248
2249 ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf"
2250 if ip a show veth1-mail &>/dev/null; then
2251 s $ecmd "$@"
2252 return
2253 fi
2254 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1)
2255 m s nsenter -t $pid -n -m $ecmd "$@"
2256 }
2257
2258 # get pid of systemd service
2259 servicepid() {
2260 local pid unit dir
2261 unit="$1"
2262 pid=$(systemctl show --property MainPID --value "$unit")
2263 case $pid in
2264 [1-9]*) : ;;
2265 *)
2266
2267 dir=/sys/fs/cgroup/system.slice
2268 if [[ ! -d $dir ]]; then
2269 # t10 and older directory.
2270 dir=/sys/fs/cgroup/systemd/system.slice
2271 fi
2272
2273 # 0 or empty. This file includes the MainPid, so I expect we
2274 # could just get this in the first place, but i don't know if that
2275 # is always the case.
2276 pid=$(head -n1 $dir/${unit%.service}.service/cgroup.procs)
2277 ;;
2278 esac
2279 if [[ $pid ]]; then
2280 printf "%s\n" "$pid"
2281 else
2282 return 1
2283 fi
2284 }
2285
2286 sdnbash() { # systemd namespace bash
2287 local unit pid
2288 if (( $# != 1 )); then
2289 echo $0: error wrong number of args >&2
2290 return 1
2291 fi
2292 unit=$1
2293 pid=$(servicepid $unit)
2294 m sudo nsenter -t $pid -n -m sudo -u $USER -i bash
2295 }
2296
2297 sdnbashroot() { # systemd namespace bash
2298 local unit pid
2299 if (( $# != 1 )); then
2300 echo $0: error wrong number of args >&2
2301 return 1
2302 fi
2303 unit=$1
2304 pid=$(servicepid $unit)
2305 m sudo nsenter -t $pid -n -m bash
2306 }
2307
2308
2309 sdncmd() { # systemd namespace cmd
2310 local unit pid
2311 if (( $# <= 2 )); then
2312 echo $0: error wrong number of args >&2
2313 return 1
2314 fi
2315 unit=$1
2316 shift
2317 pid=$(servicepid $unit)
2318 m sudo nsenter -t $pid -n -m sudo -u $USER -i "$@"
2319 }
2320
2321
2322 mailnnbash() {
2323 sdnbash mailnn
2324 }
2325
2326 # we use wireguard now, use mailnnbash.
2327 # mailvpnbash() {
2328 # m sudo nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*mail.conf") -n -m sudo -u $USER -i bash
2329 # }
2330
2331 eximbash() {
2332 local pid
2333 pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1)
2334 if [[ ! $pid ]]; then
2335 echo "eximbash: failed to find exim pid. systemctl -n 30 status exim4:"
2336 systemctl status exim4
2337 fi
2338 m sudo nsenter -t $pid -n -m
2339 }
2340 spamnn() {
2341 local spamdpid
2342 spamdpid=$(systemctl show --property MainPID --value spamassassin)
2343 m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@"
2344 }
2345 unboundbash() {
2346 m sudo nsenter -t $(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp') -n -m sudo -u $USER -i bash
2347 }
2348
2349 nmtc() {
2350 s nmtui-connect "$@"
2351 }
2352
2353 mailnncheck() {
2354 local unit pid ns mailnn
2355 # mailvpn would belong on the list if using openvpn
2356 for unit in mailnn unbound dovecot spamassassin exim4 radicale; do
2357 pid=$(servicepid $unit)
2358 echo debug: unit=$unit pid=$pid
2359 if [[ ! $pid ]]; then
2360 echo failed to find pid for unit=$unit
2361 continue
2362 fi
2363 if ! ns=$(s readlink /proc/$pid/ns/net); then
2364 echo failed to find ns for unit=$unit pid=$pid
2365 continue
2366 fi
2367 if [[ $mailnn ]]; then
2368 if [[ $ns != "$mailnn" ]]; then
2369 echo "$unit ns $ns != $mailnn"
2370 fi
2371 else
2372 mailnn=$ns
2373 fi
2374 done
2375
2376 }
2377
2378
2379 vpncmd() {
2380 m sudo -E env "PATH=$PATH" nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf") -n "$@"
2381 }
2382
2383 vpni() {
2384 vpncmd sudo -u iank env "PATH=$PATH" "$@"
2385 }
2386 vpnbash() {
2387 vpncmd bash
2388 }
2389
2390
2391 vpn() {
2392 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
2393 local vpn_service=openvpn-client
2394 else
2395 local vpn_service=openvpn
2396 fi
2397
2398 [[ $1 ]] || { echo need arg; return 1; }
2399 journalctl --unit=$vpn_service@$1 -f -n0 &
2400 # sometimes the journal doesnt open until after the vpn output
2401 # has happened. hoping this fixes that.
2402 sleep 1
2403 sudo systemctl start $vpn_service@$1
2404 # sometimes the ask-password agent does not work and needs a delay.
2405 sleep .5
2406 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779240
2407 # noticed around 8-2017 after update from around stretch release
2408 # on debian testing, even though the bug is much older.
2409 sudo systemd-tty-ask-password-agent
2410 }
2411
2412 fixu() {
2413 local stats
2414 ls -lad /run/user/1000
2415 stats=$(stat -c%a-%g-%u /run/user/1000)
2416 if [[ $stats != 700-1000-1000 ]]; then
2417 m s chmod 700 /run/user/1000; m s chown iank.iank /run/user/1000
2418 fi
2419 }
2420
2421 # systemctl is-enabled / status / cat says nothing, instead theres
2422 # some obscure symlink. paths copied from man systemd.unit.
2423 # possibly also usefull, but incomplete, doesnt show units not loaded in memory:
2424 # seru list-dependencies --reverse --all UNIT
2425 sysd-deps() {
2426 local f
2427 local -a dirs search
2428 ngset
2429
2430 case $1 in
2431 u)
2432 search=(
2433 ~/.config/systemd/user.control/*
2434 $XDG_RUNTIME_DIR/systemd/user.control/*
2435 $XDG_RUNTIME_DIR/systemd/transient/*
2436 $XDG_RUNTIME_DIR/systemd/generator.early/*
2437 ~/.config/systemd/user/*
2438 /etc/systemd/user/*
2439 $XDG_RUNTIME_DIR/systemd/user/*
2440 /run/systemd/user/*
2441 $XDG_RUNTIME_DIR/systemd/generator/*
2442 ~/.local/share/systemd/user/*
2443 /usr/lib/systemd/user/*
2444 $XDG_RUNTIME_DIR/systemd/generator.late/*
2445 )
2446 ;;
2447 *)
2448 search=(
2449 /etc/systemd/system.control/*
2450 /run/systemd/system.control/*
2451 /run/systemd/transient/*
2452 /run/systemd/generator.early/*
2453 /etc/systemd/system/*
2454 /etc/systemd/systemd.attached/*
2455 /run/systemd/system/*
2456 /run/systemd/systemd.attached/*
2457 /run/systemd/generator/*
2458 /lib/systemd/system/*
2459 /run/systemd/generator.late/*
2460 )
2461 ;;
2462 esac
2463 for f in "${search[@]}"; do
2464 [[ -d $f ]] || continue
2465 case $f in
2466 *.requires|*.wants)
2467 dirs+=("$f")
2468 ;;
2469 esac
2470 done
2471 # dirs is just so we write out the directory names, ls does it when there is 2 or more dirs.
2472 case ${#dirs[@]} in
2473 1)
2474 echo "${dirs[0]}:"
2475 ll "${dirs[@]}"
2476 ;;
2477 0) : ;;
2478 *)
2479 ll "${dirs[@]}"
2480 ;;
2481 esac
2482 ngreset
2483 }
2484
2485 fixvpndns() {
2486 local link istls
2487 read _ link _ istls < <(resolvectl dnsovertls tunfsf)
2488 case $istls in
2489 yes|no) : ;;
2490 *) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;;
2491 esac
2492 s busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no
2493 }
2494
2495 vpnoff() {
2496 [[ $1 ]] || { echo need arg; return 1; }
2497 if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then
2498 local vpn_service=openvpn-client
2499 else
2500 local vpn_service=openvpn
2501 fi
2502 sudo systemctl stop $vpn_service@$1
2503 }
2504 vpnoffc() { # vpn off client
2505 ser stop openvpn-client-tr@client
2506 }
2507 vpnc() {
2508 ser start openvpn-client-tr@client
2509 }
2510
2511
2512 vspicy() { # usage: VIRSH_DOMAIN
2513 # connect to vms made with virt-install
2514 spicy -p $(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
2515 sed -r "s/.*port='([0-9]+).*/\1/")
2516 }
2517
2518 wian() {
2519 cat-new-files /m/4e/INBOX/new
2520 }
2521
2522 wtr() { curl wttr.in/boston; }
2523
2524 xevkb() { xev -event keyboard; }
2525
2526 # * misc stuff
2527
2528 vrun() {
2529 printf "running: %s\n" "$*"
2530 "$@"
2531 }
2532
2533 f=/a/f/ansible-configs/files/common/etc/fsf-workstation-bashrc.sh
2534 if [[ -e $f ]]; then
2535 # shellcheck disable=SC1090
2536 source $f
2537 fi
2538
2539 electrum() {
2540 # https://electrum.readthedocs.io/en/latest/tor.html
2541 # https://github.com/spesmilo/electrum-docs/issues/129
2542 s rsync -ptog --chown bitcoin:bitcoin ~/.Xauthority /var/lib/bitcoind/.Xauthority
2543 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/electrum-4.2.1-x86_64.AppImage -p socks5:localhost:9050
2544 }
2545 monero() {
2546 sudo -u bitcoin DISPLAY=$DISPLAY XAUTHORITY=/var/lib/bitcoind/.Xauthority /a/opt/monero-gui-v0.17.3.2/monero-wallet-gui
2547 }
2548
2549
2550 reset-konsole() {
2551 # we also have a file in /a/c/...konsole...
2552 local f=$HOME/.config/konsolerc
2553 setini DefaultProfile profileian.profile "Desktop Entry" $f
2554 setini Favorites profileian.profile "Favorite Profiles" $f
2555 setini ShowMenuBarByDefault false KonsoleWindow $f
2556 setini TabBarPosition Top TabBar $f
2557 }
2558
2559 reset-sakura() {
2560 while read -r k v; do
2561 # shellcheck disable=SC2154
2562 setini $k $v sakura /a/c/subdir_files/.config/sakura/sakura.conf
2563 done <<'EOF'
2564 colorset1_back rgb(33,37,39)
2565 less_questions true
2566 audible_bell No
2567 visible_bell No
2568 disable_numbered_tabswitch true
2569 scroll_lines 10000000
2570 scrollbar true
2571 EOF
2572 }
2573
2574 # make a page of links found in the files $@. redirect output
2575 linkhtml() {
2576 gr -oh 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)' "$@" | \
2577 rev | sort -u | rev | sed 's,.*,<a href="\0">\0</a><br\>,'
2578 }
2579
2580 reset-xscreensaver() {
2581 # except for spash, i set these by setting gui options in
2582 # xscreensaver-command -demo
2583 # then finding the corresponding option in .xscreensaver
2584 # spash, i happened to notice in .xscreensaver
2585 #
2586 # dpmsOff, monitor doesnt come back on using old free software supported nvidia card
2587 cat > /home/iank/.xscreensaver <<'EOF'
2588 mode: blank
2589 dpmsEnabled: True
2590 dpmsStandby: 0:07:00
2591 dpmsSuspend: 0:08:00
2592 dpmsOff: 0:00:00
2593 timeout: 0:05:00
2594 lock: True
2595 lockTimeout: 0:06:00
2596 splash: False
2597 EOF
2598
2599 }
2600
2601
2602 # very useful, copy directory structure 3 deep. add remove /*/ to change level
2603 # rsync -aivh --exclude '/*/*/*/' -f"+ */" -f"- *" SRC DEST
2604
2605
2606 # * stuff that makes sense to be at the end
2607 if [[ "$SUDOD" ]]; then
2608 # allow failure, for example if we are sudoing into a user with diffferent/lesser permissions.
2609 cd "$SUDOD" ||:
2610 unset SUDOD
2611 elif [[ -d /a ]] && [[ $PWD == "$HOME" ]] && [[ $- == *i* ]]; then
2612 cd /a
2613 OLDPWD=
2614 fi
2615
2616
2617
2618
2619 # for mitmproxy to get a newer python.
2620 # commented until i want to use it because it
2621 # noticably slows bash startup
2622 #
2623
2624 mypyenvinit () {
2625 if [[ $EUID == 0 || ! -e ~/.pyenv/bin ]]; then
2626 echo "error: dont be root. make sure pyenv is installed"
2627 return 1
2628 fi
2629 export PATH="$HOME/.pyenv/bin:$PATH"
2630 eval "$(pyenv init -)"
2631 eval "$(pyenv virtualenv-init -)"
2632 }
2633
2634
2635 export GOPATH=$HOME/go
2636 path-add $GOPATH/bin
2637 path-add /usr/local/go/bin
2638
2639 # I have the git repo and a release. either one should work.
2640 # I have both because I was trying to solve an issue that
2641 # turned out to be unrelated.
2642 # ARDUINO_PATH=/a/opt/Arduino/build/linux/work
2643
2644 ## i should have documented this...
2645 # based on https://github.com/keyboardio/Kaleidoscope
2646 export KALEIDOSCOPE_DIR=/a/opt/Kaleidoscope
2647
2648 # They want to be added to the start, but i think
2649 # that should be avoided unless we really need it.
2650 path-add --end ~/.npm-global
2651
2652
2653 path-add --end $HOME/.cargo/bin
2654
2655 if type -P rg &>/dev/null; then
2656 # --no-messages because of annoying errors on broken symlinks
2657 # -z = search .gz etc files
2658 # -. = search dotfilesq
2659 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 $?; }
2660 #fails if not exist. ignore
2661 complete -r rg 2>/dev/null ||:
2662 else
2663 alias rg=grr
2664 fi
2665
2666
2667
2668 # taken from default changes to bashrc and bash_profile
2669 path-add --end --ifexists $HOME/.rvm/bin
2670 # also had ruby bin dir, but moved that to environment.sh
2671 # so its included in overall env
2672
2673
2674 export BASEFILE_DIR=/a/bin/fai-basefiles
2675
2676 #export ANDROID_HOME=/a/opt/android-home
2677 # https://f-droid.org/en/docs/Installing_the_Server_and_Repo_Tools/
2678 #export USE_SDK_WRAPPER=yes
2679 #PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
2680
2681 # didnt get drush working, if I did, this seems like the
2682 # only good thing to include for it.
2683 # Include Drush completion.
2684 # if [ -f "/home/ian/.drush/drush.complete.sh" ] ; then
2685 # source /home/ian/.drush/drush.complete.sh
2686 # fi
2687
2688
2689 # best practice
2690 unset IFS
2691
2692 # https://wiki.archlinux.org/index.php/Xinitrc#Autostart_X_at_login
2693 # i added an extra condition as gentoo xorg guide says depending on
2694 # $DISPLAY is fragile.
2695 if [[ ! $DISPLAY && $XDG_VTNR == 1 ]] && shopt -q login_shell && isarch; then
2696 exec startx
2697 fi
2698
2699
2700 # ensure no bad programs appending to this file will have an affect
2701 return 0