#!/bin/bash # Copyright (C) 2019 Ian Kelling # SPDX-License-Identifier: AGPL-3.0-or-later # this gets sourced. shebang is just for file mode detection # * settings if [[ $LESSHISTFILE == - ]]; then HISTFILE= c() { cd "$@"; } elif [[ $HISTFILE ]]; then HISTFILE=$HOME/.bh fi source /a/bin/distro-setup/path-add-function path-add /a/exe # add this with absolute paths as needed for better security #path-add --end /path/to/node_modules/.bin ## for yarn, etc #path-add --end /usr/lib/node_modules/corepack/shims/ # pip3 --user things go here: path-add --end ~/.local/bin path-add --ifexists --end /a/work/libremanage path-add --ifexists --end /a/opt/adt-bundle*/tools /a/opt/adt-bundle*/platform-tools path-add --ifexists --end /a/opt/scancode-toolkit-3.10. path-add --ifexists --end /p/bin case $HOSTNAME in sy|bo) # https://askubuntu.com/questions/1254544/vlc-crashes-when-opening-any-file-ubuntu-20-04 if grep -qE '^VERSION_CODENAME="(nabia|focal)"' /etc/os-release &>/dev/null; then export MESA_LOADER_DRIVER_OVERRIDE=i965 fi ;; esac export WCDHOME=/a case $EUID in 0) # shellcheck disable=SC2034 # used in brc SL_SSH_ARGS="-F $HOME/.ssh/confighome" ;; esac # * include files # generated instead of dynamic for the benefit of shellcheck #for x in /a/bin/distro-functions/src/* /a/bin/!(githtml)/*-function?(s); do echo source $x ; done source /a/bin/distro-functions/src/identify-distros source /a/bin/log-quiet/logq-function # for x in /a/bin/bash_unpublished/source-!(.#*); do echo source $x; done source /a/bin/bash_unpublished/source-semi-priv source /a/bin/bash_unpublished/source-state source /a/bin/log-quiet/logq-function # not used # if [[ -s /a/opt/alacritty/extra/completions/alacritty.bash ]]; then # source /a/opt/alacritty/extra/completions/alacritty.bash # fi source /a/bin/ds/beet-data # * functions multimic() { local i local -a sources m pactl unload-module module-loopback m pactl unload-module module-null-sink m pactl unload-module module-remap-source IFS=" " read -r -a sources <<<"$(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p')" if (( ! $# )); then i=0 for s in ${sources[@]}; do e $i $s i=$(( i+1 )) done read -r l set -- $l fi m pactl load-module module-null-sink sink_name=ianinput sink_properties=device.description=ianinputs for i; do m pactl load-module module-loopback source=${sources[i]} sink_dont_move=true sink=ianinput done pactl load-module module-remap-source source_name=iancombine master=ianinput.monitor source_properties=device.description=iancombine } # h ssh test # For testing restrictive ssh. hstest() { install-my-scripts d=$(mktemp -d) sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config s command ssh -F $d/config -i /q/root/h "$@" } # h rsync test # For testing restrictive rsync hrtest() { # install-my-scripts d=$(mktemp -d) sed '/^ *IdentityFile/d' ~/.ssh/config >$d/config s rsync -e "ssh -F $d/config -i /q/root/h" "$@" } # rsync as root and avoid the default restrictive h key & config. rootrsync() { s rsync -e "ssh -F /root/.ssh/confighome" "$@" } zcheck() { ssh bow DISPLAY=:0 scrot /tmp/oegu.jpg scp bow:/tmp/oegu.jpg /t ssh bow rm /tmp/oegu.jpg feh /t/oegu.jpg } slemacs() { local arg rtime v arg="$1" remote="$2" if [[ $arg == [89]0Etiona* ]]; then v=${arg::1} rtime=${arg#*Etiona} # remote time if [[ ! $rtime ]]; then rtime=0 fi dir=/a/opt/emacs-trisquel${v}-nox/.iank ltime=$(stat -c%Y $dir/e/e/.emacs.d/init.el) if (( ltime > rtime )); then m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" $dir "$remote":/home/iank fi fi } sle() { # sl emacs local f=/home/iank/.emacs.d/init.el 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 "$@" } ccomp ssh sle # Run this manually after .emacs.d changes. Otherwise, to check if # files changed with find takes 90ms. sl normally only adds 25ms. We # could cut it down to 10ms if we put things on a btrfs filesystem and # looked for changes there, or used some inotify thing, but that seems # like too much work. egh() { # emacs gnuhope RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank lists2d.fsf.org:.ianktrisquel_9 RSYNC_RSH=ssh m rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank lists2d.fsf.org:/home/iank } ekw() { local shell="bash -s" if [[ $HOSTNAME != kw ]]; then shell="ssh kw.office.fsf.org" bbk -m /a -t kw fi $shell <<'EOF' sudo mkdir /root/.ianktrisquel_9 sudo rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel9-nox/.iank /root/.ianktrisquel_9 rsync -rptL --delete --filter=". /b/ds/sl/rsync-filter" /a/opt/emacs-trisquel8-nox/.iank /home/iank EOF } rm-docker-iptables() { s iptables -S | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables $l; done s iptables -S -t nat | gr docker | gr -- -A | sed 's/-A/-D/'| while read -r l; do sudo iptables -t nat $l; done s iptables -S | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables $l; done s iptables -S -t nat | gr docker | gr -- -N | sed 's/-N/-X/'| while read -r l; do sudo iptables -t nat $l; done } # usage mkschroot [-] distro codename packages # - means no piping in of sources.list mkschroot() { local sources force repo n distro force=false while [[ $1 == -* ]]; do case $1 in -f) force=true; shift ;; -s) sources="$2" if [[ ! -s $sources ]]; then echo mkschroot: error: sources file $sources does not exist or is empty return 1 fi shift 2 ;; esac done distro=$1 shift case $distro in trisquel) repo=http://mirror.fsf.org/trisquel/ ;; ubuntu) repo=http://archive.ubuntu.com/ubuntu/ ;; debian) repo=http://deb.debian.org/debian/ ;; esac n=$1 shift if ! $force && schroot -l | grep -xFq chroot:$n; then echo "$0: $n schroot already installed, skipping" return 0 fi apps=($@) d=/nocow/schroot/$n sd /etc/schroot/chroot.d/$n.conf <> $f # fi # su iank # wget https://aur.archlinux.org/cgit/aur.git/snapshot/anbox-image-gapps.tar.gz # tar xzf anbox-image-gapps.tar.gz # cd anbox-image-gapps # makepkg -s } # clock back in to timetrack from last entry tback() { sqlite3 /p/.timetrap.db "update entries set end = NULL where id = (select max(id) from entries);" } # sshfs example: # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other eqgo() { local -a array tmpstr tmpstr=$(exiqgrep -i -r.\*) mapfile -t array <<<"$tmpstr" enn -M "${array[@]}" } eqgo1() { enn -M "$(exipick -i -r.\*|h1)" } gnupload(){ /a/f/gnulib/build-aux/gnupload "$@" } abrowserrmcompat() { local f ngset f=(/p/c/firefox*/compatibility.ini) if (( ${#f[@]} )); then rm ${f[@]} fi ngreset } checkre() { s checkrestart -b /a/bin/ds/checkrestart-blacklist -pv } cp-blocked-domains-to-brains() { cp /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn } cp-blocked-domains-to-ansible() { cp /a/f/brains/sysadmin/kb/blocked_email_domains.mdwn /a/f/ans/roles/exim/files/mx/simple/etc/exim4/bad-sender_domains } anki() { # crashes on adding new cards in t9 schroot -c buster -- anki } acat() { ngset hrcat /m/md/alerts/{cur,new}/* ngreset hr; echo bk; hr ssh bk.b8.nz "shopt -s nullglob; hrcat /m/md/INBOX/new/* /m/md/INBOX/cur/*" } aclear() { ngset rm -f /m/md/alerts/{cur,new}/* ngreset ssh bk.b8.nz "shopt -s nullglob; rm -f /m/md/INBOX/new/* /m/md/INBOX/cur/*" system-status _ } alerts() { find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f } ralerts() { # remote alerts local ret shell # this list is duplicated in check-remote-mailqs for h in bk je li frodo x3wg kdwg sywg; do echo $h: shell="ssh $h" if [[ $HOSTNAME == "${h%wg}" ]]; then shell= fi ret=0 $shell find /var/local/cron-errors /home/iank/cron-errors /sysd-mail-once-state -type f || ret=$? if (( ret )); then echo ret:$ret fi done } ap() { # pushd in case current directory has an ansible.cfg file pushd /a/xans >/dev/null ansible-playbook -v -l ${1:- $(hostname -f)} site.yml popd >/dev/null } aw() { pushd /a/work/ans >/dev/null time ansible-playbook -i inventory adhoc.yml "$@" popd >/dev/null } ad() { pushd /a/bin/distro-setup/a >/dev/null ansible-playbook site.yml "$@" popd >/dev/null } astudio() { # googling android emulator libGL error: failed to load driver: r600 # lead to http://stackoverflow.com/a/36625175/14456 export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 /a/opt/android-studio/bin/studio.sh "$@" & r } # Convert brains file path to url and vice versa # usage: brains [URL_OR_PATH] brains() { _iki-convert '(/a)?/f/brains' brains.fsf.org "$@" } glue() { _iki-convert '(/a)?/f/gluestick' gluestick.office.fsf.org "$@" } # usage: $0 REPO_PATH [URL_OR_PATH] _iki-convert() { local url domain path input err repo_dir dir url_dir url repo_dir="$1" domain="$2" shift 2 err=false if $err; then return 1 fi if [[ $1 ]]; then input="$*" else read -r -p "enter path or url"$'\n' input fi case $input in http*) path="$repo_dir/${input##http*://"$domain"/wiki/}" if [[ $path == */ ]]; then path=${path%/}.mdwn fi j printf "%s\n" "$path" ;; *) path=$(fp "$input") url_dir=$(echo "$path" | sed -r "s,^$repo_dir/,,") url="https://$domain/wiki/$url_dir" url="${url%.mdwn}/" j echo "$url" ;; esac } # Generate beet smartplaylists for navidrome. # for going in the reverse direction, run # /b/ds/navidrome-playlist-export beetsmartplaylists() { install -m 0700 -d /tmp/ianbeetstmp beet splupdate # kill off any playlists we deleted. they will still need manual # killing from a navidrome client. rm -rf /i/converted/beetsmartplaylists mkdir -p /i/converted/beetsmartplaylists for f in /tmp/ianbeetstmp/*; do sed 's,^/i/m,/i/converted,;s,\.flac$,.mp3,' "$f" >"/i/converted/beetsmartplaylists/${f##*/}" rm "$f" done rmdir /tmp/ianbeetstmp } # internal function for beetrating, in case we need to ssh beetrating-stdin() { local tmp rating path cpath sqlpath userid # plucked this from the db. im the only user. userid=23cc2eb9-e35e-4811-a0f0-d5f0dd6eb634 while read -r rating path; do cpath="/i/converted${path#/i/m}" # converted path case $cpath in *.flac) cpath="${cpath%.*}.mp3" ;; esac if [[ ! -e $cpath ]]; then echo "beetraing: error: this should not happen, path does not exist: $cpath" return 1 fi sqlpath="${cpath//\'/\'\'}" 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';") if [[ $old_rating ]]; then if [[ $old_rating != "$rating" ]]; then echo "setting rating $old_rating -> $rating $cpath" # https://stackoverflow.com/a/50317320 # we got a timeout error once. arbitrarily chose 15 seconds. sqlite3 /i/navidrome/navidrome.db ".timeout 15000" " update annotation set rating = $rating where item_id in ( select media_file.id from annotation inner join media_file on annotation.item_id = media_file.id where media_file.path = '$sqlpath' and annotation.item_type = 'media_file' );" fi else echo "setting rating $rating $cpath" # /a/opt/navidrome/persistence/sql_annotations.go v0.48.0 # https://www.sqlite.org/lang_insert.html 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';" fi done } # Export beets ratings into navidrome beetrating() { local ssh_prefix if [[ $HOSTNAME != kd ]]; then ssh_prefix="ssh b8.nz" fi # shellcheck disable=SC2016 # obvious reason beet ls -f '$rating $path' $nav_convert_query | $ssh_prefix beetrating-stdin } # Do transcoding and hardlinking of audio files for navidrome. beetconvert() { local tmpf tmpf="$(mktemp)" # a bunch of effort to ignore output we dont care about... sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf beet -c $tmpf convert -y $nav_convert_query > >(grep -vFx 'ignore_this' ||:) 2> >(grep -v '^convert: Skipping' ||:) rm "$tmpf" } # This deletes files in the converted directory which should no longer # be there due to a rename of the unconverted file. beetconvert-rm-extras() { local l tmpf local -A paths tmpf="$(mktemp)" # shellcheck disable=SC2016 # obvious reason beet ls -f '$path' $nav_convert_query >"$tmpf" ## begin removal of files that are leftover from previous conversion, # eg, previously rated > 1, now rated 1. while read -r l; do convertedpath="/i/converted${l#/i/m}" case $convertedpath in *.flac) convertedpath="${convertedpath%.flac}.mp3" ;; esac paths[$convertedpath]=t done <"$tmpf" find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \) -name '*.mp3' -o -name '*.m4a' >"$tmpf" while read -r l; do if [[ ! ${paths[$l]} ]]; then rm -v "$l" fi # note: the pruning is duplicative of filtering on name, but whatever. done <"$tmpf" rm "$tmpf" } beets-gen-playlists() { local i str local -a query_array query_str for i in "${!bpla[@]}"; do query_str=() eval "query_array=(${bpla[$i]})" for str in "${query_array[@]}"; do query_str+=("\"$str\"") done cat <$tmpf beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:) rm "$tmpf" beetag-nostatus 1 } kill-bg-quiet() { # https://stackoverflow.com/a/5722874 kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||: } # debug variables dv() { for arg; do printf "%s=%s " "$arg" "${!arg}" done echo } # Must be called from beetag for variables to be setup beetag-help() { local -i i j col_total row col button_total row_total remainder_cols remainder_term col_total=4 button_total=${#button_map[@]} row_total=$(( button_total / col_total )) remainder_cols=$(( button_total % col_total )) # for debugging #dv button_total row_total remainder_cols beetag-nostatus # - 3 is just a constant that helps things work in practice. if [[ $LINES ]] && (( LINES - 3 < scrolled )); then hr for (( i=0; i/dev/null; then printf "%s\n" "$*" | socat - /tmp/mpvsock >/dev/null ||: fi } # meant to be called from beetag # o for get output mpvrpco() { # note: testing for background jobs will output nothing if we are in a pipeline printf "%s\n" "$*" | socat - /tmp/mpvsock ||: } # meant to be called from beetag mpvrpc-percent-pos() { mpvrpco '{ "command": ["get_property", "percent-pos"] }' | jq .data | sed 's/\..*/%/' 2>/dev/null ||: } # tag with beets. # usage: beetag [-r] [-s] QUERY # it lists the query, reads an input char for tagging one by one. # # note, you may want to change the play command for doing rapid taging # by immediately jumping forward into the song. this is set in the beets # config yaml. # # (available buttons: ` \ ) ] [ and non-printing chars, see # https://stackoverflow.com/questions/10679188/casing-arrow-keys-in-bash # # # note: after foregrounding the player, must quit it to get back. can't ctrl-c. # # keys I dont need help to remember: # 1-5 rate # q quit # ret next # beetag() { local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random path local do_rare_genres read_wait help line lsout tmp ls_line skip_lookback local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec local pl_state_path pl_state_dir pl_state_file tmpstr local new_random pl_seed_path seed_num seed_file fmt first_play repeat1 local -a buttons button_map ids tags tmp_tags initial_ls ls_lines paths local -A button_i local -i i j volume scrolled id_count line_int skip_start pre_j_count head_count skip_lookback local -i overflow_lines overflow first_play=true erasable_line=false escape_char=$(printf "\u1b") scrolled=999 # more than any $LINES ### begin arg processing ### random=false repeat1=false new_random=false case $1 in -r) random=true shift ;; -s) random=false shift ;; -x) new_random=true shift ;; esac if (( ! $# )); then echo beetag: error expected a query arg >&2 return 1 fi ### end arg processing ### beetpull do_rare_genres=false volume=70 read_wait=2 doplay=true last_genre_i=$(( ${#common_genres[@]} - 1 )) buttons=( {a..p} {r..w} {6..8} , . / - "=") button_map=(${common_genres[@]} ${pl_tags[@]}) fstring= for tag in "${pl_tags[@]}"; do fstring+="%ifdef{$tag,$tag }" done for (( i=0; i<${#buttons[@]}; i++ )); do button_i[${buttons[i]}]=$i done # note: this structure of files is rather haphazard. seed_num=1 # later we might want a few seed_file=seed$seed_num if $random; then pl_state_file=$seed_num else pl_state_file=sorted fi pl_state_dir=/i/info/pl-state if [[ $playlist ]]; then pl_state_dir=$pl_state_dir/$playlist else pl_state_dir=$pl_state_dir/nopl fi pl_state_path=$pl_state_dir/$pl_state_file pl_seed_path=$pl_state_dir/$seed_file if $new_random || [[ ! -r $pl_seed_path ]]; then mkdir -p $pl_state_dir { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path fi # PijokVipiotOzeph is just a random string for a delimiter # shellcheck disable=SC2016 # false positive fmt='%ifdef{rating,$rating }'"$fstring"'$genre | $title - $artist - $album $length $id PijokVipiotOzeph $path' # shellcheck disable=SC2016 # obvious reason tmpstr=$(beet ls -f "$fmt" "$@" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } ) mapfile -t initial_ls <<<"$tmpstr" if [[ ! ${initial_ls[0]} ]]; then echo "beetag: error: no result from beet ls $*" return 1 fi id_count=${#initial_ls[@]} for line in "${initial_ls[@]}"; do path="${line#*PijokVipiotOzeph }" # https://github.com/koalaman/shellcheck/issues/2171 # shellcheck disable=SC2190 # bug in shellcheck, looking at paths from an earlier function paths+=("$path") line_no_path="${line% PijokVipiotOzeph*}" id="${line_no_path##* }" ids+=("$id") right_pad="${line_no_path%% |*}" ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}" ls_lines+=("$ls_line") i=$(( i+1 )) done j=0 if [[ $playlist ]]; then if [[ -r $pl_state_path ]]; then j=$(cat $pl_state_path) fi fi # i only care to see a smallish portion of the list when starting. head_count=$(( LINES - 20 )) head_start=$(( j - head_count / 2 )) if (( head_start < 0 )); then head_start=0 fi for (( i=head_start; i < head_count && i < id_count; i++ )); do ls_line="${ls_lines[$i]}" if (( i == j )); then echo "* $ls_line" else echo "$ls_line" fi done if $doplay; then #{ mpv --profile=a --volume=$volume --idle 2>&1 & } 2>/dev/null mpv --profile=a --volume=$volume --idle & # if we dont sleep, can expect an error like this: # socat[1103381] E connect(5, AF=1 "/tmp/mpvsock", 14): Connection refused sleep .1 fi while true; do id=${ids[j]} path="${paths[$j]}" lsout="${ls_lines[j]}" tags=( ${lsout%%,*} ) beetag-help printf "██ %s\n" "$lsout" beetag-nostatus 1 if $doplay; then # https://stackoverflow.com/a/7687716 # note: duplicated down below # # notes on old method of invoking mpv each time: # https://superuser.com/questions/305933/preventing-bash-from-displaying-done-when-a-background-command-finishes-execut # we can't disown or run in a subshell or set +m because all that # disabled job control from working properly in ways we want. # todo: figure out some kind of answer to this. I think the solution # is that we are waiting in 2 second intervals and checking if the # background job exists. Instead, we should make mpv just idle # when it is done with a song and then send it a command to play a new track. #{ mpv --profile=a --volume=$volume "$path" 2>&1 & } 2>/dev/null # old #{ beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null # on slow systems, we may need to wait like .3 seconds before mpv # is ready. so impatiently check until it is ready if $first_play; then first_play=false for (( i=0; i<20; i++ )); do if [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' 2>/dev/null | jq .data) == true ]]; then mpvrpc '{ "command": ["loadfile", "'"$path"'"] }' 2>/dev/null break fi sleep .1 done else mpvrpc '{ "command": ["loadfile", "'"$path"'"] }' fi erasable_line=false fi while true; do char= if $doplay; then ret=0 read -rsN1 -t $read_wait char || ret=$? read_wait=2 # Automatically skip to the next song if this one ends, unless # we turn off the autoplay. if (( ret == 142 )) || [[ ! $char ]]; then if jobs -p | grep -q . &>/dev/null && \ [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' | jq .data) == false ]]; then continue else break fi fi else read -rsN1 char fi beetag-help if [[ $char == $'\n' ]]; then break fi case $char in ";") j=$(( j - 2 )) break ;; "'") if $doplay; then echo "play toggled off" doplay=false else doplay=true mpvrpc '{ "command": ["loadfile", "'"$path"'"] }' erasable_line=false fi beetag-nostatus 1 continue ;; _) m beet rm --delete --force "id:$id" beetag-nostatus 4 # guessing. dont want to test atm break ;; [1-5]) beetmq "id:$id" rating=$char continue ;; 9) volume=$(( volume - 5 )) if (( volume < 0 )); then volume=0 fi ;;& 0) volume+=5 if (( volume > 130 )); then volume=130 fi ;;& 0|9) mpvrpc '{ "command": ["set_property", "volume", '$volume'] }' beetag-status echo volume=$volume continue ;; ']') if $repeat1; then repeat1=false else repeat1=true fi echo repeat1=$repeat1 continue ;; q) kill-bg-quiet return ;; y) if $do_rare_genres; then do_rare_genres=false button_map=(${common_genres[@]} ${pl_tags[@]}) last_genre_i=$(( ${#rare_genres[@]} - 1 )) else do_rare_genres=true button_map=(${rare_genres[@]} ${pl_tags[@]}) last_genre_i=$(( ${#rare_genres[@]} - 1 )) fi local -A button_i for (( i=0; i<${#buttons[@]}; i++ )); do button_i[${buttons[i]}]=$i done for (( i=0; i<${#button_map[@]}; i++ )); do echo ${buttons[i]} ${button_map[i]} done continue ;; z) beetag-nostatus 3 # if we ctrl-z, it will put the whole function into sleep. so # basically, we can't return from a foregrounded mpv like we # would like to without some strange mechanism I can't think # of. So, instead, detect ctrl-c and wait a while for prompt # input. One idea would be to use a music player like mpd where # we can send it messages. if ! fg; then read_wait=10 fi continue ;; # " ") # output time if we aren't already paused if [[ $(mpvrpco '{ "command": ["get_property", "pause"] }' | jq .data) == false ]]; then # minutes/seconds #date -d @"$(mpvrpco '{ "command": ["get_property", "playback-time"] }' | jq .data)" +%M:%S ||: beetag-status mpvrpc-percent-pos fi # originally found this solution, which worked fine. #kill -STOP %% &>/dev/null # mpvrpc '{ "command": ["cycle", "pause"] }' continue ;; "$escape_char") expected_input=true read -rsn2 escaped_input skip_input_regex="^[0-9]+$" case $escaped_input in # up char: show all the songs, use less '[A') skip_start=0 skip_lookback=5 if (( j - skip_lookback > skip_start )); then skip_start=$(( j - skip_lookback )) fi beetag-nostatus $(( id_count - skip_start - 1 )) { line_int=0 for (( i=skip_start; i < id_count; i++ )); do if (( i == j )); then echo " * ${ls_lines[i]}" continue fi echo "$line_int | ${ls_lines[i]}" line_int+=1 done } | less -F ;; # down char '[B') # skip forward, but show the last few songs anyways. skip_start=0 skip_lookback=3 if (( j - skip_lookback > skip_start )); then skip_start=$(( j - skip_lookback )) fi beetag-nostatus $(( id_count - skip_start - 1 )) line_int=0 overflow_lines=$LINES for (( i=skip_start; i < overflow_lines - 1 && i < id_count; i++ )); do ls_line="${ls_lines[i]}" overflow=$(( ${#ls_line} / ( COLUMNS - 1 ) )) overflow_lines=$(( overflow_lines - overflow )) if (( i == j )); then echo " * $ls_line" continue fi echo "$line_int | $ls_line" line_int+=1 done ;; # left key '[D') seek_sec=-8 ;;& # right key '[C') seek_sec=8 ;;& '[C'|'[D') beetag-status mpvrpc-percent-pos erasable_line=true mpvrpc '{ "command": ["seek", "'$seek_sec'"] }' continue ;; *) expected_input=false ;; esac if $expected_input; then read -r skip_input case $skip_input in q) kill-bg-quiet return ;; esac if [[ $skip_input =~ $skip_input_regex ]]; then pre_j_count=$(( j - skip_start )) j=$(( j + skip_input - pre_j_count )) if (( skip_input < pre_j_count )); then j=$(( j - 1 )) fi fi break fi ;; esac char_i=${button_i[$char]} new_item=${button_map[$char_i]} if [[ ! $char_i || ! $new_item ]]; then echo "error: no mapping of input: $char found, try again" continue fi if (( char_i <= last_genre_i )); then m beetmq "id:$id" genre=$new_item else remove=false tmp_tags=() for tag in ${tags[@]}; do if [[ $new_item == "$tag" ]]; then remove=true else tmp_tags+=("$tag") fi done if $remove; then tags=("${tags[@]}") m beetmq "id:$id" "$new_item!" else tags+=("$new_item") m beetmq "id:$id" $new_item=t fi fi done if ! $repeat1; then if (( j < id_count - 1 )); then j+=1 else j=0 fi fi if [[ $playlist ]]; then echo $j >$pl_state_path fi done } # usage: FILE|ALBUM_DIR [GENRE] beetadd() { local import_path genre_arg single_track_arg import_path="$1" if [[ ! -e $import_path ]]; then echo "beetadd error: path does not exist" fi if [[ $2 ]]; then genre_arg="--set genre=$2" fi if [[ -f $import_path ]]; then single_track_arg=-s fi beet import --set totag=t $single_track_arg $genre_arg "$import_path" beetag totag:t beet modify -y totag:t "totag!" } # update navidrome music data after doing beets tagging beet2nav() { m beetpull m beetconvert m beetrating # this function would naturally just be part of beetconvert, # but we want beetrating to happen sooner so that our ssh auth dialog # happens earlier. Currently 17 seconds for that. m beetconvert-rm-extras m beetsmartplaylists } # pull in beets library locally beetpull() { local sshfs_host sshfs_host=b8.nz if [[ $HOSTNAME == kd ]]; then return 0 fi if [[ ! -e /i ]]; then s mkdir /i s chown iank:iank /i fi if ! mountpoint /i &>/dev/null; then m sshfs $sshfs_host:/i /i fi } # remove all playlists in navidrome, for when I make big # playlist name changes and just want to scrap everything. nav-rm-plists() { local tmpf id tmpf=$(mktemp) if [[ $HOSTNAME != kd ]]; then echo "error: run on kd" return 1 fi sqlite3 /i/navidrome/navidrome.db "select id from playlist" >$tmpf while read -r id; do 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" done <$tmpf rm $tmpf } # escape regex. # # This is not perfect but generally good enough. It escapes all # metachars listed man 3 pcrepattern. er() { sed 's/[]\\^$.[|()?*+{}]/[&]/g; s/\^/\\^/g' <<<"$*" } # usage beegenre QUERY # # beet set genre for QUERY based on existing artist most used genre on # # inverse of query for each artist found in QUERY. If query starts with # "artist:" it is used as the artist instead of each artist in QUERY. # beegenre() { local count artist artregex genre singleartist tmpf tmpf2 local -a artists genres singleartist=false case $1 in artist:*) singleartist=true artist="$1" shift ;; esac tmpf=$(mktemp) tmpf2=$(mktemp) if $singleartist; then # shellcheck disable=SC2016 # obvious reason beet ls -f '$genre' "$artist" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1 >$tmpf read -r count genre <$tmpf ||: beet modify "$artist" "$@" genre=$genre else # shellcheck disable=SC2016 # obvious reason beet ls -f '$artist' "$@" | sort -u >$tmpf while read -r artist; do artregex=$(er "$artist") # shellcheck disable=SC2016 # obvious reason beet ls -f '$genre' "artist::^$artregex$" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1 >$tmpf2 read -r count genre <$tmpf2 || continue if [[ $count ]]; then artists+=("$artregex") genres+=("$genre") echo "beet modify -y $* \"artist::^$artist$\" genre=$genre # $count" fi done <$tmpf read -r -N 1 -s -p "Y/n " char case $char in [Yy$'\n']) for (( i=0; i<${#artists[@]}; i++ )); do beet modify -y "$@" "artist::^${artists[i]}$" genre=${genre[i]} done ;; esac fi rm $tmpf } # prettify the date btrbk-date() { local indate indate="$1" shift date +%F_%T%:::z -d "$(sed -r 's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<"$indate")" "$@" } btrbk-undate() { # fudCaHougfirp is a random string { if [[ $1 ]]; then echo "$1" else cat fi } | sed -r 's/-0([45])( |$)/fudCaHougfirp0\100/;s/_/T/;s/[:-]//g;s/fudCaHougfirp/-/' } btrbk-date-sed() { local line while read -r line; do 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 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*}" 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}" mid="${line:${#pre}:22}" echo "$pre$(btrbk-date "$mid")$post" else echo "$line" fi done } jrbtrbk() { jr -u btrbk-run -u btrbk -u switch-mail-host -u btrbk-spread "$@" } # internal function btrbk-host-debug-show-host() { for f; do snaphost= for host in $remote $alt local; do if line=$(grep -P "\S*$f" /tmp/b/s/$host.log); then if [[ $snaphost ]]; then e error: snaphost=$snaphost, host=$host line="$line" fi if [[ $line == ssh* ]]; then tmp="${line#ssh://}" snaphost="${tmp%%/*}" else snaphost=$host fi fi done echo $snaphost $f | btrbk-date-sed done } # If we get a btrfs receive error like this: # ERROR: ... clone: did not find source subvol # running this command will help track down the problem. # Alter remote= and alt=. When I used it, remote is # the host having the error when I push a snapshot. # Alt is just the other host that takes snapshots # besides the local host. btrbk-host-debug() { remote=b8.nz alt=sywg.b8.nz mkdir -p /tmp/b/s for host in $remote $alt; do h=$(ssh $host hostname) rsync -a /var/log/btrbk $host:/var/log/btrbk /var/log/btrbk/$h grr '\bsnapshot success' /var/log/btrbk/$h >/tmp/b/$h.log ## this takes a while, we only want to do it on 1st run # if [[ -s /tmp/b/$host.log ]]; then continue; fi # ssh $host journalctl -u btrbk-run -u btrbk -u switch-mail-host -u btrbk-spread >/tmp/b/$host.log done gr '\bsnapshot success' /var/log/btrbk/*.log >/tmp/b/local.log cd /tmp/b for f in *.log; do gr '\bsnapshot success' $f >s/$f done cd /mnt/root/btrbk localq=(q.*) declare -A localq_a for f in "${localq[@]}"; do localq_a[$f]=t done remoteq=() for f in $(ssh $remote "cd /mnt/root/btrbk; echo q.*"); do if [[ ! ${localq_a[$f]} ]]; then remoteq+=($f) fi done btrbk-host-debug-show-host "${localq[@]}" if (( ${#remoteq[@]} >= 1 )); then echo "=== $remote only ====" btrbk-host-debug-show-host ${remoteq[@]} fi } # note, to check for glue records # First, find some the .org nameservers: # dig +trace iankelling.org # then, query one: # dig ns1.iankelling.org @b0.org.afilias-nst.org. # Now, compare for a domain that does have glue records setup (note the A # and AAAA records in ADDITIONAL SECTION, those are glue records like the # one I'm asking for): # $ dig ns1.gnu.org @b0.org.afilias-nst.org. # todo: make sm pull/push use systemd instead of the journal cat command bbk() { # btrbk wrapper local ret=0 c / local active=true systemctl is-active btrbk.timer || active=false if $active; then ser stop btrbk.timer fi btrbk_is_active=$(systemctl is-active btrbk.service ||:) case $btrbk_is_active in inactive|failed) : ;; *) echo "bbk: error: systemctl is-active btrbk.service output: $btrbk_is_active" if $active; then ser start btrbk.timer; fi return 1 ;; esac # todo: consider changing this to srun and having the args come # from a file like /etc/default/btrbk, like is done in exim s jdo btrbk-run "$@" if $active; then if (( ret )); then echo bbk: WARNING: btrbk.timer not restarted due to failure else ser start btrbk.timer fi fi return $ret } faimon() { fai-monitor | pee cat "fai-monitor-gui -" } bfg() { java -jar /a/opt/bfg-1.12.14.jar "$@"; } bigclock() { xclock -digital -update 1 -face 'arial black-80:bold' } nnn() { /a/opt/nnn -H "$@"; } locat() { # log-once cat local files ngset files=(/var/local/cron-errors/* /home/iank/cron-errors/* /sysd-mail-once-state/*) case ${#files[@]} in 0) : ;; 1) echo ${files[0]} head ${files[0]} ;; *) head ${files[@]} ;; esac ngreset } scr() { screen -RD "$@" } # usage: first get an adb shell on the phone. # # just followed instructions in readme at # https://github.com/Yuubi-san/ceb-tools # tried to use ceb2txt but it failed because of schema # slightly different than what it expected. cheogram-get-logs() { #adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup read -r -p "do cheogram backup on phone, do not enable extra cheogram data. press any key when done" cd /p/cheogram rm -rf Backup b adb pull /storage/emulated/0/Download/Cheogram/Backup sqlite3 b /dev/null ||: unset jr_pid fg &>/dev/null ||: # this avoids any err-catch (( ret == 0 )) || return $ret } # service run, and watch the output srun() { local unit ret=0 unit=$1 journalctl -qn2 -f -u $unit & systemctl start $unit sleep 2 kill $jr_pid &>/dev/null ||: unset jr_pid fg &>/dev/null ||: } sm() { # switch mail host local tmp keyhash c / # run latest keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}') tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"' ||:) if [[ ! $tmp ]]; then s ssh-add /root/.ssh/home fi s jdo switch-mail-host "$@" return $ret } sh2() { # switch host2 local tmp keyhash c / # run latest keyhash=$(s ssh-keygen -lf /root/.ssh/home | awk '{print $2}') tmp=$(s ssh-add -l | awk '$2 == "'$keyhash'"') if [[ ! $tmp ]]; then s ssh-add /root/.ssh/home fi install-my-scripts s jdo switch-host2 "$@" return $ret } # shellcheck disable=SC2120 lipush() { # note, i had --delete-excluded, but that deletes all files in --exclude-from on # the remote site, which doesn't make sense, so not sure why i had it. local p a # excluding emacs for now #p=(/a/opt/{emacs-debian11{,-nox},mu,emacs} /a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts}) p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter) a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes" ret=0 for h in li je bk; do m s rsync "$@" $a ${p[@]} /p/c/machine_specific/$h root@$h.b8.nz:/ ## only li is debian11 #p[0]=/a/opt/emacs-trisuqel10 #p[1]=/a/opt/emacs-trisquel10-nox done m s rsync "$@" -ahviSAXPH root@li.b8.nz:/a/h/proposed-comments/ /a/h/proposed-comments || ret=$? return $ret } bkpush() { # no emacs. for running faster. p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter) a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes" ret=0 m rsync "$@" $a ${p[@]} /p/c/machine_specific/bk root@bk.b8.nz:/ || ret=$? return $ret } jepush() { # no emacs. for running faster. p=(/a/bin /a/exe /a/h /a/c /p/c/machine_specific/vps{,.hosts} /c/roles/prom_export/files/simple/usr/local/bin/fsf-install-node-exporter) a="-ahviSAXPH --specials --devices --delete --relative --exclude-from=/p/c/li-rsync-excludes" ret=0 m rsync "$@" $a ${p[@]} /p/c/machine_specific/je root@je.b8.nz:/ || ret=$? return $ret } bindpush() { dsign iankelling.org expertpathologyreview.com zroe.org amnimal.ninja lipush for h in li bk; do m sl $h <<'EOF' source ~/.bashrc m dnsup EOF done } bindpushb8() { lipush for h in li bk; do m sl $h <<'EOF' source ~/.bashrc m dnsb8 EOF done } dnsup() { conflink -f m ser reload named } dnsb8() { local f=/var/lib/bind/db.b8.nz m ser stop named m sleep 1 m sudo rm -fv $f.jnl $f.signed.jnl m sudo install -m 644 -o bind -g bind /p/c/machine_specific/vps/bind-initial/db.b8.nz $f m ser restart named } dnsecgen() { # keys generated like this # because of https://ftp.isc.org/isc/dnssec-guide/dnssec-guide.pdf # https://blog.apnic.net/2019/05/23/how-to-deploying-dnssec-with-bind-and-ubuntu-server/ # key length is longer than that guide because # we are using those at fsf and when old key lengths # become insecure, I want some extra time to update. # dnsecgen (in brc2) local zone=$1 dnssec-keygen -a RSASHA256 -b 2048 $zone dnssec-keygen -f KSK -a RSASHA256 -b 4096 $zone for f in K"$zone".*.key; do # eg Kb8.nz.+008+47995.key tag=47995 # in dnsimple, you add the long string from this. # in gandi, you add the long string from the .key file, # then see that the digest matches the ds. echo "tag is the number after DS" dnssec-dsfromkey -a SHA-256 $f done # For b8.nz, we let bind read the keys and sign, and # right now they have root ownership, so let them # get group read. chmod g+r ./*.private } dsign() { # create .signed file # note: full paths probably not needed. local arg for arg; do local zone=${arg#db.} local dir=/p/c/machine_specific/vps/filesystem/var/lib/bind dnssec-signzone -S -e +31536000 -o $zone -K $dir -d $dir $dir/db.$zone done } # set day start for use in other programs. # expected to do be in a format like 830, or 800 or 1300. ds() { if [[ $1 ]]; then echo $1 >/b/data/daystart else cat /b/data/daystart fi } #### begin bitcoin related things btc() { local f=/etc/bitcoin/bitcoin.conf # importprivkey will timeout if using the default of 15 mins. # upped it to 1 hour. bitcoin-cli -rpcclienttimeout=60000 -"$(s grep rpcuser= $f)" -"$(s grep rpcpassword= $f)" "$@" } btcusd() { # $1 btc in usd local price price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)" printf "$%s\n" "$price" if [[ $1 ]]; then printf "$%.2f\n" "$(echo "scale=4; $price * $1"| bc -l)" fi } usdbtc() { # $1 usd in btc local price price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)" printf "$%s\n" "$price" if [[ $1 ]]; then # 100 mil satoshi / btc. 8 digits after the 1. printf "%.8f btc\n" "$(echo "scale=10; $1 / $price "| bc -l)" fi } satoshi() { # $1 satoshi in usd local price price="$(curl -s https://api.coinbase.com/v2/prices/BTC-USD/spot | jq -r .data.amount)" price=$(echo "scale=10; $price * 0.00000001"| bc -l) printf "$%f\n" "$price" if [[ $1 ]]; then printf "$%.2f\n" "$(echo "scale=10; $price * $1"| bc -l)" fi } #### end bitcoin related things cbfstool () { /a/opt/coreboot/build/cbfstool "$@"; } cgpl() { if (($#)); then cp /a/bin/data/COPYING "$@" else cp /a/bin/data/COPYING . fi } capache() { if (($#)); then cp /a/bin/data/LICENSE "$@" else cp /a/bin/data/LICENSE . fi } chrome() { if type -p chromium &>/dev/null; then cmd=chromium else cd / cmd="schroot -c bullseye chromium" CHROMIUM_FLAGS='--enable-remote-extensions' $cmd & r fi } # do all tee. # pipe to this, or just type like a shell # todo: test this dat() { tee >(ssh frodo.b8.nz) >(ssh x2) >(ssh tp.b8.nz) >(ssh kw) >(ssh tp.b8.nz) } da() { # do all local host for host in x2 kw tp.b8.nz x3.b8.nz frodo.b8.nz; do ssh $host "$@" done } debian_pick_mirror () { # netselect-apt finds a fast mirror. # but we need to replace the mirrors ourselves, # because it doesnt do that. best it can do is # output a basic sources file # here we get the server it found, get the main server we use # then substitute all instances of one for the other in the sources file # and backup original to /etc/apt/sources.list-original. # this is idempotent. the only way to identify debian sources is to # note the original server, so we put it in a comment so we can # identify it later. local file file=$(mktemp -d)/f # safe way to get file name without creating one sudo netselect-apt -o "$file" || return 1 url=$(grep ^\\w $file | head -n1 | awk '{print $2}') sudo cp -f /etc/apt/sources.list /etc/apt/sources.list-original sudo sed -ri "/http.us.debian.org/ s@( *[^ #]+ +)[^ ]+([^#]+).*@\1$url\2# http.us.debian.org@" /etc/apt/sources.list sudo apt-get update } digme() { digdiff @ns{1,2}.iankelling.org "$@" } tsr() { # ts run "$@" |& ts || return $? } dup() { local ran_d ran_d=false system-status _ case $PS1 in *[\ \]]D\ *) pushd / /b/ds/distro-begin |& ts || return $? /b/ds/distro-end |& ts || return $? popd ran_d=true ;;& *[\ \]]DB\ *) pushd / /b/ds/distro-begin |& ts || return $? popd ran_d=true ;; *[\ \]]DE\ *) pushd / /b/ds/distro-end |& ts || return $? popd ran_d=true ;;& *CONFLINK*) if ! $ran_d; then conflink fi ;; esac system-status _ } envload() { # load environment from a previous: export > file local file=${1:-$HOME/.${USER}_env} eval "$(export | sed 's/^declare -x/export -n/')" while IFS= read -r line; do # declare -x makes variables local to a function eval ${line/#declare -x/export} done < "$file" } failfunc() { asdf a b c; } failfunc2() { failfunc d e f; } # one that comes with distros is too old for newer devices fastboot() { /a/opt/android-platform-tools/fastboot "$@"; } kdecd() { /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd; } bat() { cat /sys/class/power_supply/BAT0/capacity } # List of apps to install/update # Create from existing manually installed apps by doing # fdroidcl update # fdroidcl search -i, then manually removing # automatically installed/preinstalled apps # # # my attempt at recovering from boot loop: # # in that case, boot to recovery (volume up, home button, power, let go of power after samsun logo) # # then # mount /dev/block/mmcblk0p12 /data # cd /data # find -iname '*appname*' # rm -rf FOUND_DIRS # usually good enough to just rm -rf /data/app/APPNAME # # currently broken: # # causes replicant to crash # org.quantumbadger.redreader # org.kde.kdeconnect_tp # not broke, but wont work without gps #com.zoffcc.applications.zanavi # not broke, but not using atm #com.nutomic.syncthingandroid # # doesn\'t work on replicant #net.sourceforge.opencamera # fdroid_pkgs=( net.mullvad.mullvadvpn org.schabi.newpipe io.github.subhamtyagi.lastlauncher io.anuke.mindustry com.biglybt.android.client de.marmaro.krt.ffupdater me.ccrama.redditslide org.fedorahosted.freeotp at.bitfire.davdroid com.alaskalinuxuser.justnotes com.artifex.mupdf.viewer.app com.danielkim.soundrecorder com.fsck.k9 com.ichi2.anki com.jmstudios.redmoon com.jmstudios.chibe org.kde.kdeconnect_tp com.notecryptpro com.termux cz.martykan.forecastie de.danoeh.antennapod de.blinkt.openvpn de.marmaro.krt.ffupdater eu.siacs.conversations free.rm.skytube.oss im.vector.alpha # riot info.papdt.blackblub me.tripsit.tripmobile net.gaast.giggity net.minetest.minetest net.osmand.plus org.isoron.uhabits org.linphone org.gnu.icecat org.smssecure.smssecure org.yaaic sh.ftp.rocketninelabs.meditationassistant.opensource ) # https://forum.xda-developers.com/android/software-hacking/wip-selinux-capable-superuser-t3216394 # for maru, #me.phh.superuser fdup() { local -A installed updated local p # tried putting this in go buildscript cronjob, # but it failed with undefined: os.UserCacheDir. I expect its due to # an environment variable missing, but its easier just to stick it here. m go get -u mvdan.cc/fdroidcl || return 1 m fdroidcl update if fdroidcl search -u | grep ^org.fdroid.fdroid; then fdroidcl install org.fdroid.fdroid sleep 5 m fdroidcl update fi for p in $(fdroidcl search -i| grep -o "^\S\+"); do installed[$p]=true done for p in $(fdroidcl search -u| grep -o "^\S\+"); do updated[$p]=false done for p in ${fdroid_pkgs[@]}; do if ! ${installed[$p]:-false}; then m fdroidcl install $p # sleeps are just me being paranoid since replicant has a history of crashing when certain apps are installed sleep 5 fi done for p in ${!installed[@]}; do if ! ${updated[$p]:-true}; then m fdroidcl install $p sleep 5 fi done } firefox-default-profile() { local key value section key=Default value=1 section=$1 file=/p/c/subdir_files/.mozilla/firefox/profiles.ini sed -ri "/^ *$key/d" "$file" sed -ri "/ *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d};/ *\[$section\]/a $key=$value" "$file" } fdhome() { #firefox default home profile firefox-default-profile Profile0 } fdwork() { firefox-default-profile Profile4 } ff() { if type -P firefox &>/dev/null; then firefox "$@" else iceweasel "$@" fi } fn() { firefox -P alt "$@" >/dev/null 2>&1 } fsdiff () { local missing=false local dname="${PWD##*/}" local m="/a/tmp/$dname-missing" local d="/a/tmp/$dname-diff" [[ -e $d ]] && rm "$d" [[ -e $m ]] && rm "$m" local msize=0 local fsfile while read -r line; do fsfile="$1${line#.}" if [[ -e "$fsfile" ]]; then md5diff "$line" "$fsfile" && tee -a "/a/tmp/$dname-diff" <<< "$fsfile $line" else missing=true echo "$line" >> "$m" msize=$((msize + 1)) fi done < <(find . -type f ) if $missing; then echo "$m" (( msize <= 100 )) && cat $m fi } fsdiff-test() { local tmpd x # expected output, with different tmp dirs # /tmp/tmp.HDPbwMqdC9/c/d ./c/d # /a/tmp/tmp.qLDkYxBYPM-missing # ./b tmpd="$(mktemp -d)" cd "$tmpd" echo ok > a echo nok > b mkdir c echo ok > c/d local x x=$(mktemp -d) mkdir $x/c echo different > $x/c/d echo ok > $x/a fsdiff $x rm -r "$x" "$tmpd" } rename-test() { # test whether missing files were renamed, generally for use with fsdiff # $1 = fsdiff output file, $2 = directory to compare to. pwd = fsdiff dir # echos non-renamed files local x y found unset sums for x in "$2"/*; do { sums+=( "$(md5sum < "$x")" ) ; } 2>/dev/null done while read -r line; do { missing_sum=$(md5sum < "$line") ; } 2>/dev/null renamed=false for x in "${sums[@]}"; do if [[ $missing_sum == "$x" ]]; then renamed=true break fi done $renamed || echo "$line" done < "$1" return 0 } feh() { # F = fullscren, z = random, Z = auto zoom command feh --auto-rotate -FzZ "$@" } fw() { firefox -P default "$@" >/dev/null 2>&1 } gitian() { git config user.email ian@iankelling.org } # at least in flidas, things rely on gpg being gpg1 gpg() { if type -P gpg2 &>/dev/null; then command gpg2 "$@" else command gpg "$@" fi } gse() { local email=ian@iankelling.org git send-email --notes "--envelope-sender=<$email>" \ --suppress-cc=self "$@" } gup() { /a/f/gnulib/build-aux/gnupload "$@"; } dejagnu() { /a/opt/dejagnu/dejagnu "$@"; } hstatus() { # do git status on published repos. c /a/bin/githtml for x in *; do cd "$(readlink -f $x)"/.. status=$(i status -s) || pwd if [[ $status ]]; then hr echo $x printf "%s\n" "$status" fi cd /a/bin/githtml done } # work log wlog() { local day i days_back days_back=${1:-16} for (( i=0; i= 2 )); then echo "## $(( i / 6 + 1 )) ##" fi if (( sec > 10 )); then printf "%3d %s\n" $sec "$l" else printf " %s\n" "$l" fi i=$(( i + 1 )) done } arbttlog() { # from the log, show only the currently active window, and the number of # seconds of input inactivity. 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}' ; } idea() { /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r } ilogs-local() { d=/var/lib/znc/moddata/log/iank/ for n in freenode libera; do cd $d/$n 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 done } ilogs() { sl root@iankelling.org ilogs-local "$@" } ilog-local() { local d chan chan="$1" d=/var/lib/znc/moddata/log/iank/ for n in freenode libera; do if [[ ! -d $d$n/"$chan" ]]; then continue fi cd $d$n/"$chan" hr for x in *; do echo $x; sed "s/^./${x%log}/" $x; hr; done done } ilog() { local chan chan="${1:-#fsfsys}" # use * instead of -r since that does sorted order sl root@iankelling.org ilog-local "$chan" | less +G } o() { if type gio &> /dev/null ; then gio open "$@" elif type gvfs-open &> /dev/null ; then gvfs-open "$@" else xdg-open "$@" fi # another alternative is run-mailcap } ccomp xdg-open o # jfilter() { # grep -Evi -e "^(\S+\s+){4}(sudo|sshd|cron)\[\S*:" \ # -e "^(\S+\s+){4}systemd\[\S*: (starting|started) (btrfsmaintstop|dynamicipupdate|spamd dns bug fix cronjob|rss2email)\.*$" # } # jtail() { # journalctl -n 10000 -f "$@" | jfilter # } # jr() { journalctl "$@" | jfilter | less ; } # jrf() { journalctl -n 200 -f "$@" | jfilter; } jr() { journalctl "$@" ; } jrf() { journalctl -n 200 -f "$@" ; } ccomp journalctl jtail jr jrf ## old version for model01. i need to get that firmware working again. # kff() { # keyboardio firmware flash. you must hold down the tilde key # pushd /a/opt/Model01-Firmware # # if we didn't want this yes hack, then remove "shell read" from # # /a/opt/Kaleidoscope/etc/makefiles/sketch.mk # yes $'\n' | VERBOSE=1 make flash # popd # } kff() { pushd /a/opt/Kaleidoscope/examples/Devices/Keyboardio/Model100 make flash popd } wgkey() { local umask_orig name if (( $# != 1 )); then e expected 1 arg >&2 return 1 fi name=$1 umask_orig=$(umask) umask 0077 wg genkey | tee $name-priv.key | wg pubkey > $name-pub.key umask $umask_orig } declare -A vpn_ips vpn_ips[kd]=2 # note: 1, 4, 5 are occupied by mail wireguard vpn_ips[x3]=8 vpn_ips[sy]=12 vpn_ips[x2]=13 vpn_ips[kw]=27 vpn_ips[bo]=28 vpn_ips[frodo]=34 vpn-ips-update() { local host ipsuf f files for host in ${!vpn_ips[@]}; do ipsuf=${vpn_ips[$host]} wghole $host $ipsuf u /a/bin/ds/machine_specific/$host/filesystem/etc/systemd/system/openvpn-client-tr@.service < 3 )); then e expected 2-3 arg of hostname, ip suffix, and extrahost >&2 return 1 fi local host ipsuf umask_orig vpn_allowed host=$1 ipsuf=$2 if [[ $3 ]]; then extrahost=,$3 fi for vpn_host in ${!vpn_ips[@]}; do if [[ $vpn_host == "$host" ]]; then continue fi vpn_allowed+=",10.174.${vpn_ips[$vpn_host]}.2/32" done mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard ( cd /p/c/machine_specific/$host/filesystem/etc/wireguard umask_orig=$(umask) umask 0077 if [[ ! -s hole-priv.key || ! -s hole-pub.key ]]; then wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key fi cat >wghole.conf </dev/null; then m sudo mount --bind /root/mount_namespaces /root/mount_namespaces fi m sudo mount --make-private /root/mount_namespaces if [[ ! -e /root/mount_namespaces/$ns ]]; then m sudo touch /root/mount_namespaces/$ns fi if ! sudo mountpoint /root/mount_namespaces/$ns >/dev/null; then m sudo unshare --propagation slave --mount=/root/mount_namespaces/$ns /bin/true fi m sudo -E /usr/bin/nsenter --mount=/root/mount_namespaces/$ns "$@" } mnsr() { # mns run local ns=$1 shift mns $ns sudo -u iank -E env "PATH=$PATH" "$@" } mnsnonet() { ns=$1 lomh if ! s ip netns list | grep -Fx nonet &>/dev/null; then s ip netns add nonet fi mns $ns --net=/var/run/netns/nonet sudo -E -u iank /bin/bash lomh } lom() { # l = the loopback device local l base # get sudo pass cached right away if ! sudo -nv 2>/dev/null; then sudo -v fi if [[ $1 == /* ]]; then base=${1##*/} fs_file=$1 if mns $base mountpoint -q /mnt/$base; then return 0 fi l=$(losetup -j $fs_file | sed -rn 's/^([^ ]+): .*/\1/p' | head -n1 ||:) if [[ ! $l ]]; then l=$(sudo losetup -f) m sudo losetup $l $fs_file fi if ! sudo cryptsetup status /dev/mapper/$base &>/dev/null; then if ! m sudo cryptsetup luksOpen $l $base; then m sudo losetup -d $l return 1 fi fi m sudo mkdir -p /mnt/$base m mns $base mount /dev/mapper/$base /mnt/$base m mns $base chown $USER:$USER /mnt/$base lomh else base=$1 if mns $base mountpoint /mnt/$base &>/dev/null; then m mns $base umount /mnt/$base fi if sudo cryptsetup status /dev/mapper/$base &>/dev/null; then if ! m sudo cryptsetup luksClose /dev/mapper/$base; then echo lom: failed cryptsetup luksClose /dev/mapper/$base return 1 fi fi l=$(losetup -l --noheadings | awk '$6 ~ /\/'$base'$/ {print $1}') if [[ $l ]]; then m sudo losetup -d $l else echo lom: warning: no loopback device found fi fi } # mu personality. for original, just run mp. for 2, run mp 2. # this is partly duplicated in mail-setup mp() { local dead=false for s in {1..5}; do if ! killall mu; then dead=true break fi sleep 1 done if ! $dead; then echo error: mu not dead m psg mu return 1 fi suf=$1 set -- /m/mucache ~/.cache/mu /m/.mu ~/.config/mu while (($#)); do target=$1$suf f=$2 shift 2 if [[ -e $f && ! -L $f ]]; then m rm -rf $f fi m ln -sf -T $target $f done } # maildir enable mdenable() { local md dst ln_path src two two=false case $1 in -2) two=true shift ;; esac for md; do src= if $two; then dst=/m/4e2/$md else dst=/m/4e/$md fi ln_path=/m/md/$md for d in /m/md/$md /m/4e2/$md; do if [[ -d $d && ! -L $d ]]; then src=$d break fi done if [[ ! $src ]]; then echo "error: could not find $md" >&2 return 1 fi m mv -T $src $dst m ln -sf -T $dst $ln_path done } md2enable() { mdenable -2 "$@" } mddisable() { local md=$1 dst=/m/md/$md ### begin copied from mdenable, but different d ### for d in /m/4e/$md /m/4e2/$md; do if [[ -d $d && ! -L $d ]]; then src=$d break fi done if [[ ! $src ]]; then echo "error: could not find $md" >&2 return 1 fi ### end copy from mdenable ### if [[ -L $dst ]]; then m rm $dst; fi m mv -T $src $dst } mdt() { markdown "$1" >/tmp/mdtest.html firefox /tmp/mdtest.html } mo() { xset dpms force off; } # monitor off mpvgpu() { # seems to be the best gpu decoding on my nvidia 670. # vlc gets similar or better framerate, but is much darker output on my test movie at least. case $HOSTNAME in kd) echo 0f | sudo tee -a /sys/kernel/debug/dri/0/pstate ;; esac # going back to the default slow clock, and slower fan: # echo 07 | sudo tee -a /sys/kernel/debug/dri/0/pstate if [[ $DISPLAY ]]; then mpv --vo=vdpau --hwdec=auto "$@" else # waylandvk seems to work the same mpv --gpu-context=wayland --hwdec=auto fi } mpvd() { mpv --profile=d "$@"; } # mpv all media files in . or $1 mpvm() { local -a extensions arg # get page source of https://en.wikipedia.org/w/index.php?title=Video_file_format&action=edit # into /a/x.log, then # grep '^| *\.' /a/x.log | sed 's/| *//;s/,//g' # note: to join them together for a regex, do: # old=; for e in ${extensions[@]/./}; do if [[ ! $old ]]; then old=$e; continue; fi; echo -n "$old|"; old=$e; done; echo $e extensions=( .webm .mkv .flv .flv .vob .ogv .ogg .drc .gif .gifv .mng .avi .MTS .M2TS .TS .mov .qt .wmv .yuv .rm .rmvb .viv .asf .amv .mp4 .m4p .m4v .mpg .mp2 .mpeg .mpe .mpv .mpg .mpeg .m2v .m4v .svi .3gp .3g2 .mxf .roq .nsv ) arg=("(" -iname "*${extensions[0]}") for (( i=1 ; i < ${#extensions[@]}; i++ )); do arg+=(-o -iname "*${extensions[i]}") done arg+=(")") dir=${1:-.} # debug: #find $dir "${arg[@]}" -size +200k find $dir "${arg[@]}" -size +200k -exec mpv --profile=d '{}' + } mpvs() { mpv --profile=s "$@"; } myirc() { if [[ ! $1 ]]; then set -- fsfsys fi local -a d d=( /var/lib/znc/moddata/log/iank/{freenode,libera} ) # use * instead of -r since that does sorted order ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\= 2 )); then echo "error: iank: unexpected multiple files" return 1 fi fname="${fnames[0]}" src=/a/opt/roms/$fname if [[ ! -f $src ]]; then echo debvm: not found $src, download from eg: https://cloud.debian.org/images/cloud/buster/latest/ return 1 fi cp -a $src /t # note, in fai-revm we do this: not sure why, maybe because of br device # --graphics spice,listen=0.0.0.0 m s virt-install --osinfo debian11 --rng /dev/urandom -n deb${ver}tmp --import -r $ram --vcpus 2 --disk /t/$fname --graphics spice # note: to ssh into this machine will require host key generation: ssh-keygen -A # random: for cvs2git on gnu www, use debian 10. I could use trisquel, # but happen to want to try out the debian cloud images. the upstream # requires python2 and hasn't really changed since the version in d10. # # apt install cvs2git cvs # # 7G was not enough # mount -o mode=1777,nosuid,nodev,size=34G -t tmpfs tmpfs /tmp # cvs2git --encoding utf_8 --fallback-encoding ascii --dumpfile=dump www-rsync/www |& tee /tmp/l ## www-rsync is an rsynced copy of the cvsfrom savannah } mygajim() { local time time_sec time_pretty days days=${1:-16} 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 case $time in 16*) : ;; *) continue ;; esac if ! time_pretty=$(date +%F.%R -d @$time); then echo bad time: $time return 1 fi echo $time_pretty "$l" time_sec=${time%%.*} # only look at the last 18 days. generally just use this for timesheet. if (( time_sec < EPOCHSECONDS - 60 * 60 * 24 * days )); then break; fi done } allmygajim() { sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs where contact_name = 'iank'" | less } gajlogs() { sqlite3 -separator ' ' /p/c/subdir_files/.local/share/gajim/logs.db "select time, message from logs" | less } net-dev-info() { e "lspci -nnk|gr -iA2 net" lspci -nnk|gr -iA2 net hr e "s lshw -C network" hr sudo lshw -C network } nk() { ser stop NetworkManager ser disable NetworkManager ser stop NetworkManager-wait-online.service ser disable NetworkManager-wait-online.service ser stop dnsmasq sudo resolvconf -d NetworkManager # ser start dnsmasq sudo ifup br0 } ngo() { sudo ifdown br0 ser start NetworkManager sleep 4 sudo nmtui-connect } otp() { oathtool --totp -b "$*" | xclip -selection clipboard } j() { "$@" |& pee "xclip -r -selection clipboard" cat } # x copy xc() { xclip -r -selection clipboard } # echo copy ec() { pee "xclip -r -selection clipboard" cat } pakaraoke() { # from http://askubuntu.com/questions/456021/remove-vocals-from-mp3-and-get-only-instrumentals 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 } pfind() { #find *$1* in $PATH [[ $# != 1 ]] && { echo requires 1 argument; return 1; } local pathArray IFS=: pathArray=($PATH); unset IFS find "${pathArray[@]}" -iname "*$1*" } pick-trash() { # trash-restore lists everything that has been trashed at or below CWD # This picks out files just in CWD, not subdirectories, # which also match grep $1, usually use $1 for a time string # which you get from running restore-trash once first local name x ask local nth=1 # last condition is to not ask again for ones we skipped while name="$( echo | restore-trash | gr "$PWD/[^/]\+$" | gr "$1" )" \ && [[ $name ]] && (( $(wc -l <<<"$name") >= nth )); do name="$(echo "$name" | head -n $nth | tail -n 1 )" read -r -p "$name [Y/n] " ask if [[ ! $ask || $ask == [Yy] ]]; then x=$( echo "$name" | gr -o "^\s*[0-9]*" ) echo $x | restore-trash > /dev/null elif [[ $ask == [Nn] ]]; then nth=$((nth+1)) else return fi done } pub() { rld /a/h/_site/ li:/var/www/iankelling.org/html } pumpa() { # fixes the menu bar in xmonad. this won\'t be needed when xmonad # packages catches up on some changes in future (this is written in # 4/2017) # # geekosaur: so youll want to upgrade to xmonad 0.13 or else use a # locally modified XMonad.Hooks.ManageDocks that doesnt set the # work area; turns out it\'s impossible to set correctly if you are # not a fully EWMH compliant desktop environment # # geekosaur: chrome shows one failure mode, qt/kde another, other # gtk apps a third, ... I came up with a setting that works for me # locally but apparently doesnt work for others, so we joined the # other tiling window managers in giving up on setting it at all # xprop -root -remove _NET_WORKAREA command pumpa & r } # reviewboard, used at my old job #rbpipe() { rbt post -o --diff-filename=- "$@"; } #rbp() { rbt post -o "$@"; } rebr() { sudo ifdown br0 sudo ifup br0 } r2e() { command r2e -d /p/c/rss2email.json -c /p/c/rss2email.cfg "$@"; } # only run on MAIL_HOST. simpler to keep this on one system. r2eadd() { # usage: name url # initial setup of rss2email: # r2e new r2e@iankelling.org # that initializes files, and sets default email. # symlink to the config doesnt work, so I copied it to /p/c # and then use cli option to specify explicit path. # Only option changed from default config is to set # force-from = True # # or else for a few feeds, the from address is set by the feed, and # if I fail delivery, then I send a bounce message to that from # address, which makes me be a spammer. r2e add $1 "$2" $1@r2e.iankelling.org # get up to date and dont send old entries now: r2e run --no-send $1 } rspicy() { # usage: HOST DOMAIN # connect to spice vm remote host. use vspicy for local host local port # shellcheck disable=SC2087 port=$(ssh $1<$tmp/timing # todo, the current sleep seems pretty good, but it # would be nice to have an empirical measurement, or # some better wait to sync up. # # note: --loop-file=no prevents it from hanging if you have that # set to inf the mpv config. # --loop=no prevents it from exit code 3 due to stdin if you # had it set to inf in mpv config. # # args go to mpv, for example --volume=80, 50% cat >$out <&2 return 1 fi spamdpid=$(systemctl status spamassassin| sed -n '/^ *Main PID:/s/[^0-9]//gp') spamcpre="nsenter -t $spamdpid -n -m" s $spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$1" } # mail related testmail() { declare -gi _seq; _seq+=1 echo "test body" | m mail -s "test mail from $HOSTNAME, $_seq" "${@:-root@localhost}" # for testing to send from an external address, you can do for example # -fian@iank.bid -aFrom:ian@iank.bid web-6fnbs@mail-tester.com # note in exim, you can retry a deferred message # s exim -M MSG_ID # MSG_ID is in /var/log/exim4/mainlog, looks like 1ccdnD-0001nh-EN } # to test sieve, use below command. for fsf mail, see offlineimap-sync script # make modifications, then copy to live file, use -eW to actually modify mailbox # # Another option is to use sieve-test SCRIPT MAIL_FILE. note, # sieve-test doesnt know about envelopes, Im not sure if sieve-filter does. # sieve with output filter. arg is mailbox, like INBOX. # This depends on dovecot conf, notably mail_location in /etc/dovecot/conf.d/10-mail.conf # always run this first, edit the test files, then run the following testsieve() { 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 } runsieve() { c ~/sieve; cp personal{test,}.sieve; cp lists{test,}.sieve; cp personalend{test,}.sieve sieve-filter -eWv ~/sieve/maintest.sieve ${1:-INBOX} delete &> /tmp/testsieve.log 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 } # usage: # alertme SUBJECT # printf "subject\nbody\n" | alertme alertme() { if [[ -t 0 ]]; then exim -t <' $to < To: $to Subject: Mail delivery failed: returning message to sender This message was created automatically by mail delivery software. EOF } # toggle keyboard tk() { # based on # https://askubuntu.com/questions/160945/is-there-a-way-to-disable-a-laptops-internal-keyboard id=$(xinput --list --id-only 'AT Translated Set 2 keyboard') if xinput list | grep -F '∼ AT Translated Set 2 keyboard' &>/dev/null; then echo enabling keyboard # find the first slave keyboard number, they are all the same in my output. # if they werent, worst case we would need to save the slave number somewhere # when it got disabled. slave=$(xinput list | sed -n 's/.*slave \+keyboard (\([0-9]*\)).*/\1/p' | head -n1) xinput reattach $id $slave else xinput float $id fi } tm() { # timer in minutes # --no-config (sleep "$(calc "$* * 60")" && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 & } ## usage: to connect to my main transmission daemon from a different host, run this trans-remote-route() { : } trg() { transmission-remote-gtk & r; } # TODO: this wont work transmission.lan doesnt exist trc() { # example, set global upload limit to 100 kilobytes: # trc -u 100 TR_AUTH=":$(jq -r .profiles[0].password ~/.config/transmission-remote-gtk/config.json)" transmission-remote transmission.lan -ne "$@" } trysleep() { retries="$1" sleepsecs="$2" shift 2 for (( i=0; i < retries - 1; i++ )); do if "$@"; then return 0 fi sleep $sleepsecs done "$@" } tu() { local s if [[ -e $1 && ! -w $1 || ! -w $(dirname "$1") ]]; then s=s; fi # full path for using in some initial setup steps $s /a/exe/teeu "$@" } enn() { local ecmd pid ecmd="/usr/sbin/exim4 -C /etc/exim4/my.conf" if ip a show veth1-mail &>/dev/null; then s $ecmd "$@" return fi pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1) m s nsenter -t $pid -n -m $ecmd "$@" } # get pid of systemd service servicepid() { local pid unit dir unit="$1" pid=$(systemctl show --property MainPID --value "$unit") case $pid in [1-9]*) : ;; *) dir=/sys/fs/cgroup/system.slice if [[ ! -d $dir ]]; then # t10 and older directory. dir=/sys/fs/cgroup/systemd/system.slice fi # 0 or empty. This file includes the MainPid, so I expect we # could just get this in the first place, but i don't know if that # is always the case. pid=$(head -n1 $dir/${unit%.service}.service/cgroup.procs) ;; esac if [[ $pid ]]; then printf "%s\n" "$pid" else return 1 fi } sdnbash() { # systemd namespace bash local unit pid if (( $# != 1 )); then echo $0: error wrong number of args >&2 return 1 fi unit=$1 pid=$(servicepid $unit) m sudo nsenter -t $pid -n -m sudo -u $USER -i bash } sdnbashroot() { # systemd namespace bash local unit pid if (( $# != 1 )); then echo $0: error wrong number of args >&2 return 1 fi unit=$1 pid=$(servicepid $unit) m sudo nsenter -t $pid -n -m bash } sdncmd() { # systemd namespace cmd local unit pid if (( $# <= 2 )); then echo $0: error wrong number of args >&2 return 1 fi unit=$1 shift pid=$(servicepid $unit) m sudo nsenter -t $pid -n -m sudo -u $USER -i "$@" } mailnnbash() { sdnbash mailnn } # we use wireguard now, use mailnnbash. # mailvpnbash() { # m sudo nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*mail.conf") -n -m sudo -u $USER -i bash # } eximbash() { local pid pid=$(pgrep -f "/usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf"|h1) if [[ ! $pid ]]; then echo "eximbash: failed to find exim pid. systemctl -n 30 status exim4:" systemctl status exim4 fi m sudo nsenter -t $pid -n -m } spamnn() { local spamdpid spamdpid=$(systemctl show --property MainPID --value spamassassin) m sudo nsenter -t $spamdpid -n -m sudo -u Debian-exim spamassassin "$@" } unboundbash() { m sudo nsenter -t "$(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp')" -n -m sudo -u $USER -i bash } nmtc() { s nmtui-connect "$@" } mailnncheck() { local unit pid ns mailnn # mailvpn would belong on the list if using openvpn for unit in mailnn unbound dovecot spamassassin exim4 radicale; do pid=$(servicepid $unit) echo debug: unit=$unit pid=$pid if [[ ! $pid ]]; then echo failed to find pid for unit=$unit continue fi if ! ns=$(s readlink /proc/$pid/ns/net); then echo failed to find ns for unit=$unit pid=$pid continue fi if [[ $mailnn ]]; then if [[ $ns != "$mailnn" ]]; then echo "$unit ns $ns != $mailnn" fi else mailnn=$ns fi done } vpncmd() { m sudo -E env "PATH=$PATH" nsenter -t "$(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf")" -n "$@" } vpni() { vpncmd sudo -u iank env "PATH=$PATH" "$@" } vpnbash() { vpncmd bash } vpn() { if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then local vpn_service=openvpn-client else local vpn_service=openvpn fi [[ $1 ]] || { echo need arg; return 1; } journalctl --unit=$vpn_service@$1 -f -n0 & # sometimes the journal doesnt open until after the vpn output # has happened. hoping this fixes that. sleep 1 sudo systemctl start $vpn_service@$1 # sometimes the ask-password agent does not work and needs a delay. sleep .5 # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779240 # noticed around 8-2017 after update from around stretch release # on debian testing, even though the bug is much older. sudo systemd-tty-ask-password-agent } fixu() { local stats ls -lad /run/user/1000 stats=$(stat -c%a-%g-%u /run/user/1000) if [[ $stats != 700-1000-1000 ]]; then m s chmod 700 /run/user/1000; m s chown iank.iank /run/user/1000 fi } # unmute um() { pactl set-sink-mute @DEFAULT_SINK@ false rm -f /tmp/ianknap } nap() { pactl set-sink-mute @DEFAULT_SINK@ true touch /tmp/ianknap } # systemctl is-enabled / status / cat says nothing, instead theres # some obscure symlink. paths copied from man systemd.unit. # possibly also usefull, but incomplete, doesnt show units not loaded in memory: # seru list-dependencies --reverse --all UNIT sysd-deps() { local f local -a dirs search ngset case $1 in u) search=( ~/.config/systemd/user.control/* $XDG_RUNTIME_DIR/systemd/user.control/* $XDG_RUNTIME_DIR/systemd/transient/* $XDG_RUNTIME_DIR/systemd/generator.early/* ~/.config/systemd/user/* /etc/systemd/user/* $XDG_RUNTIME_DIR/systemd/user/* /run/systemd/user/* $XDG_RUNTIME_DIR/systemd/generator/* ~/.local/share/systemd/user/* /usr/lib/systemd/user/* $XDG_RUNTIME_DIR/systemd/generator.late/* ) ;; *) search=( /etc/systemd/system.control/* /run/systemd/system.control/* /run/systemd/transient/* /run/systemd/generator.early/* /etc/systemd/system/* /etc/systemd/systemd.attached/* /run/systemd/system/* /run/systemd/systemd.attached/* /run/systemd/generator/* /lib/systemd/system/* /run/systemd/generator.late/* ) ;; esac for f in "${search[@]}"; do [[ -d $f ]] || continue case $f in *.requires|*.wants) dirs+=("$f") ;; esac done # dirs is just so we write out the directory names, ls does it when there is 2 or more dirs. case ${#dirs[@]} in 1) echo "${dirs[0]}:" ll "${dirs[@]}" ;; 0) : ;; *) ll "${dirs[@]}" ;; esac ngreset } fixvpndns() { local link istls read -r _ link _ istls < <(resolvectl dnsovertls tunfsf) case $istls in yes|no) : ;; *) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;; esac s busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager SetLinkDNSOverTLS is $link no } vpnoff() { [[ $1 ]] || { echo need arg; return 1; } if [[ -e /lib/systemd/system/openvpn-client@.service ]]; then local vpn_service=openvpn-client else local vpn_service=openvpn fi sudo systemctl stop $vpn_service@$1 } vpnoffc() { # vpn off client ser stop openvpn-client-tr@client } vpnc() { ser start openvpn-client-tr@client } vspicy() { # usage: VIRSH_DOMAIN # connect to vms made with virt-install spicy -p "$(sudo virsh dumpxml "$1"|grep "/dev/null | grep -iP --color=auto "$*" ||: rgv -m 5 "$*" $paths /a/t.org /p/w.org /a/work.org ||: } # setup: # pip3 install linode-cli # linode-cli livp9() { local input ip id tmp input=$1 if [[ $2 ]]; then id=$2 ip=$3 else tmp=$(mktemp) echo $tmp linode-cli --json --pretty linodes create --root_pass loxHuceygomGisun | tee $tmp read -r ip id <<<"$(tail -n+2 $tmp | jq -r '.[0].ipv4[0] , .[0].id')" for string in $ip $id; do case $string in [0-9]*) : ;; *) echo "livp9: bad value ip=$ip id=$id input=$input" return 1 ;; esac done rm $tmp while true; do if timeout 4 ssh $ip :; then break fi sleep 3 done fi ssh $ip <\0,' } reset-xscreensaver() { # except for spash, i set these by setting gui options in # xscreensaver-command -demo # then finding the corresponding option in .xscreensaver # spash, i happened to notice in .xscreensaver # # dpmsOff, monitor doesnt come back on using old free software supported nvidia card cat > /home/iank/.xscreensaver <<'EOF' mode: blank dpmsEnabled: True dpmsStandby: 0:07:00 dpmsSuspend: 0:08:00 dpmsOff: 0:00:00 timeout: 0:05:00 lock: True lockTimeout: 0:06:00 splash: False EOF } # very useful, copy directory structure 3 deep. add remove /*/ to change level # rsync -aivh --exclude '/*/*/*/' -f"+ */" -f"- *" SRC DEST # * stuff that makes sense to be at the end if [[ "$SUDOD" ]]; then # allow failure, for example if we are sudoing into a user with diffferent/lesser permissions. cd "$SUDOD" ||: unset SUDOD elif [[ -d /a ]] && [[ $PWD == "$HOME" ]] && [[ $- == *i* ]]; then cd /a OLDPWD= fi # for mitmproxy to get a newer python. # commented until i want to use it because it # noticably slows bash startup # mypyenvinit () { if [[ $EUID == 0 || ! -e ~/.pyenv/bin ]]; then echo "error: dont be root. make sure pyenv is installed" return 1 fi export PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)" } export GOPATH=$HOME/go path-add $GOPATH/bin path-add /usr/local/go/bin # I have the git repo and a release. either one should work. # I have both because I was trying to solve an issue that # turned out to be unrelated. # ARDUINO_PATH=/a/opt/Arduino/build/linux/work ## i should have documented this... # based on https://github.com/keyboardio/Kaleidoscope export KALEIDOSCOPE_DIR=/a/opt/Kaleidoscope # They want to be added to the start, but i think # that should be avoided unless we really need it. path-add --end ~/.npm-global path-add --end $HOME/.cargo/bin if type -P rg &>/dev/null; then # --no-messages because of annoying errors on broken symlinks # -z = search .gz etc files # -. = search dotfiles 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 $?; } #fails if not exist. ignore complete -r rg 2>/dev/null ||: else alias rg=grr fi # rg with respecting vcs ignore files rgv() { ret=0 # -. = search dotfiles # -z = search zipped files # -i = case insensitive # -M = max columns # --no-messages because of annoying errors on broken symlinks command rg -. -z --no-messages -i -M 900 -g '!.git' -g '!auto-save-list' -g '!.savehist' "$@" || ret=$? return $ret } amall() { printf "$(tput setaf 5 2>/dev/null ||:)█ coresite █$(tput sgr0 2>/dev/null||:)" amfsf "$@" printf "$(tput setaf 5 2>/dev/null ||:)█ office █$(tput sgr0 2>/dev/null||:)" amoffice "$@" } amallq() { # amall quiet amfsf "$@" amoffice "$@" } amfsf() { sedi -r '/alertmanager.url/s/@office//' ~/.config/amtool/config.yml amtool "$@" } amoffice() { sedi -r '/alertmanager.url/s/@fsf/@office.fsf/' ~/.config/amtool/config.yml amtool "$@" } amls() { amall silence query "$@" } amrmall() { amfsf silence expire $(amfsf silence query -q) amoffice silence expire $(amoffice silence query -q) } youtube-dl-update() { sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl sudo chmod a+rx /usr/local/bin/youtube-dl } # https://github.com/yt-dlp/yt-dlp/wiki/Installation yt-dlp-update() { sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp sudo chmod a+rx /usr/local/bin/yt-dlp # Make executable } mpvyt() { mpv --ytdl ytdl_path=/usr/local/bin/yt-dlp "$@" } # taken from default changes to bashrc and bash_profile path-add --end --ifexists $HOME/.rvm/bin # also had ruby bin dir, but moved that to environment.sh # so its included in overall env export BASEFILE_DIR=/a/bin/fai-basefiles #export ANDROID_HOME=/a/opt/android-home # https://f-droid.org/en/docs/Installing_the_Server_and_Repo_Tools/ #export USE_SDK_WRAPPER=yes #PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools # didnt get drush working, if I did, this seems like the # only good thing to include for it. # Include Drush completion. # if [ -f "/home/ian/.drush/drush.complete.sh" ] ; then # source /home/ian/.drush/drush.complete.sh # fi # best practice unset IFS # https://wiki.archlinux.org/index.php/Xinitrc#Autostart_X_at_login # i added an extra condition as gentoo xorg guide says depending on # $DISPLAY is fragile. if [[ ! $DISPLAY && $XDG_VTNR == 1 ]] && shopt -q login_shell && isarch; then exec startx fi # ensure no bad programs appending to this file will have an affect return 0