diff --git a/build/azure-pipelines/cli/prepare.js b/build/azure-pipelines/cli/prepare.js index b0cb2e6152a..e46cae42e3d 100644 --- a/build/azure-pipelines/cli/prepare.js +++ b/build/azure-pipelines/cli/prepare.js @@ -42,6 +42,9 @@ const setLauncherEnvironmentVars = () => { ['VSCODE_CLI_VERSION', packageJson.version], ['VSCODE_CLI_UPDATE_ENDPOINT', product.updateUrl], ['VSCODE_CLI_QUALITY', product.quality], + ['VSCODE_CLI_NAME_SHORT', product.nameShort], + ['VSCODE_CLI_NAME_LONG', product.nameLong], + ['VSCODE_CLI_APPLICATION_NAME', product.applicationName], ['VSCODE_CLI_COMMIT', commit], [ 'VSCODE_CLI_WIN32_APP_IDS', diff --git a/build/azure-pipelines/cli/prepare.ts b/build/azure-pipelines/cli/prepare.ts index 53f33d08568..a9c172fa017 100644 --- a/build/azure-pipelines/cli/prepare.ts +++ b/build/azure-pipelines/cli/prepare.ts @@ -46,6 +46,9 @@ const setLauncherEnvironmentVars = () => { ['VSCODE_CLI_VERSION', packageJson.version], ['VSCODE_CLI_UPDATE_ENDPOINT', product.updateUrl], ['VSCODE_CLI_QUALITY', product.quality], + ['VSCODE_CLI_NAME_SHORT', product.nameShort], + ['VSCODE_CLI_NAME_LONG', product.nameLong], + ['VSCODE_CLI_APPLICATION_NAME', product.applicationName], ['VSCODE_CLI_COMMIT', commit], [ 'VSCODE_CLI_WIN32_APP_IDS', diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 4ee1f5aae98..57fba7fbe05 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -26,6 +26,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-broadcast" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + [[package]] name = "async-io" version = "1.9.0" @@ -47,10 +58,21 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.57" +name = "async-recursion" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -241,6 +263,7 @@ dependencies = [ "uuid", "windows-service", "winreg", + "zbus 3.4.0", "zip", ] @@ -503,7 +526,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" dependencies = [ - "enumflags2_derive", + "enumflags2_derive 0.6.4", + "serde", +] + +[[package]] +name = "enumflags2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive 0.7.4", "serde", ] @@ -518,6 +551,17 @@ dependencies = [ "syn", ] +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "err-derive" version = "0.3.1" @@ -532,6 +576,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fastrand" version = "1.8.0" @@ -610,9 +660,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -620,9 +670,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" @@ -637,9 +687,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" @@ -658,9 +708,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -669,21 +719,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -779,6 +829,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-literal" version = "0.3.4" @@ -1102,6 +1158,20 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "ntapi" version = "0.3.7" @@ -1362,6 +1432,16 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "ordered-stream" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034ce384018b245e8d8424bbe90577fbd91a533be74107e465e3474eb2285eef" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_str_bytes" version = "6.3.0" @@ -1830,10 +1910,10 @@ dependencies = [ "openssl", "rand 0.8.5", "serde", - "zbus", - "zbus_macros", - "zvariant", - "zvariant_derive", + "zbus 1.9.3", + "zbus_macros 1.9.3", + "zvariant 2.10.0", + "zvariant_derive 2.10.0", ] [[package]] @@ -2009,9 +2089,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -2245,9 +2325,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.30" @@ -2312,6 +2404,16 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -2632,18 +2734,54 @@ dependencies = [ "async-io", "byteorder", "derivative", - "enumflags2", + "enumflags2 0.6.4", "fastrand", "futures", "nb-connect", - "nix", + "nix 0.22.3", "once_cell", "polling", "scoped-tls", "serde", "serde_repr", - "zbus_macros", - "zvariant", + "zbus_macros 1.9.3", + "zvariant 2.10.0", +] + +[[package]] +name = "zbus" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a0b85c5608c27d2306d67e955b9c6e23a42d824205c85038a7afbe19c0ae22" +dependencies = [ + "async-broadcast", + "async-recursion", + "async-trait", + "byteorder", + "derivative", + "dirs 4.0.0", + "enumflags2 0.7.5", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "lazy_static", + "nix 0.25.0", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "zbus_macros 3.4.0", + "zbus_names", + "zvariant 3.7.1", ] [[package]] @@ -2658,6 +2796,30 @@ dependencies = [ "syn", ] +[[package]] +name = "zbus_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18018648e7e10ed856809befe7309002b87b2b12d5b282cb5040d7974b58677" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a408fd8a352695690f53906dc7fd036be924ec51ea5e05666ff42685ed0af5" +dependencies = [ + "serde", + "static_assertions", + "zvariant 3.7.1", +] + [[package]] name = "zeroize" version = "1.3.0" @@ -2684,11 +2846,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68c7b55f2074489b7e8e07d2d0a6ee6b4f233867a653c664d8020ba53692525" dependencies = [ "byteorder", - "enumflags2", + "enumflags2 0.6.4", "libc", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 2.10.0", +] + +[[package]] +name = "zvariant" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794fb7f59af4105697b0449ba31731ee5dbb3e773a17dbdf3d36206ea1b1644" +dependencies = [ + "byteorder", + "enumflags2 0.7.5", + "libc", + "serde", + "static_assertions", + "zvariant_derive 3.7.1", ] [[package]] @@ -2702,3 +2878,15 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zvariant_derive" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd58d4b6c8e26d3dd2149c8c40c6613ef6451b9885ff1296d1ac86c388351a54" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dccd4a32c42..ff7e5c071b6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -53,6 +53,7 @@ winreg = "0.10" [target.'cfg(target_os = "linux")'.dependencies] tar = { version = "0.4" } +zbus = { version = "3.4", default-features = false, features = ["tokio"] } [patch.crates-io] russh = { git = "https://github.com/microsoft/vscode-russh", branch = "main" } diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 0dea7761d93..25a4601b52a 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ use async_trait::async_trait; -use std::str::FromStr; use std::fmt; +use std::str::FromStr; use sysinfo::{Pid, SystemExt}; use tokio::sync::mpsc; use tokio::time::{sleep, Duration}; @@ -112,37 +112,45 @@ pub async fn service( ctx: CommandContext, service_args: TunnelServiceSubCommands, ) -> Result { - let manager = create_service_manager(ctx.log.clone()); + let manager = create_service_manager(ctx.log.clone(), &ctx.paths); match service_args { TunnelServiceSubCommands::Install => { // ensure logged in, otherwise subsequent serving will fail + println!("authing"); Auth::new(&ctx.paths, ctx.log.clone()) .get_credential() .await?; // likewise for license consent + println!("consent"); legal::require_consent(&ctx.paths, false)?; let current_exe = std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?; - manager.register( - current_exe, - &[ - "--cli-data-dir", - ctx.paths.root().as_os_str().to_string_lossy().as_ref(), - "tunnel", - "service", - "internal-run", - ], - )?; + println!("calling register"); + manager + .register( + current_exe, + &[ + "--verbose", + "--cli-data-dir", + ctx.paths.root().as_os_str().to_string_lossy().as_ref(), + "tunnel", + "service", + "internal-run", + ], + ) + .await?; ctx.log.result("Service successfully installed! You can use `code tunnel service log` to monitor it, and `code tunnel service uninstall` to remove it."); } TunnelServiceSubCommands::Uninstall => { - manager.unregister()?; + manager.unregister().await?; } TunnelServiceSubCommands::InternalRun => { - manager.run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args))?; + manager + .run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args)) + .await?; } } diff --git a/cli/src/constants.rs b/cli/src/constants.rs index ab412fb860f..69f4fcc5d8c 100644 --- a/cli/src/constants.rs +++ b/cli/src/constants.rs @@ -28,10 +28,12 @@ pub const VSCODE_CLI_UPDATE_ENDPOINT: Option<&'static str> = pub const TUNNEL_SERVICE_USER_AGENT_ENV_VAR: &str = "TUNNEL_SERVICE_USER_AGENT"; +const MAYBE_APPLICATION_NAME: Option<&'static str> = option_env!("VSCODE_CLI_APPLICATION_NAME"); +const MAYBE_PRODUCT_NAME_LONG: Option<&'static str> = option_env!("VSCODE_CLI_NAME_LONG"); // JSON map of quality names to arrays of app IDs used for them, for example, `{"stable":["ABC123"]}` -const VSCODE_CLI_WIN32_APP_IDS: Option<&'static str> = option_env!("VSCODE_CLI_WIN32_APP_IDS"); +const MAYBE_CLI_WIN32_APP_IDS: Option<&'static str> = option_env!("VSCODE_CLI_WIN32_APP_IDS"); // JSON map of quality names to download URIs -const VSCODE_CLI_QUALITY_DOWNLOAD_URIS: Option<&'static str> = +const MAYBE_CLI_QUALITY_DOWNLOAD_URIS: Option<&'static str> = option_env!("VSCODE_CLI_QUALITY_DOWNLOAD_URIS"); pub fn get_default_user_agent() -> String { @@ -48,7 +50,10 @@ lazy_static! { _ => get_default_user_agent(), }; pub static ref WIN32_APP_IDS: Option>> = - VSCODE_CLI_WIN32_APP_IDS.and_then(|s| serde_json::from_str(s).unwrap()); + MAYBE_CLI_WIN32_APP_IDS.and_then(|s| serde_json::from_str(s).unwrap()); pub static ref QUALITY_DOWNLOAD_URIS: Option> = - VSCODE_CLI_QUALITY_DOWNLOAD_URIS.and_then(|s| serde_json::from_str(s).unwrap()); + MAYBE_CLI_QUALITY_DOWNLOAD_URIS.and_then(|s| serde_json::from_str(s).unwrap()); + pub static ref PRODUCT_NAME_LONG: &'static str = + MAYBE_PRODUCT_NAME_LONG.unwrap_or("Code - OSS"); + pub static ref APPLICATION_NAME: &'static str = MAYBE_APPLICATION_NAME.unwrap_or("code"); } diff --git a/cli/src/tunnels.rs b/cli/src/tunnels.rs index 638432b040d..e451dfb6621 100644 --- a/cli/src/tunnels.rs +++ b/cli/src/tunnels.rs @@ -17,6 +17,8 @@ mod protocol; #[cfg_attr(windows, path = "tunnels/server_bridge_windows.rs")] mod server_bridge; mod service; +#[cfg(target_os = "linux")] +mod service_linux; #[cfg(target_os = "windows")] mod service_windows; diff --git a/cli/src/tunnels/service.rs b/cli/src/tunnels/service.rs index 8085f3b069a..f66fd5d6b9f 100644 --- a/cli/src/tunnels/service.rs +++ b/cli/src/tunnels/service.rs @@ -25,58 +25,75 @@ pub trait ServiceContainer: Send { ) -> Result<(), AnyError>; } +#[async_trait] pub trait ServiceManager { /// Registers the current executable as a service to run with the given set /// of arguments. - fn register(&self, exe: PathBuf, args: &[&str]) -> Result<(), AnyError>; + async fn register(&self, exe: PathBuf, args: &[&str]) -> Result<(), AnyError>; /// Runs the service using the given handle. The executable *must not* take /// any action which may fail prior to calling this to ensure service /// states may update. - fn run( - &self, + async fn run( + self, launcher_paths: LauncherPaths, handle: impl 'static + ServiceContainer, ) -> Result<(), AnyError>; /// Unregisters the current executable as a service. - fn unregister(&self) -> Result<(), AnyError>; + async fn unregister(&self) -> Result<(), AnyError>; } #[cfg(target_os = "windows")] pub type ServiceManagerImpl = super::service_windows::WindowsService; -#[cfg(not(target_os = "windows"))] +#[cfg(target_os = "linux")] +pub type ServiceManagerImpl = super::service_linux::SystemdService; + +#[cfg(not(any(target_os = "windows", target_os = "linux")))] pub type ServiceManagerImpl = UnimplementedServiceManager; #[allow(unreachable_code)] -pub fn create_service_manager(log: log::Logger) -> ServiceManagerImpl { - ServiceManagerImpl::new(log) +#[allow(unused_variables)] +pub fn create_service_manager(log: log::Logger, paths: &LauncherPaths) -> ServiceManagerImpl { + #[cfg(target_os = "windows")] + { + super::service_windows::WindowsService::new(log) + } + #[cfg(target_os = "linux")] + { + super::service_linux::SystemdService::new(log, paths.clone()) + } + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + { + UnimplementedServiceManager::new() + } } pub struct UnimplementedServiceManager(); #[allow(dead_code)] impl UnimplementedServiceManager { - fn new(_log: log::Logger) -> Self { + fn new() -> Self { Self() } } +#[async_trait] impl ServiceManager for UnimplementedServiceManager { - fn register(&self, _exe: PathBuf, _args: &[&str]) -> Result<(), AnyError> { + async fn register(&self, _exe: PathBuf, _args: &[&str]) -> Result<(), AnyError> { unimplemented!("Service management is not supported on this platform"); } - fn run( - &self, + async fn run( + self, _launcher_paths: LauncherPaths, _handle: impl 'static + ServiceContainer, ) -> Result<(), AnyError> { unimplemented!("Service management is not supported on this platform"); } - fn unregister(&self) -> Result<(), AnyError> { + async fn unregister(&self) -> Result<(), AnyError> { unimplemented!("Service management is not supported on this platform"); } } diff --git a/cli/src/tunnels/service_linux.rs b/cli/src/tunnels/service_linux.rs new file mode 100644 index 00000000000..e4f131d6e37 --- /dev/null +++ b/cli/src/tunnels/service_linux.rs @@ -0,0 +1,211 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + fs::File, + io::{self, Write}, + path::PathBuf, +}; + +use async_trait::async_trait; +use tokio::sync::mpsc; +use zbus::{dbus_proxy, zvariant, Connection}; + +use crate::{ + commands::tunnels::ShutdownSignal, + constants::{APPLICATION_NAME, PRODUCT_NAME_LONG}, + log, + state::LauncherPaths, + util::errors::{wrap, AnyError}, +}; + +use super::ServiceManager; + +pub struct SystemdService { + log: log::Logger, + service_file: PathBuf, +} + +impl SystemdService { + pub fn new(log: log::Logger, paths: LauncherPaths) -> Self { + Self { + log, + service_file: paths.root().join(SystemdService::service_name_string()), + } + } +} + +impl SystemdService { + async fn connect() -> Result { + let connection = Connection::session() + .await + .map_err(|e| wrap(e, "error creating dbus session"))?; + Ok(connection) + } + + async fn proxy(connection: &Connection) -> Result, AnyError> { + let proxy = SystemdManagerDbusProxy::new(connection) + .await + .map_err(|e| { + wrap( + e, + "error connecting to systemd, you may need to re-run with sudo:", + ) + })?; + + Ok(proxy) + } + + fn service_path_string(&self) -> String { + self.service_file.as_os_str().to_string_lossy().to_string() + } + + fn service_name_string() -> String { + format!("{}-tunnel.service", &*APPLICATION_NAME) + } +} + +#[async_trait] +impl ServiceManager for SystemdService { + async fn register( + &self, + exe: std::path::PathBuf, + args: &[&str], + ) -> Result<(), crate::util::errors::AnyError> { + let connection = SystemdService::connect().await?; + let proxy = SystemdService::proxy(&connection).await?; + + write_systemd_service_file(&self.service_file, exe, args) + .map_err(|e| wrap(e, "error creating service file"))?; + + proxy + .link_unit_files( + vec![self.service_path_string()], + /* 'runtime only'= */ false, + /* replace existing = */ true, + ) + .await + .map_err(|e| wrap(e, "error registering service"))?; + + info!(self.log, "Successfully registered service..."); + + proxy + .start_unit(SystemdService::service_name_string(), "replace".to_string()) + .await + .map_err(|e| wrap(e, "error starting service"))?; + + info!(self.log, "Tunnel service successfully started"); + + Ok(()) + } + + async fn run( + self, + launcher_paths: crate::state::LauncherPaths, + mut handle: impl 'static + super::ServiceContainer, + ) -> Result<(), crate::util::errors::AnyError> { + let (tx, rx) = mpsc::channel::(1); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.ok(); + tx.send(ShutdownSignal::CtrlC).await.ok(); + }); + + handle.run_service(self.log, launcher_paths, rx).await + } + + async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> { + let connection = SystemdService::connect().await?; + let proxy = SystemdService::proxy(&connection).await?; + + proxy + .stop_unit(SystemdService::service_name_string(), "replace".to_string()) + .await + .map_err(|e| wrap(e, "error unregistering service"))?; + + info!(self.log, "Successfully stopped service..."); + + proxy + .disable_unit_files( + vec![SystemdService::service_name_string()], + /* 'runtime only'= */ false, + ) + .await + .map_err(|e| wrap(e, "error unregistering service"))?; + + info!(self.log, "Tunnel service uninstalled"); + + Ok(()) + } +} + +fn write_systemd_service_file( + path: &PathBuf, + exe: std::path::PathBuf, + args: &[&str], +) -> io::Result<()> { + let mut f = File::create(path)?; + write!( + &mut f, + "[Unit]\n\ + Description={} Tunnel\n\ + After=network.target\n\ + StartLimitIntervalSec=0\n\ + \n\ + [Service]\n\ + Type=simple\n\ + Restart=always\n\ + RestartSec=10\n\ + ExecStart={} \"{}\"\n\ + \n\ + [Install]\n\ + WantedBy=multi-user.target\n\ + ", + &*PRODUCT_NAME_LONG, + exe.into_os_string().to_string_lossy(), + args.join("\" \"") + )?; + Ok(()) +} + +/// Minimal implementation of systemd types for the services we need. The full +/// definition can be found on any systemd machine with the command: +/// +/// gdbus introspect --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1 +/// +/// See docs here: https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html +#[dbus_proxy( + interface = "org.freedesktop.systemd1.Manager", + gen_blocking = false, + default_service = "org.freedesktop.systemd1", + default_path = "/org/freedesktop/systemd1" +)] +trait SystemdManagerDbus { + #[dbus_proxy(name = "EnableUnitFiles")] + fn enable_unit_files( + &self, + files: Vec, + runtime: bool, + force: bool, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + fn link_unit_files( + &self, + files: Vec, + runtime: bool, + force: bool, + ) -> zbus::Result>; + + fn disable_unit_files( + &self, + files: Vec, + runtime: bool, + ) -> zbus::Result>; + + #[dbus_proxy(name = "StartUnit")] + fn start_unit(&self, name: String, mode: String) -> zbus::Result; + + #[dbus_proxy(name = "StopUnit")] + fn stop_unit(&self, name: String, mode: String) -> zbus::Result; +} diff --git a/cli/src/tunnels/service_windows.rs b/cli/src/tunnels/service_windows.rs index 3d011dfa22b..9e743e89141 100644 --- a/cli/src/tunnels/service_windows.rs +++ b/cli/src/tunnels/service_windows.rs @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +use async_trait::async_trait; use dialoguer::{theme::ColorfulTheme, Input, Password}; use lazy_static::lazy_static; use std::{ffi::OsString, sync::Mutex, thread, time::Duration}; @@ -44,8 +45,9 @@ impl WindowsService { } } +#[async_trait] impl CliServiceManager for WindowsService { - fn register(&self, exe: std::path::PathBuf, args: &[&str]) -> Result<(), AnyError> { + async fn register(&self, exe: std::path::PathBuf, args: &[&str]) -> Result<(), AnyError> { let service_manager = ServiceManager::local_computer( None::<&str>, ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE, @@ -118,8 +120,8 @@ impl CliServiceManager for WindowsService { } #[allow(unused_must_use)] // triggers incorrectly on `define_windows_service!` - fn run( - &self, + async fn run( + self, launcher_paths: LauncherPaths, handle: impl 'static + ServiceContainer, ) -> Result<(), AnyError> { @@ -149,7 +151,7 @@ impl CliServiceManager for WindowsService { .map_err(|e| wrap(e, "error starting service dispatcher").into()) } - fn unregister(&self) -> Result<(), AnyError> { + async fn unregister(&self) -> Result<(), AnyError> { let service_manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) .map_err(|e| wrap(e, "error getting service manager"))?; @@ -214,7 +216,9 @@ fn service_main(_arguments: Vec) -> Result<(), AnyError> { match control_event { ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, ServiceControl::Stop => { - shutdown_tx.take().and_then(|tx| tx.blocking_send(ShutdownSignal::ServiceStopped).ok()); + shutdown_tx + .take() + .and_then(|tx| tx.blocking_send(ShutdownSignal::ServiceStopped).ok()); ServiceControlHandlerResult::NoError } _ => ServiceControlHandlerResult::NotImplemented,