From 59863b421481ef5fdf58b4b79eeb7a46e44d3242 Mon Sep 17 00:00:00 2001 From: Bernd Worsch Date: Fri, 12 Sep 2025 02:11:37 +0200 Subject: [PATCH] tools: add repo creation script and helper README --- .gitattributes | 1 + tools/README.md | 21 +++ tools/create_railiance_repo.sh | 269 +++++++++++++++++++++++++++++++++ 3 files changed, 291 insertions(+) create mode 100644 .gitattributes create mode 100644 tools/README.md create mode 100644 tools/create_railiance_repo.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..a2b287a --- /dev/null +++ b/tools/README.md @@ -0,0 +1,21 @@ +# Railiance Tools + +## create_railiance_repo.sh +Creates a new Railiance repo in Gitea (user or org), with a minimal scaffold. + +**Requires:** `~/.railiance_gitea.conf` + +```bash +GITEA_URL="https://git.example.com" +GITEA_USER="your-username" +GITEA_ORG="" # or your org name +GITEA_TOKEN="xxxxxxxxxxxxxxxx" # scopes: read:user + write:repository + + +## Usage +./tools/create_railiance_repo.sh +./tools/create_railiance_repo.sh --repo my-repo --public --desc "My desc" +./tools/create_railiance_repo.sh --org coulomb + +## Misc +The script relies on your Git credential helper or SSH key for the initial push. \ No newline at end of file diff --git a/tools/create_railiance_repo.sh b/tools/create_railiance_repo.sh new file mode 100644 index 0000000..64c093b --- /dev/null +++ b/tools/create_railiance_repo.sh @@ -0,0 +1,269 @@ +#!/usr/bin/env bash +# tools/create_railiance_repo.sh +# +# Railiance — sanitized bootstrap script for creating a Gitea repository. +# +# PURPOSE: +# Creates a repo (default: railiance-bootstrap) 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-bootstrap) +# --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-bootstrap" +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}"