Skip to main content

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:

  1. Connects to your PeerTube PostgreSQL database (read-only)
  2. Extracts users, channels, videos, comments, playlists, and media references
  3. Transforms the data to match Vidra's schema
  4. Loads the transformed data into Vidra's database
  5. 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:

FieldRequiredDefaultDescription
source_hostYesPeerTube instance domain (e.g., peertube.example.com)
source_db_hostYesPostgreSQL host IP or hostname
source_db_portNo5432PostgreSQL port
source_db_nameYesDatabase name
source_db_userYesDatabase username (read-only access is sufficient)
source_db_passwordYesDatabase password
source_media_pathNoPath 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"
}
}

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

StatusMeaning
pendingJob created, waiting to start
validatingConnecting to source database, verifying access
runningActively extracting, transforming, and loading data
dry_runDry-run in progress (no data written)
completedMigration finished (check stats for failures)
failedMigration failed (see error_message)
cancelledMigration 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:

ParamDefaultDescription
count20Items per page (max 100)
start0Pagination 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_password field is marked json:"-")
  • 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:

  1. Migrate storage — Copy video files, thumbnails, and streaming playlists
  2. Cutover and validate — Switch DNS, verify everything works, and monitor