feature/generador-instaladores-linux-windows
This commit is contained in:
221
packaging/scripts/publish-release.sh
Executable file
221
packaging/scripts/publish-release.sh
Executable file
@@ -0,0 +1,221 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user