From c0ebe505aca854cdd90bb8f37491c633eb407b0a Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Thu, 1 Oct 2026 23:09:52 -0300 Subject: [PATCH] feat(release): removing the .deb stops Botloft's background service A prerm script does what the NSIS uninstall hook does on Windows: on remove or purge, and not on the upgrades the in-app updater installs, it runs botloftd service uninstall as each user who has the service and removes the service and sign-in entries. Bots and data stay. --- app/src-tauri/linux/prerm.sh | 33 +++++++++++++++++++++++++++++ app/src-tauri/tauri.linux.conf.json | 7 +++++- docs/spec.md | 1 + 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100755 app/src-tauri/linux/prerm.sh diff --git a/app/src-tauri/linux/prerm.sh b/app/src-tauri/linux/prerm.sh new file mode 100755 index 00000000..535ecacc --- /dev/null +++ b/app/src-tauri/linux/prerm.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Botloft's .deb pre-removal script (spec 15.4). dpkg runs it as root +# before it removes the package, and before an upgrade replaces it. On a +# removal only, it stops Botloft's background service for every user who +# has one, as `botloftd service uninstall` does, and removes the entry that +# opens the app at sign-in. An upgrade (the in-app updater installs the +# new .deb) leaves both alone. Bots and data stay in each user's folders. +set -u + +case "${1:-}" in + remove | purge) ;; + *) exit 0 ;; +esac + +for unit in /home/*/.config/systemd/user/botloft.service /root/.config/systemd/user/botloft.service; do + [ -f "$unit" ] || continue + home=${unit%/.config/systemd/user/botloft.service} + user=$(stat -c %U "$home") || continue + uid=$(id -u "$user") || continue + data="$home/.local/share/Botloft" + daemon="$data/bin/botloftd" + if [ -x "$daemon" ] && [ -S "/run/user/$uid/bus" ]; then + # The user's service manager runs: the daemon stops its own service. + runuser -u "$user" -- env HOME="$home" XDG_RUNTIME_DIR="/run/user/$uid" \ + DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \ + "$daemon" --home "$data" service uninstall >/dev/null 2>&1 || true + fi + # Signed out, or the daemon could not: nothing runs it, so its files go. + rm -f "$unit" "$home/.config/systemd/user/default.target.wants/botloft.service" + rm -f "$home/.config/autostart/botloft.desktop" +done + +exit 0 diff --git a/app/src-tauri/tauri.linux.conf.json b/app/src-tauri/tauri.linux.conf.json index 0381c690..9009af6e 100644 --- a/app/src-tauri/tauri.linux.conf.json +++ b/app/src-tauri/tauri.linux.conf.json @@ -1,6 +1,11 @@ { "$schema": "https://schema.tauri.app/config/2", "bundle": { - "targets": ["deb", "appimage"] + "targets": ["deb", "appimage"], + "linux": { + "deb": { + "preRemoveScript": "linux/prerm.sh" + } + } } } diff --git a/docs/spec.md b/docs/spec.md index 826b4558..4eed77e4 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -855,6 +855,7 @@ Identidade: o mascote do Botloft é uma chama com olhos. O ícone do app (`app/a - 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\\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. - O manifesto do app (`src-tauri/windows/app.manifest`) é o padrão do Tauri (controles comuns v6) mais `longPathAware`. - **Linux e macOS** (18, item 8): o mesmo `pnpm bundle` gera os pacotes do sistema em que roda. O Tauri junta ao `tauri.conf.json` o `tauri.linux.conf.json` ou o `tauri.macos.conf.json`, que escolhem os pacotes. Linux: `.deb` (o Tauri põe as dependências: WebKitGTK, GTK e o `libayatana-appindicator3-1` do ícone na bandeja) e AppImage, feitos no Ubuntu 22.04 para o AppImage rodar em distribuições mais antigas (ele usa a glibc do sistema em que foi feito). macOS: `.app` e `.dmg` para Apple Silicon, a partir do macOS 11. Sem conta Apple Developer, o app não é notarizado: tem só assinatura ad-hoc (`signingIdentity = "-"`). Sem nenhuma assinatura, o macOS diria que o app está danificado; assinado assim, quem baixa o libera uma vez em Ajustes do Sistema → Privacidade e Segurança → Abrir Mesmo Assim. O sidecar vai dentro do pacote, ao lado do app, como no Windows. A tela de instalação (15.7) é só do Windows. +- **Remover o `.deb`** faz o que o hook de desinstalação do NSIS faz. O `prerm` (`src-tauri/linux/prerm.sh`, `preRemoveScript`) roda como root, só com `remove` ou `purge`: a atualização pelo app instala o `.deb` novo e chama o `prerm` com `upgrade`, que não mexe em nada. Para cada `~/.config/systemd/user/botloft.service` em `/home/*` e `/root`, roda `botloftd --home ~/.local/share/Botloft service uninstall` como o dono da pasta (`runuser`, com `HOME`, `XDG_RUNTIME_DIR` e o bus da sessão dele), se o gerenciador do usuário estiver no ar (`/run/user//bus`). Depois apaga a unit, o link de `default.target.wants` e `~/.config/autostart/botloft.desktop`, o que cobre quem está fora da sessão. Bots e dados ficam. Só a unit da pasta padrão (`botloft.service`) é tratada; um daemon de dev com outra pasta é de quem o instalou. O AppImage e o `.app` do macOS não têm desinstalador: o README e a política de assinatura dizem para rodar `botloftd service uninstall` antes. **Testado** num contêiner Debian, com usuários de mentira e um `botloftd` falso: com `upgrade` nada muda; com `remove`, o daemon de quem tem sessão é chamado como esse usuário, com a pasta certa, e os arquivos de todos saem. ### 15.5 Atualizações