DEV Community

John
John

Posted on

Migrating to Hubs Community Edition: what survives the move, and what you rebuild by hand

Your files survive the move to Hubs Community Edition. Your rooms do not. Scene GLBs, avatar GLBs and uploaded media are portable binary assets that carry across cleanly, but the database rows that turn those assets into a working room, the short room IDs, the permalinks, the accounts, the per room permissions and the moderation settings are all Reticulum state that a fresh deployment generates from scratch. Plan the migration as an asset transfer plus a manual rebuild of everything that was a link, a login or a setting.

TL;DR by reader profile

  • Agency hosting client spaces (a five person studio running 12 branded client showcases): migrate the scenes, rebuild the rooms, and budget for reissuing every client facing URL, because the old links are the single largest source of post migration support tickets.
  • Education team (a university department with 40 seminar rooms and one shared campus scene): migrate the handful of scenes that matter and re-create rooms per term, since most seminar rooms were disposable and rebuilding them is faster than reconstructing their state.
  • Events company (a three person team that spins up a venue scene per event): migrate scene sources and avatars, then invest the saved time in the Dialog SFU and TURN setup, because live audio quality decides whether the event works at all.
  • Solo creator or community organiser (one person running a monthly meetup room): expect a single evening of work, mostly re-uploading avatars and re-creating one room, and accept that your bookmarked room link is gone for good.
  • Enterprise or public sector team (an internal comms group with retention obligations): treat the migration as a new system build with its own data map, because self hosting moves recordings, uploads and access logs onto infrastructure you now have to account for.

The tradeoff is simple: binary assets move almost for free, while identity, links and configuration cost you manual work that scales with the number of rooms you kept, not the number of gigabytes you stored.


Table of contents


What survives the move to Hubs Community Edition, and what does not?

Split your inventory into two piles before you touch anything. Binary assets survive because they are just files on disk: a published Spoke scene is a glTF 2.0 binary, a custom avatar is a .glb with a small set of texture overrides, and an uploaded image, PDF or video is the original file plus a generated thumbnail. Everything else is Reticulum state, which means rows in a Postgres database that a new deployment creates fresh and never inherits.

Item What survives the move What you rebuild by hand
Published scenes The scene GLB and its textures, byte for byte The scene entry, its listing, its thumbnail and its owner
Spoke projects The project source and any assets you exported The project entry in the editor, plus a republish per scene
Custom avatars The base GLB and override maps The avatar record, its name and its assignment to an account
Room media The uploaded originals in the storage directory The link between each file and the room it was pinned in
Rooms Nothing automatic Room name, scene binding, occupancy limit, permissions, pinned objects
Accounts and links Nothing automatic Every account, every role, and every short room ID in a shared URL

The practical consequence is that migration effort tracks your room count, not your storage size. Twelve rooms sharing one 80 MB scene is a longer job than one room referencing 4 GB of uploads, because the 4 GB copies in a single rsync and the twelve rooms do not.


How Reticulum stores rooms, scenes, avatars and media

Reticulum, the Elixir backend behind Hubs, splits your world into two stores that fail independently. Postgres holds the meaning, and a storage directory on disk holds the bytes. A migration that copies one without the other produces either empty rooms or unreachable files, so map both before you plan anything.

  • Rooms live entirely in Postgres: a hub row carries the short room ID used in the URL, the display name, the maximum occupancy, the member permissions bitfield and the binding to a scene. Nothing about a room exists as a file you can copy.
  • Scenes and avatars are rows that point at files: the scene or avatar record holds the name, description, attribution and its own short ID, then references the GLB and thumbnail through file records. Copy the GLB without the row and Hubs has no way to list it.
  • Uploaded media are owned file records: each upload gets an ID, a content type and an access key that the delivery URL must present, so the raw file on disk is not addressable unless the matching row exists in the database.
  • Storage separates temporary from permanent: files dropped into a room start in an expiring area and are promoted to permanent storage when someone pins them, which is why unpinned uploads disappear from a copy taken later.
  • Pinned objects are room state, not files: position, rotation, scale and the object reference sit in their own rows, so a pinned image restores as a floating object only if both the file and that row survive.

Take pg_dump and rsync of the storage path together, from the same moment.


Do your existing room links still work after the move?

No. A Hubs room URL is a hostname plus a short room ID plus a decorative slug, and only the first two parts matter. The slug is ignored on resolution, which is why renaming a room never broke its link, but the hostname is now yours and the short ID is regenerated by your own Reticulum instance. Every link you ever pasted into a calendar invite, a client deck or a printed programme resolves to nothing you control.

  • The hostname is unrecoverable: you cannot issue a 301 redirect from a domain you never owned, so any bookmark pointing at the old service is dead and no DNS change on your side fixes it.
  • Short IDs are generated, not carried: creating a room on your instance produces a new ID, so the same scene republished twice gives you two different room addresses with identical content.
  • You can force the ID if you insert it: seeding the hub row with a chosen short ID makes the path component match the old link exactly, which turns a broken URL into one that only needs the domain swapped.
  • Six digit entry codes are per deployment: the short code flow people used to jump from a headset browser into a room only knows about rooms in your database, so every previously shared code is invalid.
  • Embeds break silently: an iframe embedded in a client site keeps rendering its container and fails inside, so nobody reports it until a meeting starts.

Budget one communication task per audience, not per room. Twelve client rooms usually means three emails, not twelve.


Which parts of a Spoke scene transfer, and which need republishing?

Treat the published scene and the editable project as two separate things, because only one of them is self contained. The published output is a GLB carrying Hubs specific behaviour in the MOZ_hubs_components glTF extension, so spawn points, waypoints, audio zones, mirrors and the generated navmesh all travel inside the file. The project is a JSON document full of absolute URLs, and those URLs pointed at an asset host that no longer answers.

  • The published GLB renders immediately: drop it into your own instance, bind a room to it, and geometry, materials, baked lighting settings, collision and navigation behave exactly as before, with no re-authoring.
  • The project file opens with holes: your .spoke project references every imported model, texture, image and audio clip by URL, so reopening it in your own Spoke gives you a scene tree with unresolved assets rather than a working edit.
  • Third party imports are the worst offenders: anything pulled in from Sketchfab or a media search result was referenced rather than embedded, so those nodes need re-importing one by one against your own configured providers.
  • Scene metadata does not ride along: name, description, attribution credits and the scene thumbnail are database fields, so a transferred GLB appears as an untitled entry until you set them.
  • Republishing regenerates derived data: a fresh publish rebuilds the navmesh and the thumbnail, which is why a scene you can only supply as a GLB stays frozen at its last published state.

Migrate GLBs for continuity, and re-import projects only for the 2 or 3 scenes you still expect to edit.


Custom avatars: what carries over and what you re-upload

Avatars split the same way scenes do, and the derived ones cause the trouble. A fully custom avatar is a single GLB you can copy anywhere. A derived avatar is not a model at all: it is a reference to a parent avatar plus texture overrides, typically a base map, an emissive map, a normal map and an ORM map. Move the child without the parent and it resolves to nothing.

  • Standalone avatar GLBs transfer intact: rig, skin weights, blend shapes and materials all survive, so a bespoke client mascot loads on your instance the moment you re-upload it and give it a name.
  • Derived avatars need their parent first: upload the base avatar, then the overrides, in that order, or the child record points at a parent ID your database has never seen.
  • The default avatar set is not your data: the stock avatars ship with the client and a fresh deployment starts with its own listings, so any curated in house set has to be re-created as avatar listings in the admin panel.
  • Nobody keeps their selection: avatar choice is stored against an account, and since accounts do not migrate, every returning user lands on a default avatar and picks again on first entry.
  • Ready Player Me links keep working: avatars pulled in by URL are fetched at use time rather than stored as your records, so that path survives the move untouched.

For a team of 20 with one branded avatar each, expect the uploads to take under an hour and the re-selection support requests to trickle in for a fortnight.


Uploaded files, orphaned assets and the storage directory

Open the storage directory and you will find no filenames. Files are written under opaque identifiers with no extension, so a 4 MB blob could be a client logo, a PDF or a thumbnail, and only the matching database row tells you which. This is why a partial migration is so hard to debug: the disk looks healthy, the rooms look empty, and nothing in the file tree explains the gap.

  • Reconcile counts before you trust the copy: run find /storage -type f | wc -l on both sides and compare it with the number of file rows in Postgres, because a mismatch found now is a five minute fix and a mismatch found later is an archaeology project.
  • Orphans come in two directions: files with no row are dead weight you can delete, while rows with no file are worse, since Hubs renders the object placeholder and then fails to load it in front of a client.
  • Derivatives inflate your storage estimate: every image carries a generated thumbnail alongside the original, so the directory size is not the sum of what people uploaded and copying only the originals leaves visible gaps in room listings.
  • Large video is served as it was uploaded: without a transcoding step in your stack, a 500 MB source file is what every visitor streams, so audit the biggest 20 files before you size bandwidth.
  • Permissions travel with the copy: use rsync -a and keep ownership consistent with the user your Reticulum container runs as, or uploads succeed and reads fail.

Copy files first, restore the database second, then verify one room per scene.


Accounts, permissions and moderation settings you rebuild by hand

There is one piece of good news here: Hubs never stored passwords. Login is a magic link sent to an email address, so nobody needs a reset campaign and you are not migrating credentials. What you are migrating is the absence of everything else, because an account row carries the identity, the admin flag and the ownership of every scene and avatar that person created.

  • Every account starts again: a returning user enters the same email address on your domain and receives a brand new account, with no history, no owned scenes and no saved avatar.
  • Ownership does not follow the asset: a scene you re-upload as an admin is owned by the admin account, so the designer who built it can no longer edit or republish it without you reassigning the record.
  • The first admin comes from configuration, not the database: your deployment nominates an admin email before anyone logs in, and that is the only account that can reach the admin panel to promote the next one.
  • Room permissions are per room defaults you set again: the toggles governing who may pin objects, spawn media, draw, share a camera or fly are stored on the hub row, so a locked down client showcase reverts to the default permissive setup unless you reapply it.
  • Moderation state is gone: closed rooms, kicked users and any ban you applied were tied to identities on the old deployment and have no meaning against new account IDs.

For 20 users across 12 rooms, the account side is self service and costs you nothing, while the permission side is roughly 12 short admin tasks.


Which third party integrations stop working until you supply your own keys?

The hosted service quietly held API credentials on your behalf. Your deployment holds none, so several features that felt native to Hubs become inert until you register for accounts and add keys to your configuration. One of them is not optional: without working outbound email, nobody can log in at all, because the magic link never arrives.

Integration State on a fresh deployment What it needs from you
Magic link login Broken, blocks all sign in SMTP credentials or a transactional mail provider, plus SPF and DKIM records
Sketchfab import Search returns nothing A Sketchfab API token in your Reticulum configuration
GIF and image search Empty results panel A key per provider, one for GIFs and one for image search
Video links from streaming sites Paste works, playback varies A resolver service reachable from your instance, checked per site
Discord room binding Absent The separate Hubs Discord bot deployed with its own bot token
Third party media embeds Blocked by the browser A working CORS proxy host, included in your content security policy

Two consequences matter for a migration plan. First, test the email path before you announce anything, because every other check depends on being able to sign in. Second, audit your existing scenes for objects that came from a search panel, since those nodes will render as broken references until the matching provider key is in place. A scene with 30 imported models can hide 3 or 4 of these, and they only surface once someone walks up to them.


How long does a 50 room migration take, and how do you verify it?

Do not estimate the whole job. Time one room end to end, then multiply, because the per room work is almost perfectly repetitive. Create the room, bind the scene, set the permissions, re-pin the objects, capture the new URL. If that loop takes you 4 minutes, 50 rooms is a little over 3 hours of clicking, and the asset copy underneath it happens once regardless of room count.

  • Front load the one time work: the file copy, the database restore, the email configuration and the provider keys are a single block of effort, and none of it repeats when you add the fiftieth room.
  • Batch by scene, not by room: rooms sharing one scene need that scene republished once, so ordering your list by scene turns 50 tasks into as few as 8 or 10 real pieces of work.
  • Verify with two browser windows: join the same room twice on different profiles, confirm you hear yourself on the second connection, and confirm both avatars move, which tests the SFU and the state channel together.
  • Watch the network panel on entry: a room that looks correct can still be firing failed requests for pinned media, so treat anything other than zero failed asset loads as unfinished.
  • Test one room on an actual headset: desktop entry passes long before the headset path does, and problems here are usually certificate or host related rather than anything to do with your migrated content.

Reserve a quarter of your total budget for the last 3 rooms. The stragglers are always the ones with imported models and pinned video.


Where do you run Hubs Community Edition, and what does the stack need?

Hubs is not one container. A working deployment runs the Reticulum backend, the web client, Spoke, the Dialog SFU that carries voice and video, a TURN server for clients behind restrictive networks, Postgres and a storage volume. That shape drives the hosting decision more than CPU or RAM does, because real time media has requirements a typical web app never raises.

  • You need several hostnames, not one: the app, the asset host, the media server and the CORS proxy each answer on their own name, all on HTTPS, and your content security policy has to list them consistently or the client blocks its own assets.
  • UDP is the hard requirement: WebRTC media flows over a UDP port range to the SFU, and any host that only exposes TCP 443 forces every participant onto TURN relaying, which changes your bandwidth profile completely.
  • TURN is not optional in practice: corporate networks routinely block direct paths, so plan for a relay listening on the standard 3478 and 5349 ports even if your own testing never touches it.
  • Postgres and storage are the state: everything else can be recreated from images, so your backup job only has to cover the database and the storage directory.
  • Your options span the usual ground: a self managed VPS, a home server, a NAS or a managed personal server. Yundera is a managed Personal Cloud Server, built on CasaOS, that runs self-hosted apps as Docker containers on a server dedicated to the user.

Choose the host on its networking behaviour first. A machine that cannot pass UDP will run every meeting through a relay.

Top comments (0)