API-Driven Migration
Vidra includes a built-in Migration ETL API that connects directly to your PeerTube PostgreSQL database, extracts data, transforms it to Vidra's schema, and loads it — all through a set of admin API endpoints. This is the recommended migration path for most operators.
How It Works
The Migration ETL runs as a background job inside Vidra. You start it via the API, and it:
- Connects to your PeerTube PostgreSQL database (read-only)
- Extracts users, channels, videos, comments, playlists, and media references
- Transforms the data to match Vidra's schema
- Loads the transformed data into Vidra's database
- Reports progress and statistics throughout
API Reference
All migration endpoints require admin authentication.
Start a Migration
curl -X POST http://your-vidra-host:8080/api/v1/admin/migrations/peertube \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_host": "peertube.example.com",
"source_db_host": "10.0.1.50",
"source_db_port": 5432,
"source_db_name": "peertube_prod",
"source_db_user": "peertube",
"source_db_password": "your-db-password",
"source_media_path": "/var/www/peertube/storage"
}'
Request fields:
| Field | Required | Default | Description |
|---|---|---|---|
source_host | Yes | — | PeerTube instance domain (e.g., peertube.example.com) |
source_db_host | Yes | — | PostgreSQL host IP or hostname |
source_db_port | No | 5432 | PostgreSQL port |
source_db_name | Yes | — | Database name |
source_db_user | Yes | — | Database username (read-only access is sufficient) |
source_db_password | Yes | — | Database password |
source_media_path | No | — | Path to PeerTube media storage (for media migration) |
Response (201 Created):
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"admin_user_id": "admin-uuid",
"source_host": "peertube.example.com",
"status": "pending",
"dry_run": false,
"created_at": "2026-03-23T10:00:00Z",
"updated_at": "2026-03-23T10:00:00Z"
}
}
Dry-Run a Migration (Recommended First Step)
A dry-run connects to the PeerTube database and reports what would be migrated without writing any data to Vidra. Always run this first.
curl -X POST http://your-vidra-host:8080/api/v1/admin/migrations/dry-run/dry-run \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_host": "peertube.example.com",
"source_db_host": "10.0.1.50",
"source_db_port": 5432,
"source_db_name": "peertube_prod",
"source_db_user": "peertube_readonly",
"source_db_password": "your-db-password"
}'
The dry-run job follows the same lifecycle as a real migration — you poll its status the same way. The response will include "dry_run": true and statistics showing entity counts without modifying any data.
Check Migration Status
Poll this endpoint to track progress:
curl http://your-vidra-host:8080/api/v1/admin/migrations/$JOB_ID \
-H "Authorization: Bearer $ADMIN_TOKEN"
Response:
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"source_host": "peertube.example.com",
"status": "running",
"dry_run": false,
"stats": {
"users": { "total": 150, "migrated": 120, "skipped": 5, "failed": 0, "errors": [] },
"channels": { "total": 45, "migrated": 45, "skipped": 0, "failed": 0, "errors": [] },
"videos": { "total": 1200, "migrated": 800, "skipped": 0, "failed": 2, "errors": ["video abc123: missing file reference"] },
"comments": { "total": 5000, "migrated": 4998, "skipped": 0, "failed": 2, "errors": [] },
"playlists": { "total": 30, "migrated": 30, "skipped": 0, "failed": 0, "errors": [] },
"media": { "total": 2400, "migrated": 0, "skipped": 0, "failed": 0, "errors": [] }
},
"started_at": "2026-03-23T10:00:05Z",
"created_at": "2026-03-23T10:00:00Z",
"updated_at": "2026-03-23T10:05:30Z"
}
}
Migration Job Statuses
| Status | Meaning |
|---|---|
pending | Job created, waiting to start |
validating | Connecting to source database, verifying access |
running | Actively extracting, transforming, and loading data |
dry_run | Dry-run in progress (no data written) |
completed | Migration finished (check stats for failures) |
failed | Migration failed (see error_message) |
cancelled | Migration was cancelled by admin |
Status transitions:
List All Migration Jobs
curl "http://your-vidra-host:8080/api/v1/admin/migrations?count=20&start=0" \
-H "Authorization: Bearer $ADMIN_TOKEN"
Query parameters:
| Param | Default | Description |
|---|---|---|
count | 20 | Items per page (max 100) |
start | 0 | Pagination offset |
Cancel a Migration
Cancel a running or pending migration:
curl -X DELETE http://your-vidra-host:8080/api/v1/admin/migrations/$JOB_ID \
-H "Authorization: Bearer $ADMIN_TOKEN"
Returns 204 No Content on success, or 400 Bad Request if the job is already in a terminal state (completed, failed, or cancelled).
Step-by-Step Walkthrough
1. Get an Admin Token
# Login as admin
ADMIN_TOKEN=$(curl -s -X POST http://your-vidra-host:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-admin-password"}' \
| jq -r '.data.access_token')
echo "Token: $ADMIN_TOKEN"
2. Run a Dry-Run
DRY_RUN_ID=$(curl -s -X POST http://your-vidra-host:8080/api/v1/admin/migrations/dry-run/dry-run \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_host": "peertube.example.com",
"source_db_host": "10.0.1.50",
"source_db_name": "peertube_prod",
"source_db_user": "peertube_readonly",
"source_db_password": "secret"
}' | jq -r '.data.id')
echo "Dry-run job: $DRY_RUN_ID"
3. Poll Until Complete
while true; do
STATUS=$(curl -s http://your-vidra-host:8080/api/v1/admin/migrations/$DRY_RUN_ID \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.data.status')
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
break
fi
sleep 5
done
4. Review Dry-Run Results
curl -s http://your-vidra-host:8080/api/v1/admin/migrations/$DRY_RUN_ID \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.data.stats'
Compare the totals against your PeerTube inventory queries. If the numbers match, proceed with the real migration.
5. Start the Real Migration
MIGRATION_ID=$(curl -s -X POST http://your-vidra-host:8080/api/v1/admin/migrations/peertube \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_host": "peertube.example.com",
"source_db_host": "10.0.1.50",
"source_db_name": "peertube_prod",
"source_db_user": "peertube",
"source_db_password": "secret",
"source_media_path": "/var/www/peertube/storage"
}' | jq -r '.data.id')
echo "Migration job: $MIGRATION_ID"
6. Monitor Progress
watch -n 10 "curl -s http://your-vidra-host:8080/api/v1/admin/migrations/$MIGRATION_ID \
-H 'Authorization: Bearer $ADMIN_TOKEN' | jq '{status: .data.status, stats: .data.stats}'"
7. Verify Completion
Once the status is completed, check the stats for any failures:
curl -s http://your-vidra-host:8080/api/v1/admin/migrations/$MIGRATION_ID \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '{
status: .data.status,
error: .data.error_message,
users: .data.stats.users,
channels: .data.stats.channels,
videos: .data.stats.videos,
comments: .data.stats.comments,
playlists: .data.stats.playlists,
media: .data.stats.media
}'
If any entities show failed > 0, check the errors array for details and refer to Troubleshooting.
Security Considerations
- Database credentials in the migration request are stored encrypted and never returned in API responses (the
source_db_passwordfield is markedjson:"-") - Use a read-only database user for the PeerTube connection when possible
- The migration API requires admin authentication — only Vidra admins can initiate migrations
- Consider using a VPN or SSH tunnel if the PeerTube database is not on the same network
# Example: SSH tunnel to PeerTube database
ssh -L 5433:localhost:5432 user@peertube-server &
# Then use localhost:5433 as source_db_host/port
After the API Migration
Once the database migration completes:
- Migrate storage — Copy video files, thumbnails, and streaming playlists
- Cutover and validate — Switch DNS, verify everything works, and monitor