CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
They existed only in /opt/ops on the VPS — no history, no backup. Rebuilding that machine would have taken the whole publishing process with it, including the Android signing flow written today. /opt/ops now symlinks here so there is one copy rather than two that drift. No secrets move: the scripts read tokens and the signing key from paths under /root that stay on the VPS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
5.1 KiB
Bash
Executable File
121 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Baixa o APK compilado no GitHub, assina aqui e publica no Gitea.
|
|
#
|
|
# A chave de assinatura fica nesta VPS e nao sobe para o GitHub. Ela e o que
|
|
# prova que uma atualizacao veio de nos: um APK assinado com outra chave nao
|
|
# instala por cima do instalado, o Android recusa. Por isso o runner entrega o
|
|
# APK sem assinatura e a assinatura acontece aqui.
|
|
#
|
|
# Uso: release-android.sh [run_id]
|
|
# sem run_id, dispara um build novo e espera terminar.
|
|
set -euo pipefail
|
|
|
|
REPO=${REPO:-/opt/backspace}
|
|
GH_REPO=${GH_REPO:-syncwrld/resenhacord}
|
|
TOOLS=/opt/ops/android-tools/build-tools
|
|
KEYSTORE=/root/android-signing/resenhacord.keystore
|
|
PASSFILE=/root/android-signing/password.txt
|
|
OUT=/opt/ops/android-out
|
|
GITEA_API=https://git.resenha.website/api/v1/repos/devsyncwrld/backspace
|
|
|
|
TOKEN=$(cat /root/.github-token)
|
|
gh() { curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" "$@"; }
|
|
|
|
# apksigner e um script que chama java, e esta VPS nao tem JDK instalado (nem
|
|
# deve ter: ela roda a producao, nao compila nada). Roda num container, com as
|
|
# ferramentas, a chave e a pasta de saida montadas. zipalign e binario nativo e
|
|
# roda direto no host.
|
|
apksigner() {
|
|
docker run --rm \
|
|
-v "$TOOLS":/tools:ro \
|
|
-v /root/android-signing:/ks:ro \
|
|
-v "$OUT":/out \
|
|
eclipse-temurin:21-jdk /tools/apksigner "$@"
|
|
}
|
|
|
|
for f in "$TOOLS/apksigner" "$TOOLS/zipalign" "$KEYSTORE" "$PASSFILE"; do
|
|
[ -e "$f" ] || { echo "faltando: $f" >&2; exit 1; }
|
|
done
|
|
|
|
RUN_ID=${1:-}
|
|
if [ -z "$RUN_ID" ]; then
|
|
echo "==> Disparando o build no GitHub"
|
|
gh -o /dev/null -X POST \
|
|
"https://api.github.com/repos/$GH_REPO/actions/workflows/android.yml/dispatches" \
|
|
-d '{"ref":"main"}'
|
|
sleep 10
|
|
RUN_ID=$(gh "https://api.github.com/repos/$GH_REPO/actions/workflows/android.yml/runs?per_page=1" \
|
|
| python3 -c "import sys,json;print(json.load(sys.stdin)['workflow_runs'][0]['id'])")
|
|
echo " run=$RUN_ID"
|
|
|
|
echo "==> Esperando (uns 4 min)"
|
|
for _ in $(seq 1 90); do
|
|
read -r status conclusion < <(gh "https://api.github.com/repos/$GH_REPO/actions/runs/$RUN_ID" \
|
|
| python3 -c "import sys,json;r=json.load(sys.stdin);print(r['status'],r['conclusion'])")
|
|
[ "$status" = "completed" ] && break
|
|
sleep 20
|
|
done
|
|
[ "${conclusion:-}" = "success" ] || { echo "build falhou: ${conclusion:-timeout}" >&2; exit 1; }
|
|
fi
|
|
|
|
echo "==> Baixando o APK sem assinatura"
|
|
mkdir -p "$OUT"
|
|
URL=$(gh "https://api.github.com/repos/$GH_REPO/actions/runs/$RUN_ID/artifacts" \
|
|
| python3 -c "import sys,json;a=json.load(sys.stdin)['artifacts'];print(a[0]['archive_download_url'] if a else '')")
|
|
[ -n "$URL" ] || { echo "o run $RUN_ID nao tem artefato" >&2; exit 1; }
|
|
curl -sL -H "Authorization: Bearer $TOKEN" "$URL" -o "$OUT/apk.zip"
|
|
rm -f "$OUT/resenhacord-unsigned.apk"
|
|
unzip -o -q "$OUT/apk.zip" -d "$OUT"
|
|
|
|
VERSION=$(python3 -c "import json;print(json.load(open('$REPO/package.json'))['version'])")
|
|
SIGNED="$OUT/Resenhacord-$VERSION.apk"
|
|
|
|
echo "==> Alinhando e assinando $VERSION"
|
|
# --key-pass omitido de proposito: sem ele o apksigner reusa a senha do
|
|
# keystore, que e a mesma. Passar o mesmo arquivo nas duas opcoes falha — ele le
|
|
# os arquivos de senha em sequencia e bate no fim do arquivo na segunda leitura.
|
|
# zipalign antes do apksigner, nao depois: o alinhamento move bytes dentro do
|
|
# zip e invalidaria a assinatura se viesse por ultimo.
|
|
"$TOOLS/zipalign" -p -f 4 "$OUT/resenhacord-unsigned.apk" "$OUT/aligned.apk"
|
|
apksigner sign \
|
|
--ks /ks/resenhacord.keystore --ks-pass file:/ks/password.txt \
|
|
--ks-key-alias resenhacord \
|
|
--out "/out/$(basename "$SIGNED")" /out/aligned.apk
|
|
rm -f "$OUT/aligned.apk" "$OUT/apk.zip"
|
|
|
|
echo "==> Conferindo a assinatura"
|
|
apksigner verify --print-certs "/out/$(basename "$SIGNED")" | head -5
|
|
|
|
if [ "${1:-}" = "--no-publish" ] || [ "${PUBLISH:-1}" = "0" ]; then
|
|
echo "APK pronto: $SIGNED"
|
|
exit 0
|
|
fi
|
|
|
|
echo "==> Publicando no Gitea"
|
|
GITEA_TOKEN=$(cat /root/.gitea-token 2>/dev/null || true)
|
|
if [ -z "$GITEA_TOKEN" ]; then
|
|
echo "sem /root/.gitea-token — APK pronto localmente em $SIGNED"
|
|
exit 0
|
|
fi
|
|
api() { curl -s -w '\n%{http_code}' -X "$1" -H "Authorization: token $GITEA_TOKEN" "$GITEA_API$2" "${@:3}"; }
|
|
body() { sed '$d' <<<"$1"; }
|
|
code() { tail -n1 <<<"$1"; }
|
|
|
|
R=$(api GET "/releases/tags/latest")
|
|
ID=$(body "$R" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
|
|
[ -n "$ID" ] || { echo "release 'latest' nao encontrada no Gitea" >&2; exit 1; }
|
|
|
|
# Remove o APK anterior: dois anexos com o mesmo nome fazem o Gitea servir o
|
|
# mais antigo, e quem baixasse pegaria a versao velha sem erro nenhum.
|
|
api GET "/releases/$ID/assets" | sed '$d' \
|
|
| python3 -c "import sys,json;[print(a['id']) for a in json.load(sys.stdin) if a['name'].endswith('.apk')]" \
|
|
| while read -r aid; do
|
|
[ -n "$aid" ] && api DELETE "/releases/$ID/assets/$aid" >/dev/null && echo " removido anexo $aid"
|
|
done
|
|
|
|
NAME=$(basename "$SIGNED")
|
|
R=$(api POST "/releases/$ID/assets?name=$NAME" -F "attachment=@$SIGNED")
|
|
[ "$(code "$R")" = "201" ] || { echo "falha no upload — HTTP $(code "$R"): $(body "$R")" >&2; exit 1; }
|
|
echo "publicado: $NAME"
|
|
echo "https://git.resenha.website/devsyncwrld/backspace/releases/download/latest/$NAME"
|