hotfix: Added aliases, implemented new runners
This commit is contained in:
parent
8e8cb05674
commit
302e5a50ce
@ -7,7 +7,26 @@ require_once __DIR__."/systemtest.php";
|
|||||||
if (file_exists(__DIR__."/banner.php"))
|
if (file_exists(__DIR__."/banner.php"))
|
||||||
require_once __DIR__."/banner.php";
|
require_once __DIR__."/banner.php";
|
||||||
|
|
||||||
|
use NoccyLabs\Hotfix\Hotfix\AliasManager;
|
||||||
|
use NoccyLabs\Hotfix\Service\ServiceManager;
|
||||||
|
use NoccyLabs\Hotfix\Service;
|
||||||
use NoccyLabs\Hotfix\HotfixApplication;
|
use NoccyLabs\Hotfix\HotfixApplication;
|
||||||
|
|
||||||
|
// Register services
|
||||||
|
ServiceManager::registerService(new Service\IxService());
|
||||||
|
|
||||||
|
// Register aliases
|
||||||
|
$paths = [
|
||||||
|
getenv("HOME")."/.hotfix.conf",
|
||||||
|
"/etc/hotfix.conf"
|
||||||
|
];
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (file_exists($path)) {
|
||||||
|
AliasManager::registerFromConfig($path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the application
|
||||||
$app = new HotfixApplication();
|
$app = new HotfixApplication();
|
||||||
$app->run();
|
$app->run();
|
||||||
|
10
examples/bash-facts.fix
Normal file
10
examples/bash-facts.fix
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
hotfix: This hotfix show how to use facts in bash
|
||||||
|
info: >
|
||||||
|
This lets you retrieve some additional system information during
|
||||||
|
runtime. Use 'hotfix facts' to see all the known facts.
|
||||||
|
author: Noccy <cvagnetoft@gmail.com>
|
||||||
|
lang: bash
|
||||||
|
---
|
||||||
|
|
||||||
|
info "Architecture: $(fact system.arch)"
|
||||||
|
info "Distribution: $(fact lsb.id)"
|
4
hotfix.conf
Normal file
4
hotfix.conf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[alias]
|
||||||
|
gist=https://gist.githubusercontent.com/{fix}/raw
|
||||||
|
pastebin=https:/pastebin.com/raw/{fix}
|
||||||
|
noccy=http://files.noccy.com/hotfix/{fix}.fix.signed
|
5
installer/make-installer.sh
Executable file
5
installer/make-installer.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cp ../hotfix src/hotfix
|
||||||
|
cp ../hotfix.conf src/hotfix.conf
|
||||||
|
cp ../LICENSE src/LICENSE
|
||||||
|
makeself --copy src hotfix-installer.run hotfix ./install.sh
|
@ -66,6 +66,13 @@ function do_install {
|
|||||||
fi
|
fi
|
||||||
chmod +x $1/hotfix
|
chmod +x $1/hotfix
|
||||||
|
|
||||||
|
if [ -f $HOME/.hotfix.conf ]; then
|
||||||
|
debug "~/.hotfix.conf already exists, will not overwrite"
|
||||||
|
else
|
||||||
|
debug "Copying hotfix.conf into ~/.hotfix.conf"
|
||||||
|
cp hotfix.conf $HOME/.hotfix.conf
|
||||||
|
fi
|
||||||
|
|
||||||
debug "Verifying that hotfix is callable..."
|
debug "Verifying that hotfix is callable..."
|
||||||
source ~/.profile &>/dev/null
|
source ~/.profile &>/dev/null
|
||||||
source ~/.bashrc &>/dev/null
|
source ~/.bashrc &>/dev/null
|
||||||
|
@ -12,8 +12,10 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
use NoccyLabs\Hotfix\Hotfix\Loader;
|
use NoccyLabs\Hotfix\Hotfix\Loader;
|
||||||
|
use NoccyLabs\Hotfix\Hotfix\Hotfix;
|
||||||
use NoccyLabs\Hotfix\System\Facts;
|
use NoccyLabs\Hotfix\System\Facts;
|
||||||
|
use NoccyLabs\Hotfix\Runner\RunnerFactory;
|
||||||
|
use NoccyLabs\Hotfix\Hotfix\HotfixLoader;
|
||||||
|
|
||||||
class ApplyCommand extends Command
|
class ApplyCommand extends Command
|
||||||
{
|
{
|
||||||
@ -26,7 +28,7 @@ class ApplyCommand extends Command
|
|||||||
$this->addOption("insecure", "I", InputOption::VALUE_NONE, "Disable signature checks. Don't use unless you know what you are doing!");
|
$this->addOption("insecure", "I", InputOption::VALUE_NONE, "Disable signature checks. Don't use unless you know what you are doing!");
|
||||||
$this->addOption("preview", "p", InputOption::VALUE_NONE, "Only preview the hotfix script, don't apply anything");
|
$this->addOption("preview", "p", InputOption::VALUE_NONE, "Only preview the hotfix script, don't apply anything");
|
||||||
|
|
||||||
$this->addArgument("hotfix", InputArgument::OPTIONAL, "The identifier, path or filename of the hotfix");
|
$this->addArgument("hotfix", InputArgument::OPTIONAL, "The identifier or filename of the hotfix");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
@ -34,69 +36,45 @@ class ApplyCommand extends Command
|
|||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
|
|
||||||
$fix = $input->getArgument("hotfix");
|
$fix = $input->getArgument("hotfix");
|
||||||
$insecure = $input->getOption("insecure");
|
|
||||||
$loader = new Loader();
|
|
||||||
|
|
||||||
if (!$fix) {
|
if (!$fix) {
|
||||||
|
$output->writeln("No hotfix specified.");
|
||||||
$loaders = $loader->getLoaders();
|
|
||||||
$output->writeln("Supported loaders:\n");
|
|
||||||
foreach ($loaders as $loader) {
|
|
||||||
$output->writeln(" * ".$loader->getInfo());
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//$loader = new Loader();
|
||||||
$output->writeln("Reading hotfix <comment>{$fix}</comment>...");
|
$output->writeln("Reading hotfix <comment>{$fix}</comment>...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$hotfix = $loader->load($fix, $insecure);
|
$hotfix = HotfixLoader::load($fix);
|
||||||
|
//$hotfix = $loader->load($fix, $insecure);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$output->writeln("<error>Error: ".$e->getMessage()."</error>");
|
$output->writeln("<error>Error: ".$e->getMessage()."</error>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$hotfix) {
|
|
||||||
$output->writeln("<error>Could not load hotfix</error>");
|
$output->writeln("");
|
||||||
|
|
||||||
|
$header = $hotfix->getHeader();
|
||||||
|
$output->writeln(" Hotfix: <fg=yellow;options=bold>".$header->getName()."</fg=yellow;options=bold>");
|
||||||
|
$output->writeln(" Author: <comment>".$header->getAuthor()."</comment>");
|
||||||
|
$info = explode("\n",wordwrap(trim($header->getInfo()), 60));
|
||||||
|
$info = "<comment>".join("</comment>\n <comment>", $info)."</comment>";
|
||||||
|
$output->writeln(" Info: ".$info);
|
||||||
|
$output->writeln("");
|
||||||
|
|
||||||
|
if (!$this->checkSignature($hotfix, $output)) {
|
||||||
|
if (!$input->getOption('insecure')) {
|
||||||
|
$output->writeln("<error>Hotfix can not be authenticated. Aborting!</error>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$this->checkRequirements($hotfix, $output)) {
|
||||||
|
$output->writeln("<error>Error: This hotfix it not compatible with the current distribution</>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$output->writeln("");
|
$output->writeln("");
|
||||||
if (($signer = $hotfix->getSignedBy())) {
|
|
||||||
$keyid = $hotfix->getKeyId();
|
|
||||||
$output->writeln("Hotfix has good signature from <fg=green;options=bold>{$signer}</fg=green;options=bold> (keyid <info>{$keyid}</info>)");
|
|
||||||
} else {
|
|
||||||
$output->writeln("<fg=red;options=bold>Warning: Hotfix is not signed or signature not valid!</fg=red;options=bold>");
|
|
||||||
}
|
|
||||||
|
|
||||||
$requires = $hotfix->getRequirements();
|
|
||||||
if (count($requires)>0) {
|
|
||||||
$engine = new ExpressionLanguage();
|
|
||||||
$facts = Facts::getSystemFacts()->getFacts();
|
|
||||||
while (true) {
|
|
||||||
$expr = array_shift($requires);
|
|
||||||
$ret = $engine->evaluate($expr, $facts);
|
|
||||||
if ($ret) {
|
|
||||||
//$output->writeln("<fg=green;options=bold>Hotfix is compatible with the current distribution</>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (count($requires)==0) {
|
|
||||||
$output->writeln("<error>Error: This hotfix it not compatible with the current distribution</>");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//} else {
|
|
||||||
//$output->writeln("<fg=green;options=bold>Hotfix indicates universal compatibility.</>");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$output->writeln("");
|
|
||||||
$output->writeln(" Hotfix: <fg=yellow;options=bold>".$hotfix->getName()."</fg=yellow;options=bold>");
|
|
||||||
$output->writeln(" Author: <comment>".$hotfix->getAuthor()."</comment>");
|
|
||||||
$info = explode("\n",wordwrap(trim($hotfix->getInfo()), 60));
|
|
||||||
$info = "<comment>".join("</comment>\n <comment>", $info)."</comment>";
|
|
||||||
$output->writeln(" Info: ".$info);
|
|
||||||
$output->writeln("");
|
|
||||||
|
|
||||||
if ($input->getOption("preview")) {
|
if ($input->getOption("preview")) {
|
||||||
$output->writeln("This is the script that will be executed:");
|
$output->writeln("This is the script that will be executed:");
|
||||||
$body = $hotfix->getBody();
|
$body = $hotfix->getBody();
|
||||||
@ -111,6 +89,7 @@ class ApplyCommand extends Command
|
|||||||
if (!$helper->ask($input, $output, $question)) {
|
if (!$helper->ask($input, $output, $question)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$output->writeln("\n<info>Applying hotfix...</info>\n");
|
$output->writeln("\n<info>Applying hotfix...</info>\n");
|
||||||
try {
|
try {
|
||||||
@ -121,4 +100,39 @@ class ApplyCommand extends Command
|
|||||||
$output->writeln("\n<info>Hotfix applied successfully</info>");
|
$output->writeln("\n<info>Hotfix applied successfully</info>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkSignature(Hotfix $hotfix, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$signature = $hotfix->getSignature();
|
||||||
|
if ($signature->isValid()) {
|
||||||
|
$keyid = $signature->getKeyId();
|
||||||
|
$signer = $signature->getSigner();
|
||||||
|
$output->writeln("Hotfix has good signature from <fg=green;options=bold>{$signer}</fg=green;options=bold> (keyid <info>{$keyid}</info>)");
|
||||||
|
} else {
|
||||||
|
$error = $signature->getError();
|
||||||
|
$output->writeln("<fg=red;options=bold>Warning: {$error}</fg=red;options=bold>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkRequirements(Hotfix $hotfix, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$requires = $hotfix->getHeader()->getRequirements();
|
||||||
|
if (count($requires)>0) {
|
||||||
|
$engine = new ExpressionLanguage();
|
||||||
|
$facts = Facts::getSystemFacts()->getFacts();
|
||||||
|
while (true) {
|
||||||
|
$expr = array_shift($requires);
|
||||||
|
$ret = $engine->evaluate($expr, $facts);
|
||||||
|
if ($ret) {
|
||||||
|
//$output->writeln("<fg=green;options=bold>Hotfix is compatible with the current distribution</>");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (count($requires)==0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,10 @@ class FactsCommand extends Command
|
|||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$facts = Facts::getSystemFacts()->getFlat();
|
$facts = Facts::getSystemFacts()->getFlat();
|
||||||
|
|
||||||
print_r($facts);
|
foreach ($facts as $fact=>$value) {
|
||||||
|
$output->writeln(" <comment>{$fact}</>: <info>{$value}</>");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/Exception/ConfigurationException.php
Normal file
7
src/Exception/ConfigurationException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Exception;
|
||||||
|
|
||||||
|
class ConfigurationException extends HotfixException
|
||||||
|
{
|
||||||
|
}
|
41
src/Hotfix/AliasManager.php
Normal file
41
src/Hotfix/AliasManager.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Hotfix;
|
||||||
|
|
||||||
|
use NoccyLabs\Hotfix\Exception\ConfigurationException;
|
||||||
|
|
||||||
|
class AliasManager
|
||||||
|
{
|
||||||
|
private static $aliases = [];
|
||||||
|
|
||||||
|
public static function registerFromConfig($file)
|
||||||
|
{
|
||||||
|
$config = parse_ini_file($file, true);
|
||||||
|
if (!array_key_exists('alias', $config)) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
foreach ($config['alias'] as $prefix=>$url) {
|
||||||
|
self::registerAlias($prefix, $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function registerAlias($prefix, $url)
|
||||||
|
{
|
||||||
|
if (strpos($url,'{fix}')===false) {
|
||||||
|
throw new ConfigurationException("The alias URL for {$prefix} does not contain the magic '{fix}' placeholder");
|
||||||
|
}
|
||||||
|
self::$aliases[$prefix] = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRegisteredAlises()
|
||||||
|
{
|
||||||
|
return self::$aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAlias($prefix)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($prefix,self::$aliases)) return null;
|
||||||
|
return self::$aliases[$prefix];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
83
src/Hotfix/Header.php
Normal file
83
src/Hotfix/Header.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Hotfix;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use NoccyLabs\Hotfix\Exception\HotfixException;
|
||||||
|
|
||||||
|
class Header
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The name of the hotfix
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Information about this hotfix
|
||||||
|
*/
|
||||||
|
protected $info;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The author of the hotfix
|
||||||
|
*/
|
||||||
|
protected $author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The language this hotfix is written in, should match a runner.
|
||||||
|
*/
|
||||||
|
protected $language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array A list of expressions to verify that the system is compatible
|
||||||
|
* with this fix
|
||||||
|
*/
|
||||||
|
protected $requirements;
|
||||||
|
|
||||||
|
public function __construct($header)
|
||||||
|
{
|
||||||
|
$header = Yaml::parse($header);
|
||||||
|
|
||||||
|
if (!(
|
||||||
|
array_key_exists('hotfix', $header) &&
|
||||||
|
array_key_exists('author', $header) &&
|
||||||
|
array_key_exists('info', $header) &&
|
||||||
|
array_key_exists('lang', $header)
|
||||||
|
)) {
|
||||||
|
throw new HotfixException("Invalid hotfix, header is missing hotfix, author, info or lang fields.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->name = $header['hotfix'];
|
||||||
|
$this->author = $header['author'];
|
||||||
|
$this->info = $header['info'];
|
||||||
|
$this->language = $header['lang'];
|
||||||
|
|
||||||
|
if (array_key_exists('for', $header)) {
|
||||||
|
$this->requirements = $header['for'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthor()
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInfo()
|
||||||
|
{
|
||||||
|
return $this->info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLanguage()
|
||||||
|
{
|
||||||
|
return $this->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequirements()
|
||||||
|
{
|
||||||
|
return $this->requirements;
|
||||||
|
}
|
||||||
|
}
|
@ -3,23 +3,45 @@
|
|||||||
namespace NoccyLabs\Hotfix\Hotfix;
|
namespace NoccyLabs\Hotfix\Hotfix;
|
||||||
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use NoccyLabs\Hotfix\Runner\RunnerFactory;
|
||||||
|
|
||||||
class Hotfix
|
class Hotfix
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $signer;
|
|
||||||
|
|
||||||
protected $keyId;
|
|
||||||
|
|
||||||
protected $header;
|
protected $header;
|
||||||
|
|
||||||
protected $body;
|
protected $body;
|
||||||
|
|
||||||
public function __construct($hotfix, $signer)
|
protected $signature;
|
||||||
|
|
||||||
|
protected $origin;
|
||||||
|
|
||||||
|
public function __construct($body, Header $header, Signature $signature, $origin)
|
||||||
{
|
{
|
||||||
$this->load($hotfix);
|
$this->header = $header;
|
||||||
$this->signer = $signer[0];
|
$this->body = $body;
|
||||||
$this->keyId = $signer[1];
|
$this->signature = $signature;
|
||||||
|
$this->origin = $origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeader()
|
||||||
|
{
|
||||||
|
return $this->header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash()
|
||||||
|
{
|
||||||
|
return sha1($this->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignature()
|
||||||
|
{
|
||||||
|
return $this->signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBody()
|
||||||
|
{
|
||||||
|
return $this->body;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function load($hotfix)
|
protected function load($hotfix)
|
||||||
@ -34,88 +56,11 @@ class Hotfix
|
|||||||
$this->body = $body;
|
$this->body = $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBody()
|
|
||||||
{
|
|
||||||
return $this->body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function apply()
|
public function apply()
|
||||||
{
|
{
|
||||||
if (!array_key_exists('lang', $this->header)) {
|
$runner = RunnerFactory::createRunner($this);
|
||||||
$lang = 'bash';
|
$runner->apply();
|
||||||
} else {
|
|
||||||
$lang = strtolower($this->header['lang']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$script = null;
|
|
||||||
$head = null;
|
|
||||||
$foot = null;
|
|
||||||
switch ($lang) {
|
|
||||||
case 'bash':
|
|
||||||
$exec = "/bin/bash";
|
|
||||||
$head = file_get_contents(__DIR__."/../stubs/bash.stub");
|
|
||||||
break;
|
|
||||||
case 'php':
|
|
||||||
$exec = "/usr/bin/env php";
|
|
||||||
$head = "<?php require_once \"".__DIR__."/../../vendor/autoload.php\"; ".file_get_contents(__DIR__."/../stubs/php.stub");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(STDERR, "Error: Unsupported language %s\n", $lang);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$tmpfile = sys_get_temp_dir()."/hotfix_".sha1($this->header['hotfix']);
|
|
||||||
|
|
||||||
file_put_contents($tmpfile, $head."\n".$this->body."\n".$foot);
|
|
||||||
passthru($exec." ".$tmpfile);
|
|
||||||
unlink($tmpfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRequirements()
|
|
||||||
{
|
|
||||||
if (!array_key_exists('for',$this->header)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return $this->header['for'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSignedBy()
|
|
||||||
{
|
|
||||||
if (!$this->signer) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return sprintf("%s <%s>",
|
|
||||||
$this->signer['uids'][0]['name'],
|
|
||||||
$this->signer['uids'][0]['email']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKeyId()
|
|
||||||
{
|
|
||||||
return $this->keyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName()
|
|
||||||
{
|
|
||||||
if (!array_key_exists('hotfix', $this->header)) {
|
|
||||||
return "Untitled hotfix";
|
|
||||||
}
|
|
||||||
return $this->header['hotfix'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInfo()
|
|
||||||
{
|
|
||||||
if (!array_key_exists('info', $this->header)) {
|
|
||||||
return "No additional information";
|
|
||||||
}
|
|
||||||
return $this->header['info'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAuthor()
|
|
||||||
{
|
|
||||||
if (!array_key_exists('author', $this->header)) {
|
|
||||||
return "Unknown author";
|
|
||||||
}
|
|
||||||
return $this->header['author'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
75
src/Hotfix/HotfixLoader.php
Normal file
75
src/Hotfix/HotfixLoader.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Hotfix;
|
||||||
|
|
||||||
|
use NoccyLabs\Hotfix\Exception\HotfixException;
|
||||||
|
use NoccyLabs\Hotfix\Service\ServiceManager;
|
||||||
|
use NoccyLabs\Hotfix\Hotfix\AliasManager;
|
||||||
|
|
||||||
|
class HotfixLoader
|
||||||
|
{
|
||||||
|
|
||||||
|
const PGP_HEADER_TOKEN = '-----BEGIN PGP SIGNATURE-----';
|
||||||
|
|
||||||
|
public static function load($uri)
|
||||||
|
{
|
||||||
|
// First, check if this is a valid URL or filename
|
||||||
|
if ((strpos($uri,"://")!==false) || (file_exists($uri))) {
|
||||||
|
return self::loadFromUri($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($uri,":")!==false) {
|
||||||
|
list($prefix,$id) = explode(":",$uri,2);
|
||||||
|
} else {
|
||||||
|
throw new HotfixException("Invalid hotfix specified {$uri}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check services first, as aliases should for security reasons not
|
||||||
|
// be allowed to overload services.
|
||||||
|
if (($service = ServiceManager::getService($prefix))) {
|
||||||
|
return self::loadFromService($service,$id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check aliases
|
||||||
|
if (($alias = AliasManager::getAlias($prefix))) {
|
||||||
|
$url = str_replace('{fix}', $id, $alias);
|
||||||
|
return self::loadFromUri($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HotfixException("Could not read hotfix {$uri}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function loadFromUri($url)
|
||||||
|
{
|
||||||
|
$source = file_get_contents($url);
|
||||||
|
return self::loadHotfix($source,$url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function loadFromService(ReaderInterface $service, $id)
|
||||||
|
{
|
||||||
|
$source = $service->read($id);
|
||||||
|
return self::loadHotfix($source,$service->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function loadHotfix($source,$origin)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Check for a signature header
|
||||||
|
if (false === strpos($source, self::PGP_HEADER_TOKEN)) {
|
||||||
|
$body = $source;
|
||||||
|
$signature = new Signature($body,null);
|
||||||
|
} else {
|
||||||
|
list ($body, $sig) = explode(self::PGP_HEADER_TOKEN, $source);
|
||||||
|
$sig = self::PGP_HEADER_TOKEN.$sig;
|
||||||
|
$signature = new Signature($body,$sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract header next
|
||||||
|
list ($header,$body) = explode("\n---\n", $body, 2);
|
||||||
|
$header = new Header($header);
|
||||||
|
|
||||||
|
$hotfix = new Hotfix($body, $header, $signature, $origin);
|
||||||
|
return $hotfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,86 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace NoccyLabs\Hotfix\Hotfix;
|
|
||||||
|
|
||||||
class Loader
|
|
||||||
{
|
|
||||||
|
|
||||||
protected $signedBy;
|
|
||||||
|
|
||||||
protected $loaders = [];
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->addLoader(new Loader\FileLoader());
|
|
||||||
$this->addLoader(new Loader\HttpLoader());
|
|
||||||
$this->addLoader(new Loader\GistLoader());
|
|
||||||
$this->addLoader(new Loader\PastebinLoader());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addLoader(Loader\LoaderInterface $loader)
|
|
||||||
{
|
|
||||||
$this->loaders[] = $loader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLoaders()
|
|
||||||
{
|
|
||||||
return $this->loaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load($fix, $insecure=false)
|
|
||||||
{
|
|
||||||
foreach ($this->loaders as $loader) {
|
|
||||||
$hotfix = $loader->load($fix);
|
|
||||||
if ($hotfix === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$sigHeader = '-----BEGIN PGP SIGNATURE-----';
|
|
||||||
if (false === strpos($hotfix, $sigHeader)) {
|
|
||||||
if (!$insecure) {
|
|
||||||
throw new \Exception("Hotfix is not signed");
|
|
||||||
}
|
|
||||||
$body = $hotfix;
|
|
||||||
$signer = null;
|
|
||||||
} else {
|
|
||||||
list ($body, $signature) = explode($sigHeader, $hotfix);
|
|
||||||
$signature = $sigHeader.$signature;
|
|
||||||
if (!$insecure) {
|
|
||||||
$signer = $this->verifySignature($body, $signature);
|
|
||||||
} else {
|
|
||||||
$signer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Hotfix($body, $signer);
|
|
||||||
}
|
|
||||||
fprintf(STDERR, "Error: Couldn't load '%s'\n", $fix);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function verifySignature($body, $signature)
|
|
||||||
{
|
|
||||||
$gpg = gnupg_init();
|
|
||||||
|
|
||||||
$sigInfo = gnupg_verify($gpg, $body, $signature);
|
|
||||||
|
|
||||||
if ($sigInfo === false) {
|
|
||||||
throw new \Exception("Hotfix signature is not valid!");
|
|
||||||
}
|
|
||||||
|
|
||||||
$fingerprint = $sigInfo[0]['fingerprint'];
|
|
||||||
$keyInfo = gnupg_keyinfo($gpg, $fingerprint);
|
|
||||||
|
|
||||||
if (empty($keyInfo)) {
|
|
||||||
throw new \Exception("Unknown signer (key id {$sigInfo[0]['fingerprint']})");
|
|
||||||
}
|
|
||||||
|
|
||||||
$subKeys = $keyInfo[0]['subkeys'];
|
|
||||||
$keyId = null;
|
|
||||||
foreach ($subKeys as $subKey) {
|
|
||||||
if ($subKey['fingerprint'] == $fingerprint) {
|
|
||||||
$keyId = $subKey['keyid'];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ $keyInfo[0], $keyId ];
|
|
||||||
}
|
|
||||||
}
|
|
98
src/Hotfix/Signature.php
Normal file
98
src/Hotfix/Signature.php
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Hotfix;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class Signature
|
||||||
|
{
|
||||||
|
/** @var bool Whether the signature is valid */
|
||||||
|
protected $valid;
|
||||||
|
|
||||||
|
/** @var string|null The name of the key used to sign */
|
||||||
|
protected $signer;
|
||||||
|
|
||||||
|
/** @var string|null The ID of the key used to sign */
|
||||||
|
protected $keyId;
|
||||||
|
|
||||||
|
protected $error;
|
||||||
|
|
||||||
|
protected $body;
|
||||||
|
|
||||||
|
protected $signature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param bool $valid Whether the signature is valid
|
||||||
|
* @param string $signer For a valid signature, the name of the signer
|
||||||
|
* @param string $keyId For a valid signature, the key ID
|
||||||
|
*/
|
||||||
|
public function __construct($body, $signature)
|
||||||
|
{
|
||||||
|
$this->body = $body;
|
||||||
|
$this->signature = $signature;
|
||||||
|
$this->verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verify()
|
||||||
|
{
|
||||||
|
if (!$this->signature) {
|
||||||
|
$this->error = "Hotfix is not signed!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gpg = gnupg_init();
|
||||||
|
|
||||||
|
$sigInfo = gnupg_verify($gpg, $this->body, $this->signature);
|
||||||
|
|
||||||
|
if ($sigInfo === false) {
|
||||||
|
$this->error = "Invalid signature";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fingerprint = $sigInfo[0]['fingerprint'];
|
||||||
|
$keyInfo = gnupg_keyinfo($gpg, $fingerprint);
|
||||||
|
|
||||||
|
if (empty($keyInfo)) {
|
||||||
|
$this->error = "Unknown signer (key id {$sigInfo[0]['fingerprint']})";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subKeys = $keyInfo[0]['subkeys'];
|
||||||
|
$keyId = null;
|
||||||
|
foreach ($subKeys as $subKey) {
|
||||||
|
if ($subKey['fingerprint'] == $fingerprint) {
|
||||||
|
$keyId = $subKey['keyid'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$signerInfo = sprintf("%s (%s)", $keyInfo[0]['uids'][0]['name'], $keyInfo[0]['uids'][0]['email']);
|
||||||
|
|
||||||
|
$this->valid = true;
|
||||||
|
$this->signer = $signerInfo;
|
||||||
|
$this->keyId = $keyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValid()
|
||||||
|
{
|
||||||
|
return ($this->valid === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSigner()
|
||||||
|
{
|
||||||
|
return $this->signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKeyId()
|
||||||
|
{
|
||||||
|
return $this->keyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getError()
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -13,12 +13,40 @@ use NoccyLabs\Hotfix\Hotfix\Hotfix;
|
|||||||
class BashRunner implements RunnerInterface
|
class BashRunner implements RunnerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
|
const BASH_STUB = "/../stubs/bash.stub";
|
||||||
|
|
||||||
|
protected $hotfix;
|
||||||
|
|
||||||
|
protected $facts;
|
||||||
|
|
||||||
public function prepare(Hotfix $hotfix, Facts $facts)
|
public function prepare(Hotfix $hotfix, Facts $facts)
|
||||||
{
|
{
|
||||||
|
$this->hotfix = $hotfix;
|
||||||
|
$this->facts = $facts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function apply()
|
public function apply()
|
||||||
{
|
{
|
||||||
|
$head = file_get_contents(__DIR__.self::BASH_STUB);
|
||||||
|
$body = $this->hotfix->getBody();
|
||||||
|
$hash = $this->hotfix->getHash();
|
||||||
|
|
||||||
|
$facts = $this->facts->getFlat();
|
||||||
|
$head .= 'function fact {'.PHP_EOL;
|
||||||
|
$head .= ' case "$1" in'.PHP_EOL;
|
||||||
|
foreach ($facts as $key=>$fact) {
|
||||||
|
$head .= ' "'.$key.'") echo "'.$fact.'";;'.PHP_EOL;
|
||||||
|
}
|
||||||
|
$head .= ' esac'.PHP_EOL;
|
||||||
|
$head .= '}'.PHP_EOL;
|
||||||
|
|
||||||
|
// Create temporary filename based on the hash of the hotfix
|
||||||
|
$tmpfile = sys_get_temp_dir()."/hotfix_".$hash;
|
||||||
|
file_put_contents($tmpfile, $head."\n".$body."\n");
|
||||||
|
|
||||||
|
// Execute the hotfix and clean up
|
||||||
|
passthru("bash {$tmpfile}");
|
||||||
|
unlink($tmpfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -13,12 +13,40 @@ use NoccyLabs\Hotfix\Hotfix\Hotfix;
|
|||||||
class PhpRunner implements RunnerInterface
|
class PhpRunner implements RunnerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
|
const PHP_STUB = "/../stubs/php.stub";
|
||||||
|
|
||||||
|
protected $hotfix;
|
||||||
|
|
||||||
|
protected $facts;
|
||||||
|
|
||||||
public function prepare(Hotfix $hotfix, Facts $facts)
|
public function prepare(Hotfix $hotfix, Facts $facts)
|
||||||
{
|
{
|
||||||
|
$this->hotfix = $hotfix;
|
||||||
|
$this->facts = $facts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function apply()
|
public function apply()
|
||||||
{
|
{
|
||||||
|
$stub = file_get_contents(__DIR__.self::PHP_STUB);
|
||||||
|
$body = $this->hotfix->getBody();
|
||||||
|
$hash = $this->hotfix->getHash();
|
||||||
|
|
||||||
|
$stub.= 'function fact($key) { ';
|
||||||
|
$stub.= 'switch ($key) {';
|
||||||
|
$facts = $this->facts->getFlat();
|
||||||
|
foreach ($facts as $key=>$value) {
|
||||||
|
$stub.= 'case "'.$key.'": return "'.$value.'";';
|
||||||
|
}
|
||||||
|
$stub.= '}}'.PHP_EOL;
|
||||||
|
|
||||||
|
// Create temporary filename based on the hash of the hotfix filename
|
||||||
|
$tmpfile = sys_get_temp_dir()."/hotfix_".$hash;
|
||||||
|
file_put_contents($tmpfile, '<?php '.$stub."\n".$body."\n");
|
||||||
|
|
||||||
|
// Execute the hotfix and clean up
|
||||||
|
passthru("php {$tmpfile}");
|
||||||
|
unlink($tmpfile);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -12,8 +12,25 @@ class RunnerFactory
|
|||||||
public static function createRunner(Hotfix $hotfix)
|
public static function createRunner(Hotfix $hotfix)
|
||||||
{
|
{
|
||||||
$facts = Facts::getSystemFacts();
|
$facts = Facts::getSystemFacts();
|
||||||
|
$meta = $hotfix->getHeader();
|
||||||
|
|
||||||
|
switch ($meta->getLanguage()) {
|
||||||
|
case 'php':
|
||||||
|
$runner = new PhpRunner();
|
||||||
|
break;
|
||||||
|
case 'bash':
|
||||||
|
$runner = new BashRunner();
|
||||||
|
break;
|
||||||
|
case 'python':
|
||||||
|
$runner = new PythonRunner();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedRunnerException("This version of hotfix doesn't support the runner ".$meta->getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$runner->prepare($hotfix, $facts);
|
||||||
|
|
||||||
|
return $runner;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -9,6 +9,8 @@ use NoccyLabs\Hotfix\Hotfix\Hotfix;
|
|||||||
interface RunnerInterface
|
interface RunnerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
public function applyHotfix(Hotfix $hotfix, Facts $facts);
|
public function prepare(Hotfix $hotfix, Facts $facts);
|
||||||
|
|
||||||
|
public function apply();
|
||||||
|
|
||||||
}
|
}
|
27
src/Service/IxService.php
Normal file
27
src/Service/IxService.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Service;
|
||||||
|
|
||||||
|
class IxService implements ServiceInterface, ReaderInterface, PublisherInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return "ix";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInfo()
|
||||||
|
{
|
||||||
|
return "Use the ix.io pastebin for accessing patches";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function publish(Hotfix $hotfix)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read($url)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
src/Service/PublisherInterface.php
Normal file
7
src/Service/PublisherInterface.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Service;
|
||||||
|
|
||||||
|
interface PublisherInterface
|
||||||
|
{
|
||||||
|
}
|
7
src/Service/ReaderInterface.php
Normal file
7
src/Service/ReaderInterface.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Service;
|
||||||
|
|
||||||
|
interface ReaderInterface
|
||||||
|
{
|
||||||
|
}
|
7
src/Service/ServiceInterface.php
Normal file
7
src/Service/ServiceInterface.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Service;
|
||||||
|
|
||||||
|
interface ServiceInterface
|
||||||
|
{
|
||||||
|
}
|
25
src/Service/ServiceManager.php
Normal file
25
src/Service/ServiceManager.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Hotfix\Service;
|
||||||
|
|
||||||
|
class ServiceManager
|
||||||
|
{
|
||||||
|
private static $services = [];
|
||||||
|
|
||||||
|
public static function registerService(ServiceInterface $service)
|
||||||
|
{
|
||||||
|
self::$services[$service->getId()] = $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRegisteredServices()
|
||||||
|
{
|
||||||
|
return self::$services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getService($id)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($id,self::$services)) return null;
|
||||||
|
return self::$services[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user