From 738829af2a714558d50fb773f146219648ee0fb5 Mon Sep 17 00:00:00 2001 From: daviirodrig <30713947+daviirodrig@users.noreply.github.com> Date: Tue, 26 May 2026 18:45:13 -0300 Subject: [PATCH 1/2] feat: platform-aware daemon labels, Linux systemd autostart, and credential store Adds a platform abstraction layer so macOS and Linux each get correct labels, paths, and behavior without scattering cfg blocks everywhere. - platform/: mod.rs with pure TargetOs/PlatformLabels (testable without a real OS), macos.rs wrapping the existing LaunchAgent logic, linux.rs implementing systemd --user install/restart/remove - daemon.rs: DaemonStatus gains plataforma, autostart_nome, service_path, instalacao_manual; delegates fully to platform module (no more cfg in daemon) - Cargo.toml: target-specific keyring (apple-native / sync-secret-service) and libc (macOS only); eliminates unused-import warnings on Linux - secrets.rs: error messages and BackupCredentialStatus.store_nome use the platform label ("Keychain" / "Secret Service") - domain.rs: ApiCapabilities gains data_root + data_root_label; filled by storage.bootstrap() with the real filesystem path (~-expanded) - UI: SettingsView, Shell, BackupView all drive text from API data instead of hardcoded macOS strings; App.tsx forwards data_root_label - 4 platform unit tests covering label correctness per OS --- Cargo.lock | 25 +++ apps/web/src/App.tsx | 5 +- apps/web/src/features/backup/BackupView.tsx | 4 +- .../src/features/settings/SettingsView.tsx | 26 ++- apps/web/src/features/shell/Shell.tsx | 16 +- apps/web/src/lib/types.ts | 14 +- crates/server/Cargo.toml | 9 +- crates/server/src/daemon.rs | 151 +++------------ crates/server/src/domain.rs | 5 + crates/server/src/lib.rs | 1 + crates/server/src/platform/linux.rs | 103 ++++++++++ crates/server/src/platform/macos.rs | 110 +++++++++++ crates/server/src/platform/mod.rs | 180 ++++++++++++++++++ crates/server/src/secrets.rs | 41 +++- crates/server/src/storage.rs | 10 +- 15 files changed, 546 insertions(+), 154 deletions(-) create mode 100644 crates/server/src/platform/linux.rs create mode 100644 crates/server/src/platform/macos.rs create mode 100644 crates/server/src/platform/mod.rs diff --git a/Cargo.lock b/Cargo.lock index bce6323..a817eb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "dbus", + "zeroize", +] + [[package]] name = "der" version = "0.7.10" @@ -2656,6 +2666,7 @@ version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ + "dbus-secret-service", "log", "security-framework 2.11.1", "security-framework 3.7.0", @@ -6347,6 +6358,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zerotrie" diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 8b409eb..0f20b6e 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -55,11 +55,14 @@ export function App() { setOpenProject(null) } + const dataRootLabel = workspace.capacidades?.data_root_label || undefined + return ( message('erro', text)} > @@ -86,7 +89,7 @@ export function App() { {screen === 'ideias' && ( )} - {screen === 'backup' && } + {screen === 'backup' && } {screen === 'arquivo' && ( )} diff --git a/apps/web/src/features/backup/BackupView.tsx b/apps/web/src/features/backup/BackupView.tsx index 55a7e3e..9a18c53 100644 --- a/apps/web/src/features/backup/BackupView.tsx +++ b/apps/web/src/features/backup/BackupView.tsx @@ -7,9 +7,11 @@ import type { Config, Snapshot } from '../../lib/types' export function BackupView({ config, + dataRootLabel, onMessage, }: { config: Config + dataRootLabel?: string onMessage: (type: 'ok' | 'erro', text: string) => void }) { const [snapshots, setSnapshots] = useState([]) @@ -45,7 +47,7 @@ export function BackupView({ />

- cada snapshot envia a pasta integral ~/Documents/PROJECTUS: configurações, kanbans, ideias, + cada snapshot envia a pasta integral {dataRootLabel ?? '~/Documents/PROJECTUS'}: configurações, kanbans, ideias, Arquivo, históricos e anexos.

{!config.r2.configurado ? ( diff --git a/apps/web/src/features/settings/SettingsView.tsx b/apps/web/src/features/settings/SettingsView.tsx index 1ef7390..034e71e 100644 --- a/apps/web/src/features/settings/SettingsView.tsx +++ b/apps/web/src/features/settings/SettingsView.tsx @@ -86,13 +86,16 @@ export function SettingsView({ setDirty(true) } + const credentialStore = credentialStatus?.store_nome ?? 'cofre do sistema' + const saveCredentials = async () => { try { await api.saveCredentials({ access_key_id: accessKey.trim(), secret_access_key: secretKey.trim() }) setAccessKey('') setSecretKey('') - setCredentialStatus(await api.credentialStatus()) - onMessage('ok', 'credenciais R2 fixadas no Keychain') + const updated = await api.credentialStatus() + setCredentialStatus(updated) + onMessage('ok', `credenciais R2 fixadas no ${updated.store_nome}`) } catch (error) { onMessage('erro', error instanceof Error ? error.message : 'não foi possível salvar credenciais') } @@ -300,10 +303,10 @@ export function SettingsView({ value={secretKey} onChange={(event) => setSecretKey(event.target.value)} /> - logo abaixo, "Secret Access Key". Salva no Keychain do macOS. + logo abaixo, "Secret Access Key". Salva no {credentialStore}.
- KEYCHAIN + {credentialStore.toUpperCase()} {credentialStatus?.fixadas ? `credenciais fixadas (${credentialStatus.access_key_id_mascarada})` @@ -316,7 +319,7 @@ export function SettingsView({ disabled={!accessKey.trim() || !secretKey.trim() || dirty || saving || !validR2Endpoint} onClick={() => void saveCredentials()} > - fixar credenciais no Keychain + fixar credenciais no {credentialStore}
@@ -334,10 +337,12 @@ export function SettingsView({

{daemon?.instalado - ? 'autostart instalado.' + ? `autostart instalado via ${daemon.autostart_nome}.` : daemon?.instalacao_disponivel - ? 'autostart ainda não instalado.' - : 'para manter o backend ativo com a janela fechada, execute ./scripts/instalar-autostart.sh.'} + ? `autostart ainda não instalado.` + : daemon?.suportado === false + ? 'autostart não disponível nesta plataforma.' + : `para manter o backend ativo com a janela fechada, use o botão instalar${daemon?.instalacao_manual ? ` ou ${daemon.instalacao_manual}` : ''}.`}

{!daemon?.instalado && daemon?.instalacao_disponivel && (