Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/tunnels/service_linux.rs
3314 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
use std::{
7
fs::File,
8
io::{self, Write},
9
path::PathBuf,
10
process::Command,
11
};
12
13
use async_trait::async_trait;
14
use zbus::{dbus_proxy, zvariant, Connection};
15
16
use crate::{
17
constants::{APPLICATION_NAME, PRODUCT_NAME_LONG},
18
log,
19
state::LauncherPaths,
20
util::errors::{wrap, AnyError, DbusConnectFailedError},
21
};
22
23
use super::ServiceManager;
24
25
pub struct SystemdService {
26
log: log::Logger,
27
service_file: PathBuf,
28
}
29
30
impl SystemdService {
31
pub fn new(log: log::Logger, paths: LauncherPaths) -> Self {
32
Self {
33
log,
34
service_file: paths.root().join(SystemdService::service_name_string()),
35
}
36
}
37
}
38
39
impl SystemdService {
40
async fn connect() -> Result<Connection, AnyError> {
41
let connection = Connection::session()
42
.await
43
.map_err(|e| DbusConnectFailedError(e.to_string()))?;
44
Ok(connection)
45
}
46
47
async fn proxy(connection: &Connection) -> Result<SystemdManagerDbusProxy<'_>, AnyError> {
48
let proxy = SystemdManagerDbusProxy::new(connection)
49
.await
50
.map_err(|e| {
51
wrap(
52
e,
53
"error connecting to systemd, you may need to re-run with sudo:",
54
)
55
})?;
56
57
Ok(proxy)
58
}
59
60
fn service_path_string(&self) -> String {
61
self.service_file.as_os_str().to_string_lossy().to_string()
62
}
63
64
fn service_name_string() -> String {
65
format!("{APPLICATION_NAME}-tunnel.service")
66
}
67
}
68
69
#[async_trait]
70
impl ServiceManager for SystemdService {
71
async fn register(
72
&self,
73
exe: std::path::PathBuf,
74
args: &[&str],
75
) -> Result<(), crate::util::errors::AnyError> {
76
let connection = SystemdService::connect().await?;
77
let proxy = SystemdService::proxy(&connection).await?;
78
79
write_systemd_service_file(&self.service_file, exe, args)
80
.map_err(|e| wrap(e, "error creating service file"))?;
81
82
proxy
83
.link_unit_files(
84
vec![self.service_path_string()],
85
/* 'runtime only'= */ false,
86
/* replace existing = */ true,
87
)
88
.await
89
.map_err(|e| wrap(e, "error registering service"))?;
90
91
info!(self.log, "Successfully registered service...");
92
93
if let Err(e) = proxy.reload().await {
94
warning!(self.log, "Error issuing reload(): {}", e);
95
}
96
97
// note: enablement is implicit in recent systemd version, but required for older systems
98
// https://github.com/microsoft/vscode/issues/167489#issuecomment-1331222826
99
proxy
100
.enable_unit_files(
101
vec![SystemdService::service_name_string()],
102
/* 'runtime only'= */ false,
103
/* replace existing = */ true,
104
)
105
.await
106
.map_err(|e| wrap(e, "error enabling unit files for service"))?;
107
108
info!(self.log, "Successfully enabled unit files...");
109
110
proxy
111
.start_unit(SystemdService::service_name_string(), "replace".to_string())
112
.await
113
.map_err(|e| wrap(e, "error starting service"))?;
114
115
info!(self.log, "Tunnel service successfully started");
116
117
if std::env::var("SSH_CLIENT").is_ok() || std::env::var("SSH_TTY").is_ok() {
118
info!(self.log, "Tip: run `sudo loginctl enable-linger $USER` to ensure the service stays running after you disconnect.");
119
}
120
121
Ok(())
122
}
123
124
async fn is_installed(&self) -> Result<bool, AnyError> {
125
let connection = SystemdService::connect().await?;
126
let proxy = SystemdService::proxy(&connection).await?;
127
let state = proxy
128
.get_unit_file_state(SystemdService::service_name_string())
129
.await;
130
131
if let Ok(s) = state {
132
Ok(s == "enabled")
133
} else {
134
Ok(false)
135
}
136
}
137
138
async fn run(
139
self,
140
launcher_paths: crate::state::LauncherPaths,
141
mut handle: impl 'static + super::ServiceContainer,
142
) -> Result<(), crate::util::errors::AnyError> {
143
handle.run_service(self.log, launcher_paths).await
144
}
145
146
async fn show_logs(&self) -> Result<(), AnyError> {
147
// show the systemctl status header...
148
Command::new("systemctl")
149
.args([
150
"--user",
151
"status",
152
"-n",
153
"0",
154
&SystemdService::service_name_string(),
155
])
156
.status()
157
.map(|s| s.code().unwrap_or(1))
158
.map_err(|e| wrap(e, "error running systemctl"))?;
159
160
// then follow log files
161
Command::new("journalctl")
162
.args(["--user", "-f", "-u", &SystemdService::service_name_string()])
163
.status()
164
.map(|s| s.code().unwrap_or(1))
165
.map_err(|e| wrap(e, "error running journalctl"))?;
166
Ok(())
167
}
168
169
async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> {
170
let connection = SystemdService::connect().await?;
171
let proxy = SystemdService::proxy(&connection).await?;
172
173
proxy
174
.stop_unit(SystemdService::service_name_string(), "replace".to_string())
175
.await
176
.map_err(|e| wrap(e, "error unregistering service"))?;
177
178
info!(self.log, "Successfully stopped service...");
179
180
proxy
181
.disable_unit_files(
182
vec![SystemdService::service_name_string()],
183
/* 'runtime only'= */ false,
184
)
185
.await
186
.map_err(|e| wrap(e, "error unregistering service"))?;
187
188
info!(self.log, "Tunnel service uninstalled");
189
190
Ok(())
191
}
192
}
193
194
fn write_systemd_service_file(
195
path: &PathBuf,
196
exe: std::path::PathBuf,
197
args: &[&str],
198
) -> io::Result<()> {
199
let mut f = File::create(path)?;
200
write!(
201
&mut f,
202
"[Unit]\n\
203
Description={} Tunnel\n\
204
After=network.target\n\
205
StartLimitIntervalSec=0\n\
206
\n\
207
[Service]\n\
208
Type=simple\n\
209
Restart=always\n\
210
RestartSec=10\n\
211
ExecStart={} \"{}\"\n\
212
\n\
213
[Install]\n\
214
WantedBy=default.target\n\
215
",
216
PRODUCT_NAME_LONG,
217
exe.into_os_string().to_string_lossy(),
218
args.join("\" \"")
219
)?;
220
Ok(())
221
}
222
223
/// Minimal implementation of systemd types for the services we need. The full
224
/// definition can be found on any systemd machine with the command:
225
///
226
/// gdbus introspect --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1
227
///
228
/// See docs here: https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html
229
#[dbus_proxy(
230
interface = "org.freedesktop.systemd1.Manager",
231
gen_blocking = false,
232
default_service = "org.freedesktop.systemd1",
233
default_path = "/org/freedesktop/systemd1"
234
)]
235
trait SystemdManagerDbus {
236
#[dbus_proxy(name = "EnableUnitFiles")]
237
fn enable_unit_files(
238
&self,
239
files: Vec<String>,
240
runtime: bool,
241
force: bool,
242
) -> zbus::Result<(bool, Vec<(String, String, String)>)>;
243
244
fn get_unit_file_state(&self, file: String) -> zbus::Result<String>;
245
246
fn link_unit_files(
247
&self,
248
files: Vec<String>,
249
runtime: bool,
250
force: bool,
251
) -> zbus::Result<Vec<(String, String, String)>>;
252
253
fn disable_unit_files(
254
&self,
255
files: Vec<String>,
256
runtime: bool,
257
) -> zbus::Result<Vec<(String, String, String)>>;
258
259
#[dbus_proxy(name = "StartUnit")]
260
fn start_unit(&self, name: String, mode: String) -> zbus::Result<zvariant::OwnedObjectPath>;
261
262
#[dbus_proxy(name = "StopUnit")]
263
fn stop_unit(&self, name: String, mode: String) -> zbus::Result<zvariant::OwnedObjectPath>;
264
265
#[dbus_proxy(name = "Reload")]
266
fn reload(&self) -> zbus::Result<()>;
267
}
268
269