tgstation-server 5.12.7
The /tg/station 13 server suite
Loading...
Searching...
No Matches
DatabaseSeeder.cs
Go to the documentation of this file.
1using System;
2using System.Linq;
3using System.Threading;
4using System.Threading.Tasks;
5
6using Microsoft.EntityFrameworkCore;
7using Microsoft.Extensions.Logging;
8using Microsoft.Extensions.Options;
9
16
17using Z.EntityFramework.Plus;
18
20{
23 {
28
33
37 readonly ILogger<DatabaseContext> databaseLogger;
38
42 readonly ILogger<DatabaseSeeder> logger;
43
48
53
60 static User SeedSystemUser(IDatabaseContext databaseContext, User tgsUser = null)
61 {
62 bool alreadyExists = tgsUser != null;
63 tgsUser ??= new User()
64 {
65 CreatedAt = DateTimeOffset.UtcNow,
67 };
68
69 // intentionally not giving a group or permissionset
70 tgsUser.Name = User.TgsSystemUserName;
71 tgsUser.PasswordHash = "_"; // This can't be hashed
72 tgsUser.Enabled = false;
73
74 if (!alreadyExists)
75 databaseContext.Users.Add(tgsUser);
76 return tgsUser;
77 }
78
91 IOptions<GeneralConfiguration> generalConfigurationOptions,
92 IOptions<DatabaseConfiguration> databaseConfigurationOptions,
93 ILogger<DatabaseContext> databaseLogger,
94 ILogger<DatabaseSeeder> logger)
95 {
96 this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
97 this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
98 databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
99 generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
100 this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger));
101 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
102 }
103
105 public async Task Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken)
106 {
107 ArgumentNullException.ThrowIfNull(databaseContext);
108
110 {
111 logger.LogCritical("DropDatabase configuration option set! Dropping any existing database...");
112 await databaseContext.Drop(cancellationToken);
113 }
114
115 var wasEmpty = await databaseContext.Migrate(databaseLogger, cancellationToken);
116 if (wasEmpty)
117 {
118 logger.LogInformation("Seeding database...");
119 await SeedDatabase(databaseContext, cancellationToken);
120 }
121 else
122 {
124 {
125 logger.LogWarning("Enabling and resetting admin password due to configuration!");
126 await ResetAdminPassword(databaseContext, cancellationToken);
127 }
128
129 await SanitizeDatabase(databaseContext, cancellationToken);
130 }
131 }
132
134 public Task Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken)
135 {
136 ArgumentNullException.ThrowIfNull(databaseContext);
137 ArgumentNullException.ThrowIfNull(downgradeVersion);
138
139 return databaseContext.SchemaDowngradeForServerVersion(
141 downgradeVersion,
143 cancellationToken);
144 }
145
152 {
153 var admin = new User
154 {
156 {
159 },
160 CreatedAt = DateTimeOffset.UtcNow,
163 Enabled = true,
164 };
166 databaseContext.Users.Add(admin);
167 return admin;
168 }
169
176 async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
177 {
178 var adminUser = SeedAdminUser(databaseContext);
179
180 // Save here because we want admin to have the first DB Id
181 // The system user isn't shown in the API except by references in the admin user and jobs
182 await databaseContext.Save(cancellationToken);
183 var tgsUser = SeedSystemUser(databaseContext);
184 adminUser.CreatedBy = tgsUser;
185
186 await databaseContext.Save(cancellationToken);
187 }
188
195 async Task SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
196 {
197 var admin = await GetAdminUser(databaseContext, cancellationToken);
198 if (admin != null)
199 {
200 if (admin.PermissionSet != null)
201 {
202 // Fix the issue with ulong enums
203 // https://github.com/tgstation/tgstation-server/commit/db341d43b3dab74fe3681f5172ca9bfeaafa6b6d#diff-09f06ec4584665cf89bb77b97f5ccfb9R36-R39
204 // https://github.com/JamesNK/Newtonsoft.Json/issues/2301
205 admin.PermissionSet.AdministrationRights &= RightsHelper.AllRights<AdministrationRights>();
206 admin.PermissionSet.InstanceManagerRights &= RightsHelper.AllRights<InstanceManagerRights>();
207 }
208
209 if (admin.CreatedBy == null)
210 {
211 var tgsUser = await databaseContext
212 .Users
213 .AsQueryable()
214 .Where(x => x.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
215 .FirstOrDefaultAsync(cancellationToken);
216
217 if (tgsUser != null)
218 logger.LogError(
219 "A user named TGS (Canonically) exists but isn't marked as the admin's creator. This may be because it was created manually. This user is going to be adapted to use as the starter of system jobs.");
220
221 tgsUser = SeedSystemUser(databaseContext, tgsUser);
222 admin.CreatedBy = tgsUser;
223 }
224 }
225
227 {
228 // normalize backslashes to forward slashes
229 var allInstances = await databaseContext
230 .Instances
231 .AsQueryable()
232 .ToListAsync(cancellationToken);
233 foreach (var instance in allInstances)
234 instance.Path = instance.Path.Replace('\\', '/');
235 }
236
238 {
239 var ids = await databaseContext
241 .AsQueryable()
242 .Where(x => x.TopicRequestTimeout == 0)
243 .Select(x => x.Id)
244 .ToListAsync(cancellationToken);
245
246 var rowsUpdated = ids.Count;
247 foreach (var id in ids)
248 {
249 var newDDSettings = new DreamDaemonSettings
250 {
251 Id = id,
252 };
253
254 databaseContext.DreamDaemonSettings.Attach(newDDSettings);
255 newDDSettings.TopicRequestTimeout = generalConfiguration.ByondTopicTimeout;
256 }
257
258 if (rowsUpdated > 0)
259 logger.LogInformation(
260 "Updated {count} instances to use database backed BYOND topic timeouts from configuration setting of {timeout}",
261 rowsUpdated,
263 }
264
265 await databaseContext.Save(cancellationToken);
266 }
267
274 async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken)
275 {
276 var admin = await GetAdminUser(databaseContext, cancellationToken);
277 if (admin != null)
278 {
279 admin.Enabled = true;
280
281 // force the user out of any groups
282 if (admin.PermissionSet == null)
283 {
284 admin.Group = null;
285 admin.GroupId = null;
286 admin.PermissionSet = new PermissionSet
287 {
290 };
291 }
292 else
293 admin.PermissionSet.AdministrationRights |= AdministrationRights.WriteUsers;
295 }
296
297 await databaseContext.Save(cancellationToken);
298 }
299
306 async Task<User> GetAdminUser(IDatabaseContext databaseContext, CancellationToken cancellationToken)
307 {
308 var admin = await databaseContext
309 .Users
310 .AsQueryable()
311 .Where(x => x.CanonicalName == User.CanonicalizeName(DefaultCredentials.AdminUserName))
312 .Include(x => x.CreatedBy)
313 .Include(x => x.PermissionSet)
314 .Include(x => x.Group)
315 .FirstOrDefaultAsync(cancellationToken);
316 if (admin == default)
317 SeedAdminUser(databaseContext);
318
319 return admin;
320 }
321 }
322}
Represents initial credentials used by the server.
static readonly string DefaultAdminUserPassword
The default admin password.
static readonly string AdminUserName
The name of the default admin user.
Configuration options for the Database.DatabaseContext.
bool DropDatabase
If the database should be deleted on application startup. Should not be used in production!...
bool ResetAdminPassword
If the admin user should be enabled and have it's password reset.
DatabaseType DatabaseType
The Configuration.DatabaseType to create.
uint ByondTopicTimeout
The timeout in milliseconds for sending and receiving topics to/from DreamDaemon. Note that a single ...
async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Initially seed a given databaseContext .
async Task SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Correct invalid database data caused by previous versions (NOT user fuckery).
readonly ILogger< DatabaseContext > databaseLogger
The ILogger used for IDatabaseContexts.
async Task Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Setup up a given databaseContext . A Task representing the running operation.
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the DatabaseSeeder.
User SeedAdminUser(IDatabaseContext databaseContext)
Add a default admin User to a given databaseContext .
readonly DatabaseConfiguration databaseConfiguration
The DatabaseConfiguration for the DatabaseSeeder.
readonly ICryptographySuite cryptographySuite
The ICryptographySuite for the DatabaseSeeder.
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the DatabaseSeeder.
async Task< User > GetAdminUser(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Get or create the admin User.
DatabaseSeeder(ICryptographySuite cryptographySuite, IPlatformIdentifier platformIdentifier, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< DatabaseConfiguration > databaseConfigurationOptions, ILogger< DatabaseContext > databaseLogger, ILogger< DatabaseSeeder > logger)
Initializes a new instance of the DatabaseSeeder class.
readonly ILogger< DatabaseSeeder > logger
The ILogger for the DatabaseSeeder.
static User SeedSystemUser(IDatabaseContext databaseContext, User tgsUser=null)
Add a default system User to a given databaseContext .
Task Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken)
Migrate a given databaseContext down. A Task representing the running operation.
async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Changes the admin password in IDatabaseContext back to it's default, enables the account,...
const string TgsSystemUserName
Username used when creating jobs automatically.
Definition: User.cs:17
static string CanonicalizeName(string name)
Change a UserName.Name into a CanonicalName.
void Attach(TModel model)
Attach a given model to the the working set.
void Add(TModel model)
Add a given model to the the working set.
Task SchemaDowngradeForServerVersion(ILogger< DatabaseContext > logger, Version targetVersion, DatabaseType currentDatabaseType, CancellationToken cancellationToken)
Attempt to downgrade the schema to the migration used for a given server targetVersion .
Task Drop(CancellationToken cancellationToken)
Attempts to delete all tables and drop the database in use.
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext.
IDatabaseCollection< DreamDaemonSettings > DreamDaemonSettings
The Models.DreamDaemonSettings in the IDatabaseContext.
IDatabaseCollection< User > Users
The Users in the IDatabaseContext.
Task< bool > Migrate(ILogger< DatabaseContext > logger, CancellationToken cancellationToken)
Creates and migrates the IDatabaseContext.
IDatabaseCollection< Instance > Instances
The Instances in the IDatabaseContext.
For initially setting up a database.
Contains various cryptographic functions.
void SetUserPassword(User user, string newPassword, bool newUser)
Sets a User.PasswordHash for a given user .
For identifying the current platform.
bool IsWindows
If the current platform is a Windows platform.
InstanceManagerRights
Rights for managing Models.Instances.
AdministrationRights
Administration rights for the server.