# CPalius CMF 2.0.1

**Released:** 2026-09-16 · **Channel:** stable · **Critical:** yes

2.0.0 could take a site down while updating to it. This release fixes the cause
and ships a recovery script for sites that are already stuck.

**If your site is currently showing `Table 'cp_users' doesn't exist`, skip to
"Recovering a stuck site" below — you cannot install this release through the
panel, because the panel is what is broken.**

Turkish: [2.0.1-tr.md](2.0.1-tr.md)

## What went wrong

`AACPUpdateController::upgrade()` wrote the new files to disk and then rendered
the updates screen. Migrations were deliberately left to "the next request via
the existing apply step".

That screen is served by the code that just landed, against a database that has
not moved yet. Every release before this one could survive that, because no
release before this one renamed a table. 2.0.0 renamed all of them, so the
render died on `Table 'cp_users' doesn't exist` — and because the thing that
died was the panel itself, the operator could never reach the button that would
have run the migrations. The site was down with no way back in.

## The fix

**Migrations now run in the same request that writes the files, before anything
renders.** They are the one step that survives that window: they need the DBAL
connection and the migration classes on disk, and neither depends on the
compiled container that is about to be thrown away. Update hooks, module
upgrades and the cache rebuild still wait for the next request, because those do
run project services through the old container.

**The upgrade route redirects instead of rendering**, which is what the patch
route has done since 1.1.3 for the same reason. A redirect needs the router and
nothing else; rendering the updates screen walks half the service graph through
a container that no longer matches the code on disk.

## Recovering a stuck site

If 2.0.0 already broke your site, the application cannot repair itself — the
panel is unreachable. `public/cp-2.0.0-recover.php` does it from outside, using
PDO directly: no Symfony, no container, no Doctrine.

1. Ask us for `cp-2.0.0-recover.php`. It is deliberately not in the repository:
   it is a one-off with no access control of its own, meant to be deleted the
   moment it has done its job.
2. Upload it to your site's `public/` folder by FTP.
3. Open `https://yoursite/cp-2.0.0-recover.php?dry=1`. This prints the plan and
   writes nothing.
4. If the plan looks right, open the same URL without `?dry=1`.
5. Empty `cp-core/var/cache/prod/`, keeping the folder.
6. If your site uses Redis, clear the Doctrine cache pools.
7. **Delete the script.**

It performs exactly what the 2.0.0 migration performs, in the same order, with
the same guards, and records the migration as executed so the normal runner does
not repeat it. Every step is guarded, so running it twice is harmless. It was
verified against a real 1.x database before release.

## Upgrading normally

A site still on 1.x can take 2.0.1 directly through AACP → Updates and will not
hit the 2.0.0 problem: the schema moves in the same request as the files.

**Back up the database first.** The 2.0.0 schema migration is not reversible;
that has not changed.

## Known limits

The recovery script reads `DATABASE_URL` out of `.env` with a small parser rather
than booting Symfony, because on a stuck site Symfony
is exactly what cannot boot. If your `.env` sets it through a mechanism other than a plain
`KEY=value` line, the script will not find it. It has no access control of its
own, so delete it as soon as it has done its job.
