#!/bin/bash set -euo pipefail IFS=$'\n\t' # ╭───────────────────────────────╮ # │ Config & Paths │ # ╰───────────────────────────────╯ PROJECT_NAME=$(basename "$(pwd)") DEFAULT_NAME="Mark Randall Havens" DEFAULT_EMAIL="mark.r.havens@gmail.com" SCRIPT_VERSION="1.0" RAD_HOME="$HOME/.radicle" RAD_BIN="$RAD_HOME/bin/rad" RAD_PATH_LINE='export PATH="$HOME/.radicle/bin:$PATH"' PROFILE_FILE="$HOME/.bashrc" PUSH_STATE_FILE="$(git rev-parse --show-toplevel)/.gitfield/.radicle-push-state" MARKDOWN_FILE="$(git rev-parse --show-toplevel)/.gitfield/radicle.sigil.md" mkdir -p "$(dirname "$MARKDOWN_FILE")" PUBLIC_GATEWAY="https://app.radicle.xyz/nodes/ash.radicle.garden" # ╭───────────────────────────────╮ # │ 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; } # ╭───────────────────────────────╮ # │ Git + Tools Precheck │ # ╰───────────────────────────────╯ info "Checking Git..." command -v git >/dev/null || { info "Installing Git..." sudo apt update && sudo apt install -y git || error "Failed to install Git" } info "Git version: $(git --version)" NAME=$(git config --global user.name || true) EMAIL=$(git config --global user.email || true) [[ -z "$NAME" || -z "$EMAIL" ]] && { info "Setting Git identity..." git config --global user.name "$DEFAULT_NAME" git config --global user.email "$DEFAULT_EMAIL" } info "Git identity: $(git config --global user.name) <$(git config --global user.email)>" # ╭───────────────────────────────╮ # │ Radicle CLI Setup │ # ╰───────────────────────────────╯ if [ ! -x "$RAD_BIN" ]; then info "Installing Radicle CLI..." sudo apt install -y curl jq unzip || error "Missing dependencies" curl -sSf https://radicle.xyz/install | sh || error "Radicle install failed" fi export PATH="$HOME/.radicle/bin:$PATH" if ! grep -Fxq "$RAD_PATH_LINE" "$PROFILE_FILE"; then echo "$RAD_PATH_LINE" >> "$PROFILE_FILE" info "→ Added PATH to $PROFILE_FILE" warn "→ Run 'source $PROFILE_FILE' for persistent CLI access" fi command -v rad >/dev/null || error "Radicle CLI unavailable. Restart terminal or check PATH." info "Radicle CLI ready: $(rad --version)" # ╭───────────────────────────────╮ # │ Radicle Identity Check │ # ╰───────────────────────────────╯ info "Checking Radicle identity..." RAD_SELF_OUTPUT=$(rad self 2>&1 || true) if ! echo "$RAD_SELF_OUTPUT" | grep -q "DID"; then info "Creating new Radicle identity..." AUTH_OUTPUT=$(rad auth 2>&1) || error "Identity creation failed" info "$AUTH_OUTPUT" else info "Radicle identity already exists." fi # ╭───────────────────────────────╮ # │ Start Rad Node │ # ╰───────────────────────────────╯ if ! pgrep -f "rad node" >/dev/null; then info "Starting Radicle node..." rad node start || error "Failed to start Radicle node" else info "✓ Node is already running." fi info "Waiting for Radicle node to be ready..." for i in {1..30}; do if rad node status >/dev/null 2>&1; then info "Radicle node is ready." break fi sleep 1 done rad node status >/dev/null 2>&1 || error "Radicle node failed to start after 30s." # ╭───────────────────────────────╮ # │ Git Repo Initialization │ # ╰───────────────────────────────╯ if [ ! -d .git ]; then info "Initializing Git repository..." git init git add . || warn "Nothing to add" git commit -m "Initial commit" || warn "Nothing to commit" else info "Git repo already initialized." fi # ╭───────────────────────────────╮ # │ Radicle Project Registration│ # ╰───────────────────────────────╯ if ! git remote | grep -q rad; then info "Registering Radicle project '$PROJECT_NAME'..." rad init --name "$PROJECT_NAME" --description "Radicle sovereign repo for $PROJECT_NAME" || error "Failed to initialize Radicle project" else info "Project '$PROJECT_NAME' already registered with Radicle." fi # ╭───────────────────────────────╮ # │ Extract Metadata │ # ╰───────────────────────────────╯ info "Extracting Radicle metadata..." # Extract Project ID PROJECT_ID=$(rad inspect | grep -o 'rad:[a-zA-Z0-9]\+' | cut -d':' -f2) info "→ Project ID from rad inspect: $PROJECT_ID" # Extract Peer ID PEER_ID="" # Try rad self first if [[ -n "$RAD_SELF_OUTPUT" ]]; then PEER_ID=$(echo "$RAD_SELF_OUTPUT" | grep -o 'z6M[a-zA-Z0-9]\+' || true) info "→ Peer ID from rad self: $PEER_ID" fi # If rad self didn't provide it, try AUTH_OUTPUT if it exists if [[ -z "$PEER_ID" && -n "${AUTH_OUTPUT:-}" ]]; then PEER_ID=$(echo "$AUTH_OUTPUT" | grep -o 'z6M[a-zA-Z0-9]\+' || true) info "→ Peer ID from rad auth: $PEER_ID" fi # If still empty, try rad node status as a last resort if [[ -z "$PEER_ID" ]]; then NODE_STATUS=$(rad node status 2>&1) PEER_ID=$(echo "$NODE_STATUS" | grep -o 'z6M[a-zA-Z0-9]\+' || true) info "→ Peer ID from rad node status: $PEER_ID" fi # Cross-check with Git remote RAD_REMOTE=$(git remote -v | grep rad | head -n1 | awk '{print $2}' || true) if [[ -n "$RAD_REMOTE" ]]; then REMOTE_PROJECT_ID=$(echo "$RAD_REMOTE" | cut -d'/' -f3) REMOTE_PEER_ID=$(echo "$RAD_REMOTE" | cut -d'/' -f4) info "→ Project ID from Git remote: $REMOTE_PROJECT_ID" info "→ Peer ID from Git remote: $REMOTE_PEER_ID" [[ "$PROJECT_ID" != "$REMOTE_PROJECT_ID" ]] && warn "Project ID mismatch: rad inspect ($PROJECT_ID) vs remote ($REMOTE_PROJECT_ID)" if [[ -z "$PEER_ID" && -n "$REMOTE_PEER_ID" ]]; then PEER_ID="$REMOTE_PEER_ID" info "→ Using Peer ID from Git remote as fallback: $PEER_ID" elif [[ -n "$REMOTE_PEER_ID" && "$PEER_ID" != "$REMOTE_PEER_ID" ]]; then warn "Peer ID mismatch: rad self ($PEER_ID) vs remote ($REMOTE_PEER_ID)" PEER_ID="$REMOTE_PEER_ID" # Prefer the remote Peer ID as it's part of the actual repo URL info "→ Using Peer ID from Git remote: $PEER_ID" fi PROJECT_ID="$REMOTE_PROJECT_ID" # Prefer the remote Project ID as it's the actual repo identifier fi # Final validation if [[ -z "$PROJECT_ID" || -z "$PEER_ID" ]]; then error "Failed to determine Project ID ($PROJECT_ID) or Peer ID ($PEER_ID). Please check Radicle configuration." fi REPO_URN="rad://$PROJECT_ID" info "✓ Metadata extracted successfully: Project ID: $PROJECT_ID, Peer ID: $PEER_ID" # ╭───────────────────────────────╮ # │ Push Current Commit Logic │ # ╰───────────────────────────────╯ CURRENT_BRANCH=$(git symbolic-ref --short HEAD) CURRENT_COMMIT=$(git rev-parse HEAD) LAST_PUSHED_COMMIT=$(cat "$PUSH_STATE_FILE" 2>/dev/null || echo "none") if [[ "$CURRENT_COMMIT" == "$LAST_PUSHED_COMMIT" ]]; then info "✓ Already pushed commit: $CURRENT_COMMIT" else info "Pushing commit '$CURRENT_COMMIT' on branch '$CURRENT_BRANCH'..." if git push rad "$CURRENT_BRANCH"; then echo "$CURRENT_COMMIT" > "$PUSH_STATE_FILE" info "✓ Pushed to Radicle successfully" else warn "Push failed — check 'rad sync status'" error "Push failed, cannot proceed." fi fi # ╭─────────────────────────────────────╮ # │ Git Metadata Snapshot │ # ╰─────────────────────────────────────╯ TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S')" DEFAULT_BRANCH="$CURRENT_BRANCH" REPO_PATH="$(git rev-parse --show-toplevel)" LATEST_SHA="$CURRENT_COMMIT" LAST_COMMIT_MSG=$(git log -1 --pretty=format:"%s") LAST_COMMIT_DATE=$(git log -1 --pretty=format:"%ad") LAST_COMMIT_AUTHOR=$(git log -1 --pretty=format:"%an <%ae>") TOTAL_COMMITS=$(git rev-list --count HEAD) TRACKED_FILES=$(git ls-files | wc -l) UNCOMMITTED=$(git diff --quiet && git diff --cached --quiet && echo "No" || echo "Yes") LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "None") HOSTNAME=$(hostname) CURRENT_USER=$(whoami) TIMEZONE=$(date +%Z) # ╭─────────────────────────────────────╮ # │ Hardware + OS Fingerprint Block │ # ╰─────────────────────────────────────╯ OS_NAME=$(uname -s) KERNEL_VERSION=$(uname -r) ARCHITECTURE=$(uname -m) 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 | awk '/ether/ {print $2}' | head -n 1 || echo "Unknown") LOCAL_IP=$(hostname -I | awk '{print $1}' || echo "Unknown") CPU_MODEL=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2 | sed 's/^ //' || echo "Unknown") RAM_GB=$(awk '/MemTotal/ {printf "%.2f", $2/1024/1024}' /proc/meminfo || echo "Unknown") # ╭─────────────────────────────────────╮ # │ Write Rich Markdown Artifact │ # ╰─────────────────────────────────────╯ PUBLIC_GATEWAY_URL="$PUBLIC_GATEWAY/rad:$PROJECT_ID/tree/$LATEST_SHA" cat > "$MARKDOWN_FILE" </dev/null || ! git ls-files "$MARKDOWN_FILE" --error-unmatch >/dev/null 2>&1; then git add "$MARKDOWN_FILE" git commit -m "Update Radicle metadata at $TIMESTAMP — $PUBLIC_GATEWAY_URL" || warn "No changes to commit for $MARKDOWN_FILE" if git push rad "$CURRENT_BRANCH"; then echo "$CURRENT_COMMIT" > "$PUSH_STATE_FILE" info "✓ Pushed metadata update to Radicle" else warn "Metadata push failed — check 'rad sync status'" fi else info "No changes to $MARKDOWN_FILE; skipping commit." fi