diff --git a/lapdev-api/src/admin.rs b/lapdev-api/src/admin.rs index ea20b34..c0692b2 100644 --- a/lapdev-api/src/admin.rs +++ b/lapdev-api/src/admin.rs @@ -365,7 +365,7 @@ pub async fn get_cluster_info( machine_types, has_enterprise, hostnames: state.conductor.hostnames.read().await.clone(), - ssh_proxy_port: state.ssh_proxy_port, + ssh_proxy_port: state.ssh_proxy_display_port, })) } diff --git a/lapdev-api/src/router.rs b/lapdev-api/src/router.rs index 67ad449..8e58ff0 100644 --- a/lapdev-api/src/router.rs +++ b/lapdev-api/src/router.rs @@ -299,7 +299,11 @@ async fn handle_catch_all( .and_then(|h| h.to_str().ok()) .map(|s| s.contains("gzip")) .unwrap_or(false); - if let Some(file) = STATIC_DIR.get_file(f) { + if let Some(file) = if let Some(d) = state.static_dir.as_ref() { + d.get_file(f) + } else { + STATIC_DIR.get_file(f) + } { let content_type = if f.ends_with(".css") { Some("text/css") } else if f.ends_with(".js") { @@ -311,7 +315,11 @@ async fn handle_catch_all( }; if let Some(content_type) = content_type { if accept_gzip { - if let Some(file) = STATIC_DIR.get_file(format!("{f}.gz")) { + if let Some(file) = if let Some(d) = state.static_dir.as_ref() { + d.get_file(format!("{f}.gz")) + } else { + STATIC_DIR.get_file(format!("{f}.gz")) + } { return Ok(( [ (axum::http::header::CONTENT_TYPE, content_type), @@ -341,7 +349,11 @@ async fn handle_catch_all( } } - if let Some(file) = STATIC_DIR.get_file("index.html") { + if let Some(file) = if let Some(d) = state.static_dir.as_ref() { + d.get_file("index.html") + } else { + STATIC_DIR.get_file("index.html") + } { return Ok(axum::response::Html::from(file.contents()).into_response()); } diff --git a/lapdev-api/src/server.rs b/lapdev-api/src/server.rs index 0532e83..42b84c1 100644 --- a/lapdev-api/src/server.rs +++ b/lapdev-api/src/server.rs @@ -29,6 +29,7 @@ struct LapdevConfig { http_port: Option, https_port: Option, ssh_proxy_port: Option, + ssh_proxy_display_port: Option, } #[derive(Parser)] @@ -49,7 +50,10 @@ struct Cli { no_migration: bool, } -pub async fn start(additional_router: Option>) { +pub async fn start( + additional_router: Option>, + static_dir: Option>, +) { let cli = Cli::parse(); let data_folder = cli .data_folder @@ -58,7 +62,7 @@ pub async fn start(additional_router: Option>) { let _result = setup_log(&cli, &data_folder).await; - if let Err(e) = run(&cli, additional_router, data_folder).await { + if let Err(e) = run(&cli, additional_router, data_folder, static_dir).await { tracing::error!("lapdev api start server error: {e:#}"); } } @@ -67,6 +71,7 @@ async fn run( cli: &Cli, additional_router: Option>, data_folder: PathBuf, + static_dir: Option>, ) -> Result<()> { let config_file = cli .config_file @@ -85,6 +90,7 @@ async fn run( let conductor = Conductor::new(LAPDEV_VERSION, db.clone(), data_folder).await?; let ssh_proxy_port = config.ssh_proxy_port.unwrap_or(2222); + let ssh_proxy_display_port = config.ssh_proxy_display_port.unwrap_or(2222); { let conductor = conductor.clone(); let bind = config.bind.clone(); @@ -101,7 +107,13 @@ async fn run( }); } - let state = CoreState::new(conductor, ssh_proxy_port).await; + let state = CoreState::new( + conductor, + ssh_proxy_port, + ssh_proxy_display_port, + static_dir, + ) + .await; let app = router::build_router(state.clone(), additional_router).await; let certs = state.certs.clone(); diff --git a/lapdev-api/src/state.rs b/lapdev-api/src/state.rs index e4175fa..2ab3dd2 100644 --- a/lapdev-api/src/state.rs +++ b/lapdev-api/src/state.rs @@ -73,11 +73,20 @@ pub struct CoreState { pub auth: Arc, pub auth_token_key: Arc>, pub certs: CertStore, + // actuall ssh proxy port pub ssh_proxy_port: u16, + // ssh proxy port to display in front end + pub ssh_proxy_display_port: u16, + pub static_dir: Arc>>, } impl CoreState { - pub async fn new(conductor: Conductor, ssh_proxy_port: u16) -> Self { + pub async fn new( + conductor: Conductor, + ssh_proxy_port: u16, + ssh_proxy_display_port: u16, + static_dir: Option>, + ) -> Self { let github_client = GithubClient::new(); let key = conductor.db.load_api_auth_token_key().await; let auth = Auth::new(&conductor.db).await; @@ -95,7 +104,9 @@ impl CoreState { auth_token_key: Arc::new(key), certs: Arc::new(std::sync::RwLock::new(Arc::new(certs))), ssh_proxy_port, + ssh_proxy_display_port, hyper_client: Arc::new(hyper_client), + static_dir: Arc::new(static_dir), }; { diff --git a/lapdev-common/src/lib.rs b/lapdev-common/src/lib.rs index 82c320e..d0d8d10 100644 --- a/lapdev-common/src/lib.rs +++ b/lapdev-common/src/lib.rs @@ -475,6 +475,8 @@ pub struct NewContainerHostConfig { pub security_opt: Option>, #[serde(rename = "StorageOpt")] pub storage_opt: Option>, + #[serde(rename = "Privileged")] + pub privileged: bool, } #[derive(Serialize, Deserialize, Debug)] diff --git a/lapdev-conductor/src/server.rs b/lapdev-conductor/src/server.rs index bfe065d..4f40754 100644 --- a/lapdev-conductor/src/server.rs +++ b/lapdev-conductor/src/server.rs @@ -350,7 +350,17 @@ impl Conductor { } } - let _ = self.connect_workspace_host_once(id, &host, port).await; + if let Err(e) = self.connect_workspace_host_once(id, &host, port).await { + tracing::error!("connect workspace host {host}:{port} failed: {e:?}"); + } + + let _ = entities::workspace_host::ActiveModel { + id: ActiveValue::Set(id), + status: ActiveValue::Set(WorkspaceHostStatus::Inactive.to_string()), + ..Default::default() + } + .update(&self.db.conn) + .await; { self.rpcs.lock().await.remove(&id); @@ -471,14 +481,6 @@ impl Conductor { let _ = rpc_future.await; tracing::debug!("workspace host connection ended"); - - entities::workspace_host::ActiveModel { - id: ActiveValue::Set(id), - status: ActiveValue::Set(WorkspaceHostStatus::Inactive.to_string()), - ..Default::default() - } - .update(&self.db.conn) - .await?; } Ok(()) diff --git a/lapdev-dashboard/src/project.rs b/lapdev-dashboard/src/project.rs index 763a2ba..b5baa9c 100644 --- a/lapdev-dashboard/src/project.rs +++ b/lapdev-dashboard/src/project.rs @@ -883,7 +883,7 @@ fn ProjectInfoView( .into_iter() .find(|m| m.id == info.machine_type) ) - .map(|machine_type| format!("{} - {} {}cores, {}GB memory, {}GB disk",machine_type.name, machine_type.cpu, if machine_type.shared { "shared "} else {""}, machine_type.memory, machine_type.disk)) + .map(|machine_type| format!("{} - {} vCPUs, {}GB memory, {}GB disk",machine_type.name, machine_type.cpu, machine_type.memory, machine_type.disk)) } @@ -1247,7 +1247,7 @@ pub fn MachineTypeView( data-uuid=move || machine_type.id.to_string() selected=move || Some(machine_type.id) == preferred_machine_type.get() > - {format!("{} - {} {}cores, {}GB memory, {}GB disk",machine_type.name, machine_type.cpu, if machine_type.shared { "shared "} else {""}, machine_type.memory, machine_type.disk)} + {format!("{} - {} vCPUs, {}GB memory, {}GB disk",machine_type.name, machine_type.cpu, machine_type.memory, machine_type.disk)} } } diff --git a/lapdev-dashboard/src/workspace.rs b/lapdev-dashboard/src/workspace.rs index d210a5b..983391d 100644 --- a/lapdev-dashboard/src/workspace.rs +++ b/lapdev-dashboard/src/workspace.rs @@ -220,7 +220,18 @@ fn OpenWorkspaceView( let port = cluster_info .with_untracked(|i| i.as_ref().map(|i| i.ssh_proxy_port).unwrap_or(2222)); let _ = window() - .open_with_url_and_target(&format!("vscode://vscode-remote/ssh-remote+{workspace_name}@{}:{port}/workspaces/{workspace_folder}", workspace_hostname.split(':').next().unwrap_or("")), "_blank"); + .open_with_url_and_target( + &format!( + "vscode://vscode-remote/ssh-remote+{workspace_name}@{}{}/workspaces/{workspace_folder}", + workspace_hostname.split(':').next().unwrap_or(""), + if port == 22 { + "".to_string() + } else { + format!(":{port}") + } + ), + "_blank" + ); } }; @@ -1076,7 +1087,17 @@ pub fn WorkspaceDetails() -> impl IntoView { { let workspace_name = workspace_name.clone(); let workspace_hostname = workspace_hostname.clone(); - move || format!("ssh {workspace_name}@{} -p {}", workspace_hostname.split(':').next().unwrap_or(""), cluster_info.with(|i| i.as_ref().map(|i| i.ssh_proxy_port).unwrap_or(2222))) + move || { + let ssh_port = cluster_info.with(|i| i.as_ref().map(|i| i.ssh_proxy_port).unwrap_or(2222)); + format!("ssh {workspace_name}@{}{}", + workspace_hostname.split(':').next().unwrap_or(""), + if ssh_port == 22 { + "".to_string() + } else { + format!(" -p {ssh_port}") + } + ) + } } @@ -1092,7 +1113,7 @@ pub fn WorkspaceDetails() -> impl IntoView { m.into_iter() .find(|m| m.id == info.machine_type) ) - .map(|machine_type| format!("{} - {} {}cores, {}GB memory, {}GB disk",machine_type.name, machine_type.cpu, if machine_type.shared { "shared "} else {""}, machine_type.memory, machine_type.disk)) + .map(|machine_type| format!("{} - {} vCPUs, {}GB memory, {}GB disk",machine_type.name, machine_type.cpu, machine_type.memory, machine_type.disk)) } @@ -1141,7 +1162,18 @@ pub fn WorkspaceDetails() -> impl IntoView { { let ws_service_name = ws_service.name.clone(); let workspace_hostname = workspace_hostname.clone(); - move || format!("ssh {ws_service_name}@{} -p {}", workspace_hostname.split(':').next().unwrap_or(""), cluster_info.with(|i| i.as_ref().map(|i| i.ssh_proxy_port).unwrap_or(2222))) + move || { + let ssh_proxy_port = cluster_info.with(|i| i.as_ref().map(|i| i.ssh_proxy_port).unwrap_or(2222)); + format!( + "ssh {ws_service_name}@{}{}", + workspace_hostname.split(':').next().unwrap_or(""), + if ssh_proxy_port == 22 { + "".to_string() + } else { + format!(" -p {ssh_proxy_port}") + }, + ) + } } diff --git a/lapdev-db/src/migration/m20231130_151650_create_organization_table.rs b/lapdev-db/src/migration/m20231130_151650_create_organization_table.rs index 6590bb4..6a0d8ac 100644 --- a/lapdev-db/src/migration/m20231130_151650_create_organization_table.rs +++ b/lapdev-db/src/migration/m20231130_151650_create_organization_table.rs @@ -37,6 +37,11 @@ impl MigrationTrait for Migration { .big_integer() .not_null(), ) + .col( + ColumnDef::new(Organization::RunningWorkspaceLimit) + .integer() + .not_null(), + ) .to_owned(), ) .await?; @@ -68,4 +73,5 @@ enum Organization { AllowWorkspaceChangeAutoStop, LastAutoStopCheck, UsageLimit, + RunningWorkspaceLimit, } diff --git a/lapdev-guest-agent/src/lib.rs b/lapdev-guest-agent/src/lib.rs index 9bcfbf0..858c82e 100644 --- a/lapdev-guest-agent/src/lib.rs +++ b/lapdev-guest-agent/src/lib.rs @@ -55,6 +55,8 @@ fn run_sshd() -> Result<(), LapdevGuestAgentError> { fs::create_dir_all("/root/.ssh/")?; fs::write("/root/.ssh/authorized_keys", public_key)?; Command::new("/usr/sbin/sshd") + .arg("-p") + .arg("22") .arg("-D") .arg("-o") .arg("AcceptEnv=*") diff --git a/lapdev-proxy-ssh/src/server.rs b/lapdev-proxy-ssh/src/server.rs index ca8e767..734d080 100644 --- a/lapdev-proxy-ssh/src/server.rs +++ b/lapdev-proxy-ssh/src/server.rs @@ -13,6 +13,7 @@ pub async fn run(conductor: Conductor, bind: &str, port: u16) -> Result<()> { let config = russh::server::Config { inactivity_timeout: Some(std::time::Duration::from_secs(3600)), + keepalive_interval: Some(std::time::Duration::from_secs(10)), auth_rejection_time: std::time::Duration::from_secs(3), auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)), methods: MethodSet::NONE | MethodSet::PUBLICKEY, diff --git a/lapdev-ws/src/server.rs b/lapdev-ws/src/server.rs index c0e571d..720516a 100644 --- a/lapdev-ws/src/server.rs +++ b/lapdev-ws/src/server.rs @@ -309,10 +309,10 @@ impl WorkspaceServer { .arg(osuser) .arg("-c") .arg("podman system service --time=0") - .status() + .output() .await; - println!("podman system service finished"); }); + tokio::time::sleep(Duration::from_secs(1)).await; } } @@ -379,12 +379,43 @@ impl WorkspaceServer { .wait() .await?; + let containers_config_folder = format!("/home/{username}/.config/containers"); + Command::new("su") + .arg("-") + .arg(username) + .arg("-c") + .arg(format!("mkdir -p {containers_config_folder}")) + .spawn()? + .wait() + .await?; + tokio::fs::write( + format!("{containers_config_folder}/registries.conf"), + r#" +unqualified-search-registries = ["docker.io"] +"#, + ) + .await?; + tokio::fs::write( + format!("{containers_config_folder}/storage.conf"), + r#" +[storage] +driver = "overlay" +"#, + ) + .await?; + Command::new("chown") + .arg("-R") + .arg(format!("{username}:{username}")) + .arg(&containers_config_folder) + .output() + .await?; + let uid = self._os_user_uid(username).await?; Command::new("loginctl") .arg("enable-linger") .arg(&uid) - .status() + .output() .await?; Ok(uid) diff --git a/lapdev-ws/src/service.rs b/lapdev-ws/src/service.rs index b0be7eb..3db6113 100644 --- a/lapdev-ws/src/service.rs +++ b/lapdev-ws/src/service.rs @@ -149,6 +149,9 @@ impl WorkspaceService for WorkspaceRpcService { "none", "--bind-addr", "0.0.0.0:30000", + "--app-name", + "Lapdev", + "--disable-workspace-trust", &format!("/workspaces/{}", ws_req.repo_name), ])?; @@ -194,6 +197,7 @@ impl WorkspaceService for WorkspaceRpcService { cap_add: vec!["NET_RAW".to_string()], security_opt: Some(vec!["label=disable".to_string()]), storage_opt: None, + privileged: true, }, networking_config: NewContainerNetworkingConfig { endpoints_config: if let Some(service) = ws_req.service.as_ref() { diff --git a/pkg/image/go/Dockerfile b/pkg/image/go/Dockerfile new file mode 100644 index 0000000..8624ec4 --- /dev/null +++ b/pkg/image/go/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/go:latest + +USER root +COPY ../../../target/release/lapdev-guest-agent /lapdev-guest-agent +RUN chmod +x /lapdev-guest-agent +COPY ../../../lapdev-ws/scripts/install_guest_agent.sh /install_guest_agent.sh +RUN sh /install_guest_agent.sh +RUN rm /install_guest_agent.sh \ No newline at end of file diff --git a/pkg/image/javascript/Dockerfile b/pkg/image/javascript/Dockerfile new file mode 100644 index 0000000..ed0b5eb --- /dev/null +++ b/pkg/image/javascript/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/javascript-node:latest + +USER root +COPY ../../../target/release/lapdev-guest-agent /lapdev-guest-agent +RUN chmod +x /lapdev-guest-agent +COPY ../../../lapdev-ws/scripts/install_guest_agent.sh /install_guest_agent.sh +RUN sh /install_guest_agent.sh +RUN rm /install_guest_agent.sh \ No newline at end of file diff --git a/pkg/image/python/Dockerfile b/pkg/image/python/Dockerfile new file mode 100644 index 0000000..2c86dc6 --- /dev/null +++ b/pkg/image/python/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/python:latest + +USER root +COPY ../../../target/release/lapdev-guest-agent /lapdev-guest-agent +RUN chmod +x /lapdev-guest-agent +COPY ../../../lapdev-ws/scripts/install_guest_agent.sh /install_guest_agent.sh +RUN sh /install_guest_agent.sh +RUN rm /install_guest_agent.sh \ No newline at end of file diff --git a/pkg/image/rust/Dockerfile b/pkg/image/rust/Dockerfile new file mode 100644 index 0000000..2c86dc6 --- /dev/null +++ b/pkg/image/rust/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/python:latest + +USER root +COPY ../../../target/release/lapdev-guest-agent /lapdev-guest-agent +RUN chmod +x /lapdev-guest-agent +COPY ../../../lapdev-ws/scripts/install_guest_agent.sh /install_guest_agent.sh +RUN sh /install_guest_agent.sh +RUN rm /install_guest_agent.sh \ No newline at end of file diff --git a/pkg/image/typescript/Dockerfile b/pkg/image/typescript/Dockerfile new file mode 100644 index 0000000..5d1b0a8 --- /dev/null +++ b/pkg/image/typescript/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/typescript-node:latest + +USER root +COPY ../../../target/release/lapdev-guest-agent /lapdev-guest-agent +RUN chmod +x /lapdev-guest-agent +COPY ../../../lapdev-ws/scripts/install_guest_agent.sh /install_guest_agent.sh +RUN sh /install_guest_agent.sh +RUN rm /install_guest_agent.sh \ No newline at end of file diff --git a/pkg/image/universal/Dockerfile b/pkg/image/universal/Dockerfile new file mode 100644 index 0000000..1db4930 --- /dev/null +++ b/pkg/image/universal/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/devcontainers/universal:latest + +USER root +COPY ../../../target/release/lapdev-guest-agent /lapdev-guest-agent +RUN chmod +x /lapdev-guest-agent +COPY ../../../lapdev-ws/scripts/install_guest_agent.sh /install_guest_agent.sh +RUN sh /install_guest_agent.sh +RUN rm /install_guest_agent.sh \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b714ef0..9e21c3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ #[tokio::main] pub async fn main() { - lapdev_api::server::start(None).await; + lapdev_api::server::start(None, None).await; }