android: script de publicacion y workflow para la APK
release.sh hace el ciclo entero y siempre igual, se lance a mano o desde un runner: empaqueta el backend, compila, verifica y publica la release en gitea. Las verificaciones no son decorativas, son las que han cazado fallos reales en esta rama: una APK puede compilar, instalar y arrancar sin el backend dentro, o haber perdido los permisos de camara y quedarse sin videollamadas. Se comprueba que estan el backend y libnode.so, los dos permisos, y la firma. El versionCode sale del numero de commits, que sube solo; el versionName, de package.json, que es de donde lo saca tambien el backend, para que la version que muestra la aplicacion no pueda discrepar de la de la APK. Los dos se pasan por entorno a Gradle en vez de editarlos a mano. La release se crea como borrador a proposito: publicar una APK firmada sin que nadie la mire no deberia poder pasar por descuido. El workflow de gitea no se dispara con cada commit (la APK son mas de 100 MB) y necesita un runner con el SDK de Android; queda documentado en su cabecera.
This commit is contained in:
parent
a22e4a49d4
commit
f465fb2288
3 changed files with 207 additions and 2 deletions
53
.gitea/workflows/apk.yml
Normal file
53
.gitea/workflows/apk.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Construye la APK y la deja publicada como borrador en las releases.
|
||||
#
|
||||
# NO se dispara sola con cada commit: la APK son mas de 100 MB y el backend tarda
|
||||
# en empaquetarse. Se lanza a mano desde la interfaz de gitea, o al crear un tag.
|
||||
#
|
||||
# El runner necesita: JDK 17 con compilador, el SDK de Android (plataforma 35 y
|
||||
# build-tools), node, zip y unzip. Un runner pelado NO vale.
|
||||
#
|
||||
# Secretos que hay que dar de alta en el repositorio:
|
||||
# OASIS_KEYSTORE_B64 el almacen de claves en base64
|
||||
# OASIS_KEYSTORE_PASS su contraseña
|
||||
# OASIS_KEY_ALIAS el alias (por defecto "oasis")
|
||||
#
|
||||
# El token para publicar lo pone gitea solo en GITEA_TOKEN.
|
||||
|
||||
name: APK
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # release.sh cuenta commits para el versionCode
|
||||
|
||||
- name: Restaurar el almacen de claves
|
||||
run: |
|
||||
echo "${{ secrets.OASIS_KEYSTORE_B64 }}" | base64 -d > /tmp/oasis.jks
|
||||
chmod 600 /tmp/oasis.jks
|
||||
|
||||
- name: Construir, verificar y publicar
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/temurin-17-jdk-amd64
|
||||
ANDROID_HOME: /opt/android-sdk
|
||||
OASIS_KEYSTORE: /tmp/oasis.jks
|
||||
OASIS_KEYSTORE_PASS: ${{ secrets.OASIS_KEYSTORE_PASS }}
|
||||
OASIS_KEY_ALIAS: ${{ secrets.OASIS_KEY_ALIAS }}
|
||||
OASIS_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
OASIS_GITEA_URL: ${{ github.server_url }}
|
||||
OASIS_GITEA_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
cd android
|
||||
./scripts/release.sh --release --publish
|
||||
|
||||
- name: Borrar el almacen de claves
|
||||
if: always()
|
||||
run: rm -f /tmp/oasis.jks
|
||||
|
|
@ -14,8 +14,9 @@ android {
|
|||
applicationId = "net.laenre.oasis"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.9.5"
|
||||
// el pipeline los pasa por entorno; a mano valen los de aqui
|
||||
versionCode = (System.getenv("OASIS_VERSION_CODE") ?: "1").toInt()
|
||||
versionName = System.getenv("OASIS_VERSION_NAME") ?: "0.9.5"
|
||||
ndk { abiFilters += listOf("arm64-v8a") }
|
||||
}
|
||||
|
||||
|
|
|
|||
151
android/scripts/release.sh
Executable file
151
android/scripts/release.sh
Executable file
|
|
@ -0,0 +1,151 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Construye la APK, la verifica y (si se le pasa un token) publica la release en
|
||||
# gitea. Pensado para lanzarlo a mano o desde un runner; hace lo mismo en los dos
|
||||
# sitios, que es lo que evita el clasico "en mi maquina si sale".
|
||||
#
|
||||
# Uso:
|
||||
# ./scripts/release.sh # compila debug y verifica
|
||||
# ./scripts/release.sh --release # compila release firmada
|
||||
# ./scripts/release.sh --release --publish
|
||||
#
|
||||
# Variables:
|
||||
# OASIS_KEYSTORE, OASIS_KEYSTORE_PASS, OASIS_KEY_ALIAS firma (obligatorias con --release)
|
||||
# OASIS_GITEA_TOKEN publicar (obligatoria con --publish)
|
||||
# OASIS_APK_BASE APK del que sacar node_modules la primera vez
|
||||
# OASIS_GITEA_URL por defecto https://gitea.laenre.net
|
||||
# OASIS_GITEA_REPO por defecto hacklab/OASIS_MOBILE
|
||||
# ANDROID_HOME por defecto ~/Android/Sdk
|
||||
# JAVA_HOME un JDK 17 o superior CON compilador
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
REPO="$(cd "$HERE/.." && pwd)"
|
||||
cd "$HERE"
|
||||
|
||||
MODE=debug
|
||||
PUBLISH=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--release) MODE=release ;;
|
||||
--publish) PUBLISH=1 ;;
|
||||
*) echo "opcion desconocida: $arg"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
GITEA_URL="${OASIS_GITEA_URL:-https://gitea.laenre.net}"
|
||||
GITEA_REPO="${OASIS_GITEA_REPO:-hacklab/OASIS_MOBILE}"
|
||||
SDK="${ANDROID_HOME:-$HOME/Android/Sdk}"
|
||||
|
||||
say() { printf '\n== %s\n' "$*"; }
|
||||
die() { printf '\n!! %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# --- 1) version -------------------------------------------------------------
|
||||
# El nombre sale de package.json, que es de donde lo saca tambien el backend, para
|
||||
# que la version que muestra la aplicacion y la de la APK no puedan discrepar.
|
||||
VERSION_NAME="${OASIS_VERSION_NAME:-$(node -p "require('$REPO/package.json').version" 2>/dev/null || echo 0.0.0)}"
|
||||
# El codigo tiene que subir en cada publicacion o Android rechaza la instalacion:
|
||||
# el numero de commits sirve y no hay que llevarlo a mano.
|
||||
VERSION_CODE="${OASIS_VERSION_CODE:-$(git -C "$REPO" rev-list --count HEAD 2>/dev/null || echo 1)}"
|
||||
TAG="v${VERSION_NAME}-${VERSION_CODE}"
|
||||
export OASIS_VERSION_NAME="$VERSION_NAME" OASIS_VERSION_CODE="$VERSION_CODE"
|
||||
say "version ${VERSION_NAME} (codigo ${VERSION_CODE}), modo ${MODE}"
|
||||
|
||||
# --- 2) comprobaciones previas ---------------------------------------------
|
||||
[ -n "${JAVA_HOME:-}" ] || die "define JAVA_HOME apuntando a un JDK con compilador"
|
||||
[ -x "${JAVA_HOME}/bin/javac" ] || die "en ${JAVA_HOME} no hay javac: es un runtime, no un JDK"
|
||||
[ -d "$SDK" ] || die "no encuentro el SDK de Android en $SDK (define ANDROID_HOME)"
|
||||
|
||||
if [ "$MODE" = release ]; then
|
||||
[ -n "${OASIS_KEYSTORE:-}" ] || die "--release necesita OASIS_KEYSTORE"
|
||||
[ -f "$OASIS_KEYSTORE" ] || die "no existe el almacen de claves: $OASIS_KEYSTORE"
|
||||
[ -n "${OASIS_KEYSTORE_PASS:-}" ] || die "--release necesita OASIS_KEYSTORE_PASS"
|
||||
fi
|
||||
if [ "$PUBLISH" = 1 ]; then
|
||||
[ -n "${OASIS_GITEA_TOKEN:-}" ] || die "--publish necesita OASIS_GITEA_TOKEN"
|
||||
[ "$MODE" = release ] || die "no se publica una APK de depuracion"
|
||||
fi
|
||||
|
||||
# --- 3) backend empaquetado -------------------------------------------------
|
||||
# prepare.sh es idempotente pero tarda; se rehace siempre porque el backend cambia
|
||||
# en cada commit y una APK con el backend viejo es peor que no tener APK.
|
||||
# La primera vez hace falta un APK base del que sacar node_modules: el del movil
|
||||
# esta podado y sin binarios nativos, y un npm install normal no lo reproduce.
|
||||
say "empaquetando el backend"
|
||||
./scripts/prepare.sh ${OASIS_APK_BASE:+"$OASIS_APK_BASE"} \
|
||||
|| die "prepare.sh ha fallado (define OASIS_APK_BASE con un APK del que sacar node_modules)"
|
||||
|
||||
ZIP=app/src/main/assets/nodejs-project.zip
|
||||
[ -f "$ZIP" ] || die "falta $ZIP"
|
||||
ZIP_MB=$(( $(stat -c%s "$ZIP") / 1024 / 1024 ))
|
||||
[ "$ZIP_MB" -ge 20 ] || die "el backend empaquetado son $ZIP_MB MB: se ha quedado a medias"
|
||||
say "backend: ${ZIP_MB} MB"
|
||||
|
||||
# --- 4) compilar ------------------------------------------------------------
|
||||
say "compilando"
|
||||
if [ "$MODE" = release ]; then
|
||||
./gradlew --quiet assembleRelease
|
||||
APK=$(ls app/build/outputs/apk/release/*.apk | head -1)
|
||||
else
|
||||
./gradlew --quiet assembleDebug
|
||||
APK=$(ls app/build/outputs/apk/debug/*.apk | head -1)
|
||||
fi
|
||||
[ -f "$APK" ] || die "no se ha generado ninguna APK"
|
||||
say "APK: $APK ($(( $(stat -c%s "$APK") / 1024 / 1024 )) MB)"
|
||||
|
||||
# --- 5) verificar -----------------------------------------------------------
|
||||
# Estas tres comprobaciones son las que han cazado fallos de verdad: una APK que
|
||||
# compila e instala puede no llevar el backend dentro, o haber perdido los permisos.
|
||||
BT=$(ls -d "$SDK"/build-tools/* 2>/dev/null | sort -V | tail -1)
|
||||
[ -n "$BT" ] || die "no encuentro build-tools en $SDK"
|
||||
|
||||
say "verificando"
|
||||
# ojo con pipefail: grep -q cierra la tuberia y el productor muere con SIGPIPE,
|
||||
# asi que se vuelca a fichero antes de mirar
|
||||
LISTING=$(mktemp); PERMS=$(mktemp)
|
||||
trap 'rm -f "$LISTING" "$PERMS"' EXIT
|
||||
unzip -l "$APK" > "$LISTING"
|
||||
"$BT/aapt2" dump permissions "$APK" > "$PERMS" 2>/dev/null || true
|
||||
|
||||
grep -q "assets/nodejs-project.zip" "$LISTING" || die "la APK no lleva el backend dentro"
|
||||
grep -q "android.permission.CAMERA" "$PERMS" || die "la APK no declara CAMERA: no podria hacer videollamadas"
|
||||
grep -q "android.permission.RECORD_AUDIO" "$PERMS" || die "la APK no declara RECORD_AUDIO"
|
||||
grep -q "libnode.so" "$LISTING" || die "la APK no lleva el runtime de node"
|
||||
|
||||
if [ "$MODE" = release ]; then
|
||||
"$BT/apksigner" verify --print-certs "$APK" > /tmp/oasis-signer.txt 2>&1 \
|
||||
|| die "la APK no esta firmada correctamente"
|
||||
FINGERPRINT=$(grep -m1 "SHA-256 digest" /tmp/oasis-signer.txt | awk '{print $NF}')
|
||||
say "huella del certificado: $FINGERPRINT"
|
||||
fi
|
||||
|
||||
SHA=$(sha256sum "$APK" | cut -d' ' -f1)
|
||||
say "sha256 de la APK: $SHA"
|
||||
|
||||
# --- 6) publicar ------------------------------------------------------------
|
||||
if [ "$PUBLISH" != 1 ]; then
|
||||
say "listo (sin publicar)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
say "publicando $TAG en $GITEA_REPO"
|
||||
API="$GITEA_URL/api/v1/repos/$GITEA_REPO"
|
||||
AUTH="Authorization: token $OASIS_GITEA_TOKEN"
|
||||
|
||||
NOTES=$(printf 'Rama experimental del hacklab sobre el Oasis de epsylon.\n\nNO es Oasis oficial: https://github.com/epsylon/oasis\n\nVersion base: %s (codigo %s)\n\nsha256 de la APK: %s\nHuella del certificado: %s\n\nComprueba la huella antes de instalar:\n\n apksigner verify --print-certs oasis.apk\n' \
|
||||
"$VERSION_NAME" "$VERSION_CODE" "$SHA" "${FINGERPRINT:-sin firmar}")
|
||||
|
||||
RELEASE_ID=$(curl -sf -X POST "$API/releases" -H "$AUTH" -H "Content-Type: application/json" \
|
||||
-d "$(node -e '
|
||||
const [tag, name, body] = process.argv.slice(1);
|
||||
process.stdout.write(JSON.stringify({ tag_name: tag, name, body, draft: true, prerelease: true }));
|
||||
' "$TAG" "Oasis Mobile $TAG" "$NOTES")" | node -pe 'JSON.parse(require("fs").readFileSync(0)).id' 2>/dev/null) \
|
||||
|| die "no se ha podido crear la release (token sin permiso, o el tag ya existe)"
|
||||
|
||||
curl -sf -X POST "$API/releases/$RELEASE_ID/assets?name=oasis-$TAG.apk" \
|
||||
-H "$AUTH" -F "attachment=@$APK" >/dev/null \
|
||||
|| die "la release se ha creado pero no se ha podido subir la APK"
|
||||
|
||||
say "publicada como borrador: $GITEA_URL/$GITEA_REPO/releases"
|
||||
say "revisala y quita el borrador a mano; no se publica sola a proposito"
|
||||
Loading…
Add table
Add a link
Reference in a new issue