feature/generador-instaladores-linux-windows

This commit is contained in:
2026-07-30 07:34:17 -06:00
parent cafe3f1b87
commit c3f1d70e23
34 changed files with 3498 additions and 252 deletions

View 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"