App desktop (Tauri), GitHub Actions de CI/release, elevação automática

- Move a UI de crates/sabia-cli/src/web/ para apps/gui/ui/: fonte única
  servida tanto pelo `sabia ui` quanto pelo app desktop.
- sabia-core::engine::Engine centraliza sessões de scan/monitor/update —
  usado pelo servidor local da CLI e pelo app Tauri, sem duplicar lógica.
- apps/gui/src-tauri: app desktop Tauri 2, mesma interface, comandos IPC
  espelhando a API HTTP. Pede elevação de administrador/root
  automaticamente ao abrir (modo monitor e captura crua exigem):
  manifesto requireAdministrator no Windows (UAC antes mesmo do app
  abrir), pkexec no Linux e osascript no macOS, com reinício automático
  e aviso + botão de nova tentativa na interface se for negada.
- Ícone gerado a partir do logo (apps/gui/src-tauri/icons/source.png).
- .github/workflows/ci.yml: fmt, clippy, testes (Linux/Windows/macOS),
  auditoria de dependências, compilação do app desktop.
- .github/workflows/release.yml: ao empurrar uma tag vX.Y.Z, builda a
  CLI (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64) e o
  app desktop pras mesmas plataformas, publica tudo numa release do
  GitHub com checksums SHA-256.
- sabia-core::update: corrige o nome do asset esperado (casava pelo
  nome fixo, mas o release empacota com a versão no nome) e a extração
  (tar.gz no Linux/macOS, zip no Windows).
- LICENSE-MIT, LICENSE-APACHE, CHANGELOG.md com a entrada 0.1.0.
- cargo fmt + clippy (-D warnings) limpos em todo o workspace.
This commit is contained in:
Lucas committed 2026-10-23 08:56:08 -03:00
1 parent fd3ef5240e
commit df27974359
29 files changed
+1268 -191

No files matched your search

+45 -15
View File
@@ -1,14 +1,20 @@
mod web;
use clap::{Parser, Subcommand};
use sabia_core::{audit::AuditLog, capture::RawCapture, iface, netlink, pcapwriter::PcapWriter, scan::ScanTable};
use sabia_core::{
audit::AuditLog, capture::RawCapture, iface, netlink, pcapwriter::PcapWriter, scan::ScanTable,
};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[derive(Parser)]
#[command(name = "sabia", about = "Sabiá — auditoria e monitoramento de redes Wi-Fi", version)]
#[command(
name = "sabia",
about = "Sabiá — auditoria e monitoramento de redes Wi-Fi",
version
)]
struct Cli {
#[command(subcommand)]
cmd: Cmd,
@@ -52,7 +58,11 @@ enum Cmd {
enum IfaceAction {
List,
/// Liga/desliga modo monitor numa interface Wi-Fi.
Monitor { iface: String, #[arg(value_enum)] state: OnOff },
Monitor {
iface: String,
#[arg(value_enum)]
state: OnOff,
},
}
#[derive(Clone, Copy, clap::ValueEnum)]
@@ -94,23 +104,31 @@ fn run(cli: Cli) -> sabia_core::Result<()> {
iface::get(&name)?; // valida que é Wi-Fi antes de tocar nela
netlink::set_monitor_mode(&name, enable)?;
let mut log = AuditLog::open(audit_path())?;
log.record(
"monitor_mode",
&name,
&format!("{{\"enable\": {enable}}}"),
)?;
println!("{name}: modo monitor {}", if enable { "ativado" } else { "desativado" });
log.record("monitor_mode", &name, &format!("{{\"enable\": {enable}}}"))?;
println!(
"{name}: modo monitor {}",
if enable { "ativado" } else { "desativado" }
);
}
},
Cmd::Scan { iface: name, channel, seconds, out } => {
Cmd::Scan {
iface: name,
channel,
seconds,
out,
} => {
let info = iface::get(&name)?;
if info.mode != iface::Mode::Monitor {
eprintln!("aviso: {name} não está em modo monitor (sabia iface monitor {name} on)");
}
let mut log = AuditLog::open(audit_path())?;
log.record("scan_start", &name, &format!("{{\"channel\": {channel:?}}}"))?;
log.record(
"scan_start",
&name,
&format!("{{\"channel\": {channel:?}}}"),
)?;
let cap = RawCapture::open(&name)?;
cap.set_read_timeout(Duration::from_millis(500))?;
@@ -143,7 +161,10 @@ fn run(cli: Cli) -> sabia_core::Result<()> {
break;
}
}
if channel.is_none() && !channels.is_empty() && last_hop.elapsed() > Duration::from_millis(300) {
if channel.is_none()
&& !channels.is_empty()
&& last_hop.elapsed() > Duration::from_millis(300)
{
chan_idx = (chan_idx + 1) % channels.len();
let _ = netlink::set_channel(&name, channels[chan_idx]);
last_hop = std::time::Instant::now();
@@ -167,16 +188,25 @@ fn run(cli: Cli) -> sabia_core::Result<()> {
w.flush()?;
}
log.record("scan_stop", &name, &format!("{{\"aps_found\": {}}}", table.access_points().len()))?;
log.record(
"scan_stop",
&name,
&format!("{{\"aps_found\": {}}}", table.access_points().len()),
)?;
println!("\n{:<18} {:<24} {:<8} {:<8} {:<6} {}", "BSSID", "SSID", "CANAL", "SINAL", "SEG", "CLIENTES");
println!(
"\n{:<18} {:<24} {:<8} {:<8} {:<6} CLIENTES",
"BSSID", "SSID", "CANAL", "SINAL", "SEG"
);
for ap in table.access_points() {
println!(
"{:<18} {:<24} {:<8} {:<8} {:<6} {}",
ap.bssid,
ap.ssid.unwrap_or_default(),
ap.channel.map(|c| c.to_string()).unwrap_or_default(),
ap.last_signal_dbm.map(|s| format!("{s}dBm")).unwrap_or_default(),
ap.last_signal_dbm
.map(|s| format!("{s}dBm"))
.unwrap_or_default(),
ap.security.unwrap_or_default(),
ap.clients.len()
);