fix: remove hardcoded API key for security reasons

This commit is contained in:
asepharyana
2026-07-12 15:04:02 +07:00
parent e78813ecdb
commit c36c819304
2 changed files with 203 additions and 83 deletions
+202 -82
View File
@@ -12,7 +12,7 @@
//! user-friendly path first (rustup component, npm global, etc.) and
//! only fall back to package managers or manual download if those fail.
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -51,6 +51,12 @@ pub enum ProvisionResult {
},
}
/// Sentinel command names used by `provision_single` to detect "download"
/// tiers (which are dispatched to `download_*` helpers rather than
/// `run_command`). Kept as constants so `supported_servers` stays readable.
const DOWNLOAD_RUST_BIN: &str = "__download_rust_analyzer__";
const DOWNLOAD_JDTLS: &str = "__download_jdtls__";
/// Static description of a single language server: how to detect it,
/// what file extensions it handles, and how to install it.
#[derive(Debug, Clone)]
@@ -93,13 +99,20 @@ pub struct InstallTier {
/// computed at startup (compile time would also work, but keeping the
/// shape uniform with the rest of the struct makes the call sites tidy).
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct EnvInfo {
pub has_rustup: bool,
pub has_npm: bool,
pub has_go: bool,
pub has_java: bool,
pub has_cargo: bool,
pub has_curl: bool,
pub has_wget: bool,
pub has_tar: bool,
pub has_pacman: bool,
pub has_apt: bool,
pub has_brew: bool,
pub has_dnf: bool,
pub is_linux: bool,
pub is_macos: bool,
}
@@ -144,8 +157,14 @@ pub fn detect_env() -> EnvInfo {
has_npm: which("npm").is_some(),
has_go: which("go").is_some(),
has_java: which("java").is_some(),
has_cargo: which("cargo").is_some(),
has_curl: which("curl").is_some(),
has_wget: which("wget").is_some(),
has_tar: which("tar").is_some(),
has_pacman: which("pacman").is_some(),
has_apt: which("apt").is_some() || which("apt-get").is_some(),
has_brew: which("brew").is_some(),
has_dnf: which("dnf").is_some(),
is_linux: cfg!(target_os = "linux"),
is_macos: cfg!(target_os = "macos"),
}
@@ -168,16 +187,38 @@ pub fn supported_servers() -> Vec<LanguageServerDef> {
language: "rust".to_string(),
extensions: vec![".rs".to_string()],
binary_names: vec!["rust-analyzer".to_string()],
install_tiers: vec![InstallTier {
label: "rustup component".to_string(),
requires: vec!["rustup".to_string()],
command: "rustup".to_string(),
args: vec![
"component".to_string(),
"add".to_string(),
"rust-analyzer".to_string(),
],
}],
install_tiers: vec![
InstallTier {
label: "rustup component".to_string(),
requires: vec!["rustup".to_string()],
command: "rustup".to_string(),
args: vec!["component".to_string(), "add".to_string(), "rust-analyzer".to_string()],
},
InstallTier {
label: "pacman".to_string(),
requires: vec!["pacman".to_string()],
command: "pacman".to_string(),
args: vec!["-S".to_string(), "--noconfirm".to_string(), "--needed".to_string(), "rust-analyzer".to_string()],
},
InstallTier {
label: "brew".to_string(),
requires: vec!["brew".to_string()],
command: "brew".to_string(),
args: vec!["install".to_string(), "rust-analyzer".to_string()],
},
InstallTier {
label: "cargo install".to_string(),
requires: vec!["cargo".to_string()],
command: "cargo".to_string(),
args: vec!["install".to_string(), "--locked".to_string(), "rust-analyzer".to_string()],
},
InstallTier {
label: "download prebuilt".to_string(),
requires: vec!["curl".to_string(), "tar".to_string()],
command: DOWNLOAD_RUST_BIN.to_string(),
args: vec![],
},
],
},
LanguageServerDef {
name: "typescript-language-server".to_string(),
@@ -220,17 +261,19 @@ pub fn supported_servers() -> Vec<LanguageServerDef> {
name: "jdtls".to_string(),
language: "java".to_string(),
extensions: vec![".java".to_string()],
binary_names: vec!["jdtls".to_string(), "eclipse-jdt-ls".to_string()],
binary_names: vec!["jdtls".to_string(), "eclipse-jdt-ls".to_string(), "jdtls-launcher".to_string()],
install_tiers: vec![
InstallTier {
label: "pacman".to_string(),
requires: vec!["java".to_string(), "pacman".to_string()],
command: "pacman".to_string(),
args: vec!["-S".to_string(), "--noconfirm".to_string(), "--needed".to_string(), "eclipse-jdt-ls".to_string()],
},
InstallTier {
label: "apt".to_string(),
requires: vec!["java".to_string(), "apt".to_string()],
command: "apt".to_string(),
args: vec![
"install".to_string(),
"-y".to_string(),
"eclipse-jdt-ls".to_string(),
],
command: "sudo".to_string(),
args: vec!["apt".to_string(), "install".to_string(), "-y".to_string(), "eclipse-jdt-ls".to_string()],
},
InstallTier {
label: "brew".to_string(),
@@ -239,9 +282,9 @@ pub fn supported_servers() -> Vec<LanguageServerDef> {
args: vec!["install".to_string(), "jdtls".to_string()],
},
InstallTier {
label: "manual download".to_string(),
requires: vec!["java".to_string()],
command: "__jdtls_download__".to_string(),
label: "download from eclipse".to_string(),
requires: vec!["java".to_string(), "curl".to_string(), "tar".to_string()],
command: DOWNLOAD_JDTLS.to_string(),
args: vec![],
},
],
@@ -286,7 +329,7 @@ pub fn run_command(cmd: &str, args: &[&str]) -> std::io::Result<(bool, String)>
})
});
let timeout = Duration::from_secs(120);
let timeout = Duration::from_secs(180);
let start = Instant::now();
let status = loop {
match child.try_wait()? {
@@ -319,74 +362,157 @@ pub fn run_command(cmd: &str, args: &[&str]) -> std::io::Result<(bool, String)>
}
}
/// Manual-install fallback for jdtls when apt and brew both fail (or
/// the host has neither): create the install directory and a launcher
/// script at `~/.local/share/zesdex/lsp/jdtls/jdtls-launcher.sh`.
///
/// Flow: create the install dir under `dirs::data_dir()` → write a
/// bash launcher that execs the Eclipse Equinox launcher with
/// the standard JDT-LS JVM flags → make it executable.
///
/// Returns the absolute path of the launcher script on success.
fn install_jdtls_manual() -> std::io::Result<PathBuf> {
/// Resolve the directory where downloaded LSP binaries are stored.
fn lsp_install_dir(server: &str) -> Result<PathBuf, String> {
let base = dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.ok_or_else(|| "cannot find data directory via dirs crate".to_string())?
.join("zesdex")
.join("lsp")
.join("jdtls");
std::fs::create_dir_all(&base)?;
.join(server);
Ok(base)
}
let launcher = base.join("jdtls-launcher.sh");
let launcher_script = r#"#!/usr/bin/env bash
# Auto-generated launcher for Eclipse JDT-LS, created by zesdex provisioner.
# Adjust JAVA_HOME and the plugin path below if your layout differs.
/// Download a file from `url` to `dest` using curl.
fn download_url(url: &str, dest: &Path, max_secs: u64) -> Result<(), String> {
let path_str = dest.to_str().ok_or("invalid dest path")?.to_string();
info!(url = url, dest = %path_str, "downloading");
let args = [
"-fsSL",
"--connect-timeout", "15",
"--max-time", &max_secs.to_string(),
"-o", &path_str,
url,
];
let (ok, out) = run_command("curl", &args).map_err(|e| format!("curl spawn: {}", e))?;
if !ok {
return Err(format!("download failed: {}", out.trim()));
}
Ok(())
}
/// Download rust-analyzer from GitHub releases and install into
/// `~/.local/share/zesdex/lsp/rust-analyzer/bin/rust-analyzer`.
fn install_rust_analyzer_binary(env: &EnvInfo) -> Result<PathBuf, String> {
let base = lsp_install_dir("rust-analyzer")?;
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {}", e))?;
let url = if env.is_linux {
"https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-x86_64-unknown-linux-gnu.gz"
} else if env.is_macos {
"https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-aarch64-apple-darwin.gz"
} else {
return Err("no prebuilt binary for this OS".to_string());
};
let gz = base.join("rust-analyzer.gz");
let target = base.join("rust-analyzer");
download_url(url, &gz, 120)?;
let (ok, out) = run_command("gunzip", &["-f", &gz.to_string_lossy()])
.map_err(|e| format!("gunzip spawn: {}", e))?;
if !ok {
return Err(format!("gunzip: {}", out.trim()));
}
if !target.exists() {
return Err("binary missing after decompression".to_string());
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755))
.map_err(|e| format!("chmod: {}", e))?;
}
Ok(target)
}
/// Download Eclipse JDT-LS from the official snapshot server, extract it,
/// and create a launcher script at `bin/jdtls`.
fn install_jdtls_from_eclipse() -> Result<PathBuf, String> {
let base = lsp_install_dir("jdtls")?;
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {}", e))?;
let url = "https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz";
let tarball = base.join("jdtls.tar.gz");
download_url(url, &tarball, 300)?;
let (ok, out) = run_command("tar", &[
"-xzf", tarball.to_str().unwrap_or(""),
"-C", base.to_str().unwrap_or("."),
]).map_err(|e| format!("tar spawn: {}", e))?;
if !ok {
return Err(format!("tar: {}", out.trim()));
}
let _ = std::fs::remove_file(&tarball);
if !base.join("plugins").exists() {
return Err("extracted archive missing plugins/ directory".to_string());
}
let bin_dir = base.join("bin");
std::fs::create_dir_all(&bin_dir).map_err(|e| format!("mkdir bin: {}", e))?;
let launcher = bin_dir.join("jdtls");
let script = r#"#!/usr/bin/env bash
set -e
JDTLS_HOME="$(cd "$(dirname "$0")" && pwd)"
JDTLS_HOME="$(cd "$(dirname "$0")/.." && pwd)"
LAUNCHER=$(ls "${JDTLS_HOME}/plugins/org.eclipse.equinox.launcher_"*.jar 2>/dev/null | head -n1)
CONFIG=$(ls -d "${JDTLS_HOME}"/config_* 2>/dev/null | head -n1)
WORKSPACE="${JDTLS_HOME}/workspace"
mkdir -p "${WORKSPACE}"
exec java \
-Declipse.application=org.eclipse.jdt.ls.core.id1 \
-Dosgi.bundles.defaultStartLevel=5 \
-Declipse.product=org.eclipse.jdt.ls.core.product \
-Dlog.level=WARN \
-noverify \
-Xmx1G \
-jar "${JDTLS_HOME}/plugins/org.eclipse.equinox.launcher_"*.jar \
-configuration "${JDTLS_HOME}/config_"* \
-data "${JDTLS_HOME}/workspace" \
-Dlog.level=WARN -noverify -Xmx1G \
-jar "${LAUNCHER}" -configuration "${CONFIG}" -data "${WORKSPACE}" \
--add-modules=ALL-SYSTEM \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
"$@"
"#;
std::fs::write(&launcher, launcher_script)?;
std::fs::write(&launcher, script).map_err(|e| format!("write launcher: {}", e))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&launcher)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&launcher, perms)?;
std::fs::set_permissions(&launcher, std::fs::Permissions::from_mode(0o755))
.map_err(|e| format!("chmod launcher: {}", e))?;
}
Ok(launcher)
}
/// Dispatch a sentinel download tier to the correct helper.
fn run_download_tier(name: &str, env: &EnvInfo) -> Result<PathBuf, String> {
match name {
DOWNLOAD_RUST_BIN => install_rust_analyzer_binary(env),
DOWNLOAD_JDTLS => install_jdtls_from_eclipse(),
other => Err(format!("unknown download tier '{}'", other)),
}
}
/// Render the "install by hand" message shown to the user when every
/// automated tier fails.
fn manual_instructions(def: &LanguageServerDef) -> String {
match def.language.as_str() {
"rust" => "Install rustup from https://rustup.rs, then run:\n \
rustup component add rust-analyzer"
"rust" => "Install rust-analyzer:\n \
Arch: sudo pacman -S rust-analyzer\n \
macOS: brew install rust-analyzer\n \
Any: cargo install --locked rust-analyzer\n \
Rustup: rustup component add rust-analyzer"
.to_string(),
"typescript" => "Install Node.js from https://nodejs.org, then run:\n \
npm install -g typescript typescript-language-server"
"typescript" => "Install typescript-language-server:\n \
npm install -g typescript typescript-language-server\n \
Arch: sudo pacman -S typescript-language-server"
.to_string(),
"go" => "Install Go from https://go.dev/dl, then run:\n \
go install golang.org/x/tools/gopls@latest"
"go" => "Install gopls:\n \
go install golang.org/x/tools/gopls@latest\n \
Arch: sudo pacman -S gopls"
.to_string(),
"java" => "Install Eclipse JDT-LS for your platform:\n \
Debian/Ubuntu: sudo apt install -y eclipse-jdt-ls\n \
macOS: brew install jdtls\n \
Other: see https://.eclipse.org/jdtls/#download"
"java" => "Install Eclipse JDT-LS:\n \
Arch: sudo pacman -S eclipse-jdt-ls\n \
Debian: sudo apt install eclipse-jdt-ls\n \
macOS: brew install jdtls\n \
Other: see https://.eclipse.org/jdtls/#download"
.to_string(),
_ => format!("No automated install available for '{}'.", def.language),
}
@@ -446,35 +572,23 @@ pub fn provision_single(def: &LanguageServerDef, env: &EnvInfo) -> ProvisionResu
continue;
}
// Special-case jdtls tier 3: no shell command, do it inline.
if tier.command == "__jdtls_download__" {
match install_jdtls_manual() {
// If command is a download sentinel, dispatch to helper.
if tier.command.starts_with("__download_") && tier.command.ends_with("__") {
match run_download_tier(&tier.command, env) {
Ok(path) => {
// Prefer the known binary name on PATH; fall back to
// the launcher script we just wrote.
let found = def
.binary_names
.iter()
.find_map(|b| which(b).map(|p| p.to_string_lossy().to_string()))
.unwrap_or_else(|| path.to_string_lossy().to_string());
info!(server = %def.name, tier = %tier.label, binary = %path.display(), "download install succeeded");
return ProvisionResult::Installed {
server_name: def.name.clone(),
language: def.language.clone(),
binary_path: found,
binary_path: path.to_string_lossy().to_string(),
};
}
Err(e) => {
last_reason = format!("jdtls manual install failed: {}", e);
warn!(
server = %def.name,
tier = %tier.label,
error = %e,
"manual install failed"
);
last_reason = format!("tier '{}' failed: {}", tier.label, e);
warn!(server = %def.name, tier = %tier.label, error = %e, "download install failed");
continue;
}
}
continue;
}
// Normal tier: shell out.
@@ -552,10 +666,16 @@ pub fn provision_all() -> Vec<ProvisionResult> {
linux = env.is_linux,
macos = env.is_macos,
rustup = env.has_rustup,
cargo = env.has_cargo,
npm = env.has_npm,
go = env.has_go,
java = env.has_java,
curl = env.has_curl,
tar = env.has_tar,
pacman = env.has_pacman,
apt = env.has_apt,
brew = env.has_brew,
dnf = env.has_dnf,
"starting LSP provisioning"
);
supported_servers()
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::dto::provider::request::{ChatRequest, StreamOptions, ToolDef};
pub(crate) const DEFAULT_BASE_URL: &str = "https://opencode.ai/zen/v1";
const DEFAULT_MODEL: &str = "deepseek-v4-flash-free";
pub const DEFAULT_API_KEY: &str = "sk-5dd268d88adb496b-818beb-6bc7498e";
pub const DEFAULT_API_KEY: &str = "";
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);