laravel-zero / phar-updater
一个使PHAR自更新变得简单和安全的工具。
Requires
- php: ^8.1
Requires (Dev)
- ext-json: *
- laravel/pint: ^1.12
- phpstan/phpstan: ^1.10.32
- phpunit/phpunit: ^9.6.11
Conflicts
README
这是一个为Laravel Zero内部使用而复制的Humbug PHAR Updater。
Laravel Zero PHAR自更新命令的后端。最初由Humbug创建。
目录
介绍
laravel-zero/phar-updater包具有以下特性
- 完全支持SSL/TLS验证。
- 支持OpenSSL phar签名。
- 简单的API,要么更新,要么异常会失控。
- 支持SHA-1/SHA-256/SHA-512版本同步和Github发布作为更新策略。
除了以下详细文档,您还可以在Laravel Zero的自更新组件中找到该包的使用。
安装
通过Composer
composer require laravel-zero/phar-updater
通过Laravel Zero组件安装器
php <application> app:install self-update
该包使用PHP Streams进行远程请求,因此需要启用openssl扩展和allow_url_open
设置。curl的支持将在以后跟进。
使用
默认更新策略使用当前远程phar的SHA-1哈希版本文件,并在版本更改时更新本地phar。还有一个Github策略,它跟踪Github发布,您可以在发布中上传新的phar文件。
基本的SHA-1 / SHA-256 / SHA-512策略
注意:SHA-1策略已被标记为弃用,您应该选择SHA-256或SHA-512策略。
创建您的自更新命令,甚至为其他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(); // Add the below to use the SHA-256 strategy. It will default to SHA-1 if excluded. $updater->setStrategy(Updater::STRATEGY_SHA256); $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或SHA-256策略的概述。它是一个简单的维护选择,用于开发或夜间版本的phars,这些phars被发布到特定的版本号。
Github发布策略
除了开发或夜间phars之外,如果您在Github上发布数字版本(即标签),您可以将其他文件(如phars)上传到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.3
、1.0.3
、1.1
、1.3rc
、1.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()
函数在更新当前phar时显式管理备份应写入的确切路径,或在使用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
方法来告诉用户在任何稳定性轨迹上可用的更新。这将获取某个稳定性级别的最新远程版本,将其与当前版本进行比较,并返回简单的真/假结果,即仅在本地版本相同或该稳定性级别根本不存在远程版本时为假。您可以很容易地区分这两种假状态,因为新版本将是一个字符串,其中版本确实存在,但如果不存在则为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 / SHA-256 / SHA-512哈希同步
phar-updater包支持一种更新策略,其中phar根据远程可用的当前PHAR文件的SHA-1、SHA-256或SHA-512哈希值进行更新。这假设只有两个到三个远程文件存在
- myname.phar
- myname.version
- myname.phar.pubkey(可选)
myname.phar
是最最近构建的phar。
myname.version
包含最最近构建的phar的SHA-1、SHA-256或SHA-512哈希值,其中哈希值是文件中的第一个字符串(如果不是唯一的字符串)。您可以使用bash生成它
# For SHA-1 sha1sum myname.phar > myname.version # For SHA-256 sha256sum myname.phar > myname.version # For SHA-512 sha512sum myname.phar > myname.version
请记住,对于您要分发的每个新phar构建,都要重新生成版本文件。使用sha1sum
/sha256sum
/sha512sum
在哈希值后添加附加数据,但这没关系,因为哈希值是文件中的第一个字符串,这是唯一的要求。
如果您使用非常推荐的OpenSSL签名,您也可以将公钥放在线作为myname.phar.pubkey
,用于您的phar的初始安装。但是,请注意,phar-updater本身将永远不会下载此密钥,也永远不会在您的文件系统中替换此密钥,也永远不会安装一个无法由本地缓存的公钥验证签名的phar。
如果您需要出于任何原因切换密钥,用户将需要手动下载包含新密钥的新phar。虽然这听起来很极端,但通常不鼓励在不让用户知道的情况下允许任意密钥更改。OpenSSL签名没有类似于中央权威机构或浏览器信任证书存储的机制,可以以安全的方式自动化此类密钥更改。
Github发布
在Github上标记新版本时,这些版本将被创建并托管为Github Releases
,允许您附加变更日志和额外的文件下载。利用这个Github功能,您可以附加新的phar文件到版本中,并将其与在Packagist上发布的版本字符串关联。
利用这种架构,更新phar文件的Github策略可以比较现有本地phar的版本与远程发布版本,并更新到最新稳定(或不稳定)版本。
目前,假设phar文件在所有版本中名称都相同,即文件名仅包含一个普通名称,如myapp.phar
,而不包含版本信息。您也可以以同样的方式上传您的phar公钥。使用已建立的惯例,即phar名称后附加.pubkey
,例如myapp.phar
将与myapp.phar.pubkey
匹配。
您可以在这里了解更多关于Github发布的信息。
虽然您可以草拟一个发布版本,但每当您创建一个新的git标签时,Github发布版本都会自动创建。如果您使用git标签,您可以转到匹配的发布版本,点击编辑
按钮并附加文件。建议在标记后尽快这样做,以限制新发布存在但未附加更新phar的时间窗口。
直接下载
PHAR Updater提供了一个抽象的Humbug\SelfUpdate\Strategy\DirectDownloadStrategyAbstract
类,可以用于通过仅使用getDownloadUrl(): string
方法快速轻松地创建下载策略。
例如,如果PHAR从https://example.com/latest/example.phar
下载最新更新,您可以使用以下代码来实现这一点
use Humbug\SelfUpdate\Strategy\DirectDownloadStrategyAbstract; class ExampleDirectDownloadStrategy extends DirectDownloadStrategyAbstract { public function getDownloadUrl(): string { return 'https://example.com/latest/example.phar'; } }
这个抽象策略还支持覆盖getCurrentRemoteVersion()
方法,因此您可以添加自定义HTTP调用或其他方法来查看最新版本。默认情况下,它返回字符串latest
。
use Illuminate\Support\Facades\Http; use Humbug\SelfUpdate\Strategy\DirectDownloadStrategyAbstract; class ExampleDirectDownloadStrategy extends DirectDownloadStrategyAbstract { /** {@inheritdoc} */ public function getCurrentRemoteVersion(Updater $updater) { return Http::get('https://example.com/example-releases.json')->object()->latest_version; } public function getDownloadUrl(): string { return "https://example.com/{$this->getCurrentRemoteVersion()}/example.phar"; } }
您还可以使用setCurrentLocalVersion()
和getCurrentLocalVersion()
方法设置和检索当前本地版本,这些方法将用于与远程版本进行比较。