tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
Program.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Specialized;
3using System.Configuration.Install;
4using System.IO;
5using System.Reflection;
6using System.Runtime.Versioning;
7using System.ServiceProcess;
8using System.Threading;
9using System.Threading.Tasks;
10
11using McMaster.Extensions.CommandLineUtils;
12
13using Microsoft.Extensions.Hosting.WindowsServices;
14using Microsoft.Extensions.Logging;
15
18
20{
24 [SupportedOSPlatform("windows")]
25 sealed class Program
26 {
30#pragma warning disable SA1401 // Fields should be private
31 internal static IWatchdogFactory WatchdogFactory = new WatchdogFactory();
32#pragma warning restore SA1401 // Fields should be private
33
37 [Option(ShortName = "u", Description = "Uninstalls ANY installed tgstation-server service >=v4.0.0")]
38 public bool Uninstall { get; }
39
43 [Option(ShortName = "x", Description = "If the service has to stop, detach any running DreamDaemon processes beforehand. Only supported on versions >=5.13.0")]
44 public bool Detach { get; }
45
49 [Option(ShortName = "r", Description = "Stop and restart the tgstation-server service")]
50 public bool Restart { get; }
51
55 [Option(ShortName = "i", Description = "Installs this executable as the tgstation-server Windows service")]
56 public bool Install { get; set; }
57
61 [Option(ShortName = "f", Description = "Automatically agree to uninstall prompts")]
62 public bool Force { get; set; }
63
67 [Option(ShortName = "s", Description = "Suppresses console output from the host watchdog")]
68 public bool Silent { get; set; }
69
73 [Option(ShortName = "c", Description = "Runs the TGS setup wizard")]
74 public bool Configure { get; set; }
75
79 [Option(ShortName = "p", Description = "Arguments passed to main host process")]
80 public string? PassthroughArgs { get; set; }
81
87 static Task<int> Main(string[] args) => CommandLineApplication.ExecuteAsync<Program>(args);
88
93 public async Task OnExecuteAsync()
94 {
95 var standardRun = !Install && !Uninstall && !Configure;
96 if (standardRun)
97 if (!Silent && !WindowsServiceHelpers.IsWindowsService())
98 {
99 var result = NativeMethods.MessageBox(
100 default,
101 "You are running the TGS windows service executable directly. It should only be run by the service control manager. Would you like to install and configure the service in this location?",
102 "TGS Service",
103 NativeMethods.MessageBoxButtons.YesNo);
104
105 if (result != NativeMethods.DialogResult.Yes)
106 return;
107
108 Install = true;
109 Configure = true;
110 }
111 else
112 using (var service = new ServerService(WatchdogFactory, GetPassthroughArgs(), LogLevel.Trace))
113 {
114 service.Run();
115 return;
116 }
117
118 if (Configure)
119 await RunConfigure(CancellationToken.None); // DCT: None available
120
121 bool stopped = false;
122 if (Uninstall)
123 {
124 foreach (ServiceController sc in ServiceController.GetServices())
125 {
126 bool match;
127 using (sc)
128 {
129 match = sc.ServiceName == ServerService.Name;
130 if (match)
131 RestartService(sc);
132 }
133
134 if (match)
135 InvokeSC(ServerService.Name);
136 }
137
138 stopped = true;
139 }
140
141 if (Install)
142 stopped |= RunServiceInstall();
143
144 if (Restart)
145 foreach (ServiceController sc in ServiceController.GetServices())
146 using (sc)
147 if (sc.ServiceName == ServerService.Name)
148 {
149 if (!stopped)
150 RestartService(sc);
151
152 sc.Start();
153 }
154 }
155
160 void InvokeSC(string? serviceToUninstall)
161 {
162 using var installer = new ServiceInstaller();
163 if (serviceToUninstall != null)
164 {
165 installer.Context = new InstallContext($"old-{serviceToUninstall}-uninstall.log", null);
166 installer.ServiceName = serviceToUninstall;
167 installer.Uninstall(null);
168 return;
169 }
170
171 var fullPathToAssembly = Path.GetFullPath(
172 Assembly.GetExecutingAssembly().Location);
173
174 var assemblyDirectory = Path.GetDirectoryName(fullPathToAssembly);
175 if (assemblyDirectory == null)
176 throw new InvalidOperationException($"Failed to resolve directory name of {assemblyDirectory}");
177
178 var assemblyNameWithoutExtension = Path.GetFileNameWithoutExtension(fullPathToAssembly);
179 var exePath = Path.Combine(assemblyDirectory, $"{assemblyNameWithoutExtension}.exe");
180
181 var programDataDirectory = Path.Combine(
182 Environment.GetFolderPath(
183 Environment.SpecialFolder.CommonApplicationData,
184 Environment.SpecialFolderOption.DoNotVerify),
185 Server.Common.Constants.CanonicalPackageName);
186
187 using var processInstaller = new ServiceProcessInstaller();
188 processInstaller.Account = ServiceAccount.LocalSystem;
189
190 // Mimicing Tgstation.Server.Host.Service.Wix here, which is the source of truth for this data
191 installer.Context = new InstallContext(
192 Path.Combine(programDataDirectory, $"tgs-install-{Guid.NewGuid()}.log"),
193 [
194 $"assemblypath=\"{exePath}\"{(String.IsNullOrWhiteSpace(PassthroughArgs) ? String.Empty : $" -p=\"{PassthroughArgs}\"")}",
195 ]);
196 installer.Description = $"{Server.Common.Constants.CanonicalPackageName} running as a Windows service.";
197 installer.DisplayName = Server.Common.Constants.CanonicalPackageName;
198 installer.StartType = ServiceStartMode.Automatic;
199 installer.ServicesDependedOn = new string[] { "Tcpip", "Dhcp", "Dnscache" };
200 installer.ServiceName = ServerService.Name;
201 installer.Parent = processInstaller;
202
203 var state = new ListDictionary();
204 try
205 {
206 installer.Install(state);
207
208 installer.Commit(state);
209 }
210 catch
211 {
212 installer.Rollback(state);
213 throw;
214 }
215 }
216
221 bool RunServiceInstall()
222 {
223 // First check if the service already exists
224 bool serviceStopped = false;
225 if (Force || !WindowsServiceHelpers.IsWindowsService())
226 foreach (ServiceController sc in ServiceController.GetServices())
227 using (sc)
228 {
229 var serviceName = sc.ServiceName;
230 if (serviceName == ServerService.Name || serviceName == "tgstation-server-4")
231 {
232 NativeMethods.DialogResult result = !Force
233 ? NativeMethods.MessageBox(
234 default,
235 $"You already have another TGS service installed ({sc.ServiceName}). Would you like to uninstall it now? Pressing \"No\" will cancel this install.",
236 "TGS Service",
237 NativeMethods.MessageBoxButtons.YesNo)
238 : NativeMethods.DialogResult.Yes;
239 if (result != NativeMethods.DialogResult.Yes)
240 return false; // is this needed after exit?
241
242 // Stop it first to give it some cleanup time
243 RestartService(sc);
244
245 // And remove it
246 InvokeSC(sc.ServiceName);
247 }
248 }
249
250 InvokeSC(null);
251
252 return serviceStopped;
253 }
254
259 void RestartService(ServiceController serviceController)
260 {
261 if (serviceController.Status != ServiceControllerStatus.Running)
262 return;
263
264 var stop = !Detach;
265 if (!stop)
266 {
268 serviceController.ExecuteCommand(serviceControllerCommand!.Value);
269 serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30));
270 if (serviceController.Status != ServiceControllerStatus.Stopped)
271 stop = true;
272 }
273
274 if (stop)
275 {
276 serviceController.Stop();
277 serviceController.WaitForStatus(ServiceControllerStatus.Stopped);
278 }
279 }
280
286 async ValueTask RunConfigure(CancellationToken cancellationToken)
287 {
288 using var loggerFactory = LoggerFactory.Create(builder =>
289 {
290 if (!Silent)
291 builder.AddConsole();
292 });
293
294 var watchdog = WatchdogFactory.CreateWatchdog(new NoopSignalChecker(), loggerFactory);
295 await watchdog.RunAsync(true, GetPassthroughArgs(), cancellationToken);
296 }
297
302 string[] GetPassthroughArgs() => PassthroughArgs?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
303 }
304}
Values able to be passed via the update file path.
static ? int GetServiceCommandId(string command)
Gets the int value of a given command .
const string CommandDetachingShutdown
Stops the server ASAP, detaching the watchdog for any running instances.
Entrypoint for the Process.
Definition Program.cs:20
IWatchdog CreateWatchdog(ISignalChecker signalChecker, ILoggerFactory loggerFactory)
Create a IWatchdog.A new IWatchdog.
ValueTask< bool > RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken)
Run the IWatchdog.
@ Restart
User can immediately restart the Watchdog.