From 43cdbf9b350e608dce121070d565cdce0aea6b17 Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Mon, 23 Feb 2026 17:54:53 -0500 Subject: [PATCH] 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. --- .../ApplicationHost.cs | 8 +--- Jellyfin.Api/Controllers/StartupController.cs | 39 +++++++++++++----- .../Users/UserManager.cs | 17 ++++---- .../PublishProfiles/FolderProfile.pubxml.user | 2 +- .../netstandard2.0/Jellyfin.CodeAnalysis.dll | Bin 8704 -> 8704 bytes .../netstandard2.0/Jellyfin.CodeAnalysis.pdb | Bin 10220 -> 10220 bytes .../Jellyfin.CodeAnalysis.AssemblyInfo.cs | 2 +- ...yfin.CodeAnalysis.AssemblyInfoInputs.cache | 2 +- .../netstandard2.0/Jellyfin.CodeAnalysis.dll | Bin 8704 -> 8704 bytes .../netstandard2.0/Jellyfin.CodeAnalysis.pdb | Bin 10220 -> 10220 bytes .../HomeSectionConfiguration.cs | 37 +++++++++++++++++ 11 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/HomeSectionConfiguration.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9d1f2f21..0fbf76a1 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -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(); - 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) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index cb80e5e0..f26dc9ed 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -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 GetFirstUser() + public Task 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(null); + } + + return Task.FromResult(new StartupUserDto { Name = user.Username - }; + }); } /// @@ -135,20 +139,33 @@ public class StartupController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task 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); diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index f430496a..bce95403 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -542,24 +542,18 @@ namespace Jellyfin.Server.Implementations.Users /// 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'"); } } diff --git a/Jellyfin.Server/Properties/PublishProfiles/FolderProfile.pubxml.user b/Jellyfin.Server/Properties/PublishProfiles/FolderProfile.pubxml.user index 9a0d94c9..e6ccac0f 100644 --- a/Jellyfin.Server/Properties/PublishProfiles/FolderProfile.pubxml.user +++ b/Jellyfin.Server/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -3,7 +3,7 @@ <_PublishTargetUrl>E:\Program Files\Jellyfin - 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||; + 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||; \ No newline at end of file diff --git a/src/Jellyfin.CodeAnalysis/bin/Debug/netstandard2.0/Jellyfin.CodeAnalysis.dll b/src/Jellyfin.CodeAnalysis/bin/Debug/netstandard2.0/Jellyfin.CodeAnalysis.dll index d7f87adcebe50f15fd64f4cfc58debdf0293a735..2df42d2a8d35880a2ffd999315f8bbdc735b03f7 100644 GIT binary patch delta 235 zcmZp0X>gg)!6H`4vT|cjjHtkr2sweYsm|`r*LF^BHBFS={6lmPlZL6WNs>WwN~)!y zg@v(YVsfIPnQ4-FQi@TMVX}Ejnz@-FP|U(8dGZ~JH7rY;uN;`%BN-szE#6>z;P+FX zSRACOpDGWwH(hw+a4rHeRNi!f1qRs*+m%Mqo Hd?XV9_ti|z delta 235 zcmZp0X>gg)!Q#ai^lxKNjHp24+nt?%10ML#6f=v+e*XRc<{zSam^9KXjE&O_l2cQY zk`oh65|d3+EG*59Oe{^(l2T30P14fLk_-%t&5|bHkyyj>;7HB;$vu(*0unRq)gHWN zcU)#-nk4?uHFEMB$zlblpdd^Ts5+m~(yZUjS$gkBv#!sZ>!oh8_@^;gFc<@28iN5t zGD9j5CIRV0AT$BuWCl})6rc)A26F}@pok?9rvX)@0>#b2>@)^5Am0E8jX|u<%jF}P E0JkVh9smFU diff --git a/src/Jellyfin.CodeAnalysis/bin/Debug/netstandard2.0/Jellyfin.CodeAnalysis.pdb b/src/Jellyfin.CodeAnalysis/bin/Debug/netstandard2.0/Jellyfin.CodeAnalysis.pdb index 9b4b64e5cd464b4debdc3069e17fb415d052514a..dbe9a4331d13c6d95dbf607665b7d995a1a97c90 100644 GIT binary patch delta 558 zcmV+}0@3~KPwY>Sd=yI;fIYzb^;$f0{@Q&c~4zT-uTp!@vLupL)}Q#i)F4UuK9Pd(w$@ zgMoH61ONc7lTB+IF%X9Df&7Odr<4-yuI=G925sV1gxhf58N!0Q3L| z5`t5s^f7zkp5P0`#KB+ARIR4Gv04Wt(b_6{RU@#{F_9nzg zMnKaBe{zU`-ogtz2b#czB*5)RlMTIk&@DVZ%87jkPeT&wuYRAZ zM`|4z;oz@|^c$u`y>`Rpf1d)idRtqE{s;PDB@$U@q0^H)a&BnZt97un^5}-d>Ezw_ zy@9^vE=Mfc9qdQax!WIe%Z}b|cn8v`x{UoXpM4u*zx9IdAH4VH)0wV~rDd~NOqXb| zxj5HzZ6~@hbKA}@8Z@|=nElH>#9*_L!*%tU#nIt#|L^SfJ%8H$8-wNL-#>lG>@0kZ wEL6(gM(ld>0>)uVs_gp(vwa3&AQKh=qa6XGIsv0b0i#_3qk93Ppp)<>`@01H$^ZZW delta 559 zcmV+~0?_^JPwY>Sd=wa%KPKSo2SKJZHDVY3MOom)eD9Hvt|1O10RY$|0RZzO0RW~X z000000RX`y00000000000RYL9UjfrA!XqaD1OR0NCjbQiq6Jm}0|55`b^rqa?g0P+ z1^|L2AOQvdxFjF}2LQXXT>^;$f4_c)xm4(LP&Umtt=wm6Z^se6K~nOE=9V?EjkCt~ z9)Wc=1ONc7lTB+JF${+9f&2%NQ%i}*$xJ>r35D&Jlm-eV3GJcuAbaFlfyXYk+%W&X zcoT*#{a_C(pDe$A^jfx?=6J7hc+fO(gGJ);K!Fkp^=~)H8jowvU}I14e}oxk0hkF8 zBu1}h>3s>pBf%F+nPa%>lvb^7PZp{>)mm5GtX-`!43t44NUh-56O1^4#gYj)*q9Po z0Yhv2$uR+j9-i2HFa$nk0d_M@*7WR=GbEZ#K3ltK{&Kc`xA5?wWDWy7Oj&5W{(Y+M z$$7HEF`^j-otJu>rpr115qu1Ye z1_sANNm#R6*!N`0usi0KJ-yuU7UV?fI(El=3@)Z|;|2XccyHWg+d6HDj799wp7ocD zi_>n=IYVa`opa|#>-OC2UiNc}eX+8?u0FDO+8^%!o!z|W58HpR8ovBb5AO>59=;|P xs%38z4kHBt>oI3l_x%F1eggg)!6H`4vT|cjjHtkr2sweYsm|`r*LF^BHBFS={6lmPlZL6WNs>WwN~)!y zg@v(YVsfIPnQ4-FQi@TMVX}Ejnz@-FP|U(8dGZ~JH7rY;uN;`%BN-szE#6>z;P+FX zSRACOpDGWwH(hw+a4rHeRNi!f1qRs*+m%Mqo Hd?XV9_ti|z delta 235 zcmZp0X>gg)!Q#ai^lxKNjHp24+nt?%10ML#6f=v+e*XRc<{zSam^9KXjE&O_l2cQY zk`oh65|d3+EG*59Oe{^(l2T30P14fLk_-%t&5|bHkyyj>;7HB;$vu(*0unRq)gHWN zcU)#-nk4?uHFEMB$zlblpdd^Ts5+m~(yZUjS$gkBv#!sZ>!oh8_@^;gFc<@28iN5t zGD9j5CIRV0AT$BuWCl})6rc)A26F}@pok?9rvX)@0>#b2>@)^5Am0E8jX|u<%jF}P E0JkVh9smFU diff --git a/src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.pdb b/src/Jellyfin.CodeAnalysis/obj/Debug/netstandard2.0/Jellyfin.CodeAnalysis.pdb index 9b4b64e5cd464b4debdc3069e17fb415d052514a..dbe9a4331d13c6d95dbf607665b7d995a1a97c90 100644 GIT binary patch delta 558 zcmV+}0@3~KPwY>Sd=yI;fIYzb^;$f0{@Q&c~4zT-uTp!@vLupL)}Q#i)F4UuK9Pd(w$@ zgMoH61ONc7lTB+IF%X9Df&7Odr<4-yuI=G925sV1gxhf58N!0Q3L| z5`t5s^f7zkp5P0`#KB+ARIR4Gv04Wt(b_6{RU@#{F_9nzg zMnKaBe{zU`-ogtz2b#czB*5)RlMTIk&@DVZ%87jkPeT&wuYRAZ zM`|4z;oz@|^c$u`y>`Rpf1d)idRtqE{s;PDB@$U@q0^H)a&BnZt97un^5}-d>Ezw_ zy@9^vE=Mfc9qdQax!WIe%Z}b|cn8v`x{UoXpM4u*zx9IdAH4VH)0wV~rDd~NOqXb| zxj5HzZ6~@hbKA}@8Z@|=nElH>#9*_L!*%tU#nIt#|L^SfJ%8H$8-wNL-#>lG>@0kZ wEL6(gM(ld>0>)uVs_gp(vwa3&AQKh=qa6XGIsv0b0i#_3qk93Ppp)<>`@01H$^ZZW delta 559 zcmV+~0?_^JPwY>Sd=wa%KPKSo2SKJZHDVY3MOom)eD9Hvt|1O10RY$|0RZzO0RW~X z000000RX`y00000000000RYL9UjfrA!XqaD1OR0NCjbQiq6Jm}0|55`b^rqa?g0P+ z1^|L2AOQvdxFjF}2LQXXT>^;$f4_c)xm4(LP&Umtt=wm6Z^se6K~nOE=9V?EjkCt~ z9)Wc=1ONc7lTB+JF${+9f&2%NQ%i}*$xJ>r35D&Jlm-eV3GJcuAbaFlfyXYk+%W&X zcoT*#{a_C(pDe$A^jfx?=6J7hc+fO(gGJ);K!Fkp^=~)H8jowvU}I14e}oxk0hkF8 zBu1}h>3s>pBf%F+nPa%>lvb^7PZp{>)mm5GtX-`!43t44NUh-56O1^4#gYj)*q9Po z0Yhv2$uR+j9-i2HFa$nk0d_M@*7WR=GbEZ#K3ltK{&Kc`xA5?wWDWy7Oj&5W{(Y+M z$$7HEF`^j-otJu>rpr115qu1Ye z1_sANNm#R6*!N`0usi0KJ-yuU7UV?fI(El=3@)Z|;|2XccyHWg+d6HDj799wp7ocD zi_>n=IYVa`opa|#>-OC2UiNc}eX+8?u0FDO+8^%!o!z|W58HpR8ovBb5AO>59=;|P xs%38z4kHBt>oI3l_x%F1eg +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace Jellyfin.Database.Implementations.ModelConfiguration +{ + using System; + using Jellyfin.Database.Implementations.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// FluentAPI configuration for the HomeSection entity. + /// + public class HomeSectionConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder 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(); + } + } +}