Start working on an all in one command

This commit is contained in:
James Gilliland 2022-03-04 00:18:18 -06:00
parent 5aa5d85f40
commit 2908793ce2
10 changed files with 355 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/vendor/

14
bin/pfatt Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env php
<?php
// application.php
require __DIR__ . '/../vendor/autoload.php';
use Pfatt\Commands\Monitor;
use Symfony\Component\Console\Application;
$application = new Application();
$application->add(new Monitor());
// ... register commands
$application->run();

37
composer.json Normal file
View file

@ -0,0 +1,37 @@
{
"name": "neclimdul/pfatt",
"description": "Bridge tool for PHP stuff.",
"type": "project",
"license": "MIT",
"autoload": {
"psr-4": {
"Pfatt\\": "src/"
}
},
"authors": [
{
"name": "James Gilliland",
"email": "neclimdul@gmail.com"
}
],
"require": {
"php": "~7.4",
"psr/log": "^1.1",
"symfony/console": "^5.4"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"vimeo/psalm": "^4.22"
},
"scripts": {
"psalm": "psalm",
"phpstan": "phpstan",
"static": [
"@phpstan",
"@psalm"
]
},
"config": {
"sort-packages": true
}
}

3
phpstan-baseline.neon Normal file
View file

@ -0,0 +1,3 @@
parameters:
ignoreErrors: []

6
phpstan.neon.dist Normal file
View file

@ -0,0 +1,6 @@
includes:
- phpstan-baseline.neon
parameters:
level: 9
paths:
- src

15
psalm.xml Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

61
src/Commands/Monitor.php Normal file
View file

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Pfatt\Commands;
use Pfatt\Config;
use Pfatt\Logger;
use Pfatt\NgController;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
final class Monitor extends Command
{
protected static $defaultName = 'monitor';
protected static $defaultDescription = 'Monitor connection to trigger 5268AC updates';
protected Config $config;
protected Logger $logger;
protected NgController $ngControl;
protected function configure(): void
{
// @todo Inject these. Maybe with a factory or container.
$this->config = new Config('', '', '');
$this->logger = new Logger('pfatt-5268AC');
$this->ngControl = new NgController($this->logger);
}
/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->setOutput($output);
$this->logger->info('Starting 5268AC ping monitor ...');
register_shutdown_function(function () { $this->logger->info('Stopping 5268AC ping monitor ...'); });
while ($input->isInteractive()) {
if ($this->ping()) {
$this->ngControl->ngRmHook();
}
elseif (!$this->ngControl->ngIsConnected()) {
$this->ngControl->ngConnect();
}
sleep(5);
}
return 0;
}
private function ping(): int
{
$process = proc_open('/sbin/ping -t2 -q -c1 ' . $this->config->getPingHost(), [], $pipes);
if ($process) {
return proc_close($process);
}
return 1;
}
}

66
src/Config.php Normal file
View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Pfatt;
/**
* Config wrapper.
*/
class Config
{
private string $pingHost = '8.8.8.8';
private string $ontInterface;
private string $rgInterface;
private string $rgEthernetMac;
public function __construct(string $ontInterface, string $rgInterface, string $rgEthernetMac, ?string $pingHost = null)
{
$this->ontInterface = $ontInterface;
$this->rgInterface = $rgInterface;
$this->rgEthernetMac = $rgEthernetMac;
if (isset($pingHost)) {
$this->pingHost = $pingHost;
}
}
/**
* Host to ping to check connection.
*
* @return string
*/
public function getPingHost(): string
{
return $this->pingHost;
}
/**
* Network interface connected to the ONT.
*
* @return string
*/
public function getOntInterface(): string
{
return $this->ontInterface;
}
/**
* Network interface connected to the Residential Gateway.
*
* @return string
*/
public function getRgInterface(): string
{
return $this->rgInterface;
}
/**
* Residential Gateway MAC address.
*
* @return string
*/
public function getRgEthernetMac(): string
{
return $this->rgEthernetMac;
}
}

63
src/Logger.php Normal file
View file

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Pfatt;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
/**
* PSR-3 style logger.
*/
class Logger extends AbstractLogger
{
private string $channel;
/**
* @var \Psr\Log\LoggerInterface|null
*/
private ?LoggerInterface $logger;
public function __construct(string $channel, LoggerInterface $logger = null)
{
$this->channel = $channel;
$this->logger = $logger;
}
/**
* {@inheritDoc}
*/
public function log($level, $message, array $context = array())
{
$full_message = $this->getTimestamp() . ' :: [' . $this->channel . '] :: ' . $message;
if (isset($this->logger)) {
$this->logger->log($level, $full_message, $context);
}
else {
echo $full_message;
}
}
/**
* Get the current timestamp formatted for logs.
*
* @return string
* ISO8601 formatted timestamp.
*/
private function getTimestamp(): string
{
return (new \DateTime())->format(\DateTimeInterface::ISO8601);
}
/**
* Add output to use for writing.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* Output service.
*/
public function setOutput(OutputInterface $output): void {
$this->logger = new ConsoleLogger($output);
}
}

89
src/NgController.php Normal file
View file

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Pfatt;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class NgController implements LoggerAwareInterface
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Use ngctl to check connection.
*/
public function ngIsConnected(): int
{
return $this->ngCtlRun('show laneapfilter:eapout');
}
/**
* Use ngctl remove connection.
*/
public function ngRmHook(): void
{
$this->logger->info('Disconnecting netgraph node ...');
$this->ngCtlRun('rmhook laneapfilter: eapout') ?
$this->logger->info('OK!') :
$this->logger->error('ERROR!');
}
/**
* Use ngctl to establish connection.
*/
public function ngConnect(): void
{
$this->logger->info('Connecting netgraph node ...');
$this->ngCtlRun('connect waneapfilter: laneapfilter: eapout eapout') ?
$this->logger->info('OK!') :
$this->logger->error('ERROR!');
}
/**
* Check filter status.
*
* @param string $arg
* Additional arguments to pass to ngctl.
*
* @return int
* Process return code.
*/
private function ngCtlRun(string $arg): int
{
$descriptorspec = [
// 0 => ['pipe', 'r'],
// 1 => ['pipe', 'w'],
// 2 => ['pipe', 'r'],
];
$cwd = '/tmp';
$env = [];
$process = proc_open(
'/usr/sbin/ngctl ' . $arg,
$descriptorspec,
$pipes,
$cwd,
$env
);
if ($process) {
return proc_close($process);
}
return 1;
}
/**
* {@inheritDoc}
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
}