Coverage for src/meshadmin/cli/commands/service.py: 15%
142 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 11:34 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 11:34 +0200
1import os
2import platform
3import shutil
4import subprocess
5import sys
6from pathlib import Path
7from typing import Annotated
9import structlog
10import typer
12from meshadmin.cli.utils import get_context_config
14service_app = typer.Typer()
15logger = structlog.get_logger(__name__)
18@service_app.command(name="install")
19def service_install():
20 context = get_context_config()
21 network_dir = context["network_dir"]
22 context_name = context["name"]
23 os_name = platform.system()
24 meshadmin_path = shutil.which("meshadmin")
26 if not meshadmin_path:
27 logger.error("meshadmin executable not found in PATH")
28 exit(1)
30 (network_dir / "env").write_text(f"MESH_CONTEXT={context_name}\n")
32 if os_name == "Darwin":
33 plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
34<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
35<plist version="1.0">
36<dict>
37 <key>Label</key>
38 <string>com.meshadmin.{context_name}</string>
39 <key>ProgramArguments</key>
40 <array>
41 <string>{meshadmin_path}</string>
42 <string>nebula</string>
43 <string>start</string>
44 </array>
45 <key>EnvironmentVariables</key>
46 <dict>
47 <key>MESH_CONTEXT</key>
48 <string>{context_name}</string>
49 </dict>
50 <key>RunAtLoad</key>
51 <true/>
52 <key>KeepAlive</key>
53 <true/>
54 <key>StandardErrorPath</key>
55 <string>{network_dir}/error.log</string>
56 <key>StandardOutPath</key>
57 <string>{network_dir}/output.log</string>
58</dict>
59</plist>
60"""
61 launch_agents_dir = Path(os.path.expanduser("~/Library/LaunchAgents"))
62 if not launch_agents_dir.exists():
63 launch_agents_dir.mkdir(exist_ok=True, parents=True)
64 plist_path = launch_agents_dir / f"com.meshadmin.{context_name}.plist"
65 plist_path.write_text(plist_content)
66 subprocess.run(["launchctl", "load", str(plist_path)])
67 logger.info(
68 "meshadmin service installed and started",
69 plist_path=str(plist_path),
70 context_name=context_name,
71 )
72 print(f"meshadmin service installed at {plist_path}")
73 print(f"Context: {context_name}")
74 print("Service has been loaded and will start automatically on login")
76 else:
77 systemd_unit = f"""[Unit]
78Description=Meshadmin {context_name}
79Wants=basic.target network-online.target nss-lookup.target time-sync.target
80After=basic.target network.target network-online.target
81Before=sshd.service
83[Service]
84#Type=notify
85#NotifyAccess=main
86SyslogIdentifier={context_name}
87EnvironmentFile={network_dir}/env
88ExecReload=/bin/kill -HUP $MAINPID
89ExecStart={meshadmin_path} nebula start
90Restart=always
92[Install]
93WantedBy=multi-user.target
94"""
95 systemd_service_path = Path(
96 f"/usr/lib/systemd/system/meshadmin-{context_name}.service"
97 )
98 systemd_service_path.write_text(systemd_unit)
99 subprocess.run(["systemctl", "daemon-reload"])
100 subprocess.run(["systemctl", "enable", f"meshadmin-{context_name}"])
101 print(f"meshadmin service installed at {systemd_service_path}")
102 print(f"Context: {context_name}")
103 print("Service has been enabled and will start automatically on boot")
106@service_app.command(name="uninstall")
107def service_uninstall():
108 context = get_context_config()
109 context_name = context["name"]
110 network_dir = context["network_dir"]
111 os_name = platform.system()
113 if os_name == "Darwin":
114 plist_path = Path(
115 os.path.expanduser(
116 f"~/Library/LaunchAgents/com.meshadmin.{context_name}.plist"
117 )
118 )
119 if plist_path.exists():
120 subprocess.run(["launchctl", "unload", str(plist_path)])
121 plist_path.unlink()
122 env_path = network_dir / "env"
123 if env_path.exists():
124 env_path.unlink()
125 logger.info("meshadmin service uninstalled", plist_path=str(plist_path))
126 print(f"meshadmin service uninstalled from {plist_path}")
127 else:
128 logger.warning("meshadmin service not found", plist_path=str(plist_path))
129 print("meshadmin service not found, nothing to uninstall")
130 else:
131 systemd_service_path = Path(
132 f"/usr/lib/systemd/system/meshadmin-{context_name}.service"
133 )
134 if systemd_service_path.exists():
135 subprocess.run(["systemctl", "stop", f"meshadmin-{context_name}"])
136 subprocess.run(["systemctl", "disable", f"meshadmin-{context_name}"])
137 subprocess.run(["systemctl", "daemon-reload"])
138 systemd_service_path.unlink()
139 env_path = network_dir / "env"
140 if env_path.exists():
141 env_path.unlink()
142 logger.info("meshadmin service uninstalled")
143 print("meshadmin service uninstalled")
144 else:
145 logger.warning("meshadmin service not found")
146 print("meshadmin service not found, nothing to uninstall")
149@service_app.command(name="start")
150def service_start():
151 context = get_context_config()
152 context_name = context["name"]
153 os_name = platform.system()
155 if os_name == "Darwin":
156 plist_path = Path(
157 os.path.expanduser(
158 f"~/Library/LaunchAgents/com.meshadmin.{context_name}.plist"
159 )
160 )
161 if plist_path.exists():
162 subprocess.run(["launchctl", "load", str(plist_path)])
163 logger.info("meshadmin service started", context=context_name)
164 print(f"meshadmin service started for context {context_name}")
165 else:
166 logger.error("meshadmin service not installed", plist_path=str(plist_path))
167 print(
168 f"meshadmin service not installed for context {context_name}. Run 'meshadmin service install' first."
169 )
170 else:
171 subprocess.run(["systemctl", "start", f"meshadmin-{context_name}"])
172 print(f"meshadmin service started for context {context_name}")
175@service_app.command(name="stop")
176def service_stop():
177 context = get_context_config()
178 context_name = context["name"]
179 os_name = platform.system()
181 if os_name == "Darwin":
182 plist_path = Path(
183 os.path.expanduser(
184 f"~/Library/LaunchAgents/com.meshadmin.{context_name}.plist"
185 )
186 )
187 if plist_path.exists():
188 subprocess.run(["launchctl", "unload", str(plist_path)])
189 logger.info("meshadmin service stopped", context=context_name)
190 print(f"meshadmin service stopped for context {context_name}")
191 else:
192 logger.error("meshadmin service not installed", plist_path=str(plist_path))
193 print(
194 f"meshadmin service not installed for context {context_name}. Nothing to stop."
195 )
196 else:
197 subprocess.run(["systemctl", "stop", f"meshadmin-{context_name}"])
198 print(f"meshadmin service stopped for context {context_name}")
201@service_app.command(name="logs")
202def service_logs(
203 follow: Annotated[
204 bool,
205 typer.Option("--follow", "-f", help="Follow the logs in real time"),
206 ] = False,
207 lines: Annotated[
208 int,
209 typer.Option("--lines", "-n", help="Number of lines to show"),
210 ] = 50,
211):
212 context = get_context_config()
213 context_name = context["name"]
214 network_dir = context["network_dir"]
215 os_name = platform.system()
217 if os_name == "Darwin":
218 error_log = network_dir / "error.log"
219 output_log = network_dir / "output.log"
221 if not error_log.exists() and not output_log.exists():
222 print(
223 f"No log files found for context {context_name}. Has the service been started?"
224 )
225 raise typer.Exit(1)
227 if follow:
228 try:
229 process = subprocess.Popen(
230 ["tail", "-f", str(error_log), str(output_log)],
231 stdout=sys.stdout,
232 stderr=sys.stderr,
233 )
234 process.wait()
235 except KeyboardInterrupt:
236 process.terminate()
237 else:
238 for log_file in [output_log, error_log]:
239 if log_file.exists():
240 print(f"\n=== {log_file.name} ===")
241 result = subprocess.run(
242 ["tail", f"-n{lines}", str(log_file)],
243 capture_output=True,
244 text=True,
245 )
246 print(result.stdout)
247 else:
248 try:
249 cmd = ["journalctl", "-u", f"meshadmin-{context_name}"]
250 if follow:
251 cmd.append("-f")
252 if lines:
253 cmd.append(f"-n{lines}")
255 if follow:
256 process = subprocess.Popen(
257 cmd,
258 stdout=sys.stdout,
259 stderr=sys.stderr,
260 )
261 process.wait()
262 else:
263 result = subprocess.run(cmd, capture_output=True, text=True)
264 print(result.stdout)
266 except subprocess.CalledProcessError as e:
267 print(f"Error accessing logs: {e}")
268 print(
269 "Make sure the service is installed and you have appropriate permissions."
270 )
271 raise typer.Exit(1)