--- /dev/null
+# for rg
+/.emacs.d
--- /dev/null
+#!/bin/bash
+
+nav_tags=(
+ ## cross-genre tags that dont really make a playlist
+ expl
+ # songs i like but they get old fast due to feeling gimicky, or cringy after a while.
+ gimicky
+ # anything sad which i sometimes like or avoid.
+ sad
+
+ ## playlists
+ # intimate, love
+ love
+ # favorite songs pump up songs
+ pump1
+ # favorite rap pump up songs, allows more songs than pump1
+ pumprap
+ # heart rending, spine tickling
+ rend
+ # for running
+ run
+)
+
+pl_tags=(
+ "${nav_tags[@]}"
+ # alternate version of a song we already have which isn't as good
+ lesser_version
+)
+
+nav_convert_query="^genre:spoken-w ^genre:skit ^lesser_version:t rating:3..5"
+
+
+common_genres=(
+ ambient
+ # gangsta rap / angry rap. something like g-rap would make beet queries for genre:rap include it
+ arp
+ avant
+ blues
+ # slow instrumental. todo: reclassify some ambient into this.
+ chill
+ classical
+ country
+ # lyrical edm. todo: some pop needs reclassification to this
+ dance
+ # like power glove
+ darkwave
+ hardcore
+ instrumental
+ latin
+ metal
+ # mq = mac quale. similar to the mr robot soundtracks.
+ # slow, foreboding. usually electronic.
+ mq
+ pop
+ rap
+ rock
+ # like rain by brian crain. mostly slow airy/broody piano
+ sleep
+ techno
+ world
+)
+
+# because we were destined to run out of single key buttons.
+rare_genres=(
+ jazz
+ musical
+ noise
+ skit
+ spoken-w
+)
+
+all_genres=(${common_genres[@]} ${rare_genres[@]})
+
+
+
+#### playlist things #####
+
+
+declare -A ignore_genres_a
+ignore_genres=(
+ skit
+ spoken-w
+)
+
+declare -A slow_genres_a
+slow_genres=(
+ ambient
+ avant
+ classical
+ noise
+ sleep
+ mq
+ jazz
+)
+
+
+tags=(
+ expl
+ sad
+)
+
+
+for g in ${ignore_genres[@]}; do
+ ignore_genres_a[$g]=t
+done
+for g in ${slow_genres[@]}; do
+ slow_genres_a[$g]=t
+done
+
+# genres that have a beat
+beat_genres=()
+genres=()
+
+
+# relatively upbeat genres to listen, eg while biking
+upbeat_genres=()
+for g in ${all_genres[@]}; do
+ if [[ ${ignore_genres_a[$g]} ]]; then continue; fi
+ genres+=($g)
+ if [[ ${slow_genres_a[$g]} ]]; then continue; fi
+ beat_genres+=($g)
+ case $g in
+ chill)
+ continue
+ ;;
+ esac
+ upbeat_genres+=($g)
+done
+
+# generate regex for beat playlist
+beat_regex=
+first=true
+for g in ${beat_genres[@]}; do
+ if $first; then
+ first=false
+ beat_regex=$g
+ else
+ beat_regex+="|$g"
+ fi
+done
+
+# generate regex for upbeat playlist
+upbeat_regex=
+first=true
+for g in ${upbeat_genres[@]}; do
+ if $first; then
+ first=false
+ upbeat_regex=$g
+ else
+ upbeat_regex+="|$g"
+ fi
+done
+
+declare -A bpla # beet playlist associative array
+beetapl() { # beet add playlist
+ local name
+ name="$1"
+ shift
+ bpla[$name]="${@@Q}"
+}
+
+for g in ${genres[@]}; do
+ for r in {3..5}; do
+ case $g in
+ pop|rap)
+ beetapl ${g}-${r} rating:${r}..5 genre::^$g\$ ^expl:t ^gimicky:t ^lesser_version:t
+ beetapl ${g}-x-${r} rating:${r}..5 genre::^$g\$ ^gimicky:t ^lesser_version:t
+ ;;
+ *)
+ beetapl ${g}-${r} rating:${r}..5 genre:$g ^gimicky:t ^lesser_version:t
+ ;;
+ esac
+ done
+done
+
+for t in ${tags[@]}; do
+ for r in {3..5}; do
+ beetapl ${t}-${r} rating:${r}..5 $t:t ^lesser_version:t
+ done
+done
+
+for r in {3..5}; do
+ beetapl beat-${r} rating:${r}..5 genre::$beat_regex ^expl:t ^gimicky:t ^lesser_version:t
+ beetapl beat-x-${r} rating:${r}..5 genre::$beat_regex ^gimicky:t ^lesser_version:t
+ beetapl upbeat-${r} rating:${r}..5 genre::$upbeat_regex ^expl:t ^gimicky:t ^lesser_version:t ^sad:t
+ beetapl upbeat-x-${r} rating:${r}..5 genre::$upbeat_regex ^gimicky:t ^lesser_version:t ^sad:t
+ beetapl gimicky-${r} rating:${r}..5 gimicky:t ^lesser_version:t
+done
+
+for r in {3..5}; do
+ beetapl \
+ sy$r rating:${r}..5 genre::$upbeat_regex ^gimicky:t ^lesser_version:t 'artist:sonic youth'
+done
+++ /dev/null
-#!/bin/bash
-
-# for generating playlist config yaml.
-# plain playlists are added manually to the yaml.
-
-f=/usr/local/lib/err;test -r $f || { echo "error: $0 no $f" >&2;exit 1;}; . $f
-
-declare -A ignore_genres_a
-ignore_genres=(
- skit
- spoken-w
-)
-
-declare -A slow_genres_a
-slow_genres=(
- ambient
- avant
- classical
- noise
- sleep
- mq
- jazz
-)
-
-
-tags=(
- expl
- sad
-)
-
-
-for g in ${ignore_genres[@]}; do
- ignore_genres_a[$g]=t
-done
-for g in ${slow_genres[@]}; do
- slow_genres_a[$g]=t
-done
-
-# genres that have a beat
-beat_genres=()
-
-# generate genres based on what is in the db.
-genres=()
-
-# relatively upbeat genres to listen, eg while biking
-upbeat_genres=()
-for g in $(beet ls -f '$genre' | sort -u); do
- if [[ ${ignore_genres_a[$g]} ]]; then continue; fi
- genres+=($g)
- if [[ ${slow_genres_a[$g]} ]]; then continue; fi
- beat_genres+=($g)
- case $g in
- chill)
- continue
- ;;
- esac
- upbeat_genres+=($g)
-done
-
-# generate regex for beat playlist
-beat_regex=
-first=true
-for g in ${beat_genres[@]}; do
- if $first; then
- first=false
- beat_regex=$g
- else
- beat_regex+="|$g"
- fi
-done
-
-# generate regex for upbeat playlist
-beat_regex=
-first=true
-for g in ${upbeat_genres[@]}; do
- if $first; then
- first=false
- upbeat_regex=$g
- else
- upbeat_regex+="|$g"
- fi
-done
-
-
-for g in ${genres[@]}; do
- for r in {3..5}; do
- case $g in
- pop|rap)
- cat <<EOF
- - name: ${g}-${r}.m3u
- query: 'rating:${r}..5 genre::^$g\$ ^expl:t ^gimicky:t ^lesser_version:t'
- - name: ${g}-x-${r}.m3u
- query: 'rating:${r}..5 genre::^$g\$ ^gimicky:t ^lesser_version:t'
-EOF
- ;;
- *)
- cat <<EOF
- - name: ${g}-${r}.m3u
- query: 'rating:${r}..5 genre:$g ^gimicky:t ^lesser_version:t'
-EOF
- ;;
- esac
- done
-done
-
-for t in ${tags[@]}; do
- for r in {3..5}; do
- cat <<EOF
- - name: ${t}-${r}.m3u
- query: 'rating:${r}..5 $t:t ^lesser_version:t'
-EOF
-
- done
-done
-
-for r in {3..5}; do
- cat <<EOF
- - name: beat-${r}.m3u
- query: 'rating:${r}..5 genre::$beat_regex ^expl:t ^gimicky:t ^lesser_version:t'
- - name: beat-x-${r}.m3u
- query: 'rating:${r}..5 genre::$beat_regex ^gimicky:t ^lesser_version:t'
- - name: upbeat-${r}.m3u
- query: 'rating:${r}..5 genre::$upbeat_regex ^expl:t ^gimicky:t ^lesser_version:t ^sad:t'
- - name: upbeat-x-${r}.m3u
- query: 'rating:${r}..5 genre::$upbeat_regex ^gimicky:t ^lesser_version:t ^sad:t'
- - name: gimicky-${r}.m3u
- query: 'rating:${r}..5 gimicky:t ^lesser_version:t'
-EOF
-done
fi
ret=0
-if [[ $HOSTNAME == $MAIL_HOST ]]; then
+if [[ $HOSTNAME == "$MAIL_HOST" ]]; then
mkdir -p /p/bkbackup
for ncdir in /var/www/ncexpertpath /var/www/ncninja; do
ncbase=${ncdir##*/}
ret=1
fi
done
- rsync --numeric-ids -ra --delete root@$host:/m /p/bkbackup
+ rsync --numeric-ids -ra --delete \
+ --exclude md/expertpathologyreview.com/testignore \
+ --exclude md/amnimal.ninja/testignore \
+ root@$host:/m /p/bkbackup
fi
exit $ret
case $EUID in
0)
+ # shellcheck disable=SC2034 # used in brc
SL_SSH_ARGS="-F $HOME/.ssh/confighome"
;;
esac
source /a/bin/bash_unpublished/source-state
source /a/bin/log-quiet/logq-function
-if [[ -s /a/opt/alacritty/extra/completions/alacritty.bash ]]; then
- source /a/opt/alacritty/extra/completions/alacritty.bash
-fi
+
+# 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
m pactl unload-module module-null-sink
m pactl unload-module module-remap-source
- sources=($(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p'))
+ IFS=" " read -r -a sources <<<"$(pacmd list-sources | sed -rn 's/.*name: <([^>]+).*/\1/p')"
if (( ! $# )); then
i=0
# usage mkschroot [-] distro codename packages
# - means no piping in of sources.list
mkschroot() {
- local force=false
+ local sources force repo n distro
+ force=false
while [[ $1 == -* ]]; do
case $1 in
-f) force=true; shift ;;
# s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
eqgo() {
- enn -M $(exiqgrep -i -r.\*)
+ enn -M "$(exiqgrep -i -r.\*)"
}
eqgo1() {
- enn -M $(exipick -i -r.\*|h1)
+ enn -M "$(exipick -i -r.\*|h1)"
}
# 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;
+ /a/opt/android-studio/bin/studio.sh "$@" & r
}
-
+# convert brains path to url
+# /f/brains/sysadmin/interns/2022/nick_shrader/intro_blog_post.mdwn
+# becomes
+# https://brains.fsf.org/wiki/sysadmin/interns/2022/nick_shrader/intro_blog_post
iki() {
local url path
if [[ $1 ]]; then
url="https://brains.fsf.org/wiki/${url#*brains/}"
url="${url%.mdwn}"
echo "$url"
- # /f/brains/sysadmin/interns/2022/nick_shrader/intro_blog_post.mdwn
- # becomes
- # https://brains.fsf.org/wiki/sysadmin/interns/2022/nick_shrader/intro_blog_post
}
rmdir /tmp/ianbeetstmp
}
-# Export beets ratings into navidrome
-beetrating() {
- local tmp tmpfile myuser userid rating path cpath sqlpath
+# 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
- tmpfile=$(mktemp)
- beet ls -f '$rating $path' ^genre:spoken-w ^genre:skit rating:2..5 >$tmpfile
while read -r rating path; do
- tmp="/i/converted${path#/i/m}"
- cpath="${tmp%.*}.mp3" # converted path
+ 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
+ if [[ $old_rating != "$rating" ]]; then
+ echo "setting rating $old_rating -> $rating $cpath"
# https://stackoverflow.com/a/50317320
- m sqlite3 /i/navidrome/navidrome.db "
+ # 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
- 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';"
+ 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
- #sqlite3 /i/navidrome/navidrome.db "select path from annotation inner join media_file on item_id = id where rating = $r;"
- done <$tmpfile
+ 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() {
- local l query
+beetconvert-rm-extras() {
+ local l tmpf
local -A paths
- query="^genre:spoken-w ^genre:skit ^lesser_version:t ^rating:1"
- # redirect is to avoid printing every file
- beet convert -y $query >/dev/null 2> >(grep -v '^convert: Skipping' ||:)
-
+ 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
*.flac) convertedpath="${convertedpath%.flac}.mp3" ;;
esac
paths[$convertedpath]=t
- done < <(beet ls -f '$path' $query)
+ 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 < <(find /i/converted -path /i/converted/beetsmartplaylists -prune -o \( -type f -print \) -name '*.mp3' -o -name '*.m4a')
- ## end
+ 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 <<EOF
+ - name: $i.m3u
+ query: '${query_str[@]}'
+EOF
+ done
+}
+
+# beet playlist. use beetag with a playlist name
+bpl() {
+ eval beetag "${bpla[$1]}"
}
+complete -W "${!bpla[*]}" bpl
+
# tag with beets.
# usage: beetag QUERY
beetag() {
local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random
local do_rare_genres read_wait
- local -a genres pl_tags buttons button_map ids tags rare_genres tmp_tags
+ local -a pl_tags buttons button_map ids tags rare_genres tmp_tags
local -A button_i
local -i volume
- do_rare_genres=false
+
+ ### begin arg processing ###
random=false
- volume=70
- read_wait=2
case $1 in
-r)
random=true
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
- genres=(
- # gangsta rap / angry rap. something like g-rap would make beet queries for genre:rap include it
- arp
- ambient
- avant
- blues
- classical
- # slow instrumental. todo: reclassify some ambient into this.
- chill
- country
- # like power glove
- dark-wave
- # lyrical edm. todo: some pop needs reclassification to this
- dance
- hardcore
- instrumental
- latin
- metal
- # mq = mac quale. similar to the mr robot soundtracks.
- # slow, foreboding. usually electronic.
- mq
- pop
- rap
- rock
- # like rain by brian crain. mostly slow broody piano
- sleep
- techno
- world
- )
# because we were destined to run out of single key buttons.
rare_genres=(
jazz
skit
spoken-w
)
- pl_tags=(
- ## cross-genre tags that dont really make a playlist
- expl
- # songs i like but they get old fast due to feeling gimicky, or cringy after a while.
- gimicky
- # alternate version of a song we already have which isn't as good
- lesser_version
- # anything sad which i sometimes like or avoid.
- sad
-
- ## playlists
- # intimate, love
- love
- # favorite songs pump up songs
- pump1
- # favorite rap pump up songs, allows more songs than pump1
- pumprap
- # heart rending, spine tickling
- rend
- # for running
- run
- )
- last_genre_i=$(( ${#genres[@]} - 1 ))
+
+ last_genre_i=$(( ${#common_genres[@]} - 1 ))
buttons=( {a..p} {r..w} 0 {6..9} , . / )
- button_map=(${genres[@]} ${pl_tags[@]})
+ button_map=(${common_genres[@]} ${pl_tags[@]})
fstring=
for tag in "${pl_tags[@]}"; do
fstring+="%ifdef{$tag,$tag }"
for (( i=0; i<${#buttons[@]}; i++ )); do
button_i[${buttons[i]}]=$i
done
- beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $artist - $album - $title' "$@"
+ # shellcheck disable=SC2016 # obvious reason
+ beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $artist - $album - $title' "$@" | head -n 100 ||:
+ # shellcheck disable=SC2016 # obvious reason
mapfile -t ids < <(beet ls -f '$id' "$@" | { if $random; then sort -R; else cat; fi; } )
for (( j=0; j<${#ids[@]}; j++ )); do
hr
id=${ids[j]}
+ # shellcheck disable=SC2016 # obvious reason
lsout="$(beet ls -f '%ifdef{rating,$rating }'"$fstring"', $genre $id $artist - $album - $title' "id:$id")"
tags=( ${lsout%%,*} )
printf "%s\n" "$lsout"
- for (( i=0; i<${#button_map[@]}; i++ )); do
- echo ${buttons[i]} ${button_map[i]}
+ for (( i=0; i<${#button_map[@]}; i++)); do
+ if (( i % 3 == 2 )); then
+ printf "%s %s\n" ${buttons[i]} ${button_map[i]}
+ else
+ printf "%s %-15s" ${buttons[i]} ${button_map[i]}
+ fi
done
if $doplay; then
beet play --args=--volume=$volume "id:$id" &
y)
if $do_rare_genres; then
do_rare_genres=false
- button_map=(${genres[@]} ${pl_tags[@]})
+ 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=$(( ${#genres[@]} - 1 ))
+ last_genre_i=$(( ${#rare_genres[@]} - 1 ))
fi
local -A button_i
for (( i=0; i<${#buttons[@]}; i++ )); do
# input. One idea would be to use a music player like mpd where
# we can send it messages.
if ! fg; then
- sleep_wait=10
+ read_wait=10
fi
continue
;;
# update navidrome music data after doing beets tagging
beet2nav() {
- beetconvert
- beetsmartplaylists
- beetrating
+ 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() {
+ if [[ $HOSTNAME == kd ]]; then
+ return 0
+ fi
if [[ ! -e /i ]]; then
s mkdir /i
s chown iank:iank /i
- sshfs b8.nz:/i /i
+ fi
+ if ! mountpoint /i &>/dev/null; then
+ m sshfs b8.nz:/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
# "artist:" it is used as the artist instead of each artist in QUERY.
#
beegenre() {
- local artist artregex genre term singleartist
- local -a artists genres terms
+ local count artist artregex genre singleartist tmpf tmpf2
+ local -a artists genres
singleartist=false
case $1 in
artist:*)
singleartist=true
- artist="$term"
+ artist="$1"
+ shift
;;
esac
+ tmpf=$(mktemp)
+ tmpf2=$(mktemp)
if $singleartist; then
- read count genre < <(beet ls -f '$genre' "$artist" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1) ||:
+ # 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")
- read count genre < <(beet ls -f '$genre' "artist::^$artregex$" "${@/#/^}" | sort | uniq -c | sort -n | tail -n1) || continue
+ # 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"
+ echo "beet modify -y $* \"artist::^$artist$\" genre=$genre # $count"
fi
- done < <(beet ls -f '$artist' "$@" | sort -u)
+ done <$tmpf
read -r -N 1 -s -p "Y/n " char
case $char in
[Yy$'\n'])
;;
esac
fi
+ rm $tmpf
}
# note, to check for glue records
# slightly different than what it expected.
cheogram-get-logs() {
adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
- read -p "do cheogram backup on phone, do not enable extra cheogram data. press any key when done"
+ 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
# it does sudo ssh, that will leave a process around that we can't kill
# and it will leave the unit hanging around in a failed state needing manual
# killing of the process.
- s systemd-run --uid $(id -u) --gid $(id -g) \
+ s systemd-run --uid "$(id -u)" --gid "$(id -g)" \
-E SSH_AUTH_SOCK=/run/openssh_agent \
--unit "$cmd_name" --wait --collect "$cmd" "$@" || ret=$?
# The sleep lets the journal output its last line
unset jr_pid
fg &>/dev/null ||:
# this avoids any err-catch
- (( $ret == 0 )) || return $ret
+ (( ret == 0 )) || return $ret
}
# service run, and watch the output
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
+ 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,
# 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
+ chmod g+r ./*.private
}
dsign() {
# create .signed file
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) "$@"
+ bitcoin-cli -rpcclienttimeout=60000 -"$(s grep rpcuser= $f)" -"$(s grep rpcpassword= $f)" "$@"
}
btcusd() { # $1 btc in usd
local price
else
cd /
cmd="schroot -c bullseye chromium"
- CHROMIUM_FLAGS='--enable-remote-extensions' $cmd &r
+ CHROMIUM_FLAGS='--enable-remote-extensions' $cmd & r
fi
}
}
firefox-default-profile() {
- key=Default value=1 section=$1
+ 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"
+ sed -ri "/ *\[$section\]/,/^ *\[[^]]+\]/{/^\s*${key}[[:space:]=]/d};/ *\[$section\]/a $key=$value" "$file"
}
fdhome() { #firefox default home profile
firefox-default-profile Profile0
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
- cd $(mktemp -d)
+ tmpd="$(mktemp -d)"
+ cd "$tmpd"
echo ok > a
echo nok > b
mkdir 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
# do git status on published repos.
c /a/bin/githtml
for x in *; do
- cd $(readlink -f $x)/..
+ cd "$(readlink -f $x)"/..
status=$(i status -s) || pwd
if [[ $status ]]; then
hr
# work log
wlog() {
- local day now i days_back
+ local day i days_back
days_back=${1:-16}
for (( i=0; i<days_back; i++ )); do
day=$( date +%F -d @$((EPOCHSECONDS - 86400*i )) )
to() { t out -a "$@"; }
ti() { t in -a "$@"; }
tl() {
+ local in_secs
to "$*"
t s lunch
t in -a "$*"
- m t out -a $(date +%F.%T -d @$(( $(date -d "$(echo $*|sed 's/[_.]/ /g')" +%s) + 60*45 )) )
+ in_secs="$(date -d "${*//[_.]/ }" +%s)"
+ m t out -a "$(date +%F.%T -d @$(( in_secs + 60*45 )) )"
t s w
}
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}' ; }
idea() {
- /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" &r
+ /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
}
+ilogs-local() {
+ 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
+}
ilogs() {
- 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"
+ 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
+ cd $d$n/"$chan" && hr
+ for x in *; do
+ echo $x; sed "s/^./${x%log}/" $x; hr;
+ done
+ done
+}
ilog() {
- chan=${1:-#fsfsys}
+ local chan
+ chan="${1:-#fsfsys}"
# use * instead of -r since that does sorted order
- 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
+ sl root@iankelling.org ilog-local "$chan" | less +G
}
o() {
ccomp journalctl jtail jr jrf
-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
+## 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
}
host=$1
ipsuf=$2
mkdir -p /p/c/machine_specific/$host/filesystem/etc/wireguard
- cd /p/c/machine_specific/$host/filesystem/etc/wireguard
- umask_orig=$(umask)
- umask 0077
- wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
- cat >wghole.conf <<EOF
+ (
+ cd /p/c/machine_specific/$host/filesystem/etc/wireguard
+ umask_orig=$(umask)
+ umask 0077
+ wg genkey | tee hole-priv.key | wg pubkey > hole-pub.key
+ cat >wghole.conf <<EOF
[Interface]
# contents hole-priv.key
PrivateKey = $(cat hole-priv.key)
Endpoint = 72.14.176.105:1194
PersistentKeepalive = 25
EOF
- umask $umask_orig
- # old approach. systemd seems to work fine and cleaner.
- rm -f ../network/interfaces.d/wghole
- cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
+ umask $umask_orig
+ # old approach. systemd seems to work fine and cleaner.
+ rm -f ../network/interfaces.d/wghole
+ cedit -q $host /p/c/machine_specific/li/filesystem/etc/wireguard/wgmail.conf <<EOF || [[ $? == 1 ]]
[Peer]
PublicKey = $(cat hole-pub.key)
AllowedIPs = 10.8.0.$ipsuf/32
EOF
- cd - >/dev/null
+ )
}
if [[ ! $1 ]]; then
set -- fsf-office
fi
- local d1 d2
+ 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 '\<iank.*' *; done" | cut --complement -c12-16
+ ssh root@iankelling.org "for f in ${d[*]}; do cd \$f/#$1; grep '\<iank.*' *; done" | cut --complement -c12-16
}
mypidgin() {
c /p/c/.purple/logs/jabber/iank@fsf.org/office@conference.fsf.org.chat
for x in *.html; do html2text -o ${x%.html}.txt $x; done;
- 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}'
+ # shellcheck disable=SC2016 # false positive on ${
+ 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}'
}
allmyirc() {
local d
local ver ram fname src
ver=$1
ram=${2:-2024}
- # * is because it might have -backports in the name
- fname=debian-$ver-*nocloud-$(dpkg --print-architecture).qcow2
+ # * is because it might have -backports in the name. we only expect 1 expansion
+ fnames=( debian-$ver-*nocloud-"$(dpkg --print-architecture)".qcow2 )
+ if (( ${#fnames[@]} >= 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/
Subject: $*
EOF
else
- read sub
+ read -r sub
{ cat <<EOF
From: alertme@b8.nz
To: alerts@iankelling.org
Subject: $*
EOF
else
- read sub
+ read -r sub
{ cat <<EOF
From: alertme@b8.nz
To: daylert@iankelling.org
torshell() {
# per man torsocks
- source `type -p torsocks` on
+ # shellcheck disable=SC1090 # expected
+ source "$(type -p torsocks)" on
}
eless2() {
tm() {
# timer in minutes
# --no-config
- (sleep $(calc "$* * 60") && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
+ (sleep "$(calc "$* * 60")" && mpv --no-config --volume 50 /a/bin/data/alarm.mp3) > /dev/null 2>&1 &
}
trg() { transmission-remote-gtk & r; }
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
+ m sudo nsenter -t "$(systemctl status unbound| sed -n '/^ *Main PID:/s/[^0-9]//gp')" -n -m sudo -u $USER -i bash
}
nmtc() {
vpncmd() {
- m sudo -E env "PATH=$PATH" nsenter -t $(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf") -n "$@"
+ m sudo -E env "PATH=$PATH" nsenter -t "$(pgrep -f "/usr/sbin/openvpn .* --config /etc/openvpn/.*client.conf")" -n "$@"
}
vpni() {
fixvpndns() {
local link istls
- read _ link _ istls < <(resolvectl dnsovertls tunfsf)
+ read -r _ link _ istls < <(resolvectl dnsovertls tunfsf)
case $istls in
yes|no) : ;;
*) echo fixvpndns error: unexpected istls value: $istls >&2; return 1 ;;
vspicy() { # usage: VIRSH_DOMAIN
# connect to vms made with virt-install
- spicy -p $(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
- sed -r "s/.*port='([0-9]+).*/\1/")
+ spicy -p "$(sudo virsh dumpxml "$1"|grep "<graphics.*type='spice'"|\
+ sed -r "s/.*port='([0-9]+).*/\1/")"
}
wian() {
}
+# rg my main files
+rgm() {
+ rg "$@" /p/pd.org /p/w.org /a/t.org /a/work.org /b
+}
+
reset-konsole() {
# we also have a file in /a/c/...konsole...
local f=$HOME/.config/konsolerc
default_args_file=/etc/btrbk-run.conf
if [[ -s $default_args_file ]]; then
+ # shellcheck disable=SC2046 # we want word splitting
set -- $(< $default_args_file) "$@"
# i havent used this feature yet, so warn about it
echo "$0: warning: default btrbk-run options set in $default_args_file (sleeping 5 seconds):"
targets=()
early=false
cron=false
+fast=false
orig_args=("$@")
-temp=$(getopt -l cron,pull-reexec,help 23ceil:m:npqrs:t:vh "$@") || usage 1
+temp=$(getopt -l cron,fast,pull-reexec,help 23ceil:m:npqrs:t:vh "$@") || usage 1
eval set -- "$temp"
while true; do
case $1 in
-c) conf_only=true ;;
# quit early, just btrbk, no extra remounting etc.
-e) early=true ;;
+ # skip various checks. when we run twice in a row for
+ # switch mail-host, no need to repeat the same checks again.
+ --fast) fast=true ;;
-i) incremental_strict=true ;;
# bytes per second, suffix k m g
-l) rate_limit=$2; shift ;;
if [[ ! -v targets && ! $source ]]; then
if $cron; then
if [[ $HOSTNAME != "$MAIL_HOST" ]]; then
- if [[ $HOSTNAME == kd && $MAIL_HOST = x2 ]]; then
- kd_spread=true
+ if [[ $HOSTNAME == kd && $MAIL_HOST == x3 ]]; then
+ if ping -q -c1 -w1 x3.office.fsf.org &>/dev/null; then
+ work_host=x3.office.fsf.org
+ elif ping -q -c1 -w1 x3wg.b8.nz &>/dev/null; then
+ work_host=x3wg.b8.nz
+ fi
+ if [[ $work_host ]]; then
+ source_state="$(ssh $work_host cat /a/bin/bash_unpublished/source-state)"
+ eval "$source_state"
+ if [[ $MAIL_HOST == x3 ]]; then
+ kd_spread=true
+ else
+ echo "MAIL_HOST=$MAIL_HOST, nothing to do"
+ mexit 0
+ fi
+ else
+ echo "MAIL_HOST=$MAIL_HOST, nothing to do"
+ mexit 0
+ fi
else
echo "MAIL_HOST=$MAIL_HOST, nothing to do"
mexit 0
fi
fi
- # x2 at home atm
- kd_spread=false
-
at_work=false
at_home=false
;;&
*)
if $at_home; then
- # main work machine
- if ping -q -c1 -w1 x3.office.fsf.org &>/dev/null; then
- targets+=(x3.office.fsf.org)
- else
- targets+=(x3wg.b8.nz)
+ if ! $kd_spread; then
+ # main work machine
+ if ping -q -c1 -w1 x3.office.fsf.org &>/dev/null; then
+ targets+=(x3.office.fsf.org)
+ else
+ targets+=(x3wg.b8.nz)
+ fi
fi
for h in frodo kd; do
if [[ $HOSTNAME == "$h" ]]; then
##### end command line parsing ########
-if [[ $source ]]; then
+if ! $fast && [[ $source ]]; then
if [[ $(ssh $source ps --no-headers -o comm 1) == systemd ]]; then
status=$(ssh $source systemctl is-active btrbk.service) || : # normally returns 3
case $status in
if ! command -v btrbk &>/dev/null; then
die "error: no btrbk binary found"
fi
-# if our mountpoints are from stale snapshots,
-# it doesn't make sense to do a backup.
-m check-subvol-stale ${mountpoints[@]} || die "found stale mountpoints in ${mountpoints[*]}"
-# for an initial run, btrbk requires the dir to exist.
-mkdir -p /mnt/{root,o}/btrbk
+if ! $fast; then
+ # if our mountpoints are from stale snapshots,
+ # it doesn't make sense to do a backup.
+ m check-subvol-stale ${mountpoints[@]} || die "found stale mountpoints in ${mountpoints[*]}"
+
+ # for an initial run, btrbk requires the dir to exist.
+ mkdir -p /mnt/{root,o}/btrbk
+fi
local_zone=$(date +%z)
if [[ $source ]]; then
- if ! zone=$(ssh root@$source date +%z); then
- if $conf_only; then
- echo "$0: warning: failed to ssh to root@$source"
- else
- die failed to ssh to root@$source
+ if $fast; then
+ zone=$local_zone
+ else
+ if ! zone=$(ssh root@$source date +%z); then
+ if $conf_only; then
+ echo "$0: warning: failed to ssh to root@$source"
+ else
+ die failed to ssh to root@$source
+ fi
+ fi
+ if [[ $zone != "$local_zone" ]]; then
+ die "error: dont confuse yourself with multiple time zones. $h has different timezone than localhost"
fi
fi
- if [[ $zone != "$local_zone" ]]; then
- die "error: dont confuse yourself with multiple time zones. $h has different timezone than localhost"
- fi
-
else
sshable=()
sshfail=()
min_idle_ms=$((1000 * 60 * 15))
for h in ${targets[@]}; do
- if remote_info=( $(timeout -s 9 6 ssh root@$h "mkdir -p /mnt/root/btrbk /mnt/o/btrbk && date +%z && df --output=size,pcent / | tail -n1") ); then
+ if $fast || $conf_only; then
+ # Use some typical values in this case
+ root_size=$(( 1024 * 1024 * 2000 )) #2tb
+ percent_used=10
+ zone=$(date +%z)
+ elif tmpstr=$(timeout -s 9 6 ssh root@$h "mkdir -p /mnt/root/btrbk /mnt/o/btrbk && date +%z && df --output=size,pcent / | tail -n1"); then
+ IFS=" " read -r -a remote_info <<<"$tmpstr"
+
zone=${remote_info[0]}
root_size=${remote_info[1]}
percent_used=${remote_info[2]%%%}
if (( ${#remote_info[@]} != 3 )); then
die "error: didnt get 3 fields in test ssh to target $h. investigate"
fi
- elif $conf_only; then
- # Use some typical values in this case
- root_size=$(( 1024 * 1024 * 2000 )) #2tb
- percent_used=10
- zone=$(date +%z)
else
sshfail+=($h)
continue
target_preserve $std_preserve
target_preserve_min 2h
+# i tried this when investigating: clone no source subvolume found error
+#incremental_prefs sro:1 srn:1 sao san:1 aro:1 arn:1
+
# if something fails and it's not obvious, try doing
# btrbk -l debug -v dryrun
mp_count=${#mountpoints[@]}
for (( i=0; i < mp_count - 1 ; i++ )); do
if [[ ${mountpoints[i]} == /q ]]; then
- unset mountpoints[i]
+ unset "mountpoints[i]"
mountpoints+=(/q)
fi
done
done
fi
+subvols=()
+for mp in "${mountpoints[@]}"; do
+ subvols+=("${mp##*/}")
+done
if [[ $source ]]; then
- m mount-latest-subvol
+ m mount-latest-subvol "${subvols[@]}"
else
m /a/exe/mount-latest-remote ${targets[@]}
fi
[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
-f=/usr/local/lib/err;test -r $f || { echo "error: $0 no $f" >&2;exit 1;}; . $f
+set -e; . /usr/local/lib/err; set +e
# inspired from
# https://github.com/kdave/btrfsmaintenance
done
fi
}
-
+tmpf=$(mktemp)
+d tmpf=$tmpf
for d; do
+
if $subvol_path; then
svp=$d
root_dir=${d%/*}
d "svp=$svp # subvolume path"
fi
- snaps=($root_dir/btrbk/$subvol_dir.20*) # Assumes we are in the 21st century.
+ # Assumes we are in the 21st century.
+ ls -1dvrq $root_dir/btrbk/$subvol_dir.20* >$tmpf
+ mapfile -t snaps <$tmpf
if [[ ! ${snaps[*]} ]]; then
# no snapshots yet
# TODO: make this an error and override with a cli flag
continue
fi
+ # last_snap by date.
+ last_snap="${snaps[0]}"
+ ## alternate slower alternative which would not rely on ls sorting:
+ # last_snap=$(
+ # for s in ${snaps[@]}; do
+ # f=${s##*/}
+ # unix_time=$(date -d $(sed -r 's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<${f#$vol.}) +%s)
+ # printf "%s %s\n" $unix_time $s # part of the pipeline
+ # # sort will fail
+ # done | sort -r | head -n 1 | awk '{print $2}' || [[ ${PIPESTATUS[1]} == 141 || ${PIPESTATUS[0]} == 32 ]]
+ # )
+ # if [[ ! $last_snap ]]; then
+ # # should not happen.
+ # echo "$0: error: could not find latest snapshot for $svp among ${snaps[*]}" >&2
+ # exit 1
+ # fi
+ d last_snap=$last_snap
+
+ if [[ ! -e $svp ]]; then
+ echo "$0: warning: subvol does not exist: $svp"
+ echo "$0 assuming this host was just for receiving and latest snap is freshest"
+ freshest_snap=$last_snap
+ stale=true
+ stale-file
+ continue
+ fi
+
# get info on last received sub
last_received=
last_received_cgen=0
if [[ $cgen -gt $last_received_cgen ]]; then
last_received_cgen=$cgen
last_received=$f
+ elif [[ $last_received ]]; then
+ # optimization: we are looking in reverse order by date, so if
+ # we find one that has a lesser cgen, assume the rest will all
+ # be lesser.
+ break
fi
fi
done
d last_received_cgen=$last_received_cgen
d last_received=$last_received
- # Get last_snap by date.
- # when a btrbk bugfix makes it into the distro,
- # we might replace this with btrbk list latest /mnt/root/$vol | ...
- last_snap=$(
- for s in ${snaps[@]}; do
- f=${s##*/}
- unix_time=$(date -d $(sed -r 's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<${f#$vol.}) +%s)
- printf "%s %s\n" $unix_time $s # part of the pipeline
- # sort will fail
- done | sort -r | head -n 1 | awk '{print $2}' || [[ ${PIPESTATUS[1]} == 141 || ${PIPESTATUS[0]} == 32 ]]
- )
- if [[ ! $last_snap ]]; then
- # should not happen.
- echo "$0: error: could not find latest snapshot for $svp among ${snaps[*]}" >&2
- exit 1
- fi
- d last_snap=$last_snap
-
- if [[ ! -e $svp ]]; then
- echo "$0: warning: subvol does not exist: $svp"
- echo "$0 assuming this host was just for receiving and latest snap is freshest"
- freshest_snap=$last_snap
- stale=true
- stale-file
- continue
- fi
-
# if there is a last_received, we can assume stale or fresh if we are newer/older
if [[ $last_received ]]; then
stale=true
# fresh if $svp has $last_snap as a snapshot,
if btrfs sub show $svp 2>/dev/null | sed '0,/^\s*Snapshot(s):/d;s/^\s*//' | \
- grep -xF ${last_snap#$root_dir/} >/dev/null; then
+ grep -xF ${last_snap#"$root_dir"/} >/dev/null; then
stale=false
else # or else $svp is a snapshot of $last_snap. we use a uuid
# comparison, which if I remember from the docs, is a bit more
stale-file
done
+rm $tmpf
file=/etc/modprobe.d/evbug.conf
line="blacklist evbug"
if [[ $(cat $file) != "$line" ]]; then
- sudo dd of=$file 2>/dev/null <<<"$line"
+ sudo dd of=$file status=none <<<"$line"
sudo depmod -a
sudo update-initramfs -u
fi
first_root_crypt=$(awk '$2 == "/" {print $1}' /etc/mtab)
tu /etc/fstab <<EOF
-$first_root_crypt /nocow btrfs noatime,subvol=nocow$( ((`nproc` > 2)) && echo ,compress=zstd ) 0 0
+$first_root_crypt /nocow btrfs noatime,subvol=nocow$( (( $(nproc) > 2)) && echo ,compress=zstd ) 0 0
EOF
sudo mkdir -p $dir
sudo chown $USER:$USER $dir
# for ziva
#p install --no-install-recommends minetest/buster libleveldb1d/buster libncursesw6/buster libtinfo6/buster
doupdate=false
+ # shellcheck disable=SC2043 # in case we want more than 1 in the loop later.
for n in bullseye; do
f=/etc/apt/sources.list.d/$n.list
t=$(mktemp)
deb http://us.archive.ubuntu.com/ubuntu/ focal-security main universe
EOF
if ! diff -q $t $f; then
- sudo dd if=$t of=$f 2>/dev/null
+ sudo dd if=$t of=$f status=none
p update
fi
deb-src http://mirror.fsf.org/trisquel/ nabia-backports main
EOF
if ! diff -q $t $f; then
- sudo dd if=$t of=$f 2>/dev/null
+ sudo dd if=$t of=$f status=none
p update
fi
deb-src http://mirror.fsf.org/trisquel/ aramo-backports main
EOF
if ! diff -q $t $f; then
- sudo dd if=$t of=$f 2>/dev/null
+ sudo dd if=$t of=$f status=none
p update
fi
# way to install suggests even if the main package is already
# installed. reinstall doesn't work, uninstalling can cause removing
# dependent packages.
+# shellcheck disable=SC2046 # word splitting is intended
pi ${pall[@]} $(apt-cache search ruby[.0-9]+-doc| awk '{print $1}') $($src/distro-pkgs)
# schroot service will restart schroot sessions after reboot.
# "equivs-control <name>, edit the file produced to provide the right
# dependency and have a nice name, then run equivs-build <name> and
# finally dpkg -i the resulting .deb file"
-
-mkct
-# edited from output of equivs-control tox
-cat >tox <<'EOF'
+# as of 2023-02, the tox dependency was removed in debian unstable, so
+# this hack will probably go away in t12.
+
+if pcheck beets; then
+ tmpdir="$(mktemp -d)"
+ cd "$tmpdir"
+ # edited from output of equivs-control tox
+ cat >tox <<'EOF'
Section: python
Priority: optional
Standards-Version: 3.9.2
Package: tox
Description: tox-dummy
EOF
-equivs-build tox
-sudo dpkg -i tox_1.0_all.deb
-rm -rf ./tox*
-pi beets python3-discogs-client
-
+ equivs-build tox
+ sudo dpkg -i tox_1.0_all.deb
+ rm -rf ./tox*
+ pi beets python3-discogs-client
+ cd
+ rm -r "$tmpdir"
+fi
# notes about barrier
# run barrier, do the gui config,
### begin prometheus ###
+
+# cleanup old files. 2023-02
+x=(/var/lib/prometheus/node-exporter/*.premerge)
+if [[ -e ${x[0]} ]]; then
+ s rm /var/lib/prometheus/node-exporter/*
+fi
+
+pi prometheus-node-exporter-collectors
case $HOSTNAME in
kd)
# Font awesome is needed for the alertmanager ui.
- pi prometheus-alertmanager prometheus prometheus-node-exporter fonts-font-awesome
+ pi prometheus-alertmanager prometheus fonts-font-awesome
/a/bin/buildscripts/prometheus
web-conf -p 9091 -f 9090 - apache2 i.b8.nz <<'EOF'
<Location "/">
ser restart prometheus-alertmanager
fi
+ /a/bin/buildscripts/prom-node-exporter -l
+
for ser in prometheus-node-exporter prometheus-alertmanager prometheus; do
sysd-prom-fail-install $ser
done
;;
*)
- pi prometheus-node-exporter
+ /a/bin/buildscripts/prom-node-exporter
;;
esac
#!/bin/bash
-f=/usr/local/lib/err;test -r $f || { echo "error: $0 no $f" >&2;exit 1;}; . $f
+set -e; . /usr/local/lib/err; set +e
[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
# Copyright (C) 2019 Ian Kelling
# SPDX-License-Identifier: AGPL-3.0-or-later
+
+# shellcheck disable=SC2254 # makes for a lot of unneeded quotes
+
+
# perusing through /el/mainlog without test messages:
# &!testignore|jtuttle|
#
m systemctl daemon-reload
fi
-# checking bhost_t is redundant, but could help us catch errors.
-if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then
- # todo: in mail-setup, we have a static list of backup hosts, not *y
- m systemctl --now enable wg-quick@wghole
+# optimization, this only needs to run once.
+if [[ ! -e /sys/class/net/wghole ]]; then
+ # checking bhost_t is redundant, but could help us catch errors.
+ if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then
+ # todo: in mail-setup, we have a static list of backup hosts, not *y
+ m systemctl --now enable wg-quick@wghole
+ fi
fi
-sysd-prom-fail-install epanicclean
-m systemctl --now enable epanicclean
+# optimization, this only needs to be run once
+if [[ ! -e /var/lib/prometheus/node-exporter/exim_paniclog.prom ]]; then
+ sysd-prom-fail-install epanicclean
+ m systemctl --now enable epanicclean
+fi
case $HOSTNAME in
je)
;;
esac
-m /a/bin/ds/mail-cert-cron -1
-sre mailcert.timer
+# optimization, this only needs to run once.
+if [[ ! -e /etc/exim4/fullchain.pem ]]; then
+ m /a/bin/ds/mail-cert-cron -1
+ m systemctl --now enable mailcert.timer
+fi
case $HOSTNAME in
$MAIL_HOST|bk)
usage() {
cat <<EOF
-Usage: ${0##*/} [OPTIONS]
+Usage: ${0##*/} [OPTIONS] [SUBVOLUMES]
-h|--help Print help and exit.
-f|--force Use kill -9 to try fixing unmount errors
exit $1
}
-all_vols=(q a o i ar qr)
tu() {
shift
done
+if (( $# )); then
+ all_vols=( "$@" )
+else
+ all_vols=(q a o i ar qr)
+fi
+
##### end command line parsing ########
ret=0
mnt /mnt/boot2
fi
-do_o=true
root_dev=$(awk '$2 == "/" {print $1}' /etc/mtab)
mapper-dev root_dev
o_dev=$(awk '$2 == "/mnt/o" {print $1}' /etc/mtab)
mapper-dev o_dev
-if [[ $o_dev == "$root_dev" ]]; then
- do_o=false
-fi
# root2_dev=$(awk '$2 == "/mnt/root2" {print $1}' /etc/mtab)
# mapper-dev root2_dev
crypt_dev=$root_dev
else # if we are in a recovery boot, find the next best crypt device
mopts=,noauto
- do_o=false
+ # todo: I think I had an idea to not setup /o in this case,
+ # but never finished implementing it
for dev in $(dmsetup ls --target crypt | awk '{print $1}'); do
dev=/dev/mapper/$dev
if awk '{print $1}' /etc/mtab | grep -Fx $dev &>/dev/null; then
# dont tax the cpus of old laptops
-if ((`nproc` > 2)); then
+if (( $(nproc) > 2)); then
mopts+=,compress=zstd
fi
# ownership, and ssh doesn\'t allow any group writable parent
# directories, so we are forced to use a directory structure similar
# to home directories
-f=(/mnt/root/btrbk/q.*); f=${f[0]}
+fa=(/mnt/root/btrbk/q.*); f=${fa[0]}
if [[ -e $f ]]; then
fstab <<EOF
$crypt_dev /q btrfs noatime,subvol=q,gid=1000$mopts 0 0
EOF
fi
-f=(/mnt/root/btrbk/qr.*); f=${f[0]}
+fa=(/mnt/root/btrbk/qr.*); f=${fa[0]}
if [[ -e $f ]]; then
fstab <<EOF
$crypt_dev /qr btrfs noatime,subvol=qr$mopts 0 0
EOF
fi
-f=(/mnt/root/btrbk/ar.*); f=${f[0]}
+fa=(/mnt/root/btrbk/ar.*); f=${fa[0]}
if [[ -e $f ]]; then
fstab <<EOF
$crypt_dev /ar btrfs noatime,subvol=ar,uid=1000,gid=1000$mopts 0 0
fi
-f=(/mnt/o/btrbk/o.*); f=${f[0]}
+fa=(/mnt/o/btrbk/o.*); f=${fa[0]}
if [[ -e $f ]]; then
- fstab <<EOF
+ if [[ $o_dev != "$root_dev" ]]; then
+ fstab <<EOF
$o_dev /o btrfs noatime,subvol=o$mopts 0 0
+EOF
+ fi
+ fstab <<EOF
/o/m /m none bind$mopts 0 0
EOF
-else
- do_o=false
fi
my_pids=($$ $PPID)
loop_limit=30
count=0
-while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != ${my_pids[-2]} && $count -lt $loop_limit ]]; do
+while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != "${my_pids[-2]}" && $count -lt $loop_limit ]]; do
count=$((count + 1))
p=$(ps -p ${my_pids[-1]} -o ppid=)
if [[ $p == 0 || ! $p ]]; then
for vol in ${all_vols[@]}; do
d=/$vol
- if ! awk '{print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
+ if ! awk '$3 == "btrfs" {print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
continue
fi
for b in ${binds[@]}; do
if mountpoint -q $b; then
bid=$(stat -c%d $b)
- if [[ $did != $bid ]]; then
+ if [[ $did != "$bid" ]]; then
umount-kill $b
fi
fi
fi
done
if [[ $bsub ]]; then
- tmp=$(mktemp)
# in testing, same subvol is 136 bytes. allow some overhead. 32 happens sometimes under systemd.
# $ errno 32
# EPIPE 32 Broken pipe
# this goes backwards from oldest. leaf_new_limit_time is just in case
# the order gets screwed up or something.
for leaf in ${leaf_vols[@]}; do
- leaf_time=$(date -d ${leaf#$vol.leaf.} +%s)
+ leaf_time=$(date -d ${leaf#"$vol".leaf.} +%s)
if (( leaf_limit_time > leaf_time || ( leaf_new_limit_time > leaf_time && count > 15 ) )); then
x btrfs sub del $leaf
fi
#!/bin/bash
-f=/usr/local/lib/err;test -r $f || { echo "error: $0 no $f" >&2;exit 1;}; . $f
-
-plists=(
- # these are useful tags
- expl
- gimicky
- sad
- # these are normal playlists
- love
- pump1
- pumprap
- rend
- run
-)
+set -e; . /usr/local/lib/err; set +e
+source /a/bin/ds/beet-data
+source /b/bash_unpublished/source-semi-priv
+
+declare -A genre_a
+for g in ${all_genres[@]}; do
+ genre_a[$g]=t
+done
# these options are mainly for debugging / developing quickly.
plist_only=false
m ()
{
printf "%s\n" "$*" >&2
- if $dry_run && [[ $1 == beet && $2 == modify ]]; then
+ if $dry_run; then
echo "dry run: $*"
else
"$@"
declare -A navirating
tmpdir=$(mktemp -d)
cd $tmpdir
+# the code here is duplicated later for non ssh context.
if [[ $HOSTNAME != kd ]]; then
- ssh b8.nz bash -s <<EOF | tar xz
+
+ ssh b8.nz bash -s "$(md5sum </a/bin/ds/beet-data)" <<'EOF' | tar xz
+if [[ $1 != "$(md5sum </a/bin/ds/beet-data)" ]]; then
+ echo error: old beet-data on kd
+ exit 1
+fi
install -m 700 -d /tmp/nav2beet
cd /tmp/nav2beet
-for r in 1 2 3 4 5; do
- sqlite3 /i/navidrome/navidrome.db ".output \$r" "select path from annotation inner join media_file on item_id = id where rating = \$r;"
-done
-find /i/m -type f -name '*.flac' >flacs
-for plist in ${plists[@]}; do
- sqlite3 /i/navidrome/navidrome.db "select path from media_file inner join playlist_tracks on media_file.id = media_file_id where playlist_id = (select id from playlist where name = '\$plist');" | sed 's,^/i/converted,/i/m,' | sort >\$plist
-done
+/a/bin/ds/nav2beet-local
tar cz -C /tmp nav2beet
EOF
-
cd nav2beet
else
- for r in 1 2 3 4 5; do
- sqlite3 /i/navidrome/navidrome.db ".output $r" "select path from annotation inner join media_file on item_id = id where rating = $r;"
- done
- find /i/m -type f -name '*.flac' >flacs
- for plist in ${plists[@]}; do
- sqlite3 /i/navidrome/navidrome.db "select path from media_file inner join playlist_tracks on media_file.id = media_file_id where playlist_id = (select id from playlist where name = '$plist');" | sed 's,^/i/converted,/i/m,' | sort >$plist
- done
+ /a/bin/ds/nav2beet-local
fi
while read -r l; do
flacs[$l]=t
if ! $plist_only; then
echo begin star rating import from navidrome to beets
- # todo: consider if this is a problem: file removed/renamed in main
- # collection, but not yet updated navidrome, we want to skip it not
- # die.
-
for r in 1 2 3 4 5; do
while read -r path; do
beetpath="/i/m${path#/i/converted}"
done
declare -A beetrating
for r in 1 2 3 4 5; do
+ # shellcheck disable=SC2016 # expected beets arg
m beet ls -f '$path' rating:$r >$tmpf
while read -r path; do
beetrating[$path]=$r
for path in "${!navirating[@]}"; do
r="${navirating[$path]}"
+ if [[ ! "${beetrating[$path]}" ]]; then
+ if [[ -e $path ]]; then
+ echo "$0: ERROR: $path exists but we have no rating for it"
+ exit 1
+ else
+ echo "$0: WARNING: $path exists in navidrome but not beets"
+ continue
+ fi
+ fi
if [[ $r != "${beetrating[$path]}" ]]; then
- # note: this assumes there are no cases like filea.mp3 filea.mp3.mp3, which would affect both files.
echo "$r != ${beetrating[$path]}, beet modify -y path:$path rating=$r"
beet modify -y "path:$path" "rating=$r"
fi
# end star rating import from navidrome to beets:
fi
-
echo begin import navidrome playlists as flexible attribute with value t.
-# These are only the playlists listed in the beets config.yaml
-# "subsonicplaylist:" and then duplicated here:
-
+shopt -s nullglob
+for path in 2genre/*/*; do
+ id="${path#*/}"
+ id="${id%/*}"
+ filename="${path##*/}"
+ tmp="${filename%%[^0-9-]*}"
+ genre="${filename#"$tmp"}"
+ if [[ ${genre_a[$genre]} ]]; then
+ is_genre=true
+ else
+ # Some playlists we create with random names to remind us to do something
+ # with these tracks later once we are at a computer.
+ is_genre=false
+ fi
+ while read -r path; do
+ flac="${path%.mp3}.flac"
+ if [[ ${flacs[$flac]} ]]; then
+ path="$flac"
+ fi
+ if $is_genre; then
+ m beet modify -y "path:$path" "genre=$genre"
+ else
+ m beet modify -y "path:$path" "$genre=t"
+ fi
+ done <"$path"
+ # how i figured this out:
+ # s tcpdump -i any -w /tmp/tcpdump port 4533
+ # then delete a test playlist in client.
+ # made some sense out of it with: http://www.subsonic.org/pages/api.jsp
+ # open file in wireshard, right click "Hypertext Transfer Protocol", copy, as printable text, put into file /tmp/headers
+ # /a/opt/h2c/h2c </tmp/headers
+ # change from https to http.
+ # then tested out removing stuff i suspected was not important,
+ # and added https and host so it would work remotely.
+ m 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
-for plist in ${plists[@]}; do
+for plist in ${nav_tags[@]}; do
echo "processing $plist"
- beet ls -f '$path' $plist:t ^genre:spoken-w ^genre:skit ^rating:1 | sort | sed 's,\.flac$,.mp3,'> p
+ # shellcheck disable=SC2016 # expected beets arg
+ beet ls -f '$path' $plist:t $nav_convert_query | sort | sed 's,\.flac$,.mp3,'> p
while read -r path; do
flac="${path%.mp3}.flac"
if [[ ${flacs[$flac]} ]]; then
done < <(comm -23 p $plist)
while read -r path; do
flac="${path%.mp3}.flac"
- echo "flac=$flac path=$path ${flacs[$flac]} ${flacs[flac]}"
if [[ ${flacs[$flac]} ]]; then
path="$flac"
fi
--- /dev/null
+#!/bin/bash
+set -e; . /usr/local/lib/err; set +e
+source /a/bin/ds/beet-data
+
+for r in 1 2 3 4 5; do
+ sqlite3 /i/navidrome/navidrome.db ".output $r" "select path from annotation inner join media_file on item_id = id where rating = $r;"
+done
+find /i/m -type f -name '*.flac' >flacs
+for plist in ${nav_tags[@]}; do
+ sqlite3 /i/navidrome/navidrome.db "select path from media_file inner join playlist_tracks on media_file.id = media_file_id where playlist_id = (select id from playlist where name = '_$plist');" | sed 's,^/i/converted,/i/m,' | sort >$plist
+done
+while read -r id name; do
+ mkdir -p 2genre/$id
+ sqlite3 /i/navidrome/navidrome.db "select path from media_file inner join playlist_tracks on media_file.id = media_file_id where playlist_id = '$id';" | sed 's,^/i/converted,/i/m,' | sort >2genre/$id/$name
+done < <(sqlite3 /i/navidrome/navidrome.db ".separator ' '" "select id, name from playlist where name like '2%'")
[s]
shuffle
-# audio
+# audio, especially with beetag
[a]
volume=75
player-operation-mode=cplayer
-
+audio-display=no
+# dont display any tags
+display-tags=
+#really-quiet
# note, useful cli option:
# --script-opts=osc-visibility=always
m $new_shell killall -q emacs ||:
e Running main btrbk
-m btrbk-run -v $bbk_args $incremental_arg -m /o || ret=$?
+m btrbk-run -v --fast $bbk_args $incremental_arg -m /o || ret=$?
if (( ret )); then
bang="$(printf "$(tput setaf 5)█$(tput sgr0)%.0s" 1 2 3 4 5 6 7)"
e $bang failed btrbk of /o. restoring old host as primary