379 lines
13 KiB
Markdown
379 lines
13 KiB
Markdown
|
PHAR Updater
|
||
|
============
|
||
|
|
||
|
[![Build Status](https://travis-ci.org/padraic/phar-updater.svg?branch=master)](https://travis-ci.org/padraic/phar-updater)
|
||
|
|
||
|
You have a phar file to distribute, and it's all the rage to include a self-update
|
||
|
command. Do you really need to write that? Here at Humbug Central, our army of
|
||
|
minions (all ten of them) have written one for you.
|
||
|
|
||
|
**Table of Contents**
|
||
|
|
||
|
- [Introduction](#introduction)
|
||
|
- [Installation](#installation)
|
||
|
- [Usage](#usage)
|
||
|
- [Basic SHA-1 Strategy](#basic-sha-1-strategy)
|
||
|
- [Github Release Strategy](#github-release-strategy)
|
||
|
- [Rollback Support](#rollback-support)
|
||
|
- [Constructor Parameters](#constructor-parameters)
|
||
|
- [Check For Updates](#check-for-updates)
|
||
|
- [Avoid Post Update File Includes](#avoid-post-update-file-includes)
|
||
|
- [Custom Update Strategies](#custom-update-strategies)
|
||
|
- [Update Strategies](#update-strategies)
|
||
|
- [SHA-1 Hash Synchronisation](sha-1-hash-synchronisation)
|
||
|
- [Github Releases](#github-releases)
|
||
|
|
||
|
Introduction
|
||
|
============
|
||
|
|
||
|
The `humbug\phar-updater` package has the following features:
|
||
|
|
||
|
* Full support for SSL/TLS verification.
|
||
|
* Support for OpenSSL phar signatures.
|
||
|
* Simple API where it either updates or Exceptions will go wild.
|
||
|
* Support for SHA-1 version synchronisation and Github Releases as update strategies.
|
||
|
|
||
|
Apart from the detailed documentation below, you can find the package being used
|
||
|
in almost every possible way within [Humbug's self-update command](https://github.com/padraic/humbug/blob/master/src/Command/SelfUpdate.php) as part of a `Symfony Console`
|
||
|
based PHAR which you may freely reuse.
|
||
|
|
||
|
Installation
|
||
|
============
|
||
|
|
||
|
```
|
||
|
composer require padraic/phar-updater
|
||
|
```
|
||
|
|
||
|
The package utilises PHP Streams for remote requests so it will require the openssl
|
||
|
extension and the `allow_url_open` setting to both be enabled. Support for curl
|
||
|
will follow in time.
|
||
|
|
||
|
Usage
|
||
|
=====
|
||
|
|
||
|
The default update strategy uses an SHA-1 hash of the current remote phar in a
|
||
|
version file, and will update the local phar when this version is changed. There
|
||
|
is also a Github strategy which tracks Github Releases where you can upload a
|
||
|
new phar file for a release.
|
||
|
|
||
|
### Basic SHA-1 Strategy
|
||
|
|
||
|
Create your self-update command, or even an update command for some other phar
|
||
|
other than the current one, and include this.
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* The simplest usage assumes the currently running phar is to be updated and
|
||
|
* that it has been signed with a private key (using OpenSSL).
|
||
|
*
|
||
|
* The first constructor parameter is the path to a phar if you are not updating
|
||
|
* the currently running phar.
|
||
|
*/
|
||
|
|
||
|
use Humbug\SelfUpdate\Updater;
|
||
|
|
||
|
$updater = new Updater();
|
||
|
$updater->getStrategy()->setPharUrl('https://example.com/current.phar');
|
||
|
$updater->getStrategy()->setVersionUrl('https://example.com/current.version');
|
||
|
try {
|
||
|
$result = $updater->update();
|
||
|
$result ? exit('Updated!') : exit('No update needed!');
|
||
|
} catch (\Exception $e) {
|
||
|
exit('Well, something happened! Either an oopsie or something involving hackers.');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
If you are not signing the phar using OpenSSL:
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* The second parameter to the constructor must be false if your phars are
|
||
|
* not signed using OpenSSL.
|
||
|
*/
|
||
|
|
||
|
use Humbug\SelfUpdate\Updater;
|
||
|
|
||
|
$updater = new Updater(null, false);
|
||
|
$updater->getStrategy()->setPharUrl('https://example.com/current.phar');
|
||
|
$updater->getStrategy()->setVersionUrl('https://example.com/current.version');
|
||
|
try {
|
||
|
$result = $updater->update();
|
||
|
$result ? exit('Updated!') : exit('No update needed!');
|
||
|
} catch (\Exception $e) {
|
||
|
exit('Well, something happened! Either an oopsie or something involving hackers.');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
If you need version information:
|
||
|
|
||
|
```php
|
||
|
use Humbug\SelfUpdate\Updater;
|
||
|
|
||
|
$updater = new Updater();
|
||
|
$updater->getStrategy()->setPharUrl('https://example.com/current.phar');
|
||
|
$updater->getStrategy()->setVersionUrl('https://example.com/current.version');
|
||
|
try {
|
||
|
$result = $updater->update();
|
||
|
if ($result) {
|
||
|
$new = $updater->getNewVersion();
|
||
|
$old = $updater->getOldVersion();
|
||
|
exit(sprintf(
|
||
|
'Updated from SHA-1 %s to SHA-1 %s', $old, $new
|
||
|
));
|
||
|
} else {
|
||
|
exit('No update needed!')
|
||
|
}
|
||
|
} catch (\Exception $e) {
|
||
|
exit('Well, something happened! Either an oopsie or something involving hackers.');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
See the Update Strategies section for an overview of how to setup the SHA-1
|
||
|
strategy. It's a simple to maintain choice for development or nightly versions of
|
||
|
phars which are released to a specific numbered version.
|
||
|
|
||
|
### Github Release Strategy
|
||
|
|
||
|
Beyond development or nightly phars, if you are released numbered versions on
|
||
|
Github (i.e. tags), you can upload additional files (such as phars) to include in
|
||
|
the Github Release.
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* Other than somewhat different setters for the strategy, all other operations
|
||
|
* are identical.
|
||
|
*/
|
||
|
|
||
|
use Humbug\SelfUpdate\Updater;
|
||
|
|
||
|
$updater = new Updater();
|
||
|
$updater->setStrategy(Updater::STRATEGY_GITHUB);
|
||
|
$updater->getStrategy()->setPackageName('myvendor/myapp');
|
||
|
$updater->getStrategy()->setPharName('myapp.phar');
|
||
|
$updater->getStrategy()->setCurrentLocalVersion('v1.0.1');
|
||
|
try {
|
||
|
$result = $updater->update();
|
||
|
$result ? exit('Updated!') : exit('No update needed!');
|
||
|
} catch (\Exception $e) {
|
||
|
exit('Well, something happened! Either an oopsie or something involving hackers.');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Package name refers to the name used by Packagist, and phar name is the phar's
|
||
|
file name assumed to be constant across versions.
|
||
|
|
||
|
It's left to the implementation to supply the current release version associated
|
||
|
with the local phar. This needs to be stored within the phar and should match
|
||
|
the version string used by Github. This can follow any standard practice with
|
||
|
recognisable pre- and postfixes, e.g.
|
||
|
`v1.0.3`, `1.0.3`, `1.1`, `1.3rc`, `1.3.2pl2`.
|
||
|
|
||
|
If you wish to update to a non-stable version, for example where users want to
|
||
|
update according to a development track, you can set the stability flag for the
|
||
|
Github strategy. By default this is set to `stable` or, in constant form,
|
||
|
`\Humbug\SelfUpdate\Strategy\GithubStrategy::STABLE`:
|
||
|
|
||
|
```php
|
||
|
$updater->getStrategy()->setStability('unstable');
|
||
|
```
|
||
|
|
||
|
If you want to ignore stability and just update to the most recent version regardless:
|
||
|
|
||
|
```php
|
||
|
$updater->getStrategy()->setStability('any');
|
||
|
```
|
||
|
|
||
|
### Rollback Support
|
||
|
|
||
|
The Updater automatically copies a backup of the original phar to myname-old.phar.
|
||
|
You can trigger a rollback quite easily using this convention:
|
||
|
|
||
|
```php
|
||
|
use Humbug\SelfUpdate\Updater;
|
||
|
|
||
|
/**
|
||
|
* Same constructor parameters as you would use for updating. Here, just defaults.
|
||
|
*/
|
||
|
$updater = new Updater();
|
||
|
try {
|
||
|
$result = $updater->rollback();
|
||
|
$result ? exit('Success!') : exit('Failure!');
|
||
|
} catch (\Exception $e) {
|
||
|
exit('Well, something happened! Either an oopsie or something involving hackers.');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
As users may have diverse requirements in naming and locating backups, you can
|
||
|
explicitly manage the precise path to where a backup should be written, or read
|
||
|
from using the `setBackupPath()` function when updating a current phar or the
|
||
|
`setRestorePath()` prior to triggering a rollback. These will be used instead
|
||
|
of the simple built in convention.
|
||
|
|
||
|
### Constructor Parameters
|
||
|
|
||
|
The Updater constructor is fairly simple. The three basic variations are:
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* Default: Update currently running phar which has been signed.
|
||
|
*/
|
||
|
$updater = new Updater;
|
||
|
```
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* Update currently running phar which has NOT been signed.
|
||
|
*/
|
||
|
$updater = new Updater(null, false);
|
||
|
```
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* Use a strategy other than the default SHA Hash.
|
||
|
*/
|
||
|
$updater = new Updater(null, false, Updater::STRATEGY_GITHUB);
|
||
|
```
|
||
|
|
||
|
```php
|
||
|
/**
|
||
|
* Update a different phar which has NOT been signed.
|
||
|
*/
|
||
|
$updater = new Updater('/path/to/impersonatephil.phar', false);
|
||
|
```
|
||
|
|
||
|
### Check For Updates
|
||
|
|
||
|
You can tell users what updates are available, across any stability track, by making
|
||
|
use of the `hasUpdate` method. This gets the most recent remote version for a
|
||
|
stability level, compares it to the current version, and returns a simple true/false
|
||
|
result, i.e. it will only be false where the local version is identical or where
|
||
|
there was no remote version for that stability level at all. You can easily
|
||
|
differentiate between the two false states as the new version will be a string
|
||
|
where a version did exist, but `false` if not.
|
||
|
|
||
|
```php
|
||
|
use Humbug\SelfUpdate\Updater;
|
||
|
|
||
|
/**
|
||
|
* Configuration is identical in every way for actual updates. You can run this
|
||
|
* across multiple configuration variants to get recent stable, unstable, and dev
|
||
|
* versions available.
|
||
|
*/
|
||
|
$updater = new Updater();
|
||
|
$updater->setStrategy(Updater::STRATEGY_GITHUB);
|
||
|
$updater->getStrategy()->setPackageName('myvendor/myapp');
|
||
|
$updater->getStrategy()->setPharName('myapp.phar');
|
||
|
$updater->getStrategy()->setCurrentLocalVersion('v1.0.1');
|
||
|
|
||
|
try {
|
||
|
$result = $updater->hasUpdate();
|
||
|
if ($result) {
|
||
|
echo(sprintf(
|
||
|
'The current stable build available remotely is: %s',
|
||
|
$updater->getNewVersion()
|
||
|
));
|
||
|
} elseif (false === $updater->getNewVersion()) {
|
||
|
echo('There are no stable builds available.');
|
||
|
} else {
|
||
|
echo('You have the current stable build installed.');
|
||
|
}
|
||
|
} catch (\Exception $e) {
|
||
|
exit('Well, something happened! Either an oopsie or something involving hackers.');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Avoid Post Update File Includes
|
||
|
|
||
|
Updating a currently running phar is made trickier since, once replaced, attempts
|
||
|
to load files from it within a process originating from an older phar is likely
|
||
|
to create an `internal corruption of phar` error. For example, if you're using
|
||
|
Symfony Console and have created an event dispatcher for your commands, the lazy
|
||
|
loading of some event classes will have this impact.
|
||
|
|
||
|
The solution is to disable or remove the dispatcher for your self-update command.
|
||
|
|
||
|
In general, when writing your self-update CLI commands, either pre-load any classes
|
||
|
likely needed prior to updating, or disable their loading if not essential.
|
||
|
|
||
|
### Custom Update Strategies
|
||
|
|
||
|
All update strategies revolve around checking for updates, and downloading updates.
|
||
|
The actual work behind replacing local files and backups is handled separate.
|
||
|
To create a custom strategy, you can implement `Humbug\SelfUpdate\Strategy\StrategyInterface`
|
||
|
and pass a new instance of your implementation post-construction.
|
||
|
|
||
|
```php
|
||
|
$updater = new Updater(null, false);
|
||
|
$updater->setStrategyObject(new MyStrategy);
|
||
|
```
|
||
|
|
||
|
The similar `setStrategy()` method is solely used to pass flags matching internal
|
||
|
strategies.
|
||
|
|
||
|
Update Strategies
|
||
|
=================
|
||
|
|
||
|
SHA-1 Hash Synchronisation
|
||
|
--------------------------
|
||
|
|
||
|
The phar-updater package only (that will change!) supports an update strategy
|
||
|
where phars are updated according to the SHA-1 hash of the current phar file
|
||
|
available remotely. This assumes the existence of only two to three remote files:
|
||
|
|
||
|
* myname.phar
|
||
|
* myname.version
|
||
|
* myname.phar.pubkey (optional)
|
||
|
|
||
|
The `myname.phar` is the most recently built phar.
|
||
|
|
||
|
The `myname.version` contains the SHA-1 hash of the most recently built phar where
|
||
|
the hash is the very first string (if not the only string). You can generate this
|
||
|
quite easily from bash using:
|
||
|
|
||
|
```sh
|
||
|
sha1sum myname.phar > myname.version
|
||
|
```
|
||
|
|
||
|
Remember to regenerate the version file for each new phar build you want to distribute.
|
||
|
Using `sha1sum` adds additional data after the hash, but it's fine since the hash is
|
||
|
the first string in the file which is the only requirement.
|
||
|
|
||
|
If using OpenSSL signing, which is very much recommended, you can also put the
|
||
|
public key online as `myname.phar.pubkey`, for the initial installation of your
|
||
|
phar. However, please note that phar-updater itself will never download this key, will
|
||
|
never replace this key on your filesystem, and will never install a phar whose
|
||
|
signature cannot be verified by the locally cached public key.
|
||
|
|
||
|
If you need to switch keys for any reason whatsoever, users will need to manually
|
||
|
download a new phar along with the new key. While that sounds extreme, it's generally
|
||
|
not a good idea to allow for arbitrary key changes that occur without user knowledge.
|
||
|
The openssl signing has no mechanism such as a central authority or a browser's trusted
|
||
|
certificate stash with which to automate such key changes in a safe manner.
|
||
|
|
||
|
|
||
|
Github Releases
|
||
|
---------------
|
||
|
|
||
|
When tagging new versions on Github, these are created and hosted as `Github Releases`
|
||
|
which allow you to attach a changelog and additional file downloads. Using this
|
||
|
Github feature allows you to attach new phars to releases, associating them with
|
||
|
a version string that is published on Packagist.
|
||
|
|
||
|
Taking advantage of this architecture, the Github Strategy for updating phars can
|
||
|
compare the existing local phar's version against remote release versions and
|
||
|
update to the most recent stable (or unstable) version from Github.
|
||
|
|
||
|
At present, it's assume that phar files all bear the same name across releases,
|
||
|
i.e. just a plain name like `myapp.phar` without versioning information in the
|
||
|
file name. You can also upload your phar's public key in the same way. Using
|
||
|
the established convention of being the phar name with `.pubkey` appended, e.g.
|
||
|
`myapp.phar` would be matched with `myapp.phar.pubkey`.
|
||
|
|
||
|
You can read more about Github releases [here](https://help.github.com/articles/creating-releases/).
|
||
|
|
||
|
While you can draft a release, Github releases are created automatically whenever
|
||
|
you create a new git tag. If you use git tagging, you can go to the matching
|
||
|
release on Github, click the `Edit` button and attach files. It's recommended to
|
||
|
do this as soon as possible after tagging to limit the window whereby a new
|
||
|
release exists without an updated phar attached.
|