| 
<?phpdeclare(strict_types=1);
 
 /**
 * This script sets up replication of another Chronicle
 */
 use GetOpt\{
 GetOpt,
 Option
 };
 use ParagonIE\EasyDB\{
 EasyDB,
 Factory
 };
 use GuzzleHttp\Client;
 use ParagonIE\Chronicle\Chronicle;
 use ParagonIE\Chronicle\Exception\InstanceNotFoundException;
 use ParagonIE\ConstantTime\Base64UrlSafe;
 use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
 
 $root = \dirname(__DIR__);
 /** @psalm-suppress UnresolvableInclude */
 require_once $root . '/cli-autoload.php';
 
 if (!\is_readable($root . '/local/settings.json')) {
 echo 'Settings are not loaded.', PHP_EOL;
 exit(1);
 }
 
 /** @var array<string, string|string[]> $settings */
 $settings = \json_decode(
 (string) \file_get_contents($root . '/local/settings.json'),
 true
 );
 $db = Factory::create(
 $settings['database']['dsn'],
 $settings['database']['username'] ?? '',
 $settings['database']['password'] ?? '',
 $settings['database']['options'] ?? []
 );
 Chronicle::setDatabase($db);
 
 /**
 * This defines the Command Line options.
 */
 $getopt = new GetOpt([
 new Option(null, 'url', Getopt::REQUIRED_ARGUMENT),
 new Option(null, 'publickey', Getopt::REQUIRED_ARGUMENT),
 new Option(null, 'name', Getopt::REQUIRED_ARGUMENT),
 new Option('i', 'instance', Getopt::OPTIONAL_ARGUMENT),
 ]);
 $getopt->process();
 
 /** @var string $url */
 $url = $getopt->getOption('url');
 /** @var string $publicKey */
 $publicKey = $getopt->getOption('publickey');
 /** @var string $name */
 $name = $getopt->getOption('name');
 /** @var string $instance */
 $instance = $getopt->getOption('instance') ?? '';
 
 try {
 if (!empty($instance)) {
 if (!\array_key_exists($instance, $settings['instances'])) {
 throw new InstanceNotFoundException(
 'Instance ' . $instance . ' not found'
 );
 }
 Chronicle::setTablePrefix($settings['instances'][$instance]);
 }
 } catch (InstanceNotFoundException $ex) {
 echo $ex->getMessage(), PHP_EOL;
 exit(1);
 }
 
 if (!isset($url, $publicKey, $name)) {
 echo "Not enough data. Please specify:\n",
 "\t--name\n",
 "\t--publickey\n",
 "\t--url\n";
 exit(1);
 }
 
 // Retrieve public key from remote server.
 /** @var array<string, string> $response */
 $response = json_decode(
 (string) (new Client())
 ->get($url)
 ->getBody()
 ->getContents(),
 true
 );
 
 // Make sure the server's public key matches.
 if (!hash_equals($response['public-key'], $publicKey)) {
 echo 'ERROR: Server\'s public key does not match the one you provided!', PHP_EOL;
 echo '- ' . $publicKey . PHP_EOL;
 echo '+ ' . $response['public-key'] . PHP_EOL;
 exit(4);
 }
 
 // Write to database...
 
 try {
 $publicKeyObj = new SigningPublicKey(
 Base64UrlSafe::decode($publicKey)
 );
 } catch (\Throwable $ex) {
 echo $ex->getMessage(), PHP_EOL;
 exit(1);
 }
 
 $db->beginTransaction();
 $db->insert(Chronicle::getTableNameUnquoted('replication_sources', true), [
 'name' => $name,
 'uniqueid' => Base64UrlSafe::encode(random_bytes(33)),
 'publickey' => $publicKey,
 'url' => $url
 ]);
 if (!$db->commit()) {
 $db->rollBack();
 /** @var array<int, string> $errorInfo */
 $errorInfo = $db->errorInfo();
 echo $errorInfo[0], PHP_EOL;
 exit(1);
 }
 
 |