chore: padroniza arquitetura Ciberfauna, elevação root na abertura e CI multi-arch no runner ARM
CI / Formatação e clippy (push) Successful in 20s
CI / Auditoria de dependências (push) Successful in 9s
CI / Compila o app desktop (Linux) (push) Successful in 1m55s
Release / Cria a release (push) Successful in 5s
CI / Testes (Linux) (push) Successful in 4m25s
Release / CLI (linux-aarch64) (push) Successful in 2m18s
Release / CLI (linux-x86_64) (push) Successful in 2m51s
Release / App desktop (linux-aarch64) (push) Successful in 9m19s
Release / App desktop (linux-x86_64) (push) Failing after 11m11s
Release / App desktop (windows) (push) Successful in 10m27s
Release / Monta o latest.json (push) Skipped
CI / Formatação e clippy (push) Successful in 20s
CI / Auditoria de dependências (push) Successful in 9s
CI / Compila o app desktop (Linux) (push) Successful in 1m55s
Release / Cria a release (push) Successful in 5s
CI / Testes (Linux) (push) Successful in 4m25s
Release / CLI (linux-aarch64) (push) Successful in 2m18s
Release / CLI (linux-x86_64) (push) Successful in 2m51s
Release / App desktop (linux-aarch64) (push) Successful in 9m19s
Release / App desktop (linux-x86_64) (push) Failing after 11m11s
Release / App desktop (windows) (push) Successful in 10m27s
Release / Monta o latest.json (push) Skipped
This commit is contained in:
1 parent
009c3d61de
commit
2786d28e7e
98 files changed
+10594
-372
No files matched your search
@@ -15,7 +15,7 @@ async fn rpc(state: tauri::State<'_, State>, method: String, params: serde_json:
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
/// Versão nova encontrada na release do GitHub.
|
||||
/// Versão nova encontrada na release do Gitea.
|
||||
#[derive(serde::Serialize)]
|
||||
struct UpdateInfo {
|
||||
current: String,
|
||||
@@ -82,6 +82,21 @@ async fn update_install(app: tauri::AppHandle) -> Result<(), String> {
|
||||
app.restart()
|
||||
}
|
||||
|
||||
/// `true` quando o processo já tem privilégio suficiente para ler discos brutos.
|
||||
/// No Windows o manifesto já exige administrador antes mesmo do app abrir,
|
||||
/// então aqui é sempre `true`.
|
||||
#[tauri::command]
|
||||
fn is_elevated() -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
unsafe { libc::geteuid() == 0 }
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Reabre o app com privilégios de administrador (Linux/macOS). No Windows o
|
||||
/// manifesto já exige elevação. O app novo encerra este quando abre; se a
|
||||
/// autorização falhar, a janela continua aqui e a interface mostra o motivo.
|
||||
@@ -89,62 +104,7 @@ async fn update_install(app: tauri::AppHandle) -> Result<(), String> {
|
||||
fn relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
use tauri::Emitter;
|
||||
let exe = elevated_exe()?;
|
||||
let cfg = guara_core::settings::config_dir();
|
||||
let log = std::env::temp_dir().join(format!("guara-elevate-{}.log", std::process::id()));
|
||||
let _ = std::fs::write(&log, b"");
|
||||
let out = std::fs::OpenOptions::new().append(true).open(&log).map_err(|e| e.to_string())?;
|
||||
let err = out.try_clone().map_err(|e| e.to_string())?;
|
||||
let pid = std::process::id().to_string();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut cmd = {
|
||||
let mut c = std::process::Command::new("pkexec");
|
||||
c.arg("/usr/bin/env");
|
||||
for k in ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS", "GDK_BACKEND"] {
|
||||
if let Ok(v) = std::env::var(k) {
|
||||
c.arg(format!("{k}={v}"));
|
||||
}
|
||||
}
|
||||
c.arg(format!("GUARA_CONFIG_DIR={}", cfg.display()));
|
||||
c.arg(format!("GUARA_REPLACE_PID={pid}"));
|
||||
c.arg(&exe);
|
||||
c
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut cmd = {
|
||||
let shell = format!(
|
||||
"GUARA_CONFIG_DIR='{}' GUARA_REPLACE_PID={} '{}' >/dev/null 2>&1 &",
|
||||
cfg.display(),
|
||||
pid,
|
||||
exe.display()
|
||||
);
|
||||
let script = format!("do shell script \"{}\" with administrator privileges", shell.replace('\\', "\\\\").replace('"', "\\\""));
|
||||
let mut c = std::process::Command::new("osascript");
|
||||
c.args(["-e", &script]);
|
||||
c
|
||||
};
|
||||
cmd.stdout(out).stderr(err);
|
||||
let mut child = cmd.spawn().map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
||||
std::thread::spawn(move || {
|
||||
// No Linux o pkexec só termina com sucesso quando o app elevado é fechado (e o app
|
||||
// elevado já terá encerrado este processo). Qualquer falha antes disso vira aviso.
|
||||
let status = child.wait();
|
||||
if matches!(&status, Ok(s) if s.success()) {
|
||||
let _ = std::fs::remove_file(&log);
|
||||
return;
|
||||
}
|
||||
let detail = std::fs::read_to_string(&log).unwrap_or_default();
|
||||
let msg = match status.ok().and_then(|s| s.code()) {
|
||||
Some(126) => "Autorização cancelada.".to_string(),
|
||||
Some(127) => "Não foi possível autenticar como administrador.".to_string(),
|
||||
_ => format!("Não foi possível reiniciar como administrador. {}", detail.lines().last().unwrap_or("").trim()),
|
||||
};
|
||||
let _ = app.emit("elevate-failed", msg);
|
||||
let _ = std::fs::remove_file(&log);
|
||||
});
|
||||
Ok(())
|
||||
do_relaunch_elevated(app)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -153,6 +113,66 @@ fn relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
use tauri::Emitter;
|
||||
let exe = elevated_exe()?;
|
||||
let cfg = guara_core::settings::config_dir();
|
||||
let log = std::env::temp_dir().join(format!("guara-elevate-{}.log", std::process::id()));
|
||||
let _ = std::fs::write(&log, b"");
|
||||
let out = std::fs::OpenOptions::new().append(true).open(&log).map_err(|e| e.to_string())?;
|
||||
let err = out.try_clone().map_err(|e| e.to_string())?;
|
||||
let pid = std::process::id().to_string();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut cmd = {
|
||||
let mut c = std::process::Command::new("pkexec");
|
||||
c.arg("/usr/bin/env");
|
||||
for k in ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS", "GDK_BACKEND"] {
|
||||
if let Ok(v) = std::env::var(k) {
|
||||
c.arg(format!("{k}={v}"));
|
||||
}
|
||||
}
|
||||
c.arg(format!("GUARA_CONFIG_DIR={}", cfg.display()));
|
||||
c.arg(format!("GUARA_REPLACE_PID={pid}"));
|
||||
c.arg(&exe);
|
||||
c
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut cmd = {
|
||||
let shell = format!(
|
||||
"GUARA_CONFIG_DIR='{}' GUARA_REPLACE_PID={} '{}' >/dev/null 2>&1 &",
|
||||
cfg.display(),
|
||||
pid,
|
||||
exe.display()
|
||||
);
|
||||
let script = format!("do shell script \"{}\" with administrator privileges", shell.replace('\\', "\\\\").replace('"', "\\\""));
|
||||
let mut c = std::process::Command::new("osascript");
|
||||
c.args(["-e", &script]);
|
||||
c
|
||||
};
|
||||
cmd.stdout(out).stderr(err);
|
||||
let mut child = cmd.spawn().map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
||||
std::thread::spawn(move || {
|
||||
// No Linux o pkexec só termina com sucesso quando o app elevado é fechado (e o app
|
||||
// elevado já terá encerrado este processo). Qualquer falha antes disso vira aviso.
|
||||
let status = child.wait();
|
||||
if matches!(&status, Ok(s) if s.success()) {
|
||||
let _ = std::fs::remove_file(&log);
|
||||
return;
|
||||
}
|
||||
let detail = std::fs::read_to_string(&log).unwrap_or_default();
|
||||
let msg = match status.ok().and_then(|s| s.code()) {
|
||||
Some(126) => "Autorização cancelada.".to_string(),
|
||||
Some(127) => "Não foi possível autenticar como administrador.".to_string(),
|
||||
_ => format!("Não foi possível reiniciar como administrador. {}", detail.lines().last().unwrap_or("").trim()),
|
||||
};
|
||||
let _ = app.emit("elevate-failed", msg);
|
||||
let _ = std::fs::remove_file(&log);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Caminho que o root consegue executar. No AppImage, `current_exe` aponta para
|
||||
/// dentro de um ponto de montagem FUSE do usuário, inacessível ao root: usa o
|
||||
/// próprio arquivo `.AppImage` (variável `APPIMAGE`).
|
||||
@@ -228,7 +248,7 @@ fn main() {
|
||||
let eng = for_protocol.clone();
|
||||
std::thread::spawn(move || responder.respond(serve_file(&eng, &request)));
|
||||
})
|
||||
.setup(|_app| {
|
||||
.setup(|app| {
|
||||
// Quando abrimos elevados a pedido do app normal, ele fecha o app antigo.
|
||||
#[cfg(unix)]
|
||||
if let Some(pid) = std::env::var("GUARA_REPLACE_PID").ok().and_then(|p| p.parse::<i32>().ok()) {
|
||||
@@ -242,9 +262,20 @@ fn main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ler discos brutos exige root: pede elevação assim que o app abre,
|
||||
// sem esperar o usuário procurar um botão. Se a autorização falhar,
|
||||
// a janela continua aberta e a interface mostra o aviso.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
if unsafe { libc::geteuid() != 0 } && std::env::var("GUARA_REPLACE_PID").is_err() {
|
||||
let handle = app.handle().clone();
|
||||
std::thread::spawn(move || {
|
||||
let _ = do_relaunch_elevated(handle);
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![rpc, relaunch_elevated, update_check, update_install])
|
||||
.invoke_handler(tauri::generate_handler![rpc, is_elevated, relaunch_elevated, update_check, update_install])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("erro ao iniciar o Guará");
|
||||
}
|
||||
Reference in new issue
Block a user