feat(openwrt): add OpenVPN package installation workflow

This commit is contained in:
litoral05
2026-05-06 17:31:56 +01:00
parent 11dbaca04c
commit b5850d36e6
3 changed files with 266 additions and 1 deletions
+2 -1
View File
@@ -7,7 +7,8 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![
openwrt::detect_openwrt_router,
openwrt::test_openwrt_ssh,
openwrt::prepare_openwrt_router
openwrt::prepare_openwrt_router,
openwrt::install_openwrt_openvpn_packages
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+148
View File
@@ -52,6 +52,18 @@ pub struct OpenWrtPrepareResult {
logs: String,
}
#[derive(Serialize)]
pub struct OpenWrtPackageInstallResult {
success: bool,
updated_opkg: bool,
installed_openvpn: bool,
installed_luci: bool,
installed_ca_bundle: bool,
installed_kmod_tun: bool,
message: String,
logs: String,
}
#[tauri::command]
pub fn detect_openwrt_router() -> OpenWrtDetectionResult {
let candidates = vec![detect_default_gateway(), Some("192.168.1.1".to_string())];
@@ -299,6 +311,123 @@ pub fn prepare_openwrt_router(
}
}
#[tauri::command]
pub fn install_openwrt_openvpn_packages(
host: String,
username: String,
password: String
) -> OpenWrtPackageInstallResult {
let address = format!("{}:22", host);
let socket = match address.parse::<SocketAddr>() {
Ok(socket) => socket,
Err(err) => {
return OpenWrtPackageInstallResult {
success: false,
updated_opkg: false,
installed_openvpn: false,
installed_luci: false,
installed_ca_bundle: false,
installed_kmod_tun: false,
message: format!("Endereço inválido: {}", err),
logs: "".to_string(),
};
}
};
let tcp = match TcpStream::connect_timeout(&socket, Duration::from_secs(5)) {
Ok(stream) => stream,
Err(err) => {
return OpenWrtPackageInstallResult {
success: false,
updated_opkg: false,
installed_openvpn: false,
installed_luci: false,
installed_ca_bundle: false,
installed_kmod_tun: false,
message: format!("Falha ao ligar por TCP/SSH: {}", err),
logs: "".to_string(),
};
}
};
let mut session = match Session::new() {
Ok(session) => session,
Err(err) => {
return OpenWrtPackageInstallResult {
success: false,
updated_opkg: false,
installed_openvpn: false,
installed_luci: false,
installed_ca_bundle: false,
installed_kmod_tun: false,
message: format!("Falha ao criar sessão SSH: {}", err),
logs: "".to_string(),
};
}
};
session.set_tcp_stream(tcp);
if let Err(err) = session.handshake() {
return OpenWrtPackageInstallResult {
success: false,
updated_opkg: false,
installed_openvpn: false,
installed_luci: false,
installed_ca_bundle: false,
installed_kmod_tun: false,
message: format!("Falha no handshake SSH: {}", err),
logs: "".to_string(),
};
}
if let Err(err) = session.userauth_password(&username, &password) {
return OpenWrtPackageInstallResult {
success: false,
updated_opkg: false,
installed_openvpn: false,
installed_luci: false,
installed_ca_bundle: false,
installed_kmod_tun: false,
message: format!("Falha na autenticação SSH: {}", err),
logs: "".to_string(),
};
}
let command = build_openvpn_install_command();
let logs = run_ssh_command(&session, &command).unwrap_or_default();
let updated_opkg = logs.contains("OPKG_UPDATED");
let installed_openvpn = logs.contains("OPENVPN_OK");
let installed_luci = logs.contains("LUCI_OK");
let installed_ca_bundle = logs.contains("CA_BUNDLE_OK");
let installed_kmod_tun = logs.contains("KMOD_TUN_OK");
let success =
updated_opkg &&
installed_openvpn &&
installed_luci &&
installed_ca_bundle &&
installed_kmod_tun;
OpenWrtPackageInstallResult {
success,
updated_opkg,
installed_openvpn,
installed_luci,
installed_ca_bundle,
installed_kmod_tun,
message: if success {
"Pacotes OpenVPN instalados com sucesso.".to_string()
} else {
"Instalação incompleta. Verifique os logs.".to_string()
},
logs,
}
}
fn tcp_port_open(host: &str, port: u16) -> bool {
let address = format!("{}:{}", host, port);
@@ -645,3 +774,22 @@ fn build_prepare_command(target_hostname: &str, new_password: &str) -> String {
fn shell_escape_single_quotes(value: &str) -> String {
value.replace('\'', "'\\''")
}
fn build_openvpn_install_command() -> String {
"\
set -e; \
echo '=== OPENVPN PACKAGE INSTALL START ==='; \
echo 'Updating OPKG repositories...'; \
opkg update; \
echo OPKG_UPDATED; \
echo 'Installing OpenVPN packages...'; \
opkg install openvpn-openssl luci-app-openvpn ca-bundle kmod-tun; \
echo PACKAGES_INSTALL_DONE; \
echo 'Validating installed packages...'; \
command -v openvpn >/dev/null 2>&1 && echo OPENVPN_OK || echo OPENVPN_FAIL; \
opkg list-installed | grep -q '^luci-app-openvpn ' && echo LUCI_OK || echo LUCI_FAIL; \
opkg list-installed | grep -q '^ca-bundle ' && echo CA_BUNDLE_OK || echo CA_BUNDLE_FAIL; \
opkg list-installed | grep -q '^kmod-tun ' && echo KMOD_TUN_OK || echo KMOD_TUN_FAIL; \
echo '=== OPENVPN PACKAGE INSTALL COMPLETE ==='\
".to_string()
}
+116
View File
@@ -54,6 +54,17 @@ type SshResult = {
message: string;
};
type PackageInstallResult = {
success: boolean;
updated_opkg: boolean;
installed_openvpn: boolean;
installed_luci: boolean;
installed_ca_bundle: boolean;
installed_kmod_tun: boolean;
message: string;
logs: string;
};
type Props = {
routers: RouterItem[];
};
@@ -75,6 +86,9 @@ export function OpenWrtConfigPage({ routers }: Props) {
const [preparing, setPreparing] = useState(false);
const [prepareResult, setPrepareResult] = useState<PrepareResult | null>(null);
const [installingPackages, setInstallingPackages] = useState(false);
const [packageResult, setPackageResult] = useState<PackageInstallResult | null>(null);
const compatibility = sshResult?.compatibility;
async function detectRouter() {
@@ -134,10 +148,43 @@ export function OpenWrtConfigPage({ routers }: Props) {
}
}
async function installOpenVpnPackages() {
try {
setInstallingPackages(true);
setPackageResult(null);
const result = await invoke<PackageInstallResult>(
"install_openwrt_openvpn_packages",
{
host,
username,
password,
}
);
setPackageResult(result);
if (result.success) {
const refreshed = await invoke<SshResult>("test_openwrt_ssh", {
host,
username,
password,
});
setSshResult(refreshed);
}
} finally {
setInstallingPackages(false);
}
}
async function connectSsh() {
try {
setConnecting(true);
setPackageResult(null);
setPrepareResult(null);
const result = await invoke<SshResult>("test_openwrt_ssh", {
host,
username,
@@ -394,6 +441,22 @@ export function OpenWrtConfigPage({ routers }: Props) {
value={sshResult.readiness.free_memory || "-"}
/>
</div>
<div className="modal-actions">
<button
className="primary"
onClick={installOpenVpnPackages}
disabled={
installingPackages ||
!sshResult?.connected ||
!sshResult.readiness.opkg_ok ||
!sshResult.readiness.internet_ok ||
!sshResult.readiness.dns_ok
}
>
{installingPackages ? "A instalar pacotes..." : "Instalar Pacotes OpenVPN"}
</button>
</div>
</div>
)}
@@ -543,6 +606,59 @@ export function OpenWrtConfigPage({ routers }: Props) {
)}
</div>
)}
{packageResult && (
<div className="dashboard-card wide-panel">
<div className="card-header">
<div>
<h2>Instalação OpenVPN</h2>
<p>Pacotes necessários no router</p>
</div>
<span className={`badge ${packageResult.success ? "success" : "failed"}`}>
{packageResult.success ? "Sucesso" : "Falhou"}
</span>
</div>
<div className="detail-list">
<DetailRow
label="OPKG update"
value={packageResult.updated_opkg ? "OK" : "Falhou"}
/>
<DetailRow
label="openvpn-openssl"
value={packageResult.installed_openvpn ? "Instalado" : "Falhou"}
/>
<DetailRow
label="luci-app-openvpn"
value={packageResult.installed_luci ? "Instalado" : "Falhou"}
/>
<DetailRow
label="ca-bundle"
value={packageResult.installed_ca_bundle ? "Instalado" : "Falhou"}
/>
<DetailRow
label="kmod-tun"
value={packageResult.installed_kmod_tun ? "Instalado" : "Falhou"}
/>
</div>
<div className="status-note">
{packageResult.success ? <CheckCircle2 size={24} /> : <XCircle size={24} />}
<p>{packageResult.message}</p>
</div>
{packageResult.logs && (
<div className="log-box">
<pre>{packageResult.logs}</pre>
</div>
)}
</div>
)}
</div>
</section>