summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src-tauri
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-01-13 13:58:00 +0800
committerGitHub <[email protected]>2026-01-13 13:58:00 +0800
commitf05f175842d57003692ea766b07857d6f4b05f2b (patch)
treeed9d6ce0d89a1f7b615970183366e9d7031ae908 /packages/desktop/src-tauri
parentc0b214232d8820f53c6a9f7fc1d38334012417db (diff)
downloadopencode-f05f175842d57003692ea766b07857d6f4b05f2b.tar.gz
opencode-f05f175842d57003692ea766b07857d6f4b05f2b.zip
feat(desktop): spawn local server with password (#8139)
Diffstat (limited to 'packages/desktop/src-tauri')
-rw-r--r--packages/desktop/src-tauri/Cargo.lock7
-rw-r--r--packages/desktop/src-tauri/Cargo.toml1
-rw-r--r--packages/desktop/src-tauri/src/lib.rs76
3 files changed, 61 insertions, 23 deletions
diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock
index 92953ea19..43f24a6ad 100644
--- a/packages/desktop/src-tauri/Cargo.lock
+++ b/packages/desktop/src-tauri/Cargo.lock
@@ -2814,6 +2814,7 @@ dependencies = [
"tauri-plugin-updater",
"tauri-plugin-window-state",
"tokio",
+ "uuid",
"webkit2gtk",
]
@@ -5364,13 +5365,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.18.1"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom 0.3.4",
"js-sys",
- "serde",
+ "serde_core",
"wasm-bindgen",
]
diff --git a/packages/desktop/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml
index 8033d4f14..3145ae4b2 100644
--- a/packages/desktop/src-tauri/Cargo.toml
+++ b/packages/desktop/src-tauri/Cargo.toml
@@ -39,6 +39,7 @@ tauri-plugin-os = "2"
futures = "0.3.31"
semver = "1.0.27"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
+uuid = { version = "1.19.0", features = ["v4"] }
[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18.2"
diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs
index b479ed0b6..e2682ec71 100644
--- a/packages/desktop/src-tauri/src/lib.rs
+++ b/packages/desktop/src-tauri/src/lib.rs
@@ -3,6 +3,7 @@ mod window_customizer;
use cli::{install_cli, sync_cli};
use futures::FutureExt;
+use futures::future;
use std::{
collections::VecDeque,
net::TcpListener,
@@ -13,22 +14,29 @@ use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl, Webvie
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
use tauri_plugin_store::StoreExt;
+use tokio::sync::oneshot;
use crate::window_customizer::PinchZoomDisablePlugin;
const SETTINGS_STORE: &str = "opencode.settings.dat";
const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
+#[derive(Clone, serde::Serialize)]
+struct ServerReadyData {
+ url: String,
+ password: Option<String>,
+}
+
#[derive(Clone)]
struct ServerState {
child: Arc<Mutex<Option<CommandChild>>>,
- status: futures::future::Shared<tokio::sync::oneshot::Receiver<Result<String, String>>>,
+ status: future::Shared<oneshot::Receiver<Result<ServerReadyData, String>>>,
}
impl ServerState {
pub fn new(
child: Option<CommandChild>,
- status: tokio::sync::oneshot::Receiver<Result<String, String>>,
+ status: oneshot::Receiver<Result<ServerReadyData, String>>,
) -> Self {
Self {
child: Arc::new(Mutex::new(child)),
@@ -80,7 +88,7 @@ async fn get_logs(app: AppHandle) -> Result<String, String> {
}
#[tauri::command]
-async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<String, String> {
+async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerReadyData, String> {
state
.status
.clone()
@@ -137,13 +145,14 @@ fn get_sidecar_port() -> u32 {
}) as u32
}
-fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild {
+fn spawn_sidecar(app: &AppHandle, port: u32, password: &str) -> CommandChild {
let log_state = app.state::<LogState>();
let log_state_clone = log_state.inner().clone();
println!("spawning sidecar on port {port}");
let (mut rx, child) = cli::create_command(app, format!("serve --port {port}").as_str())
+ .env("OPENCODE_SERVER_PASSWORD", password)
.spawn()
.expect("Failed to spawn opencode");
@@ -184,7 +193,7 @@ fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild {
child
}
-async fn check_server_health(url: &str) -> bool {
+async fn check_server_health(url: &str, password: Option<&str>) -> bool {
let health_url = format!("{}/health", url.trim_end_matches('/'));
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
@@ -194,9 +203,13 @@ async fn check_server_health(url: &str) -> bool {
return false;
};
- client
- .get(&health_url)
- .send()
+ let mut req = client.get(&health_url);
+
+ if let Some(password) = password {
+ req = req.basic_auth("opencode", Some(password));
+ }
+
+ req.send()
.await
.map(|r| r.status().is_success())
.unwrap_or(false)
@@ -267,7 +280,7 @@ pub fn run() {
window_builder.build().expect("Failed to create window");
- let (tx, rx) = tokio::sync::oneshot::channel();
+ let (tx, rx) = oneshot::channel();
app.manage(ServerState::new(None, rx));
{
@@ -344,12 +357,18 @@ fn get_server_url_from_config(config: &cli::Config) -> Option<String> {
async fn setup_server_connection(
app: &AppHandle,
custom_url: Option<String>,
-) -> Result<(Option<CommandChild>, String), String> {
+) -> Result<(Option<CommandChild>, ServerReadyData), String> {
if let Some(url) = custom_url {
loop {
- if check_server_health(&url).await {
+ if check_server_health(&url, None).await {
println!("Connected to custom server: {}", url);
- return Ok((None, url.clone()));
+ return Ok((
+ None,
+ ServerReadyData {
+ url: url.clone(),
+ password: None,
+ },
+ ));
}
const RETRY: &str = "Retry";
@@ -374,19 +393,36 @@ async fn setup_server_connection(
let local_port = get_sidecar_port();
let local_url = format!("http://127.0.0.1:{local_port}");
- if !check_server_health(&local_url).await {
- match spawn_local_server(app, local_port).await {
- Ok(child) => Ok(Some(child)),
+ if !check_server_health(&local_url, None).await {
+ let password = uuid::Uuid::new_v4().to_string();
+
+ match spawn_local_server(app, local_port, &password).await {
+ Ok(child) => Ok((
+ Some(child),
+ ServerReadyData {
+ url: local_url,
+ password: Some(password),
+ },
+ )),
Err(err) => Err(err),
}
} else {
- Ok(None)
+ Ok((
+ None,
+ ServerReadyData {
+ url: local_url,
+ password: None,
+ },
+ ))
}
- .map(|child| (child, local_url))
}
-async fn spawn_local_server(app: &AppHandle, port: u32) -> Result<CommandChild, String> {
- let child = spawn_sidecar(app, port);
+async fn spawn_local_server(
+ app: &AppHandle,
+ port: u32,
+ password: &str,
+) -> Result<CommandChild, String> {
+ let child = spawn_sidecar(app, port, password);
let url = format!("http://127.0.0.1:{port}");
let timestamp = Instant::now();
@@ -400,7 +436,7 @@ async fn spawn_local_server(app: &AppHandle, port: u32) -> Result<CommandChild,
tokio::time::sleep(Duration::from_millis(10)).await;
- if check_server_health(&url).await {
+ if check_server_health(&url, Some(password)).await {
println!("Server ready after {:?}", timestamp.elapsed());
break Ok(child);
}