1use std::collections::HashMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4use std::{fs, process};
5
6use serde::Deserialize;
7use tokio::sync::Mutex;
8
9use crate::rpc::{ClientId, RpcRequest};
10
11#[derive(Debug, Deserialize)]
12pub struct ServiceActivationFile {
13 #[serde(rename = "type")]
14 kind: String,
15
16 object_name: String,
17 service_name: String,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub enum ServiceState {
22 Stopped,
23 Starting,
24 Running(ClientId),
25}
26
27#[derive(Debug)]
28pub struct ServiceEntry {
29 pub state: ServiceState,
30 pub service_name: String,
31 pub pending_calls: Vec<RpcRequest>,
32 pub owner: Option<ClientId>,
33}
34
35pub type SharedServices = Arc<Mutex<HashMap<String, ServiceEntry>>>;
36pub const BASE_DIR: &str = "service-activation";
37
38fn home_dir() -> PathBuf {
39 dirs::home_dir().expect("Could not determine home directory")
40}
41
42pub async fn load_service_activations(services: &SharedServices) {
43 let base_dir = home_dir().join(BASE_DIR);
44 let entries = match fs::read_dir(&base_dir) {
45 Ok(e) => e,
46 Err(e) => {
47 log::warn!(
48 "Service activation directory {:?} not accessible: {e}",
49 base_dir
50 );
51 return;
52 }
53 };
54
55 for entry in entries.flatten() {
56 let path = entry.path();
57
58 if path.extension().and_then(|s| s.to_str()) != Some("json") {
59 continue;
60 }
61
62 let content = match fs::read_to_string(&path) {
63 Ok(c) => c,
64 Err(e) => {
65 log::error!("Failed to read {:?}: {e}", path);
66 continue;
67 }
68 };
69
70 let parsed: Vec<ServiceActivationFile> = match serde_json::from_str(&content) {
71 Ok(v) => v,
72 Err(e) => {
73 log::error!("Invalid JSON in {:?}: {e}", path);
74 continue;
75 }
76 };
77
78 for item in parsed {
79 if item.kind != "RegisterService" {
80 log::warn!("Ignoring {:?}: unsupported type {}", path, item.kind);
81 continue;
82 }
83
84 log::info!(
85 "Loaded service activation: {} -> {}",
86 item.object_name,
87 item.service_name
88 );
89
90 services.lock().await.insert(
91 item.object_name,
92 ServiceEntry {
93 state: ServiceState::Stopped,
94 service_name: item.service_name,
95 pending_calls: Vec::new(),
96 owner: None,
97 },
98 );
99 }
100 }
101}
102
103pub fn spawn_service(unit: &str) {
104 let status = process::Command::new("/usr/bin/sudo")
105 .arg("/usr/bin/systemctl")
106 .arg("restart")
107 .arg(unit)
108 .status();
109
110 match status {
111 Ok(s) if s.success() => {
112 log::info!("Restarted systemd service via sudo: {}", unit);
113 }
114 Ok(s) => {
115 log::error!("sudo systemctl restart {} failed: {}", unit, s);
116 }
117 Err(e) => {
118 log::error!("Failed to execute sudo systemctl: {}", e);
119 }
120 }
121}