Importing from another platform

The archive — part 1 of 9

./import.sh opens its own wizard: pick a source, and it reads the whole thing in dry-run first and tells you what would be written -- how many posts and media files, the first few slugs, and how many items it skipped and why. Nothing is written until you confirm, and confirming means typing the number of posts rather than pressing a key -- an answer you can't give without having read the preview. The sources cover blog and newsletter platforms (WordPress, Blogger, Ghost, Medium, Substack, a Jekyll/Hugo tree, ...), the social networks (Twitter/X, Mastodon, Bluesky, Instagram, ...), podcast feeds, and the Wayback Machine for a blog whose platform no longer exists at all. The wizard groups them by that question -- a blog you published, a network you posted to, a dead site -- and the full list lives in Importing.

Every source also runs without the wizard, for cron or a scripted migration -- one scripts/migrate_<source>.rb each, e.g. scripts/migrate_bluesky.rb <handle>, scripts/migrate_instagram.rb <export-dir>, scripts/migrate_wayback.rb <https://dead-blog.example>, scripts/migrate_feed.rb <export.xml | feed-url>. Those skip the preview and write immediately; see the README.

A wizard that wrote nothing says so with its exit code. Cancelling leaves with 1, and so does reaching the end of the input without an answer -- which is what a script, a cron line or a pipe does. Zero means the import ran; it never means "nobody answered, so nothing happened".

Two things to expect on a real archive:

  • It is slow, and it says so. Media is downloaded per post, so a few thousand posts run for hours. Every phase reports progress -- what it's reading, how many items it found, then a 12/847 counter -- so a quiet terminal means something is wrong, not that it's working. Sample before committing to that: every script takes LIMIT=20 to import only the first twenty, which is enough to see whether the mapping does what you expect. A second full run then overwrites them in place.
  • The deploy guard will stop you afterwards, because a bulk import is exactly the "file count swung wildly" shape it watches for. That's working as intended: check the numbers, then re-run with --force.
  • The summary names what HTML could not become blocks. Every adapter that parses HTML bodies counts the elements it had to drop -- embeds and forms, usually -- and the summary lists them by name and count, so a silent summary means nothing was lost, not that nobody counted.

Per-source walkthroughs -- where to get each export, what is kept and skipped, undo, troubleshooting -- live in Importing.

Re-running an import is safe -- posts are matched on their source identity and overwritten in place, never duplicated (Importing); the same identity is the safe selector when undoing an import.

Back up content.nosync/ before the first real import (tar czf ../content-backup-$(date +%F).tar.gz content.nosync) -- it isn't in git, and on a server there's nothing else to fall back on.