tgstation-server 6.8.0
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
18{
21 {
26
31
35 readonly ILogger<DatabaseContext> databaseLogger;
36
40 readonly ILogger<DatabaseSeeder> logger;
41
46
51
56
63 static User SeedSystemUser(IDatabaseContext databaseContext, User? tgsUser = null)
64 {
65 bool alreadyExists = tgsUser != null;
66 tgsUser ??= new User()
67 {
68 CreatedAt = DateTimeOffset.UtcNow,
70 };
71
72 // intentionally not giving a group or permissionset
73 tgsUser.Name = User.TgsSystemUserName;
74 tgsUser.PasswordHash = "_"; // This can't be hashed
75 tgsUser.Enabled = false;
76
77 if (!alreadyExists)
78 databaseContext.Users.Add(tgsUser);
79 return tgsUser;
80 }
81
95 IOptions<GeneralConfiguration> generalConfigurationOptions,
96 IOptions<DatabaseConfiguration> databaseConfigurationOptions,
97 IOptions<SwarmConfiguration> swarmConfigurationOptions,
98 ILogger<DatabaseContext> databaseLogger,
99 ILogger<DatabaseSeeder> logger)
100 {
101 this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
102 this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
103 databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
104 generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
105 swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
106 this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger));
107 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
108 }
109
111 public async ValueTask Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken)
112 {
113 ArgumentNullException.ThrowIfNull(databaseContext);
114
116 {
117 logger.LogCritical("DropDatabase configuration option set! Dropping any existing database...");
118 await databaseContext.Drop(cancellationToken);
119 }
120
121 var wasEmpty = await databaseContext.Migrate(databaseLogger, cancellationToken);
122 if (wasEmpty)
123 {
124 logger.LogInformation("Seeding database...");
125 await SeedDatabase(databaseContext, cancellationToken);
126 }
127 else
128 {
130 {
131 logger.LogWarning("Enabling and resetting admin password due to configuration!");
132 await ResetAdminPassword(databaseContext, cancellationToken);
133 }
134
135 await SanitizeDatabase(databaseContext, cancellationToken);
136 }
137 }
138
140 public ValueTask Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken)
141 {
142 ArgumentNullException.ThrowIfNull(databaseContext);
143 ArgumentNullException.ThrowIfNull(downgradeVersion);
144
145 return databaseContext.SchemaDowngradeForServerVersion(
147 downgradeVersion,
149 cancellationToken);
150 }
151
158 {
159 var admin = new User
160 {
162 {
165 },
166 CreatedAt = DateTimeOffset.UtcNow,
169 Enabled = true,
170 };
172 databaseContext.Users.Add(admin);
173 return admin;
174 }
175
182 async ValueTask SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
183 {
184 var adminUser = SeedAdminUser(databaseContext);
185
186 // Save here because we want admin to have the first DB Id
187 // The system user isn't shown in the API except by references in the admin user and jobs
188 await databaseContext.Save(cancellationToken);
189 var tgsUser = SeedSystemUser(databaseContext);
190 adminUser.CreatedBy = tgsUser;
191
192 await databaseContext.Save(cancellationToken);
193 }
194
201 async ValueTask SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
202 {
203 var admin = await GetAdminUser(databaseContext, cancellationToken);
204 if (admin != null)
205 {
206 if (admin.PermissionSet != null)
207 {
208 // Fix the issue with ulong enums
209 // https://github.com/tgstation/tgstation-server/commit/db341d43b3dab74fe3681f5172ca9bfeaafa6b6d#diff-09f06ec4584665cf89bb77b97f5ccfb9R36-R39
210 // https://github.com/JamesNK/Newtonsoft.Json/issues/2301
211 admin.PermissionSet.AdministrationRights &= RightsHelper.AllRights<AdministrationRights>();
212 admin.PermissionSet.InstanceManagerRights &= RightsHelper.AllRights<InstanceManagerRights>();
213 }
214
215 if (admin.CreatedBy == null)
216 {
217 var tgsUser = await databaseContext
218 .Users
219 .AsQueryable()
220 .Where(x => x.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
221 .FirstOrDefaultAsync(cancellationToken);
222
223 if (tgsUser != null)
224 logger.LogError(
225 "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.");
226
227 tgsUser = SeedSystemUser(databaseContext, tgsUser);
228 admin.CreatedBy = tgsUser;
229 }
230 }
231
232 // normalize backslashes to forward slashes
233 var allInstances = await databaseContext
234 .Instances
235 .AsQueryable()
236 .Where(instance => instance.SwarmIdentifer == swarmConfiguration.Identifier)
237 .ToListAsync(cancellationToken);
238 foreach (var instance in allInstances)
239 instance.Path = platformIdentifier.NormalizePath(instance.Path!.Replace('\\', '/'));
240
242 {
243 var ids = await databaseContext
244 .DreamDaemonSettings
245 .AsQueryable()
246 .Where(x => x.TopicRequestTimeout == 0)
247 .Select(x => x.Id)
248 .ToListAsync(cancellationToken);
249
250 var rowsUpdated = ids.Count;
251 foreach (var id in ids)
252 {
253 var newDDSettings = new DreamDaemonSettings
254 {
255 Id = id,
256 };
257
258 databaseContext.DreamDaemonSettings.Attach(newDDSettings);
259 newDDSettings.TopicRequestTimeout = generalConfiguration.ByondTopicTimeout;
260 }
261
262 if (rowsUpdated > 0)
263 logger.LogInformation(
264 "Updated {count} instances to use database backed BYOND topic timeouts from configuration setting of {timeout}",
265 rowsUpdated,
267 }
268
269 await databaseContext.Save(cancellationToken);
270 }
271
278 async ValueTask ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken)
279 {
280 var admin = await GetAdminUser(databaseContext, cancellationToken);
281 if (admin != null)
282 {
283 admin.Enabled = true;
284
285 // force the user out of any groups
286 if (admin.PermissionSet == null)
287 {
288 admin.Group = null;
289 admin.GroupId = null;
290 admin.PermissionSet = new PermissionSet
291 {
294 };
295 }
296 else
297 admin.PermissionSet.AdministrationRights |= AdministrationRights.WriteUsers;
299 }
300
301 await databaseContext.Save(cancellationToken);
302 }
303
310 async ValueTask<User?> GetAdminUser(IDatabaseContext databaseContext, CancellationToken cancellationToken)
311 {
312 var admin = await databaseContext
313 .Users
314 .AsQueryable()
315 .Where(x => x.CanonicalName == User.CanonicalizeName(DefaultCredentials.AdminUserName))
316 .Include(x => x.CreatedBy)
317 .Include(x => x.PermissionSet)
318 .Include(x => x.Group)
319 .FirstOrDefaultAsync(cancellationToken);
320 if (admin == default)
321 SeedAdminUser(databaseContext);
322
323 return admin;
324 }
325 }
326}
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.
string? Identifier
The server's identifier.
Definition: SwarmServer.cs:26
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 ...
Configuration for the server swarm system.
readonly ILogger< DatabaseContext > databaseLogger
The ILogger used for IDatabaseContexts.
async ValueTask< User?> GetAdminUser(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Get or create the admin User.
async ValueTask ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Changes the admin password in IDatabaseContext back to it's default, enables the account,...
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the DatabaseSeeder.
User SeedAdminUser(IDatabaseContext databaseContext)
Add a default admin User to a given databaseContext .
async ValueTask SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Initially seed a given databaseContext .
DatabaseSeeder(ICryptographySuite cryptographySuite, IPlatformIdentifier platformIdentifier, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< DatabaseConfiguration > databaseConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, ILogger< DatabaseContext > databaseLogger, ILogger< DatabaseSeeder > logger)
Initializes a new instance of the DatabaseSeeder class.
readonly DatabaseConfiguration databaseConfiguration
The DatabaseConfiguration for the DatabaseSeeder.
readonly ICryptographySuite cryptographySuite
The ICryptographySuite for the DatabaseSeeder.
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the DatabaseSeeder.
ValueTask Downgrade(IDatabaseContext databaseContext, Version downgradeVersion, CancellationToken cancellationToken)
Migrate a given databaseContext down. A ValueTask representing the running operation.
readonly ILogger< DatabaseSeeder > logger
The ILogger for the DatabaseSeeder.
async ValueTask SanitizeDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Correct invalid database data caused by previous versions (NOT user fuckery).
async ValueTask Initialize(IDatabaseContext databaseContext, CancellationToken cancellationToken)
Setup up a given databaseContext . A ValueTask representing the running operation.
static User SeedSystemUser(IDatabaseContext databaseContext, User? tgsUser=null)
Add a default system User to a given databaseContext .
readonly SwarmConfiguration swarmConfiguration
The SwarmConfiguration for the DatabaseSeeder.
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 Add(TModel model)
Add a given model to the the working set.
ValueTask< bool > Migrate(ILogger< DatabaseContext > logger, CancellationToken cancellationToken)
Creates and migrates the IDatabaseContext.
ValueTask 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< User > Users
The Users in 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.
string NormalizePath(string path)
Normalize a path for consistency.
InstanceManagerRights
Rights for managing Models.Instances.
AdministrationRights
Administration rights for the server.