222 lines
7.8 KiB
Bash
Executable File
222 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Publica los artefactos de dist/release/ en el registro de paquetes GENÉRICOS de Gitea.
|
|
#
|
|
# Gitea calcula y expone el sha256 de cada archivo, así que se convierte en la fuente de
|
|
# verdad de los binarios: el PANEL descubre las versiones leyendo su API y verifica la
|
|
# integridad contra ese hash. Aquí, tras subir, se compara lo que reporta Gitea contra
|
|
# release.json para no dejar una publicación a medias pasando por buena.
|
|
#
|
|
# Uso:
|
|
# GITEA_TOKEN=... ./packaging/scripts/publish-release.sh
|
|
# ... --dry-run # lista qué subiría, sin subir nada
|
|
# ... --force # reemplaza archivos ya publicados de esta versión
|
|
# ... --notify-panel # avisa al PANEL para que sincronice de inmediato
|
|
#
|
|
# Variables:
|
|
# GITEA_TOKEN (requerida) PAT de Gitea con scope write:package
|
|
# GITEA_BASE_URL default https://git.aduanasoft.com
|
|
# GITEA_OWNER default ADUANASOFT
|
|
# CRAS_PACKAGE default cloudrestoreas
|
|
# PANEL_API_URL (solo --notify-panel) URL base del PANEL
|
|
# CLOUDRESTORE_API_TOKEN (solo --notify-panel) token de servicio del PANEL
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
RELEASE="$ROOT/dist/release"
|
|
MANIFEST="$RELEASE/release.json"
|
|
|
|
GITEA_BASE_URL="${GITEA_BASE_URL:-https://git.aduanasoft.com}"
|
|
GITEA_OWNER="${GITEA_OWNER:-ADUANASOFT}"
|
|
CRAS_PACKAGE="${CRAS_PACKAGE:-cloudrestoreas}"
|
|
|
|
DRY_RUN=0
|
|
FORCE=0
|
|
NOTIFY_PANEL=0
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--force) FORCE=1 ;;
|
|
--notify-panel) NOTIFY_PANEL=1 ;;
|
|
-h|--help)
|
|
grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "Opción desconocida: $arg" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
BLUE='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; NC='\033[0m'
|
|
say() { echo -e "${BLUE}==>${NC} $*"; }
|
|
ok() { echo -e "${GREEN}OK:${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}AVISO:${NC} $*" >&2; }
|
|
err() { echo -e "${RED}ERROR:${NC} $*" >&2; }
|
|
|
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
err "no existe $MANIFEST; corre packaging/scripts/package-release.sh primero"
|
|
exit 1
|
|
fi
|
|
|
|
VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$MANIFEST")"
|
|
|
|
# Los paquetes del manifiesto más los archivos de verificación, que se publican para
|
|
# poder auditar una versión sin depender del panel.
|
|
mapfile -t FILES < <(
|
|
python3 - "$MANIFEST" <<'PY'
|
|
import json, sys
|
|
data = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
for artifact in data["artifacts"]:
|
|
print(artifact["file_name"])
|
|
print("SHA256SUMS")
|
|
print("release.json")
|
|
PY
|
|
)
|
|
|
|
if [[ "${#FILES[@]}" -le 2 ]]; then
|
|
err "release.json no lista artefactos; ¿falló el build de ambas plataformas?"
|
|
exit 1
|
|
fi
|
|
|
|
PKG_BASE="$GITEA_BASE_URL/api/packages/$GITEA_OWNER/generic/$CRAS_PACKAGE/$VERSION"
|
|
API_BASE="$GITEA_BASE_URL/api/v1/packages/$GITEA_OWNER/generic/$CRAS_PACKAGE/$VERSION"
|
|
|
|
say "Publicando CloudRestoreAS $VERSION"
|
|
echo " Destino : $PKG_BASE"
|
|
echo " Archivos: ${#FILES[@]}"
|
|
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
for f in "${FILES[@]}"; do
|
|
if [[ -f "$RELEASE/$f" ]]; then
|
|
printf ' [dry-run] PUT %s (%s)\n' "$f" "$(du -h "$RELEASE/$f" | cut -f1)"
|
|
else
|
|
warn "[dry-run] falta $f"
|
|
fi
|
|
done
|
|
ok "dry-run completado; no se subió nada"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
|
err "falta GITEA_TOKEN (PAT de Gitea con scope write:package)"
|
|
echo " Ejemplo: GITEA_TOKEN=xxxx $0" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Subida ------------------------------------------------------------------------
|
|
for f in "${FILES[@]}"; do
|
|
path="$RELEASE/$f"
|
|
if [[ ! -f "$path" ]]; then
|
|
warn "no existe $f; se omite"
|
|
continue
|
|
fi
|
|
|
|
if [[ "$FORCE" -eq 1 ]]; then
|
|
# Los paquetes genéricos son inmutables: para reemplazar hay que borrar primero.
|
|
curl -sS -o /dev/null -X DELETE \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
"$PKG_BASE/$f" || true
|
|
fi
|
|
|
|
say "Subiendo $f ($(du -h "$path" | cut -f1))"
|
|
# -# manda la barra de progreso a stderr y -w el código HTTP a stdout, así se puede
|
|
# ver el avance de una subida de cientos de MB y a la vez capturar el resultado.
|
|
code="$(
|
|
curl -S -# -o /dev/null -w '%{http_code}' \
|
|
--retry 2 --retry-delay 3 --retry-connrefused \
|
|
-X PUT \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
--upload-file "$path" \
|
|
"$PKG_BASE/$f"
|
|
)"
|
|
|
|
case "$code" in
|
|
201|200) ok "$f publicado" ;;
|
|
409)
|
|
err "$f ya está publicado en la versión $VERSION"
|
|
echo " Los paquetes genéricos son inmutables. Sube una versión nueva (recomendado)" >&2
|
|
echo " o reemplaza esta a propósito con --force." >&2
|
|
exit 1 ;;
|
|
401|403)
|
|
err "Gitea rechazó el token ($code); revisa GITEA_TOKEN y su scope write:package"
|
|
exit 1 ;;
|
|
*)
|
|
err "fallo subiendo $f (HTTP $code)"
|
|
exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# --- Verificación contra la API que consume el PANEL ---------------------------------
|
|
say "Verificando en Gitea (misma API que lee el PANEL)"
|
|
files_json="$(
|
|
curl -sS -H "Authorization: token $GITEA_TOKEN" -H 'Accept: application/json' \
|
|
"$API_BASE/files"
|
|
)"
|
|
|
|
if ! MANIFEST="$MANIFEST" FILES_JSON="$files_json" python3 <<'PY'
|
|
import json, os, sys
|
|
|
|
manifest = json.load(open(os.environ["MANIFEST"], encoding="utf-8"))
|
|
try:
|
|
remote = json.loads(os.environ["FILES_JSON"])
|
|
except json.JSONDecodeError:
|
|
sys.exit("Gitea no devolvió JSON al listar los archivos del paquete")
|
|
if not isinstance(remote, list):
|
|
sys.exit(f"Respuesta inesperada de Gitea: {remote!r}")
|
|
|
|
by_name = {entry.get("name"): entry for entry in remote}
|
|
problems = []
|
|
for artifact in manifest["artifacts"]:
|
|
name = artifact["file_name"]
|
|
entry = by_name.get(name)
|
|
if entry is None:
|
|
problems.append(f"{name}: no aparece en Gitea")
|
|
continue
|
|
# Gitea calcula el sha256 del lado servidor: si coincide con el local, los bytes
|
|
# llegaron completos y el PANEL podrá verificar la descarga con ese mismo hash.
|
|
remote_sha = (entry.get("sha256") or "").lower()
|
|
if remote_sha != artifact["sha256"].lower():
|
|
problems.append(f"{name}: sha256 local {artifact['sha256'][:12]}… != remoto {remote_sha[:12] or '(vacío)'}…")
|
|
elif int(entry.get("size") or -1) != artifact["size"]:
|
|
problems.append(f"{name}: tamaño local {artifact['size']} != remoto {entry.get('size')}")
|
|
else:
|
|
print(f" {name}: sha256 y tamaño coinciden")
|
|
|
|
if problems:
|
|
print("\n".join(f" {p}" for p in problems), file=sys.stderr)
|
|
sys.exit(1)
|
|
PY
|
|
then
|
|
err "la verificación contra Gitea falló; la publicación quedó incompleta"
|
|
echo " Revisa el paquete y reintenta con --force." >&2
|
|
exit 1
|
|
fi
|
|
ok "verificación correcta"
|
|
|
|
# --- Aviso al PANEL ------------------------------------------------------------------
|
|
if [[ "$NOTIFY_PANEL" -eq 1 ]]; then
|
|
if [[ -z "${PANEL_API_URL:-}" || -z "${CLOUDRESTORE_API_TOKEN:-}" ]]; then
|
|
warn "--notify-panel requiere PANEL_API_URL y CLOUDRESTORE_API_TOKEN; se omite el aviso"
|
|
warn "El PANEL igual descubrirá la versión al sincronizar desde su UI."
|
|
else
|
|
say "Avisando al PANEL para que sincronice"
|
|
base="${PANEL_API_URL%/}"
|
|
code="$(
|
|
curl -sS -o /dev/null -w '%{http_code}' -X POST \
|
|
-H "Authorization: Bearer $CLOUDRESTORE_API_TOKEN" \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{}' \
|
|
"$base/api/restore/agent-sync"
|
|
)"
|
|
if [[ "$code" == "200" ]]; then
|
|
ok "PANEL sincronizado"
|
|
else
|
|
# No es fatal: la publicación en Gitea ya está firme y el panel puede sincronizar
|
|
# después desde su UI. Solo se informa.
|
|
warn "el PANEL respondió $code al sincronizar; hazlo desde /versiones-cras"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
ok "CloudRestoreAS $VERSION publicado en Gitea"
|
|
echo " Paquete: $GITEA_BASE_URL/-/packages/$GITEA_OWNER/generic/$CRAS_PACKAGE/$VERSION"
|
|
echo " Siguiente: en el PANEL, /versiones-cras → Sincronizar con Gitea → Activar"
|