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.

by Carcin · github.com/carcinai/carcin-wp-migrator · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/carcinai/carcin-wp-migrator/archive/refs/heads/main.zip

Readme

carcin-wp-migrator

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).

Related repos

  • 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.

Read the full README on GitHub →