nanbando/phar-updater

让PHAR自更新变得简单和安全。

2.0.1 2021-06-23 07:10 UTC

README

Build Status Scrutinizer Code Quality StyleCI Total Downloads

您有一个要分发的PHAR文件,并且包含自更新命令已经成为一种潮流。您真的需要自己编写这个吗?在Humbug中心,我们的小军团(全部十人)已经为您编写了一个。

目录

简介

padraic/phar-updater 包具有以下特性

  • 完全支持SSL/TLS验证。
  • 支持OpenSSL phar签名。
  • 简单的API,要么更新,要么异常将变得疯狂。
  • 支持SHA-1版本同步和Github发布作为更新策略。

除了下面的详细文档外,您还可以在Humbug的自更新命令中找到该包的使用示例,作为基于Symfony Console的PHAR的一部分,您可以自由重用。

安装

composer require padraic/phar-updater

该包使用PHP流进行远程请求,因此需要启用openssl扩展和allow_url_open设置。curl的支持将在未来实现。

用法

默认更新策略使用版本文件中当前远程PHAR的SHA-1哈希值,当版本更改时将更新本地PHAR。还有一个GitHub策略,它跟踪GitHub发布,您可以在发布中上传新的PHAR文件。

基本的SHA-1策略

创建您的自更新命令,甚至是更新其他PHAR(而非当前PHAR)的命令,并包含此命令。

/**
 * 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);
}

如果您不使用OpenSSL签名PHAR

/**
 * 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);
}

如果您需要版本信息

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);
}

请参阅更新策略部分,了解如何设置SHA-1策略的概述。这是一个简单易维护的选择,用于开发或夜间版本PHAR,这些版本发布到特定的编号版本。

Github发布策略

除了开发或夜间PHAR之外,如果您在GitHub上发布编号版本(即标签),则可以将额外的文件(如PHAR)上传到GitHub发布中。

/**
 * 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);
}

包名是指Packagist使用的名称,而PHAR名称是指假设跨版本恒定的PHAR文件名。

将当前发布版本与本地PHAR关联的任务留给了实现。这需要在PHAR中存储,并且应该与GitHub使用的版本字符串匹配。这可以遵循任何可识别的前缀和后缀的标准实践,例如v1.0.31.0.31.11.3rc1.3.2pl2

如果您希望更新到非稳定版本,例如用户希望根据开发轨道进行更新,您可以设置GitHub策略的稳定性标志。默认情况下,这设置为stable,或常量形式为\Humbug\SelfUpdate\Strategy\GithubStrategy::STABLE

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

如果您想忽略稳定性,只更新到最新版本

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

回滚支持

更新器自动将原始PHAR的备份复制到myname-old.phar。您可以使用此约定非常容易地触发回滚

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);
}

由于用户在命名和定位备份方面可能有不同的需求,您可以使用setBackupPath()函数显式管理备份应写入的确切路径,或在使用setRestorePath()触发回滚之前从该路径读取。这些操作将替代简单的内置约定。

构造函数参数

更新器构造函数相当简单。三种基本变体是

/**
 * 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);

检查更新

您可以通过使用hasUpdate方法告诉用户有哪些更新可用,无论稳定性轨迹如何。此方法获取稳定级别的最新远程版本,将其与当前版本进行比较,并返回简单的true/false结果,即它只会在本地版本相同或该稳定性级别根本不存在远程版本时返回false。您可以通过两种false状态轻松区分,因为新版本将是字符串(如果存在版本),否则为false

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);
}

避免更新后的文件包含

由于替换后,尝试从较旧的phar中加载文件的过程可能会引发“内部phar损坏”错误,因此更新当前运行的phar变得更为复杂。例如,如果您使用Symfony Console并为您自己的命令创建了一个事件调度器,那么某些事件类的懒加载将受到影响。

解决方案是禁用或删除您的自更新命令的调度器。

一般来说,在编写您的自更新CLI命令时,要么在更新之前预加载可能需要的任何类,要么如果它们不是必需的,则禁用它们的加载。

自定义更新策略

所有更新策略都围绕检查更新和下载更新展开。替换本地文件和备份的实际工作由其他部分单独处理。要创建自定义策略,您可以实现Humbug\SelfUpdate\Strategy\StrategyInterface并在构造后传递一个新实例的实现。

$updater = new Updater(null, false);
$updater->setStrategyObject(new MyStrategy);

类似的setStrategy()方法仅用于传递匹配内部策略的标志。

更新策略

SHA-1哈希同步

phar-updater包仅支持一种更新策略,即根据远程可用phar文件的SHA-1哈希值更新phar。这假设存在两个到三个远程文件

  • myname.phar
  • myname.version
  • myname.phar.pubkey(可选)

myname.phar是最最近构建的phar。

myname.version包含最新构建的phar的SHA-1哈希值,其中哈希值是文件中的第一个字符串(如果不是唯一的字符串)。您可以使用bash轻松生成此哈希值:

sha1sum myname.phar > myname.version

请记住,为每个新构建的并希望分发的phar重新生成版本文件。使用sha1sum在哈希值之后添加额外数据,但这没关系,因为哈希值是文件中的第一个字符串,这是唯一的要求。

如果您使用非常推荐的OpenSSL签名,您还可以将公钥作为myname.phar.pubkey放在网上,用于phar的初始安装。但是,请注意,phar-updater本身永远不会下载此密钥,永远不会替换文件系统中的此密钥,也永远不会安装无法由本地缓存的公钥验证的签名phar。

如果您出于任何原因需要切换密钥,用户将需要手动下载新的phar以及新的密钥。虽然这听起来很极端,但通常不允许用户不知情地进行任意密钥更改并不是一个好主意。OpenSSL签名没有类似中心机构或浏览器受信任证书存储的机制,可以自动化以安全方式执行此类密钥更改。

Github发布

当在GitHub上标记新版本时,这些版本作为Github Releases创建和托管,允许您附加更改日志和附加文件下载。使用此GitHub功能允许您将新的phar附加到版本,将其与在Packagist上发布的版本字符串关联。

利用这种架构,GitHub更新策略可以比较现有本地phar的版本与远程发布版本,并更新到GitHub上最新的稳定(或不稳定)版本。

目前假设所有 phar 文件在各个版本中都有相同的名称,即仅包含普通名称,例如 myapp.phar,文件名中不包含版本信息。您也可以以相同的方式上传您 phar 的公钥。使用约定俗成的命名规则,即在 phar 名称后附加 .pubkey,例如 myapp.phar 会与 myapp.phar.pubkey 匹配。

您可以在 这里 了解更多关于 Github 发布的信息。

虽然您可以起草一个发布版本,但每次您创建一个新的 git 标签时,Github 发布都会自动创建。如果您使用 git 标签,您可以前往对应的发布页面,点击 编辑 按钮,并附加文件。建议在标记后尽快执行此操作,以限制存在没有更新 phar 的发布版本的时间窗口。