This commit is contained in:
2023-06-16 21:44:35 -04:00
parent b553ac403e
commit adad8cea81
18 changed files with 360 additions and 0 deletions

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM php:cli
RUN apt-get update && apt-get install -yq --no-install-recommends cron && rm -r /var/lib/apt/lists/* \
&& docker-php-ext-install pdo_mysql
CMD [ "cron", "-f", "-L", "15" ]

3
app/bin/console Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
php /app/public/index.php "$@"

View File

@ -0,0 +1,36 @@
<?php
namespace ProVM\Command;
use Exception;
use PDOException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ProVM\Service\Remote;
#[AsCommand(
name: 'update',
hidden: false
)]
class UpdateIp extends Command
{
public function __construct(protected Remote $service, protected LoggerInterface $logger, ?string $name = 'update')
{
parent::__construct($name);
}
public function execute(InputInterface $input, OutputInterface $output): int
{
try {
$this->service->update();
return Command::SUCCESS;
} catch (PDOException $e) {
$this->logger->warning($e);
return Command::FAILURE;
} catch (Exception $e) {
$this->logger->error($e);
return Command::FAILURE;
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace ProVM\Command;
use DateTimeImmutable;
use DateInterval;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'watch',
hidden: false
)]
class Watch extends Command
{
public function __construct(protected string $period, ?string $name = 'watch')
{
parent::__construct($name);
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$period = new DateInterval($this->period);
$current = new DateTimeImmutable();
while(true) {
$now = new DateTimeImmutable();
if ($now->diff($current) === $period) {
$this->runUpdate();
$current = $now;
}
}
return Command::SUCCESS;
}
protected function runUpdate(): void
{
$command = '/app/bin/console update';
shell_exec($command);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace ProVM\Service;
use Exception;
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerInterface;
use function Safe\json_decode;
class Ipify
{
public function __construct(protected ClientInterface $client, protected LoggerInterface $logger) {}
public function get(): string
{
$this->logger->debug('Getting IP');
$response = $this->client->get('?format=json');
if (round($response->getCode() / 100, 0) !== 2) {
throw new Exception("Could not connect to '{$this->client->base_uri}'");
}
$body = $response->getBody();
$json = json_decode($body->getContents());
if (!isset($json->ip)) {
throw new Exception('Missing `ip` in JSON response');
}
return $json->ip;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace ProVM\Service;
class Remote
{
public function __construct(protected Ipify $ipService, protected Repository $dbService) {}
public function update(): void
{
$ip = $this->ipService->get();
$this->dbService->update($ip);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace ProVM\Service;
use PDO;
use Psr\Log\LoggerInterface;
class Repository
{
public function __construct(protected PDO $connection, protected string $table, protected LoggerInterface $logger) {}
public function update(string $ip): void
{
$this->logger->debug('Updating Database');
$old_ip = $this->getOld();
$this->logger->debug($old_ip);
if ($old_ip === $ip) {
$this->logger->debug('No change in IP');
return;
}
$this->doUpdate();
$this->logger->debug('Updated IP');
}
protected function getOld(): string
{
$query = "SELECT `ip` FROM `{$this->table}` WHERE `host` = ?";
$statement = $this->connection->prepare($query);
$statement->execute(['vialdelamaza']);
return $statement->fetch()['ip'];
}
protected function doUpdate(string $ip): void
{
$query = "UPDATE `remote_ip` SET `ip` = ?, `updated` = CURRENT_TIMESTAMP() WHERE `host` = ?";
$statement = $this->connection->prepare($query);
$statement->execute([$ip, 'vialdelamaza']);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace ProVM\Wrapper;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application;
class App extends Application
{
protected ContainerInterface $container;
public function getContainer(): ContainerInterface
{
return $this->container;
}
public function setContainer(ContainerInterface $container): App
{
$this->container = $container;
return $this;
}
}

22
app/composer.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "provm/remote_ip",
"type": "project",
"require": {
"guzzlehttp/guzzle": "^7.7",
"monolog/monolog": "^3.3",
"php-di/php-di": "^7.0",
"symfony/console": "^6.3",
"thecodingmachine/safe": "^2.5"
},
"require-dev": {
"phpunit/phpunit": "^10.2"
},
"autoload": {
"psr-4": {
"ProVM\\": "common/"
}
},
"config": {
"sort-packages": true
}
}

1
app/crontab Normal file
View File

@ -0,0 +1 @@
*/20 * * * 1-5 /app/bin/console

7
app/public/index.php Normal file
View File

@ -0,0 +1,7 @@
<?php
$app = require_once implode(DIRECTORY_SEPARATOR, [
dirname(__FILE__, 2),
'setup',
'app.php'
]);
$app->run();

40
app/setup/app.php Normal file
View File

@ -0,0 +1,40 @@
<?php
use DI\ContainerBuilder;
use ProVM\Wrapper\App;
require_once 'composer.php';
function buildApp(): App {
$builder = new ContainerBuilder();
$folders = [
'settings',
'setups'
];
foreach ($folders as $f) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, $f]);
if (!file_exists($folder)) {
continue;
}
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$builder->addDefinitions($file->getRealPath());
}
}
$app = new App();
$app->setContainer($builder->build());
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'middlewares']);
if (file_exists($folder)) {
$files = new FilesystemIterator($folder);
foreach($files as $file) {
if ($file->isDir()) {
continue;
}
require_once $file->getRealPath();
}
}
return $app;
}
return buildApp();

6
app/setup/composer.php Normal file
View File

@ -0,0 +1,6 @@
<?php
require_once implode(DIRECTORY_SEPARATOR, [
dirname(__FILE__, 2),
'vendor',
'autoload.php'
]);

View File

@ -0,0 +1,3 @@
<?php
$app->add($app->getContainer()->get(ProVM\Command\Watch::class));
$app->add($app->getContainer()->get(ProVM\Command\UpdateIp::class));

View File

@ -0,0 +1,19 @@
<?php
return [
'database' => function() {
return new DI\Container([
'host' => $_ENV['MYSQL_HOST'],
'name' => $_ENV['MYSQL_DATABASE'],
'user' => function() {
return new DI\Container([
'name' => $_ENV['MYSQL_USER'],
'password' => $_ENV['MYSQL_PASSWORD']
]);
},
'table' => 'remote_ip',
]);
},
'uri' => 'https://api64.ipify.org',
'period' => 'PT20M',
'retries' => 5
];

View File

@ -0,0 +1,8 @@
<?php
use Psr\Container\ContainerInterface;
return [
ProVM\Command\Watch::class => function(ContainerInterface $container) {
return new ProVM\Command\Watch($container->get('period'));
}
];

View File

@ -0,0 +1,60 @@
<?php
use Psr\Container\ContainerInterface;
return [
Psr\Log\LoggerInterface::class => function(ContainerInterface $container) {
return new Monolog\Logger('file', [
new Monolog\Handler\FilterHandler(
new Monolog\Handler\RotatingFileHandler('/var/log/remote.debug.log'),
Monolog\Level::Debug,
Monolog\Level::Warning
),
new Monolog\Handler\FilterHandler(
new Monolog\Handler\RotatingFileHandler('/var/log/remote.error.log'),
Monolog\Level::Error
)
], [
new Monolog\Processor\PsrLogMessageProcessor(),
new Monolog\Processor\IntrospectionProcessor(),
new Monolog\Processor\MemoryUsageProcessor(),
new Monolog\Processor\MemoryPeakUsageProcessor(),
]);
},
Psr\Http\Client\ClientInterface::class => function(ContainerInterface $container) {
return new GuzzleHttp\Client([
'base_uri' => $container->get('uri')
]);
},
ProVM\Service\Ipify::class => function(ContainerInterface $container) {
return new ProVM\Service\Ipify(
$container->get(Psr\Http\Client\ClientInterface::class),
$container->get(Psr\Log\LoggerInterface::class)
);
},
PDO::class => function(ContainerInterface $container) {
$database = $container->get('database');
$retries = $container->get('retries');
$r = 0;
$exception = null;
while($r < $retries) {
try {
$dsn = "mysql:host={$database->get('host')};dbname={$database->get('name')}";
return new PDO($dsn, $database->get('user')->get('name'), $database->get('user')->get('password'));
} catch (PDOException $e) {
if ($exception !== null) {
$e = new PDOException($e->getMessage(), $e->getCode(), $exception);
}
$exception = $e;
$container->get(Psr\Log\LoggerInterface::class)->debug('Retrying Connection');
}
}
throw $exception;
},
ProVM\Service\Repository::class => function(ContainerInterface $container) {
return new ProVM\Service\Repository(
$container->get(PDO::class),
$container->get('database')->get('table'),
$container->get(Psr\Log\LoggerInterface::class)
);
}
];

9
docker-compose.yml Normal file
View File

@ -0,0 +1,9 @@
services:
remote_ip:
container_name: remote_ip
build: .
restart: unless-stopped
env_file: .env
volumes:
- ./app:/app
- ./app/crontab:/var/spool/cron/crontabs/root