# Jellyfin with PostgreSQL Support

This is a fork of Jellyfin that adds PostgreSQL database support for .NET 11 preview.

## Features

- ✅ PostgreSQL database provider implementation
- ✅ Automatic database initialization from SQL scripts
- ✅ Remote database support with connection string configuration
- ✅ Database backup/restore via pg_dump/pg_restore
- ✅ Migration system disabled in favor of SQL scripts
- ✅ Support for both local and remote PostgreSQL servers

---

## Requirements

### FFmpeg Requirements

**jellyfin-ffmpeg is highly preferred and recommended** for use with Jellyfin Media Server. While standard FFmpeg can be used, the custom `jellyfin-ffmpeg` fork includes specifically targeted fixes, optimizations, and enhanced hardware acceleration support for media streaming that may not be available or prioritized in the upstream version.

#### Why jellyfin-ffmpeg is Preferred:

- **Improved Hardware Acceleration**: Better support for hardware acceleration (NVENC, QuickSync, VA-API) across a wider range of GPUs, reducing the risk of partial acceleration
- **Reduced Playback Issues**: Specifically built to handle HDR/Dolby Vision content, HLS/fMP4/MPEG-TS streaming, and complex transcoding scenarios better than standard FFmpeg
- **Optimized Performance**: Often includes more efficient software decoders (like dav1d for AV1)
- **Pre-configured**: Automatically included with official Docker images, deb packages, and Windows installers

**Link**: [jellyfin-ffmpeg repository](https://github.com/jellyfin/jellyfin-ffmpeg)

#### Installation:

**Debian/Ubuntu** (using Jellyfin repository):
```bash
# Add Jellyfin repository (if not already added)
sudo apt-get install -y software-properties-common
sudo add-apt-repository universe

# Install jellyfin-ffmpeg
sudo apt-get update
sudo apt-get install -y jellyfin-ffmpeg6
```

**Manual Download**:
Download from [jellyfin-ffmpeg releases](https://github.com/jellyfin/jellyfin-ffmpeg/releases) and configure the path in Jellyfin settings.

#### Using Standard FFmpeg (Not Recommended):

If using a custom or unofficial installation where jellyfin-ffmpeg is not available, standard FFmpeg might work but could lead to issues with specific transcoding tasks:

```bash
# Debian/Ubuntu
sudo apt-get install -y ffmpeg

# RHEL/Fedora
sudo dnf install -y ffmpeg

# Arch Linux
sudo pacman -S ffmpeg
```

**Note**: You may need to manually set the FFmpeg path in Jellyfin's dashboard under **Dashboard → Playback → FFmpeg path**.

### System Dependencies (Linux)

Jellyfin requires certain system libraries to be installed:

#### Debian/Ubuntu-based systems:

```bash
sudo apt-get update
sudo apt-get install -y \
    libfontconfig1 \
    libfreetype6 \
    libssl3 \
    libicu-dev
```

**Note**: See the [FFmpeg Requirements](#ffmpeg-requirements) section above for installing `jellyfin-ffmpeg`.

#### RHEL/CentOS/Fedora-based systems:

```bash
sudo dnf install -y \
    fontconfig \
    freetype \
    openssl \
    libicu
```

**Note**: See the [FFmpeg Requirements](#ffmpeg-requirements) section above for installing FFmpeg.

#### Arch Linux:

```bash
sudo pacman -S fontconfig freetype2 openssl icu
```

**Note**: See the [FFmpeg Requirements](#ffmpeg-requirements) section above for installing FFmpeg.

**Note**: The `libfontconfig1` library is **required** for SkiaSharp (image processing). Without it, Jellyfin will fail to start with:
```
libfontconfig.so.1: cannot open shared object file: No such file or directory
```

### PostgreSQL Requirements

- PostgreSQL 12 or higher (tested with PostgreSQL 18.3)
- `psql` client tools (for automatic schema initialization)
- Network access to PostgreSQL server (if using remote database)

#### Install PostgreSQL client tools:

**Debian/Ubuntu**:
```bash
sudo apt-get install -y postgresql-client
```

**RHEL/CentOS/Fedora**:
```bash
sudo dnf install -y postgresql
```

**Verify psql is available**:
```bash
psql --version
```

---

## Database Configuration

### Option 1: Using ConnectionString (Recommended)

Create or edit `config/database.xml` in your Jellyfin data directory:

```xml
<?xml version="1.0" encoding="utf-8"?>
<DatabaseConfigurationOptions>
  <DatabaseType>Jellyfin-PostgreSQL</DatabaseType>
  <LockingBehavior>NoLock</LockingBehavior>
  
  <CustomProviderOptions>
    <PluginName>Jellyfin-PostgreSQL</PluginName>
    <PluginAssembly>Jellyfin.Database.Providers.Postgres</PluginAssembly>
    
    <!-- Connection string with all parameters -->
    <ConnectionString>Host=192.168.1.100;Port=5432;Database=jellyfin;Username=jellyfin;Password=your_password_here</ConnectionString>
  </CustomProviderOptions>
  
  <!-- Optional: Backup configuration (works for both local and remote databases) -->
  <BackupOptions>
    <PgDumpPath>pg_dump</PgDumpPath>
    <PgRestorePath>pg_restore</PgRestorePath>
    <BackupFormat>custom</BackupFormat>
    <IncludeBlobs>true</IncludeBlobs>
    <CompressionLevel>6</CompressionLevel>
    <TimeoutSeconds>1800</TimeoutSeconds>
    <VerboseOutput>true</VerboseOutput>
  </BackupOptions>
</DatabaseConfigurationOptions>
```

### Option 2: Using Individual Options

```xml
<?xml version="1.0" encoding="utf-8"?>
<DatabaseConfigurationOptions>
  <DatabaseType>Jellyfin-PostgreSQL</DatabaseType>
  
  <CustomProviderOptions>
    <PluginName>Jellyfin-PostgreSQL</PluginName>
    <PluginAssembly>Jellyfin.Database.Providers.Postgres</PluginAssembly>
    
    <Options>
      <CustomDatabaseOption>
        <Key>Host</Key>
        <Value>192.168.1.100</Value>
      </CustomDatabaseOption>
      
      <CustomDatabaseOption>
        <Key>Port</Key>
        <Value>5432</Value>
      </CustomDatabaseOption>
      
      <CustomDatabaseOption>
        <Key>Database</Key>
        <Value>jellyfin</Value>
      </CustomDatabaseOption>
      
      <CustomDatabaseOption>
        <Key>Username</Key>
        <Value>jellyfin</Value>
      </CustomDatabaseOption>
      
      <CustomDatabaseOption>
        <Key>Password</Key>
        <Value>your_password_here</Value>
      </CustomDatabaseOption>
    </Options>
  </CustomProviderOptions>
</DatabaseConfigurationOptions>
```

### Option 3: Hybrid (ConnectionString + Overrides)

Individual options take precedence over ConnectionString:

```xml
<CustomProviderOptions>
  <!-- Base connection -->
  <ConnectionString>Host=192.168.1.100;Port=5432;Database=jellyfin;Username=jellyfin</ConnectionString>
  
  <!-- Override just the password -->
  <Options>
    <CustomDatabaseOption>
      <Key>Password</Key>
      <Value>production_password</Value>
    </CustomDatabaseOption>
  </Options>
</CustomProviderOptions>
```

---

## Database Initialization

### Automatic Initialization

When Jellyfin starts with an empty database:

1. **Database Check**: Checks if the database exists (creates if missing)
2. **Table Check**: Checks if `users.Users` table exists
3. **Auto-Initialize**: If tables don't exist, automatically runs `sql/schema_init/create_database_schema.sql` via `psql`
4. **Schema Verification**: Verifies all required schemas and tables are present

**Requirements for automatic initialization**:
- ✅ `psql` command must be in system PATH
- ✅ Database must exist (created automatically or manually)
- ✅ SQL script must be deployed: `sql/schema_init/create_database_schema.sql`

### Manual Initialization

If `psql` is not available or automatic initialization fails:

```bash
# Create database first
createdb -U postgres jellyfin

# Run schema creation script
psql -U postgres -d jellyfin -f /path/to/jellyfin/sql/schema_init/create_database_schema.sql
```

---

## Startup Logs

### Successful Startup (Empty Database):

```
[INF] PostgreSQL connection: Host="192.168.1.100", Port=5432, Database="jellyfin", ...
[INF] Database tables not found (users.Users table missing). Initializing from SQL script...
[INF] Found schema script at: .../sql/schema_init/create_database_schema.sql
[INF] Executing create_database_schema.sql (this may take several minutes)...
[INF] Executing via psql: psql -h 192.168.1.100 -p 5432 -U jellyfin -d jellyfin -f "..."
[INF] ✅ Successfully initialized database from SQL script via psql
[INF] Database is ready. You can now start Jellyfin.
[DBG] Schema 'users' contains 4 tables
[DBG] Schema 'library' contains 45 tables
[DBG] Schema 'activitylog' contains 1 tables
[INF] ✅ Database schema verification complete
[INF] There are 0 migrations for stage CoreInitialisation
[INF] There are 24 migrations for stage AppInitialisation
```

### Successful Startup (Existing Database):

```
[INF] PostgreSQL connection: Host="192.168.1.100", Port=5432, Database="jellyfin", ...
[INF] Database tables exist (users.Users table found)
[DBG] Schema 'users' contains 4 tables
[DBG] Schema 'library' contains 45 tables
[INF] ✅ Database schema verification complete
```

---

## Troubleshooting

### Error: `libfontconfig.so.1: cannot open shared object file`

**Cause**: Missing system dependency

**Solution**:
```bash
# Debian/Ubuntu
sudo apt-get install -y libfontconfig1

# RHEL/Fedora
sudo dnf install -y fontconfig
```

### Error: `psql command not found`

**Cause**: PostgreSQL client tools not installed

**Solution**:
```bash
# Debian/Ubuntu
sudo apt-get install -y postgresql-client

# RHEL/Fedora
sudo dnf install -y postgresql
```

### Error: `Failed to connect to 127.0.0.1:5432`

**Cause**: Configuration not loading correctly (using default localhost)

**Solution**:
- Verify `database.xml` is in the correct location (`/var/lib/jellyfin/config/database.xml` on Linux)
- Check that ConnectionString or individual Options are properly configured
- Ensure Host is set to your remote server IP/hostname

### Error: `database "jellyfin" does not exist`

**Cause**: Database wasn't created automatically (rare)

**Solution**:
```bash
# Create database manually
createdb -U postgres -h your-server jellyfin

# Grant privileges
psql -U postgres -h your-server -c "GRANT ALL PRIVILEGES ON DATABASE jellyfin TO jellyfin;"

# Restart Jellyfin (it will create tables automatically)
```

---

## Architecture

### Database Schema

- `activitylog` - Activity log entries
- `authentication` - API keys, devices, device options
- `displaypreferences` - User display preferences
- `library` - Media library data (BaseItems, metadata, etc.)
- `users` - User accounts, permissions, preferences

### Migration System

**Entity Framework migrations are DISABLED** for .NET 11 preview. Schema changes are managed via SQL scripts:

- **Core migrations**: Disabled (no database structure changes via EF)
- **Application migrations**: Enabled (configuration and data migrations only)

**Why**: EF migrations in preview builds can cause schema inconsistencies. SQL scripts provide reliable schema management.

---

## Backup and Restore

### Automatic Backups (via pg_dump)

Configured in `database.xml`:

```xml
<BackupOptions>
  <PgDumpPath>pg_dump</PgDumpPath>
  <BackupFormat>custom</BackupFormat>
  <CompressionLevel>6</CompressionLevel>
</BackupOptions>
```

**Works for both local and remote databases!**

### Manual Backup

```bash
# Backup to custom format (compressed)
pg_dump -h your-server -U jellyfin -d jellyfin -Fc -f jellyfin_backup.dump

# Backup to SQL format
pg_dump -h your-server -U jellyfin -d jellyfin -f jellyfin_backup.sql
```

### Manual Restore

```bash
# Restore from custom format
pg_restore -h your-server -U jellyfin -d jellyfin jellyfin_backup.dump

# Restore from SQL format
psql -h your-server -U jellyfin -d jellyfin -f jellyfin_backup.sql
```

---

## Troubleshooting Connection Issues

### Error: `57P01: terminating connection due to administrator command`

**Cause**: PostgreSQL server forcibly terminated the connection during an operation

**Common reasons**:
- PostgreSQL server restart or reload
- Connection timeout (statement_timeout, idle_in_transaction_session_timeout)
- Manual connection termination via `pg_terminate_backend()`
- Network instability or firewall issues
- Resource limits exceeded (max_connections, memory pressure)

**Solutions**:

#### 1. Configure Automatic Retry (Already Enabled)

Jellyfin now includes automatic retry logic for transient failures:
- **Max retries**: 3 attempts
- **Max delay**: 5 seconds between retries
- **Retryable errors**: Includes `57P01` (connection termination)

#### 2. Increase PostgreSQL Timeouts

Edit PostgreSQL configuration (`/etc/postgresql/*/main/postgresql.conf`):

```
# Increase statement timeout
statement_timeout = 300000              # 5 minutes (in milliseconds)

# Increase idle transaction timeout
idle_in_transaction_session_timeout = 600000  # 10 minutes

# Enable TCP keepalives (prevent network timeouts)
tcp_keepalives_idle = 60                # seconds
tcp_keepalives_interval = 10            # seconds
tcp_keepalives_count = 6
```

Reload PostgreSQL:
```bash
sudo systemctl reload postgresql
# OR
psql -U postgres -c "SELECT pg_reload_conf();"
```

#### 3. Optimize Connection String

Add keepalive and timeout parameters to your connection string:

```xml
<ConnectionString>Host=192.168.129.253;Port=5432;Database=jellyfin;Username=jellyfin;Password=yourpass;Pooling=True;Minimum Pool Size=0;Maximum Pool Size=50;Connection Idle Lifetime=300;Connection Pruning Interval=10;Timeout=30;Command Timeout=300;Keepalive=60</ConnectionString>
```

**Key parameters**:
- `Timeout=30` - Connection establishment timeout (30 seconds)
- `Command Timeout=300` - Query execution timeout (5 minutes)
- `Keepalive=60` - TCP keepalive interval (60 seconds)
- `Connection Idle Lifetime=300` - Close idle connections after 5 minutes
- `Connection Pruning Interval=10` - Check for stale connections every 10 seconds

#### 4. Check PostgreSQL Logs

On your PostgreSQL server:

```bash
# View recent logs
sudo tail -f /var/log/postgresql/postgresql-*.log

# Or check systemd journal
sudo journalctl -u postgresql -n 100 --no-pager
```

Look for:
- `received fast shutdown request`
- `terminating connection`
- `too many connections`
- `out of memory`

#### 5. Monitor Active Connections

```sql
-- Check current connections
SELECT 
    datname, 
    usename, 
    application_name,
    state,
    state_change
FROM pg_stat_activity
WHERE datname = 'jellyfin'
ORDER BY state_change DESC;

-- Check connection limits
SELECT 
    (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections,
    (SELECT COUNT(*) FROM pg_stat_activity) AS current_connections,
    (SELECT COUNT(*) FROM pg_stat_activity WHERE datname = 'jellyfin') AS jellyfin_connections;
```

---

## Building from Source

```bash
# Clone repository
git clone https://github.com/yourusername/jellyfin.git
cd jellyfin

# Checkout the upgrade branch
git checkout upgrade-to-NET11

# Build
dotnet build

# Run
cd Jellyfin.Server
dotnet run
```

---

## Contributing

This is a development/preview branch. For production use, please use the official Jellyfin releases.

## License

GNU General Public License v2.0 (same as Jellyfin)

---

## Credits

Based on [Jellyfin](https://github.com/jellyfin/jellyfin) with PostgreSQL support and .NET 11 compatibility.
