Centralize build output and generate OS-specific startup.json
- All DLLs now output to lib\[Configuration]\[TargetFramework]\ at repo root (see Directory.Build.props) - .gitignore updated to exclude /lib/ - On first run, startup.json is auto-generated with OS-appropriate default paths (Windows, Linux, macOS, or portable) - Removes null/example config; generated config is immediately usable and clearly documented - Extensive new documentation: build output, startup.json logic, visual guides, and code proofs - Publish profile now deletes existing files for clean deploys - No breaking changes: existing startup.json files are preserved - Improves first-run UX, deployment, and cross-platform consistency
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# Fix: Empty database.xml on Startup
|
||||
|
||||
## Problem
|
||||
The `database.xml` file was being created empty on startup, causing Jellyfin to fail or not properly initialize the database configuration.
|
||||
|
||||
## Root Cause
|
||||
|
||||
### Issue 1: `required` Keyword Incompatibility
|
||||
The `DatabaseConfigurationOptions` class had the `DatabaseType` property marked with the C# 11 `required` keyword:
|
||||
|
||||
```csharp
|
||||
public required string DatabaseType { get; set; }
|
||||
```
|
||||
|
||||
**Problem:** The `System.Xml.Serialization.XmlSerializer` used by Jellyfin doesn't support the `required` keyword. When trying to serialize/deserialize:
|
||||
- XML serializer couldn't create instances with required properties
|
||||
- Serialization would fail silently or create empty files
|
||||
- Deserialization from empty files would fail
|
||||
|
||||
### Issue 2: Dictionary<string, T> Not XML-Serializable
|
||||
The `PresetConfigurations` property used a `Dictionary<string, PresetDatabaseConfiguration>`:
|
||||
|
||||
```csharp
|
||||
public Dictionary<string, PresetDatabaseConfiguration>? PresetConfigurations { get; set; }
|
||||
```
|
||||
|
||||
**Problem:** `Dictionary<TKey, TValue>` is not directly XML-serializable by `XmlSerializer` without custom serialization logic.
|
||||
|
||||
## Solution Applied
|
||||
|
||||
### Fix 1: Removed `required` Keyword
|
||||
Changed `DatabaseType` to nullable and added default value in constructor:
|
||||
|
||||
**Before:**
|
||||
```csharp
|
||||
public required string DatabaseType { get; set; }
|
||||
```
|
||||
|
||||
**After:**
|
||||
```csharp
|
||||
public DatabaseConfigurationOptions()
|
||||
{
|
||||
DatabaseType = "Jellyfin-SQLite";
|
||||
LockingBehavior = DatabaseLockingBehaviorTypes.NoLock;
|
||||
}
|
||||
|
||||
public string? DatabaseType { get; set; }
|
||||
```
|
||||
|
||||
### Fix 2: Replaced Dictionary with XML-Serializable List
|
||||
Changed from `Dictionary<string, T>` to `List<PresetConfigurationItem>` with XML attributes:
|
||||
|
||||
**Before:**
|
||||
```csharp
|
||||
public Dictionary<string, PresetDatabaseConfiguration>? PresetConfigurations { get; set; }
|
||||
```
|
||||
|
||||
**After:**
|
||||
```csharp
|
||||
[XmlArray("PresetConfigurations")]
|
||||
[XmlArrayItem("PresetConfiguration")]
|
||||
public List<PresetConfigurationItem>? PresetConfigurations { get; set; }
|
||||
```
|
||||
|
||||
**New supporting class:**
|
||||
```csharp
|
||||
public class PresetConfigurationItem
|
||||
{
|
||||
[XmlElement("key")]
|
||||
public string? Key { get; set; }
|
||||
|
||||
[XmlElement("value")]
|
||||
public PresetDatabaseConfiguration? Value { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### Fix 3: Added Null Check Safety
|
||||
Added validation to ensure DatabaseType is never null when reading configuration:
|
||||
|
||||
```csharp
|
||||
// Ensure DatabaseType is set (handle corrupted or empty configuration files)
|
||||
if (string.IsNullOrWhiteSpace(efCoreConfiguration.DatabaseType))
|
||||
{
|
||||
efCoreConfiguration.DatabaseType = "Jellyfin-SQLite";
|
||||
configurationManager.SaveConfiguration("database", efCoreConfiguration);
|
||||
}
|
||||
```
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **src/Jellyfin.Database/Jellyfin.Database.Implementations/DbConfiguration/DatabaseConfigurationOptions.cs**
|
||||
- Removed `required` keyword
|
||||
- Added default constructor with defaults
|
||||
- Changed `Dictionary` to `List` with XML attributes
|
||||
|
||||
2. **src/Jellyfin.Database/Jellyfin.Database.Implementations/DbConfiguration/PresetConfigurationItem.cs** (NEW)
|
||||
- Created new class for XML-serializable key-value pairs
|
||||
- Added proper XML attributes
|
||||
|
||||
3. **Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs**
|
||||
- Updated preset creation to use `List<PresetConfigurationItem>`
|
||||
- Added null check for DatabaseType
|
||||
|
||||
## XML Output
|
||||
|
||||
The database.xml file now properly serializes:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<DatabaseConfigurationOptions>
|
||||
<DatabaseType>Jellyfin-SQLite</DatabaseType>
|
||||
<LockingBehavior>NoLock</LockingBehavior>
|
||||
<PresetConfigurations>
|
||||
<PresetConfiguration>
|
||||
<key>SQLite</key>
|
||||
<value>
|
||||
<DatabaseType>Jellyfin-SQLite</DatabaseType>
|
||||
<LockingBehavior>NoLock</LockingBehavior>
|
||||
<Description>Default SQLite database configuration. Database stored in data directory.</Description>
|
||||
</value>
|
||||
</PresetConfiguration>
|
||||
<PresetConfiguration>
|
||||
<key>PostgreSQL-Local</key>
|
||||
<value>
|
||||
<DatabaseType>Jellyfin-PostgreSQL</DatabaseType>
|
||||
<LockingBehavior>NoLock</LockingBehavior>
|
||||
<Description>PostgreSQL database on localhost. Update CustomProviderOptions with your database credentials.</Description>
|
||||
</value>
|
||||
</PresetConfiguration>
|
||||
</PresetConfigurations>
|
||||
</DatabaseConfigurationOptions>
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
1. **Delete existing database.xml** (if any)
|
||||
2. **Start Jellyfin**
|
||||
3. **Verify** `database.xml` is created in config directory
|
||||
4. **Check** file contains proper XML structure (not empty)
|
||||
5. **Confirm** Jellyfin uses SQLite by default
|
||||
6. **Test switching** to PostgreSQL by editing DatabaseType
|
||||
|
||||
## Verification Commands
|
||||
|
||||
### Windows (PowerShell)
|
||||
```powershell
|
||||
# Check if file exists and has content
|
||||
Get-Content "$env:LOCALAPPDATA\jellyfin\config\database.xml"
|
||||
|
||||
# Check file size (should be > 0 bytes)
|
||||
(Get-Item "$env:LOCALAPPDATA\jellyfin\config\database.xml").Length
|
||||
```
|
||||
|
||||
### Linux
|
||||
```bash
|
||||
# Check if file exists and has content
|
||||
cat ~/.config/jellyfin/database.xml
|
||||
|
||||
# Check file size
|
||||
ls -lh ~/.config/jellyfin/database.xml
|
||||
```
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
✅ **database.xml created** on first startup
|
||||
✅ **Contains proper XML** with DatabaseType, LockingBehavior, and PresetConfigurations
|
||||
✅ **Defaults to SQLite** if no configuration exists
|
||||
✅ **Can be edited** to switch between databases
|
||||
✅ **Presets visible** as templates in the file
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
✅ **Existing configurations** will be loaded correctly
|
||||
✅ **Old database.xml files** without presets will still work
|
||||
✅ **No data migration** needed
|
||||
✅ **Graceful degradation** - if file is corrupted, defaults to SQLite
|
||||
|
||||
## Related Issues
|
||||
|
||||
This fix resolves:
|
||||
- Empty database.xml files on startup
|
||||
- Configuration not persisting
|
||||
- Database provider selection not working
|
||||
- Preset configurations not saving
|
||||
|
||||
## Prevention
|
||||
|
||||
To prevent similar issues in future:
|
||||
1. ✅ Avoid using `required` keyword with XML-serialized classes
|
||||
2. ✅ Use XML-serializable types (avoid Dictionary, use List instead)
|
||||
3. ✅ Add default constructors to serialized classes
|
||||
4. ✅ Test XML serialization/deserialization in unit tests
|
||||
5. ✅ Add null checks when reading configuration
|
||||
|
||||
## See Also
|
||||
|
||||
- [DATABASE_CONFIGURATION_GUIDE.md](DATABASE_CONFIGURATION_GUIDE.md) - How to configure database
|
||||
- [DATABASE_PRESETS_FEATURE.md](DATABASE_PRESETS_FEATURE.md) - Preset configurations feature
|
||||
- [database.xml.example](database.xml.example) - Example configuration file
|
||||
Reference in New Issue
Block a user