220 lines
7.4 KiB
Bash
220 lines
7.4 KiB
Bash
#!/usr/bin/env bash
|
|
# gitea skill — crear un repo en gitea.nucleoriofrio.com.
|
|
#
|
|
# Soporta dos targets:
|
|
# - usuario autenticado (claudecode0): POST /api/v1/user/repos
|
|
# - org (NucleOS u otra): POST /api/v1/orgs/{org}/repos
|
|
#
|
|
# Regla dura: cualquier repo bajo el usuario `claudecode0` (case-insensitive
|
|
# match con $GITEA_BOT_USER) DEBE ser público. La justificación es que el
|
|
# usuario humano necesita poder ver lo que el bot crea — sino la cuenta
|
|
# claudecode0 sería una caja negra. Si pasás --private a un repo bajo el bot,
|
|
# el script aborta con exit 5.
|
|
#
|
|
# Para repos en orgs, --public o --private es REQUERIDO (forzar decisión
|
|
# explícita; sin default sorpresa).
|
|
#
|
|
# Uso:
|
|
# repo-create.sh <name> [--owner <user|org>] [--description "..."]
|
|
# [--public | --private] [--no-init] [--license <tpl>]
|
|
# [--gitignore <tpl>] [--default-branch <name>] [--template]
|
|
|
|
set -euo pipefail
|
|
|
|
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
QUERY="$SKILL_DIR/scripts/query.sh"
|
|
ENV_FILE="$SKILL_DIR/.env"
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
echo "ERROR: $ENV_FILE no existe. Corré setup.sh primero." >&2
|
|
exit 1
|
|
fi
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
set +a
|
|
|
|
: "${GITEA_BOT_USER:?GITEA_BOT_USER no definido en .env. Re-correr setup.sh.}"
|
|
|
|
name=""
|
|
owner=""
|
|
description=""
|
|
visibility="" # "public" | "private" | ""
|
|
auto_init=1
|
|
license=""
|
|
gitignore=""
|
|
default_branch="main"
|
|
is_template=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--owner) owner="$2"; shift 2 ;;
|
|
--description) description="$2"; shift 2 ;;
|
|
--public) visibility="public"; shift ;;
|
|
--private) visibility="private"; shift ;;
|
|
--no-init) auto_init=0; shift ;;
|
|
--license) license="$2"; shift 2 ;;
|
|
--gitignore) gitignore="$2"; shift 2 ;;
|
|
--default-branch) default_branch="$2"; shift 2 ;;
|
|
--template) is_template=1; shift ;;
|
|
-h|--help)
|
|
cat <<EOF
|
|
Uso: repo-create.sh <name> [opciones]
|
|
|
|
Argumentos:
|
|
<name> Nombre del repo (requerido)
|
|
|
|
Owner:
|
|
--owner <user|org> Default: el usuario del bot (\$GITEA_BOT_USER, "$GITEA_BOT_USER")
|
|
Pasale "NucleOS" para crear en el org.
|
|
|
|
Visibilidad:
|
|
--public | --private Para orgs, uno es REQUERIDO (sin default).
|
|
Para el usuario del bot, --private está
|
|
BLOQUEADO (regla dura: todo lo del bot debe ser
|
|
público para que el usuario humano pueda verlo).
|
|
|
|
Contenido:
|
|
--description "..." Descripción del repo
|
|
--no-init No crear README inicial (default: auto-init=true)
|
|
--license <template> ej. "MIT", "Apache-2.0", "GPL-3.0"
|
|
--gitignore <template> ej. "Node", "Python", "Go"
|
|
--default-branch <name> default: main
|
|
--template Marcar como template repo
|
|
|
|
Devuelve URL del repo creado (clone_url + html_url).
|
|
EOF
|
|
exit 0
|
|
;;
|
|
-*) echo "ERROR: flag desconocida: $1" >&2; exit 2 ;;
|
|
*)
|
|
if [[ -z "$name" ]]; then name="$1"
|
|
else echo "ERROR: argumento extra: $1" >&2; exit 2
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$name" ]]; then
|
|
echo "ERROR: pasá <name>." >&2
|
|
echo "Uso: repo-create.sh <name> [opciones] (--help para detalles)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Default owner = bot user
|
|
if [[ -z "$owner" ]]; then
|
|
owner="$GITEA_BOT_USER"
|
|
fi
|
|
|
|
# ─── Detectar si owner es el bot (case-insensitive) ─────────────────────
|
|
shopt -s nocasematch
|
|
is_bot_owner=0
|
|
if [[ "$owner" == "$GITEA_BOT_USER" ]]; then
|
|
is_bot_owner=1
|
|
fi
|
|
shopt -u nocasematch
|
|
|
|
# ─── Guard: visibilidad ─────────────────────────────────────────────────
|
|
if [[ "$is_bot_owner" -eq 1 ]]; then
|
|
# Regla dura: bot user → siempre público
|
|
if [[ "$visibility" == "private" ]]; then
|
|
cat >&2 <<EOF
|
|
ERROR: --private bloqueado para repos bajo el usuario del bot ($GITEA_BOT_USER).
|
|
|
|
Regla dura de la skill: todo lo que el bot crea bajo su propio usuario debe
|
|
ser PÚBLICO para que el usuario humano pueda auditarlo. Sin esa regla, la
|
|
cuenta del bot sería una caja negra.
|
|
|
|
Opciones:
|
|
- Crearlo público (omitir --private; será público por default)
|
|
- Crearlo en una org en vez del usuario del bot:
|
|
repo-create.sh $name --owner NucleOS --private
|
|
EOF
|
|
exit 5
|
|
fi
|
|
# Default: público
|
|
visibility="public"
|
|
else
|
|
# Org u otro user → visibilidad explícita requerida (sin default sorpresa)
|
|
if [[ -z "$visibility" ]]; then
|
|
cat >&2 <<EOF
|
|
ERROR: para repos en una org (owner=$owner), --public o --private es REQUERIDO.
|
|
|
|
Sin default para evitar sorpresas. Pasá una flag explícita:
|
|
repo-create.sh $name --owner $owner --public
|
|
repo-create.sh $name --owner $owner --private
|
|
EOF
|
|
exit 6
|
|
fi
|
|
fi
|
|
|
|
# Convertir visibilidad → boolean private
|
|
case "$visibility" in
|
|
public) is_private="false" ;;
|
|
private) is_private="true" ;;
|
|
*) echo "ERROR interno: visibility=$visibility" >&2; exit 99 ;;
|
|
esac
|
|
|
|
# ─── Construir endpoint ─────────────────────────────────────────────────
|
|
if [[ "$is_bot_owner" -eq 1 ]]; then
|
|
endpoint="/user/repos"
|
|
else
|
|
endpoint="/orgs/${owner}/repos"
|
|
fi
|
|
|
|
# ─── Build body con Python (UTF-8 safe, mismo patrón que pr-create) ─────
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
BODY_JSON="$TMP_DIR/body.json"
|
|
|
|
PY_NAME="$name" PY_DESC="$description" PY_PRIVATE="$is_private" \
|
|
PY_AUTO_INIT="$auto_init" PY_LICENSE="$license" PY_GITIGNORE="$gitignore" \
|
|
PY_DEFAULT_BRANCH="$default_branch" PY_TEMPLATE="$is_template" \
|
|
python -c '
|
|
import json, os
|
|
data = {
|
|
"name": os.environ["PY_NAME"],
|
|
"description": os.environ.get("PY_DESC", ""),
|
|
"private": os.environ["PY_PRIVATE"] == "true",
|
|
"auto_init": os.environ["PY_AUTO_INIT"] == "1",
|
|
"default_branch": os.environ["PY_DEFAULT_BRANCH"],
|
|
"template": os.environ["PY_TEMPLATE"] == "1",
|
|
}
|
|
if os.environ.get("PY_LICENSE"):
|
|
data["license"] = os.environ["PY_LICENSE"]
|
|
if os.environ.get("PY_GITIGNORE"):
|
|
data["gitignores"] = os.environ["PY_GITIGNORE"]
|
|
print(json.dumps(data))
|
|
' > "$BODY_JSON"
|
|
|
|
# ─── POST ───────────────────────────────────────────────────────────────
|
|
echo "→ POST $endpoint" >&2
|
|
echo " name: $name" >&2
|
|
echo " owner: $owner $([ "$is_bot_owner" -eq 1 ] && echo "(bot user)" || echo "(org)")" >&2
|
|
echo " visibility: $visibility" >&2
|
|
echo " auto_init: $auto_init default_branch: $default_branch" >&2
|
|
[[ -n "$description" ]] && echo " description: $description" >&2
|
|
[[ -n "$license" ]] && echo " license: $license" >&2
|
|
[[ -n "$gitignore" ]] && echo " gitignore: $gitignore" >&2
|
|
|
|
resp="$("$QUERY" -X POST \
|
|
-H 'Content-Type: application/json; charset=utf-8' \
|
|
--data-binary @"$BODY_JSON" \
|
|
"$endpoint")"
|
|
|
|
echo "$resp" | PYTHONIOENCODING=utf-8 python -c "
|
|
import json, sys
|
|
d = json.load(sys.stdin)
|
|
if d.get('id') and d.get('full_name'):
|
|
visibility_label = 'private' if d.get('private') else 'public'
|
|
print(f'OK repo creado: {d[\"full_name\"]} ({visibility_label})')
|
|
print(f' html_url: {d.get(\"html_url\")}')
|
|
print(f' clone_url: {d.get(\"clone_url\")}')
|
|
print(f' ssh_url: {d.get(\"ssh_url\")}')
|
|
else:
|
|
print('ERROR:', d.get('message') or d, file=sys.stderr)
|
|
sys.exit(1)
|
|
"
|