├── .gitattributes ├── LICENSE.md ├── README.md ├── apps.py ├── archive ├── fullbackup.sh ├── imagepad.sh ├── imageprep.sh ├── miconprep.sh ├── pi.sh ├── setupmac.sh ├── ticonprep.sh ├── todisk.sh ├── toserver.sh ├── updatemac.sh └── wiconprep.sh ├── binstall.zsh ├── cbz_blitzer.sh ├── cbz_scanner.sh ├── configmac.zsh ├── cppy.zsh ├── cr.zsh ├── crc.py ├── cs.sh ├── deploy.sh ├── fixgit.sh ├── getcaskversions.zsh ├── gitcheck.zsh ├── gitlog.sh ├── hugo-deploy.zsh ├── iconprep.sh ├── imagenum.sh ├── increment.sh ├── lowerext.sh ├── makepico.zsh ├── media-backup.zsh ├── mnuiconprep.sh ├── mp-version.py ├── outman.zsh ├── packapp.zsh ├── packcli.zsh ├── pdfer.sh ├── pi.zsh ├── pigitup.zsh ├── pinstall.sh ├── pireadonly.sh ├── scripts.code-workspace ├── setimagedpi.sh ├── setup.zsh ├── setupmac.zsh ├── todisk.zsh ├── toserver.zsh ├── updatemac.zsh ├── updatepi.sh ├── updatepi.zsh ├── virtual-pxat.py ├── xcodeautobuild.sh ├── yamlizer.py └── zinstall.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | ### Copyright © 2022 Tony Smith (@smittytone) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smittytone’s Script Archive # 2 | 3 | *Useful zsh and bash scripts* 4 | 5 | In the case of scripts which operate as utilities, use the `--help` switch to learn how to make use of the tool. Scripts which are intended to be run once — for example, the Raspberry Pi setup scripts — are documented in comments. Scripts run in other contexts, eg. in Xcode, are not yet documented. 6 | 7 | ## Tools and Utilities ## 8 | 9 | ### outman.zsh ### 10 | 11 | Output man pages to text files. Specify the page and its output text file as pairs of arguments. 12 | 13 | - [View Script](outman.zssh) 14 | 15 | ### image<num/prep>.sh ### 16 | 17 | Image processing scripts. See [this blog post](https://smittytone.wordpress.com/2019/10/24/macos-image-manipulation-with-sips/). 18 | 19 | **Note** imageprep 5.2.0 reverses the order of crop, scale and pad dimensions *from* *to* . 20 | 21 | - [View Image Cropping/Padding/Scaling Script](imageprep.sh) 22 | - [View Image Numbering Script](imagenum.sh) 23 | 24 | ### cs.sh ### 25 | 26 | Confirm or reject a downloaded file’s SHA-256. 27 | 28 | - [View Script](cs.sh) 29 | 30 | ### lowerext.sh ### 31 | 32 | Convert the working directory’s file extensions to lowercase. 33 | 34 | - [View Script](lowerext.sh) 35 | 36 | ### pdfer.sh ### 37 | 38 | Converts `.docx` files downloaded from Google Docs to `.pdf`. 39 | 40 | - [View Script](pdfer.sh) 41 | 42 | ### cbz_blitzer.sh ### 43 | 44 | Scans a folder (and sub-folders) for .cbz files and converts them to .pdf files. Created for a one-off project. Included here to record folder scanning and file manipulation algorithms. 45 | 46 | - [View Script](cbz_blitzer.sh) 47 | 48 | ## Xcode and Development ## 49 | 50 | ### iconprep.sh ### 51 | 52 | macOS/watchOS/iOS app icon maker script. 53 | 54 | - [View Script](iconprep.sh) 55 | 56 | ### xcodeautobuild.sh ### 57 | 58 | Xcode-oriented build script for auto-incrementing a project's build number at build time. 59 | 60 | - [View Script](xcodeautobuild.sh) 61 | 62 | ### packcli.zsh and packapp.zsh ### 63 | 64 | Shell scripts to automate the creation of macOS app installer packages, and their signing and notarization. For use with apps distributed outside the Mac App Store. 65 | 66 | - [View packapp Script](packapp.zsh) 67 | - [View packcli Script](packcli.zsh) 68 | 69 | ## Mac Setup and Config ## 70 | 71 | ### updatemac.zsh / updatemac.sh ### 72 | 73 | Update local config files from the `dotfiles` repo. 74 | 75 | - [View bash Script](updatemac.sh) 76 | - [View zsh Script](updatemac.zsh) 77 | 78 | ### setupmac.sh ### 79 | 80 | Set up a new Mac. 81 | 82 | - [View Script](setupmac.sh) 83 | 84 | ## Mac Backup ## 85 | 86 | ### to<disk/server>.sh ### 87 | 88 | Local media back-up scripts, targeting disk and server. 89 | 90 | - [View Disk Script](todisk.sh) 91 | - [View Server Script](toserver.sh) 92 | 93 | ## Raspberry Pi Setup and Config ## 94 | 95 | ### updatepi.zsh / updatepi.sh ### 96 | 97 | Update local config files from the `dotfiles` repo. 98 | 99 | - [View bash Script](updatepi.sh) 100 | - [View zsh Script](updatepi.zsh) 101 | 102 | ### <p/z>install.sh ### 103 | 104 | Setup scripts for the Raspberry Pi and the Raspberry Pi Zero. 105 | 106 | - [View Raspberry Pi Script](pinstall.sh) 107 | - [View Pi Zero Script](zinstall.sh) 108 | 109 | ### pi.sh ### 110 | 111 | SD card preparation script for Raspberry Pis. **Note** This runs on a macOS host. 112 | 113 | - [View Script](pi.sh) 114 | 115 | ### pireadonly.sh ### 116 | 117 | Hack a Raspberry Pi to run in read-only mode (no writes to the SD). 118 | 119 | - [View Script](pireadonly.sh) 120 | 121 | ## Miscellaneous ## 122 | 123 | ### cppy.zsh ### 124 | 125 | Copy a bunch of Python files to a CircuitPython device. The first file on the list is renamed `code.py`. 126 | 127 | - [View Script](cppy.zsh) -------------------------------------------------------------------------------- /apps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import json 6 | import requests 7 | from requests.auth import HTTPBasicAuth 8 | from datetime import datetime, timedelta 9 | 10 | do_list_only = False 11 | app_list = None 12 | apps_to_delete = [] 13 | deployed_app = "" 14 | api_url = "https://microvisor.twilio.com/v1/" 15 | auth = HTTPBasicAuth(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"]) 16 | 17 | if len(sys.argv) > 1: 18 | for index, item in enumerate(sys.argv): 19 | if item.lower() in ("-h", "--help"): 20 | print("Usage: apps [--list]") 21 | print("Options: -l / --list List but do not delete stale apps.") 22 | print("Stale apps are those more that 24 hours old. Stale apps") 23 | print("that are still assigned to a device will not be deleted") 24 | sys.exit() 25 | if item.lower() in ("-l", "--list"): 26 | do_list_only = True 27 | 28 | print("Listing your Microvisor applications...") 29 | date_now = datetime.now() 30 | resp = requests.get(api_url + "Apps?PageSize=200", auth=auth) 31 | if resp.status_code == 200: 32 | try: 33 | app_list = resp.json() 34 | if "apps" in app_list: 35 | for app in app_list["apps"]: 36 | sid = app["sid"] 37 | name = app["unique_name"] 38 | date_then = datetime.strptime(app["date_created"], '%Y-%m-%dT%H:%M:%SZ') 39 | if date_now - date_then >= timedelta(days=1): 40 | apps_to_delete.append(sid) 41 | sid = name if name else sid 42 | print(sid, "STALE") 43 | else: 44 | print(sid, "OK") 45 | except Exception as e: 46 | print("[ERROR] Could not parse response from Twilio", e) 47 | else: 48 | print("[ERROR] Unable to access your apps") 49 | 50 | if do_list_only: 51 | sys.exit() 52 | 53 | if len(apps_to_delete) > 0: 54 | print("Deleting",len(apps_to_delete),"stale apps...") 55 | resp = requests.get(api_url + "Devices?PageSize=200", auth=auth) 56 | devices = None 57 | if resp.status_code == 200: 58 | try: 59 | dev_list = resp.json() 60 | if "devices" in dev_list: 61 | devices = dev_list["devices"] 62 | except Exception as e: 63 | pass 64 | 65 | for sid in apps_to_delete: 66 | resp = requests.delete(api_url + "Apps/" + sid, auth=auth) 67 | if resp.status_code == 204: 68 | print(sid, "DELETED") 69 | elif resp.status_code == 400: 70 | if devices: 71 | for device in devices: 72 | if device["app"]["target_sid"] == sid: 73 | deployed_app = sid 74 | break 75 | print(sid, "NOT DELETED (DEPLOYED TO DEVICE", (deployed_app if deployed_app else "UNKNOWN") + ")") 76 | else: 77 | print(sid, "NOT DELETED (CODE: " + str(resp.status_code) + ")") 78 | -------------------------------------------------------------------------------- /archive/fullbackup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Backup to Uber-Disk Script 4 | # Version 1.0.0 5 | 6 | read -n 1 -r -p "Connect disk 4TB then press any key when it has mounted\n" key 7 | if [ -d /Volumes/4TB ]; then 8 | read -n 1 -r -p "Connect disk MEDIA then press any key when it has mounted\n" key 9 | if [ -d /Volumes/Media ]; then 10 | read -n 1 -r -p "Press any key to continue...\n" key 11 | echo "/Volumes/Media and /Volumes/4TB mounted. Backup Stage 1a commencing." 12 | rsync -avz /Volumes/Media/ /Volumes/4TB/Backup/'Media Disk'/ --exclude=".*/" --exclude=".*" 13 | echo "Backup Stage 1a Complete." 14 | else 15 | echo "/Volumes/Media is not mounted." 16 | fi 17 | 18 | read -n 1 -r -p "Connect disk ATV then press any key when it has mounted\n" key 19 | if [ -d /Volumes/ATV ]; then 20 | echo "/Volumes/ATV and /Volumes/4TB mounted. Backup Stage 2 commencing." 21 | rsync -avz /Volumes/ATV/ /Volumes/4TB/Backup/ATV/ --exclude=".*/" --exclude=".*" 22 | echo "Backup Stage 2 Complete. Please disconnect /Volumes/ATV, connect /Volumes/MEDIA2 and then run Backup script 3." 23 | else 24 | echo "/Volumes/ATV is not mounted." 25 | fi 26 | 27 | read -n 1 -r -p "Connect disk MEDIA2 then press any key when it has mounted\n" key 28 | if [ -d /Volumes/MEDIA2 ]; then 29 | echo "/Volumes/MEDIA2 and /Volumes/4TB mounted. Backup Stage 1 commencing." 30 | rsync -avz /Volumes/MEDIA2/ /Volumes/4TB/Backup/'Media Disk'/ --exclude=".*/" --exclude=".*" 31 | echo "Backup Stage 3 Complete. Please disconnect /Volumes/MEDIA 2 and /Volumes/4TB" 32 | echo "Remember to back up /Users/smitty manually." 33 | else 34 | echo "Disk MEDIA2 is not mounted." 35 | fi 36 | else 37 | echo "Disk 4TB is not mounted -- backup cannot continue" 38 | exit 1 39 | fi 40 | -------------------------------------------------------------------------------- /archive/imagepad.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | # NOTE You may need to change the above line to /bin/bash 3 | 4 | # Crop and/or pad image files 5 | # 6 | # Version 3.0.1 7 | 8 | 9 | # Function to show help info - keeps this out of the code 10 | function showHelp() { 11 | echo -e "\nImage Size Adjust Utility\n" 12 | echo -e "Usage:\n imagepad [-s path] [-d path] [-c padColour] [-a c crop_height crop_width] " 13 | echo " [-a p pad_height pad_width] [-r] [-k] [-h]" 14 | echo " NOTE You can selet either crop, pad or both, but pad actions will always be" 15 | echo -e " performed before crop actions\n" 16 | echo "Options:" 17 | echo " -s / --source [path] The path to the images. Default: current working directory." 18 | echo " -d / --destination [path] The path to the images. Default: Downloads folder." 19 | echo " -c / --colour [colour] The padding colour in Hex, eg. A1B2C3. Default: FFFFFF." 20 | echo " -a / --action [type] [height] [width] The crop/pad dimensions. Type is c (crop) or p (pad)." 21 | echo " -r / --resolution [dpi] Set the image dpi. Default: 300." 22 | echo " -k / --keep Keep the source file. Without this, the source will be deleted." 23 | echo " -q / --quiet Silence output messages (errors excepted)." 24 | echo " -h / --help This help screen." 25 | echo 26 | } 27 | 28 | 29 | # Set inital state values 30 | destPath="$HOME/Downloads" 31 | sourcePath=~+ 32 | argType=c 33 | padColour=FFFFFF 34 | cropHeight=2182 35 | cropWidth=1668 36 | padHeight=2224 37 | padWidth=1668 38 | dpi=300 39 | doCrop=0 40 | doPad=0 41 | doRes=0 42 | noMessages=0 43 | deleteSource=1 44 | argIsAValue=0 45 | args=(-s -d -c -r -a -h -q -k) 46 | 47 | # Process the arguments 48 | argCount=0 49 | for arg in "$@"; do 50 | if [[ $argIsAValue -gt 0 ]]; then 51 | # The argument should be a value (previous argument was an option) 52 | if [[ ${arg:0:1} = "-" ]]; then 53 | # Next value is an option: ie. missing value 54 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 55 | exit 1 56 | fi 57 | 58 | # Set the appropriate internal value 59 | case "$argIsAValue" in 60 | 1) sourcePath=$arg ;; 61 | 2) destPath=$arg ;; 62 | 3) padColour=$arg ;; 63 | 4) dpi=$arg ;; 64 | 5) argType=$arg ;; # Next argument is the 'type' value ('c' or 'i') 65 | 6) if [[ $argType = "c" ]]; then 66 | doCrop=1 67 | cropHeight=$arg 68 | else 69 | doPad=1 70 | padHeight=$arg 71 | fi ;; 72 | 7) if [[ $argType = "c" ]]; then 73 | doCrop=1 74 | cropWidth=$arg 75 | else 76 | doPad=1 77 | padWidth=$arg 78 | fi ;; 79 | *) echo "Error: Unknown argument"; exit 1 ;; 80 | esac 81 | 82 | # Reset 'argIsAValue' for values 5 through 7 (crop and pad actionsm, which have extra params) 83 | if [[ $argIsAValue -eq 7 || $argIsAValue -lt 5 ]]; then 84 | argIsAValue=0 85 | else 86 | ((argIsAValue++)) 87 | fi 88 | else 89 | if [[ $arg = "-s" || $arg = "--source" ]]; then 90 | argIsAValue=1 91 | elif [[ $arg = "-d" || $arg = "--destination" ]]; then 92 | argIsAValue=2 93 | elif [[ $arg = "-c" || $arg = "--colour" || $arg = "--color" ]]; then 94 | argIsAValue=3 95 | elif [[ $arg = "-r" || $arg = "--resolution" ]]; then 96 | doRes=1 97 | argIsAValue=4 98 | elif [[ $arg = "-a" || $arg = "--action" ]]; then 99 | argIsAValue=5 100 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 101 | showHelp 102 | exit 0 103 | elif [[ $arg = "-q" || $arg = "--quiet" ]]; then 104 | noMessages=1 105 | elif [[ $arg = "-k" || $arg = "--keep" ]]; then 106 | deleteSource=0 107 | fi 108 | fi 109 | 110 | ((argCount++)) 111 | 112 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 113 | echo "Error: Missing value for $arg" 114 | exit 1 115 | fi 116 | done 117 | 118 | fileCount=0 119 | 120 | # Check that the source directory is good; bail otherwise 121 | if ! [ -e "$sourcePath" ]; then 122 | echo "Source directory $sourcePath cannot be found -- exiting" 123 | exit 1 124 | fi 125 | 126 | # Check that the destination directory is good; bail otherwise 127 | if ! [ -e "$destPath" ]; then 128 | echo "Target directory $destPath cannot be found -- exiting" 129 | exit 1 130 | fi 131 | 132 | if [ $noMessages -eq 0 ]; then 133 | echo "Source: $sourcePath" 134 | echo "Target: $destPath" 135 | fi 136 | 137 | for file in "$sourcePath"/* 138 | do 139 | if [ -f "$file" ]; then 140 | # Get the extension and make it uppercase 141 | filename="${file##*/}" 142 | extension=${file##*.} 143 | extension=${extension^^*} 144 | filename="${filename%.*}" 145 | 146 | # Make sure the file's of the right type 147 | if [[ $extension = "PNG" || $extension = "JPG" || $extension = "JPEG" ]]; then 148 | if [ $noMessages -eq 0 ]; then 149 | echo "Converting $file to $destPath/$filename.$extension..." 150 | fi 151 | 152 | # Copy the file from source to destination 153 | cp "$file" "$destPath/$filename.$extension" 154 | 155 | # Set the dpi 156 | if [ $doRes -eq 1 ]; then 157 | sips "$destPath/$filename.$extension" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null 158 | fi 159 | 160 | # Pad the file, as requested 161 | if [ $doPad -eq 1 ]; then 162 | sips "$destPath/$filename.$extension" -p "$padHeight" "$padWidth" --padColor "$padColour" &> /dev/null 163 | fi 164 | 165 | # Crop the file, as requested 166 | if [ $doCrop -eq 1 ]; then 167 | sips "$destPath/$filename.$extension" -c "$cropHeight" "$cropWidth" --padColor "$padColour" &> /dev/null 168 | fi 169 | 170 | # Increment the file count 171 | ((fileCount++)) 172 | 173 | 174 | # Remove the source file if requested 175 | if [ $deleteSource -gt 0 ]; then 176 | rm "$file" 177 | fi 178 | fi 179 | fi 180 | done 181 | 182 | # Present a task report 183 | if [ $noMessages -eq 0 ]; then 184 | if [ $fileCount -eq 1 ]; then 185 | echo "1 file converted" 186 | elif [ $fileCount -gt 1 ]; then 187 | echo "$fileCount files converted" 188 | else 189 | echo "No files converted" 190 | fi 191 | fi -------------------------------------------------------------------------------- /archive/imageprep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # imageprep.sh 5 | # 6 | # Crop, pad, scale and/or reformat image files 7 | # 8 | # @author Tony Smith 9 | # @copyright 2020, Tony Smith 10 | # @version 5.2.5 11 | # @license MIT 12 | # 13 | 14 | 15 | # Function to show help info - keeps this out of the code 16 | function showHelp { 17 | echo -e "\nimageprep 5.2.5\n" 18 | echo -e "A macOS Image Adjustment Utility\n" 19 | echo -e "Usage:\n imageprep [-s path] [-d path] [-c padColour] [-a s scale_height scale_width] " 20 | echo -e " [-a p pad_height pad_width] [-a c crop_height crop_width] [-r] [-f] [-k] [-h]\n" 21 | echo " NOTE You can select either crop, pad or scale or all three, but actions will always" 22 | echo -e " be performed in this order: pad, then crop, then scale.\n" 23 | echo "Options:" 24 | echo " -s / --source [path] The path to an image or a directory of images. Default: current working directory." 25 | echo " -d / --destination [path] The path to the images. Default: source directory." 26 | echo " -a / --action [type] [width] [height] The crop/pad dimensions. Type is s (scale), c (crop) or p (pad)." 27 | echo " -c / --colour [colour] The padding colour in Hex, eg. A1B2C3. Default: FFFFFF." 28 | echo " -r / --resolution [dpi] Set the image dpi, eg. 300" 29 | echo " -f / --format [format] Set the image format: JPG/JPEG, PNG or TIF/TIFF" 30 | echo " -k / --keep Keep the source file. Without this, the source will be deleted." 31 | echo " -q / --quiet Silence output messages (errors excepted)." 32 | echo " -h / --help This help screen." 33 | echo 34 | } 35 | 36 | 37 | # FROM 5.1.0 38 | # Separarate out the code that processes a given file into a function 39 | function processFile { 40 | file="$1" 41 | 42 | if ! [ -s "$file" ]; then 43 | echo "[ERROR] $file has no content -- ignoring " 44 | return 45 | fi 46 | 47 | # Get the extension and make it uppercase 48 | parseFileName "$file" 49 | 50 | # Make sure the file's of the right type 51 | if [[ $extension = "png" || $extension = "jpg" || $extension = "jpeg" || $extension = "tif" || $extension = "tiff" ]]; then 52 | if [ "$noMessages" -eq 0 ]; then 53 | echo -n "Processing $file as " 54 | fi 55 | 56 | # Copy the file before editing... 57 | # FROM 5.0.1 -- ...but not if source and destination match 58 | # FROM 5.0.3 -- Move this up so it happens first 59 | if [ "$file" != "$destPath/$filename.$extension" ]; then 60 | cp "$file" "$destPath/$filename.$extension" &> /dev/null 61 | fi 62 | 63 | # FROM 5.0.0 64 | # Set the format (and perform the copy) 65 | if [ "$reformat" -eq 1 ]; then 66 | # FROM 5.0.2 67 | # If we're converting from PNG or TIFF, perform an dpi change before converting to target format 68 | if [ "$doRes" -eq 1 ]; then 69 | doneRes=0 70 | if [[ $extension = "png" || $extension = "tiff" || $extension = "tif" ]]; then 71 | sips "$destPath/$filename.$extension" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null 72 | doneRes=1 73 | fi 74 | fi 75 | 76 | # Output the new format as a new copy, then delete the old copy and set the new extension 77 | sips "$destPath/$filename.$extension" -s format "$format" --out "$destPath/$filename.$formatExtension" &> /dev/null 78 | rm "$destPath/$filename.$extension" 79 | extension=$formatExtension 80 | fi 81 | 82 | if [ "$noMessages" -eq 0 ]; then 83 | echo "$destPath/$filename.$extension" 84 | fi 85 | 86 | # Pad the file, as requested 87 | if [ "$doPad" -eq 1 ]; then 88 | sips "$destPath/$filename.$extension" -p "$padHeight" "$padWidth" --padColor "$padColour" &> /dev/null 89 | fi 90 | 91 | # Crop the file, as requested 92 | if [ "$doCrop" -eq 1 ]; then 93 | sips "$destPath/$filename.$extension" -c "$cropHeight" "$cropWidth" --padColor "$padColour" &> /dev/null 94 | fi 95 | 96 | # Scale the file, as requested 97 | if [ "$doScale" -eq 1 ]; then 98 | sips "$destPath/$filename.$extension" -z "$scaleHeight" "$scaleWidth" --padColor "$padColour" &> /dev/null 99 | fi 100 | 101 | # Set the dpi 102 | if [[ "$doRes" -eq 1 && "$doneRes" -eq 0 ]]; then 103 | if [[ "$extension" = "jpg" || "$extension" = "jpeg" ]]; then 104 | # sips does not apply dpi settings to JPEGs (why???) so if the target image is a JPEG, 105 | # convert it to PNG, apply the dpi settings and then convert it back again. 106 | sips "$destPath/$filename.$extension" -s format png --out "$destPath/$filename-sipstmp.png" &> /dev/null 107 | sips "$destPath/$filename-sipstmp.png" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null 108 | sips "$destPath/$filename-sipstmp.png" -s format jpeg --out "$destPath/$filename.$extension" &> /dev/null 109 | rm "$destPath/$filename-sipstmp.png" 110 | else 111 | sips "$destPath/$filename.$extension" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null 112 | fi 113 | fi 114 | 115 | # Increment the file count 116 | ((fileCount++)) 117 | 118 | # Remove the source file if requested 119 | if [ "$deleteSource" -gt 0 ]; then 120 | rm "$file" 121 | fi 122 | fi 123 | } 124 | 125 | 126 | function parseFileName { 127 | filename="${1##*/}" 128 | extension=${1##*.} 129 | extension=${extension,,} 130 | filename="${filename%.*}" 131 | } 132 | 133 | 134 | # Set inital state values 135 | sourcePath=~+ 136 | destPath="DEFAULT" 137 | argType=c 138 | padColour=FFFFFF 139 | cropHeight=2182 140 | cropWidth=1668 141 | padHeight=2224 142 | padWidth=1668 143 | scaleHeight=$padHeight 144 | scaleWidth=$padWidth 145 | dpi=300 146 | format=UNSET 147 | formatExtension=png 148 | doCrop=0 149 | doPad=0 150 | reformat=0 151 | doScale=0 152 | doRes=0 153 | doneRes=0 154 | noMessages=0 155 | deleteSource=1 156 | argIsAValue=0 157 | args=(-s -d -c -r -f -a -h -q -k) 158 | 159 | # Process the arguments 160 | argCount=0 161 | for arg in "$@"; do 162 | if [[ "$argIsAValue" -gt 0 ]]; then 163 | # The argument should be a value (previous argument was an option) 164 | if [[ ${arg:0:1} = "-" ]]; then 165 | # Next value is an option: ie. missing value 166 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 167 | exit 1 168 | fi 169 | 170 | # Set the appropriate internal value 171 | case "$argIsAValue" in 172 | 1) sourcePath=$arg ;; 173 | 2) destPath=$arg ;; 174 | 3) padColour=$arg ;; 175 | 4) dpi=$arg ;; 176 | 5) format=$arg ;; 177 | 6) argType=$arg ;; # Next argument is the 'type' value ('c', 'p' or 's') 178 | 7) if [ "$argType" = "c" ]; then 179 | doCrop=1 180 | cropWidth=$arg 181 | elif [ "$argType" = "s" ]; then 182 | doScale=1 183 | scaleWidth=$arg 184 | else 185 | doPad=1 186 | padWidth=$arg 187 | fi ;; 188 | 8) if [ "$argType" = "c" ]; then 189 | doCrop=1 190 | cropHeight=$arg 191 | elif [ "$argType" = "s" ]; then 192 | doScale=1 193 | scaleHeight=$arg 194 | else 195 | doPad=1 196 | padHeight=$arg 197 | fi ;; 198 | *) echo "Error: Unknown argument"; exit 1 ;; 199 | esac 200 | 201 | # Reset 'argIsAValue' for values 6 through 8 (ie. actions, which have extra params) 202 | if [[ "$argIsAValue" -eq 8 || "$argIsAValue" -lt 6 ]]; then 203 | argIsAValue=0 204 | else 205 | ((argIsAValue++)) 206 | fi 207 | else 208 | if [[ $arg = "-s" || $arg = "--source" ]]; then 209 | argIsAValue=1 210 | elif [[ $arg = "-d" || $arg = "--destination" ]]; then 211 | argIsAValue=2 212 | elif [[ $arg = "-c" || $arg = "--colour" || $arg = "--color" ]]; then 213 | argIsAValue=3 214 | elif [[ $arg = "-r" || $arg = "--resolution" ]]; then 215 | doRes=1 216 | argIsAValue=4 217 | elif [[ $arg = "-f" || $arg = "--format" ]]; then 218 | reformat=1 219 | argIsAValue=5 220 | elif [[ $arg = "-a" || $arg = "--action" ]]; then 221 | argIsAValue=6 222 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 223 | showHelp 224 | exit 0 225 | elif [[ $arg = "-q" || $arg = "--quiet" ]]; then 226 | noMessages=1 227 | elif [[ $arg = "-k" || $arg = "--keep" ]]; then 228 | deleteSource=0 229 | else 230 | echo "Error: Unknown option $arg" 231 | exit 1 232 | fi 233 | fi 234 | 235 | ((argCount++)) 236 | 237 | if [[ "$argCount" -eq $# && "$argIsAValue" -ne 0 ]]; then 238 | echo "Error: Missing value for $arg" 239 | exit 1 240 | fi 241 | done 242 | 243 | fileCount=0 244 | 245 | # Check that the source directory is good; bail otherwise 246 | if ! [ -e "$sourcePath" ]; then 247 | echo "Source directory $sourcePath cannot be found -- exiting" 248 | exit 1 249 | fi 250 | 251 | # FROM 5.2.1 252 | # If destination not set, use the source 253 | if [ "$destPath" = "DEFAULT" ]; then 254 | destPath="$sourcePath" 255 | fi 256 | 257 | # Check that the destination directory is good; bail otherwise 258 | if ! [ -e "$destPath" ]; then 259 | echo "Target directory $destPath cannot be found -- exiting" 260 | exit 1 261 | fi 262 | 263 | # FROM 5.0.0 264 | # Check the reformatting, if present 265 | if [ "$reformat" -eq 1 ]; then 266 | # Make the format value lowercase 267 | format=${format,,} 268 | 269 | # Is the value valid? 270 | valid=0 271 | formatExtension=$format 272 | if [ "$format" = "jpg" ]; then 273 | format=jpeg 274 | valid=1 275 | elif [ "$format" = "jpeg" ]; then 276 | formatExtension=jpg 277 | valid=1 278 | elif [ "$format" = "tif" ]; then 279 | format=tiff 280 | formatExtension=tiff 281 | valid=1 282 | elif [ "$format" = "tiff" ]; then 283 | valid=1 284 | elif [ "$format" = "png" ]; then 285 | valid=1 286 | fi 287 | 288 | if ! [ $valid -eq 1 ]; then 289 | echo "Invalid image format selected: $format -- exiting" 290 | exit 1 291 | fi 292 | fi 293 | 294 | # Output the source and destination directories 295 | if [ "$noMessages" -eq 0 ]; then 296 | echo "Source: $(realpath $sourcePath)" 297 | echo "Target: $(realpath $destPath)" 298 | if [ $doRes -eq 1 ]; then 299 | echo "New DPI: $dpi" 300 | fi 301 | fi 302 | 303 | # FROM 5.1.4 304 | # Auto-enable 'keep files' if the source and destination are the same 305 | if [ "$sourcePath" = "$destPath" ]; then 306 | deleteSource=0 307 | fi 308 | 309 | # From 5.2.4 310 | destPath=$(realpath $destPath) 311 | 312 | # FROM 5.1.0 313 | # Check for a single input file 314 | if [ -f "$sourcePath" ]; then 315 | # Process the single input file 316 | processFile "$sourcePath" 317 | elif [ -d "$sourcePath" ]; then 318 | # Process the contents of the supplied directory 319 | for file in "$sourcePath"/*; do 320 | if [ -f "$file" ]; then 321 | processFile "$file" 322 | fi 323 | done 324 | else 325 | echo "[ERROR] $sourcePath is not a valid directory -- ignoring " 326 | fi 327 | 328 | # Present a task report, if requested 329 | if [ "$noMessages" -eq 0 ]; then 330 | if [ $fileCount -eq 1 ]; then 331 | echo "1 file converted" 332 | elif [ $fileCount -gt 1 ]; then 333 | echo "$fileCount files converted" 334 | else 335 | echo "No files converted" 336 | fi 337 | fi 338 | -------------------------------------------------------------------------------- /archive/miconprep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | # NOTE You may need to change the above line to /bin/bash 3 | 4 | # Prep macOS App Icons 5 | # 6 | # Version 1.0.1 7 | 8 | # Function to show help info - keeps this out of the code 9 | function showHelp() { 10 | echo -e "\nmacOS Icon Maker\n" 11 | echo -e "Usage:\n miconprep [-p path]\n" 12 | echo "Options:" 13 | echo " -p / --path [path] The path to the images. Default: current working directory" 14 | echo " -h / --help This help screen" 15 | echo 16 | } 17 | 18 | # Set inital state values 19 | path=$"HOME"/dummy.png 20 | target="$HOME"/Desktop 21 | argIsAValue=0 22 | args=(-p) 23 | sizes=(16 32 64 96 128 256 512 1024) 24 | 25 | # Process the arguments 26 | argCount=0 27 | for arg in "$@" 28 | do 29 | if [[ $argIsAValue -gt 0 ]]; then 30 | # The argument should be a value (previous argument was an option) 31 | if [[ ${arg:0:1} = "-" ]]; then 32 | # Next value is an option: ie. missing value 33 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 34 | exit 1 35 | fi 36 | 37 | # Set the appropriate internal value 38 | case "$argIsAValue" in 39 | 1) path=$arg ;; 40 | *) echo "Error: Unknown argument" exit 1 ;; 41 | esac 42 | 43 | argIsAValue=0 44 | else 45 | if [[ $arg = "-p" || $arg = "--path" ]]; then 46 | argIsAValue=1 47 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 48 | showHelp 49 | exit 0 50 | fi 51 | fi 52 | 53 | ((argCount++)) 54 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 55 | echo "Error: Missing value for $arg" 56 | exit 1 57 | fi 58 | done 59 | 60 | if [ -f "$path" ]; then 61 | # Get the extension and make it uppercase for testing 62 | extension=${path##*.} 63 | ext_test=${extension^^*} 64 | 65 | # Make sure the file's of the right type 66 | if [[ $ext_test = "PNG" || $ext_test = "JPG" || $ext_test = "JPEG" ]]; then 67 | for size in ${sizes[@]}; do 68 | newname="$target"/icon_"$size.$extension" 69 | cp "$path" "$newname" 70 | sips "$newname" -Z "$size" -i > /dev/null 71 | done 72 | else 73 | echo "Source image must be a PNG or JPG. It is a $extension" 74 | exit 1 75 | fi 76 | else 77 | echo "Source image $path can't be found" 78 | exit 1 79 | fi 80 | -------------------------------------------------------------------------------- /archive/pi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pi Image Installation 4 | # Version 1.0.5 5 | 6 | url=https://downloads.raspberrypi.org/raspbian_latest 7 | 8 | clear 9 | echo "macOS Raspberry Pi Image Installer with optional WiFi setup" 10 | read -n 1 -s -p "Install image on a standard Pi [P] or a Pi Zero [Z] image " choice 11 | echo 12 | 13 | choice=${choice^^*} 14 | 15 | if [[ "$choice" != "P" && "$choice" != "Z" ]]; then 16 | exit 0 17 | fi 18 | 19 | pitype=Zero 20 | 21 | if [[ "$choice" = "P" ]]; then 22 | pitype=Standard 23 | fi 24 | 25 | if ! [[ -e "tmp" ]]; then 26 | mkdir tmp 27 | fi 28 | 29 | cd tmp || exit 1 30 | 31 | if ! [ -e "p.img" ]; then 32 | echo "Downloading Raspberry Pi $pitype OS image... " 33 | curl -O -L -# "$url" 34 | 35 | read -p "Enter SHA 256 or [ENTER] to bypass this check " choice 36 | if [ -n "$choice" ]; then 37 | sha=$(shasum -a 256 raspbian_latest) 38 | echo "Download SHA 256: $sha" 39 | echo " Entered SHA 256: $choice" 40 | if [ "$choice" = "$sha" ]; then 41 | echo "MATCH" 42 | else 43 | echo "SHAs do not match -- do not proceed with this file" 44 | exit 1 45 | fi 46 | fi 47 | 48 | echo "Decompressing Raspberry Pi $pitype OS image... " 49 | mv raspbian_latest r.zip 50 | unzip r.zip 51 | mv *.img p.img 52 | fi 53 | 54 | read -n 1 -s -p "Insert SD card and press any key when it has appeared on the desktop " 55 | echo 56 | 57 | ok=0 58 | while [ $ok -eq 0 ]; do 59 | echo "Disk list... " 60 | diskutil list external 61 | 62 | read -p "Enter the SD card's disk number (eg. 2 for /dev/disk2) " disknum 63 | 64 | if [ -z "$disknum" ]; then 65 | echo "Invalid disk number -- exiting " 66 | exit 1 67 | fi 68 | 69 | unmountname="/dev/disk$disknum" 70 | ddname="/dev/rdisk$disknum" 71 | 72 | if diskutil unmountdisk "$unmountname"; then 73 | 74 | if [ -e "p.img" ]; then 75 | echo "About to copy Raspberry Pi $pitype OS image to SD card $unmountname... " 76 | 77 | read -n 1 -s -p "Are you sure? [Y]es, [N]o or [C]ancel Setup" key 78 | echo 79 | 80 | if [ ${key^^*} != "Y" ]; then 81 | if [ ${key^^*} = "C" ]; then 82 | exit 0 83 | else 84 | continue 85 | fi 86 | fi 87 | 88 | echo "Copying Raspberry Pi $pitype OS image to SD card $unmountname... " 89 | sudo dd if=p.img of="$ddname" bs=1m 90 | else 91 | echo "Missing Pi img file -- aborting" 92 | exit 1 93 | fi 94 | 95 | read -n 1 -s -p "Press any key when 'boot' has appeared on the desktop " 96 | echo 97 | 98 | if ! [ -e "/Volumes/boot/ssh" ]; then 99 | echo "Enabling SSH... " 100 | touch "/Volumes/boot/ssh" 101 | fi 102 | 103 | read -p "Enter your WiFi SSID " ssid 104 | if [ -n "$ssid" ]; then 105 | read -p "Enter your WiFi password " psk 106 | echo "Setting up WiFi... SSID: \"$ssid\", PWD: \"$psk\"" 107 | echo -e "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\ncountry=GB\n\nnetwork={\n ssid=\"$ssid\"\n psk=\"$psk\"\n key_mgmt=WPA-PSK\n}" > "/Volumes/boot/wpa_supplicant.conf" 108 | fi 109 | 110 | echo "Copying setup script to /boot... " 111 | src="$GIT/scripts/pinstall.sh" 112 | if [ "$choice" = "Z" ]; then 113 | src="$GIT/scripts/zinstall.sh" 114 | fi 115 | cp "$src" /Volumes/boot 116 | 117 | echo "Cleaning up... " 118 | diskutil unmountdisk /Volumes/boot 119 | cd .. 120 | rm -r tmp 121 | ok=1 122 | fi 123 | done 124 | 125 | echo Done 126 | -------------------------------------------------------------------------------- /archive/setupmac.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Mac install script 4 | # Version 1.1.0 5 | 6 | # Do intro 7 | clear 8 | echo "macOS Install Script 1.1.0" 9 | 10 | # Set exit-on-failure 11 | set -e 12 | 13 | # Update macOS 14 | sudo softwareupdate --install --all 15 | 16 | # Apply preferred Energy Saver settings 17 | sudo pmset -a lessbright 0 18 | sudo pmset -a disksleep 10 19 | sudo pmset -b displaysleep 15 20 | sudo pmset -b sleep 15 21 | sudo pmset -b powernap 0 22 | sudo pmset -c displaysleep 60 23 | sudo pmset -c sleep 60 24 | sudo pmset -c powernap 1 25 | 26 | # Ask for and set the machine's machine name 27 | read -p "Enter your preferred hostname " hostname 28 | if [ -n "$hostname" ]; then 29 | echo -e "\nSetting machine name to $hostname" 30 | sudo scutil --set HostName "$hostname" 31 | sudo scutil --set LocalHostName "$hostname" 32 | sudo scutil --set ComputerName "$hostname" 33 | sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName -string "$hostname" 34 | dscacheutil -flushcache 35 | fi 36 | 37 | # Set dark mode 38 | osascript -e 'tell application "System Events" to tell appearance preferences to set dark mode to true' 39 | 40 | # Clean up Home folder items 41 | echo -n "Hiding Home folder items: " 42 | chflags hidden "$HOME/Movies" 43 | echo -n "Movies, " 44 | chflags hidden "$HOME/Public" 45 | echo "Public" 46 | echo "Showing the Library folder..." 47 | chflags nohidden "$HOME/Library" 48 | 49 | # Set up git and clone key repos 50 | echo "Preparing Git..." 51 | xcode-select --install 52 | target="$HOME/GitHub" 53 | if ! [ -e "$target" ]; then 54 | mkdir "$target" 55 | fi 56 | 57 | cd "$target" || exit 1 58 | git clone https://github.com/smittytone/scripts.git 59 | git clone https://github.com/smittytone/dotfiles.git 60 | 61 | # Run the app settings script 62 | # FROM 1.0.5 correct called script's name 63 | "$target/scripts/updatemac.sh --full" 64 | 65 | # Restart Finder and Dock to effect changes 66 | killall Finder Dock 67 | 68 | # Install applications... brew first 69 | echo "Installing Brew... " 70 | if /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; then 71 | echo "Installing Brew-sourced Utilities... " 72 | apps=("bash" "nano" "coreutils" "gitup" "jq" "ncurses" "readline" "shellcheck" "libdvdcss" "node" "python3" "hugo") 73 | for app in "${apps[@]}"; do 74 | brew install "$app" 75 | done 76 | 77 | echo "Installing Applications... " 78 | apps=("handbrake" "vlc" "skype" "firefox" "omnidisksweeper") 79 | for app in "${apps[@]}"; do 80 | brew cask install "$app" 81 | done 82 | fi 83 | 84 | echo "Installing Cocoapods (requires authorizaton)... " 85 | sudo gem install cocoapods 86 | 87 | echo "Installing Pylint... " 88 | pip3 install pylint 89 | 90 | read -n 1 -s -p "Press [ENTER] to open websites for other app downloads, or [S] to skip " key 91 | echo 92 | # Make argument lowercase 93 | key=${key,,} 94 | if [ "$key" != "s" ]; then 95 | open http://www.dropbox.com 96 | open http://www.barebones.com 97 | open https://desktop.github.com 98 | open http://www.rogueamoeba.com/piezo 99 | open https://www.bresink.com/osx/TinkerTool/download.php 100 | open http://www.audacityteam.org/download/mac/ 101 | #open http://www.skype.com 102 | #open http://handbrake.fr 103 | #open http://www.mozilla.org 104 | #open http://www.skype.com 105 | #open http://www.videolan.org 106 | fi 107 | 108 | read -n 1 -s -p "Press [ENTER] to open the App Store, or [S] to skip " key 109 | echo 110 | key=${key,,} 111 | if [ "$key" != "s" ]; then 112 | open "/Applications/App Store.app" 113 | fi 114 | 115 | read -n 1 -s -p "Connect drive '2TB-APFS' and press [ENTER] to copy music, or [S] to skip " key 116 | echo 117 | key=${key,,} 118 | if [ "$key" != "s" ]; then 119 | cp -R /Volumes/2TB-APFS/Music "$HOME/Music" 120 | fi 121 | 122 | read -n 1 -s -p "Press [ENTER] to finish " 123 | echo 124 | 125 | echo "Cleaning up... " 126 | brew cleanup 127 | 128 | echo "Done" 129 | -------------------------------------------------------------------------------- /archive/ticonprep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | # NOTE You may need to change the above line to /bin/bash 3 | 4 | # Prep macOS App Toolbar Icons 5 | # 6 | # Version 1.0.0 7 | 8 | # Function to show help info - keeps this out of the code 9 | function showHelp() { 10 | echo -e "\nmacOS Toolbar Icon Maker\n" 11 | echo -e "Usage:\n ticonprep [-p path]\n" 12 | echo "Options:" 13 | echo " -p / --path [path] The path to the images. Default: current working directory" 14 | echo " -h / --help This help screen" 15 | echo 16 | } 17 | 18 | # Set inital state values 19 | path=$"HOME"/dummy.png 20 | target="$HOME"/Desktop 21 | argIsAValue=0 22 | args=(-p) 23 | sizes=(32 64 96) 24 | 25 | # Process the arguments 26 | argCount=0 27 | for arg in "$@" 28 | do 29 | if [[ $argIsAValue -gt 0 ]]; then 30 | # The argument should be a value (previous argument was an option) 31 | if [[ ${arg:0:1} = "-" ]]; then 32 | # Next value is an option: ie. missing value 33 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 34 | exit 1 35 | fi 36 | 37 | # Set the appropriate internal value 38 | case "$argIsAValue" in 39 | 1) path=$arg ;; 40 | *) echo "Error: Unknown argument" exit 1 ;; 41 | esac 42 | 43 | argIsAValue=0 44 | else 45 | if [[ $arg = "-p" || $arg = "--path" ]]; then 46 | argIsAValue=1 47 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 48 | showHelp 49 | exit 0 50 | fi 51 | fi 52 | 53 | ((argCount++)) 54 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 55 | echo "Error: Missing value for $arg" 56 | exit 1 57 | fi 58 | done 59 | 60 | if [ -f "$path" ]; then 61 | # Get the extension and make it uppercase for testing 62 | filename="${path##*/}" 63 | extension=${filename##*.} 64 | filename="${filename%.*}" 65 | ext_test=${extension^^*} 66 | 67 | # Make sure the file's of the right type 68 | if [[ $ext_test = "PNG" || $ext_test = "JPG" || $ext_test = "JPEG" ]]; then 69 | count=0 70 | for size in ${sizes[@]}; do 71 | sizemark="" 72 | if [ $count -eq 1 ]; then 73 | sizemark='@2x' 74 | fi 75 | if [ $count -eq 2 ]; then 76 | sizemark='@3x' 77 | fi 78 | newname="$target/$filename$sizemark.$extension" 79 | cp "$path" "$newname" 80 | sips "$newname" -Z "$size" -i > /dev/null 81 | ((count++)) 82 | done 83 | else 84 | echo "Source image must be a PNG or JPG. It is a $extension" 85 | exit 1 86 | fi 87 | else 88 | echo "Source image $path can't be found" 89 | exit 1 90 | fi 91 | -------------------------------------------------------------------------------- /archive/todisk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Backup to Disk Script 4 | # Version 2.2.0 5 | 6 | target_vol=2TB-APFS 7 | doMusic=1 8 | doBooks=1 9 | d_sources=("/Comics" "/OneDrive/eBooks") 10 | m_sources=("/Music/Alternative" "/Music/Classical" "/Music/Comedy" "/Music/Doctor Who" 11 | "/Music/Electronic" "/Music/Folk" "/Music/Pop" "/Music/Metal" "/Music/Rock" 12 | "/Music/SFX" "/Music/Singles" "/Music/Soundtracks" "/Music/Spoken Word") 13 | 14 | # Functions 15 | function doSync { 16 | # Sync the source to the target 17 | # Arg 1 should be the source directory 18 | # Arg 2 should be the target directory 19 | name="${1##*/}" 20 | echo -n "Syncing $name" 21 | 22 | # Prepare a readout of changed files ONLY (rsync does not do this) 23 | list=$(rsync -az "$HOME/$1" "$2" --itemize-changes --exclude ".DS_Store") 24 | lines=$(grep '>' < <(echo -e "$list")) 25 | 26 | # Check we have files to report 27 | if [ -n "$lines" ]; then 28 | # Files were sync'd so count the total number 29 | count=0 30 | while IFS= read -r line; do 31 | ((count++)) 32 | done <<< "$lines" 33 | echo "... $count files changed:" 34 | # Output the files changed 35 | while IFS= read -r line; do 36 | trimline=$(echo "$line" | cut -c 11-) 37 | if [ -n "$trimline" ]; then 38 | echo " /$trimline" 39 | fi 40 | done <<< "$lines" 41 | else 42 | echo "... no files changed" 43 | fi 44 | } 45 | 46 | # Runtime start 47 | # Process the arguments 48 | argCount=0 49 | for arg in "$@"; do 50 | if [[ ${arg,,} = "--books" || ${arg,,} = "-b" ]]; then 51 | doMusic=0 52 | ((argCount++)) 53 | elif [[ ${arg,,} = "--music" || ${arg,,} = "-m" ]]; then 54 | doBooks=0 55 | ((argCount++)) 56 | elif [[ ${arg,,} = "--help" || ${arg,,} = "-h" ]]; then 57 | echo -e "todisk.sh\n" 58 | echo -e "Usage:\n" 59 | echo -e " todisk.sh [-m] [-b] []\n" 60 | echo -e "sOptions:\n" 61 | echo " -m / --music Backup music only. Default: backup both" 62 | echo " -b / --books Backup eBooks only. Default: backup both" 63 | echo " Optional drive name. Default: 2TB-APFS" 64 | echo 65 | exit 0 66 | else 67 | target_vol=$arg 68 | fi 69 | done 70 | 71 | # Set the target path based on supplied disk name (or default) 72 | target_path="/Volumes/$target_vol" 73 | 74 | # Check that the user is not exluding both jobs 75 | if [[ $doBooks -eq 0 && $doMusic -eq 0 ]]; then 76 | echo "Mutually exclusive switches set -- backup cannot continue" 77 | exit 1 78 | fi 79 | 80 | # If no switches were specified, assume we're running interactively 81 | # and invite the user to continue at their own pace 82 | if [ $argCount -eq 0 ]; then 83 | clear 84 | echo "Backup to Disk" 85 | read -n 1 -s -p "Connect '$target_vol' then press [ENTER] when it has mounted" 86 | echo 87 | fi 88 | 89 | # Make sure the target disk is mounted 90 | if [ -d "$target_path" ]; then 91 | echo "Disk '$target_vol' mounted." 92 | 93 | # Sync document sources 94 | if [ $doBooks -eq 1 ]; then 95 | for source in "${d_sources[@]}"; do 96 | doSync "$source" "$target_path" 97 | done 98 | fi 99 | 100 | # Sync music sources 101 | if [ $doMusic -eq 1 ]; then 102 | for source in "${m_sources[@]}"; do 103 | doSync "$source" "$target_path/Music" 104 | done 105 | fi 106 | else 107 | echo "Disk '$target_vol' is not mounted." 108 | fi 109 | -------------------------------------------------------------------------------- /archive/toserver.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Backup to Server Script 4 | # Version 4.0.0 5 | 6 | count=0 7 | success1=99 8 | success2=99 9 | musicMounted=0 10 | homeMounted=0 11 | doBooks=1 12 | doMusic=1 13 | server="NONE" 14 | serverAuth=~/.config/sync/bookmarks 15 | d_sources=("/Documents/Comics" "/OneDrive/eBooks") 16 | m_sources=("/Music/Alternative" "/Music/Classical" "/Music/Comedy" "/Music/Doctor Who" 17 | "/Music/Electronic" "/Music/Folk" "/Music/Pop" "/Music/Metal" "/Music/Rock" 18 | "/Music/SFX" "/Music/Singles" "/Music/Soundtracks" "/Music/Spoken Word") 19 | 20 | # From 4.0.0 21 | # Functions 22 | function doSync { 23 | # Sync the source to the target 24 | # Arg 1 should be the source directory 25 | # Arg 2 should be the target directory 26 | name="${source##*/}" 27 | echo -n "$name" 28 | 29 | # Prepare a readout of changed files ONLY (rsync does not do this) 30 | list=$(rsync -az "$HOME/$1" "$2" --itemize-changes --exclude ".*") 31 | lines=$(grep '>' < <(echo -e "$list")) 32 | 33 | # Check we have files to report 34 | if [ -n "$lines" ]; then 35 | # Files were sync'd so count the total number 36 | count=0 37 | while IFS= read -r line; do 38 | ((count++)) 39 | done <<< "$lines" 40 | echo "... $count files changed:" 41 | # Output the files changed 42 | while IFS= read -r line; do 43 | trimline=$(echo "$line" | cut -c 11-) 44 | if [ -n "$trimline" ]; then 45 | echo "/$trimline" 46 | fi 47 | done <<< "$lines" 48 | else 49 | echo "... no files changed" 50 | fi 51 | } 52 | 53 | # Check for either of the two possible switches: 54 | # --books - Backup the 'books' job only 55 | # --music - Backup the 'music' job only 56 | # --server - Address of the target server, eg. '192.168.0.1' or 'server.local' 57 | # NOTE 'argCount' is a flag that stays 0 if no switches were included 58 | argCount=0 59 | argIsValue=0 60 | for arg in "$@" 61 | do 62 | if [ $argIsValue -eq 1 ]; then 63 | if [ $argCount -eq 1 ]; then 64 | server="$arg" 65 | fi 66 | else 67 | if [ $arg = "--books" ]; then 68 | doMusic=0 69 | ((argCount++)) 70 | elif [ $arg = "--music" ]; then 71 | doBooks=0 72 | ((argCount++)) 73 | elif [ $arg = "--server" ]; then 74 | argIsValue=1 75 | ((argCount++)) 76 | else 77 | echo "Error: Unknown argument ($arg)" 78 | exit 1 79 | fi 80 | fi 81 | done 82 | 83 | # Check that the user is not exluding both jobs 84 | if [[ $doBooks -eq 0 && $doMusic -eq 0 ]]; then 85 | echo "Mutually exclusive switches set -- backup cannot continue" 86 | exit 1 87 | fi 88 | 89 | # Check the 'bookmarks' file is present 90 | if ! [ -f $serverAuth ]; then 91 | echo "No server auth file found -- backup cannot continue" 92 | exit 1 93 | fi 94 | 95 | # From 3.0.0 96 | # Check for a valid server 97 | if [ $server == "NONE" ]; then 98 | echo "No server addrss supplied -- backup cannot continue" 99 | exit 1 100 | fi 101 | 102 | # If no switches were specified, assume we're running interactively 103 | # and invite the user to continue at their own pace 104 | if [ $argCount -eq 0 ]; then 105 | clear 106 | echo "Backup to Server" 107 | read -n 1 -s -p "Press [ENTER] to start " 108 | echo 109 | fi 110 | 111 | # Read in the server auth file lines to make sure there IS a line to read 112 | while IFS= read -r line; do 113 | # Make the mount point 114 | if ! [ -d mntpoint ]; then 115 | echo "Making mntpoint..." 116 | mkdir mntpoint 117 | fi 118 | 119 | # If we're doing the 'music' backup job, mount the relevant server store 120 | # and flag that it is mounted 121 | if [ $doMusic -eq 1 ]; then 122 | if ! [ -d mntpoint/music ]; then 123 | echo "Making mntpoint/music..." 124 | mkdir mntpoint/music 125 | echo "Mounting mntpoint/music..." 126 | if mount -t smbfs "//$line@$server/music" mntpoint/music; then 127 | musicMounted=1 128 | fi 129 | fi 130 | fi 131 | 132 | # If we're doing the 'books' backup job, mount the relevant server store 133 | # and flag that it is mounted 134 | if [ $doBooks -eq 1 ]; then 135 | if ! [ -d mntpoint/home ]; then 136 | echo "Making mntpoint/home..." 137 | mkdir mntpoint/home 138 | echo "Mounting mntpoint/home..." 139 | if mount -t smbfs "//$line@$server/home" mntpoint/home; then 140 | homeMounted=1 141 | fi 142 | fi 143 | fi 144 | 145 | ((count++)) 146 | done < $serverAuth 147 | 148 | # No server auth lines read? Then bail 149 | if [ $count -eq 0 ]; then 150 | echo "No bookmarks present -- backup cannot continue" 151 | exit 1 152 | fi 153 | 154 | # Run the 'books' backup job 155 | if [[ -d mntpoint/home && $homeMounted -eq 1 ]]; then 156 | echo "Backing-up Comics and Books..." 157 | for source in "${d_sources[@]}"; do 158 | doSync "$source" mntpoint/home 159 | #rsync -avz "$HOME/$source" mntpoint/home --exclude ".*" 160 | done 161 | fi 162 | 163 | # Run the 'music' backup job 164 | if [[ -d mntpoint/music && $musicMounted -eq 1 ]]; then 165 | echo "Backing-up Music..." 166 | for source in "${m_sources[@]}"; do 167 | doSync "$source" mntpoint/music 168 | #rsync -avz "$HOME/$source" mntpoint/music --exclude ".*" 169 | done 170 | fi 171 | 172 | # If no switches were specified, assume we're running interactively 173 | # and invite the user to continue at their own pace 174 | if [ $argCount -eq 0 ]; then 175 | read -n 1 -s -p "Press [ENTER] to finish " 176 | echo 177 | fi 178 | 179 | # If we mounted the music store, unmount it now 180 | # Exit with an error if we can't 181 | if [ $musicMounted -eq 1 ]; then 182 | echo "Dismounting mntpoint/music..." 183 | umount mntpoint/music 184 | success1=$? 185 | 186 | if [ $success1 -ne 0 ]; then 187 | echo ~+"/mntpoint/music failed to unmount -- please unmount it manually and remove the mointpoint" 188 | exit 1 189 | fi 190 | else 191 | success1=0 192 | fi 193 | 194 | # If we mounted the books store, unmount it now 195 | # Exit with an error if we can't 196 | if [ $homeMounted -eq 1 ]; then 197 | echo "Dismounting mntpoint/home..." 198 | umount mntpoint/home 199 | success2=$? 200 | 201 | if [ $success2 -ne 0 ]; then 202 | echo ~+"/mntpoint/home failed to unmount -- please unmount it manually and remove the mointpoint" 203 | exit 1 204 | fi 205 | else 206 | success2=0 207 | fi 208 | 209 | # Make sure the unmount operations succeeded, warning if not 210 | if [[ $success1 -eq 0 && $success2 -eq 0 ]]; then 211 | # Remove the share mount points if both unmounts were successful 212 | echo "Removing mntpoint..." 213 | rm -r mntpoint 214 | else 215 | echo "Could not remove mntpoint -- exiting" 216 | exit 1 217 | fi 218 | 219 | echo Done 220 | -------------------------------------------------------------------------------- /archive/updatemac.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # updatemac.sh 5 | # 6 | # Update local Macs user config files 7 | # (eg. between multiple machines) 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-20, Tony Smith 11 | # @version 2.0.1 12 | # @license MIT 13 | # 14 | 15 | 16 | source="$HOME/documents/github/dotfiles" 17 | target="$HOME/Library" 18 | 19 | if ! [ -e "$source" ]; then 20 | echo "Please clone the repo \'dotfiles\' before proceeding -- exiting " 21 | exit 1 22 | fi 23 | 24 | # Process any arguments 25 | # NOTE P == Partial, ie. only update frequently used app configs 26 | # F == Full, ie. update frequently used app configs AND apply 27 | # macOS configurations AND occasional use app configs 28 | choice="ASK" 29 | for arg in "$@"; do 30 | theArg=${arg^^*} 31 | if [[ $theArg = "-F" || $theArg = "--FULL" ]]; then 32 | choice="F" 33 | elif [[ $theArg = "-P" || $theArg = "--PARTIAL" ]]; then 34 | choice="P" 35 | fi 36 | done 37 | 38 | # No valid arguments passed, so ask the user for the type of update 39 | 40 | if [ "$choice" = "ASK" ]; then 41 | read -n 1 -s -p "Full [F] or partial [P] update? " choice 42 | if [ -z "$choice" ]; then 43 | echo "Cancelling..." 44 | exit 0 45 | fi 46 | fi 47 | 48 | choice=${choice^^*} 49 | if [[ "$choice" != "F" && "$choice" != "P" ]]; then 50 | echo "Invalid option selected: '$choice' -- cancelling... " 51 | exit 1 52 | fi 53 | 54 | # The following are items that are likely to change often 55 | echo "Updating primary config files... " 56 | 57 | # bash profile 58 | # FROM 2.0.1 rename saved file 59 | cp -v "$source/mac_bash_profile" "$HOME/.bash_profile" 60 | 61 | # FROM 2.0.1 62 | # ZSH rc file 63 | cp -v "$source/mac_zshrc" "$HOME/.zshrc" 64 | 65 | # nano rc file 66 | cp -v "$source/nanorc" "$HOME/.nanorc" 67 | 68 | # vscode settings 69 | cp -v "$source/vs_settings.json" "$target/Application Support/Code/User/settings.json" 70 | 71 | # gitup config 72 | if ! [ -e "$HOME/.config/gitup" ]; then 73 | echo "Adding ~/.config/gitup... " 74 | mkdir -p "$HOME/.config/gitup" 75 | fi 76 | cp -v "$source/bookmarks" "$HOME/.config/gitup/bookmarks" 77 | 78 | if ! [ -e "$HOME/.config/git" ]; then 79 | echo "Adding ~/.config/git... " 80 | mkdir -p "$HOME/.config/git" 81 | fi 82 | 83 | # git global exclude file 84 | # Added it to partial install in 1.0.3 85 | if cp -v "$source/gitignore_global" "$HOME/.config/git/gitignore_global"; then 86 | # Add a reference to the file to git (assumes git installed) 87 | git config --global core.excludesfile "$HOME/.config/git/gitignore_global" 88 | fi 89 | 90 | # The following are items that won't be overwritten. They are very, very unlikely 91 | # to be changed once installed in the first place 92 | if [ "$choice" = "F" ]; then 93 | echo "Updating additional config files... " 94 | # FROM 1.2.0 -- Don't copy FFMPEG under Catalina 95 | # cp -nvR "$source/ffmpeg/" "$target/ffmpeg" 96 | cp -nvR "$source/Services/Copy File Path.workflow" "$target/Services/Copy File Path.workflow" 97 | 98 | # FROM 1.2.1 -- fix duplication of files vs folders 99 | cp -nvR "$source/LaunchAgents/" "$target/LaunchAgents" 100 | cp -nvR "$source/Quicklook/" "$target/Quicklook" 101 | 102 | # FROM 1.3.0 -- add ~/Library/Filters folder (custom Quartz filters) 103 | cp -nvR "$source/Filters/" "$target/Filters" 104 | 105 | # FROM 1.1.0 -- Don't bother with BBEdit for now 106 | # cp -nvR "$source/bbedit_squirrel.plist" "$target/Application Support/BBEdit/Language Modules/Squirrel.plist" 107 | 108 | # FROM 1.5.0 -- Add 64-bit libdvdcss 109 | cp -nv "$source/libdvdcss/libdvdcss.2.dylib" "/usr/local/lib/libdvdcss.2.dylib" 110 | 111 | # FROM 1.4.0 -- Add second terminal file (HomebrewMeDark) 112 | cp -nv "$source/HomebrewMe.terminal" "$HOME/Desktop/HomebrewMe.terminal" 113 | cp -nv "$source/HomebrewMeDark.terminal" "$HOME/Desktop/HomebrewMeDark.terminal" 114 | echo "Terminal settings files 'HomebrewMe' and 'HomebrewMeDark' copied to desktop. To use them, open Terminal > Preferences > Profiles and import" 115 | 116 | cp -nv "$source/pixelmator_shapes.pxs" "$HOME/Desktop/pixelmator_shapes.pxs" 117 | echo "Pixelmater shapes file 'pixelmator_shapes.pxs' copied to desktop. To use it, open Pixelmator > File > Import..." 118 | 119 | # FROM 1.1.0 120 | # Run the various macOS config scriptlets 121 | echo "Configuring macOS... " 122 | cd "$source/config" 123 | for task in *; do 124 | echo $task 125 | . "$task" 126 | done 127 | 128 | # FROM 2.0.0 129 | # Install Xcode CLI if necessary 130 | result=$(xcode-select --install 2>&1) 131 | error=$(grep 'already installed' < <(echo -e "$result")) 132 | if [ -n "$error" ]; then 133 | # CLI installed already 134 | echo "Xcode Command Line Tools installed" 135 | exit -1 136 | else 137 | # Check for opening of external installer 138 | note=$(grep 'install requested' < <(echo -e "$result")) 139 | if [ -n "$note" ]; then 140 | echo "Installing Xcode Command Line Tools... " 141 | fi 142 | fi 143 | fi 144 | 145 | echo "Configuration files updated" 146 | exit 0 147 | -------------------------------------------------------------------------------- /archive/wiconprep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | # NOTE You may need to change the above line to /bin/bash 3 | 4 | # Prep Apple Watch App Icons and Complication Icons 5 | # 6 | # Version 1.0.0 7 | 8 | 9 | # Function to show help info - keeps this out of the code 10 | function showHelp() { 11 | echo -e "\nApple Watch Icon Maker\n" 12 | echo -e "Usage:\n imagepad [-p path] [-c color] [-d c crop_height crop_width] [-d p pad_height pad_width]\n" 13 | echo "Options:" 14 | echo " -p / --path [path] The path to the images. Default: current working directory" 15 | echo " -h / --help This help screen" 16 | echo 17 | } 18 | 19 | 20 | # Set inital state values 21 | path=$"HOME"/dummy.png 22 | argIsAValue=0 23 | args=(-p) 24 | asizes=(216 196 172 100 88 87 80 58 55 48) 25 | csizes=(224 203 182 64 58 52 50 44 40 36 32) 26 | target="$HOME"/Desktop 27 | 28 | # Process the arguments 29 | argCount=0 30 | for arg in "$@" 31 | do 32 | if [[ $argIsAValue -gt 0 ]]; then 33 | # The argument should be a value (previous argument was an option) 34 | if [[ ${arg:0:1} = "-" ]]; then 35 | # Next value is an option: ie. missing value 36 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 37 | exit 1 38 | fi 39 | 40 | # Set the appropriate internal value 41 | case "$argIsAValue" in 42 | 1) path=$arg ;; 43 | *) echo "Error: Unknown argument" exit 1 ;; 44 | esac 45 | 46 | argIsAValue=0 47 | else 48 | if [[ $arg = "-p" || $arg = "--path" ]]; then 49 | argIsAValue=1 50 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 51 | showHelp 52 | exit 0 53 | fi 54 | fi 55 | 56 | ((argCount++)) 57 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 58 | echo "Error: Missing value for $arg" 59 | exit 1 60 | fi 61 | done 62 | 63 | if [ -f "$path" ]; then 64 | # Get the extension and make it uppercase 65 | extension=${path##*.} 66 | ext_test=${extension^^*} 67 | 68 | # Make sure the file's of the right type 69 | if [[ $ext_test = "PNG" || $ext_test = "JPG" || $ext_test = "JPEG" ]]; then 70 | for size in ${asizes[@]}; do 71 | newname="$target"/icon_"$size.$extension" 72 | cp "$path" "$newname" 73 | sips "$newname" -Z "$size" -i > /dev/null 74 | done 75 | 76 | for size in ${csizes[@]}; do 77 | newname="$target"/comp_"$size.$extension" 78 | cp "$path" "$newname" 79 | sips "$newname" -Z "$size" -i > /dev/null 80 | done 81 | else 82 | echo "Source image must be a PNG or JPG. It is a $extension" 83 | exit 1 84 | fi 85 | else 86 | echo "Source image $path can't be found" 87 | exit 1 88 | fi 89 | -------------------------------------------------------------------------------- /binstall.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # binstall.zsh 4 | # 5 | # Install key scripts into /usr/local/bin or alternative directory. 6 | # 7 | # NOTE Depends upon $GIT/dotfiles/Mac/keyscripts and 8 | # $GIT/scripts as source of script list and scripts, 9 | # respectively. And $GIT must be set. 10 | # 11 | # @version 1.6.1 12 | # @author Tony Smith (@smittytone) 13 | # @copyright 2022 14 | # @licence MIT 15 | 16 | app_version="1.6.1" 17 | bin_dir=/usr/local/bin 18 | source_file="$GIT/dotfiles/Mac/keyscripts" 19 | scripts_dir="$GIT/scripts" 20 | repos=() 21 | states=() 22 | versions=() 23 | typeset -i update_count=0 24 | typeset -i name_max=0 25 | typeset -i ver_max=7 26 | typeset -i do_show=1 27 | typeset -i do_list=0 28 | typeset -i source_changed=0 29 | typeset -i scripts_changed=0 30 | typeset -i bin_changed=0 31 | 32 | # FROM 1.5.0 33 | # Colours 34 | green=$(tput setaf 2) 35 | yellow=$(tput setaf 3) 36 | white=$(tput setaf 7 && tput bold) 37 | normal=$(tput sgr0) 38 | bar="${white}|${normal}" 39 | 40 | # FROM 1.1.0 41 | # Display the version if requested 42 | get_version() { 43 | script=$(cat "${1}") 44 | result=$(grep '# @version' < <(echo -e "${script}")) 45 | result=$(tr -s " " < <(echo -e "${result}")) 46 | version=$(echo "${result}" | cut -d " " -s -f 3) 47 | repos+=(${1:t}) 48 | versions+=(${version}) 49 | 50 | if [[ "$2" == "N" ]]; then 51 | states+=("${green}Up to date${normal}") 52 | else 53 | states+=("${yellow}Updated ${normal}") 54 | ((update_count++)) 55 | fi 56 | 57 | # Set the version number and app name max column width 58 | if [[ ${#1:t} -gt ${name_max} ]] name_max=${#1:t} 59 | if [[ ${#version:t} -gt ${ver_max} ]] ver_max=${#version:t} 60 | } 61 | 62 | # FROM 1.3.0 63 | # Print the main output table header 64 | # FROM 1.4.1 -- span column three to max version string size, 65 | # pass in string lengths 66 | print_header_main() { 67 | printf "${white}| %-*s | %-*s | %-10s |\n+-" ${1} Utility ${2} Version State 68 | print_mid ${1} ${2} 69 | printf "-+------------+${normal}\n" 70 | } 71 | 72 | # FROM 1.4.0 73 | # Print the list output table header 74 | # FROM 1.4.1 -- pass in string lengths 75 | print_header_list() { 76 | printf "${white}| %-*s | %-*s |\n+-" ${1} Utility ${2} Version 77 | print_mid ${1} ${2} 78 | printf "-+${normal}\n" 79 | } 80 | 81 | print_mid() { 82 | printf "-%.0s" {0..${1}} 83 | printf "+" 84 | printf "-%.0s" {0..${2}} 85 | } 86 | 87 | print_err_and_exit() { 88 | print_err ${1} 89 | exit 1 90 | } 91 | 92 | print_err() { 93 | echo "[ERROR] ${1}" 94 | } 95 | 96 | # FROM 1.6.0 97 | print_message() { 98 | if [[ $do_show -eq 1 ]] echo "${1}" 99 | } 100 | 101 | show_version() { 102 | echo "binstall ${app_version}" 103 | } 104 | show_help() { 105 | echo 106 | show_version 107 | echo 108 | echo -e "Usage:\n binstall [-l] [-q] [-v] [-h] [-t path/to/install/directory]\n" 109 | echo "Options:" 110 | echo " -l / --list Just list scripts current states" 111 | echo " -q / --quiet Don't show script version and state information" 112 | echo " -v / --version Display the utility's version and exit" 113 | echo " -t / --target [path] Specify an install directory. Default: /usr/local/bin" 114 | echo " -f / --file [path] Specify a script-list file. Default: [git]/dotfiles/Mac/keyscripts" 115 | echo " -s / --source [path] Specify a script source directory. Default: [git]/scripts" 116 | echo " -h / --help This help screen" 117 | echo 118 | } 119 | 120 | # Parse args 121 | typeset -i arg_value=0 122 | typeset -i arg_count=0 123 | for arg in "$@"; do 124 | larg=${arg:l} 125 | if [[ $arg_value -gt 0 ]]; then 126 | # The argument should be a value (previous argument was an option) 127 | if [[ ${arg:0:1} = "-" ]]; then 128 | # Next value is an option: ie. missing value 129 | print_err_and_exit "Missing value for ${args[((arg_value - 1))]}" 130 | fi 131 | 132 | # Set the appropriate internal value 133 | case $arg_value in 134 | 1) bin_dir="${arg}" && bin_changed=1 ;; 135 | 2) scripts_dir="${arg}" && scripts_changed=1 ;; 136 | 3) source_file="${arg}" && source_changed=1 ;; 137 | *) print_err_and_exit "Unknown argument" ;; 138 | esac 139 | 140 | arg_value=0 141 | else 142 | if [[ "${larg}" == "-q" || "${larg}" == "--quiet" ]]; then 143 | do_show=0 144 | elif [[ "${larg}" == "-l" || "${larg}" == "--list" ]]; then 145 | do_list=1 146 | elif [[ "${larg}" == "-t" || "${larg}" == "--target" ]]; then 147 | arg_value=1 148 | elif [[ "${larg}" == "-s" || "${larg}" == "--scripts" ]]; then 149 | arg_value=2 150 | elif [[ "${larg}" == "-f" || "${larg}" == "--file" ]]; then 151 | arg_value=3 152 | elif [[ "${larg}" == "-h" || "${larg}" == "--help" ]]; then 153 | show_help 154 | exit 0 155 | elif [[ "${larg}" == "-v" || "${larg}" == "--version" ]]; then 156 | show_version 157 | exit 0 158 | fi 159 | fi 160 | 161 | ((arg_count++)) 162 | if [[ $arg_count -eq $# && $arg_value -ne 0 ]]; then 163 | print_err_and_exit "Missing value for option ${arg}" 164 | fi 165 | done 166 | 167 | # FROM 1.5.0 168 | # Check for incompatible flags 169 | if [[ $do_list -eq 1 && $do_show -eq 0 ]]; then 170 | print_err_and_exit "Incompatable flags chosen: you can't list files quietly -- exiting" 171 | fi 172 | 173 | # Check for a bin directory and make if it's not there yet 174 | if [[ ! -e "${bin_dir}" ]]; then 175 | mkdir -p "${bin_dir}" || print_err_and_exit "Could not create ${bin_dir} -- exiting" 176 | fi 177 | 178 | # FROM 1.6.0 179 | # Messages 180 | if [[ $bin_changed -ne 0 ]] print_message "Installation directory set to ${bin_dir}" 181 | if [[ $source_changed -ne 0 ]] print_message "File list set to ${source_file}" 182 | if [[ $scripts_changed -ne 0 ]] print_message "Scripts directory set to ${scripts_dir}" 183 | 184 | # Load in the list of scripts 185 | if [[ -e "${source_file}" ]]; then 186 | if [[ -e "${scripts_dir}" ]]; then 187 | # Read in each line of 'keyscripts', each of which 188 | # is the name of a script to copy, eg. 'update.zsh' 189 | while IFS= read -r line; do 190 | target_file="${bin_dir}/${line:t:r}" 191 | 192 | # FROM 1.4.0 -- Don't copy, just get the version, 193 | # if we're just listing files 194 | if [[ $do_list -eq 0 ]]; then 195 | # FROM 1.0.1 -- check of the source and target are different 196 | # FROM 1.0.2 -- don't block install of uninstalled scripts 197 | diff_result="DO" 198 | 199 | # FROM 1.6.1 -- check source script exists 200 | if [[ -f "${scripts_dir}/${line}" ]]; then 201 | if [[ -f ${target_file} ]]; then 202 | diff_result=$(diff ${target_file} "${scripts_dir}/${line}") 203 | fi 204 | 205 | # FROM 1.0.1 206 | # Only copy if the file is different 207 | if [[ -n ${diff_result} ]]; then 208 | cp "${scripts_dir}/${line}" ${target_file} 209 | get_version ${target_file} Y 210 | else 211 | get_version ${target_file} N 212 | fi 213 | 214 | # Make the file executable 215 | chmod +x ${target_file} 216 | else 217 | # Not a 'stop script' error 218 | print_err "${line} in the file list does not reference an actual script" 219 | fi 220 | else 221 | # Just get the version for each source file 222 | if [[ -e $target_file ]]; then 223 | get_version $target_file N 224 | fi 225 | fi 226 | done < "${source_file}" 227 | else 228 | print_err_and_exit "'${scripts_dir}' does not exist... exiting" 229 | fi 230 | else 231 | print_err_and_exit "'${source_file}' does not exist... exiting" 232 | fi 233 | 234 | # Display the output 235 | # FROM 1.3.0 -- as a table 236 | # FROM 1.4.0 -- with an alternative version-only list 237 | # FROM 1.4.1 -- pass string lengths to function calls 238 | if [[ $do_show -eq 1 ]]; then 239 | if [[ $do_list -eq 0 ]]; then 240 | print_header_main ${name_max} ${ver_max} 241 | format_string="${bar} %-*s ${bar} %-*s ${bar} %-9s ${bar} \n" 242 | for (( i = 1 ; i <= ${#repos[@]} ; i++ )); do 243 | printf ${format_string} ${name_max} ${repos[i]} ${ver_max} ${versions[i]} ${states[i]} 244 | done 245 | else 246 | print_header_list ${name_max} ${ver_max} 247 | format_string="${bar} %-*s ${bar} %-*s ${bar}\n" 248 | for (( i = 1 ; i <= ${#repos[@]} ; i++ )); do 249 | printf ${format_string} ${name_max} ${repos[i]} ${ver_max} ${versions[i]} 250 | done 251 | fi 252 | else 253 | case "$update_count" in 254 | 0) echo "No scripts updated" ;; 255 | 1) echo "1 script updated" ;; 256 | *) echo "$update_count scripts updated" ;; 257 | esac 258 | fi -------------------------------------------------------------------------------- /cbz_blitzer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # cbz_bitzer.sh 5 | # 6 | # CBZ Blitzer — converts .cbz files to .pdfs 7 | # Requires pdfmaker 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-20, Tony Smith 11 | # @version 1.0.3 12 | # @license MIT 13 | # 14 | 15 | 16 | function processFolder { 17 | # Set the supplied argument as a variable 18 | currentFolder="$1" 19 | 20 | # Make sure the supplied folder exists and IS a folder 21 | if ! [[ -e "$currentFolder" || -d "$currentFolder" ]]; then 22 | return 23 | fi 24 | 25 | # How many items in the current folder? 26 | count=0 27 | for file in "$currentFolder"/*; do 28 | ((count++)) 29 | done 30 | 31 | # Iterate through the current folder's items 32 | for file in "$currentFolder"/*; do 33 | if [ -d "$file" ]; then 34 | # Item is a directory 35 | foldername=$(basename -- "$file") 36 | if [ "$foldername" = "__MACOSX" ]; then 37 | # This is an artifact from macOS-created .zips -- kill it 38 | rm -rf "$file" 39 | else 40 | # The folder is good to process 41 | processFolder "$file" 42 | fi 43 | else 44 | # Item is a file -- get its extension 45 | extension=${file##*.} 46 | extension=${extension,,} 47 | 48 | if [ "$extension" = "cbz" ]; then 49 | # File's extension is .cbz, so... 50 | echo "Processing $file..." 51 | 52 | # ...get the file's name... 53 | filename=$(basename -- "$file") 54 | newfilename=${filename%.*} 55 | 56 | # ...make a folder in the current with the file's name... 57 | mkdir "$currentFolder/$newfilename" 58 | 59 | # ...unpack the .cbz to the new folder... 60 | unzip -q "$file" -d "$currentFolder/$newfilename" 61 | 62 | # ...and change the .cbz to .zip so it's not processed again 63 | #mv "$file" "$currentFolder/$newfilename.zip" 64 | 65 | # Code for PDF-ing 66 | # Set image DPI for unpacked files 67 | imageprep.sh -q -r 300 -k -s "$currentFolder/$newfilename" -d "$currentFolder/$newfilename" 68 | # 69 | # Put prepped images into a PDF stored here 70 | pdfmaker -c 0.5 -s "$currentFolder/$newfilename" -d "$currentFolder/$newfilename.pdf" 71 | # 72 | # Remove the folder of images 73 | rm -rf "$currentFolder/$newfilename" 74 | rm "$file" 75 | fi 76 | fi 77 | done 78 | 79 | # How many items NOW in the current folder? 80 | newcount=0 81 | for file in "$currentFolder"/*; do 82 | ((newcount++)) 83 | done 84 | 85 | # Has the file count changed? If so, reprocess it 86 | # NOTE Count will change when we expand the cbz files 87 | if [ "$newcount" -ne "$count" ]; then 88 | processFolder "$currentFolder" 89 | fi 90 | } 91 | 92 | # Just start processing the contents of the current folder 93 | processFolder "$PWD" 94 | -------------------------------------------------------------------------------- /cbz_scanner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # cbz_scanner.sh 5 | # 6 | # CBZ file finder. Looks in $PWD and sub-directories 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-20, Tony Smith 10 | # @version 1.0.1 11 | # @license MIT 12 | # 13 | 14 | 15 | function processFolder { 16 | # Set the supplied argument as a variable 17 | currentFolder="$1" 18 | 19 | # Make sure the supplied folder exists and IS a folder 20 | if ! [[ -e "$currentFolder" || -d "$currentFolder" ]]; then 21 | return 22 | fi 23 | 24 | # How many items in the current folder? 25 | count=0 26 | for file in "$currentFolder"/*; do 27 | ((count++)) 28 | done 29 | 30 | # Iterate through the current folder's items 31 | for file in "$currentFolder"/*; do 32 | if [ -d "$file" ]; then 33 | # Item is a directory 34 | foldername=$(basename -- "$file") 35 | if [ "$foldername" = "__MACOSX" ]; then 36 | # This is an artifact from macOS-created .zips -- kill it 37 | echo "$file" 38 | else 39 | # The folder is good to process 40 | processFolder "$file" 41 | fi 42 | else 43 | # Item is a file -- get its extension 44 | extension=${file##*.} 45 | extension=${extension,,} 46 | 47 | if [ "$extension" = "cbz" ]; then 48 | # File's extension is .cbz, so... 49 | echo "$file..." 50 | fi 51 | fi 52 | done 53 | 54 | # How many items NOW in the current folder? 55 | newcount=0 56 | for file in "$currentFolder"/*; do 57 | ((newcount++)) 58 | done 59 | 60 | # Has the file count changed? If so, reprocess it 61 | # NOTE Count will change when we expand the cbz files 62 | if [ "$newcount" -ne "$count" ]; then 63 | processFolder "$currentFolder" 64 | fi 65 | } 66 | 67 | # Just start processing the contents of the current folder 68 | processFolder "$PWD" 69 | -------------------------------------------------------------------------------- /configmac.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # DEPENDENCIES 4 | # dofiles repo at $HOME/GitHub/dotfiles 5 | 6 | # Set up key directories 7 | file_source="$HOME/GitHub/dotfiles" 8 | file_target="$HOME/Library" 9 | 10 | if [[ -e "${file_source}" ]]; then 11 | # The following are items that are likely to change often 12 | echo "Updating primary config files... " 13 | 14 | # bash profile 15 | cp -v "${file_source}/Mac/bash_profile" "$HOME/.bash_profile" 16 | 17 | # ZSH rc file 18 | cp -v "${file_source}/Mac/zshrc_base" "$HOME/.zshrc" 19 | echo "export PICO_SDK_PATH=$HOME/GitHub/pico-sdk" > "$HOME/.exports" 20 | 21 | # nano rc file 22 | cp -v "${file_source}/Mac/nanorc" "$HOME/.nanorc" 23 | 24 | # vscode settings 25 | cp -v "${file_source}/Mac/vs_settings.json" "${file_target}/Application Support/Code/User/settings.json" 26 | 27 | # pylint rc file 28 | cp -v "${file_source}/Universal/pylintrc" "$HOME/.pylintrc" 29 | 30 | # git config 31 | if [[ ! -e "$HOME/.config/git" ]]; then 32 | echo "Adding ~/.config/git... " 33 | mkdir -p "$HOME/.config/git" || echo 'Could not add ~/.config/git' 34 | fi 35 | 36 | # git global exclude file 37 | # Added it to partial install in 1.0.3 38 | if cp -v "${file_source}/Universal/gitignore_global" "$HOME/.config/git/gitignore_global"; then 39 | # Add a reference to the file to git (assumes git installed) 40 | git config --global core.excludesfile "$HOME/.config/git/gitignore_global" 41 | fi 42 | 43 | echo "Updating additional config files... " 44 | cp -nvR "${file_source}/Mac/Services/" "${file_target}/Services" 45 | 46 | cp -nv "${file_source}/Mac/HomebrewMe.terminal" "$HOME/Desktop/HomebrewMe.terminal" 47 | cp -nv "${file_source}/Mac/HomebrewMeDark.terminal" "$HOME/Desktop/HomebrewMeDark.terminal" 48 | echo "Terminal settings files 'HomebrewMe' and 'HomebrewMeDark' copied to desktop. To use them, open Terminal > Preferences > Profiles and import" 49 | 50 | # Run the various macOS config scriptlets 51 | echo "Configuring macOS... " 52 | if cd "${file_source}/Mac/config"; then 53 | chmod +x ./* 54 | for task in *; do 55 | #echo ${task} 56 | source ${task} 57 | done 58 | fi 59 | 60 | echo "Mac configuration files updated" 61 | 62 | # Restart Finder and Dock to effect changes 63 | killall Finder Dock 64 | else 65 | echo "No dotfiles repo cloned 66 | fi -------------------------------------------------------------------------------- /cppy.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # cppy.zsh 4 | # 5 | # Copy a code file to a CircuitPython device with UF2 bootloader 6 | # 7 | # @author Tony Smith 8 | # @copyright 2022, Tony Smith 9 | # @version 1.1.0 10 | # @license MIT 11 | 12 | 13 | APP_NAME=$(basename $0) 14 | APP_NAME=${APP_NAME:t} 15 | APP_VERSION="1.1.0" 16 | 17 | # Functions 18 | show_help() { 19 | echo -e "cppy $APP_VERSION\n" 20 | echo -e "Usage:\n" 21 | echo -e " cppy ... \n" 22 | echo -e "Options:\n" 23 | echo -e " -h / --help Show this help page\n" 24 | echo -e "Description:\n" 25 | echo " Copies the specified file(s) to the CIRCUITPY drive, if mounted." 26 | echo " The first file will be renamed 'code.py', but subsequent files will" 27 | echo -e " be copied unchanged. Only .py and .mpy files will be copied.\n" 28 | exit 0 29 | } 30 | 31 | # FROM 1.0.2 32 | show_error() { 33 | echo "[ERROR] $1" 1>&2 34 | exit 1 35 | } 36 | 37 | # Runtime start 38 | local target="/volumes/CIRCUITPY" 39 | 40 | # Process the arguments 41 | local code_file="" 42 | local lib_files=() 43 | typeset -i arg_count=0 44 | 45 | for arg in "$@"; do 46 | # Temporarily convert argument to lowercase, zsh-style 47 | # And check for options first 48 | local check_arg=${arg:l} 49 | if [[ "${check_arg}" = "--help" || "${check_arg}" = "-h" ]]; then 50 | show_help 51 | exit 0 52 | fi 53 | 54 | # The arg is not an option so check its extension to 55 | # see if it is a file to copy 56 | local ext=${arg:e} 57 | if [[ "$ext" = "py" || "$ext" = "mpy" ]]; then 58 | # Only proceed for '.py' and '.mpy' files 59 | if [[ arg_count -eq 0 ]]; then 60 | # The first item is the one that will 61 | # become 'code.py' on the device 62 | code_file="${arg}" 63 | ((arg_count += 1)) 64 | else 65 | # Add a library file to the list 66 | lib_files+=("${arg}") 67 | fi 68 | fi 69 | done 70 | 71 | # Check that the drive is mounted 72 | if ! [[ -e "${target}" ]]; then 73 | show_error "${target} not mounted -- cannot continue" 74 | fi 75 | 76 | # Bail if no files were provided 77 | if [[ arg_count -eq 0 ]]; then 78 | show_error "No Python files to copy -- cannot continue" 79 | fi 80 | 81 | # Check if there are other files, eg. libs, to copy 82 | if [[ ${#lib_files[@]} -gt 0 ]]; then 83 | for file in ${lib_files}; do 84 | echo "Copying ${file} to ${target}/lib/${file:t}..." 85 | cp "${file}" "${target}/lib" 86 | done 87 | fi 88 | 89 | # Copy the primary file 90 | echo "Copying ${code_file} to ${target}/code.py..." 91 | cp "${code_file}" "${target}/code.py" -------------------------------------------------------------------------------- /cr.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # 4 | # cover rename 5 | # 6 | # Process comic cover names 7 | # 8 | # @author Tony Smith 9 | # @copyright 2023, Tony Smith 10 | # @version 1.0.0 11 | # @license MIT 12 | # 13 | 14 | 15 | declare -A months 16 | months=([january]=01 [february]=02 [march]=03 [april]=04 [may]=05 [june]=06 [july]=07 [august]=08 [september]=09 [october]=10 [november]=11 [december]=12) 17 | 18 | 19 | # Get all the entries in the working directory 20 | for file in ~+/*; do 21 | # Only process files 22 | if [ -f "${file}" ]; then 23 | # Get the extension 24 | fname=${file:t:r} 25 | extension=${file:t:e} 26 | extension=${extension:l} 27 | 28 | fyear="" 29 | fmonth="" 30 | fday="" 31 | fiss="" 32 | 33 | # Get the filename and add back the extension 34 | fiss=$(echo "${fname}" | cut -d " " -s -f 1) 35 | 36 | if [[ -n ${fiss} && ${fiss} != " " ]]; then 37 | fdate=$(echo "${fname}" | cut -d " " -s -f 2) 38 | else 39 | fdate=${fname} 40 | fi 41 | 42 | # Set $1 to anything to get the monthly version 43 | if [ -z $1 ]; then 44 | fyear=$(echo "${fdate}" | cut -d "-" -s -f 3) 45 | fyear="${fyear:0:-1}" 46 | 47 | fmonth=$(echo "${fdate}" | cut -d "-" -s -f 2) 48 | [ ${#fmonth} -eq 1 ] && fmonth="0${fmonth}" 49 | [ -z ${#fmonth} ] && fmonth="xx" 50 | 51 | fday=$(echo "${fdate}" | cut -d "-" -s -f 1) 52 | fday=${fday:1} 53 | [ ${#fday} -eq 1 ] && fday="0${fday}" 54 | [ -z ${#fday} ] && fday="xx" 55 | 56 | newfilename="${fyear}-${fmonth}-${fday}" 57 | else 58 | fmonth=$(echo "${fname}" | cut -d " " -s -f 2) 59 | fmonth="${fmonth:1}" 60 | fmonth=${fmonth:l} 61 | fmonth=${months[${fmonth}]} 62 | 63 | fyear=$(echo "${fname}" | cut -d " " -s -f 3) 64 | fyear=${fyear:0:-1} 65 | 66 | newfilename="${fyear}-${fmonth}" 67 | fi 68 | 69 | if [[ -n ${fiss} && ${fiss} != " " ]]; then 70 | [ ${#fiss} -eq 1 ] && fiss="00${fiss}" 71 | [ ${#fiss} -eq 2 ] && fiss="0${fiss}" 72 | newfilename="${newfilename} (${fiss})" 73 | else 74 | newfilename="${newfilename} (xxx)" 75 | fi 76 | 77 | newfilename="${newfilename}.${extension}" 78 | echo "${file:h}/${newfilename}" 79 | 80 | # Apply the change 81 | mv "${file}" "${file:h}/${newfilename}" 82 | fi 83 | done 84 | -------------------------------------------------------------------------------- /crc.py: -------------------------------------------------------------------------------- 1 | crc16_table = ( 2 | 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 3 | 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 4 | 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 5 | 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 6 | 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 7 | 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 8 | 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 9 | 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 10 | 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 11 | 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 12 | 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 13 | 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 14 | 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 15 | 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 16 | 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 17 | 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 18 | 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 19 | 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 20 | 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 21 | 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 22 | 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 23 | 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 24 | 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 25 | 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 26 | 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 27 | 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 28 | 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 29 | 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 30 | 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 31 | 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 32 | 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 33 | 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 34 | ) 35 | 36 | 37 | msg = "43 67 4f 31 36 79 30 53 41 37 58 72 4c 51 3d 3d" 38 | msg = "43 4d 44 3a 44 67 41 41" 39 | msg = "4f 4b 3a 44 67 65 65" 40 | msg = "OK:DgAA" 41 | 42 | data = [] 43 | for c in msg: 44 | data.append(ord(c)) 45 | 46 | ''' 47 | parts = msg.split(" ") 48 | for i in range(0, len(parts)): 49 | b = "0x" + parts[i] 50 | data.append(int(b, base=16)) 51 | ''' 52 | 53 | crc = 0xffff 54 | length = len(data) 55 | i = 0 56 | while (length > 0): 57 | length -= 1 58 | index = data[i] ^ (crc & 0xFF) 59 | i += 1 60 | crc = crc >> 8 61 | crc ^= crc16_table[index] 62 | 63 | print(crc, f'0x{format(crc, "04x")}') 64 | 65 | crc = 0xFFFF 66 | poly_reversed = 0xA001 67 | 68 | for i in range(0, len(data)): 69 | crc ^= data[i] 70 | for j in range(0, 8): 71 | cry = crc & 0x0001 72 | crc = crc >> 1 73 | if (cry > 0): 74 | crc ^= poly_reversed 75 | 76 | s = f'0x{format(crc, "04x")}' 77 | d = [] 78 | for c in s: 79 | a = ord(c) 80 | d.append(f'0x{format(a, "02x")}') 81 | print(crc, s, d) 82 | -------------------------------------------------------------------------------- /cs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # cs.sh 5 | # 6 | # Check SHAs 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-20, Tony Smith 10 | # @version 1.0.2 11 | # @license MIT 12 | # 13 | 14 | 15 | # Function to show help info - keeps this out of the code 16 | function showHelp() { 17 | echo -e "\nCheck SHA 1.0.2\n" 18 | echo -e "Usage:\n cs [-f path] [sha]\n" 19 | } 20 | 21 | # Set inital state values 22 | argIsAValue=0 23 | sourceFile="" 24 | theSha="" 25 | 26 | # Process the arguments 27 | argCount=0 28 | for arg in "$@"; do 29 | # Make argument lowercase 30 | arg=${arg,,} 31 | 32 | if [[ $argIsAValue -gt 0 ]]; then 33 | # The argument should be a value (previous argument was an option) 34 | if [[ ${arg:0:1} = "-" ]]; then 35 | # Next value is an option: ie. missing value 36 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 37 | exit 1 38 | fi 39 | 40 | # Set the appropriate internal value 41 | case "$argIsAValue" in 42 | 1) sourceFile=$arg ;; 43 | *) echo "[Error] Unknown argument" exit 1 ;; 44 | esac 45 | 46 | argIsAValue=0 47 | else 48 | if [[ $arg = "-f" || $arg = "--file" ]]; then 49 | argIsAValue=1 50 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 51 | showHelp 52 | exit 0 53 | else 54 | theSha=$arg 55 | fi 56 | fi 57 | 58 | ((argCount++)) 59 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 60 | echo "[Error] Missing value for $arg" 61 | exit 1 62 | fi 63 | done 64 | 65 | # Check the supplied values 66 | if [ -z "$theSha" ]; then 67 | echo "[Error] Missing SHA" 68 | exit 1 69 | fi 70 | 71 | if [ -z "$sourceFile" ]; then 72 | echo "[Error] No file specified" 73 | exit 1 74 | fi 75 | 76 | if [ ! -f "$sourceFile" ]; then 77 | echo "[Error] File not found: $sourceFile" 78 | exit 1 79 | fi 80 | 81 | # Get and extract the SHA 82 | aSha=$(shasum -a 256 "$sourceFile") 83 | aSha=$(echo "$aSha" | cut -d " " -f 1) 84 | 85 | # Check the SHA 86 | if [ "$aSha" = "$theSha" ]; then 87 | echo "SHAs match" 88 | else 89 | echo "SHAs do not match:" 90 | echo "Specified: $theSha" 91 | echo "From file: $aSha" 92 | fi 93 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Deploy compiled firmare to RP2040-based board 4 | # 5 | # Usage: 6 | # ./deploy {path/to/uf2} 7 | # 8 | # @author Tony Smith 9 | # @copyright 2022, Tony Smith 10 | # @version 1.1.0 11 | # @license MIT 12 | 13 | APP_NAME=$(basename $0) 14 | APP_NAME=${APP_NAME:t} 15 | APP_VERSION="1.1.0" 16 | 17 | # Display an error string and bail 18 | show_error_and_exit() { 19 | echo "[ERROR] $1" 20 | exit 1 21 | } 22 | 23 | # List available Pico and, if necessary, get the 24 | # user to choose the one they want to copy to 25 | dlist() { 26 | # Get deviece list by platform 27 | if [[ ${1} = Darwin ]]; then 28 | devices=($(/bin/ls /dev/cu.usb* 2>/dev/null)) 29 | else 30 | devices=($(/bin/ls /dev/ttyACM* 2>/dev/null)) 31 | fi 32 | 33 | if [ ${#devices[@]} -gt 0 ]; then 34 | # If there are multiple devices, get the user to choose one 35 | if [[ ${#devices[@]} -gt 1 ]]; then 36 | # List the devices 37 | count=1 38 | for device in "${devices[@]}"; do 39 | echo "$count. $device" 40 | ((count+=1)) 41 | done 42 | 43 | # Get the user to pick one 44 | acount=$((count-1)) 45 | while true; do 46 | read -p "Enter device number (1-${acount}) " dev_num 47 | [[ ${dev_num} -gt 0 && ${dev_num} -lt ${count} ]] && break 48 | done 49 | 50 | # Record the selected device 51 | ((dev_num-=1)) 52 | devices=${devices[${dev_num}]} 53 | fi 54 | else 55 | show_error_and_exit "No devices connected" 56 | fi 57 | } 58 | 59 | if [[ -z ${1} ]]; then 60 | echo "Usage: deploy {path/to/uf2}" 61 | exit 0 62 | fi 63 | 64 | if [[ ! -f ${1} ]]; then 65 | echo "[ERROR] ${1} cannot be found" 66 | exit 1 67 | fi 68 | 69 | # What platform are we running on? 70 | platform=$(uname) 71 | 72 | # Get any connected devices 73 | # Only device, or choice of device, will be in ${devices} 74 | dlist ${platform} 75 | 76 | # Put the selected Pico onto BOOTSEL mode 77 | if [[ ${platform} = Darwin ]]; then 78 | # macOS mount path 79 | pico_path=/Volumes/RPI-RP2 80 | stty -f ${devices} 1200 || show_error_and_exit "Could not connect to device ${devices}" 81 | else 82 | # NOTE This is for Raspberry Pi -- you may need to change it 83 | # depending on how you or your OS locate USB drive mount points 84 | pico_path="/media/$USER/RPI-RP2" 85 | stty -F ${devices} 1200 || show_error_and_exit "Could not connect to device ${devices}" 86 | 87 | # Allow for command line usage -- ie. not in a GUI terminal 88 | # Command line is SHLVL 1, so script is SHLVL 2 (under the GUI we'd be a SHLVL 3) 89 | if [[ $SHLVL -eq 2 ]]; then 90 | # Mount the disk, but allow time for it to appear (not immediate on RPi) 91 | sleep 5 92 | rp2_disk=$(sudo fdisk -l | grep FAT16 | cut -f 1 -d ' ') 93 | if [[ -z ${rp2_disk} ]]; then 94 | show_error_and_exit "Could not see device ${devices}" 95 | fi 96 | 97 | sudo mkdir ${pico_path} || show_error_and_exit "Could not make mount point ${pico_path}" 98 | sudo mount ${rp2_disk} ${pico_path} -o rw || show_error_and_exit "Could not mount device ${devices}" 99 | fi 100 | fi 101 | 102 | echo "Waiting for Pico to mount..." 103 | count=0 104 | while [ ! -d ${pico_path} ]; do 105 | sleep 0.1 106 | ((count+=1)) 107 | [[ ${count} -eq 200 ]] && show_error_and_exit "Pico mount timed out" 108 | done 109 | sleep 0.5 110 | 111 | # Copy the target file 112 | echo "Copying ${1} to ${devices}..." 113 | if [[ ${platform} = Darwin ]]; then 114 | cp ${1} ${pico_path} 115 | else 116 | sudo cp ${1} ${pico_path} 117 | if [[ $SHLVL -eq 2 ]]; then 118 | # We're at the command line, so unmount (RPi GUI does this automatically) 119 | sudo umount ${rp2_disk} && echo "Pico unmounted" && sudo rm -rf ${pico_path} && echo "Mountpoint removed" 120 | fi 121 | fi 122 | echo Done 123 | -------------------------------------------------------------------------------- /fixgit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # fixgit.sh 5 | # 6 | # Fix Git repos when .gitignore is updated but doesn't take 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-20, Tony Smith 10 | # @version 1.0.1 11 | # @license MIT 12 | # 13 | 14 | 15 | echo "FixGit" 16 | read -n 1 -s -p "You should be in your project directory. Press [G] to continue, or any other key to cancel " key 17 | key=${key^^*} 18 | 19 | if [ "$key" = "G" ]; then 20 | git rm -r --cached . 21 | git add . 22 | git commit -m "[Git Fixed] .gitignore is now working" 23 | echo "Git Fixed" 24 | fi 25 | -------------------------------------------------------------------------------- /getcaskversions.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | 4 | # getcaskversions 5 | # 6 | # List current cask versions 7 | # 8 | # @author Tony Smith 9 | # @copyright 2021, Tony Smith 10 | # @version 1.0.2 11 | # @license MIT 12 | 13 | 14 | casks="$GIT/homebrew-smittytone/Casks" 15 | if cd "$casks"; then 16 | for cask in *; do 17 | while IFS= read -r line; do 18 | version_line=$(echo $line | grep 'version') 19 | if [[ -n "$version_line" ]]; then 20 | version=$(echo "$version_line" | egrep -o '[0-9]+.[0-9]+.[0-9]+') 21 | cask_name=$(echo "$cask" | cut -d "." -s -f 1) 22 | echo "$cask_name is at version $version" 23 | fi 24 | done < "$cask" 25 | done 26 | else 27 | echo "ERROR -- Casks folder '${casks}' not found" 28 | exit 1 29 | fi 30 | -------------------------------------------------------------------------------- /gitcheck.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # gitcheck.zsh 4 | # 5 | # Display $GIT directory repos with unmerged or uncommitted changes 6 | # 7 | # @author Tony Smith 8 | # @copyright 2022, Tony Smith 9 | # @version 1.3.2 10 | # @license MIT 11 | 12 | 13 | show_error_and_exit() { 14 | echo "[ERROR] $1" 15 | exit 1 16 | } 17 | 18 | local max=0 19 | local repos=() 20 | local states=() 21 | local branches=() 22 | local show_branches=0 23 | 24 | if [[ -z "$GIT" ]]; then 25 | show_error_and_exit 'Environment variable "$GIT" not set with your Git directory' 26 | fi 27 | 28 | if [[ ! -d "$GIT" ]]; then 29 | show_error_and_exit 'Directory referenced by environment variable "$GIT" does not exist' 30 | fi 31 | 32 | # FROM 1.3.1 33 | # Process the arguments 34 | for arg in "$@"; do 35 | # Temporarily convert argument to lowercase, zsh-style 36 | check_arg=${arg:l} 37 | if [[ "${check_arg}" = "--branches" || "${check_arg}" = "-b" ]]; then 38 | show_branches=1 39 | else 40 | show_error_and_exit "Unknown command ${arg}" 41 | fi 42 | done 43 | 44 | # FROM 1.2.1 -- Add progress marker 45 | echo -n "Checking" 46 | if cd "$GIT"; then 47 | # Process the files 48 | for repo in *; do 49 | if [[ -d "${repo}" && -d "${repo}/.git" ]]; then 50 | if cd "${repo}"; then 51 | local state="" 52 | if [[ "$show_branches" -eq 1 ]]; then 53 | # FROM 1.3.1 -- determine repo current branches 54 | repos+=("$repo") 55 | if [[ ${#repo} -gt ${max} ]] max=${#repo} 56 | local branch=$(git branch --show-current) 57 | branches+=("${branch}") 58 | else 59 | # Determine repo states, but only those that are not up to date 60 | local unmerged=$(git status --ignore-submodules) 61 | unmerged=$(grep 'is ahead' < <((echo -e "$unmerged"))) 62 | if [[ -n "${unmerged}" ]]; then 63 | state="unmerged" 64 | fi 65 | 66 | local uncommitted=$(git status --porcelain --ignore-submodules) 67 | if [[ -n "${uncommitted}" ]]; then 68 | state="uncommitted" 69 | fi 70 | 71 | if [[ -n "$state" ]]; then 72 | states+=("${state}") 73 | repos+=("${repo}") 74 | 75 | if [[ ${#repo} -gt ${max} ]] max=${#repo} 76 | fi 77 | fi 78 | 79 | cd .. 80 | fi 81 | 82 | # FROM 1.2.1 Add progress marker 83 | echo -n "." 84 | fi 85 | done 86 | fi 87 | 88 | if [[ ${#repos} -eq 0 ]]; then 89 | echo -e "\nAll local repos up to date" 90 | else 91 | # FROM 1.3.1 -- show repo current branches, or states 92 | if [[ "$show_branches" -eq 1 ]]; then 93 | echo -e "\nLocal repo current branches:" 94 | for (( i = 1 ; i <= ${#repos[@]} ; i++ )); do 95 | printf '%*s is on %s\n' ${max} ${repos[i]} ${branches[i]} 96 | done 97 | else 98 | echo -e "\nLocal repos with changes:" 99 | for (( i = 1 ; i <= ${#repos[@]} ; i++ )); do 100 | printf '%*s has %s changes\n' ${max} ${repos[i]} ${states[i]} 101 | done 102 | fi 103 | fi 104 | 105 | exit 0 -------------------------------------------------------------------------------- /gitlog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Not my code -- written by A Houghton 4 | 5 | echo -e "Year\tAdded\tRemoved\tTotal" 6 | 7 | total=0 8 | 9 | for year in $(seq 2014 2020); do 10 | yearAdd=0 11 | yearSub=0 12 | TMPFILE=$(mktemp) 13 | git log --numstat --pretty="%H" --since="${year}-01-01 00:00" --until="${year}-12-31 23:59" | egrep '(\d+\s+){2}' > $TMPFILE 14 | while read add sub ignore; do 15 | yearAdd=$((yearAdd+add)) 16 | yearSub=$((yearSub+sub)) 17 | done < $TMPFILE 18 | rm $TMPFILE 19 | total=$((total+yearAdd-yearSub)) 20 | echo -e "$year\t$yearAdd\t-$yearSub\t$total" 21 | done 22 | -------------------------------------------------------------------------------- /hugo-deploy.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Rebuild Hugo site 4 | # Version 2.0.1 5 | 6 | # If a command fails then the deploy stops 7 | #set -e 8 | 9 | # Set variables 10 | do_deploy=1 11 | arg_count=0 12 | arg_is_a_value=0 13 | source="" 14 | destination="" 15 | msg="Latest upload @ $(date)" 16 | 17 | # Process the arguments 18 | for arg in "$@" 19 | do 20 | if [[ $arg_is_a_value -gt 0 ]]; then 21 | # The argument should be a value (previous argument was an option) 22 | if [[ ${arg:0:1} = "-" ]]; then 23 | # Next value is an option: ie. missing value 24 | echo "Error: Missing value for ${arg[((arg_is_a_value - 1))]}" 25 | exit 1 26 | fi 27 | 28 | # Set the appropriate internal value 29 | case "$arg_is_a_value" in 30 | 1) msg=$arg ;; 31 | 2) source=$arg ;; 32 | 3) destination=$arg ;; 33 | *) echo "Error: Unknown argument" exit 1 ;; 34 | esac 35 | 36 | arg_is_a_value=0 37 | else 38 | if [[ $arg = "-m" || $arg = "--message" ]]; then 39 | arg_is_a_value=1 40 | elif [[ $arg = "-t" || $arg = "--test" ]]; then 41 | do_deploy=0 42 | elif [[ $arg = "-s" || $arg = "--source" ]]; then 43 | arg_is_a_value=2 44 | elif [[ $arg = "-t" || $arg = "--target" ]]; then 45 | arg_is_a_value=3 46 | fi 47 | fi 48 | 49 | ((arg_count += 1)) 50 | 51 | if [[ $arg_count -eq $# && $arg_is_a_value -ne 0 ]]; then 52 | echo "Error: Missing value for $arg" 53 | exit 1 54 | fi 55 | done 56 | 57 | # FROM 2.0.1 58 | # Make sure Hugo is installed 59 | which hugo > /dev/null 60 | if [[ $? -ne 0 ]]; then 61 | echo "Error: Hugo not installed" 62 | exit 1 63 | fi 64 | 65 | # Move to source directory 66 | cd "$source" || exit 1 67 | 68 | # Zap any existing build 69 | rm -rf public 70 | 71 | # Run a test server or a build 72 | if [[ $do_deploy -eq 0 ]]; then 73 | # Serve for testing 74 | hugo -D server 75 | else 76 | # Build the site 77 | if hugo; then 78 | # If we're also deploying 79 | if [[ $do_deploy -eq 1 ]]; then 80 | # Copy the build site into the served repo 81 | cp -r public/ "$destination/" 82 | 83 | # Commit the changes in the served repo and push 84 | cd "$destination" || exit 1 85 | git add . 86 | git commit -m "$msg" 87 | git push origin master 88 | fi 89 | fi 90 | fi 91 | -------------------------------------------------------------------------------- /iconprep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # iconprep.sh 5 | # 6 | # Prep macOS/watchOS/iOS Icons 7 | # 8 | # @shell bash -- requires 5.0.0+ 9 | # @author Tony Smith 10 | # @copyright 2024, Tony Smith 11 | # @version 1.3.0 12 | # @license MIT 13 | # 14 | 15 | # Function to show help info - keeps this out of the code 16 | function showHelp() { 17 | echo -e "\nIcon Prepare\n" 18 | echo -e "Usage:\n iconprep [-s path] [-d path] [-t type]\n" 19 | echo "Options:" 20 | echo " -s / --source [path] The path of the source image. The source image should" 21 | echo " be at least 1024x1024, and a PNG or JPG file" 22 | echo " -n / --name [name] The base name of output icons. Default: set by type" 23 | echo " -d / --destination [path] The path to the target folder. Default: desktop" 24 | echo " -t / --type [int] The output type: 1 - macOS app icons (Default)" 25 | echo " 2 - macOS toolbar icons" 26 | echo " 3 - watchOS app icons" 27 | echo " 4 - watchOS complication icons" 28 | echo " 5 - iOS app icons" 29 | echo " 6 - Web app icons" 30 | echo " -h / --help This help screen" 31 | echo 32 | } 33 | 34 | # Set inital state values 35 | source_image="UNSET" 36 | dest_folder="$HOME/Desktop" 37 | icon_type=1 38 | arg_value=0 39 | args=(-s -t -d) 40 | m_a_sizes=(16 32 64 96 128 256 512 1024) 41 | m_t_sizes=(32 64 96) 42 | w_a_sizes=(216 196 172 100 88 87 80 58 55 48) 43 | w_c_sizes=(224 203 182 64 58 52 50 44 40 36 32) 44 | i_a_sizes=(40 60 58 87 76 114 80 120 180 128 192 136 152 167 1024) 45 | s_w_sizes=(64 128) 46 | # FROM 1.2.0 47 | names=(macos_appicon macos_toolbar watchos_app_icon watchos_comp_icon ios_app_icon web_app_icon) 48 | name="NONE" 49 | 50 | # Funcions 51 | m_a_make() { 52 | # Make macOS app icons 53 | for size in "${m_a_sizes[@]}"; do 54 | make "${dest_folder}/${name}_${size}.${extension}" 55 | done 56 | } 57 | 58 | m_t_make() { 59 | # Make macOS toolbar icons 60 | count=0 61 | filename="${source_image##*/}" 62 | filename="${filename%.*}" 63 | for size in "${m_t_sizes[@]}"; do 64 | sizemark="" 65 | if [ $count -eq 1 ]; then 66 | sizemark='@2x' 67 | fi 68 | if [ $count -eq 2 ]; then 69 | sizemark='@3x' 70 | fi 71 | make "${dest_folder}/${name}_${filename}${sizemark}.${extension}" 72 | ((count++)) 73 | done 74 | } 75 | 76 | w_a_make() { 77 | # Make watcOS complication icons 78 | for size in "${w_a_sizes[@]}"; do 79 | make "${dest_folder}/${name}_${size}.${extension}" 80 | done 81 | } 82 | 83 | w_c_make() { 84 | # Make watcOS complication icons 85 | for size in "${w_c_sizes[@]}"; do 86 | make "${dest_folder}/${name}_${size}.${extension}" 87 | done 88 | } 89 | 90 | i_a_make() { 91 | # Make watcOS complication icons 92 | for size in "${i_a_sizes[@]}"; do 93 | make "${dest_folder}/${name}_${size}.${extension}" 94 | done 95 | } 96 | 97 | s_w_make() { 98 | # Make smittytone web site app icons 99 | for size in "${s_w_sizes[@]}"; do 100 | make "${dest_folder}/${name}_${size}.${extension}" 101 | done 102 | } 103 | 104 | make() { 105 | # Generic function to copy source to new file and then resize the copy 106 | cp "$source_image" "$1" 107 | sips "$1" -Z "$size" -i > /dev/null 108 | } 109 | 110 | 111 | # Runtime start 112 | # Process the arguments 113 | arg_count=0 114 | for arg in "$@" 115 | do 116 | if [[ $arg_value -gt 0 ]]; then 117 | # The argument should be a value (previous argument was an option) 118 | if [[ ${arg:0:1} = "-" ]]; then 119 | # Next value is an option: ie. missing value 120 | echo "Error -- Missing value for ${args[((arg_value - 1))]}" 121 | exit 1 122 | fi 123 | 124 | # Set the appropriate internal value 125 | case "$arg_value" in 126 | 1) source_image=$arg ;; 127 | 2) icon_type=$arg ;; 128 | 3) dest_folder=$arg ;; 129 | 4) name=$arg ;; 130 | *) echo "Error -- Unknown argument" exit 1 ;; 131 | esac 132 | 133 | arg_value=0 134 | else 135 | if [[ $arg = "-s" || $arg = "--source" ]]; then 136 | arg_value=1 137 | elif [[ $arg = "-t" || $arg = "--type" ]]; then 138 | arg_value=2 139 | elif [[ $arg = "-d" || $arg = "--destination" ]]; then 140 | arg_value=3 141 | elif [[ $arg = "-n" || $arg = "--name" ]]; then 142 | arg_value=4 143 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 144 | showHelp 145 | exit 0 146 | fi 147 | fi 148 | 149 | ((arg_count++)) 150 | if [[ $arg_count -eq $# && $arg_value -ne 0 ]]; then 151 | echo "Error -- Missing value for $arg" 152 | exit 1 153 | fi 154 | done 155 | 156 | # FROM 1.2.0 157 | # Fix the name 158 | if [ "$name" = "NONE" ]; then 159 | name=${names[(($icon_type - 1))]} 160 | fi 161 | 162 | # Make sure we have a source image 163 | if [ "$source_image" != "UNSET" ]; then 164 | if [ -f "$source_image" ]; then 165 | if [ -d "$dest_folder" ] ; then 166 | # Get the extension and make it uppercase 167 | extension=${source_image##*.} 168 | ext_test=${extension^^*} 169 | 170 | # Make sure the file's of the right type 171 | if [[ $ext_test = "PNG" || $ext_test = "JPG" || $ext_test = "JPEG" ]]; then 172 | # Make the icons by type 173 | case "$icon_type" in 174 | 1) m_a_make ;; 175 | 2) m_t_make ;; 176 | 3) w_a_make ;; 177 | 4) w_c_make ;; 178 | 5) i_a_make ;; 179 | 6) s_w_make ;; 180 | *) echo "Error -- Unknown icon type specified ($icon_type)" ; exit 1 ;; 181 | esac 182 | else 183 | echo "Source image must be a PNG or JPG. It is a $extension" 184 | exit 1 185 | fi 186 | else 187 | echo "Destination folder $dest_folder can't be found" 188 | exit 1 189 | fi 190 | else 191 | echo "Source image $source_image can't be found" 192 | exit 1 193 | fi 194 | else 195 | echo "Error -- No source image set" 196 | exit 1 197 | fi 198 | -------------------------------------------------------------------------------- /imagenum.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # imagenum.sh 5 | # 6 | # Rename and number a sequence of PNG files, and convert them to JPEG 7 | # FROM 2.0.0 -- Rename JPGs too, but omit conversion (natch); change commands 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-20, Tony Smith 11 | # @version 2.1.1 12 | # @license MIT 13 | # 14 | 15 | 16 | # Function to show help info - keeps this out of the code 17 | function showHelp() { 18 | echo -e "\nImage Renumber Utility\n" 19 | echo -e "Usage:\n imagenum [-p path] [-t path] [-n name] [-s start] [-d digits] [-c separator] [-k] [-q] [-h]\n" 20 | echo "Options:" 21 | echo " -p / --path [path] The path to the source images. Default: current directory." 22 | echo " -t / --target [path] Where to place converted images. Default: source directory." 23 | echo " -n / --name [name] The name of the image sequence. Default: Untitled." 24 | echo " -s / --start [number] The first number in the sequence. Default: 01." 25 | echo " -d / --digits [number] The number of digits in the sequence number. Default: 3." 26 | echo ' -c / --separator [symbol] The symbol used to separate name from number. Default: " ".' 27 | echo " -k / --keep Keep the source files; don\'t delete them. Default: false." 28 | echo " -q / --quiet Silence output (errors excepted)." 29 | echo " -h / --help This help screen." 30 | echo 31 | } 32 | 33 | 34 | # Set inital state values 35 | start=1 36 | digits=3 37 | name=Untitled 38 | path=~+ 39 | dest="" 40 | sep=space 41 | argIsAValue=0 42 | args=(-p -t -n -s -d -c -k -h) 43 | doKeep=0 44 | verbose=1 45 | 46 | # Process the arguments 47 | argCount=0 48 | for arg in "$@" 49 | do 50 | if [[ $argIsAValue -gt 0 ]]; then 51 | # The argument should be a value (previous argument was an option) 52 | if [ "${arg:0:1}" = "-" ]; then 53 | # Next value is an option: ie. missing value 54 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 55 | exit 1 56 | fi 57 | 58 | # Set the appropriate internal value 59 | case "$argIsAValue" in 60 | 1) name=$arg ;; 61 | 2) start=$arg ;; 62 | 3) digits=$arg ;; 63 | 4) sep=$arg ;; 64 | 5) path=$arg ;; 65 | 6) dest=$arg ;; 66 | *) echo "Error: Unknown argument" exit 1 ;; 67 | esac 68 | 69 | argIsAValue=0 70 | else 71 | # Make argument lowercase 72 | arg=${arg,,} 73 | 74 | if [[ $arg = "-n" || $arg = "--name" ]]; then 75 | argIsAValue=1 76 | elif [[ $arg = "-s" || $arg = "--start" ]]; then 77 | argIsAValue=2 78 | elif [[ $arg = "-d" || $arg = "--digits" ]]; then 79 | argIsAValue=3 80 | elif [[ $arg = "-c" || $arg = "--separator" ]]; then 81 | argIsAValue=4 82 | elif [[ $arg = "-p" || $arg = "--path" ]]; then 83 | argIsAValue=5 84 | elif [[ $arg = "-t" || $arg = "--target" ]]; then 85 | argIsAValue=6 86 | elif [[ $arg = "-k" || $arg = "--keep" ]]; then 87 | doKeep=1 88 | elif [[ $arg = "-q" || $arg = "--quiet" ]]; then 89 | verbose=0 90 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 91 | showHelp 92 | exit 0 93 | else 94 | echo "Error: Unknown argument ($arg)" 95 | exit 1 96 | fi 97 | fi 98 | 99 | ((argCount++)) 100 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 101 | echo "Error: Missing value for $arg" 102 | exit 1 103 | fi 104 | done 105 | 106 | # Check that the maximum file sequence number is not greater than 'digits' 107 | fileCount=0 108 | for file in "$path"/* 109 | do 110 | if [ -f "$file" ]; then 111 | # Get the extension and make it uppercase 112 | extension=${file##*.} 113 | extension=${extension^^*} 114 | 115 | # Make sure the file's of the right type 116 | # FROM 2.0.0 -- include JPG and JPEG files 117 | if [[ "$extension" = "PNG" || "$extension" = "JPG" || "$extension" = "JPEG" || "$extension" = "TIFF" ]]; then 118 | ((fileCount++)) 119 | fi 120 | fi 121 | done 122 | 123 | if [ ${#fileCount} -gt "$digits" ]; then 124 | echo "[Error] Specified digits ($digits) is less than the number of digits required (${#fileCount}) - use -d to set the number of output digits" 125 | exit 1 126 | fi 127 | 128 | # FROM 2.1.0 -- implement quiet operation 129 | if [ $verbose -eq 1 ]; then 130 | if [ $fileCount -eq 0 ]; then 131 | echo "There are no suitable files to convert in folder $path" 132 | exit 0 133 | elif [ $fileCount -eq 1 ]; then 134 | if [ "$extension" = "PNG" ]; then 135 | echo "1 PNG file in folder $path will now be converted and renumbered..." 136 | else 137 | echo "1 file in folder $path will now be renumbered..." 138 | fi 139 | else 140 | echo "$fileCount files in folder $path will now be renumbered..." 141 | fi 142 | fi 143 | 144 | # FROM 2.0.0 -- set the default destination 145 | if [ -z "$dest" ]; then 146 | dest="$path" 147 | echo "DEST: $dest" 148 | fi 149 | 150 | count=$start 151 | for file in "$path"/* 152 | do 153 | if [ -f "$file" ]; then 154 | # Get the extension and make it uppercase 155 | extension=${file##*.} 156 | extension=${extension^^*} 157 | 158 | # Make sure the file's of the right type 159 | # FROM 2.0.0 -- include JPG and JPEG files 160 | if [[ "$extension" = "PNG" || "$extension" = "JPG" || "$extension" = "JPEG" || "$extension" = "TIFF" ]]; then 161 | 162 | # Make the new file name 163 | value=$count 164 | digitCount=${#value} 165 | 166 | # Prefix the sequence number with zeroes 167 | while [ "$digitCount" -lt "$digits" ]; do 168 | value="0$value" 169 | ((digitCount++)) 170 | done 171 | 172 | # Add in the separator: either one of the set strings 173 | # (space, underscore, under, hash, minus) or the passed-in string 174 | if [ "$sep" = "space" ]; then 175 | filename="$name $value.jpg" 176 | else 177 | case $sep in 178 | hash) dep="#" ;; 179 | underscore) dep="_" ;; 180 | under) dep="_" ;; 181 | minus) dep="-" ;; 182 | *) dep=$sep ;; 183 | esac 184 | 185 | filename="$name$dep$value.jpg" 186 | fi 187 | 188 | # Copy the PNG file to the destination directory 189 | cp "$file" "$dest/$filename" 190 | 191 | # FROM 2.0.0 -- only delete the source file if permitted 192 | if [ $doKeep -eq 0 ]; then 193 | rm "$file" 194 | fi 195 | 196 | if [ $verbose -eq 1 ]; then 197 | echo "Converting $file to $dest/$filename..." 198 | fi 199 | 200 | # Convert the copied PNG to JPEG (compression 60%) 201 | # FROM 2.0.0 -- only do this if necessary 202 | # NOTE We've already renamed the file extension to stop sips 203 | # throwing an error 204 | if [[ "$extension" = "PNG" || "$extension" = "TIFF" ]]; then 205 | sips "$dest/$filename" -s format jpeg -s formatOptions 60 > /dev/null 206 | fi 207 | 208 | # Increment the file count 209 | ((count++)) 210 | fi 211 | fi 212 | done 213 | 214 | if [ $verbose -eq 1 ]; then 215 | ((count = count - start)) 216 | if [ $count -eq 1 ]; then 217 | echo "1 file converted" 218 | elif [ $count -gt 1 ]; then 219 | echo "$count files converted" 220 | else 221 | echo "No files converted" 222 | fi 223 | fi 224 | -------------------------------------------------------------------------------- /increment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! -f ${PWD}/.buildcount ]; then 3 | echo "BUILD_COUNT=1" > ${PWD}/.buildcount 4 | else 5 | source ${PWD}/.buildcount 6 | BUILD_COUNT=$((BUILD_COUNT+1)) 7 | echo "BUILD_COUNT=${BUILD_COUNT}" > ${PWD}/.buildcount 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /lowerext.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # lowerext.sh 5 | # 6 | # Make all file extensions in the working directory lower case 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-20, Tony Smith 10 | # @version 1.0.1 11 | # @license MIT 12 | # 13 | 14 | 15 | # Get all the entries in the working directory 16 | for file in ~+/*; do 17 | # Only process files 18 | if [ -f "$file" ]; then 19 | # Get the extension 20 | extension=${file##*.} 21 | 22 | # Only proceed if there *is* an extension 23 | if [ -n "$extension" ]; then 24 | # Make the extension lowercase 25 | newextension=${extension,,} 26 | 27 | # No need to re-convert lowercase extensions, 28 | # so check new and old version don't match 29 | if [ "$extension" != "$newextension" ]; then 30 | # Get the filename and add back the extension 31 | newfile=${file%.*} 32 | newfile="$newfile.$newextension" 33 | 34 | # Move the file 35 | mv "$file" "$newfile" 36 | echo "Processed $file to $newfile" 37 | fi 38 | fi 39 | fi 40 | done 41 | -------------------------------------------------------------------------------- /makepico.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # makepico.zsh 5 | # 6 | # Create a baseline Raspberry Pi Pico C-language project 7 | # 8 | # @author Tony Smith 9 | # @copyright 2021, Tony Smith 10 | # @version 2.3.0 11 | # @license MIT 12 | # 13 | 14 | 15 | # FROM 1.0.2 16 | # Check that PICO_SDK_PATH is defined 17 | # and the SDK is installed 18 | if [[ -z "${PICO_SDK_PATH}" ]]; then 19 | show_error "Environment variable PICO_SDK_PATH not set" 20 | else 21 | ls "${PICO_SDK_PATH}" >/dev/null 2>&1 22 | if [[ $? -ne 0 ]]; then 23 | show_error "Pico SDK not installed at ${PICO_SDK_PATH}" 24 | fi 25 | fi 26 | 27 | 28 | show_help() { 29 | echo -e "\nmakepico 2.3.0\n\nInitialise a Pi Pico Project\n" 30 | echo -e "Usage:\n makepico [path/to/project/name] [-c] [-d] [-n your name] [-h]\n" 31 | echo "Options:" 32 | echo " -c / --cpp Set up the project for C++. Default: false" 33 | echo " -a / --asm Set up the project for ASM. Default: false" 34 | echo " -d / --debug Set up the project for SWD. Default: false" 35 | echo " -n / --name Your name for the comments. Default: " 36 | echo " -v / --version The project's inital version. Default: 1.0.0" 37 | echo " -h / --help This help screen" 38 | echo 39 | } 40 | 41 | show_error() { 42 | echo "[ERROR] $1" 43 | exit 1 44 | } 45 | 46 | make_project() { 47 | # Create and copy the project files 48 | # Args: 1 -- the project path 49 | 50 | # Check the path and, if necessary 51 | # create intermediate directories 52 | # NOTE The check will exit on error 53 | check_path "${1}" 54 | 55 | # Get project name in lower case 56 | project_name=${1:t:l} 57 | project_path=${1} 58 | 59 | # Create initial C/CXX/ASM files 60 | make_source_files "${1}" 61 | 62 | # Copy over the .make file from the SDK 63 | file="pico_sdk_import.cmake" 64 | if cp "${PICO_SDK_PATH}/external/${file}" "${project_path}/${file}"; then 65 | # NOP 66 | else 67 | show_error "Could not copy the ${file} file" 68 | fi 69 | 70 | # Make the CMakeLists.txt file for this project 71 | make_cmake_file "${1}" 72 | 73 | # FROM 1.0.2 74 | make_vscode "${1}" 75 | 76 | # And done... 77 | project_name=${1:t} 78 | echo "Project ${project_name} created at ${project_path}" 79 | } 80 | 81 | make_source_files() { 82 | # Output lines to the file 83 | # Args: 1 -- project path 84 | 85 | # FROM 1.3.0 86 | file_ext="c" 87 | project_type="C" 88 | if [[ $do_cpp -eq 1 ]]; then 89 | file_ext="cpp" 90 | project_type="C++" 91 | fi 92 | # FROM 2.3.0 93 | if [[ $do_asm -eq 1 ]]; then 94 | file_ext="S" 95 | project_type="ASM" 96 | fi 97 | 98 | echo "Creating ${project_type} project files..." 99 | project_name=${1:t} 100 | source_file=${1:t:l} 101 | 102 | write_header "$project_name" "${1}/main.${file_ext}" 103 | 104 | if [[ $do_asm -eq 1 ]]; then 105 | # FROM 2.3.0 106 | # Write main function 107 | { 108 | echo ".thumb_func @ Use Thumb instructions" 109 | echo ".global main @ Set entry point" 110 | echo 111 | echo "main:" 112 | } >> "${1}/main.${file_ext}" 113 | else 114 | write_header "$project_name" "${1}/main.h" 115 | 116 | # FROM 2.0.0 117 | # Write main() function 118 | { 119 | echo "#include \"main.h\"" 120 | echo 121 | echo 'int main() {' 122 | echo ' // Use for debugging' 123 | echo ' stdio_init_all();' 124 | echo 125 | echo ' return 0;' 126 | echo '}' 127 | } >> "${1}/main.${file_ext}" 128 | 129 | # FROM 1.3.0 130 | # Break into sections for C/C++ usage 131 | 132 | # Add header guard 133 | { 134 | echo "#ifndef _${project_name:u}_MAIN_HEADER_" 135 | echo "#define _${project_name:u}_MAIN_HEADER_" 136 | echo 137 | } >> "${1}/main.h" 138 | 139 | # C++ standard libraries or C standard libraries 140 | if [[ $do_cpp -eq 1 ]]; then 141 | { 142 | echo '/*' 143 | echo ' * C++ HEADERS' 144 | echo ' */' 145 | echo '#include ' 146 | echo '#include ' 147 | echo '#include ' 148 | echo '#include ' 149 | echo '#include ' 150 | echo '#include ' 151 | echo 152 | } >> "${1}/main.h" 153 | else 154 | { 155 | echo '/*' 156 | echo ' * C HEADERS' 157 | echo ' */' 158 | echo '#include ' 159 | echo '#include ' 160 | echo '#include ' 161 | echo '#include ' 162 | echo '#include ' 163 | echo 164 | } >> "${1}/main.h" 165 | fi 166 | 167 | # Standard Pico libraries 168 | { 169 | echo '/*' 170 | echo ' * PICO HEADERS' 171 | echo ' */' 172 | echo '#include "pico/stdlib.h"' 173 | echo '#include "pico/binary_info.h"' 174 | echo '#include "hardware/gpio.h"' 175 | echo '#include "hardware/i2c.h"' 176 | echo '#include "hardware/spi.h"' 177 | echo '#include "hardware/adc.h"' 178 | echo '#include "hardware/uart.h"' 179 | echo 180 | } >> "${1}/main.h" 181 | 182 | if [[ $do_cpp -eq 1 ]]; then 183 | { 184 | echo '#ifdef __cplusplus' 185 | echo 'extern "C" {' 186 | echo '#endif' 187 | echo 188 | echo '/*' 189 | echo ' * Usual header code here' 190 | echo ' */' 191 | echo 192 | echo '#ifdef __cplusplus' 193 | echo '}' 194 | echo '#endif' 195 | echo 196 | } >> "${1}/main.h" 197 | fi 198 | 199 | { 200 | echo "#endif // _${project_name:u}_MAIN_HEADER_" 201 | } >> "${1}/main.h" 202 | fi 203 | } 204 | 205 | write_header() { 206 | # Write header info 207 | { 208 | echo "/*" 209 | echo " * ${1} for Raspberry Pi Pico" 210 | echo " *" 211 | echo " * @version ${proj_version}" 212 | echo " * @author ${users_name}" 213 | echo " * @copyright $(date +'%Y')" 214 | echo " * @licence MIT" 215 | echo " *" 216 | echo " */" 217 | } > "$2" 218 | } 219 | 220 | make_cmake_file() { 221 | # Output lines to the file, interpolating the project name 222 | # Args: 1 -- project path 223 | 224 | echo "Creating CMakeLists.txt..." 225 | project_name=${1:t} 226 | source_file=${project_name:l} 227 | 228 | # FROM 1.3.0 229 | file_ext="c" 230 | if [[ $do_cpp -eq 1 ]]; then 231 | file_ext="cpp" 232 | fi 233 | # FROM 2.3.0 234 | if [[ $do_asm -eq 1 ]]; then 235 | file_ext="S" 236 | fi 237 | 238 | { 239 | echo 'cmake_minimum_required(VERSION 3.14)' 240 | echo 'include(pico_sdk_import.cmake)' 241 | echo "project(${project_name} VERSION ${proj_version})" 242 | echo "add_executable(${project_name}" 243 | echo " main.${file_ext})" 244 | echo 245 | echo 'pico_sdk_init()' 246 | echo 247 | echo "pico_enable_stdio_usb(${project_name} 1)" 248 | echo "pico_enable_stdio_uart(${project_name} 1)" 249 | echo "pico_add_extra_outputs(${project_name})" 250 | echo 251 | echo "target_link_libraries(${project_name}" 252 | echo ' pico_stdlib' 253 | echo ' hardware_gpio' 254 | echo ' hardware_i2c' 255 | echo ' hardware_spi' 256 | echo ' hardware_adc' 257 | echo ' hardware_uart)' 258 | } >> "${1}/CMakeLists.txt" 259 | } 260 | 261 | make_vscode() { 262 | # FROM 1.0.2 263 | # Make the vscode settings 264 | echo "Configuring VSCode..." 265 | mkdir "${1}/.vscode" 266 | { 267 | echo '{' 268 | echo ' "cmake.environment": {' 269 | echo " \"PICO_SDK_PATH\": \"${PICO_SDK_PATH}\"" 270 | echo ' },' 271 | echo ' "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"' 272 | echo '}' 273 | } >> "${1}/.vscode/settings.json" 274 | 275 | # Debug flag set? write out an SWD launch config 276 | if [[ $do_swd -eq 1 ]]; then 277 | { 278 | echo '{' 279 | echo ' "version": "0.2.0",' 280 | echo ' "configurations": [' 281 | echo ' { "type": "cortex-debug",' 282 | echo ' "name": "Pico Debug",' 283 | echo ' "device": "RP2040",' 284 | echo ' "gdbPath": "arm-none-eabi-gdb",' 285 | echo ' "cwd": "${workspaceRoot}",' 286 | echo ' "executable": "${command:cmake.launchTargetPath}",' 287 | echo ' "request": "launch",' 288 | echo ' "servertype": "openocd",' 289 | echo ' "configFiles": [' 290 | echo ' "/interface/picoprobe.cfg",' 291 | echo ' "/target/rp2040.cfg"' 292 | echo ' ],' 293 | echo ' "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",' 294 | echo ' "runToEntryPoint": "main",' 295 | echo ' "postRestartCommands": [' 296 | echo ' "break main",' 297 | echo ' "continue"' 298 | echo ' ]' 299 | echo ' }' 300 | echo ' ]' 301 | echo '}' 302 | } >> "${1}/.vscode/launch.json" 303 | fi 304 | } 305 | 306 | check_path() { 307 | # Check the path is valid 308 | # Args: 1 -- the project path as specified by the user 309 | project_name={$1:t} 310 | if [[ ! -d "${1}" ]]; then 311 | if mkdir -p "${1}" >> /dev/null; then 312 | echo "Creating project directory..." 313 | else 314 | show_error "Could not create path for project ${project_name}" 315 | fi 316 | fi 317 | } 318 | 319 | # Runtime start 320 | # Get the arguments, which should be project path(s) 321 | projects=() 322 | do_swd=0 323 | do_cpp=0 324 | do_asm=0 325 | users_name="" 326 | proj_version="1.0.0" 327 | next_is_arg=0 328 | last_arg="" 329 | 330 | for arg in "$@"; do 331 | upper_arg=${arg:u} 332 | if [[ $next_is_arg -gt 0 ]]; then 333 | # The argument should be a value (previous argument was an option) 334 | if [[ ${arg:0:1} = "-" ]]; then 335 | # Next value is an option: ie. missing value 336 | show_error "Missing value for ${last_arg}" 337 | fi 338 | 339 | # Set the appropriate internal value 340 | case "$next_is_arg" in 341 | 1) users_name=$arg ;; 342 | 2) proj_version=$arg ;; 343 | *) show_error "Unknown argument" exit 1 ;; 344 | esac 345 | 346 | # Reset 347 | next_is_arg=0 348 | else 349 | if [[ "$upper_arg" == "-N" || "$upper_arg" == "--NAME" ]]; then 350 | next_is_arg=1 351 | elif [[ "$upper_arg" == "-C" || "$upper_arg" == "--CPP" ]]; then 352 | do_cpp=1 353 | elif [[ "$upper_arg" == "-A" || "$upper_arg" == "--ASM " ]]; then 354 | do_asm=1 355 | elif [[ "$upper_arg" == "-D" || "$upper_arg" == "--DEBUG" ]]; then 356 | do_swd=1 357 | elif [[ "$upper_arg" == "-V" || "$upper_arg" == "--VERSION" ]]; then 358 | next_is_arg=2 359 | elif [[ "$upper_arg" == "-H" || "$upper_arg" == "--HELP" ]]; then 360 | show_help 361 | exit 0 362 | else 363 | projects+=("$arg") 364 | fi 365 | 366 | last_arg=$uarg 367 | fi 368 | done 369 | 370 | # FROM 2.3.0 Watch out for conflicting project types 371 | if [[ $do_cpp -eq 1 && do_asm -eq 1 ]]; then 372 | show_error "Conflicting project types selected" 373 | fi 374 | 375 | if [[ ${#projects[@]} -gt 0 ]]; then 376 | for project in "${projects[@]}"; do 377 | make_project "$project" 378 | done 379 | else 380 | show_help 381 | exit 0 382 | fi 383 | -------------------------------------------------------------------------------- /media-backup.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # media-backup.zsh 5 | # 6 | # Backup to Disk Script 7 | # 8 | # @author Tony Smith 9 | # @copyright 2025, Tony Smith 10 | # @version 1.0.1 11 | # @license MIT 12 | # 13 | 14 | typeset -i do_music=1 15 | typeset -i do_books=1 16 | target_disk="500GB" 17 | # Arrays of directory paths: local machine, music server mount, home server mount 18 | local_sources=('Library/Mobile Documents/com~apple~CloudDocs/Documents/eBooks') 19 | music_sources=(Alternative Classical Comedy 'Doctor Who' Electronic Folk Pop Metal Rock 20 | SFX Singles Soundtracks 'Spoken Word' Instrumental) 21 | home_sources=(Comics) 22 | 23 | bold=$(tput bold) 24 | normal=$(tput sgr0) 25 | 26 | # Functions 27 | do_sync() { 28 | # Sync the source to the target 29 | # Arg 1 should be the source directory 30 | # Arg 2 should be the target directory 31 | local name="${1:t}" 32 | echo -n " Syncing ${name}... " 33 | 34 | # Prepare a readout of changed files ONLY (rsync does not do this) 35 | local list=$(rsync -az "$1" "$2" -i --exclude ".DS_Store") 36 | local lines=$(grep '>' < <(echo -e "$list")) 37 | 38 | # Check we have files to report 39 | if [[ -n "$lines" ]]; then 40 | # Files were sync'd so count the total number 41 | typeset -i count=0 42 | local cols=$(tput cols) 43 | while IFS= read -r line; do 44 | ((count++)) 45 | done <<< "${lines}" 46 | echo "${count} files changed:" 47 | # Output the files changed 48 | count=1 49 | while IFS= read -r line; do 50 | # rsync 2.6.x use 'cut -c 11-'; below is for 3.3.x 51 | local trimmed=$(echo "${line}" | cut -c 14-) 52 | number=$(printf "%03d" ${count}) 53 | [[ -n "${trimmed}" ]] && echo " ${number}. /${trimmed}" 54 | ((count++)) 55 | done <<< "$lines" 56 | else 57 | echo "no files changed" 58 | fi 59 | } 60 | 61 | show_help() { 62 | echo "media-backup" 63 | echo "Usage:" 64 | echo " media-backup {-m|-b} {drive_name}" 65 | echo "Options:" 66 | echo " -m / --music Backup music only. Default: backup both" 67 | echo " -b / --books Backup eBooks only. Default: backup both" 68 | echo " Optional drive name. Default: 500GB" 69 | echo 70 | } 71 | 72 | show_error_and_exit() { 73 | echo "[ERROR] $1" 1>&2 74 | exit 1 75 | } 76 | 77 | 78 | # Runtime start 79 | # Process the arguments 80 | typeset -i arg_count=0 81 | for arg in "$@"; do 82 | # Temporarily convert argument to lowercase, zsh-style 83 | check_arg=${arg:l} 84 | if [[ "${check_arg}" = "--books" || "${check_arg}" = "-b" ]]; then 85 | do_music=0 86 | ((arg_count += 1)) 87 | elif [[ "${check_arg}" = "--music" || "${check_arg}" = "-m" ]]; then 88 | do_books=0 89 | ((arg_count += 1)) 90 | elif [[ "${check_arg}" = "--help" || "${check_arg}" = "-h" ]]; then 91 | show_help 92 | exit 0 93 | else 94 | target_disk="${arg}" 95 | ((arg_count += 1)) 96 | fi 97 | done 98 | 99 | # Check that the user is not excluding both jobs 100 | [[ ${do_books} -eq 0 && ${do_music} -eq 0 ]] && show_error_and_exit "Mutually exclusive options set -- backup cannot continue" 101 | 102 | # Set the target path based on supplied disk name (or default) 103 | target_path="/Volumes/${target_disk}" 104 | 105 | # Make sure the target disk is mounted 106 | if [[ -d "${target_path}" ]]; then 107 | echo "Disk ${bold}${target_disk}${normal} mounted." 108 | 109 | # Sync document sources 110 | if [[ ${do_books} -eq 1 ]]; then 111 | echo "${bold}Locally hosted items${normal}" 112 | for source in "${local_sources[@]}"; do 113 | do_sync "${HOME}/${source}" "${target_path}" 114 | done 115 | 116 | echo "${bold}Server-hosted items${normal}" 117 | if [[ -d /Volumes/home ]]; then 118 | for source in "${home_sources[@]}"; do 119 | do_sync "/Volumes/home/${source}" "${target_path}" 120 | done 121 | else 122 | show_error_and_exit "${bold}Home${normal} server not mounted" 123 | fi 124 | fi 125 | 126 | # Sync music sources 127 | if [[ ${do_music} -eq 1 ]]; then 128 | echo "${bold}Server-hosted music${normal}" 129 | if [[ -d /Volumes/Music ]]; then 130 | for source in "${music_sources[@]}"; do 131 | do_sync "/Volumes/Music/${source}" "${target_path}/Music" 132 | done 133 | else 134 | show_error_and_exit "${bold}Music${normal} server not mounted" 135 | fi 136 | fi 137 | else 138 | show_error_and_exit "Disk ${bold}${target_disk}${normal} is not mounted." 139 | fi 140 | 141 | exit 0 142 | -------------------------------------------------------------------------------- /mnuiconprep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # mnuiconprep.sh 5 | # 6 | # Prep MNU images 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-20, Tony Smith 10 | # @version 1.0.1 11 | # @license MIT 12 | # 13 | 14 | 15 | # Function to show help info - keeps this out of the code 16 | function showHelp() { 17 | echo -e "\nIcon Maker for MNU\n" 18 | echo -e "Usage:\n mnuiconprep [-p path] [-d path] [-t type]\n" 19 | echo "Options:" 20 | echo " -s / --source [path] The path of the source image(s). Default: ~/Downloads" 21 | echo " -d / --destination [path] The path to the target folder. Default: ~/Desktop" 22 | echo " -h / --help This help screen" 23 | echo 24 | } 25 | 26 | 27 | # Set inital state values 28 | sourceFolder="$HOME/Downloads" 29 | destFolder="$HOME/Desktop" 30 | extension="png" 31 | argIsAValue=0 32 | args=(-s -d) 33 | # Set required sizes (@2x will be created too: 34 | # 20 - Menu icon 35 | # 64 - Popover icon 36 | m_a_sizes=(20 64) 37 | 38 | # Functions 39 | m_a_make() { 40 | # Make MNU script icons 41 | for size in ${m_a_sizes[@]}; do 42 | # Set the destination file name 43 | destFile=${1%.*} 44 | 45 | # Set the destination extension lowercase 46 | extension=${1##*.} 47 | extension=${extension,,} 48 | 49 | # Make the standard-size image 50 | make "$1" "$destFolder/$destFile-$size.$extension" "$size" 51 | 52 | # Make the retina-size image (@2x) 53 | retinaSize=$(($size * 2)) 54 | make "$1" "$destFolder/$destFile-$size@2x.$extension" "$retinaSize" 55 | done 56 | } 57 | 58 | make() { 59 | # Generic function to copy source to new file and then resize the copy using SIPS 60 | # $1 - The source image file 61 | # $2 - The destination image file 62 | # $3 - The destination image size (width and height) 63 | cp "$1" "$2" 64 | sips "$2" -Z "$3" -i > /dev/null 65 | } 66 | 67 | # Process the arguments 68 | argCount=0 69 | for arg in "$@" 70 | do 71 | if [[ $argIsAValue -gt 0 ]]; then 72 | # The argument should be a value (previous argument was an option) 73 | if [[ ${arg:0:1} = "-" ]]; then 74 | # Next value is an option: ie. missing value 75 | echo "Error: Missing value for ${args[((argIsAValue - 1))]}" 76 | exit 1 77 | fi 78 | 79 | # Set the appropriate internal value 80 | case "$argIsAValue" in 81 | 1) sourceFolder=$arg ;; 82 | 2) destFolder=$arg ;; 83 | *) echo "Error: Unknown argument" exit 1 ;; 84 | esac 85 | 86 | argIsAValue=0 87 | else 88 | if [[ $arg = "-s" || $arg = "--source" ]]; then 89 | argIsAValue=1 90 | elif [[ $arg = "-d" || $arg = "--destination" ]]; then 91 | argIsAValue=2 92 | elif [[ $arg = "-h" || $arg = "--help" ]]; then 93 | showHelp 94 | exit 0 95 | fi 96 | fi 97 | 98 | ((argCount++)) 99 | if [[ $argCount -eq $# && $argIsAValue -ne 0 ]]; then 100 | echo "Error: Missing value for $arg" 101 | exit 1 102 | fi 103 | done 104 | 105 | # Make sure we have a source image 106 | if [ "$sourceFolder" != "UNSET" ]; then 107 | if [ -d "$sourceFolder" ]; then 108 | if [ -d "$destFolder" ] ; then 109 | cd "$sourceFolder" 110 | for file in * 111 | do 112 | # Make the images 113 | echo "Processing '$file'..." 114 | m_a_make "$file" 115 | done 116 | else 117 | echo "Destination folder $destFolder can't be found" 118 | exit 1 119 | fi 120 | else 121 | echo "Source folder $sourceFolder can't be found" 122 | exit 1 123 | fi 124 | else 125 | echo "Error: No source folder set" 126 | exit 1 127 | fi 128 | -------------------------------------------------------------------------------- /mp-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # MicroPython Checker 5 | # 6 | # Print the latest version of MicroPython (https://micropython.org) available 7 | # 8 | # @author Tony Smith 9 | # @copyright 2024, Tony Smith 10 | # @version 1.2.0 11 | # @license MIT 12 | # 13 | # @Pre-requisites: `requests` library [`pip3 install requests`] 14 | # 15 | # ΝΟΤΕS 16 | # 17 | # 1.2.0 - Inverts behaviour: previews must now be requested with --include-previews 18 | 19 | import requests 20 | import argparse 21 | import sys 22 | 23 | 24 | def compare(b, i): 25 | global highest 26 | if highest[i] > b[i]: return True 27 | if highest[i] < b[i]: 28 | highest = b 29 | return True 30 | return False 31 | 32 | 33 | # semver index values 34 | MAJOR = 0 35 | MINOR = 1 36 | PATCH = 2 37 | 38 | # Versions 39 | current = [0, 0, 0] 40 | highest = [0, 0, 0] 41 | 42 | # Get the current version as an arg 43 | parser = argparse.ArgumentParser(description="Compare your current MicroPython version with the latest") 44 | parser.add_argument("-v", "--version", metavar="x.y.z", type=str, help="A MicroPython version, eg. 1.16.1", required=False) 45 | parser.add_argument("-i", "--include-previews", dest="ignore", action='store_false', help="Whether proview releases should be listed", required=False) 46 | parser.set_defaults(ignore=True) 47 | args = parser.parse_args() 48 | 49 | # Check any parsed version 50 | if args.version is not None: 51 | try: 52 | parts = args.version.split(".") 53 | if len(parts) > 3: throw 54 | if len(parts) == 3: current = [int(parts[0]), int(parts[1]), int(parts[2])] 55 | if len(parts) == 2: current = [int(parts[0]), int(parts[1]), 0] 56 | if len(parts) == 1: current = [int(parts[0]), 0, 0] 57 | except Exception as _: 58 | print("[ERROR] Could not process", args.version, "as a version number") 59 | sys.exit(1) 60 | 61 | # Get the latest MicroPython version 62 | url = "https://api.github.com/repos/micropython/micropython/tags" 63 | headers = {"Accept": "application/vnd.github.v3+json"} 64 | response = requests.get(url, headers=headers) 65 | if response.status_code == 200: 66 | # Got data -- try and parse it 67 | try: 68 | data = response.json() 69 | # Get the latest MP version 70 | for item in data: 71 | if "name" in item: 72 | name = item["name"] 73 | if name[0] == "v": name = name[1:] 74 | parts = name.split(".") 75 | if len(parts) == 2: parts.append("0") 76 | is_preview = False 77 | if "preview" in parts[2]: 78 | parts[2] = parts[2].split("-")[0] 79 | is_preview = True 80 | version = [int(parts[0]), int(parts[1]), int(parts[2]), is_preview] 81 | parts = version 82 | 83 | if (is_preview and not args.ignore) or not is_preview: 84 | if compare(version, MAJOR): continue 85 | if compare(version, MINOR): continue 86 | if highest[PATCH] < parts[PATCH]: highest = parts 87 | 88 | # Only compare version if one was supplied 89 | highest_version = str(highest[0]) + "." + str(highest[1]) + "." + str(highest[2]) 90 | if highest[3]: 91 | highest_version += " (PREVIEW)" 92 | if args.version is not None: 93 | match = 0 94 | for i in range(0, 3): 95 | if highest[i] == current[i]: match += 1 96 | if match == 3: 97 | print("You have the current version of MicroPython", highest_version) 98 | else: 99 | print("Micropython current release is", highest_version, "-- you have", args.version) 100 | else: 101 | print("Micropython current release is", highest_version) 102 | except Exception as e: 103 | print("[ERROR] Could not parse response from GitHub", e) 104 | else: 105 | print("[ERROR] Unable to access MicroPython repo") 106 | -------------------------------------------------------------------------------- /outman.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # outman.zsh 5 | # 6 | # Output a man page to a text file 7 | # 8 | # @author Tony Smith 9 | # @copyright 2020, Tony Smith 10 | # @version 1.0.4 11 | # @license MIT 12 | # 13 | 14 | APP_NAME=$(basename $0) 15 | APP_NAME=${APP_NAME:t} 16 | APP_VERSION="1.0.4" 17 | 18 | # Functions 19 | show_help() { 20 | echo -e "outman $APP_VERSION\n" 21 | echo -e "Usage:\n" 22 | echo -e " outman ... \n" 23 | echo -e "Options:\n" 24 | echo -e " -h / --help Show this help page\n" 25 | echo -e "Description:\n" 26 | echo " Each man page must be accompanied by a target text file, or the script" 27 | echo " will not continue and no files will be generated. If the path to the " 28 | echo " target file does not exist, it will be created if permissions allow." 29 | echo " If no file extension is provided for the target, '.txt' will be added." 30 | exit 0 31 | } 32 | 33 | # FROM 1.0.2 34 | show_error() { 35 | echo "${APP_NAME} error: $1" 1>&2 36 | } 37 | 38 | # Runtime start 39 | # Process the arguments 40 | typeset -i is_source=1 41 | sources=() 42 | targets=() 43 | for arg in "$@"; do 44 | test_arg=${arg:l} 45 | if [[ $test_arg = "-h" || $test_arg = "--help" ]]; then 46 | show_help 47 | exit 0 48 | fi 49 | 50 | # Add the source and target arguments to the arrays 51 | if [[ $is_source -eq 1 ]]; then 52 | is_source=0 53 | sources+=($arg) 54 | else 55 | is_source=1 56 | targets+=($arg) 57 | fi 58 | done 59 | 60 | # Check that source and target counts match 61 | if [[ ${#sources} -ne ${#targets} ]]; then 62 | show_error "At least one specified man page has no text file target -- cannot continue " 63 | exit 1 64 | fi 65 | 66 | # Specify 'target_count' as an integer 67 | typeset -i target_count=0 68 | for a_source in $sources; do 69 | # Increase the target array index counter 70 | ((target_count++)) 71 | 72 | # Get the man page name from the source 73 | man_page=$(man "$a_source" 2>&1) 74 | 75 | # Check that there IS a man page for the entry 76 | if [[ $? -eq 0 ]]; then 77 | # Get the target that matches the current source 78 | a_target=${targets[$target_count]} 79 | 80 | # Check the path exists 81 | file_path=${a_target:h} 82 | if [[ ! -e "$file_path" ]]; then 83 | if mkdir -p "$file_path"; then 84 | echo "[INFO] Path '$file_path' created" 85 | else 86 | show_error "Could not create path '$file_paths'" 87 | continue 88 | fi 89 | fi 90 | 91 | # Get the file name, zsh style, and check it's not null 92 | # If it is, append '.txt' to the target 93 | file_extension=${a_target:t:e} 94 | if [[ -z "$file_extension" ]]; then 95 | a_target="$a_target.txt" 96 | fi 97 | 98 | # Output the man page to the text file 99 | echo "$man_page" | col -b > "$a_target" 100 | else 101 | show_error "'$a_source' is not a valid man page" 102 | fi 103 | done 104 | -------------------------------------------------------------------------------- /packapp.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # packapp 5 | # 6 | # App release preparation script 7 | # 8 | # @author Tony Smith (@smittytone) 9 | # @copyright 2023 Tony Smith 10 | # @version 4.0.5 11 | # @license MIT 12 | # 13 | 14 | APP_NAME=$(basename $0) 15 | APP_NAME=${APP_NAME:t} 16 | APP_VERSION="4.0.5" 17 | 18 | typeset -i is_arg=0 19 | typeset -i add_scripts=0 20 | typeset -i debug=0 21 | typeset -i poll_delay=30 22 | app_name="none" 23 | app_source="$PWD" 24 | script_source="$PWD" 25 | path_arg="zzz" 26 | poll_status="zzz" 27 | uname="none" 28 | cert="none" 29 | 30 | # Functions 31 | # Simple function to present help information 32 | show_help() { 33 | echo -e "\npackapp -- create a signed and notarized app package\n" 34 | echo "This script requires an Apple Developer Account. You will need to set up an app key" 35 | echo "for the Apple ID to which your Developer Account is linked, and to have saved this app key" 36 | echo "in your Mac's keychain under the ID 'AC_PASSWORD'. Pass the keychain item's account name to" 37 | echo "the script as your username." 38 | echo "Get the app key by signing into 'https://appleid.apple.com/account/home' and visiting" 39 | echo "Sign-In and Security > App-Specific Passwords > Generate an app-specific password." 40 | echo -e "\nUsage: packapp [OPTIONS] \n" 41 | echo "Options:" 42 | echo " -s / --scripts [path] - Add pre- and/or post-install scripts to the package." 43 | echo " -u / --user - Your username." 44 | echo " -c / --cert - Your Developer ID Installer certificate descriptor, eg." 45 | echo " -c / --cert - 'Developer ID Installer: Fred Bloggs (ABCDEF1234)'" 46 | echo " -v / --verbose - Show extra informational output." 47 | echo -e " -h / --help - This help page.\n" 48 | echo "Example:" 49 | echo " packapp.zsh -u my_keychain_un -c \"Developer ID Installer: Fred Bloggs (ABCDEF1234)\" \"\$HOME/my app.app\"" 50 | echo 51 | } 52 | 53 | show_error() { 54 | echo "${APP_NAME} error: $1" 1>&2 55 | } 56 | 57 | 58 | # Exit script on fail 59 | #set -e 60 | setopt nomatch 61 | 62 | # Process the command line arguments 63 | for var in "$@"; do 64 | if [ $is_arg -eq 0 ]; then 65 | # Make argument lower case 66 | var=${var:l} 67 | if [[ "$var" = "-h" || "$var" = "--help" ]]; then 68 | # Display help then bail 69 | show_help 70 | exit 0 71 | elif [[ "$var" = "-v" || "$var" = "--verbose" ]]; then 72 | # Enable verbose mode 73 | debug=1 74 | elif [[ "$var" = "-s" || "$var" = "--scripts" ]]; then 75 | # Next arg should be the script directory path 76 | add_scripts=1 77 | is_arg=1 78 | elif [[ "$var" = "-u" || "$var" = "--user" ]]; then 79 | # Get the username 80 | is_arg=2 81 | elif [[ "$var" = "-c" || "$var" = "--cert" ]]; then 82 | # Get the Developer ID Installer cert name 83 | is_arg=3 84 | elif [ "${var:0:1}" = "-" ]; then 85 | # An unknown arg included: warn and bail 86 | show_error "Unknown option ($var) included" 87 | exit 1 88 | else 89 | # Assume this is the app path 90 | path_arg="$var" 91 | fi 92 | else 93 | if [ "${var:0:1}" = "-" ]; then 94 | show_error "Missing argument" 95 | exit 1 96 | elif [ $is_arg -eq 1 ]; then 97 | # Set the script source 98 | script_source="$var" 99 | elif [ $is_arg -eq 2 ]; then 100 | # Set the username 101 | uname="$var" 102 | elif [ $is_arg -eq 3 ]; then 103 | # Set the Developer ID Installer cert 104 | cert="$var" 105 | else 106 | # An arg does not have a value: warn and bail 107 | show_error "Option selected without expected argument" 108 | exit 1 109 | fi 110 | 111 | is_arg=0 112 | fi 113 | done 114 | 115 | # Parse any specifiec app path 116 | while true; do 117 | if [[ "$path_arg" != "zzz" ]]; then 118 | app_name=${path_arg:t} 119 | if [[ "$app_name" != "$path_arg" ]]; then 120 | # The argument is a path, so extract the directory 121 | app_source=${path_arg:h} 122 | fi 123 | 124 | extension=${app_name:e} 125 | 126 | if [[ -n "$extension" ]]; then 127 | # The supplied name has an extension... 128 | if [[ "$extension" != "app" ]]; then 129 | # ...but it's not .app, so bail 130 | show_error "Selected file is not an app (it's a .$extension)" 131 | exit 1 132 | else 133 | # Remove the extension from the filename 134 | app_name=${app_name:r} 135 | break 136 | fi 137 | else 138 | # The supplied name lacks an extenion... is it file or dir? 139 | if [[ -d "$path_arg" ]]; then 140 | # It's a directory, so use the full path 141 | app_source=$path_arg 142 | path_arg="zzz" 143 | else 144 | break 145 | fi 146 | fi 147 | else 148 | # No app name specified, so check for a .app file in source directory 149 | # Set the following option to pevent an error on empty directories 150 | setopt NULL_GLOB 151 | typeset -i app_count=0 152 | for file in ${app_source}/* ; do 153 | # Only process directories (.app is a directory) 154 | if [[ -d "$file" ]]; then 155 | # Get the extension 156 | extension=${file:t:e} 157 | 158 | # If it's a .app, use it as the app name, eg. 'MNU' 159 | if [[ "$extension" = "app" ]]; then 160 | app_name=${file:t:r} 161 | ((app_count += 1)) 162 | fi 163 | fi 164 | done 165 | # Put zsh back to where it was 166 | unsetopt NULL_GLOB 167 | 168 | # Check, report and bail on multiple .app matches 169 | if [[ $app_count -gt 1 ]]; then 170 | show_error "Multiple apps found in $app_source. Please specify the one you want to package" 171 | exit 1 172 | fi 173 | 174 | # Check, report and bail on no .app matches 175 | if [[ $app_count -eq 0 ]]; then 176 | show_error "No apps found in $app_source " 177 | exit 1 178 | fi 179 | 180 | break 181 | fi 182 | done 183 | 184 | # Set the filename 185 | app_filename="$app_name.app" 186 | 187 | # FROM 3.0.0 188 | # Confirm we have a username... 189 | if [[ "$uname" = "none" ]]; then 190 | show_error "You must provide a username with the -u/--user option" 191 | exit 1 192 | fi 193 | 194 | # ...and a cert 195 | if [[ "$cert" = "none" ]]; then 196 | show_error "You must provide a Developer ID Installer with the -c/--cert option" 197 | exit 1 198 | fi 199 | 200 | # Confirm the specified app is present in the source directory 201 | if [[ ! -e "$app_source/$app_filename" ]]; then 202 | show_error "App '$app_name' not found in '$app_source'" 203 | exit 1 204 | fi 205 | 206 | # Check for script additions 207 | if [[ $add_scripts -eq 1 ]]; then 208 | if [[ ! -e "$script_source" ]]; then 209 | show_error "'$script_source' scripts directory missing " 210 | exit 1 211 | fi 212 | fi 213 | 214 | # Get the bundle ID 215 | bundle_id=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$app_source/$app_filename/Contents/Info.plist") 216 | 217 | # Finally, output the data we have parsed 218 | if [[ $debug -eq 1 ]]; then 219 | echo "App Path: $app_source" 220 | echo "App Name: $app_filename" 221 | echo "Bundle ID: $bundle_id" 222 | echo "Dev. ID Cert: $cert" 223 | 224 | if [ -n "$extra" ]; then 225 | echo " Scripts: $script_source" 226 | fi 227 | 228 | # Check the app to be packaged 229 | spctl -a -v ${app_source}/${app_filename} 230 | fi 231 | 232 | # Build the package 233 | echo "Making and signing package... " 234 | if [[ $add_scripts -eq 1 ]]; then 235 | success=$(pkgbuild --scripts "$script_source" --identifier "$bundle_id.pkg" --install-location '/Applications' --sign "$cert" --component "$app_source/$app_filename" "$app_source/$app_name.pkg") 236 | else 237 | success=$(pkgbuild --identifier "$bundle_id.pkg" --install-location '/Applications' --sign "$cert" --component "$app_source/$app_filename" "$app_source/$app_name.pkg") 238 | fi 239 | 240 | if [[ -z "$success" ]]; then 241 | show_error "Package build error" 242 | exit 1 243 | fi 244 | 245 | # Notarize the package by getting the app's bundle ID then calling altool 246 | # See https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/customizing_the_notarization_workflow#3087734 247 | echo "Uploading package for notarization... this may take some time " 248 | n_time=$(date +%s) 249 | response=$(xcrun altool --notarize-app --file "$app_source/$app_name.pkg" --primary-bundle-id "$bundle_id" --username "$uname" --password "@keychain:AC_PASSWORD") 250 | 251 | # Check the altool response for errors, and bail if there are any 252 | err_line=$(grep 'product-errors' < <(echo -e "$response")) 253 | if [[ -n "$err_line" ]]; then 254 | show_error "$response" 255 | exit 1 256 | fi 257 | 258 | # Get the notarization job ID from the response 259 | e_time=$(date +%s) 260 | job_id_line=$(grep 'RequestUUID =' < <(echo -e "$response")) 261 | job_id=$(echo "$job_id_line" | cut -d "=" -s -f 2 | cut -d " " -f 2) 262 | 263 | if [[ $debug -eq 1 ]]; then 264 | n_time=$((e_time - n_time)) 265 | echo "Notarization upload completed after $n_time seconds. Notarization job ID: $job_id" 266 | fi 267 | 268 | # Repeatedly check the notarization status until the job succeeds or fails 269 | # TODO Add a timeout and error check 270 | echo "Polling for notarization... " 271 | n_time=$(date +%s) 272 | while true; do 273 | # Request a notarization job status 274 | # See https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/customizing_the_notarization_workflow#3087732 275 | response=$(xcrun altool --notarization-info "$job_id" -u "$uname" -p "@keychain:AC_PASSWORD") 276 | 277 | # Check the altool response for errors, and bail if there are any 278 | if [[ -z "$response" ]]; then 279 | show_error "$response " 280 | exit 1 281 | fi 282 | 283 | # Parse the response to get the job status 284 | status_line=$(grep '\s*Status:' < <(echo -e "$response")) 285 | poll_status=$(echo "$status_line" | cut -d ":" -s -f 2) 286 | 287 | if [[ $debug -eq 1 ]]; then 288 | echo "Status: $poll_status" 289 | fi 290 | 291 | # Break out of the poll loop on notarization success 292 | if [[ "$poll_status" = "success" || "$poll_status" = " success" ]]; then 293 | break 294 | fi 295 | 296 | # Exit the app on notarization failure 297 | if [[ "$poll_status" = "invalid" || "$poll_status" = " invalid" ]]; then 298 | show_error "Unable to notarize $app_name.pkg -- outputting response: " 299 | echo "$response" 300 | exit 1 301 | fi 302 | 303 | # Pause for 'pollDelay' then continue the poll loop 304 | sleep "$poll_delay" 305 | done 306 | 307 | e_time=$(date +%s) 308 | 309 | if [[ $debug -eq 1 ]]; then 310 | n_time=$((e_time - n_time)) 311 | echo "Polling completed after $n_time seconds." 312 | fi 313 | 314 | echo "Adding notarization to $app_name.pkg " 315 | success=$(xcrun stapler staple "$app_source/$app_name.pkg") 316 | if [[ -z "$success" ]]; then 317 | exit 1 318 | fi 319 | 320 | # Confirm stapling 321 | if [[ $debug -eq 1 ]]; then 322 | spctl -a -v --type install "$app_source/$app_name.pkg" 323 | fi 324 | 325 | echo "Done -- you can now add $app_name.pkg to a .dmg file " 326 | -------------------------------------------------------------------------------- /packcli.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # packcli 5 | # 6 | # Command line tool release preparation script 7 | # 8 | # @author Tony Smith (@smittytone) 9 | # @copyright 2023 Tony Smith 10 | # @version 4.0.2 11 | # @license TBD 12 | # 13 | 14 | 15 | # Simple function to present help information 16 | show_help() { 17 | echo -e "\npackcli -- create a signed and notarized CLI app package\n" 18 | echo "This script requires an Apple Developer Account. You will need to set up a 2FA app key" 19 | echo "for the Apple ID linked to your Developer Account, and to have saved this key in your" 20 | echo "Mac's keychain. Pass the keychain item's name to the script as your profile." 21 | echo "Get the app key by signing into 'https://appleid.apple.com/account/home' and visiting" 22 | echo "Sign-In and Security > App-Specific Passwords > Generate an app-specific password." 23 | echo -e "Usage: packcli.zsh [OPTIONS]\n" 24 | echo "Options:" 25 | echo " -s / --source {path} The location of the target project. Default: current directory." 26 | echo " -n / --name {name} The target's name. If no name is supplied, packcli uses" 27 | echo " the name of the project directory." 28 | echo " -v / --version {version} The target's version. Default: 1.0.0." 29 | echo " -b / --bundleid {ID} The target's bundle ID. packcli will attempt to read this" 30 | echo " from the project's Info.plist file." 31 | echo " -p / --profile {name} The keychain ID for your profile. Use notarytool to generate." 32 | echo " -c / --cert {name} Your Apple Developer Installer certificate name, eg." 33 | echo " 'Developer ID Installer: Fred Bloggs (ABCDEF1234)'." 34 | echo " -a / --add {path} Add scripts to the package. Default: the project's pkgscripts directory." 35 | echo " -z Build the app but do not package it." 36 | echo " -d / --debug Enable debugging messages." 37 | echo -e " -h / --help This help page.\n" 38 | } 39 | 40 | # Emit error then exit 41 | show_error_then_exit() { 42 | echo "[ERROR] $1 " 1>&2 43 | exit 1 44 | } 45 | 46 | # Setup 47 | setopt nomatch 48 | app_name=untitled 49 | app_version=1.0.0 50 | app_dir="$PWD" 51 | scripts_dir="${app_dir}/pkgscripts" 52 | bundle_id=none 53 | profile_name=none 54 | cert_name=none 55 | typeset -i is_arg=0 56 | typeset -i add_scripts=0 57 | typeset -i debug=0 58 | typeset -i no_pack=0 59 | 60 | # Process the command line arguments 61 | for arg in "$@"; do 62 | if [[ ${is_arg} -eq 0 ]]; then 63 | var=${arg:l} 64 | if [[ "${var}" = "-h" || "${var}" = "--help" ]]; then 65 | # Display help then bail 66 | show_help 67 | exit 0 68 | elif [[ "${var}" = "-a" || "${var}" = "--add" ]]; then 69 | is_arg=8 70 | add_scripts=1 71 | elif [[ "${var}" = "-d" || "${var}" = "--debug" ]]; then 72 | debug=1 73 | elif [[ "${var}" = "-n" || "${var}" = "--name" ]]; then 74 | # Next arg should be the cli tool name, eg. 'pdfmaker' 75 | is_arg=2 76 | elif [[ "${var}" = "-s" || "${var}" = "--source" ]]; then 77 | # Next arg should be the project folder, eg. '$GIT/pdfmaker' 78 | is_arg=3 79 | elif [[ "${var}" = "-b" || "${var}" = "--bundleid" ]]; then 80 | # Next arg should be the cli tool bundle ID, eg. 'com.bps.pdfmaker' 81 | is_arg=4 82 | elif [[ "${var}" = "-v" || "${var}" = "--version" ]]; then 83 | # Next arg should be the cli tool's version string 84 | is_arg=5 85 | elif [[ "${var}" = "-c" || "${var}" = "--cert" ]]; then 86 | # Next arg should be the cert name 87 | is_arg=7 88 | elif [[ "${var}" = "-p" || "${var}" = "--profile" ]]; then 89 | # Next arg should be the the profile name 90 | is_arg=9 91 | elif [[ "${var}" = "-z" ]]; then 92 | # Don't create a package 93 | no_pack=1 94 | else 95 | # An unknown arg included: warn and bail 96 | show_error_then_exit "Unknown option (${arg}) included" 97 | fi 98 | else 99 | case ${is_arg} in 100 | 2) app_name="${arg}" ;; 101 | 3) app_dir="${arg}" ;; 102 | 4) bundle_id="${arg}" ;; 103 | 5) app_version="${arg}" ;; 104 | 6) user_name="${arg}" ;; 105 | 7) cert_name="${arg}" ;; 106 | 8) scripts_dir="${arg}" ;; 107 | 9) profile_name="${arg}" ;; 108 | *) show_error_then_exit "Option selected without expected parameter: ${is_arg}" ;; 109 | esac 110 | is_arg=0 111 | fi 112 | done 113 | 114 | # Switch to app source directory 115 | cd "${app_dir}" || show_error_then_exit "Could not switch to app directory" 116 | 117 | # FROM 4.0.0 -- Correctly set default app name 118 | if [[ "${app_name}" = "untitled" ]]; then 119 | app_name="$PWD" 120 | app_name="${app_name:t}" 121 | fi 122 | 123 | if [[ ${no_pack} -eq 0 ]]; then 124 | # FROM 4.0.0 -- Check bundle ID before we proceed 125 | if [[ "${bundle_id}" = "none" ]]; then 126 | plist_path=$(find . -name 'Info.plist') 127 | if [[ -n "${plist_path}" ]]; then 128 | # Extract bundle ID from project info.plist file 129 | bundle_id=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "${plist_path}") 130 | else 131 | show_error_then_exit "No bundle ID specified or found" 132 | fi 133 | fi 134 | 135 | # FROM 4.0.0 -- Confirm we have a profile name 136 | if [[ "${profile_name}" = "none" ]]; then 137 | show_error_then_exit "No keychain profile provided" 138 | fi 139 | 140 | # FROM 3.1.0 -- Confirm we have a cert name 141 | if [[ "${cert_name}" = "none" ]]; then 142 | show_error_then_exit "You must provide a certificate ID with the -c/--cert switch" 143 | fi 144 | 145 | # Check for script additions 146 | if [[ ${add_scripts} -eq 1 ]]; then 147 | if [[ ! -e "${scripts_dir}" ]]; then 148 | show_error_then_exit "Could not locate scripts directory ${scripts_dir}" 149 | fi 150 | fi 151 | fi 152 | 153 | # Debug output 154 | if [[ ${debug} -eq 1 && ${no_pack} -eq 0 ]]; then 155 | echo " App: ${app_name}" 156 | echo "Bundle ID: ${bundle_id}" 157 | echo " Version: ${app_version}" 158 | echo " Path: ${app_dir}/build/${app_name}" 159 | echo " PKG: ${app_dir}/build/${app_name}-${app_version}.pkg" 160 | if [[ ${add_scripts} -eq 1 ]]; then 161 | echo " Scripts: ${scripts_dir}" 162 | fi 163 | fi 164 | 165 | # Build the package 166 | echo "Building ${app_name}... " 167 | if [[ ${debug} -eq 1 ]]; then 168 | xcodebuild clean install -target "${app_name}" || show_error_then_exit "Could not build app" 169 | else 170 | xcodebuild -quiet clean install -target "${app_name}" || show_error_then_exit "Could not build app" 171 | fi 172 | 173 | # FROM 4.0.1 174 | # Exit if no package is required 175 | if [[ ${no_pack} -eq 1 ]]; then 176 | echo "Binary compiled to ${app_dir}/build/pkgroot/usr/local/bin/${app_name}" 177 | exit 0 178 | fi 179 | 180 | # Build and sign the package 181 | echo "Making and signing the package... " 182 | if [[ ${add_scripts} -eq 1 ]]; then 183 | success=$(pkgbuild --scripts "${scripts_dir}" --root build/pkgroot --identifier "${bundle_id}.pkg" --install-location "/" --sign "${cert_name}" --version "${app_version}" "build/${app_name}-${app_version}.pkg") 184 | else 185 | success=$(pkgbuild --root build/pkgroot --identifier "${bundle_id}.pkg" --install-location "/" --sign "${cert_name}" --version "${app_version}" "build/${app_name}-${app_version}.pkg") 186 | fi 187 | 188 | if [[ -z "${success}" ]]; then 189 | show_error_then_exit "Failed to make the package" 190 | fi 191 | 192 | # Notarize the package by getting the app's bundle ID then calling notarytool 193 | # NOTE altool is now deprecated 194 | echo "Requesting package notarization... this may take some time " 195 | n_time=$(date +%s) 196 | response=$(xcrun notarytool submit "build/${app_name}-${app_version}.pkg" --wait -p ${profile_name}) 197 | 198 | # Get the notarization job ID from the response 199 | e_time=$(date +%s) 200 | job_id_line=$(grep -m 1 ' id:' < <(echo -e "${response}")) 201 | job_id=$(echo "${job_id_line}" | cut -d ":" -s -f 2 | cut -d " " -f 2) 202 | 203 | if [[ ${debug} -eq 1 ]]; then 204 | n_time=$((e_time - n_time)) 205 | echo "Notarization completed after ${n_time} seconds. Job ID: ${job_id}" 206 | fi 207 | 208 | # Get the notarization status from the response 209 | status_line=$(grep -m 1 ' status:' < <(echo -e "${response}")) 210 | status_result=$(echo "${status_line}" | cut -d ":" -s -f 2 | cut -d " " -f 2) 211 | 212 | if [[ ${status_result} != "Accepted" ]]; then 213 | show_error_then_exit "Notarization failed with status q${status_result}" 214 | fi 215 | 216 | # Staple the notarization result 217 | echo "Adding notarization to build/${app_name}-${app_version}.pkg " 218 | success=$(xcrun stapler staple "build/${app_name}-${app_version}.pkg") 219 | if [[ -z "${success}" ]]; then 220 | show_error_then_exit "Could not staple notarization to app" 221 | fi 222 | 223 | # Confirm stapling 224 | echo "Checking notarization to build/${app_name}-${app_version}.pkg " 225 | spctl --assess -vvv --type install "build/${app_name}-${app_version}.pkg" 226 | 227 | # Clean up 228 | echo "Moving package to desktop " 229 | mv "build/${app_name}-${app_version}.pkg" "${HOME}/desktop/${app_name}-${app_version}.pkg" 230 | 231 | if [[ -d "${app_dir}/manpage" ]]; then 232 | echo "Moving man page file to desktop " 233 | cp "${app_dir}/manpage/"* $HOME/desktop 234 | fi 235 | 236 | rm -rf "build" 237 | exit 0 238 | -------------------------------------------------------------------------------- /pdfer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # pdfer.sh 5 | # 6 | # Convert .docx files downloaded from Google Docs into PDFs 7 | # Uses textutil (macOS/BSD) and cupsfilter (CUPS) 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-20, Tony Smith 11 | # @version 1.0.2 12 | # @license MIT 13 | # 14 | 15 | 16 | for file in ~+/* 17 | do 18 | # Only process files 19 | if [ -f "$file" ]; then 20 | # Get the extension and make it uppercase 21 | extension=${file##*.} 22 | extension=${extension^^*} 23 | 24 | # Make sure the file's of the right type 25 | if [ "$extension" = "DOCX" ]; then 26 | # Strip off the extension 27 | filename=${file%.*} 28 | 29 | # Convert the DOCX to HTML 30 | textutil -convert html -output "$filename.html" "$file" 31 | 32 | # Convert the HTML to PDF 33 | cupsfilter "$filename.html" > "$filename.pdf" 34 | 35 | # Remove the original file and the HTML file 36 | rm "$file" "$filename.html" >/dev/null 37 | else 38 | # Skipping a file... 39 | echo -e "Skipping $file with extension $extension" 40 | fi 41 | fi 42 | done 43 | -------------------------------------------------------------------------------- /pi.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # Pi Image Installation 4 | # 5 | # @version 2.1.1 6 | # @author Tony Smith (@smittytone) 7 | # @copyright 2022 8 | # @licence MIT 9 | 10 | # CONDITIONS 11 | unsetopt nomatch 12 | 13 | # GLOBALS 14 | pi_type=Standard 15 | pi_zip_file=NONE 16 | app_version="2.1.1" 17 | debug=0 18 | # Colours 19 | red="\e[40;31m" 20 | rasp="\e[107;31m" 21 | bold="\e[40;1m" 22 | reset="\e[0m" 23 | green="\e[40;32m" 24 | yellow="\e[40;33m" 25 | white="\e[40;97m" 26 | head="\e[107;30m" 27 | 28 | # FUNCTIONS 29 | show_error_and_exit() { 30 | echo -e "${red}${bold}[ERROR]${reset} ${1}" 31 | exit 1 32 | } 33 | 34 | get_pi_zip_file() { 35 | show_debug "File type: ${1:e:l}" 36 | if [[ "${1:e:l}" == "zip" ]]; then 37 | pi_zip_file=$(/bin/ls *.zip 2> /dev/null) 38 | else 39 | pi_zip_file=$(/bin/ls *.xz 2> /dev/null) 40 | fi 41 | } 42 | 43 | show_version() { 44 | echo -e "${green}Version ${app_version}${reset}" 45 | } 46 | 47 | show_debug() { 48 | if [[ $debug -eq 1 ]]; then 49 | echo -e "${yellow}${bold}[DEBUG]${reset} $1" 50 | fi 51 | } 52 | 53 | # RUNTIME START 54 | # Check input PiOS download location 55 | for var in "$@"; do 56 | if [[ ${var:u} = "-D" || ${var:u} = "--DEBUG" ]]; then 57 | debug=1 58 | else 59 | URL=${var} 60 | fi 61 | done 62 | 63 | # Get the Mac CPU type for the OpenSSL location 64 | # NOTE Actual values will depend on how you set up Homebrew 65 | CPU=$(uname -p) 66 | if [[ "$CPU" == "arm" ]]; then 67 | openssl_path=/opt/homebrew/opt/openssl/bin/openssl 68 | else 69 | openssl_path=/usr/local/opt/openssl/bin/openssl 70 | fi 71 | 72 | if [[ ! -f "${openssl_path}" ]]; then 73 | show_error_and_exit "This app requires OpenSSL. Please install it using Homebrew (brew install openssl@3) and then retry this app" 74 | fi 75 | 76 | # Show intro 77 | clear 78 | echo -e "${bold}${head} macOS ${rasp}Raspberry Pi${head} Image Installer with Optional WiFi Setup ${reset}" 79 | show_version 80 | read -k -s "choice?Install for a standard Pi [P] or a Pi Zero [Z] image (default: standard) " 81 | echo 82 | 83 | choice=${choice:u} 84 | if [[ "${choice}" = "Z" ]] pi_type=Zero 85 | 86 | if [[ ! -e tmp ]]; then 87 | show_debug "No tmp directory... creating one" 88 | mkdir tmp || show_error_and_exit "Could not create 'tmp' directory at this location" 89 | fi 90 | 91 | cd tmp || show_error_and_exit "Could not enter 'tmp' directory" 92 | 93 | # Get the PiOS image 94 | if [[ ! -f p.img ]]; then 95 | get_pi_zip_file ${URL} 96 | show_debug "Archive file: ${pi_zip_file}" 97 | 98 | if [[ ! -f "${pi_zip_file}" ]]; then 99 | # No zip file available, so download URL 100 | if [[ -z ${URL} ]]; then 101 | show_error_and_exit "No PiOS download URL specified.\nDownloads available at 'https://www.raspberrypi.com/software/operating-systems/'" 102 | # eg. https://downloads.raspberrypi.org/raspios_armhf/images/raspios_armhf-2022-01-28/2022-01-28-raspios-bullseye-armhf.zip 103 | fi 104 | if [[ ${URL:e:l} != "zip" && ${URL:e:l} != "xz" ]] show_error_and_exit "Please supply an archive file URL" 105 | 106 | # Get URL 107 | echo "Downloading Raspberry Pi ${pi_type} OS image... " 108 | curl -O -L -# ${URL} 109 | get_pi_zip_file ${URL} 110 | show_debug "Archive file: ${pi_zip_file}" 111 | fi 112 | 113 | read "choice?Enter SHA 256 or just hit [ENTER] to bypass this check " 114 | if [[ -n "${choice}" ]]; then 115 | echo "Calculating SHA256..." 116 | sha=$(shasum -a 256 ${pi_zip_file}) 117 | sha=$(echo "${sha}" | cut -d " " -f 1) 118 | echo "Download SHA 256: ${sha}" 119 | echo " Entered SHA 256: ${choice}" 120 | if [[ "${choice}" = "${sha}" ]]; then 121 | echo "SHA256 values match" 122 | else 123 | show_error_and_exit "SHAs do not match -- do not proceed with this file" 124 | fi 125 | fi 126 | 127 | # Unzip the image file 128 | echo "Decompressing Raspberry Pi ${pi_type} OS image... " 129 | if [[ ${URL:e:l} == "zip" ]]; then 130 | unzip -o ${pi_zip_file} > /dev/null 2>&1 131 | else 132 | xz -d -k ${pi_zip_file} > /dev/null 2>&1 133 | fi 134 | 135 | mv *.img p.img 136 | else 137 | show_debug "PiOS image already downloaded" 138 | fi 139 | 140 | read -k -s "choice?Insert SD card and press any key when it has appeared on the desktop " 141 | echo 142 | 143 | typeset -i ok=0 144 | while [[ ${ok} -eq 0 ]]; do 145 | echo "Disk list... " 146 | # NOTE Adding 'external' to following command doesn't always work: 147 | # Mac SD card slots list cards is internal (macOS 12.4 at least) 148 | diskutil list 149 | 150 | read "disk_num?Enter the SD card's disk number (eg. 2 for /dev/disk2) " 151 | 152 | if [ -z "${disk_num}" ]; then 153 | echo "Invalid disk number -- exiting " 154 | exit 1 155 | fi 156 | 157 | unmount_name="/dev/disk${disk_num}" 158 | dd_name="/dev/rdisk${disk_num}" 159 | 160 | if diskutil unmountdisk "${unmount_name}"; then 161 | # Copy across the PiOS disk image 162 | if [ -f p.img ]; then 163 | echo "About to copy Raspberry Pi ${pi_type} OS image to SD card ${unmount_name}... " 164 | read -k -s "key?Are you sure? [Y]es, [N]o or [E]xit Setup " 165 | echo 166 | 167 | if [[ ${key:u} != "Y" ]]; then 168 | if [[ ${key:u} = "E" ]]; then 169 | exit 0 170 | else 171 | continue 172 | fi 173 | fi 174 | 175 | echo "Copying Raspberry Pi ${pi_type} OS image to SD card ${unmount_name}... " 176 | sudo dd if=p.img of="${dd_name}" bs=1m 177 | else 178 | show_error_and_exit "Missing Pi img file... aborting" 179 | fi 180 | 181 | read -k -s "choice?Press any key when 'boot' has appeared on the desktop " 182 | echo 183 | 184 | # Enable SSH 185 | if [[ ! -e "/Volumes/boot/ssh" ]]; then 186 | echo "Enabling SSH... " 187 | touch "/Volumes/boot/ssh" 188 | fi 189 | 190 | # Set up user account 191 | while; do 192 | read "username?Enter your new system username " 193 | if [[ -n "${username}" ]]; then 194 | break 195 | else 196 | echo "[ERROR] Please enter a non-zero username" 197 | fi 198 | done 199 | 200 | while; do 201 | read "password?Enter your new system password " 202 | if [[ -n "${password}" ]]; then 203 | break 204 | else 205 | echo "[ERROR] Please enter a non-zero password" 206 | fi 207 | done 208 | 209 | echo "Setting up a user account for \"${username}\"" 210 | epw=$(echo "${password}" | "${openssl_path}" passwd -6 -stdin) 211 | echo "${username}:${epw}" > "/Volumes/boot/userconf.txt" 212 | 213 | # Set up WiFi 214 | read "ssid?Enter your WiFi SSID " 215 | if [[ -n "${ssid}" ]]; then 216 | read "psk?Enter your WiFi password " 217 | echo "Setting up WiFi... SSID: \"${ssid}\", PWD: \"${psk}\"" 218 | echo -e "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\ncountry=GB\n\nnetwork={\n ssid=\"${ssid}\"\n psk=\"${psk}\"\n key_mgmt=WPA-PSK\n}" > "/Volumes/boot/wpa_supplicant.conf" 219 | fi 220 | 221 | # Copy setup script for the user to run 222 | echo "Copying setup script to /boot... " 223 | src="$GIT/scripts/pinstall.sh" 224 | if [ "${pi_type}" = "Zero" ]; then 225 | src="$GIT/scripts/zinstall.sh" 226 | fi 227 | cp "${src}" /Volumes/boot 228 | 229 | # Fix mouse slowness 230 | in_file=$(cat /Volumes/boot/cmdline.txt) 231 | in_file=${in_file:0:-1} 232 | echo "${in_file} usbhid.mousepoll=8" >> /Volumes/boot/cmdline.txt 233 | 234 | # All done... 235 | echo "Cleaning up... " 236 | sudo diskutil unmountdisk /Volumes/boot 237 | cd .. 238 | rm -r tmp 239 | ok=1 240 | fi 241 | done 242 | 243 | # Finished 244 | echo "${bold}${green}Done${reset}" 245 | -------------------------------------------------------------------------------- /pigitup.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/zsh 2 | 3 | # 4 | # Update Git libs 5 | # 6 | # Wraps Python virtual envronment stuff 7 | # as mandated by Debian Bookworm 8 | # see https://www.raspberrypi.com/documentation/computers/os.html#python-on-raspberry-pi 9 | # 10 | # @author Tony Smith (@smittytone) 11 | # @copyight 2023, Tony Smith 12 | # @version 1.0.0 13 | # 14 | # This script ssumes you have set up a user-level virtual environment 15 | # using the following commands: 16 | # 17 | # ``` 18 | # python -m venv ~/.env` 19 | # source ~/.env/bin/activate 20 | # pip install gitup 21 | # deactivate 22 | # ``` 23 | # 24 | 25 | source ~/.env/bin/activate 26 | gitup $GIT 27 | deactivate 28 | -------------------------------------------------------------------------------- /pinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Pi Installation Script 1.3.0 3 | 4 | # Switch to home directory 5 | cd "$HOME" || exit 1 6 | 7 | # Remove stock folders 8 | echo "Removing home sub-directories..." 9 | rm -rf Pictures 10 | rm -rf Music 11 | rm -rf Videos 12 | rm -rf python_games 13 | rm -rf Templates 14 | rm -rf MagPi 15 | rm -rf Bookshelf 16 | 17 | # Update the apt-get database, etc. 18 | echo "Updating system..." 19 | sudo apt-get update 20 | sudo apt-get -y dist-upgrade 21 | sudo apt-get -y autoremove 22 | 23 | # Make directories 24 | echo "Creating directories..." 25 | mkdir "$HOME/GitHub" 26 | 27 | # Update .bashrc 28 | echo "Configuring command line... " 29 | echo $'export PS1=\'$PWD > \'' >> .bashrc 30 | export PATH=$PATH:/usr/local/bin 31 | 32 | # Applications 33 | echo -n "Installing utilities: screen..." 34 | sudo apt-get -q -y install screen 35 | echo -n " scrot..." 36 | sudo apt-get -q -y install scrot 37 | echo -n " zsh..." 38 | sudo apt-get -q -y install zsh 39 | echo -n " pylint" 40 | sudo pip3 -q install pylint 41 | 42 | # Node 43 | # version="12.4.0" 44 | # echo -e "\nInstalling Node $version..." 45 | # mkdir tmp 46 | # cd tmp || exit 1 47 | # wget "https://nodejs.org/dist/v$version/node-v$version-linux-arm64.tar.gz" 48 | # tar -xzf "node-v$version-linux-arm64.tar.gz" 49 | # cd "node-v$version-linux-arm64" 50 | # sudo cp -R * /usr/local/ 51 | 52 | # Git 53 | if cd "$HOME/GitHub"; then 54 | echo "Cloning key repos..." 55 | git clone https://github.com/smittytone/dotfiles.git 56 | git clone https://github.com/smittytone/scripts.git 57 | 58 | # Setup configs 59 | cp dotfiles/Pi/bash_aliases "$HOME"/.bash_aliases 60 | cp dotfiles/Pi/nanorc "$HOME"/.nanorc 61 | cp dotfiles/Universal/pylintrc "$HOME"/.pylintrc 62 | cp dotfiles/Universal/gitignore_global "$HOME"/.gitignore_global 63 | git config --global core.excludesfile "$HOME"/.gitignore_global 64 | 65 | # From 1.1.0 66 | # Setup and enable VNC service 67 | sudo cp dotfiles/Pi/pi_virtual_desktop.service /etc/systemd/system/vnc_vd.service 68 | sudo systemctl enable vnc_vd.service 69 | fi 70 | 71 | # FROM 1.3.0 set Z as default shell 72 | z_path=$(which zsh) 73 | if [[ -f "${z_path}" ]]; then 74 | chsh -s "${z_path}" 75 | fi 76 | 77 | # Remove the script 78 | if cd "$HOME"; then 79 | echo "Cleaning up..." 80 | rm pinstall.sh 81 | rm -rf tmp 82 | fi 83 | 84 | read -n 1 -s -p "Press [S] to shutdown, [R] to reboot or any other key to cancel " key 85 | key=${key^^*} 86 | if [ "$key" = "S" ]; then 87 | sudo shutdown -h now 88 | elif [ "$key" = "R" ]; then 89 | sudo shutdown -r now 90 | else 91 | echo 92 | fi 93 | -------------------------------------------------------------------------------- /pireadonly.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CREDIT TO THESE TUTORIALS: 4 | # petr.io/en/blog/2015/11/09/read-only-raspberry-pi-with-jessie 5 | # hallard.me/raspberry-pi-read-only 6 | # k3a.me/how-to-make-raspberrypi-truly-read-only-reliable-and-trouble-free 7 | # https://learn.adafruit.com/read-only-raspberry-pi 8 | 9 | 10 | if [ $(id -u) -ne 0 ]; then 11 | echo "Installer must be run as root." 12 | echo "Try 'sudo bash $0'" 13 | exit 1 14 | fi 15 | 16 | clear 17 | 18 | echo "This script configures a Raspberry Pi" 19 | echo "SD card to boot into read-only mode," 20 | echo "obviating need for clean shutdown." 21 | echo "NO FILES ON THE CARD CAN BE CHANGED" 22 | echo "WHEN PI IS BOOTED IN THIS STATE. Either" 23 | echo "the filesystems must be remounted in" 24 | echo "read/write mode, card must be mounted" 25 | echo "R/W on another system, or an optional" 26 | echo "jumper can be used to enable read/write" 27 | echo "on boot." 28 | echo 29 | echo "Links to original tutorials are in" 30 | echo "script source. THIS IS A ONE-WAY" 31 | echo "OPERATION. THERE IS NO SCRIPT TO" 32 | echo "REVERSE THIS SETUP! ALL other system" 33 | echo "config should be complete before using" 34 | echo "this script. MAKE A BACKUP FIRST." 35 | echo 36 | echo "Run time ~5 minutes. Reboot required." 37 | echo 38 | echo -n "CONTINUE? [y/N] " 39 | read 40 | if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then 41 | echo "Canceled." 42 | exit 0 43 | fi 44 | 45 | # FEATURE PROMPTS ---------------------------------------------------------- 46 | # Installation doesn't begin until after all user input is taken. 47 | 48 | INSTALL_RW_JUMPER=0 49 | INSTALL_HALT=0 50 | INSTALL_WATCHDOG=0 51 | 52 | # Given a list of strings representing options, display each option 53 | # preceded by a number (1 to N), display a prompt, check input until 54 | # a valid number within the selection range is entered. 55 | selectN() { 56 | for ((i=1; i<=$#; i++)); do 57 | echo $i. ${!i} 58 | done 59 | echo 60 | REPLY="" 61 | while : 62 | do 63 | echo -n "SELECT 1-$#: " 64 | read 65 | if [[ $REPLY -ge 1 ]] && [[ $REPLY -le $# ]]; then 66 | return $REPLY 67 | fi 68 | done 69 | } 70 | 71 | SYS_TYPES=(Pi\ 3\ /\ Pi\ Zero\ W All\ other\ models) 72 | WATCHDOG_MODULES=(bcm2835_wdog bcm2708_wdog) 73 | OPTION_NAMES=(NO YES) 74 | 75 | echo -n "Enable boot-time read/write jumper? [y/N] " 76 | read 77 | if [[ "$REPLY" =~ (yes|y|Y)$ ]]; then 78 | INSTALL_RW_JUMPER=1 79 | echo -n "GPIO pin for R/W jumper: " 80 | read 81 | RW_PIN=$REPLY 82 | fi 83 | 84 | echo -n "Install GPIO-halt utility? [y/N] " 85 | read 86 | if [[ "$REPLY" =~ (yes|y|Y)$ ]]; then 87 | INSTALL_HALT=1 88 | echo -n "GPIO pin for halt button: " 89 | read 90 | HALT_PIN=$REPLY 91 | fi 92 | 93 | echo -n "Enable kernel panic watchdog? [y/N] " 94 | read 95 | if [[ "$REPLY" =~ (yes|y|Y)$ ]]; then 96 | INSTALL_WATCHDOG=1 97 | echo "Target system type:" 98 | selectN "${SYS_TYPES[0]}" \ 99 | "${SYS_TYPES[1]}" 100 | WD_TARGET=$? 101 | fi 102 | 103 | # VERIFY SELECTIONS BEFORE CONTINUING -------------------------------------- 104 | 105 | echo 106 | if [ $INSTALL_RW_JUMPER -eq 1 ]; then 107 | echo "Boot-time R/W jumper: YES (GPIO$RW_PIN)" 108 | else 109 | echo "Boot-time R/W jumper: NO" 110 | fi 111 | if [ $INSTALL_HALT -eq 1 ]; then 112 | echo "Install GPIO-halt: YES (GPIO$HALT_PIN)" 113 | else 114 | echo "Install GPIO-halt: NO" 115 | fi 116 | if [ $INSTALL_WATCHDOG -eq 1 ]; then 117 | echo "Enable watchdog: YES (${SYS_TYPES[WD_TARGET-1]})" 118 | else 119 | echo "Enable watchdog: NO" 120 | fi 121 | echo 122 | echo -n "CONTINUE? [y/N] " 123 | read 124 | if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then 125 | echo "Canceled." 126 | exit 0 127 | fi 128 | 129 | # START INSTALL ------------------------------------------------------------ 130 | # All selections have been validated at this point... 131 | 132 | # Given a filename, a regex pattern to match and a replacement string: 133 | # Replace string if found, else no change. 134 | # (# $1 = filename, $2 = pattern to match, $3 = replacement) 135 | replace() { 136 | grep $2 $1 >/dev/null 137 | if [ $? -eq 0 ]; then 138 | # Pattern found; replace in file 139 | sed -i "s/$2/$3/g" $1 >/dev/null 140 | fi 141 | } 142 | 143 | # Given a filename, a regex pattern to match and a replacement string: 144 | # If found, perform replacement, else append file w/replacement on new line. 145 | replaceAppend() { 146 | grep $2 $1 >/dev/null 147 | if [ $? -eq 0 ]; then 148 | # Pattern found; replace in file 149 | sed -i "s/$2/$3/g" $1 >/dev/null 150 | else 151 | # Not found; append on new line (silently) 152 | echo $3 | sudo tee -a $1 >/dev/null 153 | fi 154 | } 155 | 156 | # Given a filename, a regex pattern to match and a string: 157 | # If found, no change, else append file with string on new line. 158 | append1() { 159 | grep $2 $1 >/dev/null 160 | if [ $? -ne 0 ]; then 161 | # Not found; append on new line (silently) 162 | echo $3 | sudo tee -a $1 >/dev/null 163 | fi 164 | } 165 | 166 | # Given a filename, a regex pattern to match and a string: 167 | # If found, no change, else append space + string to last line -- 168 | # this is used for the single-line /boot/cmdline.txt file. 169 | append2() { 170 | grep $2 $1 >/dev/null 171 | if [ $? -ne 0 ]; then 172 | # Not found; insert in file before EOF 173 | sed -i "s/\'/ $3/g" $1 >/dev/null 174 | fi 175 | } 176 | 177 | echo 178 | echo "Starting installation..." 179 | echo "Updating package index files..." 180 | apt-get update 181 | 182 | echo "Removing unwanted packages..." 183 | #apt-get remove -y --force-yes --purge triggerhappy logrotate dbus \ 184 | # dphys-swapfile xserver-common lightdm fake-hwclock 185 | # Let's keep dbus...that includes avahi-daemon, a la 'raspberrypi.local', 186 | # also keeping xserver & lightdm for GUI login (WIP, not working yet) 187 | apt-get remove -y --force-yes --purge triggerhappy logrotate \ 188 | dphys-swapfile fake-hwclock 189 | apt-get -y --force-yes autoremove --purge 190 | 191 | # Replace log management with busybox (use logread if needed) 192 | echo "Installing ntp and busybox-syslogd..." 193 | apt-get -y --force-yes install ntp busybox-syslogd; dpkg --purge rsyslog 194 | 195 | echo "Configuring system..." 196 | 197 | # Install boot-time R/W jumper test if requested 198 | GPIOTEST="gpio -g mode $RW_PIN up\n\ 199 | if [ \`gpio -g read $RW_PIN\` -eq 0 ] ; then\n\ 200 | \tmount -o remount,rw \/\n\ 201 | \tmount -o remount,rw \/boot\n\ 202 | fi\n" 203 | if [ $INSTALL_RW_JUMPER -ne 0 ]; then 204 | apt-get install -y --force-yes wiringpi 205 | # Check if already present in rc.local: 206 | grep "gpio -g read" /etc/rc.local >/dev/null 207 | if [ $? -eq 0 ]; then 208 | # Already there, but make sure pin is correct: 209 | sed -i "s/^.*gpio\ -g\ read.*$/$GPIOTEST/g" /etc/rc.local >/dev/null 210 | 211 | else 212 | # Not there, insert before final 'exit 0' 213 | sed -i "s/^exit 0/$GPIOTEST\\nexit 0/g" /etc/rc.local >/dev/null 214 | fi 215 | fi 216 | 217 | # Install watchdog if requested 218 | if [ $INSTALL_WATCHDOG -ne 0 ]; then 219 | apt-get install -y --force-yes watchdog 220 | # $MODULE is specific watchdog module name 221 | MODULE=${WATCHDOG_MODULES[($WD_TARGET-1)]} 222 | # Add to /etc/modules, update watchdog config file 223 | append1 /etc/modules $MODULE $MODULE 224 | replace /etc/watchdog.conf "#watchdog-device" "watchdog-device" 225 | replace /etc/watchdog.conf "#max-load-1" "max-load-1" 226 | # Start watchdog at system start and start right away 227 | # Raspbian Stretch needs this package installed first 228 | apt-get install -y --force-yes insserv 229 | insserv watchdog; /etc/init.d/watchdog start 230 | # Additional settings needed on Jessie 231 | append1 /lib/systemd/system/watchdog.service "WantedBy" "WantedBy=multi-user.target" 232 | systemctl enable watchdog 233 | # Set up automatic reboot in sysctl.conf 234 | replaceAppend /etc/sysctl.conf "^.*kernel.panic.*$" "kernel.panic = 10" 235 | fi 236 | 237 | # Install gpio-halt if requested 238 | if [ $INSTALL_HALT -ne 0 ]; then 239 | apt-get install -y --force-yes wiringpi 240 | echo "Installing gpio-halt in /usr/local/bin..." 241 | cd /tmp 242 | curl -LO https://github.com/adafruit/Adafruit-GPIO-Halt/archive/master.zip 243 | unzip master.zip 244 | cd Adafruit-GPIO-Halt-master 245 | make 246 | mv gpio-halt /usr/local/bin 247 | cd .. 248 | rm -rf Adafruit-GPIO-Halt-master 249 | 250 | # Add gpio-halt to /rc.local: 251 | grep gpio-halt /etc/rc.local >/dev/null 252 | if [ $? -eq 0 ]; then 253 | # gpio-halt already in rc.local, but make sure correct: 254 | sed -i "s/^.*gpio-halt.*$/\/usr\/local\/bin\/gpio-halt $HALT_PIN \&/g" /etc/rc.local >/dev/null 255 | else 256 | # Insert gpio-halt into rc.local before final 'exit 0' 257 | sed -i "s/^exit 0/\/usr\/local\/bin\/gpio-halt $HALT_PIN \&\\nexit 0/g" /etc/rc.local >/dev/null 258 | fi 259 | fi 260 | 261 | # Add fastboot, noswap and/or ro to end of /boot/cmdline.txt 262 | append2 /boot/cmdline.txt fastboot fastboot 263 | append2 /boot/cmdline.txt noswap noswap 264 | append2 /boot/cmdline.txt ro^o^t ro 265 | 266 | # Move /var/spool to /tmp 267 | rm -rf /var/spool 268 | ln -s /tmp /var/spool 269 | 270 | # Move /var/lib/lightdm and /var/cache/lightdm to /tmp 271 | rm -rf /var/lib/lightdm 272 | rm -rf /var/cache/lightdm 273 | ln -s /tmp /var/lib/lightdm 274 | ln -s /tmp /var/cache/lightdm 275 | 276 | # Make SSH work 277 | replaceAppend /etc/ssh/sshd_config "^.*UsePrivilegeSeparation.*$" "UsePrivilegeSeparation no" 278 | # bbro method (not working in Jessie?): 279 | #rmdir /var/run/sshd 280 | #ln -s /tmp /var/run/sshd 281 | 282 | # Change spool permissions in var.conf (rondie/Margaret fix) 283 | replace /usr/lib/tmpfiles.d/var.conf "spool\s*0755" "spool 1777" 284 | 285 | # Move dhcpd.resolv.conf to tmpfs 286 | touch /tmp/dhcpcd.resolv.conf 287 | rm /etc/resolv.conf 288 | ln -s /tmp/dhcpcd.resolv.conf /etc/resolv.conf 289 | 290 | # Make edits to fstab 291 | # make / ro 292 | # tmpfs /var/log tmpfs nodev,nosuid 0 0 293 | # tmpfs /var/tmp tmpfs nodev,nosuid 0 0 294 | # tmpfs /tmp tmpfs nodev,nosuid 0 0 295 | replace /etc/fstab "vfat\s*defaults\s" "vfat defaults,ro " 296 | replace /etc/fstab "ext4\s*defaults,noatime\s" "ext4 defaults,noatime,ro " 297 | append1 /etc/fstab "/var/log" "tmpfs /var/log tmpfs nodev,nosuid 0 0" 298 | append1 /etc/fstab "/var/tmp" "tmpfs /var/tmp tmpfs nodev,nosuid 0 0" 299 | append1 /etc/fstab "\s/tmp" "tmpfs /tmp tmpfs nodev,nosuid 0 0" 300 | 301 | # PROMPT FOR REBOOT -------------------------------------------------------- 302 | 303 | echo "Done." 304 | echo 305 | echo "Settings take effect on next boot." 306 | echo 307 | echo -n "REBOOT NOW? [y/N] " 308 | read 309 | if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then 310 | echo "Exiting without reboot." 311 | exit 0 312 | fi 313 | echo "Reboot started..." 314 | reboot 315 | exit 0 316 | 317 | -------------------------------------------------------------------------------- /scripts.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /setimagedpi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for file in *; do 4 | if [ -f "$file" ]; then 5 | # Make sure the file's of the right type 6 | filename="${file##*/}" 7 | extension=${file##*.} 8 | extension=${extension,,} 9 | 10 | if [[ "$extension" = "jpg" || "$extension" = "jpeg" || "$extension" = "png" ]]; then 11 | h=$(sips $file -g dpiHeight) 12 | s=$(grep '\s*dpiHeight: ' < <(echo -e "$h")) 13 | h=$(echo "$s" | cut -d: -f2) 14 | 15 | w=$(sips $file -g dpiWidth) 16 | s=$(grep '\s*dpiWidth: ' < <(echo -e "$w")) 17 | w=$(echo "$s" | cut -d: -f2) 18 | 19 | echo -n "Processing $file... was $h x $w dpi... " 20 | sips $file -s dpiHeight 300 -s dpiWidth 300 --out "200-$filename" &> /dev/null 21 | 22 | h=$(sips "200-$filename" -g dpiHeight) 23 | s=$(grep '\s*dpiHeight: ' < <(echo -e "$h")) 24 | h=$(echo "$s" | cut -d: -f2) 25 | 26 | w=$(sips "200-$filename" -g dpiWidth) 27 | s=$(grep '\s*dpiWidth: ' < <(echo -e "$w")) 28 | w=$(echo "$s" | cut -d: -f2) 29 | echo "is $h x $w dpi" 30 | fi 31 | fi 32 | done 33 | -------------------------------------------------------------------------------- /setup.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # setupmac.zsh 5 | # 6 | # Mac install script 7 | # 8 | # @author Tony Smith 9 | # @copyright 2024, Tony Smith 10 | # @version 4.0.0 11 | # @license MIT 12 | # 13 | 14 | 15 | function show_errors() { 16 | if [[ ${#errors[@]} -ne 0 ]]; then 17 | echo "Errors encountered during setup:" 18 | count=1 19 | for error in "${errors[@]}"; do 20 | echo " ${count}. ${error}" 21 | (( count+=1 )) 22 | done 23 | exit 1 24 | fi 25 | } 26 | 27 | 28 | APP_NAME=$(basename $0) 29 | APP_NAME=${APP_NAME:t} 30 | APP_VERSION=4.0.0 31 | errors=() 32 | 33 | # FROM 3.0.0 -- Get macOS verison 34 | VERSION=$(system_profiler SPSoftwareDataType | grep macOS | awk {'print $4'}) 35 | VERSION_MAJOR=$(echo $VERSION | cut -d. -f1) 36 | VERSION_MINOR=$(echo $VERSION | cut -d. -f2) 37 | VERSION_PATCH=$(echo $VERSION | cut -d. -f3) 38 | 39 | # FROM 3.0.0 -- Get the system architecture 40 | ARCH=$(uname -a | awk -F" " '{print $NF}') 41 | 42 | # Do intro 43 | clear 44 | echo "macOS Install Script ${APP_VERSION} on ${ARCH}" 45 | 46 | read -k -s "key?Continue? [Y/N] " 47 | [[ "${key:u}" != "Y" ]] && echo && exit 1 48 | echo 49 | 50 | : ' 51 | # Apply preferred Energy Saver settings 52 | # NOTE Must be run as root 53 | sudo pmset -b displaysleep 60 54 | sudo pmset -a disksleep 60 55 | sudo pmset -b sleep 60 56 | sudo pmset -a womp 0 57 | sudo pmset -b powernap 1 58 | 59 | # Ask for and set the machines machine name 60 | # NOTE Must be run as root 61 | read "hostname?Enter your preferred hostname " 62 | if [[ -n "${hostname}" ]]; then 63 | echo "Setting machine name to ${hostname}" 64 | sudo scutil --set HostName "${hostname}" 65 | sudo scutil --set LocalHostName "${hostname}" 66 | sudo scutil --set ComputerName "${hostname}" 67 | sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName -string "$hostname" 68 | dscacheutil -flushcache 69 | fi 70 | 71 | # Set dark mode 72 | osascript -e 'tell application "System Events" to tell appearance preferences to set dark mode to true' 73 | 74 | # Clean up Home folder items 75 | folders=(Movies Public) 76 | echo -n "Hiding Home folder items: " 77 | for folder in "${folders[@]}"; do 78 | chflags hidden "$HOME/${folder}" 79 | echo -n "${folder} " 80 | done 81 | echo 82 | 83 | echo "Showing the Library folder..." 84 | chflags nohidden "$HOME/Library" 85 | 86 | # Homebrew 87 | if [[ -n $(which brew) ]]; then 88 | echo "Installing Brew-sourced CLI tools... " 89 | brew update 90 | apps=(bash nano coreutils gitup jq ncurses readline shellcheck libdvdcss python3 hugo "sass/sass/sass" sphinx-doc "cloudflare/cloudflare/cloudflared") 91 | for app in "${apps[@]}"; do 92 | brew install "${app}" 93 | done 94 | 95 | echo "Installing Applications... " 96 | apps=(handbrake skype firefox omnidisksweeper) 97 | for app in "${apps[@]}"; do 98 | brew install --cask "${app}" 99 | done 100 | 101 | echo "Installing My Applications... " 102 | brew tap smittytone/homebrew-smittytone 103 | apps=(ascii mnu imageprep pdfmaker squinter the-valley utitool) 104 | for app in "${apps[@]}"; do 105 | brew install --cask "${app}" 106 | done 107 | else 108 | errors+="Homebrew not installed" 109 | show_errors 110 | fi 111 | 112 | brew cleanup 113 | 114 | # Node 115 | echo "Installing node via nvm... " 116 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash 117 | #nvm install 20 118 | 119 | # Cloudflared 120 | echo "Installing cloudflared... " 121 | target=/usr/local/etc/cloudflared 122 | [[ ! -e "${target}" ]] && mkdir "${target}" 123 | cat < /usr/local/etc/cloudflared/config.yaml 124 | proxy-dns: true 125 | proxy-dns-upstream: 126 | - https://1.1.1.1/dns-query 127 | - https://8.8.8.8/dns-query 128 | EOF 129 | echo "To start cloudflare, run: sudo cloudflared service install" 130 | 131 | # Set up git and clone key repos 132 | echo "Preparing Git..." 133 | target="$HOME/GitHub" 134 | [[ ! -e "${target}" ]] && mkdir "${target}" 135 | cd "${target}" || show_errors 136 | 137 | if [[ -n $(which git) ]]; then 138 | repos=(scripts dotfiles devscripts) 139 | for repo in "${repos[@]}"; do 140 | [[ ! -e "${repo}" ]] && git clone "https://github.com/smittytone/${repo}.git" 141 | done 142 | fi 143 | 144 | # Run the app settings script 145 | # FROM 3.0.0 -- a new script 146 | # NOTE Script depends on dotfiles repo 147 | target="$HOME/GitHub/scripts/configmac.zsh" 148 | [[ -e "${target}" ]] && source "${target}" 149 | ' 150 | 151 | # All done 152 | read -k -s "key?Press [ENTER] to finish " 153 | echo 154 | 155 | # FROM 2.2.0 156 | # Report any issues encountered 157 | show_errors 158 | 159 | # Successful completion 160 | echo "Done" 161 | exit 0 162 | -------------------------------------------------------------------------------- /setupmac.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # setupmac.zsh 5 | # 6 | # Mac install script 7 | # 8 | # @author Tony Smith 9 | # @copyright 2020, Tony Smith 10 | # @version 3.0.1 11 | # @license MIT 12 | # 13 | 14 | 15 | function show_errors() { 16 | if [[ ${#errors[@]} -eq 0 ]]; then 17 | echo "Errors encountered during setup:" 18 | count=1 19 | for error in "${errors[@]}"; do 20 | echo " ${count}. ${error}" 21 | (( count+=1 )) 22 | done 23 | exit 1 24 | fi 25 | } 26 | 27 | 28 | APP_NAME=$(basename $0) 29 | APP_NAME=${APP_NAME:t} 30 | APP_VERSION="3.0.1" 31 | errors=() 32 | 33 | # Do intro 34 | clear 35 | echo "macOS Install Script $APP_VERSION" 36 | 37 | # REMOVED FROM 2.3.0 -- Update macOS 38 | # sudo softwareupdate --install --all 39 | 40 | # FROM 3.0.0 -- Get macOS verison 41 | VERSION=$(system_profiler SPSoftwareDataType | grep macOS | awk {'print $4'}) 42 | VERSION_MAJOR=$(echo $VERSION | cut -d. -f1) 43 | VERSION_MINOR=$(echo $VERSION | cut -d. -f2) 44 | VERSION_PATCH=$(echo $VERSION | cut -d. -f3) 45 | 46 | # FROM 3.0.0 -- Get the system architecture 47 | ARCH=$(uname -a | awk -F" " '{print $NF}') 48 | 49 | # Apply preferred Energy Saver settings 50 | # NOTE Must be run as root 51 | sudo pmset -a lessbright 0 52 | sudo pmset -a disksleep 10 53 | sudo pmset -a womp 0 54 | sudo pmset -b displaysleep 15 55 | sudo pmset -b sleep 15 56 | sudo pmset -b powernap 0 57 | sudo pmset -c displaysleep 60 58 | sudo pmset -c sleep 60 59 | sudo pmset -c powernap 1 60 | 61 | # Ask for and set the machine's machine name 62 | # NOTE Must be run as root 63 | read "hostname?Enter your preferred hostname " 64 | if [[ -n "$hostname" ]]; then 65 | echo -e "\nSetting machine name to $hostname" 66 | sudo scutil --set HostName "$hostname" 67 | sudo scutil --set LocalHostName "$hostname" 68 | sudo scutil --set ComputerName "$hostname" 69 | sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName -string "$hostname" 70 | dscacheutil -flushcache 71 | fi 72 | 73 | # Set dark mode 74 | osascript -e 'tell application "System Events" to tell appearance preferences to set dark mode to true' 75 | 76 | # Clean up Home folder items 77 | echo -n "Hiding Home folder items: " 78 | chflags hidden "$HOME/Movies" 79 | echo -n "Movies, " 80 | chflags hidden "$HOME/Public" 81 | echo "Public" 82 | echo "Showing the Library folder..." 83 | chflags nohidden "$HOME/Library" 84 | 85 | # Install Xcode CLI 86 | #if xcode-select --install; then 87 | # echo "Xcode CLI installed" 88 | #els 89 | # echo "Xcode CLI already installed or could not be installed" 90 | # errors+="Xcode CLI installation" 91 | #fi 92 | 93 | # Install applications... brew first 94 | # NOTE This will install Xcode CLI 95 | echo "Installing Brew... " 96 | if /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"; then 97 | echo "Installing Brew-sourced Utilities... " 98 | apps=(bash nano coreutils gitup jq ncurses readline shellcheck libdvdcss node python3 hugo "sass/sass/sass" sphinx-doc) 99 | for app in "${apps[@]}"; do 100 | brew install "${app}" 101 | done 102 | 103 | echo "Installing Applications... " 104 | apps=(handbrake skype firefox omnidisksweeper) 105 | for app in "${apps[@]}"; do 106 | brew install --cask "${app}" 107 | done 108 | 109 | # FROM 2.2.0 110 | echo "Installing My Applications... " 111 | brew tap smittytone/homebrew-smittytone 112 | apps=(ascii mnu imageprep pdfmaker squinter the-valley utitool) 113 | for app in "${apps[@]}"; do 114 | brew install --cask "${app}" 115 | done 116 | 117 | # FROM 3.0.0 118 | brew link --force sphinx-doc 119 | else 120 | echo "Could not install Brew" 121 | errors+="Brew installation" 122 | show_errors 123 | fi 124 | 125 | # Set up git and clone key repos 126 | echo "Preparing Git..." 127 | target="$HOME/GitHub" 128 | [[ ! -e "${target}" ]] && mkdir "${target}" 129 | 130 | cd "${target}" || show_errors 131 | [[ ! -e scripts ]] && git clone https://github.com/smittytone/scripts.git 132 | [[ ! -e dotfiles ]] && git clone https://github.com/smittytone/dotfiles.git 133 | 134 | # Run the app settings script 135 | # FROM 3.0.0 -- a new script 136 | # NOTE Script depends on dotfiles repo 137 | scripts/configmac.zsh 138 | 139 | #echo "Installing Cocoapods (requires authorizaton)... " 140 | #sudo gem install cocoapods 141 | 142 | #echo "Installing Python modules... " 143 | #pip3 install pylint sphinx-rtd-theme 144 | 145 | # FROM 2.1.0 146 | # Install node packages 147 | which npm >> /dev/null 148 | if [[ $? -eq 0 ]]; then 149 | echo "Installing Node packages... " 150 | npm install -g uglify-js 151 | npm install -g uglifycss 152 | fi 153 | 154 | # All done 155 | read -k -s "key?Press [ENTER] to finish " 156 | echo 157 | 158 | echo "Cleaning up... " 159 | brew cleanup 160 | 161 | # FROM 2.2.0 162 | # Report any issues encountered 163 | show_errors 164 | 165 | echo "Done" 166 | exit 0 167 | -------------------------------------------------------------------------------- /todisk.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # todisk.zsh 5 | # 6 | # Backup to Disk Script 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-21, Tony Smith 10 | # @version 3.4.0 11 | # @license MIT 12 | # 13 | 14 | APP_NAME=$(basename $0) 15 | APP_NAME=${APP_NAME:t} 16 | APP_VERSION="3.4.0" 17 | 18 | typeset -i do_music=1 19 | typeset -i do_books=1 20 | target_vol=2TB-APFS 21 | source_dir="$HOME" 22 | d_sources=("Comics" "Library/Mobile Documents/com~apple~CloudDocs/Documents/eBooks") 23 | m_sources=("/Music/Alternative" "/Music/Classical" "/Music/Comedy" "/Music/Doctor Who" 24 | "/Music/Electronic" "/Music/Folk" "/Music/Pop" "/Music/Metal" "/Music/Rock" 25 | "/Music/SFX" "/Music/Singles" "/Music/Soundtracks" "/Music/Spoken Word" 26 | "/Music/Instrumental") 27 | # FROM 3.2.0 28 | # Add user fonts 29 | f_sources=("Library/Fonts") 30 | 31 | # Functions 32 | do_sync() { 33 | # Sync the source to the target 34 | # Arg 1 should be the source directory 35 | # Arg 2 should be the target directory 36 | local name="${1:t}" 37 | echo -n "Syncing ${name}... " 38 | 39 | # Prepare a readout of changed files ONLY (rsync does not do this) 40 | local list=$(rsync -az "$source_dir/$1" "$2" --itemize-changes --exclude ".DS_Store") 41 | local lines=$(grep '>' < <(echo -e "$list")) 42 | 43 | # Check we have files to report 44 | if [[ -n "$lines" ]]; then 45 | # Files were sync'd so count the total number 46 | typeset -i count=0 47 | local cols=$(tput cols) 48 | while IFS= read -r line; do 49 | ((count++)) 50 | done <<< "$lines" 51 | echo "$count files changed:" 52 | # Output the files changed 53 | while IFS= read -r line; do 54 | local trimline=$(echo "$line" | cut -c 11-) 55 | if [[ -n "$trimline" ]]; then 56 | echo " /$trimline" 57 | fi 58 | done <<< "$lines" 59 | else 60 | echo "no files changed" 61 | fi 62 | } 63 | 64 | show_help() { 65 | echo -e "todisk $APP_VERSION\n" 66 | echo -e "Usage:\n" 67 | echo -e " todisk [-m] [-b] []\n" 68 | echo -e "Options:\n" 69 | echo " -m / --music Backup music only. Default: backup both" 70 | echo " -b / --books Backup eBooks only. Default: backup both" 71 | echo " Optional drive name. Default: 2TB-APFS" 72 | echo 73 | } 74 | 75 | show_error() { 76 | echo "${APP_NAME} error: $1" 1>&2 77 | } 78 | 79 | 80 | # Runtime start 81 | # Process the arguments 82 | typeset -i arg_count=0 83 | for arg in "$@"; do 84 | # Temporarily convert argument to lowercase, zsh-style 85 | check_arg=${arg:l} 86 | if [[ "$check_arg" = "--books" || "$check_arg" = "-b" ]]; then 87 | do_music=0 88 | ((arg_count += 1)) 89 | elif [[ "$check_arg" = "--music" || "$check_arg" = "-m" ]]; then 90 | do_books=0 91 | ((arg_count += 1)) 92 | elif [[ "$check_arg" = "--all" || "$check_arg" = "-a" ]]; then 93 | # Dummy arg to avoid presenting request to add disk 94 | ((arg_count += 1)) 95 | elif [[ "$check_arg" = "--help" || "$check_arg" = "-h" ]]; then 96 | show_help 97 | exit 0 98 | else 99 | target_vol="$arg" 100 | fi 101 | done 102 | 103 | # Set the target path based on supplied disk name (or default) 104 | target_path="/Volumes/$target_vol" 105 | 106 | # Check that the user is not exluding both jobs 107 | if [[ $do_books -eq 0 && $do_music -eq 0 ]]; then 108 | show_error "Mutually exclusive options set -- backup cannot continue" 109 | exit 1 110 | fi 111 | 112 | # If no options were specified, assume we're running interactively 113 | # and invite the user to continue at their own pace 114 | if [[ $arg_count -eq 0 ]]; then 115 | clear 116 | echo "Backup to Disk" 117 | # Get input, zsh style 118 | read -k -s "choice?Connect '$target_vol' then press [ENTER] when it has mounted " 119 | echo 120 | fi 121 | 122 | # Make sure the target disk is mounted 123 | if [[ -d "$target_path" ]]; then 124 | echo "Disk '$target_vol' mounted." 125 | 126 | # Sync document sources 127 | if [[ $do_books -eq 1 ]]; then 128 | for source in "${d_sources[@]}"; do 129 | do_sync "$source" "$target_path" 130 | done 131 | 132 | # FROM 3.2.0 133 | # Add user fonts 134 | for source in "${f_sources[@]}"; do 135 | do_sync "$source" "$target_path" 136 | done 137 | fi 138 | 139 | # Sync music sources 140 | if [[ $do_music -eq 1 ]]; then 141 | for source in "${m_sources[@]}"; do 142 | do_sync "$source" "$target_path/Music" 143 | done 144 | fi 145 | else 146 | echo "Disk '$target_vol' is not mounted." 147 | fi 148 | -------------------------------------------------------------------------------- /toserver.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # toserver.zsh 5 | # 6 | # Backup to Server Script 7 | # 8 | # @author Tony Smith 9 | # @copyright 2019-22, Tony Smith 10 | # @version 5.5.4 11 | # @license MIT 12 | # 13 | 14 | APP_NAME=$(basename $0) 15 | APP_NAME=${APP_NAME:t} 16 | APP_VERSION="5.5.4" 17 | 18 | typeset -i do_music=1 19 | typeset -i do_books=1 20 | typeset -i do_docus=1 21 | typeset -i count=0 22 | typeset -i music_mounted=0 23 | typeset -i home_mounted=0 24 | server_auth="$HOME/.config/sync/bookmarks" 25 | target_vol="NONE" 26 | d_sources=("/Comics" "/Library/Mobile Documents/com~apple~CloudDocs/Documents/eBooks") 27 | m_sources=("/Music/Alternative" "/Music/Classical" "/Music/Comedy" "/Music/Doctor Who" 28 | "/Music/Electronic" "/Music/Folk" "/Music/Pop" "/Music/Metal" "/Music/Rock" 29 | "/Music/SFX" "/Music/Singles" "/Music/Soundtracks" "/Music/Spoken Word" 30 | "/Music/Instrumental") 31 | 32 | # FROM 5.2.0 33 | # Add user fonts 34 | b_sources=("/Library/Fonts" "/Pictures" "/Documents" "/Library/CloudStorage/OneDrive-Personal/Programming") 35 | 36 | # From 4.0.0 37 | # Functions 38 | do_sync() { 39 | # Sync the source to the target 40 | # Arg 1 should be the source directory 41 | # Arg 2 should be the target directory 42 | name=${1:t} 43 | echo -n " ${name}... " 44 | 45 | # Prepare a readout of changed files ONLY (rsync does not do this) 46 | list=$(rsync -az "$HOME/$1" "$2" --itemize-changes --exclude ".*" --exclude "*.photoslibrary") 47 | lines=$(grep '>' < <(echo -e "$list")) 48 | 49 | # Check we have files to report 50 | if [[ -n "$lines" ]]; then 51 | # Files were sync'd so count the total number 52 | count=0 53 | while IFS= read -r line; do 54 | ((count += 1)) 55 | done <<< "$lines" 56 | echo "$count files changed:" 57 | # Output the files changed 58 | while IFS= read -r line; do 59 | trimline=$(echo "$line" | cut -c 11-) 60 | if [ -n "$trimline" ]; then 61 | echo " /$trimline" 62 | fi 63 | done <<< "$lines" 64 | else 65 | echo "no files changed" 66 | fi 67 | } 68 | 69 | # FROM 5.0.2 70 | show_error() { 71 | echo "${APP_NAME} error: $1" 72 | } 73 | 74 | # FROM 5.1.0 75 | # FROM 5.4.0 - add general docs backup 76 | show_help() { 77 | echo -e "toserver $APP_VERSION\n" 78 | echo -e "Usage:\n" 79 | echo -e " toserver [-m] [-b] [-d] []\n" 80 | echo -e "Options:\n" 81 | echo " -m / --music Backup music only. Default: backup all" 82 | echo " -b / --books Backup eBooks only. Default: backup all" 83 | echo " -d / --docs Backup docs only. Default: backup all" 84 | echo " Server server name, eg. '192.168.0.1' or 'server.local'" 85 | echo 86 | } 87 | 88 | # Runtime start 89 | # Process the arguments 90 | typeset -i arg_count=0 91 | for arg in "$@"; do 92 | # Temporarily convert argument to lowercase, zsh-style 93 | check_arg=${arg:l} 94 | if [[ "$check_arg" = "--books" || "$check_arg" = "-b" ]]; then 95 | do_music=0 96 | do_docus=0 97 | ((arg_count += 1)) 98 | elif [[ "$check_arg" = "--music" || "$check_arg" = "-m" ]]; then 99 | do_books=0 100 | do_docus=0 101 | ((arg_count += 1)) 102 | elif [[ "$check_arg" = "--docs" || "$check_arg" = "-d" ]]; then 103 | do_books=0 104 | do_music=0 105 | ((arg_count += 1)) 106 | elif [[ "$check_arg" = "--help" || "$check_arg" = "-h" ]]; then 107 | show_help 108 | exit 0 109 | else 110 | target_vol="$arg" 111 | ((arg_count += 1)) 112 | fi 113 | done 114 | 115 | # Check that the user is not exluding both jobs 116 | if [[ $do_books -eq 0 && $do_music -eq 0 && $do_docus -eq 0 ]]; then 117 | show_error "Mutually exclusive options set -- backup cannot continue" 118 | exit 1 119 | fi 120 | 121 | # Check the 'bookmarks' file is present 122 | if [[ ! -f $server_auth ]]; then 123 | show_error "No server auth file found -- backup cannot continue" 124 | exit 1 125 | fi 126 | 127 | # From 3.0.0 128 | # Check for a valid server 129 | if [[ $target_vol = "NONE" ]]; then 130 | show_error "No server address supplied -- backup cannot continue" 131 | exit 1 132 | fi 133 | 134 | # If no options were specified, assume we're running interactively 135 | # and invite the user to continue at their own pace 136 | if [[ $arg_count -eq 0 ]]; then 137 | clear 138 | read -k -s "choice?Press [ENTER] to start " 139 | echo 140 | fi 141 | 142 | # Read in the server auth file lines to make sure there IS a line to read 143 | # File is plain text with a single string: : 144 | # See 'server_auth' setting above for file's location 145 | while IFS= read -r line; do 146 | # Make the mount point 147 | if [[ ! -d mntpoint ]]; then 148 | echo "Making mntpoint..." 149 | mkdir mntpoint 150 | fi 151 | 152 | # If we're doing the 'music' backup job, mount the relevant server store 153 | # and flag that it is mounted 154 | if [[ $do_music -eq 1 ]]; then 155 | if [[ ! -d mntpoint/music ]]; then 156 | echo "Making mntpoint/music..." 157 | if mkdir mntpoint/music; then 158 | echo "Mounting mntpoint/music..." 159 | if mount -t smbfs -o nobrowse "//$line@$target_vol/music" mntpoint/music; then 160 | music_mounted=1 161 | fi 162 | fi 163 | fi 164 | fi 165 | 166 | # If we're doing the 'books' backup job, mount the relevant server store 167 | # and flag that it is mounted 168 | if [[ $do_books -eq 1 || $do_docus -eq 1 ]]; then 169 | if [[ ! -d mntpoint/home ]]; then 170 | echo "Making mntpoint/home..." 171 | if mkdir mntpoint/home; then 172 | echo "Mounting mntpoint/home..." 173 | if mount -t smbfs -o nobrowse "//$line@$target_vol/home" mntpoint/home; then 174 | home_mounted=1 175 | fi 176 | fi 177 | fi 178 | fi 179 | 180 | ((count += 1)) 181 | done < $server_auth 182 | 183 | # No server auth lines read? Then bail 184 | if [[ $count -eq 0 ]]; then 185 | show_error "No bookmarks present -- backup cannot continue" 186 | exit 1 187 | fi 188 | 189 | # Run the 'books' backup job 190 | if [[ -d mntpoint/home && $home_mounted -eq 1 ]]; then 191 | if [[ $do_books -eq 1 ]]; then 192 | echo "Backing-up Comics and Books..." 193 | for source in "${d_sources[@]}"; do 194 | do_sync "$source" mntpoint/home 195 | done 196 | fi 197 | 198 | # FROM 5.4.0 199 | # Run the 'docs' backup job 200 | if [[ $do_docus -eq 1 ]]; then 201 | echo "Backing-up Documents..." 202 | for source in "${b_sources[@]}"; do 203 | do_sync "$source" mntpoint/home 204 | done 205 | fi 206 | fi 207 | 208 | # Run the 'music' backup job 209 | if [[ -d mntpoint/music && $music_mounted -eq 1 ]]; then 210 | echo "Backing-up Music..." 211 | for source in "${m_sources[@]}"; do 212 | do_sync "$source" mntpoint/music 213 | done 214 | fi 215 | 216 | # If no switches were specified, assume we're running interactively 217 | # and invite the user to continue at their own pace 218 | if [[ $arg_count -eq 0 ]]; then 219 | read -k -s "choice?Press [ENTER] to finish " 220 | echo 221 | fi 222 | 223 | # If we mounted the music store, unmount it now 224 | # Exit with an error if we can't 225 | if [[ $music_mounted -eq 1 ]]; then 226 | echo "Dismounting mntpoint/music..." 227 | if ! umount mntpoint/music; then 228 | show_error "/mntpoint/music failed to unmount -- please unmount it manually and remove the mointpoint" 229 | exit 1 230 | fi 231 | fi 232 | 233 | # If we mounted the books store, unmount it now 234 | # Exit with an error if we can't 235 | if [[ $home_mounted -eq 1 ]]; then 236 | echo "Dismounting mntpoint/home..." 237 | if ! umount mntpoint/home; then 238 | show_error "/mntpoint/home failed to unmount -- please unmount it manually and remove the mointpoint" 239 | exit 1 240 | fi 241 | fi 242 | 243 | # Make sure the unmount operations succeeded, warning if not 244 | echo "Removing mntpoint..." 245 | if rm -r mntpoint; then 246 | echo "Done" 247 | else 248 | show_error "Could not remove mntpoint -- exiting" 249 | exit 1 250 | fi 251 | -------------------------------------------------------------------------------- /updatemac.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # updatemac.zsh 5 | # 6 | # Update local Macs user config files 7 | # (eg. between multiple machines) 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-20, Tony Smith 11 | # @version 4.3.0 12 | # @license MIT 13 | # 14 | 15 | # Set up key directories 16 | file_source="$HOME/GitHub/dotfiles" 17 | file_target="$HOME/Library" 18 | 19 | if [[ ! -e "$file_source" ]]; then 20 | echo "Please clone the repo \'dotfiles\' before proceeding -- exiting " 21 | exit 1 22 | fi 23 | 24 | # Process any arguments 25 | # NOTE P == Partial, ie. only update frequently used app configs 26 | # F == Full, ie. update frequently used app configs AND apply 27 | # macOS configurations AND occasional use app configs 28 | choice="ASK" 29 | for arg in $@; do 30 | # Make the argument upper case for comparisons 31 | theArg=${arg:u} 32 | if [[ "$theArg" = "-F" || "$theArg" = "--FULL" ]]; then 33 | choice="F" 34 | elif [[ "$theArg" = "-P" || "$theArg" = "--PARTIAL" ]]; then 35 | choice="P" 36 | fi 37 | done 38 | 39 | # No valid arguments passed, so ask the user for the type of update 40 | if [[ "$choice" = "ASK" ]]; then 41 | # Get input, zsh style 42 | read -k -s "choice?Full [F] or partial [P] update? " 43 | echo 44 | if [[ $choice = $'\n' ]]; then 45 | echo "Cancelling..." 46 | exit 0 47 | fi 48 | fi 49 | 50 | choice=${choice:u} 51 | if [[ "$choice" != "F" && "$choice" != "P" ]]; then 52 | echo "Invalid option selected: '$choice' -- cancelling... " 53 | exit 1 54 | fi 55 | 56 | # The following are items that are likely to change often 57 | echo "Updating primary config files... " 58 | 59 | # bash profile 60 | # FROM 2.0.1 rename saved file 61 | cp -v "$file_source/Mac/bash_profile" "$HOME/.bash_profile" 62 | 63 | # FROM 2.0.1 64 | # ZSH rc file 65 | cp -v "$file_source/Mac/zshrc" "$HOME/.zshrc" 66 | 67 | # nano rc file 68 | cp -v "$file_source/Mac/nanorc" "$HOME/.nanorc" 69 | 70 | # vscode settings 71 | cp -v "$file_source/Mac/vs_settings.json" "$file_target/Application Support/Code/User/settings.json" 72 | 73 | # FROM 4.0.0 74 | # pylint rc file 75 | cp -v "$file_source/Universal/pylintrc" "$HOME/.pylintrc" 76 | 77 | # FROM 4.2.0 78 | # NOTE Script no longer updates Gitup bookmarks (deprecated) 79 | 80 | # git config 81 | if [[ ! -e "$HOME/.config/git" ]]; then 82 | echo "Adding ~/.config/git... " 83 | mkdir -p "$HOME/.config/git" || echo 'Could not add ~/.config/git' 84 | fi 85 | 86 | #FROM 4.1.0 87 | # binstall 88 | bin_dir=/usr/local/bin 89 | scripts_dir=$GIT/scripts 90 | if [[ ! -e $bin_dir ]]; then 91 | mkdir -p $bin_dir || echo 'Could not create ${bin_dir}' 92 | fi 93 | if [[ -e $scripts_dir/binstall.zsh ]]; then 94 | cp $scripts_dir/binstall.zsh $bin_dir/binstall 95 | chmod +x $bin_dir/binstall 96 | $bin_dir/binstall 97 | fi 98 | 99 | # git global exclude file 100 | # Added it to partial install in 1.0.3 101 | if cp -v "$file_source/Universal/gitignore_global" "$HOME/.config/git/gitignore_global"; then 102 | # Add a reference to the file to git (assumes git installed) 103 | git config --global core.excludesfile "$HOME/.config/git/gitignore_global" 104 | fi 105 | 106 | # The following are items that won't be overwritten. They are very, very unlikely 107 | # to be changed once installed in the first place 108 | if [[ "$choice" = "F" ]]; then 109 | echo "Updating additional config files... " 110 | cp -nvR "$file_source/Mac/Services/" "$file_target/Services" 111 | #cp -nvR "$file_source/Mac/Quicklook/" "$file_target/Quicklook" 112 | 113 | # FROM 1.5.0 -- Add 64-bit libdvdcss 114 | # FROM 4.2.1 -- Remove: this should be installed via homebrew 115 | #cp -nv "$file_source/Mac/libdvdcss/libdvdcss.2.dylib" /usr/local/lib/libdvdcss.2.dylib 116 | 117 | # FROM 1.4.0 -- Add second terminal file (HomebrewMeDark) 118 | cp -nv "$file_source/Mac/HomebrewMe.terminal" "$HOME/Desktop/HomebrewMe.terminal" 119 | cp -nv "$file_source/Mac/HomebrewMeDark.terminal" "$HOME/Desktop/HomebrewMeDark.terminal" 120 | echo "Terminal settings files 'HomebrewMe' and 'HomebrewMeDark' copied to desktop. To use them, open Terminal > Preferences > Profiles and import" 121 | 122 | #cp -nv "$file_source/Mac/pixelmator_shapes.pxs" "$HOME/Desktop/pixelmator_shapes.pxs" 123 | #echo "Pixelmater shapes file 'pixelmator_shapes.pxs' copied to desktop. To use it, open Pixelmator > File > Import..." 124 | 125 | # FROM 2.0.0 126 | # Install Xcode CLI if necessary 127 | result=$(xcode-select --install 2>&1) 128 | error=$(grep 'already installed' < <(echo -e "$result")) 129 | if [[ -n "$error" ]]; then 130 | # CLI installed already 131 | echo "Xcode Command Line Tools installed" 132 | else 133 | # Check for opening of external installer 134 | note=$(grep 'install requested' < <(echo -e "$result")) 135 | if [[ -n "$note" ]]; then 136 | echo "Installing Xcode Command Line Tools... " 137 | fi 138 | fi 139 | 140 | # FROM 1.1.0 141 | # Run the various macOS config scriptlets 142 | echo "Configuring macOS... " 143 | if cd "$file_source/Mac/config"; then 144 | chmod +x . 145 | for task in *; do 146 | echo $task 147 | ./$task 148 | done 149 | fi 150 | fi 151 | 152 | echo "Mac configuration files updated" 153 | -------------------------------------------------------------------------------- /updatepi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # updatepi.sh 5 | # 6 | # Update local Pi user config files 7 | # (eg. between multiple machines) 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-23, Tony Smith 11 | # @version 4.0.0 12 | # @license MIT 13 | # 14 | 15 | show_error_and_exit() { 16 | echo $1 17 | exit 1 18 | } 19 | 20 | if [[ "$EUID" -ne 0 ]]; then 21 | show_error_and_exit "Please run this script as root" 22 | fi 23 | 24 | if [[ ${1} == "" ]]; then 25 | show_error_and_exit 'Usage: sudo ./updatepi.sh {username}' 26 | fi 27 | 28 | id "${1}" > /dev/null 2>&1 29 | if [[ $? -ne 0 ]]; then 30 | show_error_and_exit "User '${1}' not known" 31 | fi 32 | 33 | file_source="$HOME/GitHub/dotfiles" 34 | 35 | apt update 36 | apt install -y zsh git curl > /dev/null 2>&1 37 | 38 | sudo -u ${1} mdkir -p "$HOME/GitHub" 39 | sudo -u ${1} cd "$HOME/GitHub" 40 | sudo -u ${1} git clone https://github.com/smittytone/dotfiles 41 | 42 | if [[ ! -e "${file_source}" ]]; then 43 | show_error_and_exit "Please clone the repo 'dotfiles' before proceeding -- exiting " 44 | fi 45 | 46 | echo "Updating primary config files... " 47 | 48 | # nano rc file 49 | cp -v "$file_source/Pi/nanorc" "$HOME/.nanorc" 50 | 51 | # bash profile 52 | cp -v "$file_source/Pi/bash_aliases" "$HOME/.bash_aliases" 53 | 54 | # Sync .zshrc 55 | cp -v "$file_source/Pi/zshrc" "$HOME/.zshrc" 56 | 57 | # pylint rc file 58 | cp -v "$file_source/Universal/pylintrc" "$HOME/.pylintrc" 59 | 60 | # git config 61 | if [[ ! -e "$HOME/.config/git" ]]; then 62 | echo "Adding ~/.config/git... " 63 | mkdir -p "$HOME/.config/git" 64 | fi 65 | 66 | # git global exclude file 67 | if cp -v "$file_source/Universal/gitignore_global" "$HOME/.config/git/gitignore_global"; then 68 | # Add a reference to the file to git 69 | git config --global core.excludesfile "$HOME/.config/git/gitignore_global" 70 | fi 71 | 72 | # Change shell 73 | chsh -s /bin/zsh ${1} 74 | 75 | # Install node 76 | curl -sL https://deb.nodesource.com/setup_19.x | bash 77 | apt install -y nodejs 78 | 79 | echo "Pi configuration files updated -- please reboot" 80 | -------------------------------------------------------------------------------- /updatepi.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # 4 | # updatepi.zsh 5 | # 6 | # Update local Pi user config files 7 | # (eg. between multiple machines) 8 | # 9 | # @author Tony Smith 10 | # @copyright 2019-20, Tony Smith 11 | # @version 3.0.0 12 | # @license MIT 13 | # 14 | 15 | file_source="$HOME/GitHub/dotfiles" 16 | 17 | if [[ ! -e "$file_source" ]]; then 18 | echo "Please clone the repo 'dotfiles' before proceeding -- exiting " 19 | exit 1 20 | fi 21 | 22 | echo "Updating primary config files... " 23 | 24 | # nano rc file 25 | cp -v "$file_source/Pi/nanorc" "$HOME/.nanorc" 26 | 27 | # bash profile 28 | cp -v "$file_source/Pi/bash_aliases" "$HOME/.bash_aliases" 29 | 30 | # FROM 2.0.0 31 | # Sync .zshrc 32 | cp -v "$file_source/Pi/zshrc" "$HOME/.zshrc" 33 | 34 | # FROM 4.0.0 35 | # pylint rc file 36 | cp -v "$file_source/Universal/pylintrc" "$HOME/.pylintrc" 37 | 38 | # gitup config 39 | # FROM 3.0.0 fix location 40 | if [[ ! -e "$HOME/.config/gitup" ]]; then 41 | echo "Adding ~/.config/gitup... " 42 | mkdir -p "$HOME/.config/gitup" 43 | fi 44 | 45 | cp -v "$file_source/Pi/bookmarks" "$HOME/.config/gitup/bookmarks" 46 | 47 | if [[ ! -e "$HOME/.config/git" ]]; then 48 | echo "Adding ~/.config/git... " 49 | mkdir -p "$HOME/.config/git" 50 | fi 51 | 52 | # git global exclude file 53 | if cp -v "$file_source/Universal/gitignore_global" "$HOME/.config/git/gitignore_global"; then 54 | # Add a reference to the file to git (assumes git installed) 55 | git config --global core.excludesfile "$HOME/.config/git/gitignore_global" 56 | fi 57 | 58 | echo "Pi configuration files updated" 59 | -------------------------------------------------------------------------------- /xcodeautobuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script takes the build number from the release target plist, increments and then saves back 4 | # Add to Xcode as an early 'run shell script' build phase 5 | 6 | buildNum=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PRODUCT_SETTINGS_PATH}") 7 | buildNum=$(($buildNum + 1)) 8 | /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNum" "${PRODUCT_SETTINGS_PATH}" 9 | -------------------------------------------------------------------------------- /yamlizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import json 6 | import yaml 7 | 8 | if __name__ == '__main__': 9 | if len(sys.argv) > 1: 10 | for index, item in enumerate(sys.argv): 11 | file_ext = os.path.splitext(item)[1] 12 | if file_ext == ".json": 13 | file_name = os.path.splitext(item)[0] 14 | with open(file_name + ".yaml", "w") as file: 15 | file.write(yaml.dump(json.load(open(item)))) 16 | -------------------------------------------------------------------------------- /zinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Pi Zero Installation Script 1.1.0 4 | 5 | # Switch to home directory 6 | cd "$HOME" 7 | 8 | # Remove stock folders 9 | echo "Removing home sub-directories..." 10 | rm -rf Pictures 11 | rm -rf Music 12 | rm -rf Videos 13 | rm -rf python_games 14 | rm -rf Templates 15 | rm -rf MagPi 16 | 17 | # Updating System 18 | echo -e "\nUpdating system..." 19 | sudo apt-get update 20 | sudo apt-get -y dist-upgrade 21 | sudo apt-get -y autoremove 22 | 23 | # Make directories 24 | echo -e "\nCreating directories..." 25 | mkdir "$HOME/GitHub" 26 | mkdir "$HOME/Python" 27 | 28 | # Update .bashrc 29 | echo -e "\nConfiguring command line..." 30 | echo "export PS1='\$(pwd) > '" >> .bashrc 31 | echo "GIT=$HOME/GitHub" >> .bashrc 32 | echo "alias la='ls -lahF --color=auto'" > .bash_aliases 33 | echo "alias ls='ls -lhF --color=auto'" >> .bash_aliases 34 | echo "alias rs='sudo shutdown -r now'" >> .bash_aliases 35 | echo "alias sd='sudo shutdown -h now'" >> .bash_aliases 36 | echo "alias python=python3" >> .bash_aliases 37 | echo "alias update='sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y'" >> .bash_aliases 38 | export PATH=$PATH:/usr/local/bin 39 | 40 | # Applications 41 | echo -e "\nInstalling screen..." 42 | sudo apt-get -q -y install screen 43 | echo " nginx..." 44 | sudo apt-get -q -y install nginx 45 | echo " ruby..." 46 | sudo apt-get -q -y install ruby 47 | echo " scrot..." 48 | sudo apt-get -q -y install scrot 49 | echo " mdless..." 50 | sudo gem -q install mdless 51 | echo -e "pylint\n" 52 | sudo pip3 -q install pylint 53 | 54 | # Node 55 | version="12.4.0" 56 | echo -e "\nInstalling Node..." 57 | mkdir tmp 58 | cd tmp || exit 1 59 | wget "https://nodejs.org/dist/v$version/node-v$version-linux-armv6l.tar.gz" 60 | tar -xzf "node-v$version-linux-armv6l.tar.gz" 61 | cd "node-v$version-linux-armv6l" 62 | sudo cp -R * /usr/local/ 63 | 64 | # Git 65 | echo -e "\nCloning key repos..." 66 | cd "$HOME/GitHub" || exit 1 67 | git clone https://github.com/smittytone/dotfiles.git 68 | git clone https://github.com/smittytone/scripts.git 69 | 70 | # Setup configs 71 | cp dotfiles/nanorc "$HOME"/.nanorc 72 | cp dotfiles/pylintrc "$HOME"/.pylintrc 73 | cp dotfiles/gitignore_global "$HOME"/.gitignore_global 74 | git config --global core.excludesfile "$HOME"/.gitignore_global 75 | 76 | echo -e "\nCleaning up..." 77 | # Remove the script 78 | cd "$HOME" 79 | rm zinstall.sh 80 | rm -r tmp 81 | 82 | read -n 1 -s -p "Press any key to reboot (or [C] to cancel)" key 83 | key=${key^^*} 84 | if [ "$key" != "C" ]; then 85 | sudo shutdown -r now 86 | else 87 | echo 88 | fi 89 | --------------------------------------------------------------------------------