Files
pgsql-jellyfin/docs/STARTUP_JSON_VERIFICATION.md
T
wjones 8f860a8ec3 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
2026-02-26 14:21:26 -05:00

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:

  1. Current working directory

    • Example: E:\Projects\pgsql-jellyfin\startup.json
    • This is where you run jellyfin from
  2. Application base directory

    • Example: E:\Projects\pgsql-jellyfin\Jellyfin.Server\bin\Release\net11.0\startup.json
    • Where the jellyfin.exe/dll is located
  3. Config subdirectory

    • Example: E:\Projects\pgsql-jellyfin\Jellyfin.Server\bin\Release\net11.0\config\startup.json
    • Allows organizing config files

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:

  1. Command-line args (highest)
  2. Environment variables
  3. startup.json ← HERE
  4. Built-in defaults (lowest)

What's Read: All Paths:* values:

  • Paths:DataDir
  • Paths:ConfigDir
  • Paths:CacheDir
  • Paths:LogDir
  • Paths:TempDir
  • Paths: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!