17 KiB
17 KiB
v1.0.0-rc.6
06-16-2026
-
- A truncated Grav 2.0 download can no longer be staged. PHP's HTTP stream reports end-of-file when the server, a proxy, or a flaky connection drops mid-transfer, so the kickoff's download loop exited cleanly on a partial file, and the only validation afterward was a 1KB minimum-size check — a release zip cut off at any point past that sailed through and got marked as staged. The wizard then failed at the extract step with the unhelpful
Could not open zip (code 35)(libzip's "truncated zip archive"). The kickoff now cross-checks the bytes received against the response'sContent-Length, verifies the file actually opens as a zip archive (with at least one entry) before the.migratingflag is written, deletes the partial file on any failure, and reports what went wrong — including how many bytes arrived versus how many were expected — along with the remedy (retry, or download the release manually and pointsource_local_zipat it).
- A truncated Grav 2.0 download can no longer be staged. PHP's HTTP stream reports end-of-file when the server, a proxy, or a flaky connection drops mid-transfer, so the kickoff's download loop exited cleanly on a partial file, and the only validation afterward was a 1KB minimum-size check — a release zip cut off at any point past that sailed through and got marked as staged. The wizard then failed at the extract step with the unhelpful
-
- The wizard's extract step now recognizes the libzip error codes that mean the staged zip is damaged (19 not-a-zip, 21 inconsistent, 35 truncated) and explains that the download was interrupted and how to recover (Reset Migration on the admin page, then stage again), instead of printing only the bare numeric code.
- A configured
source_local_zipis validated as a readable archive before staging, so a bad manual download is caught at kickoff with a pointer to re-download and verify it withunzip -t(the file itself is left in place, since the user supplied it). - Disk-full or quota errors while saving the download are now detected at write time instead of silently truncating the file.
v1.0.0-rc.5
06-10-2026
-
- A customized admin path now carries across the migration. Grav 1.7 stores the admin route in
admin.yaml, but Admin 2.0 reads its ownadmin2.yaml, so a site that changed/adminto e.g./backendas a security measure would otherwise revert to the default after migrating. When the source route differs from the/admindefault, the migration now writes it into the stagedadmin2.yaml(normalized to Admin 2.0's/pathform), merging into any existing admin2 config rather than overwriting it. Only the route is carried — it's the one 1.7 admin setting Admin 2.0 has an equivalent for. [#6] - The migrate page now checks whether the
user/data,user/accounts,user/configanduser/envfolders are reachable over the web and, on Apache, offers a one-click fix that blocks them; on other webservers it shows the exact rule to add by hand. - Migration now scans content for URL-based image transforms that bypass Grav's media object (e.g.
image.jpg?cropResize=300,200) and turns on Grav 2.0's newsystem.images.url_actionstoggle in the stagedsystem.yamlwhen it finds any. Grav 1.7 applied these query-string actions with no gate; 2.0 disables them by default because they run with arguments an unauthenticated visitor controls. The normal developer-driven path is unaffected and deliberately left alone: a Twig/Markdown media call likepage.media['x'].cropResize(300,200), or a Markdown image whose file is the page's own media (), is resolved through the media object into a hashed cache URL with no query string and never touches the toggle. Only references Grav can't resolve to page media — absolute/rooted paths,theme:///image://stream paths, files that aren't co-located, and anything hand-written in a theme template — keep their literal?action=URL and need the toggle, so those are what flip it on. External and protocol-relative URLs (https://cdn.example.com/x.jpg?format=webp,//host/x.jpg?…) are skipped, since they're served by the remote host and a CDN's own query string can't be a Grav image action. The report lists the pages and templates involved, and flags any transform that requests an image above thesystem.images.max_pixelsceiling (still refused even with the toggle on) so you can raise the limit or rework it.
- A customized admin path now carries across the migration. Grav 1.7 stores the admin route in
v1.0.0-rc.4
06-03-2026
-
- Migration now scans the source site for Twig-in-content usage (both per-page
process: twig: trueand the site-widesystem.yamlopt-ins) and turns on Grav 2.0's newsecurity.twig_contentgate in the migrated install, so those pages keep rendering after promote. If any Twig-enabled page also reads site config inside Twig, theconfigaccess toggle is enabled too. - Migration now scans Twig-enabled page content for the function and filter calls it uses and re-enables them for Grav 2.0, which tightened Twig security. Raw PHP functions (e.g.
strtoupper) are added to bothsystem.twig.safe_functions/safe_filters(so they're callable at all) andsecurity.twig_sandbox.allowed_functions/allowed_filters(so sandboxed page content may call them); your existingsafe_functionsentries are preserved and merged in. Plugin-provided Twig functions (e.g.unite_gallery) are added to the sandbox allowlist and called out in the report — the providing plugin still needs to register them (ideally via theonBuildTwigSandboxPolicyevent). Functions Grav 2.0 refuses outright —Utils::isDangerousFunction()(system,exec,preg_replace, …) and the sandbox's by-design exclusions (constant,read_file,evaluate, …) — are never added and are listed in the report instead. The sandbox lists are written in full (core defaults plus additions) because Grav merges them by index.
- Migration now scans the source site for Twig-in-content usage (both per-page
-
- Step 1 pre-flight now warns when PHP's
set_time_limit,ignore_user_abort, orproc_openare blocked bydisable_functions(common on managed hosts such as RunCloud). Withset_time_limitdisabled the wizard cannot lift PHP's execution limit, so the long Step 2 copy or the GPM update of many plugins can exceedmax_execution_timeand die with a silent HTTP 500. The warning prints the currentmax_execution_timeand the remedy (raise the host'smax_execution_time/ php-fpmrequest_terminate_timeout/ proxy read timeout). The checks are advisory only and do not block the migration. [#5] - Migration now strips the dead
twig.undefined_functions/undefined_filterskeys from the stagedsystem.yaml— Grav 2.0 removed the blanket undefined-function escape hatch (an unlisted function/filter is now a hard error). The retainedsafe_functions/safe_filterskeys are preserved and merged with anything found in content. Block-style values are rewritten cleanly; a multi-line flow value the rewrite can't safely touch is flagged for you to finish by hand. - After turning on Grav 2.0's Twig in content security gate, the migration now also removes the matching legacy flag from the staged
system.yamlso the migrated install has one setting in one place instead of two doing the same job. An explicit opt-out (pages.process.twig: false) is preserved. - Removing that legacy flag handles more
system.yamlshapes than before: quoted values, files without a trailing newline, Windows line endings, and unrelatedtwig:keys under sibling blocks (which a previous version could accidentally strip too). Thesystem.yamlrewrite is now atomic so an interrupted migration cannot corrupt the staged config. When the legacy flag is written in flow-style (e.g.process: { twig: true }) the migration cannot safely auto-remove it without losing comments, and the wizard now flags this so you can finish the cleanup by hand. - The source-site scanner now recognises the same quoted truthy values the per-page scanner already does, so a 1.x install with
pages.process.twig: "true"correctly turns on the 2.0 security gate after migration.
- Step 1 pre-flight now warns when PHP's
-
- Backup zip created on Windows is now extractable.
mg_zip_webrootwas passingSplFileInfo::getPathname()output straight intoZipArchive::addFile($abs, $rel)— on Windows that meant entry names were stored with native\\separators instead of the/the zip spec requires. Every standards-tolerant extractor (7-Zip, Windows Explorer's in-place viewer, macOS Archive Utility) treated the backslashes as literal filename characters, dumping every file in the zip's root with names likeuser\plugins\admin\file.phpand rendering directory entries as a flat breadcrumb list. Now normalized to/regardless of OS. A standalone repair scriptwizard/mg-repair-backup.phpships in this release for users whose pre-rc.3 Windows backup zips are still on disk — it rewrites the entry names so the zip extracts correctly with any tool.
- Backup zip created on Windows is now extractable.
v1.0.0-rc.3
05-13-2026
-
- Pre-promote callout now warns to close any editor, git GUI (Sourcetree, GitHub Desktop, GitKraken), and terminal that has the webroot open — on Windows these processes hold file handles that block the Phase 2 delete pass.
- Promote step on Windows now runs a pre-flight scan for locked files BEFORE deleting anything, so the wizard reports the specific paths (e.g.
user/plugins/foo/.git/index) the user needs to free, rather than half-destroying the webroot and failing midway. macOS and Linux skip the scan —unlink()succeeds on open files there. - Promote failure callout now names the specific file that couldn't be deleted (e.g.
user/plugins/foo/.git/objects/pack/pack-abc.idx) instead of just the top-level entry, so it's obvious which editor or git GUI to close. - Promote failure callout now includes recovery instructions for the backup zip, including a Windows-specific warning that File Explorer's in-place zip viewer renders nested paths as a flat breadcrumb list (
system·src·Grav·…) and that you must use Right-click → Extract All… rather than dragging entries out. - README has a new Recovering from a failed promote section documenting the three-phase rollback model and platform-specific extraction commands.
-
- nginx config snippet shown in Step 5 (Test) is now actually functional. The previous version put the PHP
location ~ \.php$block as a sibling of thelocation ^~ /grav-2/prefix — but nginx never evaluates sibling regex locations once an^~prefix match wins, so PHP under the stage path was served as a static download. The snippet now nests the PHP block inside the prefix block and addsfastcgi_split_path_infoandfastcgi_indexto match Grav's documented nginx template. [#3] - Outbound HTTP from the migration wizard now honors Grav's
system.http.proxy_urlandsystem.http.proxy_cert_pathsettings (and the standardHTTPS_PROXY/HTTP_PROXY/ALL_PROXYenv vars as fallback). Previously, every HTTP call — the Grav 2.0 zip download, GPM catalog queries, GitHub release lookups, plugin/theme replacement zips, the curated compat registry — built its own stream context with no proxy support and silently failed for sites behind a corporate proxy. Kickoff now forwards the site's proxy config into the.migratingflag at staging time, and the standalone wizard reads it via a newmg_http_context()helper. [#2]
- nginx config snippet shown in Step 5 (Test) is now actually functional. The previous version put the PHP
v1.0.0-rc.2
05-06-2026
-
- Step 2 compatibility breakdown now has a dedicated Will be upgraded bucket for plugins whose installed version reads as 1.7-only but for which GPM has a newer 2.0-compatible release. Previously these were rendered under Incompatible even though Phase 4's
gpm updatewill land the new version — misleading because the user's skip/disable policy doesn't apply to them.
- Step 2 compatibility breakdown now has a dedicated Will be upgraded bucket for plugins whose installed version reads as 1.7-only but for which GPM has a newer 2.0-compatible release. Previously these were rendered under Incompatible even though Phase 4's
-
- Replacement installs (admin2 + api) are now guaranteed even when the curated compatibility registry is offline or has been pruned of those entries — a hardcoded baseline maps
admin → admin2(withrequires: [api]) and is merged under the remote response so any remote entry still wins per slug. - GPM upgrade detection no longer silently fails:
getgrav.org/downloadsreturns the install URL underzipball_url, but the wizard was readingdownload. Normalized insidemg_fetch_gpm_indexso every plugin with a newer 2.0-compatible release on GPM now lands in the Will be upgraded bucket and gets installed via GPM during the upgrade pass (instead of silently falling through to the GitHub fallback path).
- Replacement installs (admin2 + api) are now guaranteed even when the curated compatibility registry is offline or has been pruned of those entries — a hardcoded baseline maps
v1.0.0-rc.1
05-04-2026
-
- Two reset modes — Restart Wizard keeps the downloaded Grav 2.0 zip and lets you re-run from step 1, Reset Migration wipes everything and starts over.
-
- Plugin upgrade lookups now ask GPM for the release that fits Grav 2.0 specifically, so suggested upgrades reflect what actually works on the destination.
- Plugin upgrades during migration are offered for any plugin with a newer 2.0-compatible release on GPM, not only those in the curated compatibility registry.
- Replacement installs (admin2, api, etc.) now fall back to the newest tagged GitHub release — including beta tags — when a plugin isn't on GPM yet.
- Plugin updates during Copy & Migrate now run through Grav 2.0's own
bin/gpm, matching how a regular admin update behaves. - Compatibility breakdown table groups rows by status with per-bucket counts (Compatible / Needs update / Incompatible / Will be installed) and color-coded labels for where each verdict came from.
- Symlinked plugins and themes are preserved through the migration, so developer setups with linked plugin clones don't get clobbered.
- Long-running steps (bulk copy, plugin upgrade) no longer time out on shared hosts with low
max_execution_time. - The "already staged" error when starting a new migration now points at the Restart/Reset buttons instead of asking you to delete files by hand.
-
- Recursive delete during reset no longer follows symlinks — protects real files outside the staged tree.
- Plugin upgrade pass no longer clobbers plugins that are about to be replaced (admin → admin2, etc.).
- Compatibility policy (skip/disable) now applies after the upgrade pass, so freshly upgraded 2.0-compatible plugins aren't then disabled.
- CLI php detection handles hosts where
PHP_BINARYpoints atphp-fpmorphp-cgi.
v1.0.0-beta.5
04-25-2026
v1.0.0-beta.4
04-21-2026
-
- Default source URL now points at the released Grav 2.0 beta
grav-updatepackage (https://getgrav.org/download/core/grav-update/2.0.0-beta.1?testing) instead of a local dev zip. The update package ships system/vendor/bin only (no baselineuser/pages) — this avoids polluting migrated sites with default home/typography pages that the full install package would otherwise drop on top of the source content. - Staging flow reworked around a single bulk copy: Step 2 now copies the entire source
user/directory verbatim into the staged install (including any custom folders beyond plugins/themes/accounts), then applies plugin compat policy, auto-updates, and replacement installs in place. Step 3 becomes a transform-only step that rewritesadmin.*→api.*on the already-copied account yamls. Step 4 is a confirmation/summary of what landed in stageduser/. - Staged layout is now package-agnostic. After extract,
user/,user/{plugins,themes,accounts,config,data,pages}/, and a root.htaccess(materialized fromwebserver-configs/htaccess.txt) are created when missing, so downstream steps work whether the source zip isgrav-update,grav, orgrav-admin. - Theme handling and messaging: themes are always kept as-is (skip policy no longer removes them); incompatible themes render as ⚠ "Kept — Twig 3 compatibility enabled (verify before promoting)" rather than a scary ✗. Step 2 intro and stream subtitles explain the Twig 3 compat layer and that custom/unmarked themes are expected to work through it.
- Top-level
user/dotfiles (.git,.DS_Store, editor backups) and symlinks are explicitly excluded from the bulk copy and recorded in the step summary.
- Default source URL now points at the released Grav 2.0 beta
-
do_plugins_themesanddo_contentno longer abort with "Source or staged user/ missing" when the source package isgrav-update(which ships nouser/). Extract normalizes the skeleton first.mg_patch_staged_htaccess(used by the Test step to setRewriteBasefor sub-path testing) no longer fails ongrav-update-based stages — the extract step materializes.htaccessfrom the zip'swebserver-configs/htaccess.txttemplate when missing.
v1.0.0-beta.3
04-20-2026
v1.0.0-beta.2
04-16-2026
-
- Preserve executable bits on
bin/*during staged zip extract. The rawfwrite()-based extractor dropped the mode stored in the zip's central directory, landingbin/grav,bin/gpm,bin/plugin, andbin/composer.pharat0644post-migration and breaking CLI tooling on the fresh 2.0 install. Extract now honors the zip's unix mode when present, with a safety-netchmod 0755for anything directly underbin/so test-built zips (which omit mode metadata) also work.
- Preserve executable bits on