- 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
13 KiB
How startup.json is Loaded and Used on Startup
YES - The code DOES check startup.json on startup! ✅
Here's the complete flow:
Startup Flow Diagram
┌─────────────────────────────────────────────┐
│ 1. Jellyfin Starts │
│ Program.Main() → StartApp() │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 2. CreateApplicationPaths() │
│ (in StartupHelpers.cs) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 3. LoadStartupConfiguration() │
│ Searches for startup.json in: │
│ • Current directory │
│ • AppContext.BaseDirectory │
│ • AppContext.BaseDirectory/config │
└──────────────────┬──────────────────────────┘
│
┌─────────┴─────────┐
│ │
Found? Yes No
│ │
▼ ▼
┌──────────────────┐ ┌───────────────────────┐
│ 4a. Load JSON │ │ 4b. Create Default │
│ Return config│ │ (OS-specific) │
└────────┬─────────┘ └───────────┬───────────┘
│ │
└────────────┬───────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 5. Resolve Paths (Priority Order): │
│ 1. Command-line args │
│ 2. Environment variables │
│ 3. startup.json values ← HERE! │
│ 4. Built-in defaults │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 6. Create ServerApplicationPaths object │
│ with resolved paths │
└─────────────────────────────────────────────┘
Code Flow Details
Step 1: Startup Entry Point
File: Jellyfin.Server/Program.cs
// Line 88-90
private static async Task StartApp(StartupOptions options)
{
ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options);
// ...
}
Step 2: Load Configuration
File: Jellyfin.Server/Helpers/StartupHelpers.cs
// Line 222-223
public static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{
// Try to load startup configuration from file
var startupConfig = LoadStartupConfiguration();
// ...
}
Step 3: Search for startup.json
File: Jellyfin.Server/Helpers/StartupHelpers.cs
// Lines 78-112
private static IConfigurationRoot? LoadStartupConfiguration()
{
const string ConfigFileName = "startup.json";
// Search locations in priority order
var searchPaths = new[]
{
Path.Combine(Directory.GetCurrentDirectory(), ConfigFileName),
Path.Combine(AppContext.BaseDirectory, ConfigFileName),
Path.Combine(AppContext.BaseDirectory, "config", ConfigFileName)
};
foreach (var configPath in searchPaths)
{
if (File.Exists(configPath))
{
try
{
var config = new ConfigurationBuilder()
.AddJsonFile(configPath, optional: false, reloadOnChange: false)
.Build();
Console.WriteLine($"Loaded startup configuration from: {configPath}");
return config;
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Failed to load startup configuration from {configPath}: {ex.Message}");
}
}
}
// No configuration file found - create a default one
CreateDefaultStartupConfiguration();
return null;
}
Step 4: Use startup.json Values
File: Jellyfin.Server/Helpers/StartupHelpers.cs
Each path is resolved with this priority chain:
// DataDir example (lines 230-234)
var dataDir = options.DataDir // 1. Command-line
?? Environment.GetEnvironmentVariable("JELLYFIN_DATA_DIR") // 2. Environment
?? startupConfig?.GetValue<string>("Paths:DataDir") // 3. startup.json ← HERE!
?? Path.Join( // 4. Built-in default
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"jellyfin");
// ConfigDir (lines 236-248)
var configDir = options.ConfigDir
?? Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR")
?? startupConfig?.GetValue<string>("Paths:ConfigDir"); // ← startup.json checked!
// CacheDir (lines 250-260)
var cacheDir = options.CacheDir
?? Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR")
?? startupConfig?.GetValue<string>("Paths:CacheDir"); // ← startup.json checked!
// LogDir (lines 289-293)
var logDir = options.LogDir
?? Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR")
?? startupConfig?.GetValue<string>("Paths:LogDir"); // ← startup.json checked!
// TempDir (lines 295-301)
var tempDir = options.TempDir
?? Environment.GetEnvironmentVariable("JELLYFIN_TEMP_DIR")
?? startupConfig?.GetValue<string>("Paths:TempDir"); // ← startup.json checked!
// WebDir (lines 262-287)
var webDir = options.WebDir
?? Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR")
?? startupConfig?.GetValue<string>("Paths:WebDir"); // ← startup.json checked!
Search Locations (In Order)
When looking for startup.json, the code checks these locations:
-
Current working directory
- Example:
E:\Projects\pgsql-jellyfin\startup.json - This is where you run
jellyfinfrom
- Example:
-
Application base directory
- Example:
E:\Projects\pgsql-jellyfin\Jellyfin.Server\bin\Release\net11.0\startup.json - Where the jellyfin.exe/dll is located
- Example:
-
Config subdirectory
- Example:
E:\Projects\pgsql-jellyfin\Jellyfin.Server\bin\Release\net11.0\config\startup.json - Allows organizing config files
- Example:
Priority Order for Path Resolution
For each path (DataDir, ConfigDir, etc.), the code checks in this order:
┌──────────────────────────────────────┐
│ 1. Command-line argument │ Highest Priority
│ --datadir /custom/path │ (Always wins)
└──────────────────┬───────────────────┘
│ If not provided
▼
┌──────────────────────────────────────┐
│ 2. Environment variable │
│ JELLYFIN_DATA_DIR=/custom/path │
└──────────────────┬───────────────────┘
│ If not set
▼
┌──────────────────────────────────────┐
│ 3. startup.json value │ ← YOUR QUESTION!
│ "DataDir": "/custom/path" │ YES, IT CHECKS HERE!
└──────────────────┬───────────────────┘
│ If null or missing
▼
┌──────────────────────────────────────┐
│ 4. Built-in OS-specific default │ Lowest Priority
│ (code determines based on OS) │ (Fallback)
└──────────────────────────────────────┘
Console Output
When startup.json is Found
Loaded startup configuration from: E:\Projects\pgsql-jellyfin\startup.json
When startup.json is Not Found
Created default startup configuration at: E:\Projects\pgsql-jellyfin\startup.json
Using Windows defaults - using C:/ProgramData/jellyfin
You can customize this file to set different paths for Jellyfin.
Real-World Examples
Example 1: Using startup.json
Your startup.json:
{
"Paths": {
"DataDir": "D:/Media/Jellyfin/Data",
"ConfigDir": "D:/Media/Jellyfin/Config",
"CacheDir": "E:/Cache/Jellyfin",
"LogDir": "D:/Logs/Jellyfin"
}
}
What happens on startup:
// For DataDir:
options.DataDir // null (not provided)
?? Environment.GetEnvironmentVariable(...) // null (not set)
?? startupConfig?.GetValue("Paths:DataDir") // "D:/Media/Jellyfin/Data" ← USED!
// Result: DataDir = "D:/Media/Jellyfin/Data"
Console output:
Loaded startup configuration from: E:\Projects\pgsql-jellyfin\startup.json
[INF] Data directory: D:/Media/Jellyfin/Data
[INF] Config directory: D:/Media/Jellyfin/Config
[INF] Cache directory: E:/Cache/Jellyfin
[INF] Log directory: D:/Logs/Jellyfin
Example 2: Override with Command-Line
Your startup.json:
{
"Paths": {
"DataDir": "D:/Media/Jellyfin/Data"
}
}
Command:
jellyfin --datadir "F:/CustomData"
What happens:
options.DataDir // "F:/CustomData" ← USED! (highest priority)
?? Environment.GetEnvironmentVariable(...) // (not checked)
?? startupConfig?.GetValue("Paths:DataDir") // (not checked)
// Result: DataDir = "F:/CustomData"
Example 3: Override with Environment Variable
Your startup.json:
{
"Paths": {
"DataDir": "D:/Media/Jellyfin/Data"
}
}
Environment:
$env:JELLYFIN_DATA_DIR = "G:/EnvData"
jellyfin
What happens:
options.DataDir // null
?? Environment.GetEnvironmentVariable(...) // "G:/EnvData" ← USED!
?? startupConfig?.GetValue("Paths:DataDir") // (not checked)
// Result: DataDir = "G:/EnvData"
Testing the Behavior
Test 1: Verify startup.json is Loaded
# Create a test startup.json
@"
{
"Paths": {
"DataDir": "E:/TEST_JELLYFIN_DATA",
"LogDir": "E:/TEST_JELLYFIN_LOGS"
}
}
"@ | Out-File startup.json
# Run Jellyfin (it will create these directories and use them)
.\jellyfin.exe
# Check console output - should see:
# "Loaded startup configuration from: E:\Projects\pgsql-jellyfin\startup.json"
Test 2: Verify Priority Order
# Set in startup.json
echo '{"Paths":{"DataDir":"E:/FROM_JSON"}}' > startup.json
# Override with environment variable
$env:JELLYFIN_DATA_DIR = "E:/FROM_ENV"
# Run Jellyfin
.\jellyfin.exe
# Result: Will use E:/FROM_ENV (environment wins over startup.json)
Test 3: Verify Search Locations
# Put startup.json in current directory
echo '{"Paths":{"DataDir":"E:/CURRENT_DIR"}}' > startup.json
# Also put one in base directory
echo '{"Paths":{"DataDir":"E:/BASE_DIR"}}' > .\bin\Release\net11.0\startup.json
# Run from current directory
.\jellyfin.exe
# Result: Uses E:/CURRENT_DIR (current directory checked first)
Summary
✅ YES - startup.json IS Checked on Startup!
When: During application initialization in CreateApplicationPaths()
Where: Searches 3 locations (current dir, base dir, config subdir)
Priority: 3rd in the resolution chain:
- Command-line args (highest)
- Environment variables
- startup.json ← HERE
- Built-in defaults (lowest)
What's Read: All Paths:* values:
Paths:DataDirPaths:ConfigDirPaths:CacheDirPaths:LogDirPaths:TempDirPaths:WebDir
Confirmation: Console output shows "Loaded startup configuration from: [path]" when found
Code References
| Function | File | Purpose |
|---|---|---|
StartApp() |
Program.cs:88 | Entry point |
CreateApplicationPaths() |
StartupHelpers.cs:222 | Path resolution |
LoadStartupConfiguration() |
StartupHelpers.cs:78 | Loads startup.json |
CreateDefaultStartupConfiguration() |
StartupHelpers.cs:116 | Creates default if missing |
Verified: The code absolutely checks and uses startup.json on every startup! ✅