diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..39c5277 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +switch_case_indent = true + +[*.{yml,yaml,json,md}] +indent_size = 2 diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json new file mode 100644 index 0000000..33c7766 --- /dev/null +++ b/.github/linters/.jscpd.json @@ -0,0 +1,3 @@ +{ + "threshold": 5 +} diff --git a/.github/workflows/CI-CD.yml b/.github/workflows/CI-CD.yml index 300ee16..6cf652c 100644 --- a/.github/workflows/CI-CD.yml +++ b/.github/workflows/CI-CD.yml @@ -3,14 +3,24 @@ name: CI-CD on: [push] +permissions: + contents: read + packages: read + statuses: write + jobs: lint: name: Linters runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false - name: Run Super-Linter - uses: github/super-linter@latest + uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Biome's JSON formatting conflicts with Prettier's; keep Prettier + VALIDATE_BIOME_FORMAT: false diff --git a/.gitignore b/.gitignore index 561b90f..29bef76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ ; Secret files are for tokens or other "Secret stuff" *.secret .DS_Store + +; Machine-local scripts (work-specific etc.), only the README is committed +bin/local/* +!bin/local/README.md diff --git a/README.md b/README.md index 077b1b1..6bcfd47 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,18 @@ # Scripts + These are some non-secret scripts/configs I use locally to develop. Since I tend to break things, or redo my local setup I place them in version control ## Fair warning -I highly recommend not running other people commands blindly on your machine. So I don't expect others to do the same with mine. Which is why the only documentation included; is meant for myself. +I highly recommend not running other people commands blindly on your machine. So I don't expect others to do the same with mine. Which is why the only documentation included; is meant for myself. ## Setup -### .bashrc -Add the following to your .bashrc -```shell -# Jor bin in PATH -export PATH="$HOME/Projects/Scripts/bin:$PATH" - -# Jor .profile -for f in $HOME/Projects/Scripts/profile/*; do - . "$f" -done - -# Jor aliasses -for f in $HOME/Projects/Scripts/aliasses/*; do - . "$f" -done -``` +Run the install script. It adds bin/ and bin/local/ (gitignored, +machine-local scripts) to PATH and sources every file under profile/ and +aliases/ from your shell rc file (~/.zshrc for Zsh, ~/.bashrc for Bash), +see `./install --help`: -### Config ```shell -mkdir -p ~/.ssh -cp config/ssh/config ~/.ssh/config -chmod 600 ~/.ssh/config +./install ``` diff --git a/aliases/git b/aliases/git new file mode 100755 index 0000000..aafbf35 --- /dev/null +++ b/aliases/git @@ -0,0 +1,3 @@ +#!/bin/sh + +alias grh="git reset --hard" diff --git a/aliasses/kubectl b/aliases/kubectl similarity index 62% rename from aliasses/kubectl rename to aliases/kubectl index 438e002..1278dea 100755 --- a/aliasses/kubectl +++ b/aliases/kubectl @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh alias k8='kubectl' diff --git a/aliasses/minikube b/aliases/minikube similarity index 93% rename from aliasses/minikube rename to aliases/minikube index 1ad06fa..43a29b4 100755 --- a/aliasses/minikube +++ b/aliases/minikube @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh alias podmani='podman machine init --cpus 4 --memory 4096 --disk-size 100' alias minikubed='minikube start --driver=podman --container-runtime containerd' #--alsologtostderr diff --git a/aliasses/utils b/aliases/utils similarity index 82% rename from aliasses/utils rename to aliases/utils index 6e8439c..4728910 100755 --- a/aliasses/utils +++ b/aliases/utils @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh alias conn='ping 8.8.8.8|grep -ivE "time=[0-9][0-9]\."' diff --git a/aliasses/vim b/aliases/vim similarity index 57% rename from aliasses/vim rename to aliases/vim index 3cb9c9a..7b7e709 100755 --- a/aliasses/vim +++ b/aliases/vim @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh alias vi='vim' diff --git a/aliasses/git b/aliasses/git deleted file mode 100755 index e56fe48..0000000 --- a/aliasses/git +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -alias grh="git reset --hard" -# alias git-squash="git reset $(git merge-base master $(git "$1" --show-current))" diff --git a/bin/cb b/bin/cb index f130e5b..748524b 100755 --- a/bin/cb +++ b/bin/cb @@ -1,8 +1,50 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then - echo "Clean all local git branches except master|development|develop" - exit 0 +usage() { + cat <<'EOF' +Usage: cb + +Cleans up local git branches: deletes every local branch that has no +branch of the same name on any remote. + +Runs 'git fetch --prune' first so the remote state is current. The +currently checked out branch is never deleted, and branches checked out +in other worktrees are skipped. +EOF +} + +case "$1" in + -h | --help) + usage + exit 0 + ;; +esac + +if [ -z "$(git remote)" ]; then + echo "No remotes configured, nothing to compare against" >&2 + exit 1 fi -git branch | grep -iEv "master|development|develop|\\*" | xargs git branch -D +git fetch --prune + +current=$(git rev-parse --abbrev-ref HEAD) + +# Branches checked out in a worktree, one refs/heads/... per line. +in_worktrees=$(git worktree list --porcelain | awk '/^branch /{print substr($0, 8)}') + +git for-each-ref --format='%(refname:short)' refs/heads | while read -r branch; do + [ "$branch" = "$current" ] && continue + + if printf '%s\n' "$in_worktrees" | grep -Fxq "refs/heads/$branch"; then + echo "Skipping '$branch', it is checked out in a worktree" + continue + fi + + for remote in $(git remote); do + if git rev-parse --verify --quiet "refs/remotes/$remote/$branch" >/dev/null; then + continue 2 + fi + done + + git branch -D "$branch" +done diff --git a/bin/crb b/bin/crb deleted file mode 100755 index 35e8920..0000000 --- a/bin/crb +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Clean remote branches - -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then - echo "Clean all remote git branches except master|development|develop" - echo "\$1 blacklist regex, matches won't be removed" - echo "\$2 NODRYRUN to actually run this" - exit 0 -fi - -WHITELIST="origin/master$|origin/development$|origin/develop$|origin/HEAD" -if [ "$1" != "" ]; then - WHITELIST="$WHITELIST|$1" -fi - -echo "Removing branches:" -DYINGBRANCHES=$(git branch -r | grep -iEv "$WHITELIST") -for DYINGBRANCH in $DYINGBRANCHES -do - BRANCHNAME=${DYINGBRANCH#"origin/"} - if [ "$2" == "NODRYRUN" ]; then - echo "No dry run, removing $BRANCHNAME now!!!!!!!!!!" - git push origin --delete "$BRANCHNAME" - else - echo "Dry run, pretend removing $BRANCHNAME" - fi -done - -echo "Branches kept:" -git branch -r | grep -iE "$WHITELIST" - -echo "$WHITELIST" diff --git a/bin/create_base64 b/bin/create_base64 index 7e5f739..cd4e36d 100755 --- a/bin/create_base64 +++ b/bin/create_base64 @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "_base64.txt copy of file" echo "\$1 original filename" exit 0 diff --git a/bin/create_certificate_fingerprint b/bin/create_certificate_fingerprint index 73b701e..ada99b3 100755 --- a/bin/create_certificate_fingerprint +++ b/bin/create_certificate_fingerprint @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "Get SHA1 fingerprint of a .pem file." echo "\$1 .pem cert" exit 0 diff --git a/bin/create_certkey b/bin/create_certkey index a3edf81..5ed4cf7 100755 --- a/bin/create_certkey +++ b/bin/create_certkey @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "Create an RSA key.pem and cert.pem" echo "\$1 CN for the cert" exit 0 diff --git a/bin/create_csr b/bin/create_csr index dcd34e9..58099c5 100755 --- a/bin/create_csr +++ b/bin/create_csr @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 2 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 2 ] || [ "$1" = "--help" ]; then echo "Create a csr" echo "\$1 key file .pem" echo "\$2 CN for the csr" diff --git a/bin/create_csr_cert b/bin/create_csr_cert index 94a234b..454c81a 100755 --- a/bin/create_csr_cert +++ b/bin/create_csr_cert @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 3 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 3 ] || [ "$1" = "--help" ]; then echo "Create an validate a CSR en generate valid_cert.pem" echo "\$1 .csr file" echo "\$2 CA cert.pem" diff --git a/bin/create_keystore b/bin/create_keystore index 1fc4b2a..7e50de2 100755 --- a/bin/create_keystore +++ b/bin/create_keystore @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "Create a PKCS12 keystore of a .pem" echo "\$1 .pem file" exit 0 diff --git a/bin/create_keystore_jks b/bin/create_keystore_jks index 1537ca5..efdd40a 100755 --- a/bin/create_keystore_jks +++ b/bin/create_keystore_jks @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 2 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 2 ] || [ "$1" = "--help" ]; then echo "Create a JKS keystore of a PKCS12 keystore" echo "\$1 .pkcs12 keystore file" echo "\$2 .jks keystore file" diff --git a/bin/create_truststore b/bin/create_truststore index aa76f31..180671d 100755 --- a/bin/create_truststore +++ b/bin/create_truststore @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "Create a PKCS12 (.pfx) truststore of a .pem file" echo "\$1 .pem file" exit 0 diff --git a/bin/create_truststore_jks b/bin/create_truststore_jks index 54f3516..283b331 100755 --- a/bin/create_truststore_jks +++ b/bin/create_truststore_jks @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [ $# -lt 3 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 3 ] || [ "$1" = "--help" ]; then echo "Create a .jks truststore of a .pem file. Or add a pem to a truststore" echo "\$1 .pem cert" echo "\$2 alias for the cert in the truststore" diff --git a/bin/ec b/bin/ec new file mode 100755 index 0000000..45b004d --- /dev/null +++ b/bin/ec @@ -0,0 +1,32 @@ +#!/bin/sh + +usage() { + cat <<'EOF' +Usage: ec + +Creates an empty commit with the message 'chore: empty commit' and pushes +it, e.g. to trigger CI. + +When the branch has no upstream yet, it is pushed with -u to set one. +EOF +} + +case "$1" in + -h | --help) + usage + exit 0 + ;; +esac + +git commit --allow-empty -m "chore: empty commit" || exit 1 + +if git rev-parse --verify --quiet '@{u}' >/dev/null 2>&1; then + git push +else + remote=$(git remote | head -n 1) + if [ -z "$remote" ]; then + echo "No remote to push to" >&2 + exit 1 + fi + git push -u "$remote" HEAD +fi diff --git a/bin/gacp b/bin/gacp index ab1135f..372d2a0 100755 --- a/bin/gacp +++ b/bin/gacp @@ -1,8 +1,8 @@ -#!/bin/bash +#!/bin/sh set -e -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "git adds, commits and pushes" echo "\$1 is the commit message" exit 0 diff --git a/bin/gc b/bin/gc index dadf473..cf421cf 100755 --- a/bin/gc +++ b/bin/gc @@ -1,54 +1,173 @@ -#!/bin/bash - -if [ $# -lt 1 ] || [[ $* == *"--help"* ]]; then - echo "Does a git checkout on a rev. Searching in this order" - echo " - Git tag" - echo " - Local branch" - echo " - Remote branch" - echo " - Creates a new branch branch" - exit 0 -fi +#!/bin/sh + +usage() { + cat <<'EOF' +Usage: gc + +Finds a git rev matching and checks it out. may also be a +partial (case-insensitive) match of a branch or tag name. + +Well-known branch aliases are swapped when the requested branch does not +exist but its counterpart does: master <-> main, develop <-> development. +So 'gc master' checks out 'main' in a repo that only has 'main'. + +Search order: + 1. Exact tag + 2. Exact local branch (followed by a git pull) + 3. Remote-prefixed branch, e.g. 'origin/my-branch' checks out 'my-branch' + 4. Exact branch on any remote (checked out as a tracking branch) + 5. Any other rev, e.g. a commit hash (detached HEAD) + 6. Partial match on local branch names + 7. Partial match on remote branch names (matched against 'remote/branch') + 8. Partial match on tag names + 9. Nothing matched: a new branch named is created + +A local branch that is checked out in another worktree (locked or not) has +that worktree removed before being checked out here. A worktree holding +uncommitted changes is left alone and gc exits with an error instead. + +If a match is ambiguous (multiple names, or the same branch on multiple +remotes), all matches are listed and nothing is checked out. Disambiguate +remotes with a prefix, e.g. 'gc upstream/my-branch'. +EOF +} + +case "$1" in + '' | -h | --help) + usage + exit 0 + ;; +esac + +rev=$1 + +# Checks out a local branch and pulls it when it has an upstream. When +# another worktree (locked or not) already has the branch checked out, that +# worktree is removed first, unless it holds uncommitted changes. +checkout_branch() { + other=$(git worktree list --porcelain | awk -v ref="refs/heads/$1" ' + /^worktree / { path = substr($0, 10) } + /^branch / { if (substr($0, 8) == ref) print path }') + if [ -n "$other" ] && [ "$other" != "$(git rev-parse --show-toplevel)" ]; then + echo "Branch '$1' is checked out in worktree $other, removing it" + git worktree unlock "$other" >/dev/null 2>&1 + if ! git worktree remove "$other"; then + echo "Could not remove worktree $other (uncommitted changes?)." >&2 + echo "Clean it up or run: git worktree remove --force '$other'" >&2 + return 1 + fi + fi + git checkout "$1" || return 1 + if git rev-parse --verify --quiet '@{u}' >/dev/null 2>&1; then + git pull + fi +} + +# Checks out a single partial match, or lists them all when ambiguous. +checkout_match() { + kind=$1 + matches=$2 + + [ -n "$matches" ] || return 1 + + count=$(printf '%s\n' "$matches" | wc -l | tr -d ' ') + if [ "$count" -gt 1 ]; then + echo "'$rev' matches multiple $kind:" >&2 + printf '%s\n' "$matches" | sed 's/^/ /' >&2 + exit 1 + fi + + case "$kind" in + 'local branches') + echo "Checking out local branch '$matches' (matched '$rev')" + checkout_branch "$matches" + ;; + 'remote branches') + branch=${matches#*/} + echo "Checking out remote branch '$matches' as '$branch'" + git checkout -b "$branch" --track "$matches" + ;; + 'tags') + echo "Checking out tag '$matches' (matched '$rev')" + git checkout "tags/$matches" + ;; + esac + exit $? +} + +# Returns the well-known counterpart of a branch name, if any. +alias_for() { + case "$1" in + master) echo main ;; + main) echo master ;; + develop) echo development ;; + development) echo develop ;; + esac +} + +# True when the branch exists locally or on any remote. +branch_exists() { + git rev-parse --verify --quiet "refs/heads/$1" >/dev/null && return 0 + for remote in $(git remote); do + git rev-parse --verify --quiet "refs/remotes/$remote/$1" >/dev/null && + return 0 + done + return 1 +} git fetch --prune -if git rev-parse --verify tags/"$1" > /dev/null 2>&1; then - echo "Checking out local tags/$1" - git checkout tags/"$1" - exit 0 +alias=$(alias_for "$rev") +if [ -n "$alias" ] && ! branch_exists "$rev" && branch_exists "$alias"; then + echo "No branch '$rev' here, using '$alias' instead" + rev=$alias fi -if git rev-parse --verify "$1" > /dev/null 2>&1; then - echo "Checking out local branch $1" - git checkout "$1" - git pull - exit 0 +if git rev-parse --verify --quiet "refs/tags/$rev" >/dev/null; then + echo "Checking out tag '$rev'" + git checkout "tags/$rev" + exit $? fi -if git rev-parse --verify origin/"$1" > /dev/null 2>&1; then - echo "Checking out remote branch $1" - git checkout origin/"$1" -b "$1" - exit 0 +if git rev-parse --verify --quiet "refs/heads/$rev" >/dev/null; then + echo "Checking out local branch '$rev'" + checkout_branch "$rev" + exit $? fi -if git rev-parse --verify origin/"$1" > /dev/null 2>&1; then - echo "Checking out remote branch $1" - git checkout origin/"$1" -b "$1" - exit 0 +if git rev-parse --verify --quiet "refs/remotes/$rev" >/dev/null; then + branch=${rev#*/} + if git rev-parse --verify --quiet "refs/heads/$branch" >/dev/null; then + echo "Checking out local branch '$branch' for '$rev'" + checkout_branch "$branch" + else + echo "Checking out remote branch '$rev'" + git checkout -b "$branch" --track "$rev" + fi + exit $? fi -LOCAL_BRANCH=$(git branch|grep -E "$1" -m 1 | xargs) -if [ -n "$LOCAL_BRANCH" ]; then - echo "!!! REGEX MATCHED LOCAL BRANCH '$LOCAL_BRANCH'" - git checkout "$LOCAL_BRANCH" - exit 0 -fi +checkout_match 'remote branches' \ + "$(for remote in $(git remote); do + git rev-parse --verify --quiet "refs/remotes/$remote/$rev" >/dev/null && + echo "$remote/$rev" + done)" -REMOTE_BRANCH=$(git branch -r|grep -E "$1" -m 1 | xargs | sed -e "s/^origin\///") -if [ -n "$REMOTE_BRANCH" ]; then - echo "!!! REGEX MATCHED REMOTE BRANCH '$REMOTE_BRANCH'" - git checkout origin/"$REMOTE_BRANCH" -b "$REMOTE_BRANCH" - exit 0 +if git rev-parse --verify --quiet "$rev^{commit}" >/dev/null; then + echo "Checking out rev '$rev' (detached HEAD)" + git checkout "$rev" + exit $? fi -echo "!!! CHECKING OUT NEW BRANCH $1" -git checkout -b "$1" +checkout_match 'local branches' \ + "$(git for-each-ref --format='%(refname:short)' refs/heads | grep -i -- "$rev")" + +checkout_match 'remote branches' \ + "$(git for-each-ref --format='%(refname)' refs/remotes | + sed 's|^refs/remotes/||' | grep -v '/HEAD$' | grep -i -- "$rev")" + +checkout_match 'tags' \ + "$(git for-each-ref --format='%(refname:short)' refs/tags | grep -i -- "$rev")" + +echo "No tag, branch, or rev matching '$rev' found, creating branch '$rev'" +git checkout -b "$rev" diff --git a/bin/lib/checksum_changed b/bin/lib/checksum_changed index 7cb618d..9198fc9 100755 --- a/bin/lib/checksum_changed +++ b/bin/lib/checksum_changed @@ -1,22 +1,13 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then - echo "Check if the checksum of a file changed since last check" +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then + echo "Check if the checksum of a file changed since it was last saved" + echo "with save_checksum. Exits 0 when it changed, 1 when it did not." echo "\$1 is the file name" exit 0 fi -checksum_changed () { - # shellcheck source=/dev/null - source "${BASH_SOURCE%/*}/cof" - CACHED_SHASUM=$(cof "$1") +current=$("$(dirname "$0")/cof" "$1") +cached=$(cat ".globalignored/$1.checksum" 2>/dev/null) - mkdir -p .globalignored/ - touch .globalignored/"$1".checksum - - if [[ $CACHED_SHASUM = .globalignored/"$1".checksum ]] ; then - exit 1 - fi - - exit 0 -} +[ "$current" != "$cached" ] diff --git a/bin/lib/cof b/bin/lib/cof index 15983df..a4fdff6 100755 --- a/bin/lib/cof +++ b/bin/lib/cof @@ -1,16 +1,9 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "Print the sha256 checksum of a file" + echo "\$1 is the file name" exit 0 fi -cof () { - # Sha 256 checksum of file - SHASUM_OUTPUT=$(shasum -a 256 "$1") - - # Print the first element of the shasum output - # shellcheck disable=SC2086 - set -- $SHASUM_OUTPUT - echo "$1" -} +shasum -a 256 "$1" | cut -d' ' -f1 diff --git a/bin/lib/project_root b/bin/lib/project_root deleted file mode 100755 index eff7db9..0000000 --- a/bin/lib/project_root +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -if [[ $* == *"--help"* ]]; then - echo "Finds the full path of a project onder ~/Projects" - exit 0 -fi - -project_root () { - # Find the root of a project if it is located on Source. Also check for some common prefixes - # TODO make DRY - - DIR=$(find -L ~/Projects -maxdepth 2 -type d -iname "$1") - - if [ "$DIR" != "" ]; then - echo "$DIR" - exit 0 - fi - - - DIR=$(find -L ~/Projects -maxdepth 2 -type d -iname "nos-$1") - - if [ "$DIR" != "" ]; then - echo "$DIR" - exit 0 - fi - - echo "'$1' not found" 1>&2 - exit 1 -} diff --git a/bin/lib/save_checksum b/bin/lib/save_checksum index acdf75a..7a175e3 100755 --- a/bin/lib/save_checksum +++ b/bin/lib/save_checksum @@ -1,15 +1,11 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then +if [ $# -lt 1 ] || [ "$1" = "--help" ]; then echo "Save the checksum of a file under .globalignored." - echo "Useful for detecting gemfile.locks and pom.xml changes." + echo "Useful for detecting Gemfile.lock and pom.xml changes." + echo "\$1 is the file name" exit 0 fi -save_checksum () { - mkdir -p .globalignored/ - touch .globalignored/"$1".checksum - # shellcheck source=/dev/null - source "${BASH_SOURCE%/*}/save_checksum" - cof "$1" > .globalignored/"$1".checksum -} +mkdir -p .globalignored +"$(dirname "$0")/cof" "$1" >".globalignored/$1.checksum" diff --git a/bin/local/README.md b/bin/local/README.md new file mode 100644 index 0000000..6299772 --- /dev/null +++ b/bin/local/README.md @@ -0,0 +1,6 @@ +# Local scripts + +Scripts in this folder are on PATH (the install script adds it) but never +committed: everything here except this file is gitignored. Use it for +machine- or work-specific scripts that do not belong in a private +repository. diff --git a/bin/mci b/bin/mci index 7d41371..31e7fcd 100755 --- a/bin/mci +++ b/bin/mci @@ -1,17 +1,18 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then +if [ "$1" = "--help" ]; then echo "Performs a Maven clean install without tests and updates the pom.xml checksum" exit 0 fi -JAVA_VERSION=$(grep java.version pom.xml | sed 's/[^0-9]//g') -echo "Detected java version: $JAVA_VERSION" - -JAVA_HOME=$(/usr/libexec/java_home -v "$JAVA_VERSION") -export JAVA_HOME +JAVA_VERSION=$(sed -n 's|.*[[:space:]]*\([0-9][0-9.]*\).*|\1|p' pom.xml | head -n 1) +if [ -n "$JAVA_VERSION" ] && [ -x /usr/libexec/java_home ]; then + echo "Detected java version: $JAVA_VERSION" + JAVA_HOME=$(/usr/libexec/java_home -v "$JAVA_VERSION") + export JAVA_HOME +else + echo "Could not detect the java version from pom.xml, using the default java" +fi mvn clean install -Dmaven.test.skip=true -Ddependency-check.skip=true -# shellcheck source=/dev/null -source "${BASH_SOURCE%/*}/lib/save_checksum" -save_checksum pom.xml +"$(dirname "$0")/lib/save_checksum" pom.xml diff --git a/bin/sb b/bin/sb index 2b4b4b7..3b23fb9 100755 --- a/bin/sb +++ b/bin/sb @@ -1,38 +1,32 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then +if [ "$1" = "--help" ]; then echo "Start a spring boot server" echo "Does a maven clean install if the pom.xml changed" echo " --pull to git pull before starting" exit 0 fi -# Read arguments -for arg in "$@" -do - case $arg in +for arg in "$@"; do + case "$arg" in --pull) - SHOULD_PULL=1 - shift - ;; + echo 'Pulling' + git pull + ;; esac done -if [[ $SHOULD_PULL = "1" ]] ; then - echo 'Pulling' - git pull -fi - -# shellcheck source=/dev/null -source "${BASH_SOURCE%/*}/lib/checksum_changed" -if checksum_changed pom.xml; then +if "$(dirname "$0")/lib/checksum_changed" pom.xml; then echo 'pom.xml changed; Running "mvn install -Dmaven.test.skip=true"' - mci + "$(dirname "$0")/mci" fi -JAVA_VERSION=$(grep java.version pom.xml | sed 's/[^0-9]//g') -echo "Detected java version: $JAVA_VERSION" - -JAVA_HOME=$(/usr/libexec/java_home -v "$JAVA_VERSION") -export JAVA_HOME +JAVA_VERSION=$(sed -n 's|.*[[:space:]]*\([0-9][0-9.]*\).*|\1|p' pom.xml | head -n 1) +if [ -n "$JAVA_VERSION" ] && [ -x /usr/libexec/java_home ]; then + echo "Detected java version: $JAVA_VERSION" + JAVA_HOME=$(/usr/libexec/java_home -v "$JAVA_VERSION") + export JAVA_HOME +else + echo "Could not detect the java version from pom.xml, using the default java" +fi mvn clean spring-boot:run -Dmaven.test.skip=true diff --git a/bin/sq b/bin/sq new file mode 100755 index 0000000..a0f1a14 --- /dev/null +++ b/bin/sq @@ -0,0 +1,108 @@ +#!/bin/sh + +usage() { + cat <<'EOF' +Usage: sq + +Squashes all commits on the current branch into a single commit with + as its commit message. The squashed commits are those not on +the base branch: the remote default branch when known, otherwise the +first of master, main, develop or development that exists. + +When the branch has an upstream, the squashed branch is pushed with +'git push --force-with-lease' afterwards. + +Refuses to run with uncommitted changes, on the base branch itself, or on +a detached HEAD. Also refuses when another local branch points at a commit +between the base and HEAD (a stacked branch), since squashing would absorb +that branch's commits. +EOF +} + +case "$1" in + -h | --help) + usage + exit 0 + ;; + '') + usage >&2 + exit 1 + ;; +esac + +message=$1 + +current=$(git rev-parse --abbrev-ref HEAD) || exit 1 +if [ "$current" = "HEAD" ]; then + echo "Not on a branch (detached HEAD), nothing to squash" >&2 + exit 1 +fi + +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "You have uncommitted changes, commit or stash them first" >&2 + exit 1 +fi + +# The remote default branch, e.g. origin/main. +base='' +for remote in $(git remote); do + if ref=$(git symbolic-ref --quiet "refs/remotes/$remote/HEAD"); then + base=${ref#refs/remotes/} + break + fi +done +if [ -z "$base" ]; then + for name in master main develop development; do + if git rev-parse --verify --quiet "refs/heads/$name" >/dev/null; then + base=$name + break + fi + done +fi +if [ -z "$base" ]; then + echo "Could not determine the base branch" >&2 + exit 1 +fi + +if [ "$current" = "${base#*/}" ]; then + echo "On base branch '$current', refusing to squash it" >&2 + exit 1 +fi + +merge_base=$(git merge-base "$base" HEAD) || exit 1 + +count=$(git rev-list --count "$merge_base..HEAD") +if [ "$count" -lt 2 ]; then + echo "Nothing to squash: $count commit(s) on '$current' since '$base'" + exit 0 +fi + +# A local branch tip strictly between the merge base and HEAD means the +# current branch is stacked on it; squashing would absorb its commits. +head=$(git rev-parse HEAD) +stacked=$(git for-each-ref --format='%(refname:short)' refs/heads | + while read -r branch; do + [ "$branch" = "$current" ] && continue + tip=$(git rev-parse "refs/heads/$branch") + [ "$tip" = "$merge_base" ] && continue + [ "$tip" = "$head" ] && continue + if git merge-base --is-ancestor "$merge_base" "$tip" && + git merge-base --is-ancestor "$tip" "$head"; then + echo "$branch" + fi + done) +if [ -n "$stacked" ]; then + echo "Refusing to squash: '$current' is stacked on top of:" >&2 + printf '%s\n' "$stacked" | sed 's/^/ /' >&2 + echo "Squashing down to '$base' would absorb those branches' commits" >&2 + exit 1 +fi + +echo "Squashing $count commits on '$current' since '$base' into one" +git reset --soft "$merge_base" && + git commit -m "$message" || exit 1 + +if git rev-parse --verify --quiet "$current@{u}" >/dev/null 2>&1; then + echo "Pushing '$current' with --force-with-lease" + git push --force-with-lease +fi diff --git a/bin/sw b/bin/sw index be0d415..6726332 100755 --- a/bin/sw +++ b/bin/sw @@ -1,16 +1,70 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then - echo "Tries to find a project under ~/Source and opens it in VScode." - echo "TODO: allow for other IDEs to work as well" - exit 0 +usage() { + cat <<'EOF' +Usage: sw + +Finds a project folder under $PROJECTS_DIR (default: ~/Projects) and opens +it in VSCode. Folders are searched for directly under the projects folder +and one level deeper. is matched against folder names as a +case-insensitive extended regex; whole-name matches win over partial +matches, and direct children win over deeper folders. + +If multiple folders match equally well, all matches are listed and nothing +is opened. +EOF +} + +case "$1" in + '' | -h | --help) + usage + exit 0 + ;; +esac + +name=$1 +projects=${PROJECTS_DIR:-$HOME/Projects} + +if [ ! -d "$projects" ]; then + echo "Projects folder '$projects' does not exist" >&2 + exit 1 fi -set -e +if ! command -v code >/dev/null 2>&1; then + echo "VSCode's 'code' command not found on PATH" >&2 + exit 1 +fi + +# Opens a single match in VSCode, or lists them all when ambiguous. +open_match() { + matches=$1 + + [ -n "$matches" ] || return 1 + + count=$(printf '%s\n' "$matches" | wc -l | tr -d ' ') + if [ "$count" -gt 1 ]; then + echo "'$name' matches multiple projects:" >&2 + printf '%s\n' "$matches" | sed 's/^/ /' >&2 + exit 1 + fi + + echo "Opening $matches" + code "$matches" + exit $? +} -# shellcheck source=/dev/null -source "${BASH_SOURCE%/*}/lib/project_root" +# Lists folders between the given depths whose name matches the regex. +match_projects() { + find -L "$projects" -mindepth "$1" -maxdepth "$2" \ + -type d ! -path '*/.*' 2>/dev/null | + while read -r dir; do + basename "$dir" | grep -iqE "$3" && echo "$dir" + done +} -project_location=$(project_root "$1") +open_match "$(match_projects 1 2 "^($name)\$")" +open_match "$(match_projects 1 1 "$name")" +open_match "$(match_projects 2 2 "$name")" -(cd "$project_location" && code .) +echo "No project matching '$name' found under $projects" >&2 +exit 1 diff --git a/bin/wip b/bin/wip index 709b211..8b47ef3 100755 --- a/bin/wip +++ b/bin/wip @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if [[ $* == *"--help"* ]]; then +if [ "$1" = "--help" ]; then echo "Quickly commits and current changes under a 'WIP' message." exit 0 fi diff --git a/bin/wsldockerd b/bin/wsldockerd deleted file mode 100755 index 8bab854..0000000 --- a/bin/wsldockerd +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# if [[ $* == *"--help"* ]]; then -# echo "Starts Docker Daemon on WSL (because lack of systemd)" -# exit 0 -# fi - -# DOCKER_DISTRO="Ubuntu" -# DOCKER_DIR=/mnt/wsl/shared-docker -# DOCKER_SOCK="$DOCKER_DIR/docker.sock" -# export DOCKER_HOST="unix://$DOCKER_SOCK" -# if [ ! -S "$DOCKER_SOCK" ]; then -# mkdir -p "$DOCKER_DIR" -# chmod 755 "$DOCKER_DIR" -# chgrp docker "$DOCKER_DIR" -# /mnt/c/Windows/System32/wsl.exe -d $DOCKER_DISTRO sh -c "nohup sudo -b dockerd < /dev/null > $DOCKER_DIR/dockerd.log 2>&1" -# fi diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..64d6552 --- /dev/null +++ b/claude.md @@ -0,0 +1,4 @@ +# Claude instructions + +All scripts must be POSIX compatible. And contain a --help flag that +explains what the script does diff --git a/config/ssh/config b/config/ssh/config deleted file mode 100644 index 884dc0b..0000000 --- a/config/ssh/config +++ /dev/null @@ -1,6 +0,0 @@ -Host github.com - User git -Host *amazonaws.com - User ec2-user -Host rpi.jor.dev - User alarm diff --git a/install b/install new file mode 100755 index 0000000..bc3ffbd --- /dev/null +++ b/install @@ -0,0 +1,48 @@ +#!/bin/sh + +usage() { + cat <<'EOF' +Usage: ./install [rc-file] + +Installs these scripts into your shell rc file: adds bin/ and bin/local/ +to PATH and sources every file under profile/ and aliases/. + +[rc-file] defaults to ~/.zshrc when your shell is zsh, ~/.bashrc when it +is bash, and ~/.profile otherwise. Running the script again is a no-op: +the block is only added once. +EOF +} + +case "$1" in + -h | --help) + usage + exit 0 + ;; +esac + +scripts_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) + +rc=$1 +if [ -z "$rc" ]; then + case "${SHELL##*/}" in + zsh) rc=$HOME/.zshrc ;; + bash) rc=$HOME/.bashrc ;; + *) rc=$HOME/.profile ;; + esac +fi + +marker="# Jor Scripts" +if [ -f "$rc" ] && grep -qF "$marker" "$rc"; then + echo "Already installed in $rc" + exit 0 +fi + +cat >>"$rc" < /dev/null || ssh-add "$HOME"/.ssh/*.key +if ! ssh-add -l >/dev/null 2>&1 && ls "$HOME"/.ssh/*.key >/dev/null 2>&1; then + ssh-add "$HOME"/.ssh/*.key +fi diff --git a/profile/keychain-rpi b/profile/keychain-rpi index 0b9c167..9394e28 100755 --- a/profile/keychain-rpi +++ b/profile/keychain-rpi @@ -1,18 +1,18 @@ -#!/bin/bash +#!/bin/sh -if ! hostname | grep -iqE 'raspberry' &> /dev/null ; then +if ! hostname | grep -iq 'raspberry'; then return fi -if ls ~/.keychain/*-sh &> /dev/null ; then - keychain --agents ssh &> /dev/null +if ls ~/.keychain/*-sh >/dev/null 2>&1; then + keychain --agents ssh >/dev/null 2>&1 # shellcheck source=/dev/null - source ~/.keychain/*-sh + . ~/.keychain/*-sh fi -#ssh-add -l &> /dev/null || ssh-add "$HOME"/.ssh/*.key +#ssh-add -l > /dev/null 2>&1 || ssh-add "$HOME"/.ssh/*.key -if ! pgrep gpg-agent &> /dev/null ; then +if ! pgrep gpg-agent >/dev/null 2>&1; then GPG_TTY=$(tty) export GPG_TTY # shellcheck disable=SC2046 diff --git a/profile/keychain-wsl b/profile/keychain-wsl index 7b7cfe0..4351b29 100755 --- a/profile/keychain-wsl +++ b/profile/keychain-wsl @@ -1,18 +1,20 @@ -#!/bin/bash +#!/bin/sh -if ! which powershell.exe &> /dev/null ; then +if ! command -v powershell.exe >/dev/null 2>&1; then return fi -if ls ~/.keychain/*-sh &> /dev/null ; then - keychain --agents ssh &> /dev/null +if ls ~/.keychain/*-sh >/dev/null 2>&1; then + keychain --agents ssh >/dev/null 2>&1 # shellcheck source=/dev/null - source ~/.keychain/*-sh + . ~/.keychain/*-sh fi -ssh-add -l &> /dev/null || ssh-add "$HOME"/.ssh/*.key +if ! ssh-add -l >/dev/null 2>&1 && ls "$HOME"/.ssh/*.key >/dev/null 2>&1; then + ssh-add "$HOME"/.ssh/*.key +fi -if ! pgrep gpg-agent &> /dev/null ; then +if ! pgrep gpg-agent >/dev/null 2>&1; then GPG_TTY=$(tty) export GPG_TTY # shellcheck disable=SC2046 diff --git a/profile/ksops b/profile/ksops index c043ffb..2e8f403 100755 --- a/profile/ksops +++ b/profile/ksops @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh -export XDG_CONFIG_HOME=$HOME/.config +export XDG_CONFIG_HOME="$HOME"/.config diff --git a/profile/kubectl b/profile/kubectl index 920e525..0111616 100755 --- a/profile/kubectl +++ b/profile/kubectl @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh -export KUBECONFIG=$HOME/.kube/config.yaml +export KUBECONFIG="$HOME"/.kube/config.yaml diff --git a/profile/less b/profile/less index 70efefe..a839d2d 100755 --- a/profile/less +++ b/profile/less @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh export LESS="-eirMFX" diff --git a/profile/nvm b/profile/nvm index 3abe594..e8e1b36 100755 --- a/profile/nvm +++ b/profile/nvm @@ -1,37 +1,37 @@ -#!/bin/bash +#!/bin/sh -if ! test -f "/opt/homebrew/opt/nvm/nvm.sh" ; then +if [ ! -f "/opt/homebrew/opt/nvm/nvm.sh" ]; then return fi export NVM_DIR="$HOME/.nvm" # shellcheck source=/dev/null -[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm +[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && . "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm # shellcheck source=/dev/null -[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion +[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && . "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion +# Auto load correct Node version on directory change; the hook is a zsh +# feature, so only register it there. +load_nvmrc() { + node_version="$(nvm version)" + nvmrc_path="$(nvm_find_nvmrc)" -# Auto load correct Node version -autoload -U add-zsh-hook -load-nvmrc() { - # shellcheck disable=SC2155 - local node_version="$(nvm version)" - # shellcheck disable=SC2155 - local nvmrc_path="$(nvm_find_nvmrc)" + if [ -n "$nvmrc_path" ]; then + nvmrc_node_version=$(nvm version "$(cat "$nvmrc_path")") - if [ -n "$nvmrc_path" ]; then - # shellcheck disable=SC2155 - local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")") - - if [ "$nvmrc_node_version" = "N/A" ]; then - nvm install - elif [ "$nvmrc_node_version" != "$node_version" ]; then - nvm use + if [ "$nvmrc_node_version" = "N/A" ]; then + nvm install + elif [ "$nvmrc_node_version" != "$node_version" ]; then + nvm use + fi + elif [ "$node_version" != "$(nvm version default)" ]; then + echo "Reverting to nvm default version" + nvm use default fi - elif [ "$node_version" != "$(nvm version default)" ]; then - echo "Reverting to nvm default version" - nvm use default - fi } -add-zsh-hook chpwd load-nvmrc -load-nvmrc + +if [ -n "$ZSH_VERSION" ]; then + autoload -U add-zsh-hook + add-zsh-hook chpwd load_nvmrc +fi +load_nvmrc diff --git a/profile/projects b/profile/projects new file mode 100755 index 0000000..d42fbfb --- /dev/null +++ b/profile/projects @@ -0,0 +1,3 @@ +#!/bin/sh + +export PROJECTS_DIR="$HOME/Projects" diff --git a/profile/pyenv b/profile/pyenv index e28e254..68f9851 100755 --- a/profile/pyenv +++ b/profile/pyenv @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if ! ls "$HOME/.pyenv" &> /dev/null ; then +if [ ! -d "$HOME/.pyenv" ]; then return fi diff --git a/profile/sentry b/profile/sentry index 89afefe..a540757 100755 --- a/profile/sentry +++ b/profile/sentry @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh export SENTRY_IGNORE_API_RESOLUTION_ERROR=1 diff --git a/profile/vim b/profile/vim index 601f070..5c712bd 100755 --- a/profile/vim +++ b/profile/vim @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh export EDITOR=vim diff --git a/profile/wsl b/profile/wsl index 3c87c49..2c6a23b 100755 --- a/profile/wsl +++ b/profile/wsl @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/sh -if ! which powershell.exe &> /dev/null ; then +if ! command -v powershell.exe >/dev/null 2>&1; then return fi