Newer
Older
openstack-caracal-ipv4 / setup-gitbucket-repo.sh
#!/usr/bin/env bash
# setup-gitbucket-repo.sh
#
# Initialize this repository locally and push it to a self-hosted GitBucket
# instance at git.baldurkeep.com (or any GitBucket-compatible host).
#
# Usage:
#   ./setup-gitbucket-repo.sh                       # interactive prompts
#   ./setup-gitbucket-repo.sh --dry-run             # show what would happen
#
# Environment overrides (skip the prompts when set):
#   GITBUCKET_HOST    e.g. git.baldurkeep.com
#   GITBUCKET_USER    GitBucket username
#   GITBUCKET_OWNER   Repo owner (user or group). Defaults to GITBUCKET_USER.
#   GITBUCKET_REPO    Repo name. Default: vr0-dc0-caracal
#   GITBUCKET_TOKEN   API token for creating the repo (if it does not exist yet)
#   GIT_USER_NAME     Local git author name (e.g. "Jesse Austin")
#   GIT_USER_EMAIL    Local git author email (e.g. jesse.austin@neumatrix.com)
#   GIT_REMOTE_PROTO  ssh|https — default https
#   GIT_BRANCH        default branch name — default main
#
# Idempotency:
#   - Detects existing .git directory and skips `git init`
#   - Detects existing remote 'origin' and adjusts URL if it differs (with confirmation)
#   - Will not push if there are no commits (nothing to push)
#
# What this script does NOT do:
#   - Store credentials. You will be prompted by git/SSH for auth at push time.
#   - Create groups/organizations on GitBucket — the owner must exist already.
#   - Force-push or rewrite history.

set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true
IFS=$'\n\t'

# ----- Helpers --------------------------------------------------------------

err()  { printf '\033[1;31mERROR\033[0m %s\n' "$*" >&2; }
warn() { printf '\033[1;33mWARN\033[0m  %s\n' "$*" >&2; }
info() { printf '\033[1;36mINFO\033[0m  %s\n' "$*"; }
ok()   { printf '\033[1;32mOK\033[0m    %s\n' "$*"; }

die() { err "$*"; exit 1; }

prompt() {
  # prompt VAR "Question text" "default"
  local __var=$1 __q=$2 __default=${3:-}
  local __reply
  if [[ -n "${!__var:-}" ]]; then
    # already set via env; skip
    return 0
  fi
  if [[ -n "$__default" ]]; then
    read -r -p "$__q [$__default]: " __reply || true
    __reply=${__reply:-$__default}
  else
    read -r -p "$__q: " __reply || true
  fi
  if [[ -z "$__reply" ]]; then
    die "Empty response for $__var"
  fi
  printf -v "$__var" '%s' "$__reply"
}

prompt_secret() {
  local __var=$1 __q=$2
  local __reply
  if [[ -n "${!__var:-}" ]]; then
    return 0
  fi
  read -r -s -p "$__q: " __reply || true
  echo
  printf -v "$__var" '%s' "$__reply"
}

confirm() {
  local __q=$1
  local __reply
  read -r -p "$__q [y/N]: " __reply || true
  [[ "${__reply,,}" == "y" || "${__reply,,}" == "yes" ]]
}

# ----- Argument parsing -----------------------------------------------------

DRY_RUN=0
for arg in "$@"; do
  case "$arg" in
    --dry-run) DRY_RUN=1 ;;
    -h|--help)
      sed -n '1,40p' "$0" | grep -E '^#'
      exit 0
      ;;
    *) die "Unknown argument: $arg" ;;
  esac
done

run() {
  # Wrapper that echoes and (in dry-run) skips execution.
  # Subshell with IFS=' ' so $* joins args with a space for display only.
  (IFS=' '; printf '\033[1;90m+ %s\033[0m\n' "$*")
  if [[ "$DRY_RUN" -eq 0 ]]; then
    "$@"
  fi
}

# ----- Repo root sanity -----------------------------------------------------

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
cd "$SCRIPT_DIR"

# Sanity check that we're in the expected repo structure.
for required in README.md bundle.yaml runbooks docs netbox; do
  [[ -e "$required" ]] || die "Required path '$required' missing — are you in the repo root?"
done

ok "Running from repo root: $SCRIPT_DIR"

# ----- Tool checks ----------------------------------------------------------

command -v git  >/dev/null || die "git not installed"
command -v curl >/dev/null || warn "curl not installed — repo auto-create on GitBucket disabled"

# ----- Gather configuration -------------------------------------------------

prompt GITBUCKET_HOST   "GitBucket host"               "git.baldurkeep.com"
prompt GITBUCKET_USER   "GitBucket username"
prompt GITBUCKET_OWNER  "Repo owner (user or group)"   "$GITBUCKET_USER"
prompt GITBUCKET_REPO   "Repo name"                     "openstack-caracal-ipv4"
prompt GIT_USER_NAME    "Git author name"
prompt GIT_USER_EMAIL   "Git author email"
prompt GIT_REMOTE_PROTO "Remote protocol (ssh|https)"  "https"
prompt GIT_BRANCH       "Default branch"               "main"

case "$GIT_REMOTE_PROTO" in
  ssh)
    REMOTE_URL="git@${GITBUCKET_HOST}:${GITBUCKET_OWNER}/${GITBUCKET_REPO}.git"
    ;;
  https)
    REMOTE_URL="https://${GITBUCKET_HOST}/git/${GITBUCKET_OWNER}/${GITBUCKET_REPO}.git"
    ;;
  *)
    die "GIT_REMOTE_PROTO must be 'ssh' or 'https' (got: $GIT_REMOTE_PROTO)"
    ;;
esac

cat <<EOF

----- Configuration --------------------------------------------------------
GitBucket host  : $GITBUCKET_HOST
GitBucket user  : $GITBUCKET_USER
Repo owner      : $GITBUCKET_OWNER
Repo name       : $GITBUCKET_REPO
Git author      : $GIT_USER_NAME <$GIT_USER_EMAIL>
Remote URL      : $REMOTE_URL
Default branch  : $GIT_BRANCH
Dry-run         : $([[ $DRY_RUN -eq 1 ]] && echo YES || echo no)
----------------------------------------------------------------------------
EOF

confirm "Proceed?" || die "Aborted by user"

# ----- Create repo on GitBucket via API (if token provided) -----------------

GITBUCKET_API_BASE="https://${GITBUCKET_HOST}/api/v3"
REPO_API_URL="${GITBUCKET_API_BASE}/repos/${GITBUCKET_OWNER}/${GITBUCKET_REPO}"

create_repo_via_api() {
  if [[ -z "${GITBUCKET_TOKEN:-}" ]]; then
    info "GITBUCKET_TOKEN not set — skipping API repo creation"
    info "If the repo does not exist on GitBucket, create it manually now:"
    info "  https://${GITBUCKET_HOST}/${GITBUCKET_OWNER}"
    info "  → New repository → name: ${GITBUCKET_REPO}, do NOT initialize with README"
    return 0
  fi

  if ! command -v curl >/dev/null; then
    warn "curl missing; cannot call API"
    return 0
  fi

  info "Checking if repo already exists on GitBucket..."
  local http_code
  http_code=$(curl -sS -o /dev/null -w '%{http_code}' \
    -H "Authorization: token ${GITBUCKET_TOKEN}" \
    "$REPO_API_URL" || true)

  case "$http_code" in
    200)
      ok "Repo ${GITBUCKET_OWNER}/${GITBUCKET_REPO} already exists on GitBucket"
      return 0
      ;;
    404)
      info "Repo does not exist — creating via API"
      ;;
    *)
      warn "Unexpected API response code: $http_code (continuing)"
      return 0
      ;;
  esac

  local create_url
  if [[ "$GITBUCKET_OWNER" == "$GITBUCKET_USER" ]]; then
    # User-owned repo
    create_url="${GITBUCKET_API_BASE}/user/repos"
  else
    # Group-owned repo
    create_url="${GITBUCKET_API_BASE}/orgs/${GITBUCKET_OWNER}/repos"
  fi

  local payload
  payload=$(printf '{"name":"%s","description":"%s","private":true,"auto_init":false}' \
    "$GITBUCKET_REPO" \
    "Charmed OpenStack Caracal 2024.1 — IPv4-only testcloud deployment (VR0 DC0 v1). IPv6/dual-stack tracked separately as v2.")

  if [[ "$DRY_RUN" -eq 1 ]]; then
    info "[dry-run] would POST to $create_url with payload: $payload"
    return 0
  fi

  http_code=$(curl -sS -o /tmp/gitbucket-create.json -w '%{http_code}' \
    -X POST \
    -H "Authorization: token ${GITBUCKET_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$payload" \
    "$create_url" || true)

  case "$http_code" in
    200|201)
      ok "Repo created: ${GITBUCKET_OWNER}/${GITBUCKET_REPO}"
      ;;
    *)
      err "Repo creation failed (HTTP $http_code). Response:"
      cat /tmp/gitbucket-create.json >&2 || true
      die "Aborting before git operations"
      ;;
  esac
}

create_repo_via_api

# ----- Git init -------------------------------------------------------------

if [[ -d .git ]]; then
  info ".git exists — skipping git init"
else
  run git init -b "$GIT_BRANCH"
fi

run git config user.name  "$GIT_USER_NAME"
run git config user.email "$GIT_USER_EMAIL"

# ----- Remote setup ---------------------------------------------------------

if git remote get-url origin >/dev/null 2>&1; then
  EXISTING_URL=$(git remote get-url origin)
  if [[ "$EXISTING_URL" != "$REMOTE_URL" ]]; then
    warn "Existing 'origin' remote URL: $EXISTING_URL"
    warn "Desired URL:                  $REMOTE_URL"
    if confirm "Update remote URL?"; then
      run git remote set-url origin "$REMOTE_URL"
    fi
  else
    ok "Remote 'origin' already set correctly"
  fi
else
  run git remote add origin "$REMOTE_URL"
fi

# ----- Stage + commit -------------------------------------------------------

run git add .

if [[ "$DRY_RUN" -eq 0 ]]; then
  if git diff --staged --quiet; then
    info "No staged changes — nothing to commit"
  else
    if [[ -z "$(git log --oneline -1 2>/dev/null || true)" ]]; then
      MSG="Initial commit — VR0 DC0 Omega Cloud Caracal repo scaffolding"
    else
      MSG="Update repo content"
    fi
    run git commit -m "$MSG"
  fi
else
  info "[dry-run] would commit (skipping)"
fi

# ----- Push ----------------------------------------------------------------

if [[ "$DRY_RUN" -eq 1 ]]; then
  info "[dry-run] would push to origin/$GIT_BRANCH"
  exit 0
fi

if [[ -z "$(git log --oneline -1 2>/dev/null || true)" ]]; then
  info "No commits to push"
  exit 0
fi

info "Pushing to origin/$GIT_BRANCH (you may be prompted for credentials)..."
run git push -u origin "$GIT_BRANCH"

ok "Push complete"
ok "Repository ready at: https://${GITBUCKET_HOST}/${GITBUCKET_OWNER}/${GITBUCKET_REPO}"