Files
pgsql-jellyfin/docs/CODE_PROOF_STARTUP_JSON.md
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

9.2 KiB

Code Proof: startup.json IS Checked on Startup

Direct Evidence from Source Code

Evidence 1: startup.json is Loaded

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Line: 222-224

public static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{
    // Try to load startup configuration from file
    var startupConfig = LoadStartupConfiguration();  // ← LOADS startup.json HERE!
    
    // Then uses it below...
}

Evidence 2: Search and Load Logic

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Lines: 78-112

private static IConfigurationRoot? LoadStartupConfiguration()
{
    const string ConfigFileName = "startup.json";  // ← Exact filename

    // 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))  // ← CHECKS if file exists
        {
            try
            {
                var config = new ConfigurationBuilder()
                    .AddJsonFile(configPath, optional: false, reloadOnChange: false)  // ← READS the JSON
                    .Build();

                Console.WriteLine($"Loaded startup configuration from: {configPath}");  // ← CONFIRMS loaded
                return config;  // ← RETURNS the loaded config
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Warning: Failed to load startup configuration from {configPath}: {ex.Message}");
            }
        }
    }

    // If not found, creates a default one
    CreateDefaultStartupConfiguration();
    return null;
}

Evidence 3: DataDir Uses startup.json

File: Jellyfin.Server/Helpers/StartupHelpers.cs
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
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "jellyfin");

What this means:

  • If options.DataDir is null (no command-line arg)
  • AND JELLYFIN_DATA_DIR env var is not set
  • THEN it reads Paths:DataDir from startup.json ← PROOF!

Evidence 4: ConfigDir Uses startup.json

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Lines: 236-238

var configDir = options.ConfigDir
    ?? Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR")
    ?? startupConfig?.GetValue<string>("Paths:ConfigDir");  // ← READS from startup.json

Evidence 5: CacheDir Uses startup.json

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Lines: 250-252

var cacheDir = options.CacheDir
    ?? Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR")
    ?? startupConfig?.GetValue<string>("Paths:CacheDir");  // ← READS from startup.json

Evidence 6: LogDir Uses startup.json

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Lines: 289-291

var logDir = options.LogDir
    ?? Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR")
    ?? startupConfig?.GetValue<string>("Paths:LogDir");  // ← READS from startup.json

Evidence 7: TempDir Uses startup.json

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Lines: 295-297

var tempDir = options.TempDir
    ?? Environment.GetEnvironmentVariable("JELLYFIN_TEMP_DIR")
    ?? startupConfig?.GetValue<string>("Paths:TempDir");  // ← READS from startup.json

Evidence 8: WebDir Uses startup.json

File: Jellyfin.Server/Helpers/StartupHelpers.cs
Lines: 262-264

var webDir = options.WebDir
    ?? Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR")
    ?? startupConfig?.GetValue<string>("Paths:WebDir");  // ← READS from startup.json

Logical Flow Proof

Program.Main(args)
    ↓
StartApp(options)
    ↓
CreateApplicationPaths(options)  ← Entry point
    ↓
LoadStartupConfiguration()  ← Searches for startup.json
    ↓
    ├─ File.Exists(path1) ?  ← Checks current directory
    ├─ File.Exists(path2) ?  ← Checks base directory
    └─ File.Exists(path3) ?  ← Checks config subdirectory
    ↓
    [If found]
    AddJsonFile(configPath)  ← Reads the JSON
    ↓
    Console.WriteLine("Loaded startup configuration from: ...")  ← Logs success
    ↓
    return config  ← Returns IConfigurationRoot
    ↓
For each path (DataDir, ConfigDir, CacheDir, LogDir, TempDir, WebDir):
    ↓
    Check: startupConfig?.GetValue<string>("Paths:PathName")  ← USES the loaded config!
    ↓
If value exists in startup.json → USE IT
If null → Fall back to next priority level

Console Output Proof

When startup.json exists and is loaded, you'll see:

Loaded startup configuration from: E:\Projects\pgsql-jellyfin\startup.json

This message comes from line 101:

Console.WriteLine($"Loaded startup configuration from: {configPath}");

This is your visual confirmation that:

  1. The file was found
  2. The file was loaded
  3. The values are available for use

Method Call Chain

1. Program.Main()
   └─► Jellyfin.Server/Program.cs:73

2. StartApp(options)
   └─► Jellyfin.Server/Program.cs:88

3. StartupHelpers.CreateApplicationPaths(options)
   └─► Jellyfin.Server/Program.cs:90

4. LoadStartupConfiguration()
   └─► Jellyfin.Server/Helpers/StartupHelpers.cs:223
   
5. File.Exists() checks
   └─► Jellyfin.Server/Helpers/StartupHelpers.cs:92
   
6. ConfigurationBuilder.AddJsonFile()
   └─► Jellyfin.Server/Helpers/StartupHelpers.cs:96
   
7. startupConfig?.GetValue<string>("Paths:*")
   └─► Jellyfin.Server/Helpers/StartupHelpers.cs:232, 238, 252, 291, 297, 264

Variable Tracking

Variable: startupConfig
Type: IConfigurationRoot?
Created: Line 223
Used: Lines 232, 238, 252, 264, 291, 297

Full Lifecycle:

// Line 223: Created
var startupConfig = LoadStartupConfiguration();

// Line 232: Used for DataDir
?? startupConfig?.GetValue<string>("Paths:DataDir")

// Line 238: Used for ConfigDir
?? startupConfig?.GetValue<string>("Paths:ConfigDir")

// Line 252: Used for CacheDir
?? startupConfig?.GetValue<string>("Paths:CacheDir")

// Line 264: Used for WebDir
?? startupConfig?.GetValue<string>("Paths:WebDir")

// Line 291: Used for LogDir
?? startupConfig?.GetValue<string>("Paths:LogDir")

// Line 297: Used for TempDir
?? startupConfig?.GetValue<string>("Paths:TempDir")

Conclusion: The startupConfig variable is ACTIVELY USED 6 times to read path values!


Test Proof

You can verify this yourself:

Test 1: Create startup.json with custom path

@"
{
  "Paths": {
    "DataDir": "E:/PROOF_TEST_DATA"
  }
}
"@ | Out-File startup.json -Encoding UTF8

# Run Jellyfin
.\jellyfin.exe

# Expected output:
# "Loaded startup configuration from: E:\Projects\pgsql-jellyfin\startup.json"
# [INF] Data directory: E:/PROOF_TEST_DATA

If you see those messages, that's PROOF it's being loaded and used!

Test 2: Verify with non-existent path

@"
{
  "Paths": {
    "DataDir": "E:/THIS_PATH_DOES_NOT_EXIST_XYZ123"
  }
}
"@ | Out-File startup.json -Encoding UTF8

# Run Jellyfin
.\jellyfin.exe

# If startup.json is being read, Jellyfin will try to use this path
# You'll see it attempting to create/access: E:/THIS_PATH_DOES_NOT_EXIST_XYZ123

Debugging Proof

If you still doubt, add this debug line:

In: Jellyfin.Server/Helpers/StartupHelpers.cs
After line 223:

var startupConfig = LoadStartupConfiguration();

// ADD THIS:
if (startupConfig != null)
{
    Console.WriteLine("DEBUG: startup.json loaded successfully!");
    Console.WriteLine($"DEBUG: DataDir from JSON = {startupConfig.GetValue<string>("Paths:DataDir")}");
    Console.WriteLine($"DEBUG: ConfigDir from JSON = {startupConfig.GetValue<string>("Paths:ConfigDir")}");
}
else
{
    Console.WriteLine("DEBUG: No startup.json found, using defaults");
}

Run Jellyfin and you'll see exactly what values are being read from startup.json!


Conclusion: PROVEN

Evidence Count: 8 code locations where startup.json is read
Search Locations: 3 directories checked
Console Confirmation: "Loaded startup configuration from: ..." message
Priority Level: 3rd in resolution chain (after command-line and environment)
Paths Read: All 6 paths (DataDir, ConfigDir, CacheDir, LogDir, TempDir, WebDir)

Verdict: startup.json is ABSOLUTELY checked and used on every startup!


Further Evidence

Look at your own Git history:

git log --oneline --all --grep="startup" -- "Jellyfin.Server/Helpers/StartupHelpers.cs"

You'll see commits related to startup configuration, proving this is an active, maintained feature!


Mathematical Certainty: 100% proven that startup.json is checked on startup! 🎯