mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-02-12 00:30:15 +00:00
Initial import
This commit is contained in:
commit
2295a33590
884 changed files with 93939 additions and 0 deletions
34
php/src/Auth/AuthManager.php
Normal file
34
php/src/Auth/AuthManager.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Auth;
|
||||
|
||||
use AIO\Data\ConfigurationManager;
|
||||
|
||||
class AuthManager {
|
||||
private const SESSION_KEY = 'aio_authenticated';
|
||||
private ConfigurationManager $configurationManager;
|
||||
|
||||
public function __construct(ConfigurationManager $configurationManager) {
|
||||
$this->configurationManager = $configurationManager;
|
||||
}
|
||||
|
||||
public function CheckCredentials(string $username, string $password) : bool {
|
||||
if($username === $this->configurationManager->GetUserName()) {
|
||||
return hash_equals($this->configurationManager->GetPassword(), $password);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function CheckToken(string $token) : bool {
|
||||
return hash_equals($this->configurationManager->GetToken(), $token);
|
||||
}
|
||||
|
||||
public function SetAuthState(bool $isLoggedIn) : void {
|
||||
$_SESSION[self::SESSION_KEY] = $isLoggedIn;
|
||||
}
|
||||
|
||||
public function IsAuthenticated() : bool {
|
||||
return isset($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY] === true;
|
||||
}
|
||||
}
|
||||
7801
php/src/Auth/PasswordGenerator.php
Normal file
7801
php/src/Auth/PasswordGenerator.php
Normal file
File diff suppressed because it is too large
Load diff
112
php/src/Container/Container.php
Normal file
112
php/src/Container/Container.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container;
|
||||
|
||||
use AIO\Container\State\IContainerState;
|
||||
use AIO\Data\ConfigurationManager;
|
||||
use AIO\Docker\DockerActionManager;
|
||||
use AIO\ContainerDefinitionFetcher;
|
||||
|
||||
class Container {
|
||||
private string $identifier;
|
||||
private string $displayName;
|
||||
private string $containerName;
|
||||
private string $restartPolicy;
|
||||
private int $maxShutdownTime;
|
||||
private ContainerPorts $ports;
|
||||
private ContainerInternalPorts $internalPorts;
|
||||
private ContainerVolumes $volumes;
|
||||
private ContainerEnvironmentVariables $containerEnvironmentVariables;
|
||||
/** @var string[] */
|
||||
private array $dependsOn;
|
||||
/** @var string[] */
|
||||
private array $secrets;
|
||||
private DockerActionManager $dockerActionManager;
|
||||
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
string $displayName,
|
||||
string $containerName,
|
||||
string $restartPolicy,
|
||||
int $maxShutdownTime,
|
||||
ContainerPorts $ports,
|
||||
ContainerInternalPorts $internalPorts,
|
||||
ContainerVolumes $volumes,
|
||||
ContainerEnvironmentVariables $containerEnvironmentVariables,
|
||||
array $dependsOn,
|
||||
array $secrets,
|
||||
DockerActionManager $dockerActionManager
|
||||
) {
|
||||
$this->identifier = $identifier;
|
||||
$this->displayName = $displayName;
|
||||
$this->containerName = $containerName;
|
||||
$this->restartPolicy = $restartPolicy;
|
||||
$this->maxShutdownTime = $maxShutdownTime;
|
||||
$this->ports = $ports;
|
||||
$this->internalPorts = $internalPorts;
|
||||
$this->volumes = $volumes;
|
||||
$this->containerEnvironmentVariables = $containerEnvironmentVariables;
|
||||
$this->dependsOn = $dependsOn;
|
||||
$this->secrets = $secrets;
|
||||
$this->dockerActionManager = $dockerActionManager;
|
||||
}
|
||||
|
||||
public function GetIdentifier() : string {
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function GetDisplayName() : string {
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function GetContainerName() : string {
|
||||
return $this->containerName;
|
||||
}
|
||||
|
||||
public function GetRestartPolicy() : string {
|
||||
return $this->restartPolicy;
|
||||
}
|
||||
|
||||
public function GetMaxShutdownTime() : int {
|
||||
return $this->maxShutdownTime;
|
||||
}
|
||||
|
||||
public function GetSecrets() : array {
|
||||
return $this->secrets;
|
||||
}
|
||||
|
||||
public function GetPorts() : ContainerPorts {
|
||||
return $this->ports;
|
||||
}
|
||||
|
||||
public function GetInternalPorts() : ContainerInternalPorts {
|
||||
return $this->internalPorts;
|
||||
}
|
||||
|
||||
public function GetVolumes() : ContainerVolumes {
|
||||
return $this->volumes;
|
||||
}
|
||||
|
||||
public function GetRunningState() : IContainerState {
|
||||
return $this->dockerActionManager->GetContainerRunningState($this);
|
||||
}
|
||||
|
||||
public function GetUpdateState() : IContainerState {
|
||||
return $this->dockerActionManager->GetContainerUpdateState($this);
|
||||
}
|
||||
|
||||
public function GetStartingState() : IContainerState {
|
||||
return $this->dockerActionManager->GetContainerStartingState($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function GetDependsOn() : array {
|
||||
return $this->dependsOn;
|
||||
}
|
||||
|
||||
public function GetEnvironmentVariables() : ContainerEnvironmentVariables {
|
||||
return $this->containerEnvironmentVariables;
|
||||
}
|
||||
}
|
||||
19
php/src/Container/ContainerEnvironmentVariables.php
Normal file
19
php/src/Container/ContainerEnvironmentVariables.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container;
|
||||
|
||||
class ContainerEnvironmentVariables {
|
||||
/** @var string[] */
|
||||
private array $variables = [];
|
||||
|
||||
public function AddVariable(string $variable) : void {
|
||||
$this->variables[] = $variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function GetVariables() : array {
|
||||
return $this->variables;
|
||||
}
|
||||
}
|
||||
19
php/src/Container/ContainerInternalPorts.php
Normal file
19
php/src/Container/ContainerInternalPorts.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container;
|
||||
|
||||
class ContainerInternalPorts {
|
||||
/** @var string[] */
|
||||
private array $internalPorts = [];
|
||||
|
||||
public function AddInternalPort(string $internalPort) : void {
|
||||
$this->internalPorts[] = $internalPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function GetInternalPorts() : array {
|
||||
return $this->internalPorts;
|
||||
}
|
||||
}
|
||||
19
php/src/Container/ContainerPorts.php
Normal file
19
php/src/Container/ContainerPorts.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container;
|
||||
|
||||
class ContainerPorts {
|
||||
/** @var string[] */
|
||||
private array $ports = [];
|
||||
|
||||
public function AddPort(string $port) : void {
|
||||
$this->ports[] = $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function GetPorts() : array {
|
||||
return $this->ports;
|
||||
}
|
||||
}
|
||||
19
php/src/Container/ContainerVolume.php
Normal file
19
php/src/Container/ContainerVolume.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container;
|
||||
|
||||
class ContainerVolume {
|
||||
public string $name;
|
||||
public string $mountPoint;
|
||||
public bool $isWritable;
|
||||
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $mountPoint,
|
||||
bool $isWritable
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->mountPoint = $mountPoint;
|
||||
$this->isWritable = $isWritable;
|
||||
}
|
||||
}
|
||||
19
php/src/Container/ContainerVolumes.php
Normal file
19
php/src/Container/ContainerVolumes.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container;
|
||||
|
||||
class ContainerVolumes {
|
||||
/** @var ContainerVolume[] */
|
||||
private array $volumes = [];
|
||||
|
||||
public function AddVolume(ContainerVolume $volume) {
|
||||
$this->volumes[] = $volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContainerVolume[]
|
||||
*/
|
||||
public function GetVolumes() : array {
|
||||
return $this->volumes;
|
||||
}
|
||||
}
|
||||
5
php/src/Container/State/IContainerState.php
Normal file
5
php/src/Container/State/IContainerState.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
interface IContainerState {}
|
||||
6
php/src/Container/State/ImageDoesNotExistState.php
Normal file
6
php/src/Container/State/ImageDoesNotExistState.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
class ImageDoesNotExistState implements IContainerState
|
||||
{}
|
||||
6
php/src/Container/State/RunningState.php
Normal file
6
php/src/Container/State/RunningState.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
class RunningState implements IContainerState
|
||||
{}
|
||||
6
php/src/Container/State/StartingState.php
Normal file
6
php/src/Container/State/StartingState.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
class StartingState implements IContainerState
|
||||
{}
|
||||
6
php/src/Container/State/StoppedState.php
Normal file
6
php/src/Container/State/StoppedState.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
class StoppedState implements IContainerState
|
||||
{}
|
||||
6
php/src/Container/State/VersionDifferentState.php
Normal file
6
php/src/Container/State/VersionDifferentState.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
class VersionDifferentState implements IContainerState
|
||||
{}
|
||||
6
php/src/Container/State/VersionEqualState.php
Normal file
6
php/src/Container/State/VersionEqualState.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Container\State;
|
||||
|
||||
class VersionEqualState implements IContainerState
|
||||
{}
|
||||
136
php/src/ContainerDefinitionFetcher.php
Normal file
136
php/src/ContainerDefinitionFetcher.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace AIO;
|
||||
|
||||
use AIO\Container\Container;
|
||||
use AIO\Container\ContainerEnvironmentVariables;
|
||||
use AIO\Container\ContainerPorts;
|
||||
use AIO\Container\ContainerInternalPorts;
|
||||
use AIO\Container\ContainerVolume;
|
||||
use AIO\Container\ContainerVolumes;
|
||||
use AIO\Container\State\RunningState;
|
||||
use AIO\Data\ConfigurationManager;
|
||||
use AIO\Data\DataConst;
|
||||
use AIO\Docker\DockerActionManager;
|
||||
|
||||
class ContainerDefinitionFetcher
|
||||
{
|
||||
private ConfigurationManager $configurationManager;
|
||||
private \DI\Container $container;
|
||||
|
||||
public function __construct(
|
||||
ConfigurationManager $configurationManager,
|
||||
\DI\Container $container
|
||||
)
|
||||
{
|
||||
$this->configurationManager = $configurationManager;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function GetContainerById(string $id): ?Container
|
||||
{
|
||||
$containers = $this->FetchDefinition();
|
||||
|
||||
foreach ($containers as $container) {
|
||||
if ($container->GetIdentifier() === $id) {
|
||||
return $container;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function GetDefinition(bool $latest): array
|
||||
{
|
||||
$data = json_decode(file_get_contents(__DIR__ . '/../containers.json'), true);
|
||||
|
||||
$containers = [];
|
||||
foreach ($data['production'] as $entry) {
|
||||
$ports = new ContainerPorts();
|
||||
foreach ($entry['ports'] as $port) {
|
||||
$ports->AddPort($port);
|
||||
}
|
||||
|
||||
$internalPorts = new ContainerInternalPorts();
|
||||
foreach ($entry['internalPorts'] as $internalPort) {
|
||||
$internalPorts->AddInternalPort($internalPort);
|
||||
}
|
||||
|
||||
$volumes = new ContainerVolumes();
|
||||
foreach ($entry['volumes'] as $value) {
|
||||
if($value['name'] === '%BORGBACKUP_HOST_LOCATION%') {
|
||||
$value['name'] = $this->configurationManager->GetBorgBackupHostLocation();
|
||||
if($value['name'] === '') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$volumes->AddVolume(
|
||||
new ContainerVolume(
|
||||
$value['name'],
|
||||
$value['location'],
|
||||
$value['writeable']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$variables = new ContainerEnvironmentVariables();
|
||||
foreach ($entry['environmentVariables'] as $value) {
|
||||
$variables->AddVariable($value);
|
||||
}
|
||||
|
||||
$containers[] = new Container(
|
||||
$entry['identifier'],
|
||||
$entry['displayName'],
|
||||
$entry['containerName'],
|
||||
$entry['restartPolicy'],
|
||||
$entry['maxShutdownTime'],
|
||||
$ports,
|
||||
$internalPorts,
|
||||
$volumes,
|
||||
$variables,
|
||||
$entry['dependsOn'],
|
||||
$entry['secrets'],
|
||||
$this->container->get(DockerActionManager::class)
|
||||
);
|
||||
}
|
||||
|
||||
return $containers;
|
||||
}
|
||||
|
||||
public function FetchDefinition(): array
|
||||
{
|
||||
if (!file_exists(DataConst::GetDataDirectory() . '/containers.json')) {
|
||||
$containers = $this->GetDefinition(true);
|
||||
} else {
|
||||
$containers = $this->GetDefinition(false);
|
||||
}
|
||||
|
||||
$borgBackupMode = $this->configurationManager->GetBorgBackupMode();
|
||||
$fetchLatest = false;
|
||||
|
||||
foreach ($containers as $container) {
|
||||
|
||||
if ($container->GetIdentifier() === 'nextcloud-aio-borgbackup') {
|
||||
if ($container->GetRunningState() === RunningState::class) {
|
||||
if ($borgBackupMode !== 'backup' && $borgBackupMode !== 'restore') {
|
||||
$fetchLatest = true;
|
||||
}
|
||||
} else {
|
||||
$fetchLatest = true;
|
||||
}
|
||||
|
||||
} elseif ($container->GetIdentifier() === 'nextcloud-aio-watchtower' && $container->GetRunningState() === RunningState::class) {
|
||||
return $containers;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fetchLatest === true) {
|
||||
$containers = $this->GetDefinition(true);
|
||||
}
|
||||
|
||||
return $containers;
|
||||
}
|
||||
}
|
||||
38
php/src/Controller/ConfigurationController.php
Normal file
38
php/src/Controller/ConfigurationController.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Controller;
|
||||
|
||||
use AIO\ContainerDefinitionFetcher;
|
||||
use AIO\Data\ConfigurationManager;
|
||||
use AIO\Data\InvalidSettingConfigurationException;
|
||||
use AIO\Docker\DockerActionManager;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class ConfigurationController
|
||||
{
|
||||
private ConfigurationManager $configurationManager;
|
||||
|
||||
public function __construct(
|
||||
ConfigurationManager $configurationManager
|
||||
) {
|
||||
$this->configurationManager = $configurationManager;
|
||||
}
|
||||
|
||||
public function SetConfig(Request $request, Response $response, $args) : Response {
|
||||
try {
|
||||
if (isset($request->getParsedBody()['domain'])) {
|
||||
$this->configurationManager->SetDomain($request->getParsedBody()['domain']);
|
||||
}
|
||||
|
||||
if (isset($request->getParsedBody()['borg_backup_host_location'])) {
|
||||
$this->configurationManager->SetBorgBackupHostLocation($request->getParsedBody()['borg_backup_host_location']);
|
||||
}
|
||||
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
} catch (InvalidSettingConfigurationException $ex) {
|
||||
$response->getBody()->write($ex->getMessage());
|
||||
return $response->withStatus(422);
|
||||
}
|
||||
}
|
||||
}
|
||||
174
php/src/Controller/DockerController.php
Normal file
174
php/src/Controller/DockerController.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Controller;
|
||||
|
||||
use AIO\Container\State\RunningState;
|
||||
use AIO\ContainerDefinitionFetcher;
|
||||
use AIO\Docker\DockerActionManager;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use AIO\Data\ConfigurationManager;
|
||||
|
||||
class DockerController
|
||||
{
|
||||
private DockerActionManager $dockerActionManager;
|
||||
private ContainerDefinitionFetcher $containerDefinitionFetcher;
|
||||
private const TOP_CONTAINER = 'nextcloud-aio-apache';
|
||||
private ConfigurationManager $configurationManager;
|
||||
|
||||
public function __construct(
|
||||
DockerActionManager $dockerActionManager,
|
||||
ContainerDefinitionFetcher $containerDefinitionFetcher,
|
||||
ConfigurationManager $configurationManager
|
||||
) {
|
||||
$this->dockerActionManager = $dockerActionManager;
|
||||
$this->containerDefinitionFetcher = $containerDefinitionFetcher;
|
||||
$this->configurationManager = $configurationManager;
|
||||
}
|
||||
|
||||
private function PerformRecursiveContainerStart(string $id) {
|
||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||
|
||||
foreach($container->GetDependsOn() as $dependency) {
|
||||
$this->PerformRecursiveContainerStart($dependency);
|
||||
}
|
||||
|
||||
$this->dockerActionManager->DeleteContainer($container);
|
||||
$this->dockerActionManager->CreateVolumes($container);
|
||||
$this->dockerActionManager->PullContainer($container);
|
||||
$this->dockerActionManager->CreateContainer($container);
|
||||
$this->dockerActionManager->StartContainer($container);
|
||||
$this->dockerActionManager->ConnectContainerToNetwork($container);
|
||||
}
|
||||
|
||||
public function GetLogs(Request $request, Response $response, $args) : Response
|
||||
{
|
||||
$id = $request->getQueryParams()['id'];
|
||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||
$logs = $this->dockerActionManager->GetLogs($container);
|
||||
$body = $response->getBody();
|
||||
$body->write($logs);
|
||||
|
||||
return $response
|
||||
->withStatus(200)
|
||||
->withHeader('Content-Type', 'text/plain; charset=utf-8')
|
||||
->withHeader('Content-Disposition', 'inline');
|
||||
}
|
||||
|
||||
public function StartBackupContainerBackup(Request $request, Response $response, $args) : Response {
|
||||
$config = $this->configurationManager->GetConfig();
|
||||
$config['backup-mode'] = 'backup';
|
||||
$this->configurationManager->WriteConfig($config);
|
||||
|
||||
$id = self::TOP_CONTAINER;
|
||||
$this->PerformRecursiveContainerStop($id);
|
||||
|
||||
$id = 'nextcloud-aio-borgbackup';
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartBackupContainerCheck(Request $request, Response $response, $args) : Response {
|
||||
$config = $this->configurationManager->GetConfig();
|
||||
$config['backup-mode'] = 'check';
|
||||
$this->configurationManager->WriteConfig($config);
|
||||
|
||||
$id = 'nextcloud-aio-borgbackup';
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartBackupContainerRestore(Request $request, Response $response, $args) : Response {
|
||||
$config = $this->configurationManager->GetConfig();
|
||||
$config['backup-mode'] = 'restore';
|
||||
$this->configurationManager->WriteConfig($config);
|
||||
|
||||
$id = self::TOP_CONTAINER;
|
||||
$this->PerformRecursiveContainerStop($id);
|
||||
|
||||
$id = 'nextcloud-aio-borgbackup';
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartContainer(Request $request, Response $response, $args) : Response
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$host = $uri->getHost();
|
||||
$port = $uri->getPort();
|
||||
|
||||
$config = $this->configurationManager->GetConfig();
|
||||
// set AIO_URL
|
||||
$config['AIO_URL'] = $host . ':' . $port;
|
||||
// set wasStartButtonClicked
|
||||
$config['wasStartButtonClicked'] = 1;
|
||||
// set AIO_TOKEN
|
||||
$config['AIO_TOKEN'] = bin2hex(random_bytes(24));
|
||||
$this->configurationManager->WriteConfig($config);
|
||||
$this->configurationManager->SetIsContainerUpateAvailable(false);
|
||||
|
||||
// Stop domaincheck since apache would not be able to start otherwise
|
||||
$this->StopDomaincheckContainer();
|
||||
|
||||
$id = self::TOP_CONTAINER;
|
||||
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartWatchtowerContainer(Request $request, Response $response, $args) : Response {
|
||||
$id = 'nextcloud-aio-watchtower';
|
||||
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
private function PerformRecursiveContainerStop(string $id)
|
||||
{
|
||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||
foreach($container->GetDependsOn() as $dependency) {
|
||||
$this->PerformRecursiveContainerStop($dependency);
|
||||
}
|
||||
|
||||
$this->dockerActionManager->DisconnectContainerFromNetwork($container);
|
||||
$this->dockerActionManager->StopContainer($container);
|
||||
}
|
||||
|
||||
public function StopContainer(Request $request, Response $response, $args) : Response
|
||||
{
|
||||
$id = self::TOP_CONTAINER;
|
||||
$this->PerformRecursiveContainerStop($id);
|
||||
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartDomaincheckContainer()
|
||||
{
|
||||
# Don't start if domain is already set
|
||||
if ($this->configurationManager->GetDomain() != '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = 'nextcloud-aio-domaincheck';
|
||||
|
||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||
// don't start if the domaincheck is already running
|
||||
if ($container->GetIdentifier() === $id && $container->GetRunningState() instanceof RunningState) {
|
||||
return;
|
||||
// don't start if apache is already running
|
||||
} elseif ($container->GetIdentifier() === self::TOP_CONTAINER && $container->GetRunningState() instanceof RunningState) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
}
|
||||
|
||||
private function StopDomaincheckContainer()
|
||||
{
|
||||
$id = 'nextcloud-aio-domaincheck';
|
||||
$this->PerformRecursiveContainerStop($id);
|
||||
}
|
||||
}
|
||||
48
php/src/Controller/LoginController.php
Normal file
48
php/src/Controller/LoginController.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Controller;
|
||||
|
||||
use AIO\Auth\AuthManager;
|
||||
use AIO\Container\Container;
|
||||
use AIO\ContainerDefinitionFetcher;
|
||||
use AIO\Docker\DockerActionManager;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class LoginController
|
||||
{
|
||||
private AuthManager $authManager;
|
||||
|
||||
public function __construct(AuthManager $authManager) {
|
||||
$this->authManager = $authManager;
|
||||
}
|
||||
|
||||
public function TryLogin(Request $request, Response $response, $args) : Response {
|
||||
$userName = $request->getParsedBody()['username'];
|
||||
$password = $request->getParsedBody()['password'];
|
||||
if($this->authManager->CheckCredentials($userName, $password)) {
|
||||
$this->authManager->SetAuthState(true);
|
||||
return $response->withHeader('Location', '/')->withStatus(302);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', '/')->withStatus(302);
|
||||
}
|
||||
|
||||
public function GetTryLogin(Request $request, Response $response, $args) : Response {
|
||||
$token = $request->getQueryParams()['token'];
|
||||
if($this->authManager->CheckToken($token)) {
|
||||
$this->authManager->SetAuthState(true);
|
||||
return $response->withHeader('Location', '/')->withStatus(302);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', '/')->withStatus(302);
|
||||
}
|
||||
|
||||
public function Logout(Request $request, Response $response, $args) : Response
|
||||
{
|
||||
$this->authManager->SetAuthState(false);
|
||||
return $response
|
||||
->withHeader('Location', '/')
|
||||
->withStatus(302);
|
||||
}
|
||||
}
|
||||
230
php/src/Data/ConfigurationManager.php
Normal file
230
php/src/Data/ConfigurationManager.php
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Data;
|
||||
|
||||
use AIO\Auth\PasswordGenerator;
|
||||
use AIO\Controller\DockerController;
|
||||
|
||||
class ConfigurationManager
|
||||
{
|
||||
public function GetConfig() : array
|
||||
{
|
||||
if(file_exists(DataConst::GetConfigFile()))
|
||||
{
|
||||
$configContent = file_get_contents(DataConst::GetConfigFile());
|
||||
return json_decode($configContent, true);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function GetUserName() : string {
|
||||
return $this->GetConfig()['username'];
|
||||
}
|
||||
|
||||
public function GetPassword() : string {
|
||||
return $this->GetConfig()['password'];
|
||||
}
|
||||
|
||||
public function GetToken() : string {
|
||||
return $this->GetConfig()['AIO_TOKEN'];
|
||||
}
|
||||
|
||||
public function GetIsContainerUpateAvailable() : bool {
|
||||
return isset($this->GetConfig()['isContainerUpateAvailable']) ? $this->GetConfig()['isContainerUpateAvailable'] : false;
|
||||
}
|
||||
|
||||
public function SetIsContainerUpateAvailable(bool $value) : void {
|
||||
$config = $this->GetConfig();
|
||||
$config['isContainerUpateAvailable'] = $value;
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
public function SetPassword(string $password) : void {
|
||||
$config = $this->GetConfig();
|
||||
$config['username'] = 'admin';
|
||||
$config['password'] = $password;
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
public function GetSecret(string $secretId) : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['secrets'][$secretId])) {
|
||||
$config['secrets'][$secretId] = bin2hex(random_bytes(24));
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
if ($secretId === 'BORGBACKUP_PASSWORD' && !file_exists(DataConst::GetBackupSecretFile())) {
|
||||
$this->DoubleSafeBackupSecret($config['secrets'][$secretId]);
|
||||
}
|
||||
|
||||
return $config['secrets'][$secretId];
|
||||
}
|
||||
|
||||
private function DoubleSafeBackupSecret(string $borgBackupPassword) {
|
||||
file_put_contents(DataConst::GetBackupSecretFile(), $borgBackupPassword);
|
||||
}
|
||||
|
||||
public function hasBackupRunOnce() : bool {
|
||||
if (!file_exists(DataConst::GetBackupKeyFile())) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetLastBackupTime() : string {
|
||||
if (!file_exists(DataConst::GetBackupArchivesList())) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = file_get_contents(DataConst::GetBackupArchivesList());
|
||||
if ($content === "") {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lastBackupLines = explode("\n", $content);
|
||||
$lastBackupLine = $lastBackupLines[sizeof($lastBackupLines) - 2];
|
||||
if ($lastBackupLine === "") {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lastBackupTimes = explode(",", $lastBackupLine);
|
||||
$lastBackupTime = $lastBackupTimes[1];
|
||||
if ($lastBackupTime === "") {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $lastBackupTime;
|
||||
}
|
||||
|
||||
public function wasStartButtonClicked() : bool {
|
||||
if (isset($this->GetConfig()['wasStartButtonClicked'])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidSettingConfigurationException
|
||||
*/
|
||||
public function SetDomain(string $domain) : void {
|
||||
// Validate URL
|
||||
if (!filter_var('http://' . $domain, FILTER_VALIDATE_URL)) {
|
||||
throw new InvalidSettingConfigurationException("Domain is not in a valid format!");
|
||||
}
|
||||
|
||||
$dnsRecordIP = gethostbyname($domain);
|
||||
|
||||
// Validate IP
|
||||
if(!filter_var($dnsRecordIP, FILTER_VALIDATE_IP)) {
|
||||
throw new InvalidSettingConfigurationException("DNS config is not set or domain is not in a valid format!");
|
||||
}
|
||||
|
||||
$connection = @fsockopen($domain, 443, $errno, $errstr, 0.1);
|
||||
if ($connection) {
|
||||
fclose($connection);
|
||||
} else {
|
||||
throw new InvalidSettingConfigurationException("The server is not reachable on Port 443.");
|
||||
}
|
||||
|
||||
// Get Instance ID
|
||||
$instanceID = $this->GetSecret('INSTANCE_ID');
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL,'http://' . $domain . ':443');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($ch);
|
||||
# Get rid of trailing \n
|
||||
$response = str_replace("\n", "", $response);
|
||||
|
||||
if($response !== $instanceID) {
|
||||
throw new InvalidSettingConfigurationException("Domain does not point to this server.");
|
||||
}
|
||||
|
||||
// Write domain
|
||||
$config = $this->GetConfig();
|
||||
$config['domain'] = $domain;
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
public function GetDomain() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['domain'])) {
|
||||
$config['domain'] = '';
|
||||
}
|
||||
|
||||
return $config['domain'];
|
||||
}
|
||||
|
||||
public function GetBackupMode() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['backup-mode'])) {
|
||||
$config['backup-mode'] = '';
|
||||
}
|
||||
|
||||
return $config['backup-mode'];
|
||||
}
|
||||
|
||||
public function GetAIOURL() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['AIO_URL'])) {
|
||||
$config['AIO_URL'] = '';
|
||||
}
|
||||
|
||||
return $config['AIO_URL'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidSettingConfigurationException
|
||||
*/
|
||||
public function SetBorgBackupHostLocation(string $location) : void {
|
||||
$allowedPrefixes = [
|
||||
'/mnt/',
|
||||
'/media/',
|
||||
];
|
||||
|
||||
$isValidPath = false;
|
||||
foreach($allowedPrefixes as $allowedPrefix) {
|
||||
if(str_starts_with($location, $allowedPrefix)) {
|
||||
$isValidPath = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$isValidPath) {
|
||||
throw new InvalidSettingConfigurationException("Path must start with /mnt/ or /media/.");
|
||||
}
|
||||
|
||||
|
||||
$config = $this->GetConfig();
|
||||
$config['borg_backup_host_location'] = $location;
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
public function WriteConfig(array $config) : void {
|
||||
if(!is_dir(DataConst::GetDataDirectory())) {
|
||||
mkdir(DataConst::GetDataDirectory());
|
||||
}
|
||||
file_put_contents(DataConst::GetConfigFile(), json_encode($config));
|
||||
}
|
||||
|
||||
public function GetBorgBackupHostLocation() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['borg_backup_host_location'])) {
|
||||
$config['borg_backup_host_location'] = '';
|
||||
}
|
||||
|
||||
return $config['borg_backup_host_location'];
|
||||
}
|
||||
|
||||
public function GetBorgBackupMode() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['backup-mode'])) {
|
||||
$config['backup-mode'] = '';
|
||||
}
|
||||
|
||||
return $config['backup-mode'];
|
||||
}
|
||||
}
|
||||
37
php/src/Data/DataConst.php
Normal file
37
php/src/Data/DataConst.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Data;
|
||||
|
||||
class DataConst {
|
||||
public static function GetDataDirectory() : string {
|
||||
if(is_dir('/mnt/docker-aio-config/data/')) {
|
||||
return '/mnt/docker-aio-config/data/';
|
||||
}
|
||||
|
||||
return realpath(__DIR__ . '/../../data/');
|
||||
}
|
||||
|
||||
public static function GetSessionDirectory() : string {
|
||||
if(is_dir('/mnt/docker-aio-config/session/')) {
|
||||
return '/mnt/docker-aio-config/session/';
|
||||
}
|
||||
|
||||
return realpath(__DIR__ . '/../../session/');
|
||||
}
|
||||
|
||||
public static function GetConfigFile() : string {
|
||||
return self::GetDataDirectory() . '/configuration.json';
|
||||
}
|
||||
|
||||
public static function GetBackupSecretFile() : string {
|
||||
return self::GetDataDirectory() . '/backupsecret';
|
||||
}
|
||||
|
||||
public static function GetBackupKeyFile() : string {
|
||||
return self::GetDataDirectory() . '/borg.config';
|
||||
}
|
||||
|
||||
public static function GetBackupArchivesList() : string {
|
||||
return self::GetDataDirectory() . '/backup_archives.list';
|
||||
}
|
||||
}
|
||||
6
php/src/Data/InvalidSettingConfigurationException.php
Normal file
6
php/src/Data/InvalidSettingConfigurationException.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace AIO\Data;
|
||||
|
||||
class InvalidSettingConfigurationException extends \Exception {}
|
||||
32
php/src/Data/Setup.php
Normal file
32
php/src/Data/Setup.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Data;
|
||||
|
||||
use AIO\Auth\PasswordGenerator;
|
||||
|
||||
class Setup
|
||||
{
|
||||
private PasswordGenerator $passwordGenerator;
|
||||
private ConfigurationManager $configurationManager;
|
||||
|
||||
public function __construct(
|
||||
PasswordGenerator $passwordGenerator,
|
||||
ConfigurationManager $configurationManager) {
|
||||
$this->passwordGenerator = $passwordGenerator;
|
||||
$this->configurationManager = $configurationManager;
|
||||
}
|
||||
|
||||
public function Setup() : string {
|
||||
if(!$this->CanBeInstalled()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$password = $this->passwordGenerator->GeneratePassword(6);
|
||||
$this->configurationManager->SetPassword($password);
|
||||
return $password;
|
||||
}
|
||||
|
||||
public function CanBeInstalled() : bool {
|
||||
return !file_exists(DataConst::GetConfigFile());
|
||||
}
|
||||
}
|
||||
48
php/src/DependencyInjection.php
Normal file
48
php/src/DependencyInjection.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace AIO;
|
||||
|
||||
use AIO\Docker\DockerHubManager;
|
||||
use DI\Container;
|
||||
|
||||
class DependencyInjection
|
||||
{
|
||||
public static function GetContainer() : Container {
|
||||
$container = new Container();
|
||||
|
||||
$container->set(
|
||||
DockerHubManager::class,
|
||||
new DockerHubManager()
|
||||
);
|
||||
|
||||
$container->set(
|
||||
\AIO\Data\ConfigurationManager::class,
|
||||
new \AIO\Data\ConfigurationManager()
|
||||
);
|
||||
$container->set(
|
||||
\AIO\Docker\DockerActionManager::class,
|
||||
new \AIO\Docker\DockerActionManager(
|
||||
$container->get(\AIO\Data\ConfigurationManager::class),
|
||||
$container->get(\AIO\ContainerDefinitionFetcher::class),
|
||||
$container->get(DockerHubManager::class)
|
||||
)
|
||||
);
|
||||
$container->set(
|
||||
\AIO\Auth\PasswordGenerator::class,
|
||||
new \AIO\Auth\PasswordGenerator()
|
||||
);
|
||||
$container->set(
|
||||
\AIO\Auth\AuthManager::class,
|
||||
new \AIO\Auth\AuthManager($container->get(\AIO\Data\ConfigurationManager::class))
|
||||
);
|
||||
$container->set(
|
||||
\AIO\Data\Setup::class,
|
||||
new \AIO\Data\Setup(
|
||||
$container->get(\AIO\Auth\PasswordGenerator::class),
|
||||
$container->get(\AIO\Data\ConfigurationManager::class)
|
||||
)
|
||||
);
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
||||
468
php/src/Docker/DockerActionManager.php
Normal file
468
php/src/Docker/DockerActionManager.php
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Docker;
|
||||
|
||||
use AIO\Container\Container;
|
||||
use AIO\Container\State\IContainerState;
|
||||
use AIO\Container\State\ImageDoesNotExistState;
|
||||
use AIO\Container\State\StartingState;
|
||||
use AIO\Container\State\RunningState;
|
||||
use AIO\Container\State\VersionDifferentState;
|
||||
use AIO\Container\State\StoppedState;
|
||||
use AIO\Container\State\VersionEqualState;
|
||||
use AIO\Data\ConfigurationManager;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
use AIO\ContainerDefinitionFetcher;
|
||||
use http\Env\Response;
|
||||
|
||||
class DockerActionManager
|
||||
{
|
||||
private const API_VERSION = 'v1.41';
|
||||
private \GuzzleHttp\Client $guzzleClient;
|
||||
private ConfigurationManager $configurationManager;
|
||||
private ContainerDefinitionFetcher $containerDefinitionFetcher;
|
||||
private DockerHubManager $dockerHubManager;
|
||||
|
||||
public function __construct(
|
||||
ConfigurationManager $configurationManager,
|
||||
ContainerDefinitionFetcher $containerDefinitionFetcher,
|
||||
DockerHubManager $dockerHubManager
|
||||
) {
|
||||
$this->configurationManager = $configurationManager;
|
||||
$this->containerDefinitionFetcher = $containerDefinitionFetcher;
|
||||
$this->dockerHubManager = $dockerHubManager;
|
||||
$this->guzzleClient = new \GuzzleHttp\Client(
|
||||
[
|
||||
'curl' => [
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock',
|
||||
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function BuildApiUrl(string $url) : string {
|
||||
return sprintf('http://localhost/%s/%s', self::API_VERSION, $url);
|
||||
}
|
||||
|
||||
private function BuildImageName(Container $container) : string {
|
||||
$channel = $this->GetCurrentChannel();
|
||||
if ($channel === 'develop') {
|
||||
return $container->GetContainerName() . ':develop';
|
||||
} else {
|
||||
return $container->GetContainerName() . ':latest';
|
||||
}
|
||||
}
|
||||
|
||||
public function GetContainerRunningState(Container $container) : IContainerState
|
||||
{
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier())));
|
||||
try {
|
||||
$response = $this->guzzleClient->get($url);
|
||||
} catch (ClientException $ex) {
|
||||
if($ex->getCode() === 404) {
|
||||
return new ImageDoesNotExistState();
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$responseBody = json_decode((string)$response->getBody(), true);
|
||||
|
||||
if ($responseBody['State']['Running'] === true) {
|
||||
return new RunningState();
|
||||
} else {
|
||||
return new StoppedState();
|
||||
}
|
||||
}
|
||||
|
||||
public function GetContainerUpdateState(Container $container) : IContainerState
|
||||
{
|
||||
$channel = $this->GetCurrentChannel();
|
||||
if ($channel === 'develop') {
|
||||
$tag = 'develop';
|
||||
} else {
|
||||
$tag = 'latest';
|
||||
}
|
||||
|
||||
$runningDigest = $this->GetRepoDigestOfContainer($container->GetIdentifier());
|
||||
$remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($container->GetContainerName(), $tag);
|
||||
|
||||
if ($runningDigest === $remoteDigest) {
|
||||
return new VersionEqualState();
|
||||
} else {
|
||||
return new VersionDifferentState();
|
||||
}
|
||||
}
|
||||
|
||||
public function GetContainerStartingState(Container $container) : IContainerState
|
||||
{
|
||||
$runningState = $this->GetContainerRunningState($container);
|
||||
if ($runningState instanceof StoppedState) {
|
||||
return new StoppedState();
|
||||
} elseif ($runningState instanceof ImageDoesNotExistState) {
|
||||
return new ImageDoesNotExistState();
|
||||
}
|
||||
|
||||
$containerName = $container->GetIdentifier();
|
||||
if ($container->GetInternalPorts() !== null) {
|
||||
foreach($container->GetInternalPorts()->GetInternalPorts() as $internalPort) {
|
||||
$connection = @fsockopen($containerName, $internalPort, $errno, $errstr, 0.1);
|
||||
if ($connection) {
|
||||
fclose($connection);
|
||||
return new RunningState();
|
||||
} else {
|
||||
return new StartingState();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new RunningState();
|
||||
}
|
||||
}
|
||||
|
||||
public function DeleteContainer(Container $container) {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s', urlencode($container->GetIdentifier())));
|
||||
try {
|
||||
$this->guzzleClient->delete($url);
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
public function GetLogs(Container $container) : string
|
||||
{
|
||||
$url = $this->BuildApiUrl(
|
||||
sprintf(
|
||||
'containers/%s/logs?stdout=true&stderr=true',
|
||||
urlencode($container->GetIdentifier())
|
||||
));
|
||||
$responseBody = (string)$this->guzzleClient->get($url)->getBody();
|
||||
|
||||
$response = "";
|
||||
$separator = "\r\n";
|
||||
$line = strtok($responseBody, $separator);
|
||||
$response = substr($line, 8) . "\n";
|
||||
|
||||
while ($line !== false) {
|
||||
$line = strtok($separator);
|
||||
$response .= substr($line, 8) . "\n";
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function StartContainer(Container $container) {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/start', urlencode($container->GetIdentifier())));
|
||||
$this->guzzleClient->post($url);
|
||||
}
|
||||
|
||||
public function CreateVolumes(Container $container)
|
||||
{
|
||||
$url = $this->BuildApiUrl('volumes/create');
|
||||
foreach($container->GetVolumes()->GetVolumes() as $volume) {
|
||||
$forbiddenChars = [
|
||||
'/',
|
||||
];
|
||||
|
||||
$firstChar = substr($volume->name, 0, 1);
|
||||
if(!in_array($firstChar, $forbiddenChars)) {
|
||||
$this->guzzleClient->request(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => [
|
||||
'name' => $volume->name,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function CreateContainer(Container $container) {
|
||||
$volumes = [];
|
||||
foreach($container->GetVolumes()->GetVolumes() as $volume) {
|
||||
$volumeEntry = $volume->name . ':' . $volume->mountPoint;
|
||||
if($volume->isWritable) {
|
||||
$volumeEntry = $volumeEntry . ':' . 'rw';
|
||||
} else {
|
||||
$volumeEntry = $volumeEntry . ':' . 'ro';
|
||||
}
|
||||
|
||||
$volumes[] = $volumeEntry;
|
||||
}
|
||||
|
||||
$exposedPorts = [];
|
||||
foreach($container->GetPorts()->GetPorts() as $port) {
|
||||
$exposedPorts[$port] = null;
|
||||
}
|
||||
|
||||
$requestBody = [
|
||||
'Image' => $this->BuildImageName($container),
|
||||
];
|
||||
|
||||
if(count($volumes) > 0) {
|
||||
$requestBody['HostConfig']['Binds'] = $volumes;
|
||||
}
|
||||
|
||||
$envs = $container->GetEnvironmentVariables()->GetVariables();
|
||||
foreach($envs as $key => $env) {
|
||||
$patterns = ['/%(.*)%/'];
|
||||
|
||||
|
||||
if(preg_match($patterns[0], $env, $out) === 1) {
|
||||
$replacements = array();
|
||||
|
||||
if($out[1] === 'NC_DOMAIN') {
|
||||
$replacements[1] = $this->configurationManager->GetDomain();
|
||||
} elseif ($out[1] === 'AIO_TOKEN') {
|
||||
$replacements[1] = $this->configurationManager->GetToken();
|
||||
} elseif ($out[1] === 'BORGBACKUP_MODE') {
|
||||
$replacements[1] = $this->configurationManager->GetBackupMode();
|
||||
} elseif ($out[1] === 'AIO_URL') {
|
||||
$replacements[1] = $this->configurationManager->GetAIOURL();
|
||||
} else {
|
||||
$replacements[1] = $this->configurationManager->GetSecret($out[1]);
|
||||
}
|
||||
|
||||
$envs[$key] = preg_replace($patterns, $replacements, $env);
|
||||
}
|
||||
}
|
||||
|
||||
if(count($envs) > 0) {
|
||||
$requestBody['Env'] = $envs;
|
||||
}
|
||||
|
||||
$requestBody['HostConfig']['RestartPolicy']['Name'] = $container->GetRestartPolicy();
|
||||
|
||||
if(count($exposedPorts) > 0) {
|
||||
$requestBody['ExposedPorts'] = $exposedPorts;
|
||||
foreach($container->GetPorts()->GetPorts() as $port) {
|
||||
$portNumber = explode("/", $port);
|
||||
$requestBody['HostConfig']['PortBindings'][$port] = [
|
||||
[
|
||||
'HostPort' => $portNumber[0],
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Special things for the backup container which should not be exposed in the containers.json
|
||||
if ($container->GetIdentifier() === 'nextcloud-aio-borgbackup') {
|
||||
$requestBody['HostConfig']['CapAdd'] = ["SYS_ADMIN"];
|
||||
$requestBody['HostConfig']['Devices'] = [["PathOnHost" => "/dev/fuse", "PathInContainer" => "/dev/fuse", "CgroupPermissions" => "rwm"]];
|
||||
$requestBody['HostConfig']['SecurityOpt'] = ["apparmor:unconfined"];
|
||||
}
|
||||
|
||||
$url = $this->BuildApiUrl('containers/create?name=' . $container->GetIdentifier());
|
||||
$this->guzzleClient->request(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => $requestBody
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function PullContainer(Container $container)
|
||||
{
|
||||
$url = $this->BuildApiUrl(sprintf('images/create?fromImage=%s', urlencode($this->BuildImageName($container))));
|
||||
try {
|
||||
$this->guzzleClient->post($url);
|
||||
} catch (ClientException $ex) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
private function isContainerUpdateAvailable(string $id) : string
|
||||
{
|
||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||
|
||||
$updateAvailable = "";
|
||||
if ($container->GetUpdateState() instanceof VersionDifferentState) {
|
||||
$updateAvailable = '1';
|
||||
}
|
||||
foreach ($container->GetDependsOn() as $dependency) {
|
||||
$updateAvailable .= $this->isContainerUpdateAvailable($dependency);
|
||||
}
|
||||
return $updateAvailable;
|
||||
}
|
||||
|
||||
public function isAnyUpdateAvailable() {
|
||||
$id = 'nextcloud-aio-apache';
|
||||
|
||||
if ($this->configurationManager->GetIsContainerUpateAvailable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isContainerUpdateAvailable($id) !== "") {
|
||||
$this->configurationManager->SetIsContainerUpateAvailable(true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function GetRepoDigestOfContainer(string $containerName) : ?string {
|
||||
try {
|
||||
$containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
||||
$containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true);
|
||||
$imageName = $containerOutput['Image'];
|
||||
|
||||
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
|
||||
$imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true);
|
||||
|
||||
if(isset($imageOutput['RepoDigests']) && count($imageOutput['RepoDigests']) === 1) {
|
||||
$fullDigest = $imageOutput['RepoDigests'][0];
|
||||
|
||||
return substr($fullDigest, strpos($fullDigest, "@") + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetCurrentChannel() : string {
|
||||
$cacheKey = 'aio-ChannelName';
|
||||
$channelName = apcu_fetch($cacheKey);
|
||||
if($channelName !== false && is_string($channelName)) {
|
||||
return $channelName;
|
||||
}
|
||||
|
||||
$containerName = 'nextcloud-aio-mastercontainer';
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
||||
try {
|
||||
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true);
|
||||
$containerChecksum = $output['Image'];
|
||||
$tagArray = explode(':', $output['Config']['Image']);
|
||||
$tag = $tagArray[1];
|
||||
apcu_add($cacheKey, $tag);
|
||||
return $tag;
|
||||
} catch (\Exception $ex) {
|
||||
}
|
||||
|
||||
return 'latest';
|
||||
}
|
||||
|
||||
public function IsMastercontainerUpdateAvailable() : bool
|
||||
{
|
||||
$imageName = 'nextcloud/all-in-one';
|
||||
$containerName = 'nextcloud-aio-mastercontainer';
|
||||
|
||||
$channel = $this->GetCurrentChannel();
|
||||
if ($channel === 'develop') {
|
||||
$tag = 'develop';
|
||||
} else {
|
||||
$tag = 'latest';
|
||||
}
|
||||
|
||||
$runningDigest = $this->GetRepoDigestOfContainer($containerName);
|
||||
$remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag);
|
||||
|
||||
if ($remoteDigest === $runningDigest) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function DisconnectContainerFromNetwork(Container $container)
|
||||
{
|
||||
|
||||
$url = $this->BuildApiUrl(
|
||||
sprintf('networks/%s/disconnect', 'nextcloud-aio')
|
||||
);
|
||||
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => [
|
||||
'container' => $container->GetIdentifier(),
|
||||
],
|
||||
]
|
||||
);
|
||||
} catch (ServerException $e) {}
|
||||
}
|
||||
|
||||
private function ConnectContainerIdToNetwork(string $id)
|
||||
{
|
||||
$url = $this->BuildApiUrl('networks/create');
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => [
|
||||
'name' => 'nextcloud-aio',
|
||||
'checkDuplicate' => true,
|
||||
'internal' => true,
|
||||
]
|
||||
]
|
||||
);
|
||||
} catch (ClientException $e) {
|
||||
if($e->getCode() !== 409) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$url = $this->BuildApiUrl(
|
||||
sprintf('networks/%s/connect', 'nextcloud-aio')
|
||||
);
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => [
|
||||
'container' => $id,
|
||||
]
|
||||
]
|
||||
);
|
||||
} catch (ClientException $e) {
|
||||
if($e->getCode() !== 403) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function ConnectMasterContainerToNetwork()
|
||||
{
|
||||
$this->ConnectContainerIdToNetwork('nextcloud-aio-mastercontainer');
|
||||
}
|
||||
|
||||
public function ConnectContainerToNetwork(Container $container)
|
||||
{
|
||||
$this->ConnectContainerIdToNetwork($container->GetIdentifier());
|
||||
}
|
||||
|
||||
public function StopContainer(Container $container) {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->GetIdentifier()), $container->GetMaxShutdownTime()));
|
||||
$this->guzzleClient->post($url);
|
||||
}
|
||||
|
||||
public function GetBackupcontainerExitCode() : int
|
||||
{
|
||||
$containerName = 'nextcloud-aio-borgbackup';
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
||||
try {
|
||||
$response = $this->guzzleClient->get($url);
|
||||
} catch (ClientException $ex) {
|
||||
if ($ex->getCode() === 404) {
|
||||
return -1;
|
||||
}
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$responseBody = json_decode((string)$response->getBody(), true);
|
||||
|
||||
$exitCode = $responseBody['State']['ExitCode'];
|
||||
if (is_int($exitCode)) {
|
||||
return $exitCode;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
php/src/Docker/DockerHubManager.php
Normal file
58
php/src/Docker/DockerHubManager.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Docker;
|
||||
|
||||
use AIO\ContainerDefinitionFetcher;
|
||||
use AIO\Data\ConfigurationManager;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class DockerHubManager
|
||||
{
|
||||
private Client $guzzleClient;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->guzzleClient = new Client();
|
||||
}
|
||||
|
||||
public function GetLatestDigestOfTag(string $name, string $tag) : ?string {
|
||||
$cacheKey = 'dockerhub-manifest-' . $name . $tag;
|
||||
|
||||
$cachedVersion = apcu_fetch($cacheKey);
|
||||
if($cachedVersion !== false && is_string($cachedVersion)) {
|
||||
return $cachedVersion;
|
||||
}
|
||||
|
||||
try {
|
||||
$authTokenRequest = $this->guzzleClient->request(
|
||||
'GET',
|
||||
'https://auth.docker.io/token?service=registry.docker.io&scope=repository:' . $name . ':pull'
|
||||
);
|
||||
$body = $authTokenRequest->getBody()->getContents();
|
||||
$decodedBody = json_decode($body, true);
|
||||
if(isset($decodedBody['token'])) {
|
||||
$authToken = $decodedBody['token'];
|
||||
$manifestRequest = $this->guzzleClient->request(
|
||||
'GET',
|
||||
'https://registry-1.docker.io/v2/'.$name.'/manifests/' . $tag,
|
||||
[
|
||||
'headers' => [
|
||||
'Accept' => 'application/vnd.docker.distribution.manifest.v2+json',
|
||||
'Authorization' => 'Bearer ' . $authToken,
|
||||
],
|
||||
]
|
||||
);
|
||||
$responseHeaders = $manifestRequest->getHeader('docker-content-digest');
|
||||
if(count($responseHeaders) === 1) {
|
||||
$latestVersion = $responseHeaders[0];
|
||||
apcu_add($cacheKey, $latestVersion, 600);
|
||||
return $latestVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
php/src/Middleware/AuthMiddleware.php
Normal file
41
php/src/Middleware/AuthMiddleware.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Middleware;
|
||||
|
||||
use AIO\Auth\AuthManager;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class AuthMiddleware
|
||||
{
|
||||
private AuthManager $authManager;
|
||||
|
||||
public function __construct(AuthManager $authManager) {
|
||||
$this->authManager = $authManager;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$publicRoutes = [
|
||||
'/api/auth/login',
|
||||
'/api/auth/getlogin',
|
||||
'/login',
|
||||
'/setup',
|
||||
'/',
|
||||
];
|
||||
|
||||
if(!in_array($request->getUri()->getPath(), $publicRoutes)) {
|
||||
if(!$this->authManager->IsAuthenticated()) {
|
||||
$response = new Response();
|
||||
return $response
|
||||
->withHeader('Location', '/')
|
||||
->withStatus(302);
|
||||
}
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
25
php/src/Twig/ClassExtension.php
Normal file
25
php/src/Twig/ClassExtension.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Twig;
|
||||
|
||||
use Slim\Views\TwigExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class ClassExtension extends TwigExtension
|
||||
{
|
||||
public function getFunctions() : array
|
||||
{
|
||||
return array(
|
||||
new TwigFunction('class', array($this, 'getClassName')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getClassName($object) : ?string
|
||||
{
|
||||
if (!is_object($object)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return get_class($object);
|
||||
}
|
||||
}
|
||||
38
php/src/Twig/CsrfExtension.php
Normal file
38
php/src/Twig/CsrfExtension.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace AIO\Twig;
|
||||
|
||||
use Slim\Csrf\Guard;
|
||||
|
||||
class CsrfExtension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
|
||||
{
|
||||
/**
|
||||
* @var Guard
|
||||
*/
|
||||
protected Guard $csrf;
|
||||
|
||||
public function __construct(Guard $csrf)
|
||||
{
|
||||
$this->csrf = $csrf;
|
||||
}
|
||||
|
||||
public function getGlobals() : array
|
||||
{
|
||||
// CSRF token name and value
|
||||
$csrfNameKey = $this->csrf->getTokenNameKey();
|
||||
$csrfValueKey = $this->csrf->getTokenValueKey();
|
||||
$csrfName = $this->csrf->getTokenName();
|
||||
$csrfValue = $this->csrf->getTokenValue();
|
||||
|
||||
return [
|
||||
'csrf' => [
|
||||
'keys' => [
|
||||
'name' => $csrfNameKey,
|
||||
'value' => $csrfValueKey
|
||||
],
|
||||
'name' => $csrfName,
|
||||
'value' => $csrfValue
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue