Initial commit
This commit is contained in:
76
src/Command/ApplyCommand.php
Normal file
76
src/Command/ApplyCommand.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
class ApplyCommand extends Command
|
||||
{
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName("apply");
|
||||
$this->setDescription("Apply a hotfix from a file or the Internet");
|
||||
|
||||
$this->addOption("insecure", "I", InputOption::VALUE_NONE, "Disable signature checks. Don't use unless you know what you are doing!");
|
||||
|
||||
$this->addArgument("hotfix", InputArgument::OPTIONAL, "The identifier, path or filename of the hotfix");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
$fix = $input->getArgument("hotfix");
|
||||
$insecure = $input->getOption("insecure");
|
||||
|
||||
$output->writeln("Reading hotfix <comment>{$fix}</comment>...");
|
||||
|
||||
$loader = new Loader();
|
||||
try {
|
||||
$hotfix = $loader->load($fix, $insecure);
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("<error>Error: ".$e->getMessage()."</error>");
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln("");
|
||||
if (($signer = $hotfix->getSignedBy())) {
|
||||
$output->writeln("<info>Hotfix has good signature by {$signer}</info>");
|
||||
} else {
|
||||
$output->writeln("<fg=red;options=bold>Warning: Hotfix is not signed or signature not valid!</fg=red;options=bold>");
|
||||
}
|
||||
$output->writeln("");
|
||||
$output->writeln(" Hotfix: <comment>".$hotfix->getName()."</comment>");
|
||||
$output->writeln(" Author: <comment>".$hotfix->getAuthor()."</comment>");
|
||||
$output->writeln(" Info:");
|
||||
$info = explode("\n",wordwrap(trim($hotfix->getInfo()), 70));
|
||||
foreach ($info as $line) {
|
||||
$output->writeln(" | <info>{$line}</info>");
|
||||
}
|
||||
$output->writeln("");
|
||||
|
||||
$helper = $this->getHelper("question");
|
||||
$question = new ConfirmationQuestion("Do you want to apply this hotfix? [y/N] ", false);
|
||||
|
||||
if (!$helper->ask($input, $output, $question)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln("\n<info>Applying hotfix...</info>\n");
|
||||
try {
|
||||
$hotfix->apply();
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("\n<error>Error: ".$e->getMessage()."</error>");
|
||||
}
|
||||
$output->writeln("\n<info>Hotfix applied successfully</info>");
|
||||
}
|
||||
|
||||
}
|
48
src/Command/SignCommand.php
Normal file
48
src/Command/SignCommand.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
class SignCommand extends Command
|
||||
{
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName("sign");
|
||||
$this->setDescription("Sign and bundle a hotfix");
|
||||
|
||||
$this->addOption("signer", "s", InputOption::VALUE_REQUIRED, "Signer e-mail");
|
||||
$this->addArgument("hotfix", InputArgument::OPTIONAL, "The filename of the hotfix to sign");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$fix = $input->getArgument("hotfix");
|
||||
$signer = $input->getOption("signer");
|
||||
|
||||
$cmdl = 'gpg --sign --armour --detach';
|
||||
if ($signer) {
|
||||
$cmdl.= ' --default-key '.$signer;
|
||||
}
|
||||
$cmdl.= ' '.escapeshellarg($fix);
|
||||
|
||||
passthru($cmdl);
|
||||
|
||||
$out = $fix.".signed";
|
||||
|
||||
file_put_contents($out,
|
||||
file_get_contents($fix).
|
||||
file_get_contents($fix.'.asc')
|
||||
);
|
||||
unlink($fix.'.asc');
|
||||
|
||||
}
|
||||
|
||||
}
|
85
src/Hotfix/Hotfix.php
Normal file
85
src/Hotfix/Hotfix.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Hotfix
|
||||
{
|
||||
|
||||
protected $signer;
|
||||
|
||||
protected $header;
|
||||
|
||||
protected $body;
|
||||
|
||||
public function __construct($hotfix, $signer)
|
||||
{
|
||||
$this->load($hotfix);
|
||||
$this->signer = $signer;
|
||||
}
|
||||
|
||||
protected function load($hotfix)
|
||||
{
|
||||
// echo $hotfix."\n\n";
|
||||
list ($header, $body) = explode("\n---\n", $hotfix, 2);
|
||||
$header = Yaml::parse($header);
|
||||
|
||||
$this->header = $header;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function apply()
|
||||
{
|
||||
if (!array_key_exists('lang', $this->header)) {
|
||||
$lang = 'bash';
|
||||
} else {
|
||||
$lang = strtolower($this->header['lang']);
|
||||
}
|
||||
|
||||
$script = null;
|
||||
switch ($lang) {
|
||||
case 'bash':
|
||||
$exec = "/bin/bash";
|
||||
break;
|
||||
case 'php':
|
||||
$exec = "/usr/bin/env php";
|
||||
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, $this->body);
|
||||
passthru($exec." ".$tmpfile);
|
||||
unlink($tmpfile);
|
||||
}
|
||||
|
||||
public function getSignedBy()
|
||||
{
|
||||
if (!$this->signer) {
|
||||
return null;
|
||||
}
|
||||
return sprintf("%s <%s>",
|
||||
$this->signer['uids'][0]['name'],
|
||||
$this->signer['uids'][0]['email']
|
||||
);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->header['hotfix'];
|
||||
}
|
||||
|
||||
public function getInfo()
|
||||
{
|
||||
return $this->header['info'];
|
||||
}
|
||||
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->header['author'];
|
||||
}
|
||||
|
||||
}
|
67
src/Hotfix/Loader.php
Normal file
67
src/Hotfix/Loader.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?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 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;
|
||||
$signer = $this->verifySignature($body, $signature);
|
||||
}
|
||||
return new Hotfix($body, $signer);
|
||||
}
|
||||
fprintf(STDERR, "Error: Couldn't load '%s'", $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!");
|
||||
}
|
||||
|
||||
$keyInfo = gnupg_keyinfo($gpg, $sigInfo[0]['fingerprint']);
|
||||
|
||||
if (empty($keyInfo)) {
|
||||
throw new \Exception("Unknown signer (key id {$sigInfo[0]['fingerprint']})");
|
||||
}
|
||||
|
||||
return $keyInfo[0];
|
||||
}
|
||||
}
|
18
src/Hotfix/Loader/FileLoader.php
Normal file
18
src/Hotfix/Loader/FileLoader.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
class FileLoader implements LoaderInterface
|
||||
{
|
||||
public function load($fix)
|
||||
{
|
||||
if ($fix[0] !== '/') {
|
||||
$fix = getcwd()."/".$fix;
|
||||
}
|
||||
if (file_exists($fix)) {
|
||||
$hotfix = file_get_contents($fix);
|
||||
return $hotfix;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
13
src/Hotfix/Loader/GistLoader.php
Normal file
13
src/Hotfix/Loader/GistLoader.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
class GistLoader implements LoaderInterface
|
||||
{
|
||||
public function load($fix)
|
||||
{
|
||||
if (!preg_match('/^gist\:/i', $fix)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
11
src/Hotfix/Loader/HttpLoader.php
Normal file
11
src/Hotfix/Loader/HttpLoader.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
class HttpLoader implements LoaderInterface
|
||||
{
|
||||
public function load($fix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
8
src/Hotfix/Loader/LoaderInterface.php
Normal file
8
src/Hotfix/Loader/LoaderInterface.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
interface LoaderInterface
|
||||
{
|
||||
public function load($fix);
|
||||
}
|
13
src/Hotfix/Loader/PastebinLoader.php
Normal file
13
src/Hotfix/Loader/PastebinLoader.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix\Loader;
|
||||
|
||||
class PastebinLoader implements LoaderInterface
|
||||
{
|
||||
public function load($fix)
|
||||
{
|
||||
if (!preg_match('/^pastebin\:/i', $fix)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
17
src/HotfixApplication.php
Normal file
17
src/HotfixApplication.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
class HotfixApplication extends Application
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct("Hotfixer", "0.1");
|
||||
$this->add(new Command\ApplyCommand());
|
||||
$this->add(new Command\SignCommand());
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user