Como distribuir apps de desktop criados por um desenvolvedor solo sem avisos de segurança
TuBrief 편집팀
2026년 7월 15일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Se você terminou um aplicativo de desktop que roda perfeitamente localmente, o desenvolvimento está apenas pela metade. O verdadeiro obstáculo começa no momento em que o usuário clica no link de download e uma janela de aviso vermelha aparece na tela, dizendo algo como "arquivo corrompido" ou "o computador foi protegido".
Superar as barreiras de segurança do sistema operacional, controlar a velocidade de build e criar um sistema de atualização automática que mantenha a versão mais recente sempre que o usuário abrir o app é algo mais cansativo do que parece. Mesmo que você tenha escolhido o Tauri v2 por ser mais leve que o Electron, os problemas práticos do processo de distribuição permanecem. Reuni abaixo formas de desenvolvedores solo ou pequenas equipes distribuírem seus produtos de forma limpa, sem desperdiçar tempo e dinheiro desnecessários.
Um app de desktop sem Code Signing (assinatura de código) é tratado como malware pelo sistema operacional. Para evitar que avisos de segurança apareçam no PC do usuário, você precisa de dinheiro e burocracia.
Para distribuir no macOS, é obrigatório aderir ao Apple Developer Program, que custa 99 dólares por ano. Uma vez obtida a conta, você deve criar um arquivo src-tauri/Entitlements.plist que defina as permissões de exceção de segurança de memória para que o webview do Tauri funcione corretamente. Se essa configuração faltar, o app fechará imediatamente após ser aberto.
`xml
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
`
Especifique este arquivo na opção de bundle do seu src-tauri/tauri.conf.json.
`json
{
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: Your Name (TEAMID)",
"entitlements": "./Entitlements.plist",
"minimumSystemVersion": "11.0",
"dmg": {
"appPosition": { "x": 180, "y": 170 },
"applicationFolderPosition": { "x": 480, "y": 170 }
}
}
}
}
`
Antigamente, para passar pelo filtro SmartScreen do Windows, era necessário obter um certificado EV (Extended Validation) na forma de um token USB físico, custando entre 400 e 700 dólares por ano. Além do custo, é extremamente trabalhoso para um indivíduo gerenciar.
A alternativa é o Azure Trusted Signing (ATS), o serviço de assinatura baseado em nuvem da Microsoft. Pagando apenas uma taxa de assinatura mensal na casa dos 9,99 dólares, o processo de assinatura é feito dentro da nuvem HSM gerenciada pela Microsoft, eliminando a necessidade de manter chaves físicas.
AZURE_TENANT_ID, CLIENT_ID, CLIENT_SECRET) e as informações do ATS.sign-tool para aplicar a assinatura digital nos arquivos MSI ou EXE compilados pelo Tauri.Apps assinados desta forma evitam os avisos do Windows SmartScreen desde o primeiro download, ajudando a reter usuários que poderiam desistir durante a instalação.
O Tauri é leve, mas o processo de build exige rodar o compilador Rust e a cadeia de ferramentas nativa de cada SO. É comum ter erros de linker no computador de outros membros da equipe ou ter builds corrompidos devido à poluição de dependências do ambiente local, mesmo que funcione na sua máquina. Builds de distribuição devem, obrigatoriamente, rodar em um pipeline de CI/CD isolado.
O problema é que o executor hospedado padrão do GitHub não tem especificações suficientes para compilar Rust rapidamente. Se a estrutura baixa as dependências e compila tudo do zero a cada vez, um build de release pode facilmente levar mais de 10 minutos.
Neste caso, em vez de usar apenas o actions/cache para compactar e enviar arquivos para a nuvem, combinar plugins de cache dedicados que visam armazenamento NVMe de alto desempenho (swatinem/rust-cache) ou usar executores hospedados dedicados (como Namespace ou Depot) mudará drasticamente a velocidade.
Com base nos logs de build do projeto open source de player de música spotify-player, aqui está a comparação de desempenho entre um executor comum do GitHub e um executor dedicado com cache de volume local aplicado:
| Plataforma e Configuração de Cache | Tempo no executor comum do GitHub | Tempo com otimização de cache | Redução no tempo de build |
|---|---|---|---|
| Ubuntu Linux | 9min 31s | 34s | 94,0% |
| macOS Darwin | 9min 31s | 27s | 95,2% |
| Windows MSVC | 9min 31s | 44s | 92,2% |
| Custo do Workflow | $0,44 por execução | $0,074 por execução | 83,1% de economia |
Simplesmente conectar uma infraestrutura de cache de volume persistente reduz o tempo de espera de build da equipe de desenvolvimento em pelo menos 40%.
Configure a automação de distribuição no arquivo .github/workflows/publish.yml da seguinte maneira:
`yaml
jobs:
build-binaries:
strategy:
matrix:
platform: [macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
# ... Após concluir os passos de build, chame o tauri-action
`
Ao colocar o tauri-apps/tauri-action no final do fluxo de trabalho, a cada novo tag enviado, o instalador assinado para ambos os sistemas operacionais será registrado automaticamente no GitHub Release Draft.
Ao empacotar o instalador para Windows, você precisa decidir o método de instalação do WebView2, que é o mecanismo do webview. Se a conexão com a internet for garantida e o tamanho do arquivo de download precisar ser reduzido ao extremo, o método downloadBootstrapper é aceitável, pois não aumenta o tamanho do pacote. Por outro lado, se você estiver visando redes fechadas ou ambientes offline, é mais seguro incluir o offlineInstaller, mesmo que isso adicione cerca de 127 MB ao arquivo de instalação.
A forma como você lida com os dados ao operar um app baseado em Tauri também é importante. É arriscado confiar apenas no IndexedDB ou LocalStorage do navegador para armazenar dados.
Na verdade, na transição do Tauri v1 para o v2, houve uma mudança interna no esquema de domínio do webview no ambiente Windows, indo de [https://tauri.localhost](https://tauri.localhost) para [http://tauri.localhost](http://tauri.localhost). Por causa disso, o caminho do cache do navegador foi alterado à força, fazendo com que muitos perdessem seus dados existentes.
Para evitar o desastre de perder dados após a distribuição, as informações principais devem ser armazenadas diretamente como arquivos SQLite na área do sistema de arquivos nativo, em vez de no armazenamento do webview. A API appDataDir do Tauri v2 encontra automaticamente o caminho seguro de sandboxing adequado ao padrão do sistema operacional:
C:\Users\<UserName>\AppData\Roaming\<BundleIdentifier>/Users/<UserName>/Library/Application Support/<BundleIdentifier>Um exemplo de como intervir no ciclo de vida do app dentro do código Rust do backend do Tauri v2 (src-tauri/src/lib.rs) para vincular o banco de dados SQLite à área segura e rodar a migração de esquema é:
`rust
use std::fs;
use tauri::Manager;
use tauri_plugin_sql::{Migration, MigrationKind};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let database_migrations = vec![
Migration {
version: 1,
description: "initialize_user_profiles_table",
sql: "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);",
kind: MigrationKind::Up,
}
];
tauri::Builder::default()
.setup(|app| {
let local_app_dir = app.path().app_data_dir()
.expect("Critical: Could not resolve target operating system app data path.");
if !local_app_dir.exists() {
fs::create_dir_all(&local_app_dir)
.expect("Critical: Failed to establish persistent storage directory structure.");
}
Ok(())
})
.plugin(
tauri_plugin_sql::Builder::default()
.add_migrations("sqlite:users.db", database_migrations)
.build()
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
`
Configurando desta forma, mesmo que o cache interno do webview do Electron ou Chromium seja limpo devido a uma atualização automática ou reinstalação, o banco de dados real do usuário será preservado com segurança.
O método de induzir o usuário a visitar a página inicial toda vez para baixar a nova versão aumenta a taxa de rejeição. É necessário estruturar um sistema que sirva silenciosamente os arquivos de atualização, conectando um armazenamento de objetos em nuvem (Object Storage) e uma CDN.
A combinação de Cloudflare R2 e AWS CloudFront é eficiente como servidor de distribuição. O Cloudflare R2 não possui taxas de saída de dados (Egress Fees), permitindo manter o custo de tráfego de rede gerado ao liberar grandes arquivos de atualização em zero.
O arquivo de metadados (latest.json), consultado pelo cliente para verificar se existe uma nova versão, não deve ser armazenado em cache na CDN ou no navegador. A política abaixo deve ser especificada no cabeçalho de resposta:
`http
Cache-Control: no-cache, no-store, must-revalidate
`
Por outro lado, os arquivos binários de instalação reais, como contêm um valor de hash único, são imutáveis (Immutable); portanto, configure-os para serem mantidos na CDN pelo maior tempo possível para aliviar a carga de tráfego do servidor de origem.
`http
Cache-Control: public, max-age=31536000, immutable
`
No Tauri v2, a localização das opções relacionadas à atualização mudou para dentro do bloco plugins.updater. Abaixo está a especificação de configuração do tauri.conf.json:
`json
{
"bundle": {
"createUpdaterArtifacts": true
},
"plugins": {
"updater": {
"active": true,
"endpoints": [
"https://cdn.myapp.com/releases/latest.json"
],
"dialog": false,
"pubkey": "dW5zaWduZWQgYm91bmRmaXg...",
"windows": {
"installMode": "passive"
}
}
}
}
`
Para que o usuário no Windows atualize sem precisar clicar em janelas de confirmação irritantes, o installMode deve ser definido como passive ou quiet. O modo passive exibe apenas uma barra de progresso discreta em vez de uma janela de assistente de instalação, concluindo a substituição silenciosamente.
Uma vez finalizada a configuração, integre @tauri-apps/plugin-updater e @tauri-apps/plugin-process na parte do frontend para verificar novos patches no momento em que o app for aberto e induzir a reinicialização.
`typescript
import { check } from "@tauri-apps/plugin-updater";
import { ask } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
export async function runBackgroundUpdater(): Promise {
try {
const updatePayload = await check();
if (updatePayload && updatePayload.available) {
const userResponse = await ask(
`Uma nova versão [v${updatePayload.version}] está disponível. Deseja atualizar agora e reiniciar o aplicativo?`,
{
title: "Aviso de atualização automática de software",
kind: "info",
okLabel: "Instalar atualização e reiniciar",
cancelLabel: "Aplicar mais tarde"
}
);
if (userResponse) {
await updatePayload.downloadAndInstall();
await relaunch();
}
}
} catch (error) {
console.error("Ocorreu um erro durante o processo de verificação de atualização automática:", error);
}
}
`
Ao colocar esta função na etapa de montagem inicial do seu componente React ou View principal, o usuário sempre terá a versão mais recente do software sem precisar vasculhar o site diretamente.
appDataDir controlado nativamente e execute migrações de esquema a longo prazo para que os dados não fiquem corrompidos durante a atualização do app.