release 1.3.2: update from Gitea, Linux-only CLI, desktop app for Linux and Windows
CI / Formatação e clippy (push) Successful in 2m5s
CI / Auditoria de dependências (push) Successful in 10s
CI / Compila o app desktop (Linux) (push) Successful in 1m14s
CI / Testes (Linux) (push) Successful in 4m36s

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:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-30 13:41:21 -03:00
1 parent 8ba3151512
commit b47708126e
14 files changed
+317 -44

No files matched your search

+106 -5
View File
@@ -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
+4
View File
@@ -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
+17
View File
@@ -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
+17
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+2 -2
View File
@@ -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]
+1 -1
View File
@@ -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(),
})) }))
+2 -2
View File
@@ -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"
+70
View File
@@ -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
+24 -17
View File
@@ -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)
if name in urls and name + ".sig" in urls: sig_name = name + ".sig"
subprocess.check_call(["gh", "release", "download", tag, "--repo", repo, "-p", name + ".sig", "-D", d]) if name in by_name and sig_name in by_name:
assets[name] = (urls[name], open(os.path.join(d, name + ".sig")).read()) sig = api_get_text(by_name[sig_name]["browser_download_url"])
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:
+36
View File
@@ -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"
+24
View File
@@ -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
+11 -14
View File
@@ -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"], {})