aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CLAUDE.md38
-rwxr-xr-xgit-shell-commands/new-project46
-rwxr-xr-xgit-shell-commands/new-repo30
-rwxr-xr-xgit-shell-commands/set-description51
4 files changed, 149 insertions, 16 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..bf1e5df
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,38 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Overview
+
+A personal collection of bash scripts for various purposes. Scripts use `set -euo pipefail` and have inline usage documentation. New scripts should be organized into subdirectories by purpose.
+
+## Current script categories
+
+### General structure
+Each subdirectory groups scripts by deployment target or domain. Scripts are plain bash — no build step.
+
+### Git server scripts
+
+### `git-shell-commands/`
+Invoked remotely via SSH (e.g. `ssh git@treybastian.com new-project myrepo`). The git shell restricts the user to only these named commands. Scripts run in the context of the home directory of the git user.
+
+- `new-project` — creates a bare repo; `--public` also writes `public.conf` and registers the repo on the public remote; `--description <desc>` sets the repo description on both servers
+- `new-repo` — lower-level command that only creates a bare repo (called by `new-project` to register the mirror); accepts `--description <desc>`
+- `make-public` — converts an existing private repo to public by writing `public.conf` and registering the mirror
+- `set-description` — sets the description on an existing repo; if `public.conf` exists, also updates the description on the remote
+- `no-interactive-login` — blocks interactive shell access for the git user
+
+### `git-hooks/`
+Installed globally on the server via `git config --system core.hooksPath`. When `core.hooksPath` is set, git uses only that directory for hooks — repo-specific hooks are bypassed. This is solved by:
+
+- `hook-runner` — a passthrough script that checks if a repo-local hook exists at `$(pwd)/hooks/<hook-name>` and runs it. It is **symlinked** to `post-update`, `pre-recieve`, and `update` so those hooks delegate to repo-specific scripts.
+- `post-receive` — the main hook; if `public.conf` exists in the repo, it mirror-pushes to the URL inside it, then also delegates to a repo-local `post-receive` hook if present.
+
+#### Public/private distinction
+A repo is considered public if and only if it contains a `public.conf` file at its root. That file holds a single line: the remote URL to mirror to (e.g. `git@treybastian.com:repos/myrepo.git`). The `post-receive` hook reads this file to decide whether to mirror on push.
+
+#### Deployment
+- `git-shell-commands/*` → the git user's `~/git-shell-commands/` directory on the server
+- `git-hooks/*` → wherever `core.hooksPath` points (e.g. `/home/git/scripts/git-hooks`)
+
+Symlink `hook-runner` to the other hook names (`post-update`, `pre-recieve`, `update`) in the hooks directory after deploying.
diff --git a/git-shell-commands/new-project b/git-shell-commands/new-project
index 3d13e41..46c6e93 100755
--- a/git-shell-commands/new-project
+++ b/git-shell-commands/new-project
@@ -5,38 +5,52 @@ set -euo pipefail
# private repos will just exist on the server
#
# USAGE:
-# ssh git@<host> new-project <name> [--public]
+# ssh git@<host> new-project <name> [--public] [--description <desc>]
#
# EXAMPLES:
# ssh git@treybastian.com new-project myrepo
# ssh git@treybastian.com new-project myrepo --public
+# ssh git@treybastian.com new-project myrepo --public --description "my cool repo"
REMOTE_BASE_URL="git@treybastian.com:repos"
usage() {
- echo "Usage: new-project <name> [--public]"
+ echo "Usage: new-project <name> [--public] [--description <desc>]"
echo ""
echo "Examples:"
echo " new-project myrepo"
echo " new-project myrepo --public"
+ echo " new-project myrepo --public --description \"my cool repo\""
exit 1
}
-if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
- echo "Error: expected 1 or 2 arguments, got $#"
+if [ "$#" -lt 1 ]; then
+ echo "Error: expected at least 1 argument, got $#"
usage
fi
PROJECT_NAME="$1"
PUBLIC=false
+DESCRIPTION=""
+shift
-if [ "$#" -eq 2 ]; then
- if [ "$2" != "--public" ]; then
- echo "Error: unknown flag '$2'"
- usage
- fi
- PUBLIC=true
-fi
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --public)
+ PUBLIC=true
+ shift
+ ;;
+ --description)
+ [ "$#" -ge 2 ] || { echo "Error: --description requires a value"; usage; }
+ DESCRIPTION="$2"
+ shift 2
+ ;;
+ *)
+ echo "Error: unknown flag '$1'"
+ usage
+ ;;
+ esac
+done
if [[ "$PROJECT_NAME" != *.git ]]; then
PROJECT_NAME="${PROJECT_NAME}.git"
@@ -49,9 +63,17 @@ fi
git --bare init "${PROJECT_NAME}"
+if [ -n "$DESCRIPTION" ]; then
+ echo "$DESCRIPTION" > "${PROJECT_NAME}/description"
+fi
+
if [ "$PUBLIC" = true ]; then
echo "${REMOTE_BASE_URL}/${PROJECT_NAME}" > "${PROJECT_NAME}/public.conf"
- ssh git@treybastian.com new-repo "repos/${PROJECT_NAME}"
+ REMOTE_ARGS=("repos/${PROJECT_NAME}")
+ if [ -n "$DESCRIPTION" ]; then
+ REMOTE_ARGS+=(--description "$DESCRIPTION")
+ fi
+ ssh git@treybastian.com new-repo "${REMOTE_ARGS[@]}"
echo "public repo: ${REMOTE_BASE_URL}/${PROJECT_NAME}"
fi
diff --git a/git-shell-commands/new-repo b/git-shell-commands/new-repo
index c2c454a..80f7ec9 100755
--- a/git-shell-commands/new-repo
+++ b/git-shell-commands/new-repo
@@ -3,27 +3,45 @@ set -euo pipefail
# this script creates a bare git repository on the git server
#
# USAGE:
-# ssh git@<host> new-repo <name>
+# ssh git@<host> new-repo <name> [--description <desc>]
#
# EXAMPLES:
# ssh git@treybastian.com new-repo myrepo
# ssh git@treybastian.com new-repo myrepo.git
+# ssh git@treybastian.com new-repo myrepo --description "my cool repo"
usage() {
- echo "Usage: new-repo <name>"
+ echo "Usage: new-repo <name> [--description <desc>]"
echo ""
echo "Examples:"
echo " new-repo myrepo"
echo " new-repo myrepo.git"
+ echo " new-repo myrepo --description \"my cool repo\""
exit 1
}
-if [ "$#" -ne 1 ]; then
- echo "Error: expected 1 argument, got $#"
+if [ "$#" -lt 1 ]; then
+ echo "Error: expected at least 1 argument, got $#"
usage
fi
PROJECT_NAME="$1"
+DESCRIPTION=""
+shift
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --description)
+ [ "$#" -ge 2 ] || { echo "Error: --description requires a value"; usage; }
+ DESCRIPTION="$2"
+ shift 2
+ ;;
+ *)
+ echo "Error: unknown flag '$1'"
+ usage
+ ;;
+ esac
+done
if [[ "$PROJECT_NAME" != *.git ]]; then
PROJECT_NAME="${PROJECT_NAME}.git"
@@ -36,6 +54,10 @@ fi
git --bare init "$PROJECT_NAME"
+if [ -n "$DESCRIPTION" ]; then
+ echo "$DESCRIPTION" > "${PROJECT_NAME}/description"
+fi
+
echo "repo created: $PROJECT_NAME"
echo "git url: ${USER}@${HOSTNAME}:${PROJECT_NAME}"
diff --git a/git-shell-commands/set-description b/git-shell-commands/set-description
new file mode 100755
index 0000000..a6e8a50
--- /dev/null
+++ b/git-shell-commands/set-description
@@ -0,0 +1,51 @@
+#!/bin/bash
+set -euo pipefail
+# this script sets the description of an existing bare git repository
+# if public.conf exists the description is also set on the remote repository
+#
+# USAGE:
+# ssh git@<host> set-description <name> <desc>
+#
+# EXAMPLES:
+# ssh git@treybastian.com set-description myrepo "my cool repo"
+# ssh git@treybastian.com set-description myrepo.git "my cool repo"
+
+usage() {
+ echo "Usage: set-description <name> <desc>"
+ echo ""
+ echo "Examples:"
+ echo " set-description myrepo \"my cool repo\""
+ echo " set-description myrepo.git \"my cool repo\""
+ exit 1
+}
+
+if [ "$#" -ne 2 ]; then
+ echo "Error: expected 2 arguments, got $#"
+ usage
+fi
+
+PROJECT_NAME="$1"
+DESCRIPTION="$2"
+
+if [[ "$PROJECT_NAME" != *.git ]]; then
+ PROJECT_NAME="${PROJECT_NAME}.git"
+fi
+
+if [ ! -d "$PROJECT_NAME" ]; then
+ echo "Error: '$PROJECT_NAME' does not exist"
+ exit 1
+fi
+
+echo "$DESCRIPTION" > "${PROJECT_NAME}/description"
+echo "description updated: $PROJECT_NAME"
+
+PUBLIC_FILE="${PROJECT_NAME}/public.conf"
+if [ -f "$PUBLIC_FILE" ]; then
+ REPO_URL=$(cat "$PUBLIC_FILE" | tr -d '\n' | xargs)
+ REMOTE_HOST="${REPO_URL%%:*}"
+ REMOTE_PATH="${REPO_URL##*:}"
+ ssh "$REMOTE_HOST" set-description "$REMOTE_PATH" "$DESCRIPTION"
+ echo "description updated on remote: $REPO_URL"
+fi
+
+# vim: filetype=bash