Bryan Roessler 1 year ago
parent
commit
8788779202

+ 34 - 0
git/hooks-post-receive-push-to-gh.sh

@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+"/var/lib/git/gogs/gogs" hook --config='/var/lib/git/gogs/conf/app.ini' post-receive
+
+GIT_DIR="$(pwd)"
+TMP_GIT_DIR="/tmp/${GIT_DIR##*/}"
+
+if [[ ! -d "${TMP_GIT_DIR}" ]]; then
+    git clone "${GIT_DIR}" "${TMP_GIT_DIR}"
+    cd "${TMP_GIT_DIR}" || exit
+else
+    cd "${TMP_GIT_DIR}" || exit
+    unset GIT_DIR
+    git fetch --all
+    git reset --hard origin/master
+    git pull
+fi
+
+for site in *.*/; do
+    deploy_dir="/var/www/${site}"
+    if [[ ! -d "$deploy_dir" ]]; then
+        mkdir -p "$deploy_dir"
+        chgrp -R "$deploy_dir"
+        chmod g+s "$deploy_dir"
+    fi
+    pushd "${site}" || exit $?
+    ruby -S bundle install --deployment
+    ruby -S bundle exec jekyll build --destination "$deploy_dir"
+    popd || exit $?
+done
+
+exit 0
+
+git push --mirror git@github.com:cryobry/www.git
+

+ 1 - 0
hartmanlab

@@ -0,0 +1 @@
+Subproject commit 2efeb4e8874c21536a24d980ba398633b7305574

File diff suppressed because it is too large
+ 2 - 0
jriver/JRiver Saved Expressions.txt


+ 1 - 0
jriver/New Albums Sync (auto) Rules.txt

@@ -0,0 +1 @@
+[Media Type]=[Audio] [New]=[1] ~sort=[Date Imported] ~a

+ 31 - 0
jriver/expressions.txt

@@ -0,0 +1,31 @@
+IfElse(
+  IsEqual([Media Type], Audio), 
+    If(IsEqual([Media Sub Type], Podcast), 
+      podcasts/Clean([Album],3),
+      music/Clean([Album Artist (auto)],3)/[[Year]] Clean([Album],3)),
+  IsEqual([Media Sub Type], Movie), 
+    movies/Clean([Name], 3),
+  IsEqual([Media Sub Type], TV Show), 
+    tv/Clean([Series],3)/Season PadNumber([Season], 2)
+)
+
+
+
+
+IfElse(
+  IsEqual([Media Type], Audio), 
+      If(IsEmpty([Disc #],1), 
+        1[Track #], 
+        [Disc #][Track #]) - Clean([Artist] - [Name],3),
+  IsEqual([Media Sub Type], Movie), 
+    Clean([Name],3) [[Year]],
+  IsEqual([Media Sub Type], TV Show), 
+    Clean([Series] - S[Season]E[Episode] - [Name],3)
+)
+
+
+
+
+Music\Clean([Album Artist (auto)],3)\[[Year]] Clean([Album],3)
+
+If(IsEmpty([Disc #],1), 1[Track #], [Disc #][Track #]) - Clean([Artist] - [Name],3)

+ 49 - 0
jriver/jriver-fix-date-imported.py

@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+from pathlib import PureWindowsPath
+
+in_file = sys.argv[1]
+out_file = sys.argv[2]
+
+def getAlbumPath(line):
+    filename = line.lstrip('<Field Name="Filename">')
+    filename = filename.rstrip('</Field>\n')
+    path = PureWindowsPath(filename)
+    path = path.parents[0]
+    return str(path)
+
+def getDate(line):
+    date = line.lstrip('<Field Name="Date Imported">')
+    date = date.rstrip('</Field>\n')
+    return int(date)
+
+
+f = open(in_file, "r")
+lines = f.readlines()
+f.close()
+
+albums = {}
+for lnum,line in enumerate(lines):
+    if '<Field Name="Filename">' in line:
+        album = getAlbumPath(line)
+    elif '<Field Name="Date Imported">' in line:
+        date = getDate(line)
+        if album in albums:
+            albums[album].append((lnum, date))
+        else:
+            albums[album] = [(lnum, date)]
+
+earliest = {}
+for album in albums:
+    tracks = albums[album]
+    earliest_date = min(tracks, key = lambda t: t[1])[1]
+    for track in tracks:
+        lines[track[0]] = f'<Field Name="Date Imported">{earliest_date}</Field>\n'
+
+f = open(out_file, 'w')
+f_string = "".join(lines)
+f.write(f_string)
+f.close()
+exit()

+ 37 - 0
jriver/jriver-replace-date-imported.py

@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+
+in_file = sys.argv[1]
+out_file = sys.argv[2]
+
+def getImportDate(line):
+    date = line.lstrip('<Field Name="Date Imported">')
+    date = date.rstrip('</Field>\n')
+    return int(date)
+
+def getCreateDate(line):
+    date = line.lstrip('<Field Name="Date Modified">')
+    date = date.rstrip('</Field>\n')
+    return int(date)
+
+f = open(in_file, "r")
+lines = f.readlines()
+f.close()
+
+for lnum,line in enumerate(lines):
+    if '<Field Name="Date Imported">' in line:
+        import_date = getImportDate(line)
+        date_imported_line = lnum
+    elif '<Field Name="Date Modified">' in line:
+        create_date = getCreateDate(line)
+        if create_date < import_date:
+            print(import_date, create_date)
+            lines[date_imported_line] = f'<Field Name="Date Imported">{create_date}</Field>\n'    
+
+f = open(out_file, 'w')
+f_string = "".join(lines)
+f.write(f_string)
+f.close()
+exit()

+ 3 - 0
jriver/jriverexpressions.txt

@@ -0,0 +1,3 @@
+IfElse(IsEqual([Media Type], Audio), If(IsEqual([Media Sub Type], Podcast), podcasts/Clean([Album],3), music/RemoveCharacters(Clean([Album Artist (auto)],3),.,2)/[[Year]] RemoveCharacters(Clean([Album],3),.,3)), IsEqual([Media Sub Type], Movie), movies/Clean(RemoveCharacters([Name],:), 3) [[Year]], IsEqual([Media Sub Type], TV Show), tv/Clean([Series],3)/Season PadNumber([Season], 2))
+
+IfElse(IsEqual([Media Type], Audio), If(IsEmpty([Disc #],1), 1[Track #], [Disc #][Track #]) - RemoveCharacters(Clean([Artist] - [Name],3),.,3), IsEqual([Media Sub Type], Movie), Clean(RemoveCharacters([Name],:),3) [[Year]], IsEqual([Media Sub Type], TV Show), Clean([Series] - S[Season]E[Episode] - [Name],3))

+ 17 - 0
jriver/mediakeys.txt

@@ -0,0 +1,17 @@
+[Media Type]=[Audio] [Artist]=[Elton John] [Rating]=>=1 ~sort=Random
+
+MC Play/Pause:
+curl -s -o /dev/null -u Bryanhoop:Alonzo14 http://localhost:52199/MCWS/v1/Control/MCC?Command=10000
+
+MC Next Track:
+curl -s -o /dev/null -u Bryanhoop:Alonzo14 http://localhost:52199/MCWS/v1/Control/MCC?Command=10003
+
+MC Prev Track:
+curl -s -o /dev/null -u Bryanhoop:Alonzo14 http://localhost:52199/MCWS/v1/Control/MCC?Command=10004
+
+
+case $TERM in
+    xterm*)
+        precmd () {print -Pn "\e]0;${PWD/$HOME/\~}\a"}
+        ;;
+esac

+ 3 - 0
jriver/mediakeys2.txt

@@ -0,0 +1,3 @@
+Play/Pause: curl -s -o /dev/null -u Bryanhoop:Alonzo14 http://localhost:52199/MCWS/v1/Control/MCC?Command=10000
+Next: curl -s -o /dev/null -u Bryanhoop:Alonzo14 http://localhost:52199/MCWS/v1/Control/MCC?Command=10003
+Previous: curl -s -o /dev/null -u Bryanhoop:Alonzo14 http://localhost:52199/MCWS/v1/Control/MCC?Command=10004

+ 37 - 0
python/remove-music-dirs-with-just-image

@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+from pathlib import PureWindowsPath
+
+path = sys.argv[1]
+
+def getImportDate(line):
+    date = line.lstrip('<Field Name="Date Imported">')
+    date = date.rstrip('</Field>\n')
+    return int(date)
+
+def getCreateDate(line):
+    date = line.lstrip('<Field Name="Date Created">')
+    date = date.rstrip('</Field>\n')
+    return int(date)
+
+f = open(in_file, "r")
+lines = f.readlines()
+f.close()
+
+for lnum,line in enumerate(lines):
+    if '<Field Name="Date Imported">' in line:
+        import_date = getImportDate(line)
+        date_imported_line = lnum
+    elif '<Field Name="Date Created">' in line:
+        create_date = getCreateDate(line)
+        if create_date < import_date:
+            print(import_date, create_date)
+            lines[date_imported_line] = f'<Field Name="Date Imported">{create_date}</Field>\n'    
+
+f = open(out_file, 'w')
+f_string = "".join(lines)
+f.write(f_string)
+f.close()
+exit()

+ 50 - 0
python/sabnzbd-check-access-patch

@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+def check_access(access_type=4):
+    """Check if external address is allowed given access_type:
+    1=nzb
+    2=api
+    3=full_api
+    4=webui
+    5=webui with login for external
+    """
+    referrer = cherrypy.request.remote.ip
+
+    # CherryPy will report ::ffff:192.168.0.10 on dual-stack situation
+    # It will always contain that ::ffff: prefix
+
+    # Standardize input
+
+    for r in cfg.local_ranges():
+        if "/" in r:
+            prefix, suffix = r.split("/")
+            
+
+    def cidr_to_netmask(cidr):
+        cidr = int(cidr)
+        mask = (0xffffffff >> (32 - cidr)) << (32 - cidr)
+        return (str( (0xff000000 & mask) >> 24)   + '.' +
+                str( (0x00ff0000 & mask) >> 16)   + '.' +
+                str( (0x0000ff00 & mask) >> 8)    + '.' +
+                str( (0x000000ff & mask)))
+            
+
+
+    range_ok = not cfg.local_ranges() or bool(
+        [1 for r in cfg.local_ranges() if (referrer.startswith(r) or referrer.replace("::ffff:", "").startswith(r))]
+    )
+    allowed = referrer in ("127.0.0.1", "::ffff:127.0.0.1", "::1") or range_ok or access_type <= cfg.inet_exposure()
+    if not allowed:
+        logging.debug("Refused connection from %s", referrer)
+    return allowed
+
+
+def convert_cidr(ip,mask):
+    network = ''
+    iOctets = ip.split('.')
+    mOctets = mask.split('.')
+    network = str( int( iOctets[0] ) & int(mOctets[0] ) ) + '.'
+    network += str( int( iOctets[1] ) & int(mOctets[1] ) ) + '.'
+    network += str( int( iOctets[2] ) & int(mOctets[2] ) ) + '.'
+    network += str( int( iOctets[3] ) & int(mOctets[3] ) )
+    return network

+ 69 - 0
shell/btrbk-archive-rsync

@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Copyright 2023 Bryan C. Roessler
+#
+# This script replaces btrfs send|receive with rsync for resumable btrbk backups
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+TESTING=1
+
+main() {
+
+  # The source subvolume to backup
+  SRC_SUBVOLUME="$1"
+  # The location of the intermediate btrfs image file
+  # This must be at least the size of SRC (including snapshots) and ideally on fast storage
+  SRC_TMPDIR="$2"
+  # The remote destination
+  DEST_HOST="$3"
+  DEST_TMPDIR="$4"
+  DEST_DIR="$5"
+
+  # Get rid of garbled backups
+  sudo btrbk clean
+
+  # Create raw archive file
+  # TODO Encryption/compression
+  sudo btrbk archive "$SRC_SUBVOLUME" "$SRC_TMPDIR" --raw
+
+  # Retern the latest image
+  # "<snapshot-name>.<timestamp>[_N].btrfs[.gz|.bz2|.xz][.gpg]"
+  #SRC_TMPFILENAME=$(find "$SRC_TMPDIR" -type f -regex '.*/*.btrfs' -printf "%f\n" | tail -1)
+  SRC_FILEPATH="$SRC_TMPDIR/$SRC_FILENAME"
+  DEST_TMPDIR="$DEST_TMPDIR/${SRC_FILENAME%%'.btrfs'}"
+  DEST="$DEST_DIR/${SRC_FILENAME%%'.btrfs'}"
+
+  # shellcheck disable=SC2029
+  sudo rsync \
+    --append-verify \
+    --remove-source-files \
+    "$SRC_FILEPATH" "$DEST:$DEST_TMP" &&
+    ssh "$DEST_HOST" "
+      btrfs receive --verbose --progress -f $DEST_TMP $DEST &&
+      rm -f $DEST_TMPFILE
+    "
+}
+
+# For testing
+if (( TESTING )); then
+  main "${1:"/home/bryan"}" \
+    "${2:"/mnt/temp"}" \
+    "${3:"router"}" \
+    "${4:"/mnt/backup"}" \
+    "${5:"/mnt/backup/workstation/home"}" # TODO Need a separate SSD :-/ # i.e. root@router.lan from ~/.ssh/config
+else
+  # For deploy
+  main "$@"
+fi
+
+exit

+ 30 - 0
shell/build-remote

@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+#
+#
+#
+
+#REMOTE_HOST="vm-fedora33"
+
+# Manual
+
+#BUILD_DIR="$PWD"
+#REMOTE_COMMAND="cd $PWD && ./script.sh"
+
+# VSCode
+#REMOTE_HOST="vm-fedora33"
+#BUILD_DIR=${fileWorkspaceFolder}
+#REMOTE_CMD="cd ${fileWorkspaceFolder} && chmod +x ${file} && ${file}"
+
+
+# Copy local directory "$2" to remote host "$1" and run the remaining commands 
+ssh "$1" mkdir -p "$2"
+# Maybe use --delete for a cleaner dev env
+rsync -a "$2/" "$1":"$2"
+# We want this to expand on the client so we can pass our arguments to the remote
+# shellcheck disable=SC2029
+ssh "$1" "${@:2}"
+
+
+
+# exec_remote "$REMOTE_HOST" "$BUILD_DIR" "$REMOTE_CMD"
+#exec_remote vm-fedora33 ${fileWorkspaceFolder} cd ${fileWorkspaceFolder} && chmod +x ${file} && ${file}

+ 3 - 0
shell/container-home-assistant

@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+podman run -d --name="home-assistant" -v ~/.config/home-assistant:/config -v /etc/localtime:/etc/localtime:ro --net=host docker.io/homeassistant/home-assistant:stable && podman generate systemd --name "home-assistant" --container-prefix "" --separator "" > ~/.config/systemd/user/home-assistant.service && systemctl --user daemon-reload && systemctl --user enable --now home-assistant

+ 3 - 0
shell/create-home

@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+sudo homectl --storage=subvolume --real-name=Bryan --member-of=wheel --shell=/usr/bin/zsh --timezone=America/New_York -P --auto-login=on create bryan

+ 9 - 0
shell/download-torrent

@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+#
+# I develop on a laptop and prefer to send torrent files (linux isos, etc.) to my desktop
+#
+# I set .torrent links to open this script by default in firefox
+#
+
+mv "$1" "$HOME/documents/torrents/"
+chmod +rw "$HOME/documents/torrents/"*

+ 29 - 0
shell/extract

@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# This script/function will unzip most filetypes automatically
+
+extract () {
+	if [ -f "$1" ] ; then
+		case "$1" in
+		*.tar.bz2) tar xjf "$1" ;;
+		*.tar.gz) tar xzf "$1" ;;
+		*.bz2) bunzip2 "$1" ;;
+		*.rar) unrar e "$1" ;;
+		*.gz) gunzip "$1" ;;
+		*.tar) tar xf "$1" ;;
+		*.tbz2) tar xjf "$1" ;;
+		*.tgz) tar xzf "$1" ;;
+		*.zip) unzip "$1" ;;
+		*.Z) uncompress "$1" ;;
+		*.7z) 7z x "$1" ;;
+		*) echo "$1 cannot be extracted via extract()" ;;
+		esac
+	else
+		echo "$1 is not a valid file"
+	fi
+}
+
+# Allow script to be safely sourced
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+    extract "$@"
+    exit $?
+fi

+ 261 - 0
shell/fedora-enable-hibernate

@@ -0,0 +1,261 @@
+#!/usr/bin/env bash
+# This program will create a hibernation-ready swapfile in a btrfs subvolume and enable hibernation
+# Copyright Bryan C. Roessler 2021
+# This software is released under the Apache License.
+# https://www.apache.org/licenses/LICENSE-2.0
+# Tested on: Fedora 34
+
+VERBOSE=true
+function init() {
+
+    SWAP_FILE=${SWAP_FILE:-"/.swap/swapfile"}
+
+    [[ "$#" -lt 1 ]] && return 0
+
+    if input=$(getopt -s v -l image-size:,swap-size:,swap-file:,device-uuid:,verbose -- "$@"); then
+        eval set -- "$input"
+        while true; do
+            case "$1" in
+                --image-size)
+                    shift && IMAGE_SIZE="$1"
+                    ;;
+                --swap-size)
+                    shift && SWAP_SIZE="$1"
+                    ;;
+                --swap-file)
+                    declare -x SWAP_FILE
+                    shift && SWAP_FILE="$1"
+                    ;;
+                --device-uuid)
+                    shift && DEVICE_UUID="$1"
+                    ;;
+                --verbose|v)
+                    VERBOSE=true
+                    ;;
+                --)
+                    shift
+                    break
+                    ;;
+            esac
+            shift
+        done
+    else
+        echo "Incorrect option provided"
+        exit 1
+    fi
+}
+
+
+function debug() {
+    [[ "$VERBOSE" == true ]] && echo "$@"
+}
+
+
+function get_mem_size() {
+    declare -ig MEM_SIZE MEM_SIZE_GB
+    MEM_SIZE=$(free -b | grep "Mem:" | tr -s ' ' | cut -d" " -f2)
+    MEM_SIZE_GB=$(numfmt --from=iec --to-unit=G "$MEM_SIZE")
+    debug "MEM_SIZE_GB: ${MEM_SIZE_GB}"
+}
+
+
+#######################################
+# Get user y/N/iec response
+# Arguments:
+#   1. Text to display
+#   2. Global variable name to store response
+#######################################
+function response_iec() {
+    declare _response _response_l _response_u
+    read -r -p "$* [y/N/iec]: " _response
+    _response_l="${_response,,}"
+    _response_u="${_response^^}"
+    [[ "$_response_l" =~ ^(yes|y) ]] && return 0
+    if [[ "$_response" =~ ^([[:digit:]]+[[:alnum:]])$ || "$_response" =~ ^([[:digit:]]+)$ ]]; then
+        if ! declare -gi "$2"="$(numfmt --from=iec "$_response_u")"; then
+            echo "$_response is not a valid IEC value (ex. 42 512K 10M 7G 3.5T)"
+            return 1
+        else
+            declare -gi "$2"_GB="$(numfmt --from=iec --to-unit=Gi "$_response_u")"
+            return 0
+        fi
+    fi
+    exit 1
+}
+
+
+function set_swap_size() {
+    declare -gi SWAP_SIZE_GB
+    declare _response _response_l
+
+    # By default make the swapfile size the same as the system RAM
+    # This is the safest approach at the expense of disk space
+    # You can improve hibernation speed by tweaking $IMAGE_SIZE instead.
+    [[ ! -v SWAP_SIZE ]] && SWAP_SIZE=$MEM_SIZE
+
+    #SWAP_SIZE_MB=$(numfmt --from=iec --to-unit=Mi "$SWAP_SIZE")
+    SWAP_SIZE_GB=$(numfmt --from=iec  --to-unit=G "$SWAP_SIZE")
+    
+    # TODO incrementally use increasing IEC nominators 
+    echo "Ideal swapfile size is equal to system RAM: ${MEM_SIZE_GB}G"
+    
+    # TODO See above, also for image size
+    until response_iec "Set swapfile size to ${SWAP_SIZE_GB}G?" "SWAP_SIZE"; do
+        echo "Retrying..."
+    done
+    debug "SWAP_SIZE_GB: ${SWAP_SIZE_GB}G"
+}
+
+
+function set_image_size() {
+    declare _response _response_l
+    declare -i mem_size_target current_image_size current_image_size_gb
+
+    # Set ideal image size
+    if [[ ! -v IMAGE_SIZE ]]; then
+        # Sensible default (greater of 1/4 swapfile or ram size)
+        mem_size_target=$(( MEM_SIZE / 4 ))
+        swap_size_target=$(( SWAP_SIZE / 4 ))
+        IMAGE_SIZE=$(( mem_size_target > swap_size_target ? mem_size_target : swap_size_target ))
+        IMAGE_SIZE_GB=$(numfmt --from=iec --to-unit=G "$IMAGE_SIZE")
+    fi
+
+    current_image_size=$(cat /sys/power/image_size)
+    current_image_size_gb=$(numfmt --from=iec --to-unit=G "$current_image_size")
+  
+    echo "Ideal image target size is 1/4 of the RAM or swapfile size"
+    echo "Swapfile size: ${SWAP_SIZE_GB}G"
+    echo "RAM size: ${MEM_SIZE_GB}G"
+    until response_iec "Resize /sys/power/image_size from ${current_image_size_gb}G to ${IMAGE_SIZE_GB}G?" "IMAGE_SIZE"; do
+        echo "Retrying..."
+    done
+
+    debug "IMAGE_SIZE_GB: ${IMAGE_SIZE_GB}G"
+}
+
+
+function write_image_size() {
+    echo "$IMAGE_SIZE_GB" | sudo tee /sys/power/image_size
+}
+
+
+function make_swapfile () {
+    declare parent_dir
+    parent_dir=$(dirname "$SWAP_FILE")
+    ! [[ -d "$parent_dir" ]] && \
+        if ! sudo btrfs sub create "$parent_dir"; then
+            echo "Could not create a btrfs subvolume at $parent_dir"
+            echo "Please check your permissions/filesystem"
+            exit 1
+        fi
+    sudo touch "$SWAP_FILE"
+    #sudo truncate -s 0 "$SWAP_FILE"
+    sudo chattr +C "$SWAP_FILE"
+    sudo fallocate --length "$SWAP_SIZE" "$SWAP_FILE"
+    #sudo btrfs property set "$SWAP_FILE" compression none
+    #sudo dd if=/dev/zero of="$SWAP_FILE" bs=1M count="$SWAP_SIZE_MB" status=progress
+    sudo chmod 600 "$SWAP_FILE"
+    sudo mkswap "$SWAP_FILE"
+    #sudo swapon "$SWAP_FILE"
+}
+
+
+function add_to_dracut() {
+    echo 'add_dracutmodules+=" resume "' | sudo tee /etc/dracut.conf.d/50-resume.conf
+    sudo dracut -f
+}
+
+
+# function add_to_fstab() {
+#     echo "Backing up /etc/fstab to /tmp/fstab.bk"
+#     cp /etc/fstab /tmp/fstab.bk
+#     echo "$SWAP_FILE	none	swap	sw 0 0" | sudo tee -a /etc/fstab
+# }
+
+
+function get_offset() {
+    physical_offset=$(./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9)
+    debug "Physical offset: $physical_offset"
+}
+
+
+function get_device() {
+    if ! [[ -v DEVICE_UUID ]]; then
+        DEVICE_UUID=$(findmnt -no UUID -T "$SWAP_FILE")
+        # if ! DEVICE_UUID=$(sudo blkid | grep /dev/mapper/luks | cut -d' ' -f3); then
+        #     echo "Guessing device UUID failed. Please specify a --device-uuid and rerun"
+        #     exit 1
+        # fi
+    fi
+    if [[ ! $DEVICE_UUID == ^UUID=* ]]; then
+        DEVICE_UUID="UUID=${DEVICE_UUID}"
+    fi
+    debug "Device UUID: $DEVICE_UUID"
+}
+
+
+function get_offset() {
+    declare -i physical_offset 
+
+    [[ -v RESUME_OFFSET ]] && return 0
+
+    RESUME_OFFSET=$(sudo filefrag -v "$SWAP_FILE" | head -n 4 | tail -n 1 | awk '{print $4}')
+    
+    # tempdir="$(mktemp --directory --tmpdir= enable-hibernate-XXXXXXXXX)"
+    # pushd "$tempdir" || return 1
+    # curl -O "https://raw.githubusercontent.com/osandov/osandov-linux/master/scripts/btrfs_map_physical.c"
+    # gcc -O2 -o btrfs_map_physical btrfs_map_physical.c
+    # page_size=$(getconf PAGESIZE)
+    # # ./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9
+    # sudo bash -c "export physical_offset=$(./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9);"
+    # echo "$physical_offset"
+    # popd || return 1
+    # rm -rf "$tempdir"
+    # RESUME_OFFSET=$(( physical_offset / page_size ))
+    debug "Resume offset: $RESUME_OFFSET"
+}
+
+
+function update_grub() {
+    echo "Backing up /etc/default/grub to /tmp/grub.bk"
+    cp /etc/default/grub /tmp/grub.bk
+    if grub_cmdline_linux=$(grep GRUB_CMDLINE_LINUX /etc/default/grub); then
+        if [[ $grub_cmdline_linux == *resume=* ]]; then
+            sudo sed -i "s/resume=.* /resume=${DEVICE_UUID} /" /etc/default/grub
+            sudo sed -i "s/resume_offset=.* /resume_offset=${RESUME_OFFSET} /" /etc/default/grub
+        else
+            sudo sed -i "s/GRUB_CMDLINE_LINUX_DEFAULT.*/& resume=${DEVICE_UUID} resume_offset=${RESUME_OFFSET}/" /etc/default/grub
+        fi
+    else
+        echo "Your /etc/default/grub cannot be edited automatically, please add: "
+        echo "resume=${DEVICE_UUID} resume_offset=${RESUME_OFFSET}"
+        echo "...to your kernel command line in /etc/default/grub,"
+        echo 'and run "sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg"'
+        exit 1
+    fi
+    
+    sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg
+}
+
+
+function live_apply() {
+    #declare devid
+    #devid=$(lsblk | grep luks |cut -d' ' -f4)
+    echo "${RESUME_OFFSET}" | sudo tee /sys/power/resume_offset
+}
+
+
+function main() {
+    init "$@" && \
+    get_mem_size && \
+    set_swap_size && \
+    set_image_size && \
+    make_swapfile && \
+    get_offset && \
+    add_to_dracut && \
+    update_grub && \
+    suggest_reboot
+}
+
+main "$@"
+exit $?

+ 11 - 0
shell/history-clean

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# This script will clean the history file of sensitive info
+#
+
+histfile="${1:-$HISTFILE:-$HOME/.histfile}"
+
+cp -a "$histfile" "/tmp/$histfile.bak"
+
+sed --in-place '/gpg/d' "$histfile"
+
+sed --in-place '/-----BEGIN PGP MESSAGE-----/,/-----END PGP MESSAGE-----/d' "$histfile"

+ 28 - 0
shell/install-codimd

@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# This script will install a codiMD container and associated systemd services on CentOS 7 using podman
+# Copyright 2022 Bryan C. Roessler
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+database_dir="/var/lib/postgresql/data"
+
+[[ ! -d $database_dir ]] && sudo mkdir -p "$database_dir"
+
+sudo chcon -t container_file_t -R "$database_dir"
+
+podman pod create codiMD
+podman run --name codidb --pod codiMD -p 3000:3000 -v database-data:/var/lib/postgresql/data docker.io/postgres:11.6-alpine --restart=always
+podman run --name codidb --pod codiMD -p 3000:3000 -v database-data:/var/lib/postgresql/data docker.io/hackmdio/hackmd:2.4.1 --restart=always
+
+
+podman generate systemd 

+ 74 - 0
shell/installpkg

@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+# Identify host OS and execute package manager install command on input args
+#
+
+installpkg() {
+
+	getOS() {
+
+		# Widely supported method to retrieve host $ID
+		if [[ -e /etc/os-release ]]; then
+		    source /etc/os-release
+		else
+		    echo "No /etc/os-release found!"
+		    echo "Your OS is unsupported!"
+		    return 1
+		fi
+	}
+
+
+	setCmdArr () {
+
+		declare -ga CMD_ARR
+
+		# Create OS-specific package install command arrays
+		if [[ "$ID" == "fedora" ]]; then
+			CMD_ARR=( "dnf" "install" "-y" )
+		elif [[ "$ID" == "centos" && "$VERSION_ID" -ge 8 ]]; then
+			CMD_ARR=( "dnf" "install" "-y" )
+		elif [[ "$ID" == "centos" && "$VERSION_ID" -le 7 ]]; then
+			CMD_ARR=( "yum" "install" "-y" )
+		elif [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
+			CMD_ARR=( "apt-get" "install" "-y" )
+		elif [[ "$ID" == "arch" ]]; then
+			CMD_ARR=( "pacman" "-Syu" )
+		else
+			echo "Your OS is currently unsupported! You are welcome to add your own and submit a PR."
+			return 1
+		fi
+
+		# Append sudo if not running as root
+		[[ "$(whoami)" != "root" ]] && CMD_ARR=( "sudo" "${CMD_ARR[@]}" )
+	}
+
+
+	installPackage() {
+
+		# Check for input arguments
+		if [[ "$#" -ge 1 ]]; then
+			if ! "${CMD_ARR[@]}" "$@"; then
+				echo "Installation failed!"
+				return 1
+			fi
+		else
+			echo "You must supply one or more packages to install!"
+			return 1
+		fi
+	}
+
+
+	main() {
+
+		getOS && \
+		setCmdArr && \
+		installPackage "$@" && \
+        unset CMD_ARR
+	}
+
+	main "$@"
+}
+
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+	installpkg "$@"
+	exit $?
+fi

+ 79 - 0
shell/prunefiles

@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+# prunefiles, a script/function to remove all but the n latest versions of a file
+#
+
+prunefiles () {
+
+    declare -ag PREFIXES
+    declare -g KEEP_INT=1 # default # of files to keep
+
+    printHelpAndExit () {
+
+    cat <<-'EOF'
+		USAGE:
+		    pruneFiles -k 3 thisfileprefix [thatfileprefix]
+
+		OPTIONS
+		    -k, --keep NUMBER
+		        Keep NUMBER of latest files that matches each file prefix (Default: 1)
+		    -h, --help
+		        Print this help dialog and exit
+	EOF
+
+    # Exit using passed exit code
+    [[ -z $1 ]] && exit 0 || exit "$1"
+
+    }
+
+    parseInput () {
+
+        if _input=$(getopt -o +hk: -l help,keep: -- "$@"); then
+            eval set -- "$_input"
+            while true; do
+                case "$1" in
+                    -k|--keep)
+                        shift && KEEP_INT=$1
+                        ;;
+                    -h|--help)
+                        printHelpAndExit 0
+                        ;;
+                    --)
+                        shift; break
+                        ;;
+                esac
+                shift
+            done
+        else
+            echo "Incorrect option(s) provided"
+            printHelpAndExit 1
+        fi
+
+        PREFIXES=( "$@" )
+    }
+
+
+    findAndRemove () {
+
+        declare prefix file
+
+        for prefix in "${PREFIXES[@]}"; do
+            for file in $(find . -maxdepth 1 -type f -name "${prefix}*" -printf '%T@ %p\n' | sort -r -z -n | tail -n+$((KEEP_INT + 1)) | awk '{ print $2; }'); do
+                rm "$file"
+            done
+        done
+    }
+
+
+    main () {
+        parseInput "$@"
+        findAndRemove
+    }
+
+    main "$@"
+}
+
+# Allow script to be safely sourced
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+    prunefiles "$@"
+    exit $?
+fi

+ 29 - 0
shell/random-word-pair

@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# This script will create a random word pair with an underscore ex. turtle_ladder
+
+random_word_pair() {
+    # Constants 
+    local random_words num_random_words random_1 random_2 word_1 word_2
+    random_words=/usr/share/dict/words
+    # total number of non-random words available 
+    num_random_words=$(wc -l $random_words | cut -d" " -f 1)
+    # Get two random integers
+    random_1=$(shuf -i 1-"$num_random_words" -n 1)
+    random_2=$(shuf -i 1-"$num_random_words" -n 1)
+    # Get the nth word
+    word_1=$(sed "${random_1}q;d" "$random_words")
+    word_2=$(sed "${random_2}q;d" "$random_words")
+    # Sanitize words
+    word_1="${word_1,,}"
+    word_1="${word_1//-/}"
+    word_2="${word_2,,}"
+    word_2="${word_2//-/}"
+    echo "${word_1,,}_${word_2,,}"
+    return 0
+}
+
+# Allow this file to be executed directly if not being sourced
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+    random_word_pair
+    exit $?
+fi

+ 13 - 0
shell/remove-small-dirs

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+[[ $# -eq 0 ]] && echo "You must provide a directory" && exit 1
+dir="$1"
+[[ $# -eq 2 ]] && SIZE="$2" || SIZE=1000 # set the default min dir size
+
+[[ ! -d "$dir" ]] && echo "Directory does not exist" && exit 1
+
+# Print dirs to be removed
+find "$dir" -mindepth 1 -type d -exec du -ks {} + | awk -v size="$SIZE" '$1 <= size' | cut -f 2-
+read -r -p "Is this OK? [y/N]" _response
+_response_l="${_response,,}"
+[[ ! "$_response_l" =~ ^(yes|y) ]] && echo "Exiting, no changes were made" && exit 0
+find "$dir" -mindepth 1 -type d -exec du -ks {} + | awk -v size="$SIZE" '$1 <= size' | cut -f 2- | xargs -d'\n' rm -rf

+ 29 - 0
shell/rename-gaussian

@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+find . -name "*.gau" -print | xargs sed -i 's/'nproc'/'NProcShared'/g'
+find . -name "*.gau" -print | xargs sed -i 's/'mem=1GB'/'mem=500MB'/g'
+
+find . -name "*.gau" -print | xargs sed -i 's/'nproc=1'/'NProcShared=8'/g'
+find . -name "*.gau" -print | xargs sed -i 's/'mem=1GB'/'mem=8GB'/g'
+
+MYFILES=*.gau
+for f in $MYFILES
+do
+    rung09 $f
+done
+
+for i in {716269..716309}
+do
+    canceljob $i
+done
+
+find . -name "*.gau" -print | xargs sed -i 's/'NProcShared'/'nproc'/g'
+find . -name "*.gau" -print | xargs sed -i 's/'mem=500MB'/'mem=1GB'/g'
+
+find . -name "*.gau" -print | xargs sed -i 's/'NProcShared=8'/'NProcShared=1'/g'
+find . -name "*.gau" -print | xargs sed -i 's/'mem=8GB'/'mem=500MB'/g'
+
+
+daytime
+find . -name "*.gau" -print | xargs sed -i 's/'NProcShared=1'/'NProcShared=2'/g'
+find . -name "*.gau" -print | xargs sed -i 's/'mem=500MB'/'mem=2GB'/g'

+ 40 - 0
shell/rsync-external-backup

@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+# TODO LEGACY: USE BTRBK
+
+ask_ok() {
+    read -r -p "$* [y/N]: " response
+    response=${response,,}
+    [[ ! "$response" =~ ^(yes|y)$ ]] && return 1
+    return 0
+}
+
+declare -a include_dirs=(
+    "$HOME/music"
+    "$HOME/documents"
+    "$HOME/downloads"
+    "$HOME/pictures"
+    "$HOME/ebooks"
+    "$HOME/devices"
+    "$HOME"/.*
+)
+declare -a exclude_dirs=(
+    "$HOME/.**"
+    "$HOME/..**"
+)
+declare destination="/run/media/bryan/4tb-external/$HOSTNAME"
+declare rsync_cmd="rsync -aAXL --info=progress2 --delete"
+
+for i in "${include_dirs[@]}"; do
+    rsync_cmd+=" --include $i**"
+done
+
+for i in "${exclude_dirs[@]}"; do
+    rsync_cmd+=" --exclude $i"
+done
+
+echo "Command to run: $rsync_cmd $destination"
+ask_ok "Continue?" || exit 1
+
+[[ ! -d "$destination" ]] && mkdir -p "$destination"
+
+eval "$rsync_cmd $destination"

+ 9 - 0
shell/send-torrent-to-htpc

@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+#
+# I develop on a laptop and prefer to send torrent files (linux isos, etc.) to my desktop
+#
+# I set .torrent links to open this script by default in firefox
+#
+
+mv "$1" "$HOME/Documents/torrents/"
+chmod +rw "$HOME/Documents/torrents/$1"

+ 43 - 0
shell/share-link

@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Nautilus script for creating one or more shared links
+# Requires wl-clipboard and notify-send 
+
+ssh_server="bryanroessler.com"
+ssh_files_path="/var/www/repos.bryanroessler.com/files"
+www_files_path="https://repos.bryanroessler.com/files"
+
+if [[ "$#" -lt 1 ]]; then
+	echo "You must provide at least one argument"
+	exit 1
+fi
+
+hash wl-copy &>/dev/null || echo "Please install wl-copy (usually in the wl-clipboard package)"
+
+if [[ -v NAUTILUS_SCRIPT_SELECTED_URIS ]]; then
+	readarray -t files <<< "$NAUTILUS_SCRIPT_SELECTED_URIS"
+	files=("${files[@]#file://}")
+	files=("${files[@]//\%20/ }")
+else
+	files=("$@")
+fi
+
+for file in "${files[@]}"; do
+	[[ "$file" == "" ]] && continue
+	echo here
+    fname="${file##*/}"
+    random64=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 64 | head -n 1)
+	echo "rsync -a ${file} ${ssh_server}:${ssh_files_path}/${random64}/"
+	nohup rsync -a "${file}" "${ssh_server}:${ssh_files_path}/${random64}/" &
+	links_array+=("$www_files_path/${random64}/${fname// /%20}")
+done
+
+if [[ "${#links_array[@]}" == 1 ]]; then
+	printf '%s' "${links_array[@]}" | wl-copy
+else
+	printf '%s\n' "${links_array[@]}" | wl-copy
+fi
+
+hash notify-send &>/dev/null &&
+	notify-send -t 3000 -i face-smile "share-link" "File(s) uploaded and link copied to clipboard"
+
+exit 0

+ 107 - 0
shell/smart-tests

@@ -0,0 +1,107 @@
+#!/usr/bin/env bash
+# This script will report S.M.A.R.T. stats and perform tests on all available disks
+# Copyright 2021 Bryan C. Roessler
+
+init() {
+
+    if [[ $EUID != 0 ]]; then
+        echo "Must run as root!" && exit 1
+    fi
+
+    [[ ! -x $(command -v smartctl) ]] && echo "smartctl not found! Please install smartmontools." && exit 1
+
+    if _input=$(getopt -o +slihatd -l short,long,info,health,all,temp,daemon,help -- "$@"); then
+        eval set -- "$_input"
+        while true; do
+            case "$1" in
+                --short|-s)
+                    SHORT="true"
+                    ;;
+                --long|-l)
+                    LONG="true"
+                    ;;
+                --info|-i)
+                    INFO="true"
+                    ;;
+                --health|-h)
+                    HEALTH="true"
+                    ;;
+                --all|-a)
+                    ALL="true"
+                    ;;
+                --temp|-t)
+                    TEMP="true"
+                    ;;
+                --daemon|-d)
+                    DAEMON="true"
+                    ;;                                                         
+                --help)
+                    printhelp
+                    ;;
+                --)
+                    shift
+                    break
+                    ;;
+            esac
+            shift
+        done
+    else
+        echo "Error: incorrect option provided" && printhelp && exit 1
+    fi
+}
+
+
+printhelp() {
+
+    cat <<- 'EOF'
+		USAGE:
+		    smart-tests [OPTION]...
+
+		OPTIONS
+		    --short,-s
+		        Perform S.M.A.R.T short self-test
+		    --long,-l
+		        Perform S.M.A.R.T long self-test
+		    --info,-i
+		        Print S.M.A.R.T info
+		    --health,-h
+		        Perform S.M.A.R.T health assessment
+		    --temp,-t
+		        Report disk temperatures
+		    --daemon,-d
+		        Run in daemon mode for automatic health checks
+		    --all,-a
+		        Run on all drives (default)
+		    --help
+		        Print this help dialog and exit
+	EOF
+}
+
+
+main() {
+
+    mapfile -t drives_scanned < <(smartctl --scan)
+
+    for drive in "${drives_scanned[@]}"; do
+
+        name=$(echo "$drive" | cut -f1 -d" ")
+        type=$(echo "$drive" | cut -f3 -d" ")
+
+        echo "$name ($type)"
+
+        suffix=("-d" "$type" "$name")
+
+        [[ -v SHORT ]] && smartctl -t short "${suffix[@]}"
+        [[ -v LONG ]] && smartctl -t long "${suffix[@]}"
+        [[ -v INFO ]] && smartctl -i "${suffix[@]}"
+        [[ -v HEALTH ]] && smartctl -H "${suffix[@]}"
+        [[ -v ALL ]] && smartctl -a "${suffix[@]}"
+        [[ -v TEMP ]] && smartctl -a "${suffix[@]}" | grep "Current Drive Temperature:"
+        [[ -v DAEMON ]] && smartctl -t short "${suffix[@]}"
+
+    done
+}
+
+
+init "$@"
+main "$@"

+ 31 - 0
shell/speedtest-compare

@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# This script will perform speedtests over wireguard and the
+# native connections and print their output
+#
+
+_speedTestData() {
+
+	local pingbps ping_f bps_f bps_int
+	export ping_int mbps_int
+
+	pingbps=$(speedtest-cli --no-upload --csv "$@" | cut -d"," -f7-8)
+	ping_f="${pingbps%,*}" # grab first value
+	ping_int="${ping_f%.*}" # make integer
+	bps_f="${pingbps#*,}" # grab second value
+	bps_int="${bps_f%.*}" # make integer
+	mbps_int=$((bps_int / 1000000)) # convert to mbps
+}
+
+# Test Wireguard using automatic server selection
+if _speedTestData; then
+	echo "Wireguard:"
+	echo -e "\tPing: $ping_int"
+	echo -e "\tMbps: $mbps_int"
+fi
+
+# Test native connection to ISP
+if _speedTestData --server 17170; then
+	echo "Native:"
+	echo -e "\tPing: $ping_int"
+	echo -e "\tMbps: $mbps_int"
+fi

+ 31 - 0
shell/ssh-colorize

@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+if [[ -n $SSH_CLIENT ]]; then
+  case $HOSTNAME in
+    *.example.com) prompt_user_host_color='1;35';; # magenta on example.com
+    *) prompt_user_host_color='1;33';; # yellow elsewhere
+  esac
+else
+  unset prompt_user_host_color;; # omitted on the local machine
+fi
+if [[ -n $prompt_user_host_color ]]; then
+  PS1='\[\e['$prompt_user_host_color'm\]\u@\h'
+else
+  PS1=
+fi
+PS1+='\[\e[1;34m\] \w\[\e[1;31m\]$(__git_ps1)\[\e[1;0;37m\] \$\[\e[0m\] '
+
+if [[ -n $SSH_CLIENT ]]; then
+  case $HOSTNAME in
+    *.example.com) prompt_user_host_color='1;35';; # magenta on example.com
+    *) prompt_user_host_color='1;33';; # yellow elsewhere
+  esac
+else
+  unset prompt_user_host_color;; # omitted on the local machine
+fi
+if [[ -n $prompt_user_host_color ]]; then
+  PS1='\[\e['$prompt_user_host_color'm\]\u@\h'
+else
+  PS1=
+fi
+PS1+='\[\e[1;34m\] \w\[\e[1;31m\]$(__git_ps1)\[\e[1;0;37m\] \$\[\e[0m\] '

+ 3 - 0
shell/strip-exif

@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+exiftool -all= "$@"

+ 6 - 0
shell/sync-htpc-new

@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+rsync -av --info=progress2 --delete htpc.lan:/mnt/m/Movies/ htpc.lan:/mnt/n/Movies/ /mnt/array/movies/ && \
+rsync -av --info=progress2 --delete htpc.lan:/mnt/m/TV/ htpc.lan:/mnt/n/TV/ /mnt/array/tv/ && \
+rsync -av --info=progress2 --delete laptop.lan:/home/bryan/documents ~/documents && \
+rsync -av --info=progress2 --delete laptop.lan:/home/bryan/media/music /mnt/array/music
+

+ 4 - 0
shell/thinkpad-enable-trackpoint-scrolling

@@ -0,0 +1,4 @@
+#!/bin/bash
+xinput set-prop "TPPS/2 IBM TrackPoint" "Evdev Wheel Emulation" 1
+xinput set-prop "TPPS/2 IBM TrackPoint" "Evdev Wheel Emulation Button" 2
+xinput set-prop "TPPS/2 IBM TrackPoint" "Evdev Wheel Emulation Timeout" 200

+ 145 - 0
shell/toolbox-run

@@ -0,0 +1,145 @@
+#!/usr/bin/env bash
+#
+# This program will execute commands in the specified toolbox container
+#
+# Author:
+#   Bryan Roessler
+# Source:
+#    https://git.bryanroessler.com/bryan/scripts/src/master/toolboxrun
+#
+toolboxrun () {
+
+    _printHelpAndExit () {
+
+        cat <<-'EOF'
+USAGE
+    toolboxRun -c NAME [-i NAME] [-r RELEASE] [--no-sh] [-h] [-s] [-d] [COMMANDS [ARGS...]]
+
+COMMANDS
+
+    COMMANDS to run in the container (e.g. the current active file, an external
+    build script, a program residing in the container, etc.)
+    Can be empty (default entrypoint)
+
+OPTIONS
+
+    --container NAME, -c NAME
+        Assign  a  different  NAME  to the toolbox container. This is useful for creating multiple
+        toolbox containers from the same base image, or for entirely  customized  containers  from
+        custom-built base images.
+
+        If the toolbox container NAME already exists, the command passed to toolboxRun will be
+        executed in the existing toolbox. If toolbox container NAME does not exist, it will be
+        created and the COMMAND will then be run in it.
+
+    --image NAME, -i NAME
+        Change the NAME of the base image used to create the toolbox container. This is useful for
+        creating containers from custom-built base images.
+
+    --release RELEASE, -r RELEASE
+        Create a toolbox container for a different operating system RELEASE than the host.
+
+    --ephemeral
+        The toolbox will be removed after the COMMAND is executed
+
+    --recreate
+        If the toolbox NAME already exists, it will first be removed and recreated
+
+    --no-sh, -n
+        Do not wrap COMMANDS in 'sh -c'
+
+    --help, -h
+        Print this help message and exit (overrides --silent)
+
+EOF
+
+        # Exit using passed exit code
+        [[ -z $1 ]] && exit 0 || exit "$1"
+    }
+
+
+    _parseInput () {
+
+        # Parse input and set switches using getopt
+        if _input=$(getopt -o +c:i:r:nh -l container:,image:,release:,ephemeral,recreate,no-sh,help -- "$@"); then
+            eval set -- "$_input"
+            while true; do
+                case "$1" in
+                    --container|-c)
+                        shift && export _cname="$1"
+                        ;;
+                    --image|-i)
+                        shift && export _image=("-i" "$1")
+                        ;;
+                    --release|-r)
+                        shift && export _release=("-r" "$1")
+                        ;;
+                    --ephemeral)
+                        export _ephemeral="true"
+                        ;;
+                    --recreate)
+                        export _recreate="true"
+                        ;;
+                    --no-sh|-n)
+                        export _no_sh="true"
+                        ;;
+                    --help|-h)
+                        _printHelpAndExit 0
+                        ;;
+                    --)
+                        shift
+                        break
+                        ;;
+                esac
+                shift
+            done
+        else
+            echo "Incorrect options provided"
+            _printHelpAndExit 1
+        fi
+
+        # Create _pre_commands_array from remaining arguments
+        # shift getopt parameters away
+        shift $((OPTIND - 1))
+        # Assume program name is first argument
+        export _program="$1"
+        # create command array
+        declare -ga _cmd_array=("$@")
+    }
+
+
+    _shWrap () { [[ -z $_no_sh ]] && _cmd_array=("sh" "-c" "${_cmd_array[*]}"); }
+    _toolboxExists () { toolbox list -c | cut -d ' ' -f 3 | grep -w "$1" > /dev/null 2>&1; }
+    _toolboxCreate () { toolbox create -c "$1" "${_image[@]}" "${_release[@]}"; }
+    _toolboxRemove () { toolbox rm -f "$1"; }
+    _toolboxRun () { toolbox run -c "$1" "${_cmd_array[@]}"; }
+
+
+    __main () {
+
+        # Get input
+        _parseInput "$@"
+        # Wrap command with `sh -c` by default
+        [[ -z $_no_sh ]] && _shWrap
+        # Check if container exists
+        if _toolboxExists "$_cname"; then
+            if [[ -n $_recreate || -n $_ephemeral ]]; then
+                _toolboxRemove "$_cname"
+            fi
+        else
+            _toolboxCreate "$_cname"
+        fi
+
+        _toolboxRun "$_cname" && export _ec="$?"
+
+        [[ -n $_ephemeral ]] && _toolboxRemove "$_cname"
+    }
+
+    # Allow this function to be executed directly
+    __main "$@"
+}
+
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+    toolboxrun "$@"
+    exit 0
+fi

+ 19 - 0
shell/update-git-hooks

@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# This script will update the post-receive hooks of multiple bare git repos
+#
+
+for i in /var/lib/git/gogs-repositories/bryan/*/hooks/post-receive; do
+
+    # Get repo name
+    rn="${i%/hooks/post-receive}"
+    rn="${rn##*/}"
+
+    # Don't duplicate the line if it already exists
+    while IFS= read -r line; do
+        [[ "$line" == "git push --mirror git@github.com:cryobry/${rn}" ]] && continue
+    done < "$i"
+
+    # Append the line
+    #echo "git push --mirror git@github.com:cryobry/${rn} to $i"
+    echo "git push --mirror git@github.com:cryobry/${rn}" >> "$i"
+done

+ 14 - 0
systemd/battery-charge-thresholds.service

@@ -0,0 +1,14 @@
+[Unit]
+Description=Set battery charge thresholds
+After=multi-user.target
+StartLimitBurst=0
+
+[Service]
+Type=oneshot
+Restart=on-failure
+RemainAfterExit=yes
+ExecStart=/bin/bash -c 'echo 80 > /sys/class/power_supply/BAT0/charge_control_start_threshold; echo 90 > /sys/class/power_supply/BAT0/charge_control_end_threshold'
+ExecStop=/bin/bash -c 'echo 100 > /sys/class/power_supply/BAT0/charge_control_end_threshold; echo 99 > /sys/class/power_supply/BAT0/charge_control_start_threshold'
+
+[Install]
+WantedBy=multi-user.target

Some files were not shown because too many files changed in this diff