Skip to main content

Migrating from PeerTube to Vidra

Vidra is a clean-room implementation, not a PeerTube fork, and it is not API-compatible with PeerTube. Your existing PeerTube clients, embeds and plugins will not work against a Vidra instance.

What Vidra ships is migration tooling: a one-way importer that reads your PeerTube instance's PostgreSQL database directly and carries the data across. It is idempotent, resumable, and safe to run repeatedly against a still-live source right up to cutover.

Two ways to run it

PathBest forWhere the source connection lives
Admin API importMost operators. A managed run you launch from the admin console and poll for progress.Server config only — the browser never sends a DSN or a credential.
Command-line importerAnything the API cannot do: --force on an unverified schema, a run against a database the api cannot reach, or a source you want to drive from a laptop over a tunnel.--source-dsn on the command line.

Both drive the same importer. Everything in this section applies to both unless it says otherwise.

What it carries

DataCarriedNotes
Users and accountsYesUsername, email, role, verification state — and the bcrypt password hash, verbatim. Users keep their passwords.
ChannelsYesNames, descriptions, support text
VideosYesMetadata, privacy, tags, categories, languages, original publication date
Video renditionsYesThe HLS quality ladder's rungs, in reference mode
CommentsYesFull thread trees, parent-child preserved
Playlists and itemsYesMetadata and ordered items
CaptionsYesWith language metadata
SubscriptionsYesChannel follows
ChaptersYes
RatingsYesLikes and dislikes
View totalsYes, as a deltaThe ledger remembers the source total it last applied and only adds the difference, so a re-run against an unchanged source adds nothing and views Vidra served between runs are never erased. The per-day rollup is deliberately left empty: the source has one lifetime number and no daily history, and inventing buckets would fabricate a shape of data that was never measured.
Category taxonomyYesIncluding a peertube-plugin-categories custom taxonomy — see below
Avatars and bannersYesFetched from a mounted source root, or over HTTP from the source instance
Video posters and storyboardsYesThe same way
Moderation stateNo, deliberatelyVideo blacklist, account and server blocklists, abuse reports
Notification settings and watch historyNo, deliberately
Original-file provenance (videoSource)NoSimply unread today
Federation peersManualActivityPub followers need to re-follow after cutover
OAuth clientsManualThird-party apps must be re-registered
PeerTube pluginsNoVidra has no plugin system that could run them
Passwords come across

This is the opposite of what most migrations require. The importer carries the source's bcrypt hash verbatim and Vidra verifies against it, so your users sign in with the passwords they already have. There is no bulk password-reset email to send.

It is idempotent, resumable, and re-runnable

Every import writes to a durable ledger, so a re-run skips rows already imported rather than duplicating them. Three things follow from that, and they shape the whole workflow:

  1. A failed run is resumed, not restarted. Interruptions are cheap.
  2. A re-run backfills. An instance whose catalogue is already in Vidra picks up data a newer importer release learned to carry, without re-importing anything from scratch. If your migrated instance shows a broken image on every card, re-run the importer — the poster pass repairs those rows in place.
  3. You can sync repeatedly against a live source. The intended workflow is a scheduled run against a still-running PeerTube, right up to the cutover window, so the final sync is small.

For that last case there is a second, orthogonal switch: source_authoritative (the --source-authoritative flag). It decides whether a re-run may update rows the import already owns — a title edited, a password changed or a chapter moved on the source. It never touches a row created on Vidra (those have no ledger entry), never re-inserts, never rotates an actor keypair, never assigns a view count, never overwrites a rendition row, and never re-downloads media. It defaults to false: gap-filling, which is what an import has always done.

Do not confuse it with conflict_policy (skip | rename | merge | fail, default skip), which resolves username, handle, email and slug collisions at insert time.

The version gate

The importer accepts a source migrationVersion in the verified range [700, 1000] and refuses anything outside it:

source schema version 1040 is newer than the verified range [700, 1000]
— pass --force only after verifying compatibility

That refusal is a statement about what has been verified, not about what happens to work. Overriding it is a human decision:

  • On the API path, re-launch with acknowledged_schema_version set to the detected version. The server never sets that field for itself and has no default for it, and the run records what was signed off on.
  • On the CLI path, --force. Its own help says "HUMAN operators only; agents MUST NOT set this".

A source whose schema version cannot be read at all fails with undetectable_schema and is not acknowledgeable — there is no version to name. That one needs the CLI with --force.

What "verifying compatibility" can reasonably mean, given the importer only reads: check that every column it references still exists on the source (a removed or renamed column is the failure that would break the run loudly), and check what the newer schema added that the importer does not read (the quieter risk — the import does not fail, it silently carries less than you assume). Neither check proves semantic equivalence.

The category taxonomy

The importer carries each video's numeric category id, because Vidra's built-in list uses PeerTube's ids on purpose. That only works while the source runs the stock 1–18 list. An instance running peertube-plugin-categories does not: it deletes the stock entries and defines its own at higher ids.

So the taxonomy comes across too, into an instance setting that replaces the built-in list when set. Two rules govern a re-run:

  • A source without the plugin gets no override. Absent, disabled or carrying no taxonomy all read the same way: Vidra's built-in list stands.
  • An operator's taxonomy is never clobbered. The ledger records the exact value the import applied. A stored value that still equals it is the import's own and is updated when the source moves; anything else — an edit, a hand-configured taxonomy, a cleared key — is left exactly as it is and reported under conflicts.

Next

  1. Plan your migration — inventory the source, prepare Vidra.
  2. Admin API import — the managed path.
  3. Command-line importer — every flag.
  4. Media migration — copy, reference, or neither.
  5. Cutover and validation — DNS, checks, rollback.
  6. Troubleshooting — what actually goes wrong.