feat(openwrt): add OpenVPN package installation workflow
This commit is contained in:
@@ -7,7 +7,8 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
openwrt::detect_openwrt_router,
|
openwrt::detect_openwrt_router,
|
||||||
openwrt::test_openwrt_ssh,
|
openwrt::test_openwrt_ssh,
|
||||||
openwrt::prepare_openwrt_router
|
openwrt::prepare_openwrt_router,
|
||||||
|
openwrt::install_openwrt_openvpn_packages
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ pub struct OpenWrtPrepareResult {
|
|||||||
logs: String,
|
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]
|
#[tauri::command]
|
||||||
pub fn detect_openwrt_router() -> OpenWrtDetectionResult {
|
pub fn detect_openwrt_router() -> OpenWrtDetectionResult {
|
||||||
let candidates = vec![detect_default_gateway(), Some("192.168.1.1".to_string())];
|
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 {
|
fn tcp_port_open(host: &str, port: u16) -> bool {
|
||||||
let address = format!("{}:{}", host, port);
|
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 {
|
fn shell_escape_single_quotes(value: &str) -> String {
|
||||||
value.replace('\'', "'\\''")
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,17 @@ type SshResult = {
|
|||||||
message: string;
|
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 = {
|
type Props = {
|
||||||
routers: RouterItem[];
|
routers: RouterItem[];
|
||||||
};
|
};
|
||||||
@@ -75,6 +86,9 @@ export function OpenWrtConfigPage({ routers }: Props) {
|
|||||||
const [preparing, setPreparing] = useState(false);
|
const [preparing, setPreparing] = useState(false);
|
||||||
const [prepareResult, setPrepareResult] = useState<PrepareResult | null>(null);
|
const [prepareResult, setPrepareResult] = useState<PrepareResult | null>(null);
|
||||||
|
|
||||||
|
const [installingPackages, setInstallingPackages] = useState(false);
|
||||||
|
const [packageResult, setPackageResult] = useState<PackageInstallResult | null>(null);
|
||||||
|
|
||||||
const compatibility = sshResult?.compatibility;
|
const compatibility = sshResult?.compatibility;
|
||||||
|
|
||||||
async function detectRouter() {
|
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() {
|
async function connectSsh() {
|
||||||
try {
|
try {
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
|
|
||||||
|
setPackageResult(null);
|
||||||
|
setPrepareResult(null);
|
||||||
|
|
||||||
const result = await invoke<SshResult>("test_openwrt_ssh", {
|
const result = await invoke<SshResult>("test_openwrt_ssh", {
|
||||||
host,
|
host,
|
||||||
username,
|
username,
|
||||||
@@ -394,6 +441,22 @@ export function OpenWrtConfigPage({ routers }: Props) {
|
|||||||
value={sshResult.readiness.free_memory || "-"}
|
value={sshResult.readiness.free_memory || "-"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -543,6 +606,59 @@ export function OpenWrtConfigPage({ routers }: Props) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user