diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index bb659a9233..d181d5f605 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -4,7 +4,7 @@ Exe netcoreapp2.1 Full - 4.0.1.3 + 4.0.1.4 diff --git a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs index 88bf8ee48f..2b77d018db 100644 --- a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs +++ b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs @@ -7,5 +7,5 @@ using System.Runtime.InteropServices; [assembly: Guid("29927416-3b78-49a7-a560-5ccaa638b6b4")] [assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")] -[assembly: AssemblyVersion("4.0.1.3")] -[assembly: AssemblyFileVersion("4.0.1.3")] +[assembly: AssemblyVersion("4.0.1.4")] +[assembly: AssemblyFileVersion("4.0.1.4")] diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 2d3d915541..ec75cc5b0d 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -4,7 +4,7 @@ netstandard2.0 Full false - 4.0.1.3 + 4.0.1.4 diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index 131ce67962..fe5d6acb15 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -38,12 +38,12 @@ namespace Tgstation.Server.Host.Components.Chat /// /// If this is considered a channel for admin commands /// - public bool IsAdmin { get; set; } + public bool IsAdminChannel { get; set; } /// /// If this is a 1-to-1 chat channel /// - public bool IsPrivate { get; set; } + public bool IsPrivateChannel { get; set; } /// /// For user use diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index b4d68b1139..fc7321af84 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -195,7 +195,7 @@ namespace Tgstation.Server.Host.Components.Chat { var providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First(); var enumerable = mappedChannels.Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == message.User.Channel.RealId); - if (message.User.Channel.IsPrivate) + if (message.User.Channel.IsPrivateChannel) lock (mappedChannels) { if (!provider.Connected) @@ -223,7 +223,7 @@ namespace Tgstation.Server.Host.Components.Chat var mapping = enumerable.First().Value; message.User.Channel.Id = mapping.Channel.Id; message.User.Channel.Tag = mapping.Channel.Tag; - message.User.Channel.IsAdmin = mapping.Channel.IsAdmin; + message.User.Channel.IsAdminChannel = mapping.Channel.IsAdminChannel; } } @@ -236,7 +236,7 @@ namespace Tgstation.Server.Host.Components.Chat var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant(); - if (!addressed && !message.User.Channel.IsPrivate) + if (!addressed && !message.User.Channel.IsPrivateChannel) //no mention return; @@ -302,7 +302,7 @@ namespace Tgstation.Server.Host.Components.Chat return; } - if (commandHandler.AdminOnly && !message.User.Channel.IsAdmin) + if (commandHandler.AdminOnly && !message.User.Channel.IsAdminChannel) { await SendMessage("Use this command in an admin channel!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs index 8694606731..4ff8799ce7 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands string HelpText { get; } /// - /// If the command should only be available to s who's has set + /// If the command should only be available to s who's has set /// bool AdminOnly { get; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 711454887c..50996ed74a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -97,7 +97,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Channel = new Channel { RealId = e.Channel.Id, - IsPrivate = pm, + IsPrivateChannel = pm, ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", FriendlyName = e.Channel.Name //isAdmin and Tag populated by manager @@ -188,10 +188,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return new Channel { RealId = discordChannel.Id, - IsAdmin = channel.IsAdminChannel == true, + IsAdminChannel = channel.IsAdminChannel == true, ConnectionName = discordChannel.Guild.Name, FriendlyName = discordChannel.Name, - IsPrivate = false, + IsPrivateChannel = false, Tag = channel.Tag }; }; diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index cd08c1760e..01fa89697e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -204,7 +204,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ConnectionName = address, FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName, RealId = channelId, - IsPrivate = isPrivate + IsPrivateChannel = isPrivate //isAdmin and Tag populated by manager }, FriendlyName = username, @@ -393,10 +393,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return new Channel { RealId = id.Value, - IsAdmin = x.IsAdminChannel == true, + IsAdminChannel = x.IsAdminChannel == true, ConnectionName = address, FriendlyName = channelIdMap[id.Value], - IsPrivate = false, + IsPrivateChannel = false, Tag = x.Tag }; }).ToList(); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 88a2286d47..b9b558cba2 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -12,7 +12,7 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { /// - sealed class InstanceManager : IInstanceManager, IHostedService, IDisposable + sealed class InstanceManager : IInstanceManager, IRestartHandler, IHostedService, IDisposable { /// /// The for the @@ -49,6 +49,11 @@ namespace Tgstation.Server.Host.Components /// readonly Dictionary instances; + /// + /// Used in to determine if database downgrades must be made + /// + Version downgradeVersion; + /// /// If the has been d /// @@ -62,16 +67,21 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The used to register the as a /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger logger) + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + if (serverControl == null) + throw new ArgumentNullException(nameof(serverControl)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + serverControl.RegisterForRestart(this); + instances = new Dictionary(); } @@ -225,6 +235,17 @@ namespace Tgstation.Server.Host.Components await jobManager.StopAsync(cancellationToken).ConfigureAwait(false); await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false); + + //downgrade the db if necessary + if (downgradeVersion != null) + await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false); + } + + /// + public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) + { + downgradeVersion = updateVersion != null && updateVersion < application.Version ? updateVersion : null; + return Task.CompletedTask; } } } diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs b/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs index 1c49476b35..a8cc05feba 100644 --- a/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs +++ b/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components.Interop sealed class ChatCommand { public string Command { get; set; } - public string Parameters { get; set; } + public string Params { get; set; } public User User { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs b/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs index 7145d35aae..31b4715897 100644 --- a/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs +++ b/src/Tgstation.Server.Host/Components/Interop/CommCommand.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Interop /// /// The dictionary of the /// - public IReadOnlyDictionary Parameters { get; set; } + public IReadOnlyDictionary Parameters { get; set; } /// /// The raw JSON of the diff --git a/src/Tgstation.Server.Host/Components/Interop/CommContext.cs b/src/Tgstation.Server.Host/Components/Interop/CommContext.cs index f9c5208d88..e910ad7ffa 100644 --- a/src/Tgstation.Server.Host/Components/Interop/CommContext.cs +++ b/src/Tgstation.Server.Host/Components/Interop/CommContext.cs @@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Components.Interop { command = new CommCommand { - Parameters = JsonConvert.DeserializeObject>(file), + Parameters = JsonConvert.DeserializeObject>(file), RawJson = file }; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 8cd90b5b72..d8fa568f03 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -221,6 +221,8 @@ namespace Tgstation.Server.Host.Components.Watchdog return result; }; LaunchResult = GetLaunchResult(); + + logger.LogDebug("Created session controller. Primary: {0}, CommsKey: {1}, Port: {2}", IsPrimary, reattachInformation.AccessIdentifier, Port); } /// @@ -316,7 +318,7 @@ namespace Tgstation.Server.Host.Components.Watchdog case Constants.DMCommandNewPort: lock (this) { - if (!query.TryGetValue(Constants.DMParameterData, out var stringPort) || !UInt16.TryParse(stringPort, out var currentPort)) + if (!query.TryGetValue(Constants.DMParameterData, out var stringPortObject) || !UInt16.TryParse(stringPortObject as string, out var currentPort)) { /////UHHHH logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); @@ -354,7 +356,7 @@ namespace Tgstation.Server.Host.Components.Watchdog content = new ErrorMessage { Message = "Invalid API validation request!" }; break; } - if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevel) || !Enum.TryParse(stringMinimumSecurityLevel, out var minimumSecurityLevel)) + if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevelObject) || !Enum.TryParse(stringMinimumSecurityLevelObject as string, out var minimumSecurityLevel)) apiValidationStatus = ApiValidationStatus.BadValidationRequest; else switch (minimumSecurityLevel) @@ -479,6 +481,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return false; } + reattachInformation.Port = port; return true; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 0302b24fce..882a07170e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -453,20 +453,13 @@ namespace Tgstation.Server.Host.Components.Watchdog //servers now swapped - //enable this now - monitorState.ActiveServer.ClosePortOnReboot = true; + //enable this now if inactive server is not still valid + monitorState.ActiveServer.ClosePortOnReboot = restartOnceSwapped; if (!restartOnceSwapped) - { - //inactive server still valid - - //disable this - monitorState.InactiveServer.ClosePortOnReboot = false; - //now try to reopen it on the private port //failing that, just reboot it restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); - } //break either way because any issues past this point would be solved by the reboot if (restartOnceSwapped) @@ -1059,7 +1052,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var commandObject = new ChatCommand { Command = commandName, - Parameters = arguments, + Params = arguments, User = sender }; diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 7578c21c8a..e113f1d999 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -1,7 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -169,5 +173,51 @@ namespace Tgstation.Server.Host.Models /// public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken); + + /// + /// If the MY_ class of migrations should be used instead of the MS_ class + /// + /// if the MY_ class of migrations should be used instead of the MS_ class, otherwise + protected abstract bool UseMySQLMigrations(); + + /// + public async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + if (version < new Version(4, 0)) + throw new ArgumentOutOfRangeException(nameof(version), version, "Not a valid V4 version!"); + + string targetMigration = null; + + //Update this with new migrations as they are made + //Always use the MS class + + //TODO: Uncomment once #816 is merged + /* + if (version < new Version(4, 0, 2)) + targetMigration = nameof(MSReattachCompileJobRequired); + */ + + if (targetMigration == null) + return; + + if (UseMySQLMigrations()) + targetMigration = String.Format(CultureInfo.InvariantCulture, "MY" + targetMigration.Substring(2)); + + //even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻ + var dbServiceProvider = ((IInfrastructure)Database).Instance; + var migrator = dbServiceProvider.GetRequiredService(); + + Logger.LogInformation("Migrating down to version {0}. Target: {1}", version, targetMigration); + try + { + await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogCritical("Failed to migrate! Exception: {0}", e); + } + } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 8fdb8aad74..e7c8e9dcd2 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using System; using System.Threading; using System.Threading.Tasks; @@ -87,5 +88,13 @@ namespace Tgstation.Server.Host.Models /// The for the operation /// A representing the running operation Task Initialize(CancellationToken cancellationToken); + + /// + /// Attempt to downgrade the schema to the migration used for a given server + /// + /// The tgstation-server that the schema should downgrade for + /// The for the operation + /// A representing the running operation + Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index 8c03b31ee0..718fc04d98 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -38,5 +38,8 @@ namespace Tgstation.Server.Host.Models else options.UseMySql(DatabaseConfiguration.ConnectionString); } + + /// + protected override bool UseMySQLMigrations() => true; } } diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index d3d5aa8260..94240566d3 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -26,5 +26,8 @@ namespace Tgstation.Server.Host.Models base.OnConfiguring(options); options.UseSqlServer(DatabaseConfiguration.ConnectionString); } + + /// + protected override bool UseMySQLMigrations() => false; } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index edb20c1711..a52ca184ad 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -3,7 +3,7 @@ netcoreapp2.1 Full - 4.0.1.3 + 4.0.1.4