Pular para o conteúdo

Assinatura e atualização (0.16)

Este documento é para uma pessoa, uma vez. Nada aqui se faz duas vezes, e é por isso que está escrito: quem fizer isso não vai lembrar dos detalhes daqui a seis meses, e quem herdar o projeto nunca os soube.

Tudo o que está aqui é opcional. Enquanto nada disto for feito, o repositório compila, o release.yml roda, e a página de release sai como sempre saiu: um instalador por sistema, para baixar à mão, com os avisos do sistema operacional que as NOTAS-DE-RELEASE.md explicam. O que falta sem isto é o botão de atualizar e o silêncio do SmartScreen.

---

O que o botão de atualizar diz quando não dá, e por quê

São sete respostas possíveis, e na tela cada uma é uma frase — o estado, e o que fazer. O porquê fica aqui, porque ele não muda o que a pessoa faz e cabia mal embaixo de um botão.

Duas delas mandam não tentar de novo, e as duas são as que mais custam se alguém insistir:

A tela dizO que houvePor que não adianta insistir
«este SEELE saiu sem chave de atualização»Este executável foi compilado antes de a chave do projeto existir, ou saiu do código-fonte na sua máquinaNão há chave dentro dele para conferir pacote nenhum. Não é defeito, e nenhuma versão futura conserta este binário: baixe o instalador novo da página de releases
«o pacote baixado não foi assinado por este projeto»O pacote chegou inteiro e a assinatura não é a nossaApertar de novo baixa o mesmo pacote da mesma origem. Baixe da página de releases, e — se o SEELE que você está atualizando veio de outro lugar — confira com quem o hospedou de onde ele veio

As outras cinco:

A tela dizO que houve
«não consegui perguntar se há versão nova»A rede falhou, ou a página de releases respondeu algo que o app não entendeu. Nada foi baixado. Tentar de novo faz sentido
«ainda não há versão publicada para baixar»A rede funcionou. A página respondeu, e não há nela nenhum release com manifesto de atualização. Enquanto não sair um por este caminho, este app é o mais recente que existe
«há versão nova, mas não para este sistema nem para este processador»O release existe e não traz pacote para este sistema operacional ou esta arquitetura
«a troca dos arquivos falhou»O pacote chegou inteiro e conferido, e a substituição dos arquivos não completou. Quase sempre é outra cópia do SEELE aberta
«não há versão nova escolhida para instalar»Instalar sempre instala o que a última procura encontrou, e não houve procura

A diferença entre as duas primeiras da segunda tabela é a que este projeto errou uma vez, e vale escrever: o botão dizia «a página não respondeu» sobre uma página que tinha respondido perfeitamente. Mandar conferir a conexão é mandar procurar defeito onde não há. São variantes separadas por isso.

Só uma das sete fala em «meia instalação», a da troca de arquivos, e é a única em que ela poderia existir. Nas outras seis nada instalado chega a ser tocado: o pacote é baixado inteiro para a memória e a assinatura é conferida antes do primeiro arquivo. Rede que cai, download truncado, pacote adulterado — os três terminam com o app exatamente como estava, e uma frase que tranquilizasse sobre isso estaria inventando o susto que ela vem acalmar.

---

São duas assinaturas, e elas não se substituem

Confundir as duas é o erro que faz o trabalho todo parecer redundante.

quem confereo que ela respondesem ela
do sistema operacionalWindows e macOS, na hora de abrir«quem produziu este arquivo?»o SmartScreen reclama e o Smart App Control bloqueia
do projetoo próprio SEELE, na hora de atualizar«este pacote veio de vocês?»o botão de atualizar não existe

Um instalador com assinatura Authenticode perfeita continua sendo qualquer instalador: é o latest.json que diz qual baixar, e é a assinatura do projeto que impede que ele aponte para outro. As duas são necessárias e nenhuma cobre o buraco da outra. ADR 0026.

Faça a do projeto primeiro. Ela é gratuita, leva cinco minutos, e é a que resolve a queixa que custou um teste real — as duas máquinas em versões diferentes.

E no Windows são três defesas, não uma

Este documento dizia «SmartScreen» em todo lugar, como se fosse uma coisa só. Não é, e a diferença decide se assinar é conforto ou se é o que torna o programa instalável. A pergunta veio de quem estava pagando a conta, o que é a hora certa de ela vir.

o que fazassinar resolve?
SmartScreenavisa: «O Windows protegeu o computador»sim, e há contorno sem assinar — *Mais informações → Executar assim mesmo*
Smart App Controlimpede a execução, e não oferece «executar assim mesmo»sim, e é a única saída
Defender Antivírusvarre em busca de malwarenão. Se ele acusar, é falso positivo, e o caminho é submeter a amostra à Microsoft

O Smart App Control é o que mais surpreende, e foi o que apareceu no primeiro teste com máquina de outra pessoa. Ele é do Windows 11, só liga em instalação limpa, e passa um tempo em avaliação antes de se ligar sozinho — então duas máquinas iguais se comportam diferente e ninguém entende por quê. Quando está ligado não há caixa a dispensar: o programa não abre.

Uma consequência prática: desligar o Smart App Control é caminho sem volta. Depois de desligado, só reinstalando o Windows para religá-lo. Não é conselho a dar a quem só queria conversar com um amigo — o que se dá é um instalador assinado.

E vale dizer o que a assinatura não compra: reputação é acumulada, então um certificado novo pode continuar tomando aviso do SmartScreen nas primeiras instalações, até o serviço ver o arquivo circular. O Smart App Control é o caso em que a assinatura vale imediatamente.

---

Parte 1 — a chave do projeto (grátis, cinco minutos)

É uma chave minisign. O cargo tauri a gera.

sh
cargo install tauri-cli --version "^2" --locked   # se ainda não tiver
mkdir -p ~/.tauri
cargo tauri signer generate -w ~/.tauri/seele.key

Ele pede uma senha. Use uma, e guarde-a onde guarda as outras — o modo sem senha existe para automação e não é o seu caso. No fim ficam dois arquivos:

1.1 — a pública vai para o tauri.conf.json

Abra ~/.tauri/seele.key.pub. É uma única linha em base64. Cole-a em apps/seele-app/tauri.conf.json, no lugar da string vazia:

json
  "plugins": {
    "updater": {
      "endpoints": [
        "https://github.com/DATA-AND-DEV/SEELE-RELEASES/releases/latest/download/latest.json"
      ],
      "pubkey": "COLE-A-LINHA-AQUI",

Cuidado com o arquivo: ele não pode ganhar BOM. Se editar no Bloco de Notas do Windows, salve como "UTF-8" e não como "UTF-8 com BOM" — o analisador do Tauri recusa o arquivo e a mensagem que ele dá (expected value at line 1 column 1) não parece ter nada a ver.

Comite essa mudança. A chave pública é pública; é isso que ela é.

1.2 — a privada vira segredo do repositório

Em Settings → Secrets and variables → Actions → New repository secret:

segredoconteúdo
TAURI_SIGNING_PRIVATE_KEYo conteúdo inteiro de ~/.tauri/seele.key, copiado e colado
TAURI_SIGNING_PRIVATE_KEY_PASSWORDa senha que você escolheu

O conteúdo do arquivo, e não o caminho dele: o runner não tem o seu disco.

1.3 — guarde a privada onde você não a perca

Escrito sem rodeio, porque é a única parte irreversível deste documento:

Se você perder a chave privada ou a senha, ninguém que já instalou o SEELE vai conseguir atualizar de novo. Não há como rotacionar a chave para quem já está instalado: o app confere contra a chave que foi compilada dentro dele. A saída é publicar uma versão com a chave nova e pedir a cada pessoa que baixe e instale à mão — exatamente o problema que este trabalho existe para acabar.

Duas cópias, em lugares que não falham juntos. Um gerenciador de senhas e um papel guardado servem.

1.4 — conferir

Rode o workflow Release pela aba Actions, com uma versão de teste (0.0.1, por exemplo). Nos registros do job de cada sistema você deve ver a CLI do Tauri dizendo Finished 1 updater signature at: e, no job publicar, o latest.json impresso inteiro. Se ele não aparecer, o passo Manifesto de atualização diz por quê.

O rascunho fica em Releases, não publicado. Enquanto ele não for publicado à mão, nenhum app do mundo o enxerga — releases/latest só conta releases publicados e que não sejam pré-lançamento. É de propósito: a decisão de lançar continua sendo de uma pessoa.

---

Parte 2 — assinatura do Windows (Azure Artifact Signing)

O nome mudou: o serviço se chamava Azure Trusted Signing e agora é Azure Artifact Signing. É o mesmo produto, e é o único caminho barato para uma assinatura que o SmartScreen aceite — na ordem de dez dólares por mês, sem token físico, com o certificado ficando na nuvem da Microsoft.

A alternativa é um certificado OV ou EV de autoridade comum: entre 200 e 600 dólares por ano, e o EV exige um token USB que não entra num runner do GitHub.

Conte com alguns dias, não com uma tarde: a validação de identidade é feita por gente.

2.1 — antes de começar

histórico verificável (documento, endereço, presença pública). Como empresa, pede CNPJ e um número D-U-N-S. A validação individual costuma ser a que trava.

2.2 — criar a conta de assinatura

No portal do Azure:

  1. Create a resource → procure por Trusted Signing (ou **Artifact

Signing) → Create**.

  1. Escolha um grupo de recursos, um nome para a conta e uma região. **Anote a

região**: ela decide o endereço do serviço, que é https://<região>.codesigning.azure.net — por exemplo https://eus.codesigning.azure.net para East US.

  1. Plano: o Basic basta para este volume.

2.3 — validar a identidade

Dentro da conta criada, Identity validations → New identity validation. Preencha exatamente como consta nos documentos: um espaço a mais no nome da empresa reprova. Envie e espere.

O nome que você põe aqui é o que o Windows vai mostrar como publicador do instalador. Escolha pensando em quem vai ler o aviso.

2.4 — criar o perfil de certificado

Depois de a validação passar: Certificate profiles → New certificate profile → tipo Public Trust. Dê um nome e anote-o.

2.5 — criar a identidade que o CI usa

O runner precisa entrar no Azure sozinho. Isso é um app registration no Microsoft Entra ID:

  1. Entra ID → App registrations → New registration. Nome livre.

Anote o Application (client) ID e o Directory (tenant) ID.

  1. Dentro dele, Certificates & secrets → New client secret. **Copie o

valor agora**: o portal não o mostra de novo. Anote também a validade — um segredo que expira derruba a assinatura sem aviso, e a data é a única coisa deste documento que tem prazo.

  1. Volte à conta de assinatura → Access control (IAM) → **Add role

assignment → o papel cujo nome termina em Certificate Profile Signer** (a Microsoft o chamava de "Trusted Signing Certificate Profile Signer" antes da troca de nome do serviço) → atribua ao app registration criado.

O passo 3 é o que costuma faltar: sem ele o app registration existe, autentica, e recebe "acesso negado" na hora de assinar.

2.6 — os seis segredos

Em Settings → Secrets and variables → Actions:

segredoonde estava
AZURE_ENDPOINThttps://<região>.codesigning.azure.net, do passo 2.2
AZURE_ARTIFACT_SIGNING_ACCOUNTo nome da conta, do passo 2.2
AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILEo nome do perfil, do passo 2.4
AZURE_CLIENT_IDApplication (client) ID, do passo 2.5
AZURE_CLIENT_SECRETo valor do client secret, do passo 2.5
AZURE_TENANT_IDDirectory (tenant) ID, do passo 2.5

Os três primeiros juntos ou nenhum. Com apenas parte deles, o workflow avisa e sai sem assinar, em vez de quebrar no fim do build.

2.7 — o que esperar

Assinado, o instalador para de ser "publicador desconhecido". O SmartScreen pode ainda mostrar o aviso nas primeiras semanas: ele também pesa reputação, e reputação se acumula com downloads. O que a assinatura garante é que a reputação passa a se acumular na sua identidade, em vez de recomeçar do zero a cada versão — que é a situação de hoje.

---

Parte 3 — assinatura do macOS (Apple)

Esta parte o release.yml já esperava antes deste trabalho; os segredos estão lá desde então. Resumo do que preencher:

  1. Conta paga no Apple Developer Program (99 dólares por ano).
  2. Certificado Developer ID Application — criado no portal, instalado no

Chaveiro, exportado como .p12 com senha.

  1. base64 -i certificado.p12 | pbcopy → é isso que vai no segredo.
  2. Uma senha específica de app em appleid.apple.com, para a notarização.
segredoconteúdo
APPLE_CERTIFICATEo .p12 em base64
APPLE_CERTIFICATE_PASSWORDa senha da exportação
APPLE_SIGNING_IDENTITYDeveloper ID Application: Nome (TEAMID), como aparece no Chaveiro
APPLE_IDo e-mail da conta
APPLE_PASSWORDa senha específica de app
APPLE_TEAM_IDo identificador de dez caracteres

Não remova o signingIdentity: "-" do tauri.conf.json achando que ele foi substituído. Ele é assinatura ad-hoc, e é o que faz a permissão de microfone grudar no macOS; o workflow o troca pela identidade de verdade quando o segredo existe, e o mantém quando não existe. Sem ele — nem ad-hoc nem real — o macOS pergunta pelo microfone a cada abertura e nunca lembra da resposta.

---

Empacotar à mão, com as chaves

Quando a cota de Actions acaba — já aconteceu —, empacotar/ faz o mesmo.

O caminho de um comando

empacotar/publicar.sh faz o release.yml inteiro a partir do Mac: os três sistemas, o manifesto, as somas e o rascunho no Releases.

sh
export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/seele.key)"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD='a senha'
export SEELE_GITHUB_TOKEN=…            # Contents: Read and write
export SEELE_WINDOWS_SSH=voce@windows  # docs/windows.md, seção 7

./empacotar/publicar.sh --conferir 0.1.2   # segundos: confere tudo e não compila
./empacotar/publicar.sh 0.1.2              # a rodada inteira, uma a duas horas

O Windows é alcançado por SSH, e a chave privada não vai para o disco de lá: ela atravessa pela entrada padrão a cada empacotamento. Ligar o OpenSSH Server naquela máquina é a seção 7 de docs/windows.md.

Rode o --conferir antes de sair da frente do computador. Ele responde em segundos a pergunta que, sem ele, custa noventa minutos de Linux emulado para ser respondida: está tudo no lugar para esta rodada terminar?

O que sai é um rascunho, e o que ele perde por não vir do CI está escrito na saída do script e no corpo do release: não há atestado de procedência, e não há como haver — gh attestation verify vai dizer que não encontrou atestado, e isso é o esperado. As NOTAS-DE-RELEASE.md explicam isso a quem baixa.

Os três, um de cada vez

Para rodar cada um por conta própria — ou quando um deles falhou e só ele precisa ser refeito. Para que o pacote saia atualizável, exporte a chave antes:

sh
export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/seele.key)"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD='a senha'

./empacotar/macos.sh 0.1.2
./empacotar/linux.sh 0.1.2
powershell
$env:TAURI_SIGNING_PRIVATE_KEY = Get-Content ~\.tauri\seele.key -Raw
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = 'a senha'

.\empacotar\windows.ps1 -Versao 0.1.2

Depois de reunir os três sistemas na mesma pasta entrega/, monte o manifesto — este passo é o que o CI faria por você, e esquecê-lo deixa todo mundo sem atualização até o release seguinte:

sh
python3 empacotar/manifesto.py entrega v0.1.2

Suba tudo o que estiver em entrega/, latest.json inclusive.

A assinatura do Windows pelo Azure não acontece no caminho manual: ela depende do signCommand que só o workflow escreve. Um instalador feito à mão sai sem ela, e é o comportamento certo — a alternativa seria pôr a credencial do Azure na máquina de quem empacota.

---

Resumo dos segredos

segredopara quêsem ele
TAURI_SIGNING_PRIVATE_KEYassina o pacote de atualizaçãonão há botão de atualizar
TAURI_SIGNING_PRIVATE_KEY_PASSWORDabre a chave acimaidem
AZURE_ENDPOINTassinatura do WindowsSmartScreen reclama
AZURE_ARTIFACT_SIGNING_ACCOUNTidemidem
AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILEidemidem
AZURE_CLIENT_IDo CI entra no Azureidem
AZURE_CLIENT_SECRETidemidem
AZURE_TENANT_IDidemidem
APPLE_CERTIFICATEassinatura do macOSGatekeeper reclama
APPLE_CERTIFICATE_PASSWORDidemidem
APPLE_SIGNING_IDENTITYidemidem
APPLE_IDnotarizaçãoidem
APPLE_PASSWORDidemidem
APPLE_TEAM_IDidemidem

Nenhum deles é obrigatório para o build passar. Todos entram vazios sem quebrar nada — é a mesma regra em todos, e está num passo só do release.yml, o «Gravar a versão e o que houver de credencial».