#!/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}"