release 1.3.2: update from Gitea, Linux-only CLI, desktop app for Linux and Windows
The updater reads latest.json from Gitea, built by scripts/make-latest-json.py against the Gitea API (no gh CLI). Release workflow builds the Linux CLI and the Linux+Windows desktop app, signs the updater artifacts and attaches everything to the release. Needs the repo secrets TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD, which I could not set (secret-store writes are reserved for the user) - set them from ~/.tauri/guara-updater.key and .pass before pushing a tag. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8ba3151512
commit
b47708126e
14 files changed
+319
-46
No files matched your search
@@ -1,14 +1,25 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
# Cria a entrada em "Releases" quando uma tag v* é enviada, com as notas da
|
# Quando uma tag v* é enviada: cria a entrada em "Releases" com as notas do
|
||||||
# seção correspondente do CHANGELOG.md (scripts/release-notes.sh).
|
# CHANGELOG.md, compila o CLI (Linux x86_64 e aarch64) e o app desktop (Linux
|
||||||
|
# e Windows, este por compilação cruzada com cargo-xwin), e monta o
|
||||||
|
# latest.json que o atualizador do app lê.
|
||||||
#
|
#
|
||||||
# Só as notas: os binários para macOS e Windows exigiam runners que o Gitea daqui
|
# Sem macOS: o Tauri precisa do SDK e da assinatura da Apple, presos a
|
||||||
# não tem (o único runner é Linux arm64), então não são gerados neste fluxo.
|
# hardware Apple e a uma conta de desenvolvedor, que este runner não tem.
|
||||||
|
#
|
||||||
|
# Segredos de usuário necessários: REGISTRY_TOKEN (não usado aqui, fica para o
|
||||||
|
# registro de containers de outros projetos), TAURI_SIGNING_PRIVATE_KEY e
|
||||||
|
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD (assinam os pacotes para o atualizador).
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ['v*']
|
tags: ['v*']
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
CARGO_HOME: /opt/hostedtoolcache/cargo
|
||||||
|
RUSTUP_HOME: /opt/hostedtoolcache/rustup
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Cria a release
|
name: Cria a release
|
||||||
@@ -23,9 +34,99 @@ jobs:
|
|||||||
TOKEN: ${{ gitea.token }}
|
TOKEN: ${{ gitea.token }}
|
||||||
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||||
run: |
|
run: |
|
||||||
|
# Uma execução repetida não recria a release.
|
||||||
|
if curl -fsS -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" >/dev/null 2>&1; then
|
||||||
|
echo "a release $TAG já existe"; exit 0
|
||||||
|
fi
|
||||||
scripts/release-notes.sh "$TAG" > notes.md
|
scripts/release-notes.sh "$TAG" > notes.md
|
||||||
pre=false; case "$TAG" in *-*) pre=true ;; esac
|
pre=false; case "$TAG" in *-*) pre=true ;; esac
|
||||||
jq -n --arg tag "$TAG" --arg name "Guará ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
|
jq -n --arg tag "$TAG" --arg name "Guará ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
|
||||||
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
|
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
|
||||||
curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \
|
curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \
|
||||||
-H 'Content-Type: application/json' -d @release.json
|
-H 'Content-Type: application/json' -d @release.json >/dev/null
|
||||||
|
|
||||||
|
cli:
|
||||||
|
name: CLI (${{ matrix.platform }})
|
||||||
|
needs: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/guara-release-cli
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { platform: linux-aarch64, target: aarch64-unknown-linux-gnu, apt: "" }
|
||||||
|
- { platform: linux-x86_64, target: x86_64-unknown-linux-gnu, apt: "gcc-x86-64-linux-gnu" }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
- name: Linker desta plataforma
|
||||||
|
if: ${{ matrix.apt != '' }}
|
||||||
|
run: |
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq ${{ matrix.apt }}
|
||||||
|
- name: Compila e empacota
|
||||||
|
run: scripts/package-cli.sh "${{ gitea.ref_name }}" "${{ matrix.platform }}" "${{ matrix.target }}" | tee packages.txt
|
||||||
|
- name: Anexa à release
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ gitea.token }}
|
||||||
|
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||||
|
run: scripts/release-upload.sh "${{ gitea.ref_name }}" $(cat packages.txt)
|
||||||
|
|
||||||
|
gui:
|
||||||
|
name: App desktop (${{ matrix.platform }})
|
||||||
|
needs: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/guara-release-gui
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: [linux, windows]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
- name: Dependências do Tauri (Linux)
|
||||||
|
if: matrix.platform == 'linux'
|
||||||
|
timeout-minutes: 25
|
||||||
|
run: scripts/apt-install.sh ~/apt-cache libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
|
- name: Ferramentas para compilação cruzada (Windows)
|
||||||
|
if: matrix.platform == 'windows'
|
||||||
|
run: |
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq nsis lld llvm clang
|
||||||
|
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
|
||||||
|
- name: Compila e empacota
|
||||||
|
run: scripts/build-gui.sh "${{ matrix.platform }}"
|
||||||
|
- name: Anexa à release
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ gitea.token }}
|
||||||
|
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||||
|
run: scripts/release-upload.sh "${{ gitea.ref_name }}" dist-gui/*
|
||||||
|
|
||||||
|
atualizador:
|
||||||
|
name: Monta o latest.json
|
||||||
|
needs: gui
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: latest.json
|
||||||
|
env:
|
||||||
|
TAG: ${{ gitea.ref_name }}
|
||||||
|
TOKEN: ${{ gitea.token }}
|
||||||
|
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||||
|
run: |
|
||||||
|
scripts/release-notes.sh "$TAG" > notes.md
|
||||||
|
python3 scripts/make-latest-json.py "$TAG" notes.md latest.json
|
||||||
|
- name: Anexa à release
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ gitea.token }}
|
||||||
|
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||||
|
run: scripts/release-upload.sh "${{ gitea.ref_name }}" latest.json
|
||||||
@@ -17,3 +17,7 @@ node_modules/
|
|||||||
*.img
|
*.img
|
||||||
*.guara-session.json
|
*.guara-session.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Pacotes gerados por scripts/package-cli.sh
|
||||||
|
/guara-v*
|
||||||
|
/dist-gui
|
||||||
@@ -4,6 +4,23 @@
|
|||||||
|
|
||||||
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
|
||||||
|
|
||||||
|
## [1.3.2] - 2026-10-30
|
||||||
|
|
||||||
|
The project moved from GitHub to Gitea, and the update check followed it.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Updates now come from Gitea.** The desktop app's update card reads
|
||||||
|
`latest.json` from `git.lucasrguerra.dev.br` instead of GitHub Releases.
|
||||||
|
Versions up to 1.3.1 still look at GitHub, which is no longer reachable, so
|
||||||
|
they cannot find this release: download 1.3.2 once, and later updates are
|
||||||
|
automatic.
|
||||||
|
- The CLI is published for Linux x86_64 and aarch64 only, built by the Gitea CI
|
||||||
|
when a `v*` tag is pushed, with a `.sha256` next to each package. **There is
|
||||||
|
no Windows CLI any more.** The desktop app still ships for Linux (AppImage,
|
||||||
|
`.deb`) and Windows (NSIS `.exe`, cross-compiled from Linux). There is no
|
||||||
|
macOS build: it needs Apple's SDK and code signing, which this project's CI
|
||||||
|
(one Linux runner) cannot provide.
|
||||||
|
|
||||||
## [1.3.1] - 2026-10-23
|
## [1.3.1] - 2026-10-23
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -4,6 +4,23 @@
|
|||||||
|
|
||||||
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer.
|
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer.
|
||||||
|
|
||||||
|
## [1.3.2] - 2026-10-30
|
||||||
|
|
||||||
|
O projeto saiu do GitHub para o Gitea, e a verificação de atualização foi junto.
|
||||||
|
|
||||||
|
### Alterado
|
||||||
|
- **As atualizações agora vêm do Gitea.** O cartão de atualização do app
|
||||||
|
desktop lê o `latest.json` em `git.lucasrguerra.dev.br`, e não mais as
|
||||||
|
Releases do GitHub. As versões até a 1.3.1 ainda olham o GitHub, que não
|
||||||
|
responde mais, e por isso não enxergam esta release: baixe a 1.3.2 uma vez, e
|
||||||
|
as próximas atualizações são automáticas.
|
||||||
|
- O CLI é publicado só para Linux x86_64 e aarch64, compilado pelo CI do Gitea
|
||||||
|
quando uma tag `v*` é enviada, com um `.sha256` ao lado de cada pacote. **Não
|
||||||
|
há mais CLI para Windows.** O app desktop continua saindo para Linux
|
||||||
|
(AppImage, `.deb`) e Windows (`.exe` NSIS, compilado a partir do Linux). Não
|
||||||
|
há build para macOS: exige o SDK e a assinatura da Apple, que o CI deste
|
||||||
|
projeto (um único runner Linux) não tem.
|
||||||
|
|
||||||
## [1.3.1] - 2026-10-23
|
## [1.3.1] - 2026-10-23
|
||||||
|
|
||||||
### Alterado
|
### Alterado
|
||||||
|
|||||||
Generated
+2
-2
@@ -416,7 +416,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "guara-cli"
|
name = "guara-cli"
|
||||||
version = "1.3.1"
|
version = "1.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
@@ -429,7 +429,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "guara-core"
|
name = "guara-core"
|
||||||
version = "1.3.1"
|
version = "1.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ resolver = "2"
|
|||||||
members = ["crates/guara-core", "crates/guara-cli"]
|
members = ["crates/guara-core", "crates/guara-cli"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.3.1"
|
version = "1.3.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/lucasrguerra/guara"
|
repository = "https://github.com/lucasrguerra/guara"
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "guara-gui"
|
name = "guara-gui"
|
||||||
description = "Guará: interface gráfica de recuperação de arquivos"
|
description = "Guará: interface gráfica de recuperação de arquivos"
|
||||||
version = "1.3.1"
|
version = "1.3.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/lucasrguerra/guara"
|
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/guara"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ async fn update_check(app: tauri::AppHandle) -> Result<Option<UpdateInfo>, Strin
|
|||||||
Ok(found.map(|u| UpdateInfo {
|
Ok(found.map(|u| UpdateInfo {
|
||||||
current: u.current_version.clone(),
|
current: u.current_version.clone(),
|
||||||
notes: u.body.clone().unwrap_or_default(),
|
notes: u.body.clone().unwrap_or_default(),
|
||||||
url: format!("https://github.com/lucasrguerra/guara/releases/tag/v{}", u.version),
|
url: format!("https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/tag/v{}", u.version),
|
||||||
version: u.version,
|
version: u.version,
|
||||||
auto: self_update_possible(),
|
auto: self_update_possible(),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Guara",
|
"productName": "Guara",
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"identifier": "dev.guara.app",
|
"identifier": "dev.guara.app",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../ui"
|
"frontendDist": "../ui"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDhENzRCRUU5NEI3QTAxQzgKUldUSUFYcEw2YjUwamFzMnpXM3ZKTEZPN1JIeXdLL3Q5SEF1M1FxQ284WFM0UWNsdlAyNUloWTEK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDhENzRCRUU5NEI3QTAxQzgKUldUSUFYcEw2YjUwamFzMnpXM3ZKTEZPN1JIeXdLL3Q5SEF1M1FxQ284WFM0UWNsdlAyNUloWTEK",
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://github.com/lucasrguerra/guara/releases/latest/download/latest.json"
|
"https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/latest/download/latest.json"
|
||||||
],
|
],
|
||||||
"windows": {
|
"windows": {
|
||||||
"installMode": "passive"
|
"installMode": "passive"
|
||||||
|
|||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Compila o app desktop (Tauri) e deixa os pacotes em dist-gui/.
|
||||||
|
#
|
||||||
|
# scripts/build-gui.sh linux # AppImage e .deb
|
||||||
|
# scripts/build-gui.sh windows # instalador NSIS, compilado a partir do Linux
|
||||||
|
# scripts/build-gui.sh windows-docker # o mesmo, num container, sem instalar nada
|
||||||
|
#
|
||||||
|
# O updater do Tauri assina cada pacote (createUpdaterArtifacts: true), então
|
||||||
|
# TAURI_SIGNING_PRIVATE_KEY e TAURI_SIGNING_PRIVATE_KEY_PASSWORD precisam estar
|
||||||
|
# no ambiente. Sem eles no ambiente, e com uma chave local em ~/.tauri (gerada
|
||||||
|
# por `tauri signer generate`), este script a carrega sozinho.
|
||||||
|
#
|
||||||
|
# O Windows usa cargo-xwin (SDK do Windows baixado na hora) e o NSIS; o Tauri
|
||||||
|
# documenta isso como compilação cruzada experimental. Precisa de: nsis, lld,
|
||||||
|
# llvm, clang e `cargo install cargo-xwin`. O .msi não sai daqui (o WiX é só do
|
||||||
|
# Windows).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
platform=${1:?uso: build-gui.sh <linux|windows|windows-docker>}
|
||||||
|
tauri() { npx --yes @tauri-apps/cli@2 "$@"; }
|
||||||
|
|
||||||
|
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ] && [ -f "$HOME/.tauri/guara-updater.key" ]; then
|
||||||
|
export TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/guara-updater.key")"
|
||||||
|
[ -f "$HOME/.tauri/guara-updater.pass" ] && export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat "$HOME/.tauri/guara-updater.pass")"
|
||||||
|
fi
|
||||||
|
: "${TAURI_SIGNING_PRIVATE_KEY:?defina TAURI_SIGNING_PRIVATE_KEY (ou gere a chave local com 'tauri signer generate')}"
|
||||||
|
|
||||||
|
if [ "$platform" = windows-docker ]; then
|
||||||
|
docker volume create guara-cargo >/dev/null
|
||||||
|
docker volume create guara-rustup >/dev/null
|
||||||
|
mkdir -p dist-gui
|
||||||
|
exec docker run --rm -v "$PWD":/src -v guara-cargo:/root/.cargo -v guara-rustup:/root/.rustup \
|
||||||
|
-w /src -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
|
||||||
|
-e TAURI_SIGNING_PRIVATE_KEY -e TAURI_SIGNING_PRIVATE_KEY_PASSWORD \
|
||||||
|
ubuntu:24.04 bash -c '
|
||||||
|
set -e
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq curl ca-certificates build-essential pkg-config nsis lld llvm clang nodejs npm unzip >/dev/null
|
||||||
|
[ -x /root/.cargo/bin/rustup ] || curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal >/dev/null
|
||||||
|
export PATH=/root/.cargo/bin:$PATH
|
||||||
|
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
|
||||||
|
scripts/build-gui.sh windows
|
||||||
|
chown -R "$HOST_UID:$HOST_GID" dist-gui apps/gui/src-tauri/target 2>/dev/null || true'
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p dist-gui
|
||||||
|
cd apps/gui/src-tauri
|
||||||
|
|
||||||
|
[ -f icons/icon.ico ] || tauri icon icons/source.png -o icons
|
||||||
|
|
||||||
|
case "$platform" in
|
||||||
|
linux)
|
||||||
|
tauri build --bundles appimage,deb
|
||||||
|
bundle=target/release/bundle
|
||||||
|
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
||||||
|
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
windows)
|
||||||
|
rustup target add x86_64-pc-windows-msvc >/dev/null
|
||||||
|
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
|
||||||
|
bundle=target/x86_64-pc-windows-msvc/release/bundle/nsis
|
||||||
|
cp "$bundle"/*-setup.exe ../../../dist-gui/
|
||||||
|
cp "$bundle"/*-setup.exe.sig ../../../dist-gui/ 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
*) echo "plataforma desconhecida: $platform" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ls -1 ../../../dist-gui
|
||||||
Regular → Executable
+26
-19
@@ -1,29 +1,37 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Monta o latest.json que o atualizador do app lê (Tauri updater), a partir dos pacotes e das
|
"""Monta o latest.json que o atualizador do app lê (Tauri updater), a partir dos pacotes e das
|
||||||
assinaturas `.sig` já enviados à release.
|
assinaturas `.sig` já enviados à release no Gitea.
|
||||||
|
|
||||||
Roda no job de publicação, depois que todos os jobs de empacotamento terminaram. Montar o arquivo
|
Roda no job de publicação, depois que os jobs de empacotamento terminaram. Montar o arquivo
|
||||||
aqui, em um passo só, evita a corrida de quem tenta atualizá-lo em paralelo por plataforma.
|
aqui, em um passo só, evita a corrida de quem tenta atualizá-lo em paralelo por plataforma.
|
||||||
|
|
||||||
Uso: make-latest-json.py <tag> <notas.md> <saida.json> (usa `gh` e GH_REPO/GITHUB_REPOSITORY)
|
Uso: make-latest-json.py <tag> <notas.md> <saida.json>
|
||||||
|
Precisa de API (URL do repositório na API do Gitea) e TOKEN no ambiente.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import urllib.request
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
# (chaves de plataforma, padrão do nome do pacote). {v} = versão sem o "v".
|
# (chaves de plataforma, padrão do nome do pacote). {v} = versão sem o "v".
|
||||||
|
# Sem macOS: não há runner para compilar essa plataforma.
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
(["linux-x86_64", "linux-x86_64-appimage"], "Guara_{v}_amd64.AppImage"),
|
(["linux-x86_64", "linux-x86_64-appimage"], "Guara_{v}_amd64.AppImage"),
|
||||||
(["windows-x86_64", "windows-x86_64-nsis"], "Guara_{v}_x64-setup.exe"),
|
(["windows-x86_64", "windows-x86_64-nsis"], "Guara_{v}_x64-setup.exe"),
|
||||||
(["windows-x86_64-msi"], "Guara_{v}_x64_en-US.msi"),
|
|
||||||
(["darwin-aarch64", "darwin-aarch64-app"], "Guara_{v}_aarch64.app.tar.gz"),
|
|
||||||
(["darwin-x86_64", "darwin-x86_64-app"], "Guara_{v}_x64.app.tar.gz"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def api_get(path):
|
||||||
|
req = urllib.request.Request(f"{os.environ['API']}{path}", headers={"Authorization": f"token {os.environ['TOKEN']}"})
|
||||||
|
return json.load(urllib.request.urlopen(req))
|
||||||
|
|
||||||
|
|
||||||
|
def api_get_text(url):
|
||||||
|
req = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['TOKEN']}"})
|
||||||
|
return urllib.request.urlopen(req).read().decode()
|
||||||
|
|
||||||
|
|
||||||
def build(version, notes, assets, pub_date):
|
def build(version, notes, assets, pub_date):
|
||||||
"""`assets` = {nome_do_pacote: (url, assinatura)}. Devolve o dicionário do latest.json.
|
"""`assets` = {nome_do_pacote: (url, assinatura)}. Devolve o dicionário do latest.json.
|
||||||
|
|
||||||
@@ -42,20 +50,19 @@ def build(version, notes, assets, pub_date):
|
|||||||
def main():
|
def main():
|
||||||
tag, notes_path, out = sys.argv[1:4]
|
tag, notes_path, out = sys.argv[1:4]
|
||||||
version = tag.lstrip("v")
|
version = tag.lstrip("v")
|
||||||
repo = os.environ.get("GH_REPO") or os.environ["GITHUB_REPOSITORY"]
|
rel = api_get(f"/releases/tags/{tag}")
|
||||||
rel = json.loads(subprocess.check_output(["gh", "release", "view", tag, "--repo", repo, "--json", "assets"]))
|
by_name = {a["name"]: a for a in rel["assets"]}
|
||||||
urls = {a["name"]: a["url"] for a in rel["assets"]}
|
|
||||||
assets = {}
|
assets = {}
|
||||||
with tempfile.TemporaryDirectory() as d:
|
for _, pattern in PLATFORMS:
|
||||||
for _, pattern in PLATFORMS:
|
name = pattern.format(v=version)
|
||||||
name = pattern.format(v=version)
|
sig_name = name + ".sig"
|
||||||
if name in urls and name + ".sig" in urls:
|
if name in by_name and sig_name in by_name:
|
||||||
subprocess.check_call(["gh", "release", "download", tag, "--repo", repo, "-p", name + ".sig", "-D", d])
|
sig = api_get_text(by_name[sig_name]["browser_download_url"])
|
||||||
assets[name] = (urls[name], open(os.path.join(d, name + ".sig")).read())
|
assets[name] = (by_name[name]["browser_download_url"], sig)
|
||||||
data = build(version, open(notes_path, encoding="utf-8").read(), assets,
|
data = build(version, open(notes_path, encoding="utf-8").read(), assets,
|
||||||
datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))
|
datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))
|
||||||
# Sem o Linux (AppImage) o atualizador do dono do projeto não funciona: não publica pela metade.
|
# Sem Linux e Windows o atualizador não funciona para ninguém: não publica pela metade.
|
||||||
for must in ("linux-x86_64", "windows-x86_64", "darwin-aarch64", "darwin-x86_64"):
|
for must in ("linux-x86_64", "windows-x86_64"):
|
||||||
if must not in data["platforms"]:
|
if must not in data["platforms"]:
|
||||||
sys.exit(f"faltam o pacote ou a assinatura de {must}; confira se TAURI_SIGNING_PRIVATE_KEY está nos segredos")
|
sys.exit(f"faltam o pacote ou a assinatura de {must}; confira se TAURI_SIGNING_PRIVATE_KEY está nos segredos")
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
with open(out, "w", encoding="utf-8") as f:
|
||||||
|
|||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Compila o CLI para uma plataforma e empacota: guara-<tag>-<plataforma>.tar.gz
|
||||||
|
# com um .sha256 ao lado.
|
||||||
|
#
|
||||||
|
# scripts/package-cli.sh v1.3.2 linux-x86_64 x86_64-unknown-linux-gnu
|
||||||
|
#
|
||||||
|
# Plataformas: linux-x86_64 e linux-aarch64. O alvo diferente da máquina de
|
||||||
|
# build usa o linker cruzado correspondente (gcc-x86-64-linux-gnu). Escreve os
|
||||||
|
# arquivos na raiz do repositório e imprime os nomes.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
tag=${1:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
|
||||||
|
platform=${2:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
|
||||||
|
target=${3:?uso: package-cli.sh <tag> <plataforma> <alvo-rust>}
|
||||||
|
|
||||||
|
if [ "$target" = x86_64-unknown-linux-gnu ] && [ "$(uname -m)" != x86_64 ]; then
|
||||||
|
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
|
||||||
|
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
|
||||||
|
fi
|
||||||
|
|
||||||
|
rustup target add "$target" >/dev/null 2>&1 || true
|
||||||
|
cargo build --release --locked -p guara-cli --target "$target"
|
||||||
|
|
||||||
|
name="guara-${tag}-${platform}"
|
||||||
|
rm -rf "$name"; mkdir "$name"
|
||||||
|
cp "${CARGO_TARGET_DIR:-target}/${target}/release/guara" "$name/"
|
||||||
|
cp README.md LICENSE-MIT LICENSE-APACHE "$name/"
|
||||||
|
|
||||||
|
tar czf "$name.tar.gz" "$name"
|
||||||
|
asset="$name.tar.gz"
|
||||||
|
rm -rf "$name"
|
||||||
|
sha256sum "$asset" > "$asset.sha256"
|
||||||
|
echo "$asset"
|
||||||
|
echo "$asset.sha256"
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Anexa arquivos a uma release do Gitea, trocando os de mesmo nome (assim uma
|
||||||
|
# execução repetida do CI não falha por anexo duplicado).
|
||||||
|
#
|
||||||
|
# API=https://git.exemplo.com/api/v1/repos/dono/repo TOKEN=... \
|
||||||
|
# scripts/release-upload.sh v1.2.3 arquivo1 arquivo2
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${API:?defina API (URL do repositório na API do Gitea)}"
|
||||||
|
: "${TOKEN:?defina TOKEN}"
|
||||||
|
tag=${1:?uso: release-upload.sh <tag> <arquivo>...}
|
||||||
|
shift
|
||||||
|
|
||||||
|
auth=(-H "Authorization: token $TOKEN")
|
||||||
|
id=$(curl -fsS "${auth[@]}" "$API/releases/tags/$tag" | jq -r .id)
|
||||||
|
|
||||||
|
for file in "$@"; do
|
||||||
|
name=$(basename "$file")
|
||||||
|
for old in $(curl -fsS "${auth[@]}" "$API/releases/$id/assets" | jq -r --arg n "$name" '.[] | select(.name == $n) | .id'); do
|
||||||
|
curl -fsS -X DELETE "${auth[@]}" "$API/releases/$id/assets/$old"
|
||||||
|
done
|
||||||
|
curl -fsS -X POST "${auth[@]}" -F "attachment=@$file" "$API/releases/$id/assets?name=$name" >/dev/null
|
||||||
|
echo "enviado: $name"
|
||||||
|
done
|
||||||
@@ -8,37 +8,34 @@ spec = importlib.util.spec_from_file_location("m", os.path.join(os.path.dirname(
|
|||||||
m = importlib.util.module_from_spec(spec)
|
m = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(m)
|
spec.loader.exec_module(m)
|
||||||
|
|
||||||
BASE = "https://github.com/x/y/releases/download/v1.3.0/"
|
BASE = "https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/download/v1.3.2/"
|
||||||
|
|
||||||
|
|
||||||
def assets(version="1.3.0"):
|
def assets(version="1.3.2"):
|
||||||
names = [p.format(v=version) for _, p in m.PLATFORMS]
|
names = [p.format(v=version) for _, p in m.PLATFORMS]
|
||||||
return {n: (BASE + n, f"assinatura-de-{n}\n") for n in names}
|
return {n: (BASE + n, f"assinatura-de-{n}\n") for n in names}
|
||||||
|
|
||||||
|
|
||||||
class LatestJson(unittest.TestCase):
|
class LatestJson(unittest.TestCase):
|
||||||
def test_all_platforms_and_variants(self):
|
def test_all_platforms_and_variants(self):
|
||||||
d = m.build("1.3.0", "### Novidades\n", assets(), "2026-10-23T12:00:00Z")
|
d = m.build("1.3.2", "### Novidades\n", assets(), "2026-10-30T12:00:00Z")
|
||||||
self.assertEqual(d["version"], "1.3.0")
|
self.assertEqual(d["version"], "1.3.2")
|
||||||
self.assertEqual(d["notes"], "### Novidades")
|
self.assertEqual(d["notes"], "### Novidades")
|
||||||
p = d["platforms"]
|
p = d["platforms"]
|
||||||
for k in ("linux-x86_64", "linux-x86_64-appimage", "windows-x86_64", "windows-x86_64-nsis",
|
for k in ("linux-x86_64", "linux-x86_64-appimage", "windows-x86_64", "windows-x86_64-nsis"):
|
||||||
"windows-x86_64-msi", "darwin-aarch64", "darwin-x86_64"):
|
|
||||||
self.assertIn(k, p)
|
self.assertIn(k, p)
|
||||||
# a chave genérica do Windows aponta para o instalador NSIS, não para o MSI
|
|
||||||
self.assertTrue(p["windows-x86_64"]["url"].endswith("x64-setup.exe"))
|
self.assertTrue(p["windows-x86_64"]["url"].endswith("x64-setup.exe"))
|
||||||
self.assertTrue(p["windows-x86_64-msi"]["url"].endswith(".msi"))
|
self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guara_1.3.2_amd64.AppImage")
|
||||||
self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guara_1.3.0_amd64.AppImage")
|
|
||||||
|
|
||||||
def test_missing_package_is_left_out(self):
|
def test_missing_package_is_left_out(self):
|
||||||
a = assets()
|
a = assets()
|
||||||
del a["Guara_1.3.0_x64.app.tar.gz"]
|
del a["Guara_1.3.2_x64-setup.exe"]
|
||||||
d = m.build("1.3.0", "", a, "2026-10-23T12:00:00Z")
|
d = m.build("1.3.2", "", a, "2026-10-30T12:00:00Z")
|
||||||
self.assertNotIn("darwin-x86_64", d["platforms"])
|
self.assertNotIn("windows-x86_64", d["platforms"])
|
||||||
self.assertIn("darwin-aarch64", d["platforms"])
|
self.assertIn("linux-x86_64", d["platforms"])
|
||||||
|
|
||||||
def test_other_versions_do_not_leak(self):
|
def test_other_versions_do_not_leak(self):
|
||||||
d = m.build("1.3.0", "", assets("1.2.0"), "2026-10-23T12:00:00Z")
|
d = m.build("1.3.2", "", assets("1.2.0"), "2026-10-30T12:00:00Z")
|
||||||
self.assertEqual(d["platforms"], {})
|
self.assertEqual(d["platforms"], {})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user