1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright (C) 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use color_eyre::Result;
use libp2p::Multiaddr;
use semver::Version;
use service_manager::{ServiceInstallCtx, ServiceLabel};
use std::{
    ffi::OsString,
    net::{Ipv4Addr, SocketAddr},
    path::PathBuf,
};

#[derive(Debug, PartialEq)]
pub struct InstallNodeServiceCtxBuilder {
    pub data_dir_path: PathBuf,
    pub genesis: bool,
    pub local: bool,
    pub log_dir_path: PathBuf,
    pub name: String,
    pub node_port: Option<u16>,
    pub bootstrap_peers: Vec<Multiaddr>,
    pub rpc_socket_addr: SocketAddr,
    pub safenode_path: PathBuf,
    pub service_user: String,
    pub env_variables: Option<Vec<(String, String)>>,
}

impl InstallNodeServiceCtxBuilder {
    pub fn build(self) -> Result<ServiceInstallCtx> {
        let label: ServiceLabel = self.name.parse()?;
        let mut args = vec![
            OsString::from("--rpc"),
            OsString::from(self.rpc_socket_addr.to_string()),
            OsString::from("--root-dir"),
            OsString::from(self.data_dir_path.to_string_lossy().to_string()),
            OsString::from("--log-output-dest"),
            OsString::from(self.log_dir_path.to_string_lossy().to_string()),
        ];

        if self.genesis {
            args.push(OsString::from("--first"));
        }
        if self.local {
            args.push(OsString::from("--local"));
        }
        if let Some(node_port) = self.node_port {
            args.push(OsString::from("--port"));
            args.push(OsString::from(node_port.to_string()));
        }

        if !self.bootstrap_peers.is_empty() {
            let peers_str = self
                .bootstrap_peers
                .iter()
                .map(|peer| peer.to_string())
                .collect::<Vec<_>>()
                .join(",");
            args.push(OsString::from("--peer"));
            args.push(OsString::from(peers_str));
        }

        let mut service_ctx = ServiceInstallCtx {
            label: label.clone(),
            program: self.safenode_path.to_path_buf(),
            args,
            contents: None,
            username: Some(self.service_user.to_string()),
            working_directory: None,
            environment: self.env_variables,
        };
        // Temporary fix to enable the restart cmd to properly restart a running service.
        // 'ServiceInstallCtx::content' will override the other passed in fields.
        #[cfg(target_os = "linux")]
        {
            use std::fmt::Write;
            let mut service = String::new();

            let _ = writeln!(service, "[Unit]");
            let _ = writeln!(
                service,
                "Description={}",
                service_ctx.label.to_script_name()
            );
            let _ = writeln!(service, "[Service]");
            let program = service_ctx.program.to_string_lossy();
            let args = service_ctx
                .args
                .clone()
                .into_iter()
                .map(|a| a.to_string_lossy().to_string())
                .collect::<Vec<String>>()
                .join(" ");
            let _ = writeln!(service, "ExecStart={program} {args}");
            if let Some(env_vars) = &service_ctx.environment {
                for (var, val) in env_vars {
                    let _ = writeln!(service, "Environment=\"{}={}\"", var, val);
                }
            }
            let _ = writeln!(service, "Restart=on-failure");
            let _ = writeln!(service, "User={}", self.service_user);
            let _ = writeln!(service, "KillMode=process"); // fixes the restart issue
            let _ = writeln!(service, "[Install]");
            let _ = writeln!(service, "WantedBy=multi-user.target");

            service_ctx.contents = Some(service);
        }
        #[cfg(not(target_os = "linux"))]
        {
            service_ctx.contents = None;
        }
        Ok(service_ctx)
    }
}

pub struct AddServiceOptions {
    pub bootstrap_peers: Vec<Multiaddr>,
    pub count: Option<u16>,
    pub env_variables: Option<Vec<(String, String)>>,
    pub genesis: bool,
    pub local: bool,
    pub node_port: Option<u16>,
    pub rpc_address: Option<Ipv4Addr>,
    pub safenode_bin_path: PathBuf,
    pub safenode_dir_path: PathBuf,
    pub service_data_dir_path: PathBuf,
    pub service_log_dir_path: PathBuf,
    pub url: Option<String>,
    pub user: String,
    pub version: String,
}

pub struct UpgradeOptions {
    pub bootstrap_peers: Vec<Multiaddr>,
    pub env_variables: Option<Vec<(String, String)>>,
    pub force: bool,
    pub start_node: bool,
    pub target_safenode_path: PathBuf,
    pub target_version: Version,
}