Coder Social home page Coder Social logo

phar-updater's Introduction

PHAR Updater

Build Status Scrutinizer Code Quality StyleCI Total Downloads

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

The padraic/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 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.

/**
 * 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();
    echo $result ? "Updated!\n" : "No update needed!\n";
} catch (\Exception $e) {
    echo "Well, something happened! Either an oopsie or something involving hackers.\n";
    exit(1);
}

If you are not signing the phar using OpenSSL:

/**
 * 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();
    echo $result ? "Updated!\n" : "No update needed!\n";
} catch (\Exception $e) {
    echo "Well, something happened! Either an oopsie or something involving hackers.\n";
    exit(1);
}

If you need version information:

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();
        printf(
            'Updated from SHA-1 %s to SHA-1 %s', $old, $new
        );
    } else {
        echo "No update needed!\n";
    }
} catch (\Exception $e) {
    echo "Well, something happened! Either an oopsie or something involving hackers.\n";
    exit(1);
}

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 releasing numbered versions on Github (i.e. tags), you can upload additional files (such as phars) to include in the Github Release.

/**
 * 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();
    echo $result ? "Updated!\n" : "No update needed!\n";
} catch (\Exception $e) {
    echo "Well, something happened! Either an oopsie or something involving hackers.\n";
    exit(1);
}

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:

$updater->getStrategy()->setStability('unstable');

If you want to ignore stability and just update to the most recent version regardless:

$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:

use Humbug\SelfUpdate\Updater;

/**
 * Same constructor parameters as you would use for updating. Here, just defaults.
 */
$updater = new Updater();
try {
    $result = $updater->rollback();
    if (!$result) {
        echo "Failure!\n";
        exit(1);
    }
    echo "Success!\n";
} catch (\Exception $e) {
    echo "Well, something happened! Either an oopsie or something involving hackers.\n";
    exit(1);
}

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:

/**
 * Default: Update currently running phar which has been signed.
 */
$updater = new Updater;
/**
 * Update currently running phar which has NOT been signed.
 */
$updater = new Updater(null, false);
/**
 * Use a strategy other than the default SHA Hash.
 */
$updater = new Updater(null, false, Updater::STRATEGY_GITHUB);
/**
 * 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.

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.
 *
 * This would configure update for an unsigned phar (second constructor must be
 * false in this case).
 */
$updater = new Updater(null, false);
$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) {
        printf(
            'The current stable build available remotely is: %s',
            $updater->getNewVersion()
        );
    } elseif (false === $updater->getNewVersion()) {
        echo "There are no stable builds available.\n";
    } else {
        echo "You have the current stable build installed.\n";
    }
} catch (\Exception $e) {
    echo "Well, something happened! Either an oopsie or something involving hackers.\n";
    exit(1);
}

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 separately. To create a custom strategy, you can implement Humbug\SelfUpdate\Strategy\StrategyInterface and pass a new instance of your implementation post-construction.

$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:

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.

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.

phar-updater's People

Contributors

frak avatar glensc avatar mnapoli avatar nyholm avatar omissis avatar padraic avatar pamil avatar pborreli avatar stof avatar waltertamboer avatar webflo avatar webmozart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

phar-updater's Issues

Checking hasUpdate() should not require is_writable for phar file.

I would like unprivileged system users, who do not have the ability to run self-update commands, to be able to check if an update for the command is available. Currently, the following results in an exception:

        $updater = new Updater(null, false);

        $updater->getStrategy()->setPharUrl($updateDomain . '/app/current.phar');
        $updater->getStrategy()->setVersionUrl($updateDomain . '/app/current.version');

        try {
            $result = $updater->hasUpdate();
            if ($result) {
                echo "update available";
            } else {
                echo "update NOT available";
            }

Here's the exception of the local phar file is not writable:

[Humbug\SelfUpdate\Exception\FilesystemException]
The current phar file is not writeable and cannot be replaced: /usr/bin/bobafetch.

I think the new Updater() is checking for writability... but writing shouldn't be required to check for updates.

Obey HTTP_PROXY environment variables

the updater should look after the common proxy environment variables and try to connect through the proxy if they are set.

NO_PROXY
HTTPS_PROXY
HTTP_PROXY

Security check fail with "padraic/humbug_get_contents" old version 1.0.4

Hello,
I have noticed an issue recently :
The package "padraic/phar-updater" requires "padraic/humbug_get_contents" version 1.0.4 but not newest version 1.1.2, which create failure in security check.

Are you going to update package "padraic/phar-updater" for solving this issue ?

Thanks in advance.


Symfony Security Check Report

// Checked file: /my_project/apache/volume/composer.lock

[ERROR] 1 packages have known vulnerabilities.

padraic/humbug_get_contents (1.0.4)

! [NOTE] This checker can only detect vulnerabilities that are referenced in
! the SensioLabs security advisories database. Execute this command
! regularly to check the newly discovered vulnerabilities.

Loaded config default from ".php_cs.dist".

Unsigned PHAR testing

If testing a phar configured to be updated from a signed phar, but unsigned for testing purposes only, phar-updater throws an exception due to missing pubkey. We can make testing easier by deferring the pubkey check to an actual self-update op.

Simplify usage

I still find the PHAR-Updater really backbone which leads to way too much boilertemplates for each project. A good example is the difference between PHP-Scoper and Infection self update commands. There is way too much boiler-templates, the usage is not clear enough.

IMO we should provide a Symfony bridge so that adding a self-update command would not be more than registering a command that might need a bit of configuration but not more. If too opinionated for some people, they could always do things their own way instead of using the default command.

It might be worth to check how other projects did it as well and go for a sane default. Even if very big BC breaks, 2.0 should be done right.

HHVM errors with PharException

Appears on Travis and looks like:

2) Humbug\Test\SelfUpdate\UpdaterTest::testUpdatePhar
PharException: phar has a broken or unsupported signature

/home/padraic/projects/phar-updater/src/Updater.php:470
/home/padraic/projects/phar-updater/src/Updater.php:364
/home/padraic/projects/phar-updater/src/Updater.php:139
/home/padraic/projects/phar-updater/tests/Humbug/Test/SelfUpdate/UpdaterTest.php:145

From what I've been able to tell, this is probably a straight up incompatibility within HHVM, but figured I'd note it here anyway. It would likely then impact any openssl signed PHAR beyond this project. Someone else have other ideas, or has better familiarity with HHVM, so let me know.

Document or resolve git metadata when attached to versions

In certain scenarios, a version can be injected into PHARs which is of the form: 1.0.0-26-gh378sj7.

The first part is a recognisable version number. The second part is the number of commits that the referenced build is ahead of the 1.0.0 tag by. The third part, prefixed with a g is the current commit hash.

The current solution (see #34) is to simple strip out the metadata, i.e. you end up with 1.0.0 as the normalised version. Clearly, that is not the correct answer. The correct answer is that this is a pre-release version not even marked alpha (i.e. it's 26 commits ahead of the actual 1.0.0). This tends to show up where versions are automated via box.

Solutions?

  1. Inject pre-release marker if not already present (we can easily check if a pre-release label, like alpha is already attached). Probably use -dev if none already present: 1.0.0-26-gh378sj7 becomes 1.0.0-dev.26-gh378sj7
  2. Strip the commit hash as useless information: 1.0.0-dev.26-gh378sj7 becomes 1.0.0-dev.26
  3. Check if the revised version can be evaluation by Composer\Semver, and properly compared.

Other solution?
Report to Composer? I'm really not feeling this as being their problem though. Under Semver, one would expect metadata to be attached using a + sign. It's not!

Report to Box? That's more realistic, but they may have entirely alternative reasons for why this is valid.

Manifest file -based strategy

Thanks for this library - I am going to use it instead of the abandoned project https://github.com/kherge-abandoned/php-phar-update. One advantage that had was to grab available versions and SHA1 hashes from a single "manifest" file.

I've written a custom strategy to handle this for our project:
https://github.com/pjcdawkins/platformsh-cli/blob/replace-phar-update/src/SelfUpdate/ManifestStrategy.php

and here's what the manifest file looks like:
https://platform.sh/cli/manifest.json

It seems like this could be generalised for anyone, would you be interested if I made a PR?

Remove humbug_get_contents

PHP requirement is 5.6, there's no need for the transitional package at this point for earlier versions.

Check rollback unnecessary message

When rolling back to an older release (but there is none), the result message isn't an error, but it suggests a successful rollback anyway.

Edit: Actually there is a backup - the problem is that we're not deleting backups after failed downloads, so rollbacks always succeed in that event. The impact is harmless but should be avoided by cleaning up after download errors.

humbug_get_contents() is deprecated

  [Humbug\SelfUpdate\Exception\HttpRequestException]                                                                
  humbug_get_contents() is deprecated since 1.1.0 and will be removed in 4.0.0. Use Humbug/get_contents() instead.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.