Refactor startup user creation and add HomeSection config

Refactored user initialization to always ensure at least one user exists at startup. If no users are present, a default admin user (admin/jellyfin) is created automatically. Updated StartupController endpoints to handle cases where no user exists. Simplified UserManager.InitializeAsync logic. Added HomeSectionConfiguration for EF Core entity mapping. Includes minor project and assembly info updates.
This commit is contained in:
2026-02-23 17:54:53 -05:00
parent 534b0cde91
commit 43cdbf9b35
11 changed files with 78 additions and 29 deletions
@@ -600,13 +600,9 @@ namespace Emby.Server.Implementations
FindParts();
// Ensure at least one user exists for the startup wizard
// Ensure at least one user exists
var userManager = Resolve<IUserManager>();
if (!ConfigurationManager.Configuration.IsStartupWizardCompleted)
{
Logger.LogInformation("Startup wizard not completed, ensuring initial user exists");
await userManager.InitializeAsync().ConfigureAwait(false);
}
await userManager.InitializeAsync().ConfigureAwait(false);
}
private X509Certificate2 GetCertificate(string path, string password)
+28 -11
View File
@@ -9,6 +9,7 @@ using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.StartupDtos;
using Jellyfin.Data;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -111,15 +112,18 @@ public class StartupController : BaseJellyfinApiController
[HttpGet("User")]
[HttpGet("FirstUser", Name = "GetFirstUser_2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<StartupUserDto> GetFirstUser()
public Task<StartupUserDto?> GetFirstUser()
{
// TODO: Remove this method when startup wizard no longer requires an existing user.
await _userManager.InitializeAsync().ConfigureAwait(false);
var user = _userManager.Users.First();
return new StartupUserDto
var user = _userManager.Users.FirstOrDefault();
if (user is null)
{
return Task.FromResult<StartupUserDto?>(null);
}
return Task.FromResult<StartupUserDto?>(new StartupUserDto
{
Name = user.Username
};
});
}
/// <summary>
@@ -135,20 +139,33 @@ public class StartupController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto)
{
await _userManager.InitializeAsync().ConfigureAwait(false);
var user = _userManager.Users.First();
if (string.IsNullOrWhiteSpace(startupUserDto.Password))
{
return BadRequest("Password must not be empty");
}
if (startupUserDto.Name is not null)
var user = _userManager.Users.FirstOrDefault();
// If no user exists, create the initial admin user
if (user is null)
{
if (string.IsNullOrWhiteSpace(startupUserDto.Name))
{
return BadRequest("Username must not be empty when creating initial user");
}
user = await _userManager.CreateUserAsync(startupUserDto.Name).ConfigureAwait(false);
user.SetPermission(Jellyfin.Database.Implementations.Enums.PermissionKind.IsAdministrator, true);
user.SetPermission(Jellyfin.Database.Implementations.Enums.PermissionKind.EnableContentDeletion, true);
user.SetPermission(Jellyfin.Database.Implementations.Enums.PermissionKind.EnableRemoteControlOfOtherUsers, true);
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
}
else if (startupUserDto.Name is not null)
{
user.Username = startupUserDto.Name;
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
}
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
if (!string.IsNullOrEmpty(startupUserDto.Password))
{
await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false);
@@ -542,24 +542,18 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc />
public async Task InitializeAsync()
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
// If no users exist, create the default admin user
if (_users.Any())
{
return;
}
var defaultName = Environment.UserName;
if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
{
defaultName = "MyJellyfinUser";
}
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
_logger.LogWarning("No users found, creating default admin user with username 'admin'");
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
var newUser = await CreateUserInternalAsync("admin", dbContext).ConfigureAwait(false);
newUser.SetPermission(PermissionKind.IsAdministrator, true);
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
@@ -578,6 +572,11 @@ namespace Jellyfin.Server.Implementations.Users
.ConfigureAwait(false);
_users.Add(reloadedUser.Id, reloadedUser);
// Set the default password "jellyfin"
await ChangePassword(reloadedUser, "jellyfin").ConfigureAwait(false);
_logger.LogWarning("Default admin user created with username 'admin' and password 'jellyfin'");
}
}
@@ -3,7 +3,7 @@
<Project>
<PropertyGroup>
<_PublishTargetUrl>E:\Program Files\Jellyfin</_PublishTargetUrl>
<History>True|2026-02-23T20:36:20.8511403Z||;True|2026-02-23T14:06:04.7110322-05:00||;True|2026-02-23T13:51:39.0686004-05:00||;True|2026-02-23T13:12:08.3890386-05:00||;False|2026-02-23T13:06:12.0188266-05:00||;True|2026-02-23T12:08:36.6307546-05:00||;False|2026-02-23T12:02:39.0223565-05:00||;True|2026-02-23T11:48:21.1980918-05:00||;True|2026-02-23T11:13:01.1928466-05:00||;True|2026-02-23T10:32:13.7989634-05:00||;True|2026-02-23T09:02:53.5227868-05:00||;True|2026-02-23T08:11:05.7403694-05:00||;False|2026-02-23T08:07:46.6244256-05:00||;True|2026-02-23T08:00:05.0454829-05:00||;False|2026-02-23T07:56:44.6461434-05:00||;True|2026-02-23T07:50:44.1212024-05:00||;True|2026-02-23T07:29:19.3226285-05:00||;True|2026-02-22T20:02:08.7802640-05:00||;True|2026-02-22T19:00:49.1033285-05:00||;True|2026-02-22T18:46:50.8399883-05:00||;True|2026-02-22T18:36:34.2983199-05:00||;True|2026-02-22T18:33:05.9495883-05:00||;True|2026-02-22T18:28:09.3531365-05:00||;True|2026-02-22T18:23:19.7767548-05:00||;True|2026-02-22T18:03:49.9702878-05:00||;True|2026-02-22T17:31:43.8027839-05:00||;True|2026-02-22T17:14:22.1722691-05:00||;True|2026-02-22T17:07:09.6937759-05:00||;True|2026-02-22T16:29:37.5643134-05:00||;True|2026-02-22T16:05:05.3412117-05:00||;True|2026-02-22T15:59:39.7645693-05:00||;</History>
<History>True|2026-02-23T22:50:19.2488617Z||;True|2026-02-23T17:40:34.7963081-05:00||;True|2026-02-23T17:30:42.0185573-05:00||;False|2026-02-23T16:44:17.4036787-05:00||;False|2026-02-23T16:10:39.9498056-05:00||;False|2026-02-23T16:06:47.9500254-05:00||;True|2026-02-23T15:36:20.8511403-05:00||;True|2026-02-23T14:06:04.7110322-05:00||;True|2026-02-23T13:51:39.0686004-05:00||;True|2026-02-23T13:12:08.3890386-05:00||;False|2026-02-23T13:06:12.0188266-05:00||;True|2026-02-23T12:08:36.6307546-05:00||;False|2026-02-23T12:02:39.0223565-05:00||;True|2026-02-23T11:48:21.1980918-05:00||;True|2026-02-23T11:13:01.1928466-05:00||;True|2026-02-23T10:32:13.7989634-05:00||;True|2026-02-23T09:02:53.5227868-05:00||;True|2026-02-23T08:11:05.7403694-05:00||;False|2026-02-23T08:07:46.6244256-05:00||;True|2026-02-23T08:00:05.0454829-05:00||;False|2026-02-23T07:56:44.6461434-05:00||;True|2026-02-23T07:50:44.1212024-05:00||;True|2026-02-23T07:29:19.3226285-05:00||;True|2026-02-22T20:02:08.7802640-05:00||;True|2026-02-22T19:00:49.1033285-05:00||;True|2026-02-22T18:46:50.8399883-05:00||;True|2026-02-22T18:36:34.2983199-05:00||;True|2026-02-22T18:33:05.9495883-05:00||;True|2026-02-22T18:28:09.3531365-05:00||;True|2026-02-22T18:23:19.7767548-05:00||;True|2026-02-22T18:03:49.9702878-05:00||;True|2026-02-22T17:31:43.8027839-05:00||;True|2026-02-22T17:14:22.1722691-05:00||;True|2026-02-22T17:07:09.6937759-05:00||;True|2026-02-22T16:29:37.5643134-05:00||;True|2026-02-22T16:05:05.3412117-05:00||;True|2026-02-22T15:59:39.7645693-05:00||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>
@@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.CodeAnalysis")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f833f0ceebcaa4ac5d8972494fbe574ff6b0036b")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+534b0cde918839aca165b7bd2b1c7df761aca82c")]
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.CodeAnalysis")]
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.CodeAnalysis")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
@@ -1 +1 @@
ec635030130ae48b7e69fa591520d48e411bcd6177f6760b1e8b737bdcf85112
16d1d32a5e6e4b7472006b4665d10d7735bf11c7602c8304312d34d2b63afa7f
@@ -0,0 +1,37 @@
// <copyright file="HomeSectionConfiguration.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace Jellyfin.Database.Implementations.ModelConfiguration
{
using System;
using Jellyfin.Database.Implementations.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
/// <summary>
/// FluentAPI configuration for the HomeSection entity.
/// </summary>
public class HomeSectionConfiguration : IEntityTypeConfiguration<HomeSection>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<HomeSection> builder)
{
ArgumentNullException.ThrowIfNull(builder);
// Explicitly set table name to singular to match the migration
builder.ToTable("HomeSection", "displaypreferences");
builder.HasKey(e => e.Id);
builder.Property(e => e.Order)
.IsRequired();
builder.Property(e => e.Type)
.IsRequired();
builder.Property(e => e.DisplayPreferencesId)
.IsRequired();
}
}
}