Skip to main content

tuwunel_service/admin/
execute.rs

1use tokio::task::yield_now;
2use tuwunel_core::{Err, Result, debug, debug_info, error, implement, info};
3#[cfg(feature = "console")]
4use tuwunel_core::{log::is_terminal_mode, warn};
5
6use super::CommandOutput;
7
8pub(super) const SIGNAL: &str = "SIGUSR2";
9
10/// Possibly spawn the terminal console at startup if configured and standard
11/// input is a terminal.
12#[implement(super::Service)]
13#[cfg_attr(not(feature = "console"), expect(clippy::unused_async))]
14pub(super) async fn console_auto_start(&self) {
15	#[cfg(feature = "console")]
16	if self
17		.services
18		.server
19		.config
20		.admin_console_automatic
21	{
22		if !is_terminal_mode() {
23			warn!("Not starting the admin console: standard input is not a terminal");
24			return;
25		}
26
27		// Allow more of the startup sequence to execute before spawning
28		yield_now().await;
29		self.console.start();
30	}
31}
32
33/// Shutdown the console when the admin worker terminates.
34#[implement(super::Service)]
35#[cfg_attr(not(feature = "console"), expect(clippy::unused_async))]
36pub(super) async fn console_auto_stop(&self) {
37	#[cfg(feature = "console")]
38	self.console.close().await;
39}
40
41/// Execute admin commands after startup
42#[implement(super::Service)]
43pub async fn startup_execute(&self) -> Result {
44	// List of commands to execute
45	let commands = &self.services.server.config.admin_execute;
46
47	// Determine if we're running in smoketest-mode which will change some behaviors
48	let smoketest = self.services.server.config.test.contains("smoke");
49
50	// When true, errors are ignored and startup continues.
51	let errors = !smoketest
52		&& self
53			.services
54			.server
55			.config
56			.admin_execute_errors_ignore;
57
58	for (i, command) in commands.iter().enumerate() {
59		if let Err(e) = self.execute_command(i, command.clone()).await
60			&& !errors
61		{
62			return Err(e);
63		}
64
65		yield_now().await;
66	}
67
68	// The smoketest functionality is placed here for now and simply initiates
69	// shutdown after all commands have executed.
70	if smoketest {
71		debug_info!("Smoketest mode. All commands complete. Shutting down now...");
72		self.services
73			.server
74			.shutdown()
75			.inspect_err(error::inspect_log)
76			.expect("Error shutting down from smoketest");
77	}
78
79	Ok(())
80}
81
82/// Execute admin commands after signal
83#[implement(super::Service)]
84pub(super) async fn signal_execute(&self) -> Result {
85	// List of commands to execute
86	let commands = self
87		.services
88		.server
89		.config
90		.admin_signal_execute
91		.clone();
92
93	// When true, errors are ignored and execution continues.
94	let ignore_errors = self
95		.services
96		.server
97		.config
98		.admin_execute_errors_ignore;
99
100	for (i, command) in commands.iter().enumerate() {
101		if let Err(e) = self.execute_command(i, command.clone()).await
102			&& !ignore_errors
103		{
104			return Err(e);
105		}
106
107		yield_now().await;
108	}
109
110	Ok(())
111}
112
113/// Execute one admin command after startup or signal
114#[implement(super::Service)]
115async fn execute_command(&self, i: usize, command: String) -> Result {
116	debug!("Execute command #{i}: executing {command:?}");
117
118	match self.command_in_place(command, None).await {
119		| Ok(Some(output)) => Self::execute_command_output(i, &output),
120		| Err(output) => Self::execute_command_error(i, &output),
121		| Ok(None) => {
122			info!("Execute command #{i} completed (no output).");
123			Ok(())
124		},
125	}
126}
127
128#[cfg(feature = "console")]
129#[implement(super::Service)]
130fn execute_command_output(i: usize, content: &CommandOutput) -> Result {
131	debug_info!("Execute command #{i} completed:");
132	super::console::print(content.as_str());
133	Ok(())
134}
135
136#[cfg(feature = "console")]
137#[implement(super::Service)]
138fn execute_command_error(i: usize, content: &CommandOutput) -> Result {
139	super::console::print_err(content.as_str());
140	Err!(debug_error!("Execute command #{i} failed."))
141}
142
143#[cfg(not(feature = "console"))]
144#[implement(super::Service)]
145fn execute_command_output(i: usize, content: &CommandOutput) -> Result {
146	info!("Execute command #{i} completed:\n{:#}", content.as_str());
147	Ok(())
148}
149
150#[cfg(not(feature = "console"))]
151#[implement(super::Service)]
152fn execute_command_error(i: usize, content: &CommandOutput) -> Result {
153	Err!(error!("Execute command #{i} failed:\n{:#}", content.as_str()))
154}