mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-25 05:56:58 +01:00
Porting the old instance complete. Added DD necessities
This commit is contained in:
@@ -48,7 +48,7 @@ If the command runs successfully you're all set up. Now, here is the debugging p
|
||||
1. Set your breakpoints
|
||||
1. Start the service
|
||||
1. Use your environment to attach to `TGServerService.exe` for debugging
|
||||
1. If you need to debug startup, you'll have to add `System.Diagnostics.Debugger.Start()` where you want the service to wait for you. Note that breaking in the constructor of `TGServerService.Service` will cause Windows to terminate the process if you don't move fast enough due to it thinking the service is malfunctioning. In fact, if you need to debug the constructor, it's better to launch `TGServerService.exe` as a regular debug session, you won't be able to pass the `Service.Run()` call that way, however.
|
||||
1. If you need to debug startup, you'll have to add `System.Diagnostics.Debugger.Start()` where you want the service to wait for you. Do not write a constructor for the Service classas Windows will not let you debug it properly
|
||||
|
||||
Now be careful while debugging. The service runs with root level privileges and you wouldn't want any [accidents](http://i.imgur.com/zvGEpJD.png) to happen, would you?
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// /tg/station 13 server tools API v3.1.0.2
|
||||
// /tg/station 13 server tools API
|
||||
#define SERVICE_API_VERSION_STRING "3.2.0.0"
|
||||
|
||||
//CONFIGURATION
|
||||
//use this define if you want to do configuration outside of this file
|
||||
@@ -64,14 +65,13 @@
|
||||
|
||||
//IMPLEMENTATION
|
||||
|
||||
#define SERVICE_API_VERSION_STRING "3.1.0.2"
|
||||
|
||||
#define REBOOT_MODE_NORMAL 0
|
||||
#define REBOOT_MODE_HARD 1
|
||||
#define REBOOT_MODE_SHUTDOWN 2
|
||||
|
||||
#define SERVICE_WORLD_PARAM "server_service"
|
||||
#define SERVICE_VERSION_PARAM "server_service_version"
|
||||
#define SERVICE_INSTANCE_PARAM "server_instance"
|
||||
#define SERVICE_PR_TEST_JSON "prtestjob.json"
|
||||
#define SERVICE_INTERFACE_DLL "TGServiceInterface.dll"
|
||||
#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint"
|
||||
|
||||
@@ -30,7 +30,7 @@ SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE)
|
||||
return
|
||||
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
|
||||
CRASH("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
|
||||
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(command) //trust no retval
|
||||
call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)("[params[SEVICE_INSTANCE_PARAM]] [command]") //trust no retval
|
||||
return TRUE
|
||||
|
||||
/world/proc/ChatBroadcast(message)
|
||||
|
||||
@@ -16,7 +16,7 @@ Requires python 2.7/3.6 to be installed for changelog generation
|
||||
|
||||
## Installing (GUI):
|
||||
1. Launch TGControlPanel.exe as an administrator. A shortcut can be found on your desktop
|
||||
1. Optionally switch to the `Server` tab and change the Server Path location from C:\\tgstation-server-3 to wherever you wish
|
||||
1. Optionally switch to the `Server` tab and change the Server Path location from `C:\\tgstation-server-3` to wherever you wish
|
||||
1. Go to the `Repository` Tab and set the remote address and branch of the git you with to track
|
||||
1. Hit the clone button
|
||||
1. While waiting go to the BYOND tab and install the BYOND version you wish
|
||||
@@ -120,10 +120,13 @@ The service supports updates while running a DreamDaemon instance. Simply instal
|
||||
|
||||
* `prtestjob.json`
|
||||
* This contains information about current test merged pull requests in the Repository folder
|
||||
|
||||
|
||||
* `TGS3.json`
|
||||
* This is a copy of TGS3.json from the Repository. If a repostory change creates differences between the two, update operations will be blocked until the user confirms they want to change it
|
||||
|
||||
* `Instance.cfg`
|
||||
* The encrypted internal configuration settings for a server instance. Note that access to this file bypasses API user restrictions
|
||||
|
||||
### Starting the game server:
|
||||
To run the game server, open the `Server` tab of the control panel and click either `Start`
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using TGServiceInterface;
|
||||
using System.Configuration;
|
||||
|
||||
namespace TGServerService
|
||||
{
|
||||
@@ -8,6 +8,11 @@ namespace TGServerService
|
||||
/// </summary>
|
||||
class DeprecatedInstanceConfig : InstanceConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The default <see cref="ServerInstance"/> directory
|
||||
/// </summary>
|
||||
const string DefaultInstallationPath = "C:\\tgstation-server-3";
|
||||
|
||||
/// <summary>
|
||||
/// Convert the settings version 6 .NET settings file to a config json
|
||||
/// </summary>
|
||||
@@ -15,33 +20,78 @@ namespace TGServerService
|
||||
public static InstanceConfig CreateFromNETSettings()
|
||||
{
|
||||
var Config = Properties.Settings.Default;
|
||||
var result = new DeprecatedInstanceConfig((string)Config.GetPreviousVersion("ServerDirectory"))
|
||||
{
|
||||
ProjectName = (string)Config.GetPreviousVersion("ProjectName"),
|
||||
Port = (ushort)Config.GetPreviousVersion("ServerPort"),
|
||||
CommitterName = (string)Config.GetPreviousVersion("CommitterName"),
|
||||
CommitterEmail = (string)Config.GetPreviousVersion("CommitterEmail"),
|
||||
Security = (DreamDaemonSecurity)Config.GetPreviousVersion("ServerSecurity"),
|
||||
Autostart = (bool)Config.GetPreviousVersion("DDAutoStart"),
|
||||
ChatProviderData = (string)Config.GetPreviousVersion("ChatProviderData"),
|
||||
ChatProviderEntropy = (string)Config.GetPreviousVersion("ChatProviderEntropy"),
|
||||
ReattachRequired = (bool)Config.GetPreviousVersion("ReattachToDD"),
|
||||
ReattachProcessID = (int)Config.GetPreviousVersion("ReattachPID"),
|
||||
ReattachPort = (ushort)Config.GetPreviousVersion("ReattachPort"),
|
||||
ReattachCommsKey = (string)Config.GetPreviousVersion("ReattachCommsKey"),
|
||||
ReattachAPIVersion = (string)Config.GetPreviousVersion("ReattachAPIVersion"),
|
||||
AutoUpdateInterval = (ulong)Config.GetPreviousVersion("AutoUpdateInterval"),
|
||||
AuthorizedUserGroupSID = (string)Config.GetPreviousVersion("AuthorizedGroupSID")
|
||||
};
|
||||
var result = new DeprecatedInstanceConfig(LoadPreviousNetPropertyOrDefault("ServerDirectory", "C:\\tgstation-server-3"));
|
||||
// using nameof for sanity where possible
|
||||
result.ProjectName = LoadPreviousNetPropertyOrDefault(nameof(ProjectName), result.ProjectName);
|
||||
result.Port = LoadPreviousNetPropertyOrDefault("ServerPort", result.Port);
|
||||
result.CommitterName = LoadPreviousNetPropertyOrDefault(nameof(CommitterName), result.CommitterName);
|
||||
result.CommitterEmail = LoadPreviousNetPropertyOrDefault(nameof(CommitterEmail), result.CommitterEmail);
|
||||
result.Security = LoadPreviousNetPropertyOrDefault("ServerSecurity", result.Security);
|
||||
result.Autostart = LoadPreviousNetPropertyOrDefault("DDAutoStart", result.Autostart);
|
||||
result.ChatProviderData = LoadPreviousNetPropertyOrDefault(nameof(ChatProviderData), result.ChatProviderData);
|
||||
result.ChatProviderEntropy = LoadPreviousNetPropertyOrDefault(nameof(ChatProviderEntropy), result.ChatProviderEntropy);
|
||||
result.ReattachRequired = LoadPreviousNetPropertyOrDefault("ReattachToDD", result.ReattachRequired);
|
||||
result.ReattachProcessID = LoadPreviousNetPropertyOrDefault("ReattachPID", result.ReattachProcessID);
|
||||
result.ReattachPort = LoadPreviousNetPropertyOrDefault(nameof(ReattachPort), result.ReattachPort);
|
||||
result.ReattachCommsKey = LoadPreviousNetPropertyOrDefault(nameof(ReattachCommsKey), result.ReattachCommsKey);
|
||||
result.ReattachAPIVersion = LoadPreviousNetPropertyOrDefault(nameof(ReattachAPIVersion), result.ReattachAPIVersion);
|
||||
result.AutoUpdateInterval = LoadPreviousNetPropertyOrDefault(nameof(AutoUpdateInterval), result.AutoUpdateInterval);
|
||||
result.AuthorizedUserGroupSID = LoadPreviousNetPropertyOrDefault("AuthorizedGroupSID", result.AuthorizedUserGroupSID);
|
||||
result.MigrateToCurrentVersion();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a previous .NET config <paramref name="property"/> and returns it or some <paramref name="defaultValue"/> if it wasn't set
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the <paramref name="property"/></typeparam>
|
||||
/// <param name="property">The config key of the property</param>
|
||||
/// <param name="defaultValue">The default value of the <paramref name="property"/></param>
|
||||
/// <returns>The <paramref name="property"/> if it exists, otherwise the <paramref name="defaultValue"/></returns>
|
||||
static T LoadPreviousNetPropertyOrDefault<T>(string property, T defaultValue)
|
||||
{
|
||||
//try it the simple way first
|
||||
try
|
||||
{
|
||||
var result = (T)Properties.Settings.Default.GetPreviousVersion(property);
|
||||
return result == null ? defaultValue : result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
//.NET is fucking stupid
|
||||
//If we don't have the correct property in our *CURRENT* .settings file it will automatically throw an exception when it tries to load it
|
||||
//Which means we can never fucking delete config settings
|
||||
//Which is fucking retarded
|
||||
//This hooks into the settings provider and forces it to load it anyway
|
||||
var Config = Properties.Settings.Default;
|
||||
var Provider = Config.Properties[nameof(Config.SettingsVersion)].Provider; //nameof for sanity
|
||||
|
||||
var sp = new SettingsProperty(property)
|
||||
{
|
||||
PropertyType = typeof(T),
|
||||
DefaultValue = defaultValue,
|
||||
Provider = Provider
|
||||
};
|
||||
|
||||
var ProviderInterface = Provider as IApplicationSettingsProvider;
|
||||
|
||||
var result = ProviderInterface.GetPreviousVersion(Config.Context, sp);
|
||||
if (result != null && result.PropertyValue != null)
|
||||
return (T)result.PropertyValue;
|
||||
}
|
||||
catch { }
|
||||
//f u c k i t
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DeprecatedInstanceConfig"/>. Used by the deserializer
|
||||
/// </summary>
|
||||
[Obsolete("This method is for use by the deserializer only.", true)]
|
||||
public DeprecatedInstanceConfig() : base(null) { }
|
||||
public DeprecatedInstanceConfig() : base(DefaultInstallationPath) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DeprecatedInstanceConfig"/> for a <see cref="ServerInstance"/> at <paramref name="path"/>
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace TGServerService
|
||||
public string InstanceDirectory { get; private set; }
|
||||
|
||||
public ulong Version { get; protected set; } = CurrentVersion;
|
||||
public Guid ID { get; private set; } = Guid.NewGuid();
|
||||
|
||||
public string Name { get; set; } = "TG Station Server";
|
||||
public bool Enabled { get; set; } = true;
|
||||
@@ -55,7 +54,9 @@ namespace TGServerService
|
||||
|
||||
public void Save()
|
||||
{
|
||||
File.WriteAllText(Path.Combine(InstanceDirectory, JSONFilename), new JavaScriptSerializer().Serialize(this));
|
||||
var data = new JavaScriptSerializer().Serialize(this);
|
||||
var path = Path.Combine(InstanceDirectory, JSONFilename);
|
||||
File.WriteAllText(path, data);
|
||||
}
|
||||
|
||||
public static InstanceConfig Load(string path)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace TGServerService
|
||||
/// <summary>
|
||||
/// Entry point to the program
|
||||
/// </summary>
|
||||
static void Main() => new Service();
|
||||
static void Main() => Service.Launch();
|
||||
|
||||
/// <summary>
|
||||
/// Copy a file from <paramref name="source"/> to <paramref name="dest"/>, but first ensure the destination directory exists
|
||||
|
||||
@@ -577,7 +577,7 @@ namespace TGServerService
|
||||
|
||||
GenCommsKey();
|
||||
StartingSecurity = Config.Security;
|
||||
Proc.StartInfo.Arguments = String.Format("{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}\" -{2} -public", DMB, Config.Port, SecurityWord(), serviceCommsKey, Version(), Config.Webclient ? "-webclient " : "");
|
||||
Proc.StartInfo.Arguments = String.Format("{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}&server_instance={6}\" -{2} -public", DMB, Config.Port, SecurityWord(), serviceCommsKey, Version(), Config.Webclient ? "-webclient " : "", Config.Name);
|
||||
UpdateInterfaceDll(true);
|
||||
lock (topicLock)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace TGServerService
|
||||
/// <summary>
|
||||
/// The configuration settings for the instance
|
||||
/// </summary>
|
||||
public readonly InstanceConfig Config;
|
||||
readonly InstanceConfig Config;
|
||||
/// <summary>
|
||||
/// Constructs and a <see cref="ServerInstance"/>
|
||||
/// </summary>
|
||||
@@ -51,6 +51,7 @@ namespace TGServerService
|
||||
DisposeByond();
|
||||
DisposeRepo();
|
||||
DisposeChat();
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+78
-48
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
@@ -13,7 +14,8 @@ namespace TGServerService
|
||||
/// <summary>
|
||||
/// The windows service the application runs as
|
||||
/// </summary>
|
||||
sealed partial class Service : ServiceBase
|
||||
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
|
||||
sealed class Service : ServiceBase, ITGSService, ITGConnectivity
|
||||
{
|
||||
/// <summary>
|
||||
/// The logging ID used for <see cref="Service"/> events
|
||||
@@ -50,6 +52,25 @@ namespace TGServerService
|
||||
ActiveService.EventLog.WriteEntry(message, eventType, (int)id + loggingID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and runs a <see cref="Service"/>. Do not add any code that might need debugging here as it's near impossible to get Windows to debug it properly without timing out
|
||||
/// </summary>
|
||||
public static void Launch()
|
||||
{
|
||||
if (ActiveService != null)
|
||||
throw new Exception("There is already a Service instance running!");
|
||||
ActiveService = new Service();
|
||||
Run(ActiveService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the service name. Do not add any more code due to the reasons outlined in <see cref="Launch"/>
|
||||
/// </summary>
|
||||
Service()
|
||||
{
|
||||
ServiceName = "TG Station Server";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The WCF host that contains <see cref="ITGSService"/> connects to
|
||||
/// </summary>
|
||||
@@ -69,49 +90,16 @@ namespace TGServerService
|
||||
/// <param name="oldVersion">The version to migrate from</param>
|
||||
void MigrateSettings(int oldVersion)
|
||||
{
|
||||
var Config = Properties.Settings.Default;
|
||||
switch (oldVersion)
|
||||
{
|
||||
case 6: //switch to per-instance configs
|
||||
var IC = DeprecatedInstanceConfig.CreateFromNETSettings();
|
||||
IC.Save();
|
||||
Properties.Settings.Default.InstancePaths.Add(IC.InstanceDirectory);
|
||||
Config.InstancePaths.Add(IC.InstanceDirectory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//you should seriously not add anything here
|
||||
//Use OnStart instead
|
||||
/// <summary>
|
||||
/// Construct and run a <see cref="Service"/>. Can only execute in the context of the Windows service manager
|
||||
/// </summary>
|
||||
public Service()
|
||||
{
|
||||
var Config = Properties.Settings.Default;
|
||||
try
|
||||
{
|
||||
Environment.CurrentDirectory = Directory.CreateDirectory(Path.GetTempPath() + "/TGStationServerService").FullName; //MOVE THIS POINTER BECAUSE ONE TIME I ALMOST ACCIDENTALLY NUKED MYSELF BY REFACTORING! http://imgur.com/zvGEpJD.png
|
||||
if (Properties.Settings.Default.UpgradeRequired)
|
||||
{
|
||||
var newVersion = Config.SettingsVersion;
|
||||
Config.Upgrade();
|
||||
|
||||
for(var oldVersion = Config.SettingsVersion; oldVersion < newVersion; ++oldVersion)
|
||||
MigrateSettings(oldVersion);
|
||||
|
||||
Config.SettingsVersion = newVersion;
|
||||
|
||||
Config.UpgradeRequired = false;
|
||||
Config.Save();
|
||||
}
|
||||
ServiceName = "TG Station Server";
|
||||
ActiveService = this;
|
||||
Run(this);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides and saves the configured <see cref="Properties.Settings.RemoteAccessPort"/> if requested by command line parameters
|
||||
@@ -146,6 +134,10 @@ namespace TGServerService
|
||||
/// <param name="args">Command line arguments for the <see cref="Service"/></param>
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
Environment.CurrentDirectory = Directory.CreateDirectory(Path.GetTempPath() + "/TGStationServerService").FullName; //MOVE THIS POINTER BECAUSE ONE TIME I ALMOST ACCIDENTALLY NUKED MYSELF BY REFACTORING! http://imgur.com/zvGEpJD.png
|
||||
|
||||
SetupConfig();
|
||||
|
||||
ChangePortFromCommandLine(args);
|
||||
|
||||
SetupService();
|
||||
@@ -155,13 +147,48 @@ namespace TGServerService
|
||||
OnlineAllHosts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes some changes to the <see cref="Properties.Settings"/> that always need to be done.
|
||||
/// </summary>
|
||||
void PrePrepConfig()
|
||||
{
|
||||
var Config = Properties.Settings.Default;
|
||||
|
||||
if (Config.InstancePaths == null)
|
||||
Config.InstancePaths = new StringCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upgrades up the service configuration
|
||||
/// </summary>
|
||||
void SetupConfig()
|
||||
{
|
||||
var Config = Properties.Settings.Default;
|
||||
if (Config.UpgradeRequired)
|
||||
{
|
||||
var newVersion = Config.SettingsVersion;
|
||||
Config.Upgrade();
|
||||
|
||||
PrePrepConfig();
|
||||
|
||||
for (var oldVersion = Config.SettingsVersion; oldVersion < newVersion; ++oldVersion)
|
||||
MigrateSettings(oldVersion);
|
||||
|
||||
Config.SettingsVersion = newVersion;
|
||||
|
||||
Config.UpgradeRequired = false;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="ServiceHost"/> for <see cref="ITGSService"/>
|
||||
/// </summary>
|
||||
void SetupService()
|
||||
{
|
||||
serviceHost = CreateHost(this);
|
||||
AddEndpoint(serviceHost, typeof(ITGSService), Interface.MasterInterfaceName);
|
||||
serviceHost = CreateHost(this, Interface.ServiceInterfaceName);
|
||||
AddEndpoint(serviceHost, typeof(ITGSService));
|
||||
AddEndpoint(serviceHost, typeof(ITGConnectivity));
|
||||
serviceHost.Authorization.ServiceAuthorizationManager = new AdministrativeAuthorizationManager(); //only admins can diddle us
|
||||
}
|
||||
|
||||
@@ -180,9 +207,9 @@ namespace TGServerService
|
||||
/// </summary>
|
||||
/// <param name="singleton">The <see cref="ServiceHost.SingletonInstance"/></param>
|
||||
/// <returns>The created <see cref="ServiceHost"/></returns>
|
||||
static ServiceHost CreateHost(object singleton)
|
||||
static ServiceHost CreateHost(object singleton, string endpointPostfix)
|
||||
{
|
||||
return new ServiceHost(singleton, new Uri[] { new Uri("net.pipe://localhost"), new Uri(String.Format("https://localhost:{0}", Properties.Settings.Default.RemoteAccessPort)) })
|
||||
return new ServiceHost(singleton, new Uri[] { new Uri(String.Format("net.pipe://localhost/{0}", endpointPostfix)), new Uri(String.Format("https://localhost:{0}/{1}", Properties.Settings.Default.RemoteAccessPort, endpointPostfix)) })
|
||||
{
|
||||
CloseTimeout = new TimeSpan(0, 0, 5)
|
||||
};
|
||||
@@ -237,19 +264,21 @@ namespace TGServerService
|
||||
ServiceHost SetupInstance(string path)
|
||||
{
|
||||
ServerInstance instance;
|
||||
string instanceName;
|
||||
try
|
||||
{
|
||||
var config = InstanceConfig.Load(path);
|
||||
if (hosts.ContainsKey(path))
|
||||
{
|
||||
var datInstance = ((ServerInstance)hosts[path].SingletonInstance);
|
||||
WriteEntry(String.Format("Unable to start instance at path {0}. Has the same name as instance at path {1} ({2}). Detaching...", path, datInstance.ServerDirectory(), datInstance.Config.Name), EventID.InstanceInitializationFailure, EventLogEntryType.Error, LoggingID);
|
||||
WriteEntry(String.Format("Unable to start instance at path {0}. Has the same name as instance at path {1}. Detaching...", path, datInstance.ServerDirectory()), EventID.InstanceInitializationFailure, EventLogEntryType.Error, LoggingID);
|
||||
return null;
|
||||
}
|
||||
if (!config.Enabled)
|
||||
return null;
|
||||
var ID = LockLoggingID();
|
||||
WriteEntry(String.Format("Instance {0} ({1}) assigned logging ID {2}", config.Name, path, ID), EventID.InstanceIDAssigned, EventLogEntryType.Information, ID);
|
||||
instanceName = config.Name;
|
||||
instance = new ServerInstance(config, ID);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -258,12 +287,11 @@ namespace TGServerService
|
||||
return null;
|
||||
}
|
||||
|
||||
var host = CreateHost(instance);
|
||||
hosts.Add(instance.Config.Name, host);
|
||||
|
||||
var endpointPrefix = String.Format("{0}/{1}", Interface.MasterInterfaceName, instance.Config.Name);
|
||||
var host = CreateHost(instance, String.Format("{0}/{1}", Interface.InstanceInterfaceName, instanceName));
|
||||
hosts.Add(instanceName, host);
|
||||
|
||||
foreach (var J in Interface.ValidInterfaces)
|
||||
AddEndpoint(host, J, endpointPrefix);
|
||||
AddEndpoint(host, J);
|
||||
|
||||
host.Authorization.ServiceAuthorizationManager = instance;
|
||||
return host;
|
||||
@@ -275,9 +303,9 @@ namespace TGServerService
|
||||
/// <param name="host">The service host to add the component to</param>
|
||||
/// <param name="typetype">The type of the component</param>
|
||||
/// <param name="PipePrefix">The URI prefix for accessing the <paramref name="host"/></param>
|
||||
void AddEndpoint(ServiceHost host, Type typetype, string PipePrefix)
|
||||
void AddEndpoint(ServiceHost host, Type typetype)
|
||||
{
|
||||
var bindingName = PipePrefix + "/" + typetype.Name;
|
||||
var bindingName = typetype.Name;
|
||||
host.AddServiceEndpoint(typetype, new NetNamedPipeBinding() { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Interface.TransferLimitLocal }, bindingName);
|
||||
var httpsBinding = new WSHttpBinding()
|
||||
{
|
||||
@@ -310,6 +338,8 @@ namespace TGServerService
|
||||
{
|
||||
WriteEntry(e.ToString(), EventID.ServiceShutdownFail, EventLogEntryType.Error, LoggingID);
|
||||
}
|
||||
Properties.Settings.Default.Save();
|
||||
ActiveService = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
<HintPath>..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.DirectoryServices.AccountManagement" />
|
||||
<Reference Include="System.Interactive.Async, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace TGServiceInterface.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by DD to access the interop API with call()()
|
||||
/// Used by DreamDaemon to access the interop API with call()(). Restrictions are in place so that only a DreamDaemon instance launched by the service can use this API
|
||||
/// </summary>
|
||||
[ServiceContract]
|
||||
public interface ITGInterop
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using RGiesecke.DllExport;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using TGServiceInterface.Components;
|
||||
|
||||
@@ -21,13 +22,17 @@ namespace TGServiceInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
var channel = Interface.CreateChannel<ITGInterop>();
|
||||
var parsedArgs = new List<string>();
|
||||
parsedArgs.AddRange(args);
|
||||
Interface.InstanceName = parsedArgs[0];
|
||||
parsedArgs.RemoveAt(0);
|
||||
var channel = Interface.GetComponent<ITGInterop>();
|
||||
try
|
||||
{
|
||||
channel.CreateChannel().InteropMessage(String.Join(" ", args));
|
||||
channel.InteropMessage(String.Join(" ", parsedArgs));
|
||||
}
|
||||
catch { }
|
||||
Interface.CloseChannel(channel);
|
||||
Interface.CloseAllChannels();
|
||||
}
|
||||
catch { }
|
||||
return 0;
|
||||
|
||||
@@ -29,13 +29,26 @@ namespace TGServiceInterface
|
||||
/// <summary>
|
||||
/// The maximum message size to and from a remote server
|
||||
/// </summary>
|
||||
public const long TransferLimitRemote = 10485760; //10 MB
|
||||
public const long TransferLimitRemote = 10485760; //10 MB
|
||||
|
||||
/// <summary>
|
||||
/// Base name of the communication pipe
|
||||
/// they are formatted as MasterPipeName/ComponentName
|
||||
/// Base name of communication URLs
|
||||
/// </summary>
|
||||
public const string MasterInterfaceName = "TGStationServerService";
|
||||
const string MasterInterfaceName = "TGStationServerService";
|
||||
/// <summary>
|
||||
/// Base name of the root service URLs
|
||||
/// </summary>
|
||||
public const string ServiceInterfaceName = MasterInterfaceName + "/Service";
|
||||
/// <summary>
|
||||
/// Base name of instance URLs
|
||||
/// </summary>
|
||||
public const string InstanceInterfaceName = MasterInterfaceName + "/Instance";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The name of the current instance in use
|
||||
/// </summary>
|
||||
public static string InstanceName;
|
||||
|
||||
/// <summary>
|
||||
/// If this is set, we will try and connect to an HTTPS server running at this address
|
||||
@@ -68,11 +81,13 @@ namespace TGServiceInterface
|
||||
/// <returns>A <see cref="IList{T}"/> of <see langword="interface"/> <see cref="Type"/>s that can be used with the service</returns>
|
||||
static IList<Type> CollectComponents()
|
||||
{
|
||||
//find all interfaces in this assembly in this namespace that have the service contract attribute
|
||||
var ServiceComponent = typeof(ITGSService); //this is special
|
||||
//find all interfaces in this assembly in this namespace that have the service contract attribute
|
||||
var query = from t in Assembly.GetExecutingAssembly().GetTypes()
|
||||
where t.IsInterface
|
||||
&& t.Namespace == typeof(ITGSService).Namespace
|
||||
where t.IsInterface
|
||||
&& t.Namespace == ServiceComponent.Namespace
|
||||
&& t.GetCustomAttribute(typeof(ServiceContractAttribute)) != null
|
||||
&& t != ServiceComponent
|
||||
select t;
|
||||
return query.ToList();
|
||||
}
|
||||
@@ -115,13 +130,13 @@ namespace TGServiceInterface
|
||||
{
|
||||
HTTPSURL = null;
|
||||
HTTPSPassword = null;
|
||||
ClearCachedChannels();
|
||||
CloseAllChannels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all <see cref="ChannelFactory"/>s stored in <see cref="ChannelFactoryCache"/> and clears it
|
||||
/// </summary>
|
||||
static void ClearCachedChannels()
|
||||
public static void CloseAllChannels()
|
||||
{
|
||||
lock (ChannelFactoryCache)
|
||||
{
|
||||
@@ -138,7 +153,7 @@ namespace TGServiceInterface
|
||||
/// <returns><see langword="true"/> if the <see cref="Interface"/> interface being used to connect to a service does not have the same release version as the service</returns>
|
||||
public static bool VersionMismatch(out string errorMessage)
|
||||
{
|
||||
var splits = GetComponent<ITGSService>().Version().Split(' ');
|
||||
var splits = GetService().Version().Split(' ');
|
||||
var theirs = new Version(splits[splits.Length - 1].Substring(1));
|
||||
var ours = new Version(FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion);
|
||||
if(theirs != ours)
|
||||
@@ -163,14 +178,14 @@ namespace TGServiceInterface
|
||||
HTTPSPort = port;
|
||||
HTTPSUsername = username;
|
||||
HTTPSPassword = password;
|
||||
ClearCachedChannels();
|
||||
CloseAllChannels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely shuts down a single <see cref="ChannelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="cf">The <see cref="ChannelFactory"/> to shutdown</param>
|
||||
public static void CloseChannel(ChannelFactory cf)
|
||||
static void CloseChannel(ChannelFactory cf)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -183,12 +198,23 @@ namespace TGServiceInterface
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the requested <see cref="Interface"/> component <see langword="interface"/>. This does not guarantee a successful connection. <see cref="ChannelFactory{TChannel}"/>s created this way are recycled for minimum latency and bandwidth usage
|
||||
/// Returns the requested <see cref="Interface"/> component <see langword="interface"/> for the instance <see cref="InstanceName"/>. This does not guarantee a successful connection. <see cref="ChannelFactory{TChannel}"/>s created this way are recycled for minimum latency and bandwidth usage
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component <see langword="interface"/> to retrieve</typeparam>
|
||||
/// <returns>The correct component <see langword="interface"/></returns>
|
||||
public static T GetComponent<T>()
|
||||
{
|
||||
var ToT = typeof(T);
|
||||
if (!ValidInterfaces.Contains(ToT) && ToT != typeof(ITGSService))
|
||||
throw new Exception("Invalid type!");
|
||||
return GetComponentImpl<T>(true);
|
||||
}
|
||||
|
||||
static T GetComponentImpl<T>(bool useInstanceName)
|
||||
{
|
||||
if (useInstanceName & InstanceName == null)
|
||||
throw new Exception("Instance not selected!");
|
||||
|
||||
var tot = typeof(T);
|
||||
ChannelFactory<T> cf;
|
||||
|
||||
@@ -207,28 +233,36 @@ namespace TGServiceInterface
|
||||
ChannelFactoryCache[tot].Abort();
|
||||
ChannelFactoryCache.Remove(tot);
|
||||
}
|
||||
cf = CreateChannel<T>();
|
||||
cf = CreateChannel<T>(useInstanceName ? InstanceName : null);
|
||||
ChannelFactoryCache[tot] = cf;
|
||||
}
|
||||
return cf.CreateChannel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ITGSService"/> component for the service
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="ITGSService"/> component for the service</returns>
|
||||
public static ITGSService GetService()
|
||||
{
|
||||
return GetComponentImpl<ITGSService>(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directly creates a <see cref="ChannelFactory{TChannel}"/> for <typeparamref name="T"/> without caching. This should be eventually closed by the caller
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component <see langword="interface"/> of the channel to be created</typeparam>
|
||||
/// <returns>The correct <see cref="ChannelFactory{TChannel}"/></returns>
|
||||
/// <exception cref="Exception">Thrown if <typeparamref name="T"/> isn't a valid component <see langword="interface"/></exception>
|
||||
public static ChannelFactory<T> CreateChannel<T>()
|
||||
static ChannelFactory<T> CreateChannel<T>(string instanceName)
|
||||
{
|
||||
var ToT = typeof(T);
|
||||
if (!ValidInterfaces.Contains(ToT) && ToT != typeof(ITGSService))
|
||||
throw new Exception("Invalid type!");
|
||||
var accessPath = instanceName == null ? ServiceInterfaceName : String.Format("{0}/{1}", InstanceInterfaceName, instanceName);
|
||||
|
||||
var InterfaceName = typeof(T).Name;
|
||||
if (HTTPSURL == null)
|
||||
{
|
||||
var res2 = new ChannelFactory<T>(
|
||||
new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", MasterInterfaceName, InterfaceName))); //10 megs
|
||||
new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", accessPath, InterfaceName))); //10 megs
|
||||
res2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
|
||||
return res2;
|
||||
}
|
||||
@@ -243,7 +277,7 @@ namespace TGServiceInterface
|
||||
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
|
||||
binding.Security.Mode = requireAuth ? SecurityMode.TransportWithMessageCredential : SecurityMode.Transport; //do not require auth for a connectivity check
|
||||
binding.Security.Message.ClientCredentialType = requireAuth ? MessageCredentialType.UserName : MessageCredentialType.None;
|
||||
var address = new EndpointAddress(String.Format("https://{0}:{1}/{2}/{3}", HTTPSURL, HTTPSPort, MasterInterfaceName, InterfaceName));
|
||||
var address = new EndpointAddress(String.Format("https://{0}:{1}/{2}/{3}", HTTPSURL, HTTPSPort, accessPath, InterfaceName));
|
||||
var res = new ChannelFactory<T>(binding, address);
|
||||
if (requireAuth)
|
||||
{
|
||||
@@ -261,7 +295,7 @@ namespace TGServiceInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
GetComponent<ITGConnectivity>().VerifyConnection();
|
||||
GetComponentImpl<ITGConnectivity>(false).VerifyConnection();
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -278,7 +312,7 @@ namespace TGServiceInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
GetComponent<ITGSService>().Version();
|
||||
GetService().Version();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
|
||||
+3
-3
@@ -12,6 +12,6 @@ using System.Reflection;
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
//It's impossible to make these a define, don't say I didn't warn you
|
||||
[assembly: AssemblyVersion("3.1.5.1")]
|
||||
[assembly: AssemblyFileVersion("3.1.5.1")]
|
||||
[assembly: AssemblyInformationalVersion("3.1.5.1")]
|
||||
[assembly: AssemblyVersion("3.1.5.2")]
|
||||
[assembly: AssemblyFileVersion("3.1.5.2")]
|
||||
[assembly: AssemblyInformationalVersion("3.1.5.2")]
|
||||
|
||||
Reference in New Issue
Block a user