Merge branch 'release'

This commit is contained in:
2023-06-18 19:20:46 -04:00
23 changed files with 459 additions and 0 deletions

4
.env.sample Normal file
View File

@ -0,0 +1,4 @@
MYSQL_HOST=provm.cl
MYSQL_DATABASE=incoviba
MYSQL_USER=remote_incoviba
MYSQL_PASSWORD=43918b603b84dd8bb29fd29a0ea21ba751de5dc90b26f36c

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
**/vendor/
**/*.env
**/*.lock
**/.idea/
**/logs/

5
Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM php:cli
RUN docker-php-ext-install pdo_mysql
WORKDIR /app

27
Prod.Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM php:cli AS build
RUN apt-get update && apt-get install -yq --no-install-recommends git unzip && rm -r /var/lib/apt/lists/*
COPY --from=composer /usr/bin/composer /usr/bin/composer
USER 1000
WORKDIR /code
RUN git clone --branch master http://git.provm.cl/ProVM/remote_ip.git /code
RUN composer -d /code/app install
FROM php:cli
ENV MYSQL_HOST ''
ENV MYSQL_DATABASE ''
ENV MYSQL_USER ''
ENV MYSQL_PASSWORD ''
RUN docker-php-ext-install pdo_mysql
WORKDIR /app
COPY --from=build /code/app /app
ENTRYPOINT [ "/app/bin/console" ]
CMD [ "/app/bin/console", "watch" ]
#RUN apt-get update && apt-get install -yq --no-install-recommends cron && rm -r /var/lib/apt/lists/* && cp /app/crontab /var/spool/cron/crontabs/root
#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,42 @@
<?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;
use Symfony\Component\Console\Style\SymfonyStyle;
#[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
{
$io = new SymfonyStyle($input, $output);
$io->title('Update IP');
try {
$io->info('Obtaining IP and updating database');
$this->service->update();
$io->info('Done');
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,50 @@
<?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;
use Symfony\Component\Console\Style\SymfonyStyle;
use function Safe\shell_exec;
#[AsCommand(
name: 'watch',
hidden: false
)]
class Watch extends Command
{
public function __construct(protected string $command, protected string $period, protected LoggerInterface $logger, string $name = 'watch')
{
parent::__construct($name);
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Watch');
$period = new DateInterval($this->period);
$current = new DateTimeImmutable();
$io->info('Starting');
while(true) {
$now = new DateTimeImmutable();
if ($now->diff($current) === $period) {
$io->info('Running Update');
$this->runUpdate();
$current = $now;
}
$wait = (new DateTimeImmutable((new DateTimeImmutable())->format('Y-m-d H:i:0')))->add(new DateInterval('PT1M'));
time_sleep_until($wait->getTimestamp());
}
return Command::SUCCESS;
}
protected function runUpdate(): void
{
$command = "{$this->command} update";
$this->logger->info("Running '{$command}'");
shell_exec($command);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace ProVM\Service;
use PDO;
use PDOException;
use Psr\Log\LoggerInterface;
class Connector
{
public function __construct(protected string $host, protected string $name, protected string $username, protected string $password, protected string $retries, protected LoggerInterface $logger) {}
protected PDO $connection;
/**
* @throws PDOException
*/
public function connect(): PDO
{
if (!isset($this->connection)) {
$this->logger->debug('Connecting');
$r = 0;
$exception = null;
while($r < $this->retries) {
try {
$dsn = "mysql:host={$this->host};dbname={$this->name}";
$this->connection = new PDO($dsn, $this->username, $this->password);
return $this->connection;
} catch (PDOException $e) {
$this->logger->debug('Retrying');
if ($exception !== null) {
$e = new PDOException($e->getMessage(), $e->getCode(), $exception);
}
$exception = $e;
}
$r ++;
}
throw $exception;
}
return $this->connection;
}
}

View File

@ -0,0 +1,28 @@
<?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 string $uri, protected LoggerInterface $logger) {}
public function get(): string
{
$this->logger->debug('Getting IP');
$response = $this->client->get('?format=json');
if (round($response->getStatusCode() / 100, 0) != 2) {
throw new Exception("Could not connect to '{$this->uri}'");
}
$body = $response->getBody();
$json = json_decode($body->getContents());
if (!isset($json->ip)) {
throw new Exception('Missing `ip` in JSON response');
}
$this->logger->debug("Current IP: {$json->ip}");
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 Connector $connector, 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: {$old_ip}");
if ($old_ip === $ip) {
$this->logger->debug('No change in IP');
return;
}
$this->doUpdate($ip);
$this->logger->debug('Updated IP');
}
protected function getOld(): string
{
$query = "SELECT `ip` FROM `{$this->table}` WHERE `host` = ?";
$statement = $this->connector->connect()->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->connector->connect()->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

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

@ -0,0 +1,13 @@
<?php
$app = require_once implode(DIRECTORY_SEPARATOR, [
dirname(__FILE__, 2),
'setup',
'app.php'
]);
try {
$app->run();
} catch (Exception $e) {
$app->getContainer()->get(Psr\Log\LoggerInterface::class)->warning($e);
} catch (Error $e) {
$app->getContainer()->get(Psr\Log\LoggerInterface::class)->error($e);
}

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,2 @@
<?php
Monolog\ErrorHandler::register($app->getContainer()->get(Psr\Log\LoggerInterface::class));

View File

@ -0,0 +1,22 @@
<?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' => $_ENV['MYSQL_TABLE'] ?? 'remote_ip',
]);
},
'error_logs_file' => $_ENV['ERROR_LOGS_FILE'] ?? '/logs/remote.error.log',
'debug_logs_file' => $_ENV['DEBUG_LOGS_FILE'] ?? '/logs/remote.debug.log',
'uri' => $_ENV['IPIFY_URI'] ?? 'https://api64.ipify.org',
'command' => 'php /app/public/index.php',
'period' => $_ENV['WATCH_PERIOD'] ?? 'PT20M',
'retries' => $_ENV['CONNECTION_RETRIES'] ?? 5
];

View File

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

View File

@ -0,0 +1,53 @@
<?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($container->get('debug_logs_file')),
Monolog\Level::Debug,
Monolog\Level::Warning
),
new Monolog\Handler\FilterHandler(
new Monolog\Handler\RotatingFileHandler($container->get('error_logs_file')),
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('uri'),
$container->get(Psr\Log\LoggerInterface::class)
);
},
ProVM\Service\Connector::class => function(ContainerInterface $container) {
$database = $container->get('database');
return new ProVM\Service\Connector(
$database->get('host'),
$database->get('name'),
$database->get('user')->get('name'),
$database->get('user')->get('password'),
$container->get('retries'),
$container->get(Psr\Log\LoggerInterface::class)
);
},
ProVM\Service\Repository::class => function(ContainerInterface $container) {
return new ProVM\Service\Repository(
$container->get(ProVM\Service\Connector::class),
$container->get('database')->get('table'),
$container->get(Psr\Log\LoggerInterface::class)
);
}
];

10
docker-compose.yml Normal file
View File

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