Skip to content

✨ Added production site import, with the cutover documented (S5e) - #382

Merged
acburdine merged 5 commits into
next-dockerfrom
claude/s5e-production-import-cutover-a0f4a7
Oct 9, 2026
Merged

acburdine merged 5 commits into
next-dockerfrom
claude/s5e-production-import-cutover-a0f4a7

Conversation

@acburdine

@acburdine acburdine commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/PLA-479/s5e-production-import-and-cutover

install --import now accepts bundles of production Ghost-CLI sites. The cutover itself is documented, not automated, as decided on 2026-10-07.

What the import does

  • Installs the bundle as an ordinary production site (Ghost, MySQL, Caddy), on the hosts of the bundle's url and adminUrl. --domain, --admin-domain and --email override them.
  • Refuses a source URL Caddy can't serve as it is (plain http, a port, a path) before anything is written. The message names the --domain that would serve the site at that host's root, which changes its address.
  • On a domain other than the source's, drops the source's admin domain with a warning, so a rehearsal copy never claims the live admin domain.
  • Refuses --domain/--admin-domain/--email with a local bundle, --local or --with mailpit with a production bundle, and portable production bundles (the message points to the Ghost Admin route).
  • Keeps the failure contract and "never stop anything" unchanged: where nginx still holds 80/443, Caddy fails to start, the error names the port, and the import removes what it created.
  • Leaves mail, Mailgun and Stripe settings in place, because a move needs them.

Docs

docs/install.md covers importing a production site and moving one: on the same server (stop nginx yourself; how to recover if the import fails; disable nginx and the ghost_* unit afterwards), to another host (DNS), and as a rehearsal copy. The rehearsal steps clear mail__*/bulkEmail__* from ghost.env and the Mailgun/Stripe settings and webhooks from the database. The setting names were checked against Ghost's schema.

Testing

  • manager: format, lint, typecheck and unit tests pass (482).
  • New tests/e2e/production-import.sh in CI (Production import on Linux). It installs a real Ghost-CLI production source (MySQL, systemd, nginx on 80, admin domain), then:
    • forces a failure by importing with nginx still running, and checks the directory is untouched and the documented recovery works;
    • performs the documented same-server move, then checks the exact version, staff sign-in on the admin domain, the post, the image and the config through Caddy, plus check.
  • It changes the host, so it refuses to run without GD_TEST_HOST_CHANGES=1. It hasn't run anywhere yet; this PR's CI is its first run.
  • The cross-host move stays a manual acceptance run.

🤖 Generated with Claude Code

ref https://linear.app/ghost/issue/PLA-479/s5e-production-import-and-cutover

Production Ghost-CLI sites could be exported but not imported, so main's
scripts/migrate.sh was still the only way to move one. Since 2026-10-07
the cutover is documented rather than automated, so the importer only has
to install the bundle as an ordinary production site. Stopping the
source, its nginx and moving DNS are the operator's steps.

A production site is served on the bundle's own domains unless options
name others. A source URL that Caddy cannot serve as it is (plain http, a
port or a path) is refused before anything is written, rather than served
at a different address. On a domain other than the source's, the
source's admin domain is dropped with a warning, so a rehearsal copy
never claims the live admin domain.

The importer does not strip mail, Mailgun or Stripe settings: a move
needs them. The rehearsal docs say how to clear them in the copy.

- manager/src/import.ts: accept production bundles, derive and check
  their domains, refuse portable production bundles and mode options
  that contradict the bundle.
- manager/src/commands/install.ts: plan an import from the bundle and
  share the production checks; production imports' next steps name DNS
  and the source's staff accounts instead of creating an owner.
- manager/test/import.test.ts: production imports, overrides, the admin
  domain rules and each new refusal.
- docs/install.md: importing a production site, and moving one on the
  same server, to another host, and as a rehearsal copy.
- docs/ghost-cli-replacement.md: leave only S5e's space and ownership
  work and its Linux acceptance runs.
- README.md, help, docs/architecture.md, bundle-v1.md, configuration.md:
  import is no longer local-only.
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 2f15511e-e7ac-4682-a5b8-8c1755696e0f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

ref https://linear.app/ghost/issue/PLA-479/s5e-production-import-and-cutover

Unit tests stop at a scripted daemon, so nothing showed that a real
Ghost-CLI production bundle imports, or that the documented same-server
move works with nginx in the way. S12 must not merge next-docker into
main before S5e's acceptance passes; a CI scenario keeps that true
afterwards rather than relying on one manual run.

The scenario installs a production source with Ghost-CLI's own MySQL,
systemd and nginx setup and an admin domain, forces a failure by leaving
nginx on port 80, checks the documented recovery, then performs the move
and checks the site through Caddy with Caddy's test CA. It changes the
host, so it refuses to run without GD_TEST_HOST_CHANGES=1. The cross-host
move cannot be shown on one runner and stays a manual acceptance run.

- tests/e2e/production-import.sh: the scenario.
- .github/workflows/test.yml: a job that provides Ghost-CLI, nginx and
  MySQL on the runner and runs it.
- README.md: list the script.
- docs/ghost-cli-replacement.md: say what CI covers and what is manual.
ref https://linear.app/ghost/issue/PLA-479/s5e-production-import-and-cutover

Ghost-CLI 1.33.3's migrate-export failed on every standard production
install: it copies content owned by the ghost user with sudo, and passed
ui.sudo shell strings after ui.sudo had begun to accept only arrays.
1.33.4 fixes it (TryGhost/Ghost-CLI#2405), so it is the minimum for
exporting, and what the end-to-end scenarios run.

- tests/e2e/import.sh, .github/workflows/test.yml: run 1.33.4.
- docs/install.md, docs/bundle-v1.md, pages/index.html: the minimum,
  and the exporter documentation at that tag.
- manager/src/bundle/manifest.ts: the re-export advice names it.
ref https://linear.app/ghost/issue/PLA-479/s5e-production-import-and-cutover

The exporter refuses an existing output, even with --force, so the
move's final export failed on the path the forced-failure export had
already written. An operator meets the same refusal after a rehearsal,
so the rehearsal steps now say to export to a new path.

- tests/e2e/production-import.sh: export the final bundle to its own path.
- docs/install.md: say the move's export goes to a new path.
ref https://linear.app/ghost/issue/PLA-479/s5e-production-import-and-cutover

The import and Caddy's verification of both domains passed, but signing
in on the imported site answered 500. In production Ghost emails staff
a code when they sign in from a new device, and the test site has no
mail service, so sending the code throws. The source's config now turns
the check off, which the import carries like any other setting, and the
scenario checks it reached Ghost. A failed sign-in now prints Ghost's
answer and logs.

Operators meet the same check on a real site, so the docs say the first
sign-in needs working mail.

- tests/e2e/production-import.sh: the setting, its check, and the
  failure output.
- docs/install.md: the first sign-in sends a code by email.
@acburdine
acburdine merged commit d8e1e8a into next-docker Oct 9, 2026
13 checks passed
@acburdine
acburdine deleted the claude/s5e-production-import-cutover-a0f4a7 branch October 9, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant