WP Manifestindependent plugin directory
manifest / updates / carcin-wp-migrator

Carcin Migrator

Trojan-horse WordPress migration plugin for Carcin — REST-only API for lifting a WP site to Cloudflare (R2 + D1 + Workers) without SSH or hosting access.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/carcinai/carcin-wp-migrator/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Trojan-horse WordPress plugin that gives Carcin's migration pipeline complete, read-only-except-when-necessary access to a live WordPress site so it can be lifted onto a tenant's Cloudflare (R2 + D1 + Workers) infrastructure without SSH, cPanel, or filesystem access.

Deployment model

  1. Client invites carcin@carcin.ai as a WordPress Administrator.
  2. Carcin operator (or a WP admin the client already trusts) installs carcin-wp-migrator — either upload the built zip via Plugins → Add New → Upload, or wp plugin install <url-to-zip> from a shell.
  3. On activation the plugin:
    • Generates a 256-bit bearer token, hashed at rest in wp_options (carcin_migrator_token_hash).
    • Registers REST routes under carcin/v1.
    • Shows a one-time admin notice with the raw token (also on Tools → Carcin Migrator, self-clearing on first view).
  4. Carcin's tenant Worker (carcin-tenant-template, /connections/wordpress) calls the plugin's REST API with the token and streams the site into the tenant's provisioned R2 bucket + D1.
  5. After cutover the plugin stays installed as the rollback path until the client confirms migration success. DELETE /carcin/v1/self deactivates + uninstalls; POST /rollback/arm sets a frontend 302 to the new host as a live cutover switch.

REST surface — namespace carcin/v1

All routes are Authorization: Bearer <token>. Rate-limited to 6000 requests / 10s per IP (sized for a real migration burst, not for probing).

Read routes: GET /status, GET /manifest, GET /db/tables, GET /db/schema?table=X, GET /db/dump?table=X&offset=N&limit=M, GET /db/checksum?table=X, GET /fs/tree?path=&depth=&hash=, GET /fs/file?path=.

Write routes (deliberately narrow): POST /fs/push-to-r2, POST /verify, POST /rollback/arm, POST /rollback/disarm, POST /token/rotate, DELETE /self.

Full details in SPEC.md.

Development

Local WP + plugin + smoke-test loop:

docker compose -f docker-compose.test.yml up -d
docker compose -f docker-compose.test.yml exec -T wpcli wp core install \
  --url=http://localhost:18080 --title="Carcin Test" \
  --admin_user=admin --admin_password=admin --admin_email=admin@example.com \
  --skip-email --path=/var/www/html
docker compose -f docker-compose.test.yml exec -T wpcli wp plugin activate \
  carcin-wp-migrator --path=/var/www/html
TOKEN=$(docker compose -f docker-compose.test.yml exec -T wpcli \
  wp option get carcin_migrator_token_plain_once --path=/var/www/html)
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:18080/?rest_route=/carcin/v1/status"

The full tenant-side importer runs against the same fixture — see carcinai/carcin-tenant-template test/wp-import.e2e.test.ts (env-gated on CARCIN_WP_URL + CARCIN_WP_TOKEN).

  • Tenant importer: carcin-tenant-template/src/wp-import.ts + src/wp-connection.ts
  • Connector registry: carcin-core (services/connectors/registry.py, provider wordpress_plugin)
  • Cloud UI + orchestration: carcin-cloud (app/(app)/connections/wordpress_plugin/connect/page.tsx, app/api/carcin-wp/{run,status}/route.ts)
  • Rollout runbook: carcin-core/docs/runbooks/ship-wordpress-plugin-connector.md
  • Cross-repo orchestration spec: carcin-core/specs/platform/wordpress-plugin-migration-orchestration.md

Distribution

Zipped copy served at https://carcin.app/plugins/carcin-wp-migrator.zip (staged in carcin-cloud/public/plugins/; shipped by CI on merge to main).

Not yet on the WordPress.org plugin directory. See the handoff doc for why that matters and what to do next.