commit ca46ea85643711b7021d33c7174b70df22176985
parent 63382dc40be538d110852c8dd77439ac490258f2
Author: dwrz <dwrz@dwrz.net>
Date: Wed, 7 Dec 2022 20:50:25 +0000
Add scripts
Diffstat:
27 files changed, 640 insertions(+), 0 deletions(-)
diff --git a/scripts/backup b/scripts/backup
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+
+readonly TARGETS=(
+ "$HOME/.config/gnupg/"
+ "$HOME/.config/pass/"
+)
+
+readonly HOSTS=(
+ "mobile"
+ "555.dwrz.net"
+ "516.dwrz.net"
+)
+
+backup-b2() {
+ local bucket id key destination
+ id="$(pass backblaze/duplicity/keyID)"
+ key="$(pass backblaze/duplicity/applicationKey)"
+
+ env "B2_APPLICATION_KEY_ID"="${id}" "B2_APPLICATION_KEY"="${key}" \
+ b2 sync "$@"
+ if [[ "$?" -ne 0 ]]; then
+ err "$0: failed b2 sync ${t} to ${destination}"
+ fi
+}
+
+backup-hosts() {
+ local target="$1"
+ local t="$2"
+
+ for h in "${HOSTS[@]}"; do
+ if [[ "${h}" == "mobile" ]]; then
+ rsync --archive --delete --progress --verbose "${target}" \
+ "$USER"@"${h}":/data/data/com.termux/files/home/"${t}"
+ else
+ rsync --archive --delete --progress --verbose "${target}" \
+ "$USER"@"${h}":/home/"$USER"/"${t}"
+ fi
+ if [[ "$?" -ne 0 ]]; then
+ err "$0: failed rsync ${t} to ${h}"
+ fi
+ done
+}
+
+main() {
+ # Backup to B2.
+ local bucket
+ bucket="$(pass backblaze/bucket)"
+
+ # Config
+ backup-b2 --delete --excludeDirRegex="Signal" \
+ "${HOME}/.config/" "b2://${bucket}/${HOSTNAME}/config"
+
+ # Org
+ backup-b2 --delete --excludeDirRegex="email" \
+ "${HOME}/org/" "b2://${bucket}/${HOSTNAME}/org"
+
+ # Projects
+ backup-b2 --delete "${HOME}/projects/" "b2://${bucket}/${HOSTNAME}/projects"
+
+ # Backup targets.
+ for target in "${TARGETS[@]}"; do
+ local t
+ t="$(basename "${target}")"
+
+ if [[ ! -e "${target}" ]]; then
+ err "$0: failed to backup ${t}: not found"
+ continue
+ fi
+
+ backup-hosts "${target}" "${t}"
+ done
+}
+
+main "$@"
diff --git a/scripts/backup-mobile b/scripts/backup-mobile
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+readonly DCIM="${HOME}/storage/dcim/"
+readonly PICTURES="${HOME}/storage/pictures/"
+readonly DOWNLOADS="${HOME}/storage/downloads/"
+
+if [ "$(uname -o)" != "Android" ]; then
+ err "$0: not on mobile device"
+ exit 1
+fi
+
+host="$1"
+
+if [ -d "${DCIM}" ]; then
+ printf "backing up %s\n" "${DCIM}"
+
+ rsync -av --mkpath "${DCIM}" "dwrz@${host}:/home/dwrz/mobile/dcim/"
+ if [ "$?" -eq 0 ]; then
+ rm -rf "${DCIM}"
+ fi
+fi
+
+if [ -d "${PICTURES}" ]; then
+ printf "backing up %s\n" "${PICTURES}"
+
+ rsync -av --mkpath "${PICTURES}" "dwrz@${host}:/home/dwrz/mobile/pictures/"
+ if [ "$?" -eq 0 ]; then
+ rm -rf "${PICTURES}"
+ fi
+fi
+
+if [ -d "${DOWNLOADS}" ]; then
+ printf "backing up %s\n" "${DOWNLOADS}"
+
+ rsync -av --mkpath "${DOWNLOADS}" "dwrz@${host}:/home/dwrz/mobile/downloads/"
+ if [ "$?" -eq 0 ]; then
+ rm -rf "${DOWNLOADS}"
+ fi
+fi
diff --git a/scripts/borg-backup b/scripts/borg-backup
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+case "$1" in
+ "close")
+ doas umount /mnt/archive
+ doas cryptsetup luksClose archive
+ ;;
+
+ "create")
+ name="dwrz@main.dwrz.net-$(TZ=UTC date '+%FT%T%z')"
+ borg create -v --progress --exclude /home/dwrz/.cache/ \
+ /mnt/archive/dwrz-backup/::"${name}" /home/dwrz/
+ ;;
+
+ "list") borg list /mnt/archive/dwrz-backup/ ;;
+
+ "mount") borg mount /mnt/archive/dwrz-backup/ \
+ /mnt/archive/mnt
+ ;;
+
+ "open")
+ doas cryptsetup luksOpen /dev/sda1 archive
+ doas mount /dev/mapper/archive /mnt/archive/
+ ;;
+
+ "prune") borg prune --keep-last 1 -m 12 --save-space \
+ /mnt/archive/dwrz-backup/
+ borg compact /mnt/archive/dwrz-backup/
+ ;;
+
+ "unmount") borg umount /mnt/archive/mnt ;;
+
+ *) err "$0: unrecognized command: $1" ;;
+esac
diff --git a/scripts/cameras b/scripts/cameras
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+cameras=(
+ "https://$(pass kitchen.516.dwrz.net/ip):\
+$(pass kitchen.516.dwrz.net/motion/port)/0"
+ "https://$(pass living.516.dwrz.net/ip):\
+$(pass living.516.dwrz.net/motion/port)/0"
+ "https://$(pass bedroom.516.dwrz.net/ip):\
+$(pass bedroom.516.dwrz.net/motion/port)/0"
+)
+
+auth=(
+ "$(pass kitchen.516.dwrz.net/motion/user):\
+$(pass kitchen.516.dwrz.net/motion/pw)"
+ "$(pass living.516.dwrz.net/motion/user):\
+$(pass living.516.dwrz.net/motion/pw)"
+ "$(pass bedroom.516.dwrz.net/motion/user):\
+$(pass bedroom.516.dwrz.net/motion/pw)"
+)
+
+url="detection/status"
+
+main() {
+ case "$1" in
+ "capture"|"c") url="detection/snapshot" ;;
+ "pause"|"p") url="detection/pause" ;;
+ "quit"|"q") url="action/quit" ;;
+ "restart"|"r") url="action/restart" ;;
+ "start"|"s") url="detection/start" ;;
+ "status"|"") url="detection/status" ;;
+ *) err "unrecognized command: $1"; exit 1
+ esac
+
+ for i in "${!cameras[@]}"; do
+ curl --digest --insecure --user "${auth[i]}" "${cameras[i]}/${url}"
+ done
+}
+
+main "$@"
diff --git a/scripts/dict b/scripts/dict
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+if [ -z "$1" ]; then
+ sdcv --utf8-output --color
+else
+ sdcv --non-interactive --utf8-output --color "$@" 2>&1 | \
+ fold --width=80 --spaces | \
+ less -FRX
+fi
diff --git a/scripts/err b/scripts/err
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+time="$(date -u +'%Y-%m-%dT%H:%M:%S%:z')"
+
+echo "[${time}]: $*" >&2
+
+if [ -x "$(command -v notify-send)" ]; then
+ notify-send --urgency=critical "ERROR" "$*"
+fi
diff --git a/scripts/generate-pin b/scripts/generate-pin
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+digits="$1"
+shuf --random-source=/dev/urandom -i 0-9 -r -n "${digits}" | \
+ paste -sd '' | \
+ tee /dev/tty | \
+ xclip
diff --git a/scripts/gps b/scripts/gps
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+modem="$(mmcli --list-modems | basename "$(awk -F ' ' '{print $1}')")"
+
+mmcli -m "${modem}" --enable
+
+case "$1" in
+ "disable") mmcli -m "${modem}" --location-disable-gps-raw ;;
+
+ "enable") mmcli -m "${modem}" --location-enable-gps-raw ;;
+
+ "get") mmcli --location-get -m "${modem}" ;;
+
+ *) err "unrecognized command: $1" ;;
+esac
diff --git a/scripts/image b/scripts/image
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+compress-jpg() {
+ local resolution="$1"
+ local quality="$2"
+
+ for p in *.jpg; do
+ name="${p%.*}"
+
+ convert -strip \
+ -resize "${resolution}" \
+ -quality "${quality}" \
+ "${p}" \
+ "./${name}-${resolution}.jpg"
+ done
+}
+
+compress-tiff() {
+ find . -iname "*.tif" -exec mogrify -verbose -compress zip {} +
+}
+
+main() {
+ local cmd="$1"; shift
+ case "${cmd}" in
+ "compress") compress-jpg "$@" ;;
+ esac
+}
+
+main "$@"
diff --git a/scripts/ip b/scripts/ip
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+echo "GOOGLE"
+dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com
+dig -6 TXT +short o-o.myaddr.l.google.com @ns1.google.com
+
+echo "OPENDNS"
+dig A +short myip.opendns.com @resolver1.opendns.com
+dig AAAA +short myip.opendns.com @resolver1.opendns.com
diff --git a/scripts/map-bg b/scripts/map-bg
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+dimensions="$(xrandr --current | grep '*' | uniq | awk '{print $1}')"
+height="$(echo $dimensions | cut -d 'x' -f2)"
+width="$(echo $dimensions | cut -d 'x' -f1)"
+
+coordinates="$1"
+zoom="$2"
+
+# If no coordinates were provided, take them from the password store.
+if [[ -z "$1" ]]; then
+ dir="${PASSWORD_STORE_DIR}/coordinates"
+ locations="$(find "${dir}" -mindepth 1 -exec basename -- {} .gpg \;)"
+ location="$(echo "${locations}" | shuf -n 1)"
+ coordinates="$(pass coordinates/"${location}")"
+fi
+
+# If no zoom was provided, use a number between 8 and 18, inclusive.
+if [[ -z "$2" ]]; then
+ zoom="$(shuf -i 8-18 -n 1)"
+fi
+
+# Create the map and store it in the maps directory.
+# If we fail, use an existing map.
+create-static-map \
+ --width "${width}" --height "${height}" \
+ -o "${HOME}/.cache/map-${coordinates}-${zoom}.png" \
+ -c "${coordinates}" -z "${zoom}" && \
+ feh --bg-fill "${HOME}/.cache/map-${coordinates}-${zoom}.png"
+if [[ "$?" -ne 0 ]]; then
+ feh --bg-fill --randomize "/home/dwrz/archive/images/maps/"*
+fi
diff --git a/scripts/mfa b/scripts/mfa
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+service="$1"
+if [ -z "${service}" ]; then
+ pass mfa
+ exit 0
+fi
+
+code="$(pass mfa/"${service}")"
+if [ -z "${code}" ]; then
+ err "unrecognized service: ${service}"
+ exit 1
+fi
+
+oathtool -b --totp "${code}" | tee /dev/tty | xclip
diff --git a/scripts/modname b/scripts/modname
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+for f in *.jpg; do
+ mv -n "$f" "$(date -r "$f" +"%Y%m%dT%H%M%S").jpg"
+done
diff --git a/scripts/monitor-hotplug b/scripts/monitor-hotplug
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+export DISPLAY=:0.0
+
+connect() {
+ xrandr --output eDP-1 --off --output DP-3 --auto && \
+ notify-send "Connected external monitor."
+}
+
+disconnect() {
+ xrandr --output eDP-1 --auto --output DP-3 --off && \
+ notify-send "Disconnected external monitor."
+}
+
+main() {
+ if xrandr | grep -q "DP-3 connected"; then
+ connect
+ else
+ disconnect
+ fi
+
+ feh --bg-fill --randomize "/home/dwrz/archive/images/maps/*"
+ xmodmap "${HOME}/.config/X11/xmodmap"
+}
+
+main "$@"
diff --git a/scripts/monitor-light b/scripts/monitor-light
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+doas ddcutil getvcp 10 | grep "current value"
+doas ddcutil setvcp 10 "$1" > /dev/null 2>&1
diff --git a/scripts/pkg_update b/scripts/pkg_update
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+if [ -x "$(command -v pacman)" ]; then
+ doas pacman -Syu
+ doas pacman -Qtdq | pacman -Rns -
+ doas paccache -r
+ doas paccache -ruk0
+fi
diff --git a/scripts/reveille b/scripts/reveille
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+readonly DAY_SECONDS=86400
+
+# TODO: replace this constant with an API request.
+# https://www.ssa.gov/cgi-bin/longevity.cgi
+readonly ESTIMATED_REMAINING_YEARS=50
+readonly ESTIMATED_REMAINING_DAYS="$(( "${ESTIMATED_REMAINING_YEARS}" * 365))"
+
+age() {
+ local start, end
+
+ start="$(date -d "$1" +%s)"
+ end="$(date -d "$2" +%s)"
+
+ echo $((("${end}" - "${start}") / "${DAY_SECONDS}"))
+}
+
+birthday() {
+ local year
+ local month
+ local day
+
+ year="$(pass dwrz/birth-year)"
+ month="$(pass dwrz/birth-month)"
+ day="$(pass dwrz/birth-day)"
+
+ echo "${year}-${month}-${day}"
+}
+
+draft_message() {
+ email="/tmp/reveille-$(date +%s)"
+ current_age_days="$(age "$(birthday)" "$(date '+%Y-%m-%d')" )"
+
+ cat > "$email" << EOF
+
+You have been on Earth, in this form, for $(numfmt --grouping \
+"${current_age_days}") days.
+
+You have $(numfmt --grouping ${ESTIMATED_REMAINING_DAYS}) days left. \
+You might have more.
+Or today could be your last day.
+
+Make the most of the time you have.
+
+Remember:
+- If you have the essentials, you have the foundations for happiness.
+- You are surrounded by people largely like youself: imperfect and
+ evanescent. Treasure them; they, like you, may not be around for much
+ longer.
+- People have invested in you -- with time, energy, money, lessons, and
+ perspectives. Living things have given their life to sustain you. You
+ owe the world the best version of yourself.
+- The ego does not always act in its best interests.
+- Beware the sirens' song.
+- No plan ever survives initial contact with reality.
+- Fatigue does not equal fitness.
+- Slow is smooth, and smooth is fast.
+
+QOTD:
+
+$("${HOME}/.local/bin/wisdom")
+
+EOF
+
+ echo "${email}"
+}
+
+main() {
+ mail -r "$(pass dwrz/email)" \
+ -s "$(date '+%Y-%m-%d %j/365 %W/52 %u/7')" \
+ "$(pass dwrz/email)" < "$(draft_message)"
+}
+
+main "$@"
diff --git a/scripts/screenshot b/scripts/screenshot
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+sleep 2;
+
+maim -s > screenshot-"$(date '+%Y-%m-%dT%H:%M:%S%z')".jpg
diff --git a/scripts/search b/scripts/search
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+engine="$1"; shift;
+case "$engine" in
+ "ecosia"|"e") firefox "https://www.ecosia.org/search?q=$*" ;;
+ "google"|"g") firefox "https://www.google.com/search?q=$*" ;;
+ "maps"|"m") firefox "https://maps.google.com/maps?q=$*" ;;
+ "wikipedia"|"w") firefox "https://en.wikipedia.org/wiki/$*" ;;
+ *) err "unrecognized engine: %{engine}" ;;
+esac
diff --git a/scripts/snapshot b/scripts/snapshot
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+device="/dev/video0"
+if [[ $1 != "" ]]; then
+ device="${/dev/video$1}"
+fi
+
+date="$(date -u +'%Y-%m-%dT%H:%M:%S%:z')"
+
+ffmpeg -f video4linux2 \
+ -i "$device" \
+ -vframes 1 \
+ "snapshot-${date}.jpg"
diff --git a/scripts/sync-email b/scripts/sync-email
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+err() {
+ echo "[$(date -u +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
+}
+
+main() {
+ # Sync email.
+ if ! mbsync -c "${XDG_CONFIG_HOME}/isync/mbsyncrc" -qqa ; then
+ notify-send --urgency=low "sync-email" "failed to sync email"
+ fi
+
+ # Delete emails tagged as deleted or spam.
+ notmuch search --output=files --format=text0 tag:deleted tag:spam | \
+ xargs -r0 rm
+
+ # Import new email into notmuch.
+ notmuch new
+
+ # Tag emails.
+ tag-email
+
+ # Notify unread count.
+ unread="$(notmuch count tag:unread)"
+ if [[ "${unread}" -gt 0 ]]; then
+ notify-send "Email" "${unread} unread email(s)."
+ fi
+}
+
+main "$@"
diff --git a/scripts/uncommitted b/scripts/uncommitted
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+# How many minutes to wait before displaying a warning.
+readonly WARNING_MINUTES=15
+
+readonly repos=(
+ "$HOME/.config/"
+ "$HOME/journal/2022/"
+ "$HOME/org/"
+ "$HOME/projects/src/"
+)
+
+check_repo() {
+ local d="$1"
+
+ # Change into the directory.
+ cd "${d}" || exit 1
+
+ # Check for uncommitted changes.
+ git update-index --refresh > /dev/null 2>&1
+ if git diff-index --quiet HEAD -- ; then
+ # OK; nothing to commit.
+ return
+ fi
+
+ # If changes exist, check their age relative to the last commit.
+ last_commit_time=$(git log -1 --date=unix --format=%cd)
+ current_time=$(date +%s)
+ elapsed_sec=$(("${current_time}" - "${last_commit_time}"))
+ elapsed_min=$(("${elapsed_sec}" / 60))
+
+ # Ignore changes that aren't old enough to warrant a warning.
+ if ! [[ "${elapsed_min}" -ge "${WARNING_MINUTES}" ]]; then
+ return
+ fi
+
+ notify-send --urgency=low "$d" \
+ "${elapsed_min} minutes since the last commit."
+}
+
+main() {
+ for d in "${repos[@]}"; do
+ if ! [[ -d "$d" ]]; then
+ err "$0: ${d} is not a directory"
+ continue
+ fi
+ if ! [[ -d "$d/.git/" ]]; then
+ err "$0: %{d} is not a git repo; ignoring"
+ continue
+ fi
+
+ check_repo "${d}"
+
+ done
+}
+
+main "$@"
diff --git a/scripts/unixify b/scripts/unixify
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+find . -type f -print0 | xargs -0 -n 1 -P 4 dos2unix
diff --git a/scripts/video b/scripts/video
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+
+compress() {
+ local filetype crf
+ filetype="$(printf "*.%s" "$1")"
+ # "The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default,
+ # and 51 is worst quality possible. A lower value generally leads to higher
+ # quality, and a subjectively sane range is 17–28. Consider 17 or 18 to be
+ # visually lossless or nearly so; it should look the same or nearly the same
+ # as the input but it isn't technically lossless."
+ crf="$2"
+ if [[ -z "$crf" ]]; then
+ crf=32 # Use a default CRF of 32.
+ fi
+
+ mkdir compressed
+
+ for v in ${filetype}; do
+ ffmpeg -i "file:${v}" -vcodec libx264 -crf "${crf}" "./compressed/${v}";
+ done
+
+ notify-send "video: compress: done"
+}
+
+compress_x265() {
+ local filetype crf
+ filetype="$(printf "*.%s" "$1")"
+ # "The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default,
+ # and 51 is worst quality possible. A lower value generally leads to higher
+ # quality, and a subjectively sane range is 17–28. Consider 17 or 18 to be
+ # visually lossless or nearly so; it should look the same or nearly the same
+ # as the input but it isn't technically lossless."
+ crf="$2"
+ if [[ -z "$crf" ]]; then
+ crf=23 # Use a default CRF of 23.
+ fi
+
+ mkdir compressed
+
+ for v in ${filetype}; do
+ ffmpeg -i "file:${v}" \
+ -c:v libx265 \
+ -vtag hvc1 \
+ -crf "${crf}" \
+ -c:a copy \
+ "./compressed/${v}";
+ done
+
+ notify-send "video: compress-x265: done"
+}
+
+trim() {
+ input="$1"
+ output="${input%.*}-trim.${input#*.}"
+ start="$2" # 00:00:00
+ end="$3" # 00:00:00
+
+ ffmpeg -i "${input}" -ss "${start}" -to "${end}" "${output}"
+
+ notify-send "video: trim: done"
+}
+
+main() {
+ local cmd="$1"; shift
+
+ case "${cmd}" in
+ "compress") compress "$@" ;;
+ "compress-x265") compress_x265 "$@" ;;
+ "trim") trim "$@" ;;
+ *) err "unrecognized command: ${cmd}" ;;
+ esac
+}
+
+main "$@"
diff --git a/scripts/volume b/scripts/volume
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if [ -n "$1" ]; then
+ pactl set-sink-volume @DEFAULT_SINK@ "$1%"
+fi
+
+pactl get-sink-volume @DEFAULT_SINK@
+paplay /usr/share/sounds/woodenbeaver/audio-volume-change.ogg
diff --git a/scripts/webcam b/scripts/webcam
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+n="$1"
+if [ -z "${n}" ]; then
+ n="0"
+fi
+
+mpv av://v4l2:/dev/video"${n}"
diff --git a/scripts/wisdom b/scripts/wisdom
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+fortune -a "$HOME"/archive/library/wisdom/*.fortune