#!/bin/bash set -euo pipefail IFS=$'\n\t' # ╭─────────────────────────────────────╮ # │ gitfield-local │ # ╰─────────────────────────────────────╯ # Manages a local bare Git repository as a sacred push target for redundancy. # Creates and maintains a bare repository in ~/git-local-repos/git-sigil.git # Generates rich metadata in .gitfield/local.sigil.md and updates .gitfield/push_log.json # Commands: configure, status, push # ╭─────────────────────────────────────╮ # │ CONFIGURATION │ # ╰─────────────────────────────────────╯ GIT_REMOTE_NAME="local" REPO_NAME=$(basename "$(pwd)") || REPO_NAME="Unknown" DEFAULT_NAME="Mark Randall Havens" DEFAULT_EMAIL="mark.r.havens@gmail.com" LOCAL_REPO="$HOME/git-local-repos/git-sigil.git" REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || { echo -e "\e[1;31m[ERROR]\e[0m Not inside a Git repository" >&2; exit 1; } MARKDOWN_FILE="$REPO_PATH/.gitfield/local.sigil.md" PUSH_LOG="$REPO_PATH/.gitfield/push_log.json" SCRIPT_VERSION="1.0" # ╭─────────────────────────────────────╮ # │ LOGGING UTILS │ # ╰─────────────────────────────────────╯ info() { echo -e "\e[1;34m[INFO]\e[0m $*"; } warn() { echo -e "\e[1;33m[WARN]\e[0m $*"; } error() { echo -e "\e[1;31m[ERROR]\e[0m $*" >&2; exit 1; } # ╭─────────────────────────────────────╮ # │ TOOLCHAIN SETUP │ # ╰─────────────────────────────────────╯ info "Checking for required tools..." if ! command -v git &>/dev/null; then info "Installing Git..." sudo apt update -qq 2>/dev/null || warn "apt update failed, continuing..." sudo apt install -y git 2>/dev/null || error "Git install failed" fi if ! command -v jq &>/dev/null; then info "Installing jq for JSON processing..." sudo apt install -y jq 2>/dev/null || warn "jq install failed, push_log.json updates may fail" fi # ╭─────────────────────────────────────╮ # │ AUTH + IDENTITY │ # ╰─────────────────────────────────────╯ info "Setting Git identity..." git config --global user.name "$DEFAULT_NAME" 2>/dev/null || warn "Failed to set git user name" git config --global user.email "$DEFAULT_EMAIL" 2>/dev/null || warn "Failed to set git user email" info "Git identity set to: $DEFAULT_NAME <$DEFAULT_EMAIL>" # ╭─────────────────────────────────────╮ # │ GIT INIT (IF NEEDED) │ # ╰─────────────────────────────────────╯ if [ ! -d "$REPO_PATH/.git" ]; then info "Initializing Git repository..." git -C "$REPO_PATH" init 2>/dev/null || warn "Git init failed, continuing..." git -C "$REPO_PATH" add . 2>/dev/null || warn "Nothing to add" git -C "$REPO_PATH" commit -m "Initial commit" 2>/dev/null || warn "Nothing to commit" fi # ╭─────────────────────────────────────╮ # │ LOCAL REPO CONFIGURATION │ # ╰─────────────────────────────────────╯ configure() { info "Configuring local bare repository..." # Create and verify local bare repository if [[ ! -d "$LOCAL_REPO" ]]; then info "Creating local bare repository: $LOCAL_REPO" mkdir -p "$LOCAL_REPO" || error "Failed to create $LOCAL_REPO" git -C "$LOCAL_REPO" init --bare 2>/dev/null || error "Failed to initialize bare repository" fi if ! git -C "$LOCAL_REPO" rev-parse --is-bare-repository >/dev/null 2>&1; then warn "Local repository $LOCAL_REPO is not a valid bare repository. Reinitializing..." rm -rf "$LOCAL_REPO" || error "Failed to remove invalid $LOCAL_REPO" mkdir -p "$LOCAL_REPO" || error "Failed to create $LOCAL_REPO" git -C "$LOCAL_REPO" init --bare 2>/dev/null || error "Failed to reinitialize bare repository" fi # Set permissions chmod -R u+rwX "$LOCAL_REPO" 2>/dev/null || warn "Failed to set permissions on $LOCAL_REPO" # Configure local remote REMOTE_URL="file://$LOCAL_REPO" if ! git -C "$REPO_PATH" remote get-url "$GIT_REMOTE_NAME" &>/dev/null; then info "Adding local remote: $REMOTE_URL" git -C "$REPO_PATH" remote add "$GIT_REMOTE_NAME" "$REMOTE_URL" 2>/dev/null || error "Failed to add local remote" else current_url=$(git -C "$REPO_PATH" remote get-url "$GIT_REMOTE_NAME") if [[ "$current_url" != "$REMOTE_URL" ]]; then warn "Local remote URL is incorrect ($current_url). Updating to $REMOTE_URL" git -C "$REPO_PATH" remote set-url "$GIT_REMOTE_NAME" "$REMOTE_URL" 2>/dev/null || error "Failed to update local remote URL" fi fi # Set upstream for current branch DEFAULT_BRANCH=$(git -C "$REPO_PATH" symbolic-ref --short HEAD 2>/dev/null || echo "main") if ! git -C "$REPO_PATH" rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1; then info "Setting upstream for $DEFAULT_BRANCH to $GIT_REMOTE_NAME/$DEFAULT_BRANCH" git -C "$REPO_PATH" push --set-upstream "$GIT_REMOTE_NAME" "$DEFAULT_BRANCH" 2>/dev/null || error "Failed to set upstream" fi info "Local bare repository configured successfully." } # ╭─────────────────────────────────────╮ # │ STATUS CHECK │ # ╰─────────────────────────────────────╯ status() { info "Checking local repository status..." # Verify local bare repository if [[ -d "$LOCAL_REPO" && $(git -C "$LOCAL_REPO" rev-parse --is-bare-repository 2>/dev/null) == "true" ]]; then info "Local bare repository: $LOCAL_REPO" latest_commit=$(git -C "$LOCAL_REPO" log -1 --format="%h %s (%cr)" 2>/dev/null || echo "No commits") info "Latest commit: $latest_commit" else warn "Local bare repository not found or invalid: $LOCAL_REPO" fi # Check remote configuration if git -C "$REPO_PATH" remote | grep -q "^$GIT_REMOTE_NAME$"; then remote_url=$(git -C "$REPO_PATH" remote get-url "$GIT_REMOTE_NAME") info "Local remote URL: $remote_url" else warn "Local remote not configured." fi # Check working repository status info "Working repository: $REPO_PATH" git -C "$REPO_PATH" status --short 2>/dev/null || warn "Failed to get repository status" } # ╭─────────────────────────────────────╮ # │ GIT METADATA SNAPSHOT │ # ╰─────────────────────────────────────╯ generate_metadata() { info "Generating metadata: $MARKDOWN_FILE" mkdir -p "$(dirname "$MARKDOWN_FILE")" 2>/dev/null || warn "Failed to create .gitfield directory" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "Unknown") DEFAULT_BRANCH=$(git -C "$REPO_PATH" symbolic-ref --short HEAD 2>/dev/null || echo "Unknown") LATEST_SHA=$(git -C "$REPO_PATH" rev-parse HEAD 2>/dev/null || echo "Unknown") LAST_COMMIT_MSG=$(git -C "$REPO_PATH" log -1 --pretty=format:"%s" 2>/dev/null || echo "Unknown") LAST_COMMIT_DATE=$(git -C "$REPO_PATH" log -1 --pretty=format:"%ad" 2>/dev/null || echo "Unknown") LAST_COMMIT_AUTHOR=$(git -C "$REPO_PATH" log -1 --pretty=format:"%an <%ae>" 2>/dev/null || echo "Unknown") TOTAL_COMMITS=$(git -C "$REPO_PATH" rev-list --count HEAD 2>/dev/null || echo "Unknown") TRACKED_FILES=$(git -C "$REPO_PATH" ls-files 2>/dev/null | wc -l 2>/dev/null || echo "Unknown") UNCOMMITTED=$(if ! git -C "$REPO_PATH" diff --quiet 2>/dev/null || ! git -C "$REPO_PATH" diff --cached --quiet 2>/dev/null; then echo "Yes"; else echo "No"; fi) LATEST_TAG=$(git -C "$REPO_PATH" describe --tags --abbrev=0 2>/dev/null || echo "None") HOSTNAME=$(hostname 2>/dev/null || echo "Unknown") CURRENT_USER=$(whoami 2>/dev/null || echo "Unknown") TIMEZONE=$(date +%Z 2>/dev/null || echo "Unknown") OS_NAME=$(uname -s 2>/dev/null || echo "Unknown") KERNEL_VERSION=$(uname -r 2>/dev/null || echo "Unknown") ARCHITECTURE=$(uname -m 2>/dev/null || echo "Unknown") OS_PRETTY_NAME=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"' || echo "Unknown") DOCKER_CHECK=$(grep -qE '/docker|/lxc' /proc/1/cgroup 2>/dev/null && echo "Yes" || echo "No") WSL_CHECK=$(grep -qi microsoft /proc/version 2>/dev/null && echo "Yes" || echo "No") VM_CHECK=$(systemd-detect-virt 2>/dev/null || echo "Unknown") UPTIME=$(uptime -p 2>/dev/null || echo "Unknown") MAC_ADDR=$(ip link 2>/dev/null | awk '/ether/ {print $2}' | head -n 1 2>/dev/null || echo "Unknown") LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}' 2>/dev/null || echo "Unknown") CPU_MODEL=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ //' 2>/dev/null || echo "Unknown") RAM_GB=$(awk '/MemTotal/ {printf "%.2f", $2/1024/1024}' /proc/meminfo 2>/dev/null || echo "Unknown") WEB_LINK="file://$LOCAL_REPO" cat > "$MARKDOWN_FILE" </dev/null 2>&1; then jq --arg ts "$TIMESTAMP" \ --arg branch "$DEFAULT_BRANCH" \ --arg commit "$LATEST_SHA" \ --arg msg "$LAST_COMMIT_MSG" \ '.local += [{"timestamp": $ts, "branch": $branch, "commit": $commit, "message": $msg}]' \ "$PUSH_LOG" > "$PUSH_LOG.tmp" && mv "$PUSH_LOG.tmp" "$PUSH_LOG" 2>/dev/null || warn "Failed to update $PUSH_LOG" info "Updated push log: $PUSH_LOG" else warn "jq not installed. Skipping $PUSH_LOG update." fi } # ╭─────────────────────────────────────╮ # │ PUSH TO LOCAL │ # ╰─────────────────────────────────────╯ push() { info "Pushing to local bare repository..." # Ensure remote is configured if ! git -C "$REPO_PATH" remote | grep -q "^$GIT_REMOTE_NAME$"; then warn "Local remote not configured. Running configure..." configure fi # Generate metadata generate_metadata # Commit metadata set +e info "Committing metadata file..." git -C "$REPO_PATH" add "$MARKDOWN_FILE" 2>/dev/null || warn "Failed to add metadata file" git -C "$REPO_PATH" commit -m "Local metadata link commit at $TIMESTAMP — $WEB_LINK" 2>/dev/null || warn "No changes to commit" set -e # Push to local remote DEFAULT_BRANCH=$(git -C "$REPO_PATH" symbolic-ref --short HEAD 2>/dev/null || echo "main") set +e info "Pushing to $GIT_REMOTE_NAME/$DEFAULT_BRANCH..." if ! git -C "$REPO_PATH" push "$GIT_REMOTE_NAME" "$DEFAULT_BRANCH" 2>/dev/null; then warn "Push failed. Attempting to recover..." configure git -C "$REPO_PATH" push "$GIT_REMOTE_NAME" "$DEFAULT_BRANCH" 2>/dev/null || error "Failed to push to $GIT_REMOTE_NAME/$DEFAULT_BRANCH after recovery" fi set -e info "✅ Local push complete." echo -e "\n🔗 Local repository: $WEB_LINK\n" } # ╭─────────────────────────────────────╮ # │ MAIN EXECUTION │ # ╰─────────────────────────────────────╯ main() { case "${1:-push}" in configure) configure ;; status) status ;; push) push ;; *) error "Usage: $0 {configure|status|push}" ;; esac } main "$@"