From ccaf521da4547d56fe2e8189fb225d2d2669ff82 Mon Sep 17 00:00:00 2001 From: robonen Date: Sun, 2 Aug 2026 22:21:49 +0700 Subject: [PATCH] feat: update syshelper version to 0.2.0 and enhance firewall rules in installer --- Cargo.lock | 2 +- Cargo.toml | 2 +- installer/setup.iss | 27 +++++++++++++++++++++++++++ src/main.rs | 21 +++++++++++++-------- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b137ad4..2ff8ecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1224,7 +1224,7 @@ dependencies = [ [[package]] name = "syshelper" -version = "0.1.1" +version = "0.2.0" dependencies = [ "argon2", "axum", diff --git a/Cargo.toml b/Cargo.toml index 43a963c..245b825 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syshelper" -version = "0.1.1" +version = "0.2.0" edition = "2021" [dependencies] diff --git a/installer/setup.iss b/installer/setup.iss index 2c89a77..e9782fb 100644 --- a/installer/setup.iss +++ b/installer/setup.iss @@ -234,6 +234,31 @@ begin '', SW_HIDE, ewWaitUntilTerminated, Code); end; +// Без правила брандмауэра панель доступна только с самого компьютера: Windows +// режет входящие подключения по умолчанию. Ограничиваемся частными и доменными +// сетями — в публичной (кафе, вокзал) панель наружу смотреть не должна. +procedure RemoveFirewallRule(); +var + Code: Integer; +begin + Exec(ExpandConstant('{sys}\netsh.exe'), + 'advfirewall firewall delete rule name="{#AppName}"', + '', SW_HIDE, ewWaitUntilTerminated, Code); +end; + +procedure OpenFirewallPort(); +var + Code: Integer; +begin + // сначала снимаем старое правило, иначе при переустановке они накопятся + RemoveFirewallRule(); + Exec(ExpandConstant('{sys}\netsh.exe'), + 'advfirewall firewall add rule name="{#AppName}" dir=in action=allow' + + ' protocol=TCP localport=8787 profile=private,domain' + + ' program="' + ExpandConstant('{app}\{#AppExeName}') + '"', + '', SW_HIDE, ewWaitUntilTerminated, Code); +end; + procedure CurStepChanged(CurStep: TSetupStep); var Started: Boolean; @@ -245,6 +270,7 @@ begin // только затем поднимаем службу. LockDownDataDir(); SetPassword(); + OpenFirewallPort(); Started := InstallService(); // Обе проверки — про молчаливый отказ: без них установка отрапортует об @@ -270,4 +296,5 @@ begin StopService(); RunSc('delete {#ServiceName}'); + RemoveFirewallRule(); end; diff --git a/src/main.rs b/src/main.rs index 26e0787..965077e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,12 @@ mod auth; mod service; mod update; -const ADDR: &str = "127.0.0.1:8787"; +/// Слушаем все интерфейсы, чтобы панель открывалась и с телефона или другого +/// компьютера в домашней сети. Правило брандмауэра ставит установщик, и только +/// для частных сетей — в публичной панель наружу смотреть не должна. +const BIND_ADDR: &str = "0.0.0.0:8787"; +/// Адрес для ярлыков и сообщений: 0.0.0.0 в браузер не введёшь. +const PANEL_URL: &str = "http://127.0.0.1:8787"; /// Служебные команды не должны мигать консольным окном. #[cfg(windows)] pub(crate) const CREATE_NO_WINDOW: u32 = 0x0800_0000; @@ -125,19 +130,17 @@ fn stamp() -> String { /// Открывает панель в браузере. Своего окна у программы нет, поэтому запуск /// ярлыком без этого выглядел бы как «ничего не произошло». fn open_panel() { - let url = format!("http://{ADDR}"); - #[cfg(windows)] { use std::os::windows::process::CommandExt; // пустой заголовок обязателен: иначе start примет URL за имя окна let _ = std::process::Command::new("cmd") - .args(["/C", "start", "", &url]) + .args(["/C", "start", "", PANEL_URL]) .creation_flags(CREATE_NO_WINDOW) .spawn(); } #[cfg(not(windows))] - let _ = std::process::Command::new("open").arg(&url).spawn(); + let _ = std::process::Command::new("open").arg(PANEL_URL).spawn(); } /// Окна нет, поэтому паника иначе прошла бы совершенно незаметно: процесс @@ -612,12 +615,12 @@ async fn bind_panel( ) -> Option { let mut complained = false; loop { - match tokio::net::TcpListener::bind(ADDR).await { + match tokio::net::TcpListener::bind(BIND_ADDR).await { Ok(listener) => return Some(listener), Err(e) if !complained => { complained = true; // в журнал раз, а не каждые полминуты state - .log(format!("{ADDR} занят ({e}) — жду, правила при этом работают")) + .log(format!("{BIND_ADDR} занят ({e}) — жду, правила при этом работают")) .await; } Err(_) => {} @@ -681,7 +684,9 @@ async fn serve(shutdown: tokio::sync::watch::Receiver) { let Some(listener) = bind_panel(&state, shutdown.clone()).await else { return; // службу остановили, пока ждали порт }; - state.log(format!("панель на http://{ADDR}")).await; + state + .log(format!("панель на {PANEL_URL} и на порту 8787 в локальной сети")) + .await; let mut stop = shutdown.clone(); let result = axum::serve(listener, app)