a3eb4b1b57
- Added Jellyfin.Database.Providers.Postgres project with full EF Core migration support for PostgreSQL. - Implemented automatic database creation and privilege assignment on startup. - Generated initial migration and model snapshot for PostgreSQL schema. - Updated build, test, and dependency files to include PostgreSQL provider and Npgsql packages. - Added PowerShell script for generating and testing PostgreSQL migrations. - No changes to application logic outside database provider/migration infrastructure.
414 lines
9.8 KiB
Markdown
414 lines
9.8 KiB
Markdown
# Automatic Database Creation - PostgreSQL
|
|
|
|
## Overview
|
|
|
|
Jellyfin now automatically ensures the PostgreSQL database exists before applying migrations. This eliminates the need for manual database creation.
|
|
|
|
## How It Works
|
|
|
|
### Startup Sequence
|
|
|
|
1. **Database Existence Check** (New!)
|
|
- Jellyfin connects to PostgreSQL's `postgres` maintenance database
|
|
- Checks if the target database (e.g., `jellyfin`) exists
|
|
- If not, creates the database automatically
|
|
|
|
2. **Database Schema Creation**
|
|
- Connects to the target database
|
|
- Applies EF Core migrations
|
|
- Creates all 30 tables, indexes, and constraints
|
|
|
|
3. **Application Startup**
|
|
- Continues normal Jellyfin initialization
|
|
|
|
### Code Flow
|
|
|
|
```
|
|
Startup
|
|
↓
|
|
JellyfinMigrationService.MigrateStepAsync()
|
|
↓
|
|
Check if PostgreSQL provider?
|
|
↓ YES
|
|
PostgresDatabaseProvider.EnsureDatabaseExistsAsync()
|
|
↓
|
|
├─ Connect to 'postgres' database
|
|
├─ Check if target database exists
|
|
├─ If not → CREATE DATABASE
|
|
└─ GRANT privileges
|
|
↓
|
|
Continue with migrations
|
|
↓
|
|
Apply EF Core migrations (create tables)
|
|
```
|
|
|
|
## What Gets Created
|
|
|
|
### Phase 1: Database Creation (If Needed)
|
|
|
|
When the database doesn't exist, Jellyfin automatically runs:
|
|
|
|
```sql
|
|
-- Check existence
|
|
SELECT 1 FROM pg_database WHERE datname = 'jellyfin';
|
|
|
|
-- Create if missing
|
|
CREATE DATABASE "jellyfin" OWNER "jellyfin";
|
|
|
|
-- Grant permissions
|
|
GRANT ALL PRIVILEGES ON DATABASE "jellyfin" TO "jellyfin";
|
|
```
|
|
|
|
### Phase 2: Schema Creation (Always)
|
|
|
|
EF Core migrations create all tables:
|
|
- 30 tables with relationships
|
|
- Primary keys, foreign keys, indexes
|
|
- PostgreSQL-optimized data types
|
|
|
|
## Requirements
|
|
|
|
### PostgreSQL User Permissions
|
|
|
|
The user specified in `database.xml` must have:
|
|
|
|
1. **Connection to `postgres` database**:
|
|
```sql
|
|
-- Usually granted by default
|
|
GRANT CONNECT ON DATABASE postgres TO jellyfin;
|
|
```
|
|
|
|
2. **CREATE DATABASE privilege**:
|
|
```sql
|
|
-- Option 1: Make user a CREATEDB role
|
|
ALTER USER jellyfin CREATEDB;
|
|
|
|
-- Option 2: Grant via superuser (less preferred)
|
|
ALTER USER jellyfin WITH SUPERUSER;
|
|
```
|
|
|
|
3. **Owner of the database** (automatically set during creation)
|
|
|
|
### Minimal Setup Required
|
|
|
|
**Before (Manual):**
|
|
```bash
|
|
# You had to do this manually
|
|
psql -U postgres -c "CREATE DATABASE jellyfin;"
|
|
psql -U postgres -c "CREATE USER jellyfin WITH PASSWORD 'pass';"
|
|
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE jellyfin TO jellyfin;"
|
|
```
|
|
|
|
**After (Automatic):**
|
|
```bash
|
|
# Only create user with CREATEDB privilege
|
|
psql -U postgres -c "CREATE USER jellyfin WITH PASSWORD 'pass' CREATEDB;"
|
|
|
|
# Then configure Jellyfin and start - database auto-created!
|
|
```
|
|
|
|
## Configuration
|
|
|
|
No changes needed to your existing `database.xml`:
|
|
|
|
```xml
|
|
<DatabaseType>Jellyfin-PostgreSQL</DatabaseType>
|
|
<LockingBehavior>NoLock</LockingBehavior>
|
|
<CustomProviderOptions>
|
|
<Options>
|
|
<CustomDatabaseOption>
|
|
<Key>host</Key>
|
|
<Value>localhost</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</Value>
|
|
</CustomDatabaseOption>
|
|
</Options>
|
|
</CustomProviderOptions>
|
|
```
|
|
|
|
## Logs
|
|
|
|
When the feature runs, you'll see logs like:
|
|
|
|
### Database Doesn't Exist
|
|
|
|
```
|
|
[INFO] Checking if PostgreSQL database exists...
|
|
[INFO] PostgreSQL database 'jellyfin' does not exist. Creating...
|
|
[INFO] PostgreSQL database 'jellyfin' created successfully
|
|
[INFO] Granted all privileges on database 'jellyfin' to user 'jellyfin'
|
|
```
|
|
|
|
### Database Already Exists
|
|
|
|
```
|
|
[INFO] Checking if PostgreSQL database exists...
|
|
[INFO] PostgreSQL database 'jellyfin' already exists
|
|
```
|
|
|
|
### Errors
|
|
|
|
```
|
|
[ERROR] Failed to ensure PostgreSQL database 'jellyfin' exists. Error: permission denied to create database
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Error: "permission denied to create database"
|
|
|
|
**Cause:** User doesn't have CREATE DATABASE privilege
|
|
|
|
**Solution:**
|
|
```sql
|
|
-- As PostgreSQL superuser
|
|
ALTER USER jellyfin CREATEDB;
|
|
```
|
|
|
|
### Error: "database already exists"
|
|
|
|
**Not an error!** Jellyfin detected existing database and skipped creation.
|
|
|
|
### Error: "could not connect to database postgres"
|
|
|
|
**Cause:** User doesn't have connection permission to maintenance database
|
|
|
|
**Solution:**
|
|
```sql
|
|
-- As PostgreSQL superuser
|
|
GRANT CONNECT ON DATABASE postgres TO jellyfin;
|
|
```
|
|
|
|
### Error: "role 'jellyfin' does not exist"
|
|
|
|
**Cause:** User hasn't been created yet
|
|
|
|
**Solution:**
|
|
```sql
|
|
-- As PostgreSQL superuser
|
|
CREATE USER jellyfin WITH PASSWORD 'secure_password' CREATEDB;
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### CREATEDB Privilege
|
|
|
|
Granting `CREATEDB` allows the user to create databases. This is safe for Jellyfin's dedicated user because:
|
|
- User can only create databases, not modify system tables
|
|
- User doesn't need SUPERUSER privilege
|
|
- Follows principle of least privilege
|
|
- Alternative: Pre-create database manually (defeats automation)
|
|
|
|
### Recommended Setup
|
|
|
|
**Production:**
|
|
```sql
|
|
-- Create user with limited privileges
|
|
CREATE USER jellyfin WITH PASSWORD 'strong_password' CREATEDB LOGIN;
|
|
|
|
-- Restrict to specific database after creation
|
|
REVOKE CREATEDB FROM jellyfin; -- Optional: Remove after first run
|
|
```
|
|
|
|
**Development:**
|
|
```sql
|
|
-- More permissive for convenience
|
|
CREATE USER jellyfin WITH PASSWORD 'dev_password' CREATEDB LOGIN;
|
|
```
|
|
|
|
## Comparison: Before vs After
|
|
|
|
| Aspect | Manual (Before) | Automatic (After) |
|
|
|--------|-----------------|-------------------|
|
|
| **Steps** | 3-4 SQL commands | 1 SQL command (create user) |
|
|
| **Errors** | Easy to forget steps | Impossible - automated |
|
|
| **Docker** | Need init scripts | Just configure user |
|
|
| **Fresh Install** | Extra documentation | Works out of box |
|
|
| **Error Handling** | User troubleshoots | Jellyfin logs clear errors |
|
|
|
|
## Implementation Details
|
|
|
|
### PostgresDatabaseProvider.EnsureDatabaseExistsAsync()
|
|
|
|
**Purpose:** Check and create database before migrations
|
|
|
|
**Process:**
|
|
1. Parse configuration to extract connection details
|
|
2. Build connection string to `postgres` maintenance database
|
|
3. Connect and check if target database exists
|
|
4. Create database if missing with proper owner
|
|
5. Grant all privileges to the user
|
|
6. Log success or failure
|
|
|
|
**Error Handling:**
|
|
- Catches all exceptions
|
|
- Logs detailed error messages
|
|
- Re-throws to prevent silent failures
|
|
- Shows user exactly what went wrong
|
|
|
|
### JellyfinMigrationService Integration
|
|
|
|
**Hook Point:** `MigrateStepAsync()` at `CoreInitialisation` stage
|
|
|
|
**Logic:**
|
|
```csharp
|
|
if (stage == CoreInitialisation && provider is PostgresDatabaseProvider postgres)
|
|
{
|
|
await postgres.EnsureDatabaseExistsAsync(config);
|
|
}
|
|
```
|
|
|
|
**Why CoreInitialisation?**
|
|
- Earliest stage where database is needed
|
|
- Before any migrations run
|
|
- After configuration is loaded
|
|
- Perfect timing for database creation
|
|
|
|
## Testing
|
|
|
|
### Manual Test
|
|
|
|
```bash
|
|
# 1. Create user only
|
|
psql -U postgres -c "CREATE USER jellyfin WITH PASSWORD 'test' CREATEDB;"
|
|
|
|
# 2. DO NOT create database
|
|
|
|
# 3. Configure Jellyfin with PostgreSQL
|
|
|
|
# 4. Start Jellyfin
|
|
|
|
# Expected: Database created automatically
|
|
psql -U postgres -c "\l" | grep jellyfin
|
|
# Should show: jellyfin | jellyfin | ...
|
|
```
|
|
|
|
### Clean Test
|
|
|
|
```bash
|
|
# 1. Drop existing database (if any)
|
|
psql -U postgres -c "DROP DATABASE IF EXISTS jellyfin;"
|
|
|
|
# 2. Start Jellyfin
|
|
|
|
# 3. Verify database created
|
|
psql -U jellyfin -d jellyfin -c "\dt"
|
|
# Should show all 30 tables
|
|
```
|
|
|
|
## Limitations
|
|
|
|
### What's Automated
|
|
✅ Database existence check
|
|
✅ Database creation
|
|
✅ Owner assignment
|
|
✅ Privilege granting
|
|
✅ Error logging
|
|
|
|
### What's Not Automated
|
|
❌ User creation (must exist)
|
|
❌ CREATEDB privilege (must be granted)
|
|
❌ PostgreSQL installation
|
|
❌ Network/firewall configuration
|
|
|
|
### Why These Aren't Automated
|
|
- **User creation**: Requires PostgreSQL superuser credentials
|
|
- **Privilege granting**: Security implications
|
|
- **Installation**: OS-dependent
|
|
- **Network**: Environment-specific
|
|
|
|
## Migration from Manual Setup
|
|
|
|
If you previously created the database manually:
|
|
|
|
**Impact:** None! The feature detects existing database and skips creation.
|
|
|
|
**Steps:**
|
|
1. Update Jellyfin to version with this feature
|
|
2. Start Jellyfin normally
|
|
3. See log: "PostgreSQL database 'jellyfin' already exists"
|
|
|
|
No migration or reconfiguration needed.
|
|
|
|
## Best Practices
|
|
|
|
### Docker/Containers
|
|
|
|
**docker-compose.yml:**
|
|
```yaml
|
|
services:
|
|
postgres:
|
|
image: postgres:15
|
|
environment:
|
|
POSTGRES_USER: jellyfin
|
|
POSTGRES_PASSWORD: secure_password
|
|
# Don't set POSTGRES_DB - let Jellyfin create it
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
|
|
jellyfin:
|
|
image: jellyfin/jellyfin
|
|
depends_on:
|
|
- postgres
|
|
environment:
|
|
- JELLYFIN_DB_TYPE=Jellyfin-PostgreSQL
|
|
- JELLYFIN_DB_HOST=postgres
|
|
- JELLYFIN_DB_USER=jellyfin
|
|
- JELLYFIN_DB_PASS=secure_password
|
|
```
|
|
|
|
Note: By NOT setting `POSTGRES_DB`, we let Jellyfin create the database itself.
|
|
|
|
### Kubernetes
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: jellyfin-db
|
|
type: Opaque
|
|
data:
|
|
username: amVsbHlmaW4= # jellyfin
|
|
password: <base64-encoded>
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: jellyfin-db-config
|
|
data:
|
|
init-user.sql: |
|
|
CREATE USER jellyfin WITH PASSWORD 'from-secret' CREATEDB;
|
|
```
|
|
|
|
## Summary
|
|
|
|
✅ **Automated database creation**
|
|
✅ **Minimal user setup required**
|
|
✅ **Clear error messages**
|
|
✅ **Backward compatible**
|
|
✅ **Production-ready**
|
|
✅ **Fully logged**
|
|
|
|
The automatic database creation feature makes PostgreSQL setup as simple as SQLite while maintaining security and reliability.
|
|
|
|
---
|
|
|
|
**Feature Added:** February 22, 2026
|
|
**Status:** ✅ Production Ready
|
|
**Tested:** ✅ Fresh install, existing database
|
|
**Breaking Changes:** None
|