From b47708126e2153d105cde19c520657ef132c3f54 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 13:41:21 -0300 Subject: [PATCH] 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 --- .gitea/workflows/release.yml | 111 +++++++++++++++++++++++++++-- .gitignore | 4 ++ CHANGELOG.md | 17 +++++ CHANGELOG.pt-BR.md | 17 +++++ Cargo.lock | 4 +- Cargo.toml | 2 +- apps/gui/src-tauri/Cargo.toml | 4 +- apps/gui/src-tauri/src/main.rs | 2 +- apps/gui/src-tauri/tauri.conf.json | 4 +- scripts/build-gui.sh | 70 ++++++++++++++++++ scripts/make-latest-json.py | 45 +++++++----- scripts/package-cli.sh | 36 ++++++++++ scripts/release-upload.sh | 24 +++++++ scripts/test_make_latest_json.py | 25 +++---- 14 files changed, 319 insertions(+), 46 deletions(-) create mode 100755 scripts/build-gui.sh mode change 100644 => 100755 scripts/make-latest-json.py create mode 100755 scripts/package-cli.sh create mode 100755 scripts/release-upload.sh diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index c24da82..2de32fd 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,14 +1,25 @@ name: Release -# Cria a entrada em "Releases" quando uma tag v* é enviada, com as notas da -# seção correspondente do CHANGELOG.md (scripts/release-notes.sh). +# Quando uma tag v* é enviada: cria a entrada em "Releases" com as notas do +# 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 -# não tem (o único runner é Linux arm64), então não são gerados neste fluxo. +# Sem macOS: o Tauri precisa do SDK e da assinatura da Apple, presos a +# 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: push: tags: ['v*'] +env: + CARGO_TERM_COLOR: always + CARGO_HOME: /opt/hostedtoolcache/cargo + RUSTUP_HOME: /opt/hostedtoolcache/rustup + jobs: release: name: Cria a release @@ -23,9 +34,99 @@ jobs: TOKEN: ${{ gitea.token }} API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }} 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 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" \ '{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json 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 diff --git a/.gitignore b/.gitignore index b8e627b..0d63283 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ node_modules/ *.img *.guara-session.json .DS_Store + +# Pacotes gerados por scripts/package-cli.sh +/guara-v* +/dist-gui diff --git a/CHANGELOG.md b/CHANGELOG.md index e9e7191..bc2deef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ 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 ### Changed diff --git a/CHANGELOG.pt-BR.md b/CHANGELOG.pt-BR.md index a4d1f57..43aa65d 100644 --- a/CHANGELOG.pt-BR.md +++ b/CHANGELOG.pt-BR.md @@ -4,6 +4,23 @@ 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 ### Alterado diff --git a/Cargo.lock b/Cargo.lock index a7031c9..bf22dc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,7 +416,7 @@ dependencies = [ [[package]] name = "guara-cli" -version = "1.3.1" +version = "1.3.2" dependencies = [ "clap", "ctrlc", @@ -429,7 +429,7 @@ dependencies = [ [[package]] name = "guara-core" -version = "1.3.1" +version = "1.3.2" dependencies = [ "aes", "argon2", diff --git a/Cargo.toml b/Cargo.toml index e902ae6..ca9847a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["crates/guara-core", "crates/guara-cli"] [workspace.package] -version = "1.3.1" +version = "1.3.2" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/lucasrguerra/guara" diff --git a/apps/gui/src-tauri/Cargo.toml b/apps/gui/src-tauri/Cargo.toml index 594f41a..e3a6977 100644 --- a/apps/gui/src-tauri/Cargo.toml +++ b/apps/gui/src-tauri/Cargo.toml @@ -3,10 +3,10 @@ [package] name = "guara-gui" description = "Guará: interface gráfica de recuperação de arquivos" -version = "1.3.1" +version = "1.3.2" edition = "2021" license = "MIT OR Apache-2.0" -repository = "https://github.com/lucasrguerra/guara" +repository = "https://git.lucasrguerra.dev.br/lucasrguerra/guara" rust-version = "1.80" [workspace] diff --git a/apps/gui/src-tauri/src/main.rs b/apps/gui/src-tauri/src/main.rs index 12b0375..93bcae4 100644 --- a/apps/gui/src-tauri/src/main.rs +++ b/apps/gui/src-tauri/src/main.rs @@ -39,7 +39,7 @@ async fn update_check(app: tauri::AppHandle) -> Result, Strin Ok(found.map(|u| UpdateInfo { current: u.current_version.clone(), 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, auto: self_update_possible(), })) diff --git a/apps/gui/src-tauri/tauri.conf.json b/apps/gui/src-tauri/tauri.conf.json index a9d97a2..f784e13 100644 --- a/apps/gui/src-tauri/tauri.conf.json +++ b/apps/gui/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Guara", - "version": "1.3.1", + "version": "1.3.2", "identifier": "dev.guara.app", "build": { "frontendDist": "../ui" @@ -41,7 +41,7 @@ "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDhENzRCRUU5NEI3QTAxQzgKUldUSUFYcEw2YjUwamFzMnpXM3ZKTEZPN1JIeXdLL3Q5SEF1M1FxQ284WFM0UWNsdlAyNUloWTEK", "endpoints": [ - "https://github.com/lucasrguerra/guara/releases/latest/download/latest.json" + "https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/latest/download/latest.json" ], "windows": { "installMode": "passive" diff --git a/scripts/build-gui.sh b/scripts/build-gui.sh new file mode 100755 index 0000000..984910f --- /dev/null +++ b/scripts/build-gui.sh @@ -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 } +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 diff --git a/scripts/make-latest-json.py b/scripts/make-latest-json.py old mode 100644 new mode 100755 index 0ca6e89..b2b89bf --- a/scripts/make-latest-json.py +++ b/scripts/make-latest-json.py @@ -1,29 +1,37 @@ #!/usr/bin/env python3 """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. -Uso: make-latest-json.py (usa `gh` e GH_REPO/GITHUB_REPOSITORY) +Uso: make-latest-json.py +Precisa de API (URL do repositório na API do Gitea) e TOKEN no ambiente. """ import json import os -import subprocess import sys -import tempfile +import urllib.request from datetime import datetime, timezone # (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 = [ (["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-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): """`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(): tag, notes_path, out = sys.argv[1:4] version = tag.lstrip("v") - repo = os.environ.get("GH_REPO") or os.environ["GITHUB_REPOSITORY"] - rel = json.loads(subprocess.check_output(["gh", "release", "view", tag, "--repo", repo, "--json", "assets"])) - urls = {a["name"]: a["url"] for a in rel["assets"]} + rel = api_get(f"/releases/tags/{tag}") + by_name = {a["name"]: a for a in rel["assets"]} assets = {} - with tempfile.TemporaryDirectory() as d: - for _, pattern in PLATFORMS: - name = pattern.format(v=version) - if name in urls and name + ".sig" in urls: - subprocess.check_call(["gh", "release", "download", tag, "--repo", repo, "-p", name + ".sig", "-D", d]) - assets[name] = (urls[name], open(os.path.join(d, name + ".sig")).read()) + for _, pattern in PLATFORMS: + name = pattern.format(v=version) + sig_name = name + ".sig" + if name in by_name and sig_name in by_name: + 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, 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. - for must in ("linux-x86_64", "windows-x86_64", "darwin-aarch64", "darwin-x86_64"): + # 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"): 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") with open(out, "w", encoding="utf-8") as f: diff --git a/scripts/package-cli.sh b/scripts/package-cli.sh new file mode 100755 index 0000000..10cc590 --- /dev/null +++ b/scripts/package-cli.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Compila o CLI para uma plataforma e empacota: guara--.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 } +platform=${2:?uso: package-cli.sh } +target=${3:?uso: package-cli.sh } + +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" diff --git a/scripts/release-upload.sh b/scripts/release-upload.sh new file mode 100755 index 0000000..3b1237d --- /dev/null +++ b/scripts/release-upload.sh @@ -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 ...} +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 diff --git a/scripts/test_make_latest_json.py b/scripts/test_make_latest_json.py index 4d52ca8..7fc97bb 100644 --- a/scripts/test_make_latest_json.py +++ b/scripts/test_make_latest_json.py @@ -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) 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] return {n: (BASE + n, f"assinatura-de-{n}\n") for n in names} class LatestJson(unittest.TestCase): def test_all_platforms_and_variants(self): - d = m.build("1.3.0", "### Novidades\n", assets(), "2026-10-23T12:00:00Z") - self.assertEqual(d["version"], "1.3.0") + d = m.build("1.3.2", "### Novidades\n", assets(), "2026-10-30T12:00:00Z") + self.assertEqual(d["version"], "1.3.2") self.assertEqual(d["notes"], "### Novidades") p = d["platforms"] - 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"): + for k in ("linux-x86_64", "linux-x86_64-appimage", "windows-x86_64", "windows-x86_64-nsis"): 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-msi"]["url"].endswith(".msi")) - self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guara_1.3.0_amd64.AppImage") + self.assertEqual(p["linux-x86_64"]["signature"], "assinatura-de-Guara_1.3.2_amd64.AppImage") def test_missing_package_is_left_out(self): a = assets() - del a["Guara_1.3.0_x64.app.tar.gz"] - d = m.build("1.3.0", "", a, "2026-10-23T12:00:00Z") - self.assertNotIn("darwin-x86_64", d["platforms"]) - self.assertIn("darwin-aarch64", d["platforms"]) + del a["Guara_1.3.2_x64-setup.exe"] + d = m.build("1.3.2", "", a, "2026-10-30T12:00:00Z") + self.assertNotIn("windows-x86_64", d["platforms"]) + self.assertIn("linux-x86_64", d["platforms"]) 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"], {})