Skip to content

Commit 19dfc4c

Browse files
committed
fix(installer): the publisher is Botloft, and installs made as "github" are adopted
The app's file properties and Installed apps named "github" as the publisher: Tauri's default, the second part of the identifier. On an unsigned program that reads as someone posing as GitHub. bundle.publisher is now "Botloft" for the app and the setup window, and the installer's own properties name the company too. Tauri also names the registry key that keeps the install folder after the publisher, so the installer's hooks adopt an install made under the old name: BotloftAdoptOldFolder copies the folder from Software\github\Botloft to Software\Botloft\Botloft when the new key is missing and installs there, unless /D chose another folder. It runs before the first page (wizard and passive update) and at the start of the install (the quiet installer has no pages). After installing, the old key goes, and Software\github only if it is left empty. The setup window reads the new key, then the old one. Tested for real: over 0.6.0 installed under the old publisher, with the app open, the new installer run as the updater (/P /UPDATE /R) adopted the folder, removed the old key and reopened the app; the daemon kept running. A custom folder, /D and the quiet path were tested with a small installer compiled around the same hooks file and test keys. Spec 15.4 follows, and no longer says an update runs the old uninstaller: with Tauri 2.12 it does not.
1 parent 1c0bbba commit 19dfc4c

5 files changed

Lines changed: 71 additions & 19 deletions

File tree

‎app/src-setup/src/installed.rs‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ mod registry {
3939

4040
const UNINSTALL_KEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Uninstall\Botloft";
4141
/// Tauri's NSIS remembers the install folder under
42-
/// `Software\<publisher>\<product>`; the publisher is "github", the second
43-
/// part of the identifier (spec 15.4).
44-
const FOLDER_KEY: &str = r"Software\github\Botloft";
42+
/// `Software\<publisher>\<product>`. The publisher is "Botloft"; installs
43+
/// made while it was "github", Tauri's default from the identifier, are
44+
/// adopted by the installer's hooks (spec 15.4), which look in this order.
45+
const FOLDER_KEYS: [&str; 2] = [r"Software\Botloft\Botloft", r"Software\github\Botloft"];
4546

4647
pub fn entry(name: &str) -> Option<String> {
4748
RegKey::predef(HKEY_CURRENT_USER)
@@ -52,11 +53,14 @@ mod registry {
5253
}
5354

5455
pub fn remembered_folder() -> Option<String> {
55-
RegKey::predef(HKEY_CURRENT_USER)
56-
.open_subkey(FOLDER_KEY)
57-
.ok()?
58-
.get_value::<String, _>("")
59-
.ok()
56+
let user = RegKey::predef(HKEY_CURRENT_USER);
57+
FOLDER_KEYS.iter().find_map(|key| {
58+
user.open_subkey(key)
59+
.ok()?
60+
.get_value::<String, _>("")
61+
.ok()
62+
.filter(|folder| !folder.trim().is_empty())
63+
})
6064
}
6165
}
6266

‎app/src-setup/tauri.conf.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
}
1414
},
1515
"bundle": {
16+
"publisher": "Botloft",
1617
"copyright": "Copyright 2026 Phelipe Lorran",
1718
"icon": ["../src-tauri/icons/icon.ico"]
1819
}

‎app/src-tauri/tauri.conf.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
},
3838
"bundle": {
3939
"active": true,
40+
"publisher": "Botloft",
4041
"copyright": "Copyright 2026 Phelipe Lorran",
4142
"targets": [
4243
"nsis"

‎app/src-tauri/windows/hooks.nsh‎

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,64 @@
66
; next to the title, as the Botloft mascot on the page's white.
77
!define MUI_HEADERIMAGE_RIGHT
88

9+
; The installer's own file properties name the publisher too; Tauri's
10+
; template leaves the company out.
11+
VIAddVersionKey "CompanyName" "Botloft"
12+
13+
; Tauri keeps the install folder in the registry under
14+
; Software\<publisher>\<product>. The publisher used to be "github", Tauri's
15+
; default from the identifier; it is "Botloft" now (`bundle.publisher`).
16+
; An install made under the old name is adopted under the new one, so an
17+
; update goes to the folder Botloft is in, and "uninstall before installing"
18+
; still finds that folder.
19+
!ifndef BOTLOFT_FOLDER_KEY
20+
!define BOTLOFT_FOLDER_KEY "Software\Botloft\Botloft"
21+
!endif
22+
!ifndef BOTLOFT_OLD_FOLDER_KEY
23+
!define BOTLOFT_OLD_FOLDER_KEY "Software\github\Botloft"
24+
!endif
25+
!ifndef BOTLOFT_OLD_PUBLISHER_KEY
26+
!define BOTLOFT_OLD_PUBLISHER_KEY "Software\github"
27+
!endif
28+
29+
; Copies the folder of an install made under the old publisher to the new
30+
; key, and installs there unless /D chose another folder. It does nothing
31+
; once the new key exists.
32+
Function BotloftAdoptOldFolder
33+
Push $0
34+
ReadRegStr $0 SHCTX "${BOTLOFT_FOLDER_KEY}" ""
35+
StrCmp $0 "" 0 botloft_adopt_done
36+
ReadRegStr $0 SHCTX "${BOTLOFT_OLD_FOLDER_KEY}" ""
37+
StrCmp $0 "" botloft_adopt_done 0
38+
WriteRegStr SHCTX "${BOTLOFT_FOLDER_KEY}" "" $0
39+
; Only when the installer is still on its default folder: /D wins.
40+
StrCmp $INSTDIR "$LOCALAPPDATA\Botloft" 0 botloft_adopt_done
41+
StrCpy $INSTDIR $0
42+
botloft_adopt_done:
43+
Pop $0
44+
FunctionEnd
45+
46+
; Before the first page, so the pages and "uninstall before installing" see
47+
; the adopted folder.
48+
!define MUI_CUSTOMFUNCTION_GUIINIT BotloftAdoptOldFolder
49+
50+
!macro NSIS_HOOK_PREINSTALL
51+
; The quiet installer (/S) has no pages, so nothing adopted the folder yet.
52+
; The install section already set its output to the default folder.
53+
Call BotloftAdoptOldFolder
54+
SetOutPath $INSTDIR
55+
!macroend
56+
957
!macro NSIS_HOOK_POSTINSTALL
10-
; Installed apps shows Botloft as the publisher. `bundle.publisher` stays
11-
; unset on purpose: Tauri also names the key that keeps the install folder
12-
; after it (Software\<publisher>\Botloft), so a new name would make the
13-
; first update of an existing install run the old uninstaller without its
14-
; folder.
15-
WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "Botloft"
58+
; The folder is under the new name now; the old key goes.
59+
DeleteRegKey SHCTX "${BOTLOFT_OLD_FOLDER_KEY}"
60+
DeleteRegKey /ifempty SHCTX "${BOTLOFT_OLD_PUBLISHER_KEY}"
1661
!macroend
1762

1863
!macro NSIS_HOOK_PREUNINSTALL
1964
; A real uninstall stops the daemon and removes its scheduled task and
20-
; binary. Bots and data stay in %LOCALAPPDATA%\Botloft. An update runs
21-
; this uninstaller too, with /UPDATE: then the daemon keeps running.
65+
; binary. Bots and data stay in %LOCALAPPDATA%\Botloft. An uninstaller run
66+
; with /UPDATE leaves the daemon running.
2267
${If} $UpdateMode <> 1
2368
nsExec::Exec '"$INSTDIR\botloftd.exe" service uninstall'
2469
Pop $0

‎docs/spec.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,8 +706,9 @@ Identidade: o mascote do Botloft é uma chama com olhos. O ícone do app (`app/a
706706
- `pnpm bundle` (em `app/`) roda `scripts/sidecar.mjs`, que compila o `botloftd` em release e o copia para `src-tauri/binaries/botloftd-<target triple>.exe`, e depois `tauri build --config src-tauri/tauri.bundle.conf.json`. Esse arquivo liga o `externalBin` e o NSIS; ele fica fora do `tauri.conf.json` porque o `tauri-build` copia o `externalBin` também em dev e no `cargo clippy`, o que exigiria o sidecar em todo build e sobrescreveria o `target\debug\botloftd.exe` com a cópia de release.
707707
- Quem baixa o Botloft instala pela tela de instalação (15.7), que roda este instalador NSIS em silêncio. O NSIS com as próprias telas só aparece na atualização (15.5), na desinstalação e quando a tela de instalação não pode abrir.
708708
- NSIS por usuário (`installMode = currentUser`), sem pedir administrador, em `%LOCALAPPDATA%\Botloft` (seção 5). O instalador não mexe no daemon: ele roda da própria cópia em `<home>\bin`, então o arquivo do sidecar nunca está em uso.
709-
- Hook `NSIS_HOOK_PREUNINSTALL` (`src-tauri/windows/hooks.nsh`): numa desinstalação de verdade, `botloftd service uninstall` para o daemon e apaga a tarefa e o binário, e o valor `Botloft` da chave Run sai (15.2); bots e dados ficam. Uma atualização também roda o desinstalador antigo, com `/UPDATE`: aí o hook não faz nada e o daemon continua rodando até o app novo abrir e atualizá-lo (15.2).
710-
- Marca do instalador, sem nome de terceiros à vista: o instalador e o desinstalador usam o ícone do app (`icons/icon.ico`); as telas de boas-vindas e de conclusão têm a imagem lateral `windows/installer-sidebar.bmp` (164×314, o mascote e "Botloft" sobre o preto) e as outras telas o cabeçalho `windows/installer-header.bmp` (150×57, à direita, com `MUI_HEADERIMAGE_RIGHT` no arquivo de hooks). As duas imagens saem do `app-icon.svg` sem o quadrado preto. O rodapé mostra o `bundle.copyright` no lugar de "Nullsoft Install System", e o `NSIS_HOOK_POSTINSTALL` grava "Botloft" como editor em Aplicativos instalados. O `bundle.publisher` fica vazio de propósito: o Tauri dá o nome dele à chave que guarda a pasta de instalação (`Software\<publisher>\Botloft`, hoje `github`, a segunda parte do identificador), e trocá-lo faria a primeira atualização de uma instalação existente rodar o desinstalador antigo sem a pasta. Por isso a empresa nas propriedades do `Botloft.exe` continua `github`.
709+
- Hook `NSIS_HOOK_PREUNINSTALL` (`src-tauri/windows/hooks.nsh`): numa desinstalação de verdade, `botloftd service uninstall` para o daemon e apaga a tarefa e o binário, e o valor `Botloft` da chave Run sai (15.2); bots e dados ficam. Numa atualização (`/UPDATE`), o instalador do Tauri 2.12 nem roda o desinstalador antigo: só troca os arquivos, e o daemon continua rodando até o app novo abrir e atualizá-lo (15.2). Um desinstalador chamado com `/UPDATE` também não mexe no daemon.
710+
- Marca do instalador, sem nome de terceiros à vista: o instalador e o desinstalador usam o ícone do app (`icons/icon.ico`); as telas de boas-vindas e de conclusão têm a imagem lateral `windows/installer-sidebar.bmp` (164×314, o mascote e "Botloft" sobre o preto) e as outras telas o cabeçalho `windows/installer-header.bmp` (150×57, à direita, com `MUI_HEADERIMAGE_RIGHT` no arquivo de hooks). As duas imagens saem do `app-icon.svg` sem o quadrado preto. O rodapé mostra o `bundle.copyright` no lugar de "Nullsoft Install System", e o editor é "Botloft" (`bundle.publisher`), em Aplicativos instalados e como empresa nas propriedades do `Botloft.exe` e da tela de instalação.
711+
- Editor antigo: até a 0.6.0 o `bundle.publisher` ficava vazio e o Tauri usava `github`, a segunda parte do identificador, o que num programa sem assinatura parece alguém se passando pelo GitHub. O Tauri também dá o nome do editor à chave que guarda a pasta de instalação (`Software\<editor>\Botloft`), lida para atualizar na mesma pasta e para o "desinstalar antes de instalar" do assistente. Por isso os hooks adotam a instalação antiga: `BotloftAdoptOldFolder` copia a pasta de `Software\github\Botloft` para `Software\Botloft\Botloft` quando a chave nova não existe e passa a instalar nela, a menos que `/D` tenha escolhido outra. Roda antes da primeira tela (`MUI_CUSTOMFUNCTION_GUIINIT`), o que cobre o assistente e a atualização passiva, e no começo da instalação (`NSIS_HOOK_PREINSTALL`), para o modo silencioso, que não tem telas. Depois de instalar, o `NSIS_HOOK_POSTINSTALL` apaga a chave antiga, e a `Software\github` só se ficar vazia. A tela de instalação (15.7) lê a chave nova e, na falta dela, a antiga. **Verificado** (Windows 11 25H2, 2026-09-30): sobre a 0.6.0 instalada com o editor antigo e com o app aberto, o instalador novo rodado como o atualizador (`/P /UPDATE /R`) terminou em 3 s, criou `Software\Botloft\Botloft` com a pasta que estava na chave antiga, apagou a antiga e a `Software\github` vazia, deixou o editor "Botloft" em Aplicativos instalados e reabriu o app da mesma pasta; o daemon não foi tocado. Os casos de pasta escolhida pelo dono, `/D` e modo silencioso foram testados com um instalador mínimo compilado com o mesmo arquivo de hooks e chaves de teste.
711712
- O manifesto do app (`src-tauri/windows/app.manifest`) é o padrão do Tauri (controles comuns v6) mais `longPathAware`.
712713

713714
### 15.5 Atualizações
@@ -774,7 +775,7 @@ Identidade: o mascote do Botloft é uma chama com olhos. O ícone do app (`app/a
774775

775776
**Instalação real** (I1, Windows 11 25H2, 2026-09-29, 0.5.0): numa máquina com os arquivos do Botloft mas sem a entrada em Aplicativos instalados, a tela nova instalou em cerca de 3 s, criou a entrada (editor "Botloft", pasta `%LOCALAPPDATA%\Botloft`), abriu o app dessa pasta e apagou a pasta temporária. O daemon seguiu rodando, porque o instalador não mexe nele (15.4).
776777

777-
Um teste que instala uma cópia em outra pasta e depois a desinstala apaga a entrada da instalação de verdade, porque a entrada tem o nome do produto e não o da pasta; e deixa a pasta de teste como a lembrada (`Software\github\Botloft`). Foi o que aconteceu nesta máquina. Para testar numa pasta separada, use outra conta do Windows.
778+
Um teste que instala uma cópia em outra pasta e depois a desinstala apaga a entrada da instalação de verdade, porque a entrada tem o nome do produto e não o da pasta; e deixa a pasta de teste como a lembrada (a chave do editor, 15.4). Foi o que aconteceu nesta máquina. Para testar numa pasta separada, use outra conta do Windows.
778779

779780
**Marcos.**
780781

0 commit comments

Comments
 (0)