# Building Release Packages ## Overview The updater rolls a ZIP out over the application root: every file in the package is copied to the same relative position in the project. For that to work, the package has to be cut correctly. Relevant files: - `manage-client/lib/updater.php` – checking, extracting, deploying - in this package: `scripts/create-release-zip.sh` – ready-made build script ## Package layout The root of the ZIP **is** the application root. No extra top-level directory: ```text correct wrong ------- ----- index.php myproject-v1.3.0/index.php admin/orders.php myproject-v1.3.0/admin/orders.php includes/version.php myproject-v1.3.0/includes/version.php manage-client/lib/client.php migrations/2026-08-20-01-x.php ``` A package with a top-level directory wouldn't update the project — it would create a new subfolder. ## What belongs in it - all application files - `includes/version.php` (or the configured version file) with the **new** version - `manage-client/` **without** `config.php` — this is how the client itself gets updated too - `migrations/`, if the release brings migrations along ## What must stay out | Exclusion | Reason | |---|---| | `config.php` | holds the target installation's credentials | | `manage-client/config.php` | holds the target installation's instance token | | `data/` | the target installation's operational data | | `.git/` | doesn't belong on a production server | | `build/`, `storage/` | build artifacts | `config.php`, `data/` and `.git/` are additionally protected via `MANAGE_UPDATE_PROTECTED_PATHS`: even if they end up in the package by accident, they're skipped during deployment. Excluding them at build time is still necessary, because otherwise the package contains someone else's credentials and can be downloaded from the Manage server. ## Version number The format is `vX.Y.Z` — no suffix, no prefix. Both client and server reject anything else. The version lives in exactly one place: the version file inside the package. The client never writes it itself; it changes as a side effect of copying files. If it's forgotten at build time, the instance keeps reporting the old version after the update and keeps offering the same update again. ## Building with the bundled script This package contains `scripts/create-release-zip.sh`. The script gets copied to `scripts/` of the project, adjusted once per project at the top (product name, version file, exclusions), and then run from the project directory: ```bash ./scripts/create-release-zip.sh v1.3.0 ``` The script 1. writes the version into the version file, 2. packs every file tracked by Git, minus the exclusion list, 3. prints SHA-256 and size. It uses `git ls-files`, so only checked-in files end up in the package — local experiments and ignored files stay out automatically. ## Building by hand ```bash cd /path/to/project zip -r ../myproject-v1.3.0.zip . \ -x 'config.php' \ 'manage-client/config.php' \ 'data/*' \ '.git/*' \ 'build/*' ``` Check what's actually in it — this step always pays off: ```bash unzip -l ../myproject-v1.3.0.zip | head -30 ``` ## Publishing In the Manage server under **Releases**: enter the version, upload the ZIP. Checksum and size are computed by the server itself; they are never taken from the uploader. An upload automatically sets the release as current. ## What the client checks when deploying 1. Size and SHA-256 must match the manifest, otherwise the file is deleted. 2. Every entry in the ZIP is checked against path traversal (`..`, absolute paths, drive letters, null bytes). 3. The package must contain at least one of the paths from `MANAGE_UPDATE_SANITY_PATHS`. 4. While copying, every existing target file is first backed up to `MANAGE_UPDATE_BACKUP_DIR`. ## Limits of this approach - **Deleted files are not removed.** Deployment is an overlay. A file no longer present in the new release stays behind in the installation. To make it truly disappear, that belongs in a migration. - **No maintenance mode.** The application stays reachable while files are being copied. For larger changes, update during a quiet period. - **No rollback.** The backup copies in `MANAGE_UPDATE_BACKUP_DIR` are meant for manual restoration; there's no command for it. Only the most recent run is kept. ## Next - [07_POST_UPDATE_HOOKS](07_POST_UPDATE_HOOKS.md) – migrations in the package - [04_FUNCTION_API](04_FUNCTION_API.md) – `manageUpdateApply()`