Add automatic PostgreSQL database creation and migration
- 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.
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user