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

416 lines
13 KiB
Markdown

# 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`
```csharp
// 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`
```csharp
// 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`
```csharp
// 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:
```csharp
// 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:**
```json
{
"Paths": {
"DataDir": "D:/Media/Jellyfin/Data",
"ConfigDir": "D:/Media/Jellyfin/Config",
"CacheDir": "E:/Cache/Jellyfin",
"LogDir": "D:/Logs/Jellyfin"
}
}
```
**What happens on startup:**
```csharp
// 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:**
```json
{
"Paths": {
"DataDir": "D:/Media/Jellyfin/Data"
}
}
```
**Command:**
```powershell
jellyfin --datadir "F:/CustomData"
```
**What happens:**
```csharp
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:**
```json
{
"Paths": {
"DataDir": "D:/Media/Jellyfin/Data"
}
}
```
**Environment:**
```powershell
$env:JELLYFIN_DATA_DIR = "G:/EnvData"
jellyfin
```
**What happens:**
```csharp
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
```powershell
# 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
```powershell
# 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
```powershell
# 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! ✅