Skip to main content

tuwunel_admin/server/
commands.rs

1use std::{fmt::Write, path::PathBuf, sync::Arc};
2
3use futures::TryStreamExt;
4use tuwunel_core::{
5	Err, Result, info,
6	utils::{stream::IterStream, time},
7	warn,
8};
9
10use crate::admin_command;
11
12#[admin_command]
13pub(super) async fn uptime(&self) -> Result {
14	let elapsed = self
15		.services
16		.server
17		.started
18		.elapsed()
19		.expect("standard duration");
20
21	let result = time::pretty(elapsed);
22	self.write_str(&format!("{result}.")).await
23}
24
25#[admin_command]
26pub(super) async fn show_config(&self) -> Result {
27	self.write_str(&format!("{}", *self.services.server.config))
28		.await
29}
30
31#[admin_command]
32pub(super) async fn reload_config(&self, path: Option<PathBuf>) -> Result {
33	let path = path.as_deref().into_iter();
34	self.services.config.reload(path)?;
35
36	self.write_str("Successfully reconfigured.").await
37}
38
39#[admin_command]
40pub(super) async fn list_features(&self, available: bool, enabled: bool, comma: bool) -> Result {
41	let delim = if comma { "," } else { " " };
42	if enabled && !available {
43		let features = info::rustc::features().join(delim);
44		let out = format!("`\n{features}\n`");
45		return self.write_str(&out).await;
46	}
47
48	if available && !enabled {
49		let features = info::cargo::features().join(delim);
50		let out = format!("`\n{features}\n`");
51		return self.write_str(&out).await;
52	}
53
54	let mut features = String::new();
55	let enabled = info::rustc::features();
56	let available = info::cargo::features();
57	for feature in available {
58		let active = enabled.contains(&feature.as_str());
59		let emoji = if active { "✅" } else { "❌" };
60		let remark = if active { "[enabled]" } else { "" };
61		writeln!(features, "{emoji} {feature} {remark}")?;
62	}
63
64	self.write_str(&features).await
65}
66
67#[admin_command]
68pub(super) async fn memory_usage(&self) -> Result {
69	let services_usage = self.services.memory_usage().await?;
70	let database_usage = self.services.db.engine.memory_usage()?;
71	let allocator_usage = tuwunel_core::alloc::memory_usage()
72		.map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
73
74	self.write_str(&format!(
75		"Services:\n{services_usage}\nDatabase:\n{database_usage}{allocator_usage}",
76	))
77	.await
78}
79
80#[admin_command]
81pub(super) async fn clear_caches(&self) -> Result {
82	self.services.clear_cache().await;
83
84	self.write_str("Done.").await
85}
86
87#[admin_command]
88pub(super) async fn list_backups(&self) -> Result {
89	self.services
90		.db
91		.engine
92		.backup_list()?
93		.try_stream()
94		.try_for_each(|result| write!(self, "{result}"))
95		.await
96}
97
98#[admin_command]
99pub(super) async fn backup_database(&self) -> Result {
100	let db = Arc::clone(&self.services.db);
101	let result = self
102		.services
103		.server
104		.runtime()
105		.spawn_blocking(move || match db.engine.backup() {
106			| Ok(()) => "Done".to_owned(),
107			| Err(e) => format!("Failed: {e}"),
108		})
109		.await?;
110
111	let count = self.services.db.engine.backup_count()?;
112	self.write_str(&format!("{result}. Currently have {count} backups."))
113		.await
114}
115
116#[admin_command]
117pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result {
118	let message = message.join(" ");
119	self.services.admin.send_text(&message).await;
120
121	self.write_str("Notice was sent to #admins").await
122}
123
124#[admin_command]
125pub(super) async fn reload_mods(&self) -> Result {
126	self.services.server.reload()?;
127
128	self.write_str("Reloading server...").await
129}
130
131#[admin_command]
132#[cfg(unix)]
133pub(super) async fn restart(&self, force: bool) -> Result {
134	use tuwunel_core::utils::sys::current_exe_deleted;
135
136	if !force && current_exe_deleted() {
137		return Err!(
138			"The server cannot be restarted because the executable changed. If this is expected \
139			 use --force to override."
140		);
141	}
142
143	self.services.server.restart()?;
144
145	self.write_str("Restarting server...").await
146}
147
148#[admin_command]
149pub(super) async fn shutdown(&self) -> Result {
150	warn!("shutdown command");
151	self.services.server.shutdown()?;
152
153	self.write_str("Shutting down server...").await
154}