#!/usr/bin/env bash # tools/create_railiance_repo.sh # # Railiance — sanitized bootstrap script for creating a Gitea repository. # # PURPOSE: # Creates a repo (default: railiance-cluster) in your Gitea (user or org), # scaffolds a minimal layout, commits, and pushes the initial content. # # SAFETY NOTES: # - This script NEVER writes your credentials file and NEVER prints your token. # - It requires ~/.railiance_gitea.conf to exist (see example below). # - It relies on your local Git credential helper or SSH for the initial push. # (Recommended: `git config --global credential.helper cache` or `store`) # # REQUIRED CONFIG (~/.railiance_gitea.conf): # GITEA_URL="https://git.example.com" # no trailing slash; include subpath if any # GITEA_USER="your-username" # your Gitea login (for user-owned repos) # GITEA_ORG="" # optional; set to org name to create under org # GITEA_TOKEN="xxxxxxxxxxxxxxxx" # personal access token with read:user + write:repository # # USAGE: # ./tools/create_railiance_repo.sh # ./tools/create_railiance_repo.sh --repo my-repo --desc "My desc" --public # ./tools/create_railiance_repo.sh --org coulomb # override org from config # # FLAGS: # --repo : repository name (default: railiance-cluster) # --desc : description (default provided) # --org : create under this organization (overrides GITEA_ORG) # --public : create as public repo (default: private) # --no-scaffold : skip creating local scaffold (useful if repo already has content) # -h|--help : show help # # EXITS non-zero on failure. set -euo pipefail # ----------------------------- parse args ------------------------------------ REPO_NAME="railiance-cluster" REPO_DESC="Bootstrap repo for Railiance IaC. Recreates Coulomb infra from scratch." IS_PRIVATE=true OVERRIDE_ORG="" DO_SCAFFOLD=true while [[ $# -gt 0 ]]; do case "$1" in --repo) REPO_NAME="${2:?}"; shift 2 ;; --desc) REPO_DESC="${2:?}"; shift 2 ;; --org) OVERRIDE_ORG="${2:?}"; shift 2 ;; --public) IS_PRIVATE=false; shift ;; --no-scaffold)DO_SCAFFOLD=false; shift ;; -h|--help) sed -n '1,120p' "$0" | sed -n '1,/^set -euo pipefail/p' exit 0 ;; *) echo "Unknown arg: $1" >&2; exit 2 ;; esac done # ----------------------------- load config ----------------------------------- CONFIG_FILE="${HOME}/.railiance_gitea.conf" if [[ ! -f "${CONFIG_FILE}" ]]; then echo "ERROR: Missing config ${CONFIG_FILE}" echo "Create it with:" cat <<'EOT' GITEA_URL="https://git.example.com" GITEA_USER="your-username" GITEA_ORG="" # or set to your org GITEA_TOKEN="xxxxxxxxxxxxxxxx" EOT exit 1 fi # shellcheck disable=SC1090 source "${CONFIG_FILE}" : "${GITEA_URL:?Set GITEA_URL in ${CONFIG_FILE}}" : "${GITEA_USER:?Set GITEA_USER in ${CONFIG_FILE}}" : "${GITEA_TOKEN:?Set GITEA_TOKEN in ${CONFIG_FILE}}" : "${GITEA_ORG:=}" if [[ -n "${OVERRIDE_ORG}" ]]; then GITEA_ORG="${OVERRIDE_ORG}" fi # ----------------------------- helpers --------------------------------------- need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1" >&2; exit 1; }; } need_cmd curl need_cmd git json_escape() { # minimal escape for description text printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' } curl_code() { # prints only HTTP code; follows redirects; uses token header curl -sS -o /dev/null -w "%{http_code}" -L \ -H "Authorization: token ${GITEA_TOKEN}" "$@" } repo_exists() { local owner="$1" repo="$2" code code=$(curl_code "${GITEA_URL}/api/v1/repos/${owner}/${repo}") [[ "${code}" == "200" ]] } create_repo_user() { local repo="$1" private="$2" desc_json="$3" curl_code -X POST "${GITEA_URL}/api/v1/user/repos" \ -H "Content-Type: application/json" \ -d "{\"name\":\"${repo}\",\"private\":${private},\"description\":${desc_json}}" } create_repo_org() { local org="$1" repo="$2" private="$3" desc_json="$4" curl_code -X POST "${GITEA_URL}/api/v1/orgs/${org}/repos" \ -H "Content-Type: application/json" \ -d "{\"name\":\"${repo}\",\"private\":${private},\"description\":${desc_json}}" } scaffold_repo() { local dir="$1" mkdir -p "${dir}"/{ansible,helm,k8s,tests,docs} cat > "${dir}/README.md" < "${dir}/.gitignore" <<'EOF' # OS .DS_Store # Python __pycache__/ *.pyc # Ansible artifacts *.retry # Helm build charts/*/charts/ charts/*/tmp/ # Local secrets (do not commit) secrets/ *.enc EOF cat > "${dir}/ansible/bootstrap.yml" <<'EOF' --- - name: Railiance host bootstrap (example) hosts: all become: true tasks: - name: Ensure base packages apt: name: - curl - git - jq update_cache: yes state: present EOF cat > "${dir}/tests/smoke_kube.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail echo "[placeholder] Add kube API checks here (kubectl get nodes/ns)" EOF chmod +x "${dir}/tests/smoke_kube.sh" cat > "${dir}/docs/OODA.md" <<'EOF' # OODA in Railiance - Observe: telemetry & test results - Orient: AI analyzes deltas & proposes remediations - Decide: specify → review → authorize (Git PR) - Act: GitOps applies automatically (human independent) EOF } git_init_and_push() { local dir="$1" remote_url="$2" ( cd "${dir}" git init git add . git commit -m "railiance: initial bootstrap scaffold" git branch -M main git remote add origin "${remote_url}" # relies on credential helper or SSH setup; avoids embedding token in URL git push -u origin main ) } # ----------------------------- main ------------------------------------------ DESC_JSON=$(json_escape "${REPO_DESC}") PRIVATE_JSON=$([[ "${IS_PRIVATE}" == true ]] && echo true || echo false) OWNER="${GITEA_USER}" REMOTE_URL="${GITEA_URL}/${OWNER}/${REPO_NAME}.git" if [[ -n "${GITEA_ORG}" ]]; then OWNER="${GITEA_ORG}" REMOTE_URL="${GITEA_URL}/${OWNER}/${REPO_NAME}.git" fi # Basic API reachability check (no auth needed) VER_CODE=$(curl_code "${GITEA_URL}/api/v1/version") if [[ "${VER_CODE}" != "200" ]]; then echo "ERROR: Gitea API not reachable at ${GITEA_URL}/api/v1/version (HTTP ${VER_CODE})" >&2 exit 1 fi if repo_exists "${OWNER}" "${REPO_NAME}"; then echo "Repo ${OWNER}/${REPO_NAME} already exists. Skipping creation." else if [[ -n "${GITEA_ORG}" ]]; then echo "Creating repo under org '${GITEA_ORG}'..." CODE=$(create_repo_org "${GITEA_ORG}" "${REPO_NAME}" "${PRIVATE_JSON}" "${DESC_JSON}") if [[ "${CODE}" != "201" ]]; then echo "ERROR: Failed to create repo in org (HTTP ${CODE}). Do you have org permissions?" >&2 exit 1 fi else echo "Creating repo under user '${GITEA_USER}'..." CODE=$(create_repo_user "${REPO_NAME}" "${PRIVATE_JSON}" "${DESC_JSON}") if [[ "${CODE}" != "201" ]]; then echo "ERROR: Failed to create user repo (HTTP ${CODE}). Check token scopes." >&2 exit 1 fi fi echo "Repo created: ${OWNER}/${REPO_NAME}" fi if [[ "${DO_SCAFFOLD}" == true ]]; then if [[ -d "${REPO_NAME}" ]]; then echo "Local dir '${REPO_NAME}' exists. Skipping scaffold." else echo "Scaffolding '${REPO_NAME}'..." mkdir -p "${REPO_NAME}" scaffold_repo "${REPO_NAME}" fi echo "Pushing initial commit to ${REMOTE_URL} ..." git_init_and_push "${REPO_NAME}" "${REMOTE_URL}" else echo "Skipping scaffold as requested (--no-scaffold)." fi echo "✅ Done." echo "Repository: ${OWNER}/${REPO_NAME}" echo "Remote URL: ${REMOTE_URL}"