Implement Roadrunner PHP application server for high-performance PHP and Centrifugo event hooks.

This commit is contained in:
Buster Neece 2023-12-22 22:00:40 -06:00
parent f4b54c3eb6
commit e890106010
No known key found for this signature in database
21 changed files with 772 additions and 220 deletions

View File

@ -27,6 +27,11 @@ FROM ghcr.io/azuracast/azuracast.com:builtin AS docs
#
FROM ghcr.io/azuracast/icecast-kh-ac:latest AS icecast
#
# Roadrunner build step
#
FROM ghcr.io/roadrunner-server/roadrunner:2023.3.8 AS roadrunner
#
# Final build image
#
@ -49,6 +54,9 @@ COPY --from=mariadb /etc/apt/trusted.gpg.d/mariadb.gpg /etc/apt/trusted.gpg.d/ma
COPY --from=icecast /usr/local/bin/icecast /usr/local/bin/icecast
COPY --from=icecast /usr/local/share/icecast /usr/local/share/icecast
# Add Roadrunner
COPY --from=roadrunner /usr/bin/rr /usr/local/bin/rr
# Run base build process
COPY ./util/docker/common /bd_build/

View File

@ -66,10 +66,12 @@
"psr/simple-cache": "^3.0",
"ramsey/uuid": "^4.0",
"rlanvin/php-ip": "dev-master",
"roadrunner-php/centrifugo": "^2.0",
"skoerfgen/acmecert": "^3.2",
"slim/http": "^1.1",
"slim/slim": "^4.2",
"spatie/flysystem-dropbox": "^3",
"spiral/roadrunner-http": "^3.3",
"spomky-labs/otphp": "^11",
"supervisorphp/supervisor": "dev-main",
"symfony/cache": "^6",

499
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "937d84fd61dbb782e05b56a7d49ee79e",
"content-hash": "4613de83e017a7053418e8b1903b34d2",
"packages": [
{
"name": "aws/aws-crt-php",
@ -2192,6 +2192,50 @@
],
"time": "2023-11-06T15:42:03+00:00"
},
{
"name": "google/protobuf",
"version": "v3.25.1",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "1fb247e72df401c863ed239c1660f981644af5db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/1fb247e72df401c863ed239c1660f981644af5db",
"reference": "1fb247e72df401c863ed239c1660f981644af5db",
"shasum": ""
},
"require": {
"php": ">=7.0.0"
},
"require-dev": {
"phpunit/phpunit": ">=5.0.0"
},
"suggest": {
"ext-bcmath": "Need to support JSON deserialization"
},
"type": "library",
"autoload": {
"psr-4": {
"Google\\Protobuf\\": "src/Google/Protobuf",
"GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "proto library for PHP",
"homepage": "https://developers.google.com/protocol-buffers/",
"keywords": [
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v3.25.1"
},
"time": "2023-11-15T21:36:03+00:00"
},
{
"name": "graham-campbell/guzzle-factory",
"version": "v7.0.1",
@ -5932,6 +5976,151 @@
},
"time": "2022-03-02T08:51:37+00:00"
},
{
"name": "roadrunner-php/centrifugo",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-php/centrifugo.git",
"reference": "9b48bfa1d6aee0c889ea77bb2afe8f733e0ad8e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-php/centrifugo/zipball/9b48bfa1d6aee0c889ea77bb2afe8f733e0ad8e7",
"reference": "9b48bfa1d6aee0c889ea77bb2afe8f733e0ad8e7",
"shasum": ""
},
"require": {
"ext-json": "*",
"google/protobuf": "^3.7",
"php": ">=8.1",
"roadrunner-php/roadrunner-api-dto": "^1.0",
"spiral/goridge": "^4.0",
"spiral/roadrunner": "^2023.1",
"spiral/roadrunner-worker": "^3.0"
},
"require-dev": {
"mockery/mockery": "^1.5",
"phpunit/phpunit": "^10.0",
"vimeo/psalm": ">= 5.8"
},
"type": "library",
"autoload": {
"psr-4": {
"RoadRunner\\Centrifugo\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anton Titov (wolfy-j)",
"email": "wolfy-j@spiralscout.com"
},
{
"name": "Pavel Buchnev (butschster)",
"email": "pavel.buchnev@spiralscout.com"
},
{
"name": "Aleksei Gagarin (roxblnfk)",
"email": "alexey.gagarin@spiralscout.com"
},
{
"name": "Maksim Smakouz (msmakouz)",
"email": "maksim.smakouz@spiralscout.com"
},
{
"name": "Kirill Nesmeyanov (SerafimArts)",
"email": "kirill.nesmeyanov@spiralscout.com"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/spiral/roadrunner/graphs/contributors"
}
],
"description": "RoadRunner: Centrifugo bridge",
"homepage": "https://roadrunner.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://roadrunner.dev/docs",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-php/centrifugo/tree/2.0.0"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2023-04-13T11:38:58+00:00"
},
{
"name": "roadrunner-php/roadrunner-api-dto",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-php/roadrunner-api-dto.git",
"reference": "82e0889171086b485bd4c31a3575b8ffa531e6d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-php/roadrunner-api-dto/zipball/82e0889171086b485bd4c31a3575b8ffa531e6d1",
"reference": "82e0889171086b485bd4c31a3575b8ffa531e6d1",
"shasum": ""
},
"require": {
"google/protobuf": "^v3.22",
"php": "^8.1"
},
"conflict": {
"temporal/sdk": "<2.6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Temporal\\": "generated/Temporal",
"RoadRunner\\": "generated/RoadRunner",
"GPBMetadata\\": "generated/GPBMetadata"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Pavel Butchnev (butschster)",
"email": "pavel.buchnev@spiralscout.com"
},
{
"name": "Aleksei Gagarin (roxblnfk)",
"email": "alexey.gagarin@spiralscout.com"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/roadrunner-server/roadrunner/graphs/contributors"
}
],
"description": "RoadRunner PHP API",
"homepage": "https://roadrunner.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://roadrunner.dev/docs",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-php/roadrunner-api-dto/tree/1.4.0"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2023-10-11T13:36:31+00:00"
},
{
"name": "skoerfgen/acmecert",
"version": "3.3.1",
@ -6371,6 +6560,314 @@
],
"time": "2023-09-25T07:13:36+00:00"
},
{
"name": "spiral/goridge",
"version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-php/goridge.git",
"reference": "d955f58be1c51daa1eb94a5ddaf4c2daf64ee14e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-php/goridge/zipball/d955f58be1c51daa1eb94a5ddaf4c2daf64ee14e",
"reference": "d955f58be1c51daa1eb94a5ddaf4c2daf64ee14e",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-sockets": "*",
"php": ">=8.1",
"spiral/roadrunner": "^2023"
},
"require-dev": {
"google/protobuf": "^3.22",
"infection/infection": "^0.26.1",
"jetbrains/phpstorm-attributes": "^1.0",
"phpunit/phpunit": "^10.0",
"rybakit/msgpack": "^0.7",
"vimeo/psalm": "^5.9"
},
"suggest": {
"ext-msgpack": "MessagePack codec support",
"ext-protobuf": "Protobuf codec support",
"google/protobuf": "(^3.0) Protobuf codec support",
"rybakit/msgpack": "(^0.7) MessagePack codec support"
},
"type": "goridge",
"autoload": {
"psr-4": {
"Spiral\\Goridge\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anton Titov (wolfy-j)",
"email": "wolfy-j@spiralscout.com"
},
{
"name": "Valery Piashchynski",
"homepage": "https://github.com/rustatian"
},
{
"name": "Aleksei Gagarin (roxblnfk)",
"homepage": "https://github.com/roxblnfk"
},
{
"name": "Pavel Buchnev (butschster)",
"email": "pavel.buchnev@spiralscout.com"
},
{
"name": "Maksim Smakouz (msmakouz)",
"email": "maksim.smakouz@spiralscout.com"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/roadrunner-server/roadrunner/graphs/contributors"
}
],
"description": "High-performance PHP-to-Golang RPC bridge",
"homepage": "https://spiral.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://roadrunner.dev/docs",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-php/goridge/tree/4.1.0"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2023-10-03T18:40:43+00:00"
},
{
"name": "spiral/roadrunner",
"version": "v2023.3.8",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-server/roadrunner.git",
"reference": "80eff8aa42c59bc2d2cc9a2fab4e02398fd94994"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/80eff8aa42c59bc2d2cc9a2fab4e02398fd94994",
"reference": "80eff8aa42c59bc2d2cc9a2fab4e02398fd94994",
"shasum": ""
},
"type": "metapackage",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anton Titov / Wolfy-J",
"email": "wolfy.jd@gmail.com"
},
{
"name": "Valery Piashchynski",
"homepage": "https://github.com/rustatian"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/roadrunner-server/roadrunner/graphs/contributors"
}
],
"description": "RoadRunner: High-performance PHP application server and process manager written in Go and powered with plugins",
"homepage": "https://roadrunner.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://roadrunner.dev/docs",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-server/roadrunner/tree/v2023.3.8"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2023-12-14T15:48:20+00:00"
},
{
"name": "spiral/roadrunner-http",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-php/http.git",
"reference": "7a1f7d0729ad483e9c637ee276f6e910def33f4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-php/http/zipball/7a1f7d0729ad483e9c637ee276f6e910def33f4d",
"reference": "7a1f7d0729ad483e9c637ee276f6e910def33f4d",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=8.1",
"psr/http-factory": "^1.0.1",
"psr/http-message": "^1.0.1 || ^2.0",
"spiral/roadrunner": "^2023.3",
"spiral/roadrunner-worker": "^3.1.0"
},
"require-dev": {
"buggregator/trap": "^1.0",
"jetbrains/phpstorm-attributes": "^1.0",
"nyholm/psr7": "^1.3",
"phpunit/phpunit": "^10.0",
"symfony/process": "^6.2 || ^7.0",
"vimeo/psalm": "^5.9"
},
"suggest": {
"spiral/roadrunner-cli": "Provides RoadRunner installation and management CLI tools"
},
"type": "library",
"autoload": {
"psr-4": {
"Spiral\\RoadRunner\\Http\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anton Titov (wolfy-j)",
"email": "wolfy-j@spiralscout.com"
},
{
"name": "Valery Piashchynski",
"homepage": "https://github.com/rustatian"
},
{
"name": "Aleksei Gagarin (roxblnfk)",
"homepage": "https://github.com/roxblnfk"
},
{
"name": "Pavel Buchnev (butschster)",
"email": "pavel.buchnev@spiralscout.com"
},
{
"name": "Maksim Smakouz (msmakouz)",
"email": "maksim.smakouz@spiralscout.com"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/roadrunner-server/roadrunner/graphs/contributors"
}
],
"description": "RoadRunner: HTTP and PSR-7 worker",
"homepage": "https://spiral.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://roadrunner.dev/docs",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-php/http/tree/3.3.0"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2023-12-05T20:45:38+00:00"
},
{
"name": "spiral/roadrunner-worker",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/roadrunner-php/worker.git",
"reference": "53f81c3e138a650c4605fcd60cae7c89ef24061f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roadrunner-php/worker/zipball/53f81c3e138a650c4605fcd60cae7c89ef24061f",
"reference": "53f81c3e138a650c4605fcd60cae7c89ef24061f",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.0",
"ext-json": "*",
"ext-sockets": "*",
"php": ">=8.1",
"psr/log": "^2.0|^3.0",
"spiral/goridge": "^4.1.0",
"spiral/roadrunner": "^2023.1"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "^1.0",
"phpunit/phpunit": "^10.0",
"symfony/var-dumper": "^6.3 || ^7.0",
"vimeo/psalm": "^5.9"
},
"suggest": {
"spiral/roadrunner-cli": "Provides RoadRunner installation and management CLI tools"
},
"type": "library",
"autoload": {
"psr-4": {
"Spiral\\RoadRunner\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anton Titov (wolfy-j)",
"email": "wolfy-j@spiralscout.com"
},
{
"name": "Valery Piashchynski",
"homepage": "https://github.com/rustatian"
},
{
"name": "Aleksei Gagarin (roxblnfk)",
"homepage": "https://github.com/roxblnfk"
},
{
"name": "Pavel Buchnev (butschster)",
"email": "pavel.buchnev@spiralscout.com"
},
{
"name": "Maksim Smakouz (msmakouz)",
"email": "maksim.smakouz@spiralscout.com"
},
{
"name": "RoadRunner Community",
"homepage": "https://github.com/roadrunner-server/roadrunner/graphs/contributors"
}
],
"description": "RoadRunner: PHP worker",
"homepage": "https://spiral.dev/",
"support": {
"chat": "https://discord.gg/V6EK4he",
"docs": "https://roadrunner.dev/docs",
"forum": "https://forum.roadrunner.dev/",
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
"source": "https://github.com/roadrunner-php/worker/tree/3.3.0"
},
"funding": [
{
"url": "https://github.com/sponsors/roadrunner-server",
"type": "github"
}
],
"time": "2023-12-05T19:53:37+00:00"
},
{
"name": "spomky-labs/otphp",
"version": "11.2.0",

View File

@ -69,9 +69,6 @@ return static function (RouteCollectorProxy $app) {
$group->post('/sftp-event', Controller\Api\Internal\SftpEventAction::class)
->setName('api:internal:sftp-event');
$group->post('/centrifugo', Controller\Api\Internal\CentrifugoAction::class)
->setName('api:internal:centrifugo');
$group->get('/relays', Controller\Api\Internal\RelaysController::class)
->setName('api:internal:relays')
->add(Middleware\RequireLogin::class);

View File

@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Internal;
use App\Cache\NowPlayingCache;
use App\Container\LoggerAwareTrait;
use App\Controller\SingleActionInterface;
use App\Entity\Api\NowPlaying\NowPlaying;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Service\Centrifugo;
use App\Utilities\Types;
use Psr\Http\Message\ResponseInterface;
final class CentrifugoAction implements SingleActionInterface
{
use LoggerAwareTrait;
public function __construct(
private readonly NowPlayingCache $npCache
) {
}
public function __invoke(ServerRequest $request, Response $response, array $params): ResponseInterface
{
$parsedBody = Types::array($request->getParsedBody());
$this->logger->debug('Centrifugo connection body', $parsedBody);
$channels = array_filter(
$parsedBody['channels'] ?? [],
fn($channel) => str_starts_with($channel, 'station:') || $channel === Centrifugo::GLOBAL_TIME_CHANNEL
);
$allInitialData = [];
$router = $request->getRouter();
$router->buildBaseUrl(false);
foreach ($channels as $channel) {
$initialData = [];
if ($channel === Centrifugo::GLOBAL_TIME_CHANNEL) {
$initialData['time'] = time();
} elseif (str_starts_with($channel, 'station:')) {
$stationName = substr($channel, 8);
$np = $this->npCache->getForStation($stationName);
if (!($np instanceof NowPlaying)) {
continue;
}
$np->resolveUrls($router->getBaseUrl());
$np->update();
$initialData['np'] = $np;
$initialData['triggers'] = [];
}
$allInitialData[] = [
'channel' => $channel,
'pub' => [
'data' => $initialData,
],
];
}
return $response->withJson([
'result' => [
'user' => '',
'channels' => $channels,
'data' => $allInitialData,
],
]);
}
}

View File

@ -30,27 +30,37 @@ final class Centrifugo
'method' => 'publish',
'params' => [
'channel' => self::GLOBAL_TIME_CHANNEL,
'data' => [
'time' => time(),
],
'data' => $this->buildTimeMessage(),
],
]);
}
public function buildTimeMessage(): array
{
return [
'time' => time(),
];
}
public function publishToStation(Station $station, mixed $message, array $triggers): void
{
$this->send([
'method' => 'publish',
'params' => [
'channel' => $this->getChannelName($station),
'data' => [
'np' => $message,
'triggers' => $triggers,
],
'data' => $this->buildStationMessage($message, $triggers),
],
]);
}
public function buildStationMessage(mixed $message, array $triggers = []): array
{
return [
'np' => $message,
'triggers' => $triggers,
];
}
private function send(array $body): void
{
$this->client->post(

View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace App\Service\Centrifugo;
use App\Cache\NowPlayingCache;
use App\Container\LoggerAwareTrait;
use App\Entity\Api\NowPlaying\NowPlaying;
use App\Http\RouterInterface;
use App\Service\Centrifugo;
use RoadRunner\Centrifugo\Payload\ConnectResponse;
use RoadRunner\Centrifugo\Payload\ResponseInterface;
use RoadRunner\Centrifugo\Request\Connect;
use RoadRunner\Centrifugo\Request\Invalid;
use RoadRunner\Centrifugo\Request\RequestInterface;
use RuntimeException;
use Throwable;
final class EventHandler
{
use LoggerAwareTrait;
public function __construct(
private readonly NowPlayingCache $npCache,
private readonly Centrifugo $centrifugo,
private readonly RouterInterface $router
) {
if (!$this->centrifugo->isSupported()) {
throw new RuntimeException('Centrifugo is not supported.');
}
$this->router->buildBaseUrl(false);
}
public function __invoke(RequestInterface $request): ?ResponseInterface
{
if ($request instanceof Invalid) {
$e = $request->getException();
$this->logger->error(
sprintf('Centrifugo error: %s', $e->getMessage()),
[
'exception' => $e,
]
);
return null;
}
try {
if ($request instanceof Connect) {
return $this->onConnect($request);
}
} catch (Throwable $e) {
$request->error($e->getCode(), $e->getMessage());
}
return null;
}
private function onConnect(Connect $request): ?ResponseInterface
{
$channels = array_filter(
$request->channels,
fn($channel) => str_starts_with($channel, 'station:') || $channel === Centrifugo::GLOBAL_TIME_CHANNEL
);
if (empty($channels)) {
return null;
}
$allInitialData = [];
foreach ($channels as $channel) {
if ($channel === Centrifugo::GLOBAL_TIME_CHANNEL) {
$initialData = $this->centrifugo->buildTimeMessage();
} elseif (str_starts_with($channel, 'station:')) {
$stationName = substr($channel, 8);
$np = $this->npCache->getForStation($stationName);
if (!($np instanceof NowPlaying)) {
continue;
}
$np->resolveUrls($this->router->getBaseUrl());
$np->update();
$initialData = $this->centrifugo->buildStationMessage($np);
} else {
continue;
}
$allInitialData[] = [
'channel' => $channel,
'pub' => [
'data' => $initialData,
],
];
}
return new ConnectResponse(
user: '',
data: $allInitialData,
channels: $channels
);
}
}

View File

@ -80,7 +80,7 @@ final class ServiceControl
'cron' => __('Runs routine synchronized tasks'),
'mariadb' => __('Database'),
'nginx' => __('Web server'),
'php-fpm' => __('PHP FastCGI Process Manager'),
'roadrunner' => __('Roadrunner PHP Server'),
'php-nowplaying' => __('Now Playing manager service'),
'php-worker' => __('PHP queue processing worker'),
'redis' => __('Cache'),

View File

@ -20,4 +20,3 @@ apt-get remove --purge -y php${PHP_VERSION}-dev zlib1g-dev build-essential
echo "extension=spx.so" > /etc/php/${PHP_VERSION}/mods-available/30-spx.ini
ln -s /etc/php/${PHP_VERSION}/mods-available/30-spx.ini /etc/php/${PHP_VERSION}/cli/conf.d/30-spx.ini
ln -s /etc/php/${PHP_VERSION}/mods-available/30-spx.ini /etc/php/${PHP_VERSION}/fpm/conf.d/30-spx.ini

View File

@ -8,8 +8,10 @@ websocket_disable: true
uni_websocket: true
uni_sse: true
uni_http_stream: true
proxy_connect_endpoint: http://localhost:6010/api/internal/centrifugo
proxy_connect_endpoint: grpc://127.0.0.1:6300
proxy_connect_timeout: 10s
grpc_api: true
grpc_api_port: 6301
allowed_origins:
- "*"

View File

@ -1,9 +1,5 @@
upstream php-fpm-internal {
server unix:/var/run/php-fpm-internal.sock;
}
upstream php-fpm-www {
server unix:/var/run/php-fpm-www.sock;
upstream roadrunner {
server 127.0.0.1:6090;
}
upstream centrifugo {
@ -16,40 +12,6 @@ upstream vite {
}
{{end}}
# Internal connection handler for PubSub and internal API calls
server {
listen 127.0.0.1:6010;
root /var/azuracast/www/web;
index index.php;
server_name localhost;
# Default clean URL routing
location / {
try_files $uri @clean_url;
}
location @clean_url {
rewrite ^(.*)$ /index.php last;
}
location ~ ^/index\.php(/|$) {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-fpm-internal;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
include fastcgi_params;
fastcgi_read_timeout 600;
fastcgi_buffering off;
internal;
}
}
server {
listen {{ default .Env.AZURACAST_HTTP_PORT "80" }};
listen {{ default .Env.AZURACAST_HTTPS_PORT "443" }} default_server http2 ssl;
@ -134,18 +96,10 @@ server {
{{end}}
location ~ ^/index\.php(/|$) {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
fastcgi_pass php-fpm-www;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
include fastcgi_params;
fastcgi_pass roadrunner;
fastcgi_read_timeout {{ default .Env.NGINX_TIMEOUT "1800" }};
fastcgi_buffering off;
internal;
}

View File

@ -1,53 +0,0 @@
[global]
error_log = /dev/stderr
daemonize = no
[www]
user = azuracast
group = azuracast
listen = /var/run/php-fpm-www.sock
listen.owner = azuracast
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = {{ default .Env.PHP_FPM_MAX_CHILDREN "20" }}
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200
pm.status_path = /status
pm.process_idle_timeout = 60s
chdir = /
clear_env=No
catch_workers_output = yes
decorate_workers_output = no
{{if eq .Env.PROFILING_EXTENSION_ENABLED "1"}}
process.dumpable = yes
{{end}}
[internal]
user = azuracast
group = azuracast
listen = /var/run/php-fpm-internal.sock
listen.owner = azuracast
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 50
pm.process_idle_timeout = 60s
chdir = /
clear_env=No
catch_workers_output = yes
decorate_workers_output = no

View File

@ -0,0 +1,30 @@
version: '3'
rpc:
listen: tcp://127.0.0.1:6401
server:
relay: pipes
command: "php util/rr_http_worker.php"
logs:
mode: {{if eq .Env.APPLICATION_ENV "development"}}development{{else}}production{{end}}
http:
address: 0.0.0.0:6010
fcgi:
address: tcp://127.0.0.1:6090
pool:
debug: {{if eq .Env.APPLICATION_ENV "development"}}true{{else}}false{{end}}
max_jobs: 50
centrifuge:
proxy_address: tcp://127.0.0.1:6300
grpc_api_address: tcp://127.0.0.1:6301
use_compressor: true
name: "azuracast"
pool:
debug: {{if eq .Env.APPLICATION_ENV "development"}}true{{else}}false{{end}}
command: "php util/rr_centrifugo_worker.php"
num_workers: 1
max_jobs: 250

View File

@ -1,20 +0,0 @@
#!/bin/bash
source /etc/php/.version
# Set up PHP config
dockerize -template "/etc/php/${PHP_VERSION}/fpm/05-azuracast.ini.tmpl:/etc/php/${PHP_VERSION}/fpm/conf.d/05-azuracast.ini" \
-template "/etc/php/${PHP_VERSION}/fpm/www.conf.tmpl:/etc/php/${PHP_VERSION}/fpm/pool.d/www.conf" \
cp /etc/php/${PHP_VERSION}/fpm/conf.d/05-azuracast.ini /etc/php/${PHP_VERSION}/cli/conf.d/05-azuracast.ini
# Wait for services to spin up.
gosu azuracast php /var/azuracast/www/bin/uptime_wait || exit 1
# Initialize before running FPM
gosu azuracast azuracast_cli azuracast:setup --init || exit 1
# Run initial Acme check
gosu azuracast azuracast_cli azuracast:acme:get-certificate || true
# Run PHP-FPM
exec /usr/sbin/php-fpm${PHP_VERSION} -F --fpm-config /etc/php/${PHP_VERSION}/fpm/php-fpm.conf -c /etc/php/${PHP_VERSION}/fpm/

View File

@ -0,0 +1,20 @@
#!/bin/bash
if [ $(whoami) != 'azuracast' ]; then
echo 'This script must be run as the "azuracast" user. Rerunning...'
exec gosu azuracast run_roadrunner "$@"
fi
dockerize -template "/var/azuracast/rr.yaml.tmpl:/var/azuracast/rr.yaml"
# Wait for services to spin up.
php /var/azuracast/www/bin/uptime_wait || exit 1
# Initialize before running FPM
azuracast_cli azuracast:setup --init || exit 1
# Run initial Acme check
azuracast_cli azuracast:acme:get-certificate || true
# Run PHP-FPM
exec rr serve -c /var/azuracast/rr.yaml -w /var/azuracast/www

View File

@ -1,5 +1,5 @@
[program:php-fpm]
command=run_php_fpm
[program:roadrunner]
command=run_roadrunner
priority=300
numprocs=1
autostart=true

View File

@ -12,7 +12,7 @@ echo "deb-src [signed-by=/etc/apt/keyrings/php.gpg] https://ppa.launchpadcontent
apt-get update
apt-get install -y --no-install-recommends php${PHP_VERSION}-fpm php${PHP_VERSION}-cli php${PHP_VERSION}-gd \
apt-get install -y --no-install-recommends php${PHP_VERSION}-cli php${PHP_VERSION}-gd \
php${PHP_VERSION}-curl php${PHP_VERSION}-xml php${PHP_VERSION}-zip \
php${PHP_VERSION}-gmp php${PHP_VERSION}-mysqlnd php${PHP_VERSION}-mbstring php${PHP_VERSION}-intl \
php${PHP_VERSION}-redis php${PHP_VERSION}-maxminddb
@ -20,11 +20,7 @@ apt-get install -y --no-install-recommends php${PHP_VERSION}-fpm php${PHP_VERSIO
# Copy PHP configuration
echo "PHP_VERSION=$PHP_VERSION" >> /etc/php/.version
mkdir -p /run/php
touch /run/php/php${PHP_VERSION}-fpm.pid
cp /bd_build/web/php/php.ini.tmpl /etc/php/${PHP_VERSION}/fpm/05-azuracast.ini.tmpl
cp /bd_build/web/php/www.conf.tmpl /etc/php/${PHP_VERSION}/fpm/www.conf.tmpl
cp /bd_build/web/php/php.ini.tmpl /etc/php/${PHP_VERSION}/cli/05-azuracast.ini.tmpl
# Enable FFI (for StereoTool inspection)
echo 'ffi.enable="true"' >> /etc/php/${PHP_VERSION}/mods-available/ffi.ini

View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
set -x
cp /bd_build/web/roadrunner/rr.yaml.tmpl /var/azuracast/rr.yaml.tmpl

View File

@ -0,0 +1,5 @@
#!/bin/bash
source /etc/php/.version
dockerize -template "/etc/php/${PHP_VERSION}/cli/05-azuracast.ini.tmpl:/etc/php/${PHP_VERSION}/cli/conf.d/05-azuracast.ini"

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
use App\AppFactory;
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
ini_set('display_errors', '1');
require dirname(__DIR__) . '/vendor/autoload.php';
$app = AppFactory::createApp();
$di = $app->getContainer();
/** @var App\Service\Centrifugo\EventHandler $centrifugo */
$centrifugo = $di->get(App\Service\Centrifugo\EventHandler::class);
$worker = Spiral\RoadRunner\Worker::create();
$requestFactory = new RoadRunner\Centrifugo\Request\RequestFactory($worker);
$centrifugoWorker = new RoadRunner\Centrifugo\CentrifugoWorker($worker, $requestFactory);
while ($request = $centrifugoWorker->waitRequest()) {
$response = $centrifugo->__invoke($request);
if (null !== $response) {
$request->respond($response);
}
}

44
util/rr_http_worker.php Normal file
View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
use App\AppFactory;
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
ini_set('display_errors', '1');
require dirname(__DIR__) . '/vendor/autoload.php';
$environment = AppFactory::buildEnvironment([
App\Environment::IS_CLI => false,
]);
$diBuilder = AppFactory::createContainerBuilder($environment);
$httpFactory = new App\Http\HttpFactory();
$worker = \Spiral\RoadRunner\Worker::create();
$psr7Worker = new Spiral\RoadRunner\Http\PSR7Worker($worker, $httpFactory, $httpFactory, $httpFactory);
while (true) {
try {
$request = $psr7Worker->waitRequest();
if ($request === null) {
break;
}
} catch (\Throwable $e) {
$psr7Worker->respond($httpFactory->createResponse(400));
continue;
}
try {
$di = AppFactory::buildContainer($diBuilder);
$app = AppFactory::buildAppFromContainer($di);
$response = $app->handle($request);
$psr7Worker->respond($response);
} catch (\Throwable $e) {
$psr7Worker->respond($httpFactory->createResponse(500, 'Critical error'));
$psr7Worker->getWorker()->error((string)$e);
}
gc_collect_cycles();
}