diff --git a/config/nginx-php.conf b/config/nginx-php.conf
new file mode 100644
index 0000000000000000000000000000000000000000..f1329d4fe6e60cbb0191a3ae715140ff96e37428
--- /dev/null
+++ b/config/nginx-php.conf
@@ -0,0 +1,11 @@
+location ~ \.php(?:$|/) {
+ include fastcgi_params;
+ fastcgi_split_path_info ^(.+?\.php)(/.*)$;
+ fastcgi_intercept_errors on;
+ fastcgi_pass $fastcgi_backend;
+ fastcgi_index index.php;
+ fastcgi_param SERVER_NAME $http_host;
+ fastcgi_param DOCUMENT_ROOT $site_document_root;
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ fastcgi_param SCRIPT_FILENAME $request_filename;
+}
diff --git a/lib/Commands/Compile.php b/lib/Commands/Compile.php
index 3ead649497207bf892cb6d5dc47a4b48a5680ea9..f05ad95320b70a2591540628c99f4075b383a8bd 100644
--- a/lib/Commands/Compile.php
+++ b/lib/Commands/Compile.php
@@ -4,14 +4,18 @@ namespace Studip\Dockerized\Commands;
use Studip\Dockerized\Config;
use Studip\Dockerized\Creators\DockerComposeConfiguration;
use Studip\Dockerized\Creators\NginxConfiguration;
+use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
-final class Compile extends \Symfony\Component\Console\Command\Command
+final class Compile extends Command
{
+ use GetCompiledPath;
+
protected function configure()
{
$this->setName('compile');
@@ -21,10 +25,14 @@ final class Compile extends \Symfony\Component\Console\Command\Command
p',
InputOption::VALUE_OPTIONAL,
'Path to store the compiled files',
- realpath(__DIR__ . '/../../compiled')
+ $this->getCompiledPath()
);
$this->addOption('nginx', null, InputOption::VALUE_NEGATABLE, 'Compile nginx configuration', true);
$this->addOption('docker', null, InputOption::VALUE_NEGATABLE, 'Compile docker configuration', true);
+
+ $this->addOption('build', 'b', InputOption::VALUE_NEGATABLE, 'Whether to build the containers');
+ $this->addOption('force', 'F', InputOption::VALUE_NONE, 'Force compilation');
+ $this->addOption('start', 's', InputOption::VALUE_NONE, 'Start container after compilation (requires "build")');
}
protected function initialize(InputInterface $input, OutputInterface $output)
@@ -48,25 +56,41 @@ final class Compile extends \Symfony\Component\Console\Command\Command
protected function execute(InputInterface $input, OutputInterface $output)
{
$compiledPath = realpath($input->getOption('path'));
+ $force = $input->getOption('force');
$io = new SymfonyStyle($input, $output);
- $writeConfigFile = function (string $filename, string $content) use ($compiledPath, $io): void {
- file_put_contents(
- "{$compiledPath}/{$filename}",
- $content
- );
- $io->success('Config file ' . $filename . ' written');
+ $writeConfigFile = function (string $filename, string $content) use ($compiledPath, $force, $io): bool {
+ $filepath = "{$compiledPath}/{$filename}";
+
+ if (
+ !file_exists($filepath)
+ || $content !== file_get_contents($filepath)
+ || $force
+ ) {
+ file_put_contents($filepath, $content);
+ $io->success('Config file ' . $filename . ' written');
+
+ return true;
+ }
+
+ return false;
};
+ $changed = 0;
if ($input->getOption('nginx')) {
- $writeConfigFile(
+ $changed += (int) $writeConfigFile(
'nginx.conf',
$this->createNginxConfiguration()
);
+
+ $changed += (int) $writeConfigFile(
+ 'nginx-sites.conf',
+ $this->createNginxSites()
+ );
}
if ($input->getOption('docker')) {
- $writeConfigFile(
+ $changed += (int) $writeConfigFile(
'docker-compose.yml',
$this->createDockerComposerConfiguration(
realpath(__DIR__ . '/../..'),
@@ -75,6 +99,23 @@ final class Compile extends \Symfony\Component\Console\Command\Command
);
}
+ if ($changed === 0) {
+ $io->comment('No changes');
+ }
+ if ($input->getOption('build')) {
+ $this->getApplication()->doRun(
+ new ArrayInput(['command' => 'docker:build']),
+ $output
+ );
+
+ if ($input->getOption('start')) {
+ $this->getApplication()->doRun(
+ new ArrayInput(['command' => 'docker:start']),
+ $output
+ );
+ }
+ }
+
return Command::SUCCESS;
}
@@ -89,8 +130,8 @@ final class Compile extends \Symfony\Component\Console\Command\Command
// Declare volumes
$volumes = [
- [realpath(__DIR__ . '/../../web'), '/var/www/html/studip-dockerized-config.app', 'ro'],
- [realpath(__DIR__ . '/../../config.json'), '/var/www/html/studip-dockerized-config.json', 'ro'],
+ [realpath($cwd . '/web'), '/var/www/html/studip-dockerized-config.app', 'ro'],
+ [realpath($cwd . '/config.json'), '/var/www/html/studip-dockerized-config.json', 'ro'],
...array_map(
fn(string $path, array $definition) => [$definition['source'], '/var/www/html/' . $path],
array_keys(Config::getInstance()->get('sites') ?? []),
@@ -110,15 +151,18 @@ final class Compile extends \Symfony\Component\Console\Command\Command
),
]);
- $creator->addServiceVolume(
- 'nginx',
- "{$compiledPath}/nginx.conf",
- '/etc/nginx/conf.d/default.conf',
- 'ro'
- );
+ $mounts = [
+ "{$compiledPath}/nginx.conf" => 'conf.d/nginx.conf',
+ "{$compiledPath}/nginx-sites.conf" => 'sites.conf',
+ "{$cwd}/config/nginx-php.conf" => 'php.conf',
+ "{$compiledPath}/ssl.crt" => "ssl.crt",
+ "{$compiledPath}/ssl.key" => "ssl.key",
+ ];
+ foreach ($mounts as $source => $target) {
+ $creator->addServiceVolume('nginx', $source, "/etc/nginx/{$target}", 'ro');
+ }
$creator->addServiceVolume('nginx', realpath("{$cwd}/logs/nginx"), '/var/log/nginx');
-
foreach ($volumes as $volume) {
$creator->addServiceVolume('nginx', ...$volume);
}
@@ -176,26 +220,40 @@ final class Compile extends \Symfony\Component\Console\Command\Command
{
$creator = new NginxConfiguration();
- // Register event handler to add the nginx-php configuration to each location
- $creator->on(NginxConfiguration::EVENT_LOCATION_ADD_BEFORE, function ($location, &$config) use ($creator) {
- $config[] = $creator->write('location ~ \.php(?:$|/)', [
- 'include fastcgi_params',
- 'fastcgi_split_path_info ^(.+?\.php)(/.*)$',
- 'fastcgi_intercept_errors on',
- 'fastcgi_pass $fastcgi_backend',
- 'fastcgi_index index.php',
- 'fastcgi_param SERVER_NAME $http_host',
- 'fastcgi_param DOCUMENT_ROOT $site_document_root',
- 'fastcgi_param PATH_INFO $fastcgi_path_info',
- 'fastcgi_param SCRIPT_FILENAME $request_filename',
+ // Add upstreams and servers
+ foreach (Config::getInstance()->get('php') as $version => $port) {
+ $index = $this->getPHPVersionIndex($version, 'php');
+
+ $creator->addUpstream("{$index}_backend", [
+ "server {$index}:9000"
]);
- });
+
+ $creator->addServer([
+ 'listen ' . $port . ' default_server ssl',
+ 'server_name _',
+
+ 'ssl_certificate /etc/nginx/ssl.crt',
+ 'ssl_certificate_key /etc/nginx/ssl.key',
+
+ 'root /var/www/html',
+ 'set $fastcgi_backend ' . $index . '_backend',
+ 'include sites.conf',
+ ]);
+ }
+
+ return $creator->dump();
+ }
+
+ private function createNginxSites(): string
+ {
+ $creator = new NginxConfiguration();
// Add default location
$creator->addLocation('/sites', [
'alias /var/www/html/studip-dockerized-config.app',
'index index.php index.html index.html',
'set $site_document_root /var/www/html/studip-dockerized-config.app',
+ 'include php.conf',
]);
// Add configured locations
@@ -209,22 +267,7 @@ final class Compile extends \Symfony\Component\Console\Command\Command
'alias ' . $p,
'index index.php index.html index.html',
'set $site_document_root ' . $p,
- ]);
- }
-
- // Add upstreams and servers
- foreach (Config::getInstance()->get('php') as $version => $port) {
- $index = $this->getPHPVersionIndex($version, 'php');
-
- $creator->addUpstream("{$index}_backend", [
- "server {$index}:9000"
- ]);
-
- $creator->addServer([
- 'listen ' . $port . ' default_server',
- 'server_name _',
- 'root /var/www/html',
- 'set $fastcgi_backend ' . $index . '_backend',
+ 'include php.conf',
]);
}
diff --git a/lib/Commands/Docker/Build.php b/lib/Commands/Docker/Build.php
index 080e444e727ffaa7a7c9d2586374464de75f47e7..3ba1047f6728ed06eb0c7d93e49928adc5d15530 100644
--- a/lib/Commands/Docker/Build.php
+++ b/lib/Commands/Docker/Build.php
@@ -2,6 +2,10 @@
namespace Studip\Dockerized\Commands\Docker;
use Studip\Dockerized\DockerComposeCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
final class Build extends DockerComposeCommand
{
@@ -12,6 +16,34 @@ final class Build extends DockerComposeCommand
}
protected function getDockerComposeCommand(): array
{
+ $dockerRunning = $this->isDockerRunning();
+ if ($dockerRunning) {
+ $this->createDockerComposeCommand(['down']);
+ }
+
return ['up', '--no-start'];
}
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $dockerRunning = $this->isDockerRunning();
+ if ($dockerRunning) {
+ $this->getApplication()->doRun(
+ new ArrayInput(['command' => 'docker:stop']),
+ $output
+ );
+ }
+
+ $result = parent::execute($input, $output);
+ if ($result !== Command::SUCCESS || !$dockerRunning) {
+ return $result;
+ }
+
+ $this->getApplication()->doRun(
+ new ArrayInput(['command' => 'docker:start']),
+ $output
+ );
+
+ return Command::SUCCESS;
+ }
}
\ No newline at end of file
diff --git a/lib/Commands/Keys/Create.php b/lib/Commands/Keys/Create.php
new file mode 100644
index 0000000000000000000000000000000000000000..dcec2387c57042fdcfde29048a3e89f81eec8589
--- /dev/null
+++ b/lib/Commands/Keys/Create.php
@@ -0,0 +1,47 @@
+<?php /** @noinspection PhpExpressionResultUnusedInspection */
+
+namespace Studip\Dockerized\Commands\Keys;
+
+use Studip\Dockerized\Traits\GetCompiledPath;
+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 Symfony\Component\Process\Process;
+
+final class Create extends Command
+{
+ use GetCompiledPath;
+
+ protected function configure()
+ {
+ $this->setName('keys:create');
+ $this->setDescription('Creates necessary keys for ssl');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $process = new Process([
+ 'openssl', 'req',
+ '-batch',
+ '-x509',
+ '-nodes',
+ '-days', '365',
+ '-newkey', 'rsa:2048',
+ '-keyout', $this->getCompiledPath('ssl.key'),
+ '-out', $this->getCompiledPath('ssl.crt'),
+ ]);
+ $process->run();
+
+ if (!$process->isSuccessful()) {
+ $io->error(['Could not create keys', $process->getErrorOutput()]);
+ return Command::FAILURE;
+ }
+
+ $io->success('Keys created successfully');
+
+ return Command::SUCCESS;
+ }
+}
diff --git a/lib/Config.php b/lib/Config.php
index 147ed44b3ae8ba6bcc0437092d8eae03b8fd61ab..74cb7db9647149585ee5bed767353d9b503f957e 100644
--- a/lib/Config.php
+++ b/lib/Config.php
@@ -75,7 +75,7 @@ final class Config
public function store(): void
{
- if (!is_writable($this->filename)) {
+ if (file_exists($this->filename) && !is_writable($this->filename)) {
throw new \Exception("Config file '{$this->filename}' is not writable");
}
diff --git a/lib/Creators/NginxConfiguration.php b/lib/Creators/NginxConfiguration.php
index fba16eec5b1c1c4b1c1a6916082de2f357a44d53..9e4c7103fd84901bfc392da4ce2545641252f537 100644
--- a/lib/Creators/NginxConfiguration.php
+++ b/lib/Creators/NginxConfiguration.php
@@ -70,19 +70,17 @@ final class NginxConfiguration
...array_map(
fn($server) => $this->write(
'server',
- array_merge(
- $server['config'],
- array_map(
- fn($location) => $this->write(
- "location {$location['modifier']} {$location['location']}",
- $location['config']
- ),
- $this->locations
- )
- )
+ $server['config']
),
$this->servers
),
+ ...array_map(
+ fn($location) => $this->write(
+ "location {$location['modifier']} {$location['location']}",
+ $location['config']
+ ),
+ $this->locations
+ ),
]));
$result = (string) Scope::fromFile($tempName);
unlink($tempName);
diff --git a/lib/DockerComposeCommand.php b/lib/DockerComposeCommand.php
index adc8636e873d7b6e11fc98063153130a96f534b9..f6cf4669dcbfb448982cadf307916bf88a14da42 100644
--- a/lib/DockerComposeCommand.php
+++ b/lib/DockerComposeCommand.php
@@ -1,7 +1,7 @@
<?php
namespace Studip\Dockerized;
-use Studip\Dockerized\Traits\GetCompiledDockerComposeYMLPath;
+use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -9,17 +9,29 @@ use Symfony\Component\Process\Process;
abstract class DockerComposeCommand extends Command
{
- use GetCompiledDockerComposeYMLPath;
+ use GetCompiledPath;
abstract protected function getDockerComposeCommand(): array;
protected function execute(InputInterface $input, OutputInterface $output)
{
- $process = new Process([
+ $process = $this->createDockerComposeCommand($this->getDockerComposeCommand());
+ $this->streamProcess($process, $output);
+ return $process->isSuccessful() ? Command::SUCCESS : Command::FAILURE;
+ }
+
+ protected function createDockerComposeCommand(array $command): Process
+ {
+ return new Process([
'docker', 'compose',
- '-f', self::GetCompiledDockerComposeYMLPath(),
- ...$this->getDockerComposeCommand(),
+ '-f', self::GetCompiledPath() . '/docker-compose.yml',
+ ...$command,
]);
+
+ }
+
+ protected function streamProcess(Process $process, OutputInterface $output): void
+ {
$process->setTty(true);
$process->mustRun(function ($type, $buffer) use ($output) {
if (Process::OUT === $type) {
@@ -28,7 +40,19 @@ abstract class DockerComposeCommand extends Command
$output->write('<error>' . $buffer . '</error>');
}
});
+ }
- return $process->isSuccessful() ? Command::SUCCESS : Command::FAILURE;
+ protected function isDockerRunning(): bool
+ {
+ $process = $this->createDockerComposeCommand(['ps']);
+ $process->run();
+
+ if (!$process->isSuccessful()) {
+ return false;
+ }
+
+ $output = trim($process->getOutput());
+
+ return count(explode("\n", $output)) > 1;
}
}
\ No newline at end of file
diff --git a/lib/Traits/GetCompiledDockerComposeYMLPath.php b/lib/Traits/GetCompiledDockerComposeYMLPath.php
deleted file mode 100644
index 0c04b9229f26dbac5b2d1aeed0113feb6e7633ec..0000000000000000000000000000000000000000
--- a/lib/Traits/GetCompiledDockerComposeYMLPath.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-namespace Studip\Dockerized\Traits;
-
-trait GetCompiledDockerComposeYMLPath
-{
- protected static function GetCompiledDockerComposeYMLPath(): string
- {
- $filename = realpath(__DIR__ . '/../../compiled/docker-compose.yml');
- if (!file_exists($filename)) {
- throw new \Exception('docker-compose.yml was not yet compiled');
- }
- if (!is_readable($filename)) {
- throw new \Exception('docker-compose.yml is not readable');
- }
- return $filename;
- }
-}
\ No newline at end of file
diff --git a/lib/Traits/GetCompiledPath.php b/lib/Traits/GetCompiledPath.php
new file mode 100644
index 0000000000000000000000000000000000000000..d0169b00de1145843337d822cd65ad18e16d6ea2
--- /dev/null
+++ b/lib/Traits/GetCompiledPath.php
@@ -0,0 +1,10 @@
+<?php
+namespace Studip\Dockerized\Traits;
+
+trait GetCompiledPath
+{
+ protected static function GetCompiledPath(?string $filename = null): string
+ {
+ return realpath(__DIR__ . '/../../compiled') . ($filename ? '/' . $filename : '');
+ }
+}
\ No newline at end of file
diff --git a/studip-docker b/studip-docker
index a1bb30e561cc38b53971820cd00f978a56ab77ca..aa126775ff0714be8fb39f2fabde3f9a758cc5d2 100755
--- a/studip-docker
+++ b/studip-docker
@@ -7,13 +7,13 @@ use Symfony\Component\Console\Application;
const CONFIG_FILE = __DIR__ . '/config.json';
-\Studip\Dockerized\Config::load(CONFIG_FILE);
-
$application = new Application('Stud.IP Dockerized', '1.0');
$application->add(new Commands\Init());
if (file_exists(CONFIG_FILE)) {
+ \Studip\Dockerized\Config::load(CONFIG_FILE);
+
$application->add(new Commands\Compile());
$application->add(new Commands\Sites\Add());
@@ -27,6 +27,8 @@ if (file_exists(CONFIG_FILE)) {
$application->add(new Commands\Docker\Build());
$application->add(new Commands\Docker\Start());
$application->add(new Commands\Docker\Stop());
+
+ $application->add(new Commands\Keys\Create());
}
$application->run();
diff --git a/web/index.php b/web/index.php
index 8169b242c432de2e8dbe88c2163169d37c058399..5fdebf2f5bcf369aac79fd9c7046e25311ecbc94 100644
--- a/web/index.php
+++ b/web/index.php
@@ -34,7 +34,7 @@ th {
<td><?= htmlentities($path) ?></td>
<?php foreach ($config['php'] as $version => $port): ?>
<td>
- <a href="http://localhost:<?= $port ?>/<?= htmlentities($path) ?>">
+ <a href="https://localhost:<?= $port ?>/<?= htmlentities($path) ?>">
<?= htmlentities($version) ?>
</a>
</td>