cli: add service integration for systemd (#166328)

systemd, like most 'modern' linux components, has a nice dbus API. This uses
that API to register the tunnel as a service of the calling user.

The dbus dependency is temporarily duplicated, until secret-service 3 is
released, where they update to the latest version (should be a week or two).

For https://github.com/microsoft/vscode-cli/issues/367. Next up, macOS,
then it's done :)
This commit is contained in:
Connor Peet 2022-11-14 21:39:05 -08:00 committed by GitHub
parent eab3273162
commit c47751948c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 507 additions and 65 deletions

View file

@ -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',

View file

@ -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',

248
cli/Cargo.lock generated
View file

@ -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",
]

View file

@ -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" }

View file

@ -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<i32, AnyError> {
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?;
}
}

View file

@ -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<HashMap<Quality, Vec<String>>> =
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<HashMap<Quality, String>> =
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");
}

View file

@ -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;

View file

@ -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");
}
}

View file

@ -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<Connection, AnyError> {
let connection = Connection::session()
.await
.map_err(|e| wrap(e, "error creating dbus session"))?;
Ok(connection)
}
async fn proxy(connection: &Connection) -> Result<SystemdManagerDbusProxy<'_>, 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::<ShutdownSignal>(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<String>,
runtime: bool,
force: bool,
) -> zbus::Result<(bool, Vec<(String, String, String)>)>;
fn link_unit_files(
&self,
files: Vec<String>,
runtime: bool,
force: bool,
) -> zbus::Result<Vec<(String, String, String)>>;
fn disable_unit_files(
&self,
files: Vec<String>,
runtime: bool,
) -> zbus::Result<Vec<(String, String, String)>>;
#[dbus_proxy(name = "StartUnit")]
fn start_unit(&self, name: String, mode: String) -> zbus::Result<zvariant::OwnedObjectPath>;
#[dbus_proxy(name = "StopUnit")]
fn stop_unit(&self, name: String, mode: String) -> zbus::Result<zvariant::OwnedObjectPath>;
}

View file

@ -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<OsString>) -> 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,