ipc_broker/
activate.rs

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}