mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-02-04 04:56:52 +00:00
Add support for ghcr.io (#6134)
Signed-off-by: Jean-Yves <7360784+docjyJ@users.noreply.github.com> Signed-off-by: Simon L. <szaimen@e.mail.de> Co-authored-by: Simon L. <szaimen@e.mail.de>
This commit is contained in:
parent
a6246f9544
commit
e97d4b0a3e
6 changed files with 183 additions and 103 deletions
12
community-containers/helloworld/helloworld.json
Normal file
12
community-containers/helloworld/helloworld.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"aio_services_v1": [
|
||||||
|
{
|
||||||
|
"container_name": "nextcloud-aio-helloworld",
|
||||||
|
"display_name": "Hello world",
|
||||||
|
"documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/helloworld",
|
||||||
|
"image": "ghcr.io/docjyj/aio-helloworld",
|
||||||
|
"image_tag": "%AIO_CHANNEL%",
|
||||||
|
"restart": "unless-stopped"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
8
community-containers/helloworld/readme.md
Normal file
8
community-containers/helloworld/readme.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
## Hello World
|
||||||
|
This container is a template for creating a community container.
|
||||||
|
|
||||||
|
### Repository
|
||||||
|
https://github.com/docjyj/aio-helloworld
|
||||||
|
|
||||||
|
### Maintainer
|
||||||
|
https://github.com/docjyj
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"image": {
|
"image": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"pattern": "^[a-z0-9/-]+$"
|
"pattern": "^(ghcr.io/)?[a-z0-9/-]+$"
|
||||||
},
|
},
|
||||||
"expose": {
|
"expose": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ namespace AIO;
|
||||||
|
|
||||||
use AIO\Docker\DockerHubManager;
|
use AIO\Docker\DockerHubManager;
|
||||||
use DI\Container;
|
use DI\Container;
|
||||||
|
use AIO\Docker\GitHubContainerRegistryManager;
|
||||||
|
|
||||||
class DependencyInjection
|
class DependencyInjection
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +16,11 @@ class DependencyInjection
|
||||||
new DockerHubManager()
|
new DockerHubManager()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$container->set(
|
||||||
|
GitHubContainerRegistryManager::class,
|
||||||
|
new GitHubContainerRegistryManager()
|
||||||
|
);
|
||||||
|
|
||||||
$container->set(
|
$container->set(
|
||||||
\AIO\Data\ConfigurationManager::class,
|
\AIO\Data\ConfigurationManager::class,
|
||||||
new \AIO\Data\ConfigurationManager()
|
new \AIO\Data\ConfigurationManager()
|
||||||
|
|
@ -24,7 +30,8 @@ class DependencyInjection
|
||||||
new \AIO\Docker\DockerActionManager(
|
new \AIO\Docker\DockerActionManager(
|
||||||
$container->get(\AIO\Data\ConfigurationManager::class),
|
$container->get(\AIO\Data\ConfigurationManager::class),
|
||||||
$container->get(\AIO\ContainerDefinitionFetcher::class),
|
$container->get(\AIO\ContainerDefinitionFetcher::class),
|
||||||
$container->get(DockerHubManager::class)
|
$container->get(DockerHubManager::class),
|
||||||
|
$container->get(GitHubContainerRegistryManager::class)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$container->set(
|
$container->set(
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
namespace AIO\Docker;
|
namespace AIO\Docker;
|
||||||
|
|
||||||
use AIO\Container\Container;
|
use AIO\Container\Container;
|
||||||
use AIO\Container\VersionState;
|
|
||||||
use AIO\Container\ContainerState;
|
use AIO\Container\ContainerState;
|
||||||
|
use AIO\Container\VersionState;
|
||||||
|
use AIO\ContainerDefinitionFetcher;
|
||||||
use AIO\Data\ConfigurationManager;
|
use AIO\Data\ConfigurationManager;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
use AIO\ContainerDefinitionFetcher;
|
|
||||||
use http\Env\Response;
|
use http\Env\Response;
|
||||||
|
|
||||||
readonly class DockerActionManager {
|
readonly class DockerActionManager {
|
||||||
|
|
@ -16,18 +16,19 @@ readonly class DockerActionManager {
|
||||||
private Client $guzzleClient;
|
private Client $guzzleClient;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private ConfigurationManager $configurationManager,
|
private ConfigurationManager $configurationManager,
|
||||||
private ContainerDefinitionFetcher $containerDefinitionFetcher,
|
private ContainerDefinitionFetcher $containerDefinitionFetcher,
|
||||||
private DockerHubManager $dockerHubManager
|
private DockerHubManager $dockerHubManager,
|
||||||
|
private GitHubContainerRegistryManager $gitHubContainerRegistryManager
|
||||||
) {
|
) {
|
||||||
$this->guzzleClient = new Client(['curl' => [CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock']]);
|
$this->guzzleClient = new Client(['curl' => [CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function BuildApiUrl(string $url) : string {
|
private function BuildApiUrl(string $url): string {
|
||||||
return sprintf('http://127.0.0.1/%s/%s', self::API_VERSION, $url);
|
return sprintf('http://127.0.0.1/%s/%s', self::API_VERSION, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function BuildImageName(Container $container) : string {
|
private function BuildImageName(Container $container): string {
|
||||||
$tag = $container->GetImageTag();
|
$tag = $container->GetImageTag();
|
||||||
if ($tag === '%AIO_CHANNEL%') {
|
if ($tag === '%AIO_CHANNEL%') {
|
||||||
$tag = $this->GetCurrentChannel();
|
$tag = $this->GetCurrentChannel();
|
||||||
|
|
@ -35,8 +36,7 @@ readonly class DockerActionManager {
|
||||||
return $container->GetContainerName() . ':' . $tag;
|
return $container->GetContainerName() . ':' . $tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetContainerRunningState(Container $container) : ContainerState
|
public function GetContainerRunningState(Container $container): ContainerState {
|
||||||
{
|
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier())));
|
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier())));
|
||||||
try {
|
try {
|
||||||
$response = $this->guzzleClient->get($url);
|
$response = $this->guzzleClient->get($url);
|
||||||
|
|
@ -56,8 +56,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetContainerRestartingState(Container $container) : ContainerState
|
public function GetContainerRestartingState(Container $container): ContainerState {
|
||||||
{
|
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier())));
|
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->GetIdentifier())));
|
||||||
try {
|
try {
|
||||||
$response = $this->guzzleClient->get($url);
|
$response = $this->guzzleClient->get($url);
|
||||||
|
|
@ -77,8 +76,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetContainerUpdateState(Container $container) : VersionState
|
public function GetContainerUpdateState(Container $container): VersionState {
|
||||||
{
|
|
||||||
$tag = $container->GetImageTag();
|
$tag = $container->GetImageTag();
|
||||||
if ($tag === '%AIO_CHANNEL%') {
|
if ($tag === '%AIO_CHANNEL%') {
|
||||||
$tag = $this->GetCurrentChannel();
|
$tag = $this->GetCurrentChannel();
|
||||||
|
|
@ -88,12 +86,12 @@ readonly class DockerActionManager {
|
||||||
if ($runningDigests === null) {
|
if ($runningDigests === null) {
|
||||||
return VersionState::Different;
|
return VersionState::Different;
|
||||||
}
|
}
|
||||||
$remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($container->GetContainerName(), $tag);
|
$remoteDigest = $this->GetLatestDigestOfTag($container->GetContainerName(), $tag);
|
||||||
if ($remoteDigest === null) {
|
if ($remoteDigest === null) {
|
||||||
return VersionState::Equal;
|
return VersionState::Equal;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($runningDigests as $runningDigest) {
|
foreach ($runningDigests as $runningDigest) {
|
||||||
if ($runningDigest === $remoteDigest) {
|
if ($runningDigest === $remoteDigest) {
|
||||||
return VersionState::Equal;
|
return VersionState::Equal;
|
||||||
}
|
}
|
||||||
|
|
@ -101,8 +99,7 @@ readonly class DockerActionManager {
|
||||||
return VersionState::Different;
|
return VersionState::Different;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetContainerStartingState(Container $container) : ContainerState
|
public function GetContainerStartingState(Container $container): ContainerState {
|
||||||
{
|
|
||||||
$runningState = $this->GetContainerRunningState($container);
|
$runningState = $this->GetContainerRunningState($container);
|
||||||
if ($runningState === ContainerState::Stopped || $runningState === ContainerState::ImageDoesNotExist) {
|
if ($runningState === ContainerState::Stopped || $runningState === ContainerState::ImageDoesNotExist) {
|
||||||
return $runningState;
|
return $runningState;
|
||||||
|
|
@ -110,9 +107,9 @@ readonly class DockerActionManager {
|
||||||
|
|
||||||
$containerName = $container->GetIdentifier();
|
$containerName = $container->GetIdentifier();
|
||||||
$internalPort = $container->GetInternalPort();
|
$internalPort = $container->GetInternalPort();
|
||||||
if($internalPort === '%APACHE_PORT%') {
|
if ($internalPort === '%APACHE_PORT%') {
|
||||||
$internalPort = $this->configurationManager->GetApachePort();
|
$internalPort = $this->configurationManager->GetApachePort();
|
||||||
} elseif($internalPort === '%TALK_PORT%') {
|
} elseif ($internalPort === '%TALK_PORT%') {
|
||||||
$internalPort = $this->configurationManager->GetTalkPort();
|
$internalPort = $this->configurationManager->GetTalkPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +126,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function DeleteContainer(Container $container) : void {
|
public function DeleteContainer(Container $container): void {
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->GetIdentifier())));
|
$url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->GetIdentifier())));
|
||||||
try {
|
try {
|
||||||
$this->guzzleClient->delete($url);
|
$this->guzzleClient->delete($url);
|
||||||
|
|
@ -140,8 +137,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetLogs(string $id) : string
|
public function GetLogs(string $id): string {
|
||||||
{
|
|
||||||
$url = $this->BuildApiUrl(
|
$url = $this->BuildApiUrl(
|
||||||
sprintf(
|
sprintf(
|
||||||
'containers/%s/logs?stdout=true&stderr=true×tamps=true',
|
'containers/%s/logs?stdout=true&stderr=true×tamps=true',
|
||||||
|
|
@ -162,7 +158,7 @@ readonly class DockerActionManager {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function StartContainer(Container $container) : void {
|
public function StartContainer(Container $container): void {
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s/start', urlencode($container->GetIdentifier())));
|
$url = $this->BuildApiUrl(sprintf('containers/%s/start', urlencode($container->GetIdentifier())));
|
||||||
try {
|
try {
|
||||||
$this->guzzleClient->post($url);
|
$this->guzzleClient->post($url);
|
||||||
|
|
@ -171,10 +167,9 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function CreateVolumes(Container $container): void
|
public function CreateVolumes(Container $container): void {
|
||||||
{
|
|
||||||
$url = $this->BuildApiUrl('volumes/create');
|
$url = $this->BuildApiUrl('volumes/create');
|
||||||
foreach($container->GetVolumes()->GetVolumes() as $volume) {
|
foreach ($container->GetVolumes()->GetVolumes() as $volume) {
|
||||||
$forbiddenChars = [
|
$forbiddenChars = [
|
||||||
'/',
|
'/',
|
||||||
];
|
];
|
||||||
|
|
@ -184,7 +179,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
$firstChar = substr($volume->name, 0, 1);
|
$firstChar = substr($volume->name, 0, 1);
|
||||||
if(!in_array($firstChar, $forbiddenChars)) {
|
if (!in_array($firstChar, $forbiddenChars)) {
|
||||||
$this->guzzleClient->request(
|
$this->guzzleClient->request(
|
||||||
'POST',
|
'POST',
|
||||||
$url,
|
$url,
|
||||||
|
|
@ -198,7 +193,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function CreateContainer(Container $container) : void {
|
public function CreateContainer(Container $container): void {
|
||||||
$volumes = [];
|
$volumes = [];
|
||||||
foreach ($container->GetVolumes()->GetVolumes() as $volume) {
|
foreach ($container->GetVolumes()->GetVolumes() as $volume) {
|
||||||
// // NEXTCLOUD_MOUNT gets added via bind-mount later on
|
// // NEXTCLOUD_MOUNT gets added via bind-mount later on
|
||||||
|
|
@ -226,12 +221,12 @@ readonly class DockerActionManager {
|
||||||
$requestBody['HostConfig']['Binds'] = $volumes;
|
$requestBody['HostConfig']['Binds'] = $volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($container->GetSecrets() as $secret) {
|
foreach ($container->GetSecrets() as $secret) {
|
||||||
$this->configurationManager->GetAndGenerateSecret($secret);
|
$this->configurationManager->GetAndGenerateSecret($secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
$aioVariables = $container->GetAioVariables()->GetVariables();
|
$aioVariables = $container->GetAioVariables()->GetVariables();
|
||||||
foreach($aioVariables as $variable) {
|
foreach ($aioVariables as $variable) {
|
||||||
$config = $this->configurationManager->GetConfig();
|
$config = $this->configurationManager->GetConfig();
|
||||||
$variableArray = explode('=', $variable);
|
$variableArray = explode('=', $variable);
|
||||||
$config[$variableArray[0]] = $variableArray[1];
|
$config[$variableArray[0]] = $variableArray[1];
|
||||||
|
|
@ -244,7 +239,7 @@ readonly class DockerActionManager {
|
||||||
if ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') {
|
if ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') {
|
||||||
$envs[] = $this->GetAllNextcloudExecCommands();
|
$envs[] = $this->GetAllNextcloudExecCommands();
|
||||||
}
|
}
|
||||||
foreach($envs as $key => $env) {
|
foreach ($envs as $key => $env) {
|
||||||
// TODO: This whole block below is a hack and needs to get reworked in order to support multiple substitutions per line by default for all envs
|
// TODO: This whole block below is a hack and needs to get reworked in order to support multiple substitutions per line by default for all envs
|
||||||
if (str_starts_with($env, 'extra_params=')) {
|
if (str_starts_with($env, 'extra_params=')) {
|
||||||
$env = str_replace('%COLLABORA_SECCOMP_POLICY%', $this->configurationManager->GetCollaboraSeccompPolicy(), $env);
|
$env = str_replace('%COLLABORA_SECCOMP_POLICY%', $this->configurationManager->GetCollaboraSeccompPolicy(), $env);
|
||||||
|
|
@ -256,12 +251,12 @@ readonly class DockerActionManager {
|
||||||
// Original implementation
|
// Original implementation
|
||||||
$patterns = ['/%(.*)%/'];
|
$patterns = ['/%(.*)%/'];
|
||||||
|
|
||||||
if(preg_match($patterns[0], $env, $out) === 1) {
|
if (preg_match($patterns[0], $env, $out) === 1) {
|
||||||
$replacements = array();
|
$replacements = array();
|
||||||
|
|
||||||
if($out[1] === 'NC_DOMAIN') {
|
if ($out[1] === 'NC_DOMAIN') {
|
||||||
$replacements[1] = $this->configurationManager->GetDomain();
|
$replacements[1] = $this->configurationManager->GetDomain();
|
||||||
} elseif($out[1] === 'NC_BASE_DN') {
|
} elseif ($out[1] === 'NC_BASE_DN') {
|
||||||
$replacements[1] = $this->configurationManager->GetBaseDN();
|
$replacements[1] = $this->configurationManager->GetBaseDN();
|
||||||
} elseif ($out[1] === 'AIO_TOKEN') {
|
} elseif ($out[1] === 'AIO_TOKEN') {
|
||||||
$replacements[1] = $this->configurationManager->GetToken();
|
$replacements[1] = $this->configurationManager->GetToken();
|
||||||
|
|
@ -391,10 +386,10 @@ readonly class DockerActionManager {
|
||||||
} else {
|
} else {
|
||||||
$replacements[1] = '';
|
$replacements[1] = '';
|
||||||
}
|
}
|
||||||
// Allow to get local ip-address of database container which allows to talk to it even in host mode (the container that requires this needs to be started first then)
|
// Allow to get local ip-address of database container which allows to talk to it even in host mode (the container that requires this needs to be started first then)
|
||||||
} elseif ($out[1] === 'AIO_DATABASE_HOST') {
|
} elseif ($out[1] === 'AIO_DATABASE_HOST') {
|
||||||
$replacements[1] = gethostbyname('nextcloud-aio-database');
|
$replacements[1] = gethostbyname('nextcloud-aio-database');
|
||||||
// Allow to get local ip-address of caddy container and add it to trusted proxies automatically
|
// Allow to get local ip-address of caddy container and add it to trusted proxies automatically
|
||||||
} elseif ($out[1] === 'CADDY_IP_ADDRESS') {
|
} elseif ($out[1] === 'CADDY_IP_ADDRESS') {
|
||||||
$replacements[1] = '';
|
$replacements[1] = '';
|
||||||
$communityContainers = $this->configurationManager->GetEnabledCommunityContainers();
|
$communityContainers = $this->configurationManager->GetEnabledCommunityContainers();
|
||||||
|
|
@ -419,7 +414,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count($envs) > 0) {
|
if (count($envs) > 0) {
|
||||||
$requestBody['Env'] = $envs;
|
$requestBody['Env'] = $envs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,7 +424,7 @@ readonly class DockerActionManager {
|
||||||
|
|
||||||
$exposedPorts = [];
|
$exposedPorts = [];
|
||||||
if ($container->GetInternalPort() !== 'host') {
|
if ($container->GetInternalPort() !== 'host') {
|
||||||
foreach($container->GetPorts()->GetPorts() as $value) {
|
foreach ($container->GetPorts()->GetPorts() as $value) {
|
||||||
$port = $value->port;
|
$port = $value->port;
|
||||||
$protocol = $value->protocol;
|
$protocol = $value->protocol;
|
||||||
if ($port === '%APACHE_PORT%') {
|
if ($port === '%APACHE_PORT%') {
|
||||||
|
|
@ -449,7 +444,7 @@ readonly class DockerActionManager {
|
||||||
$requestBody['HostConfig']['NetworkMode'] = 'host';
|
$requestBody['HostConfig']['NetworkMode'] = 'host';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count($exposedPorts) > 0) {
|
if (count($exposedPorts) > 0) {
|
||||||
$requestBody['ExposedPorts'] = $exposedPorts;
|
$requestBody['ExposedPorts'] = $exposedPorts;
|
||||||
foreach ($container->GetPorts()->GetPorts() as $value) {
|
foreach ($container->GetPorts()->GetPorts() as $value) {
|
||||||
$port = $value->port;
|
$port = $value->port;
|
||||||
|
|
@ -474,16 +469,16 @@ readonly class DockerActionManager {
|
||||||
$portWithProtocol = $port . '/' . $protocol;
|
$portWithProtocol = $port . '/' . $protocol;
|
||||||
$requestBody['HostConfig']['PortBindings'][$portWithProtocol] = [
|
$requestBody['HostConfig']['PortBindings'][$portWithProtocol] = [
|
||||||
[
|
[
|
||||||
'HostPort' => $port,
|
'HostPort' => $port,
|
||||||
'HostIp' => $ipBinding,
|
'HostIp' => $ipBinding,
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$devices = [];
|
$devices = [];
|
||||||
foreach($container->GetDevices() as $device) {
|
foreach ($container->GetDevices() as $device) {
|
||||||
if ($device === '/dev/dri' && ! $this->configurationManager->isDriDeviceEnabled()) {
|
if ($device === '/dev/dri' && !$this->configurationManager->isDriDeviceEnabled()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$devices[] = ["PathOnHost" => $device, "PathInContainer" => $device, "CgroupPermissions" => "rwm"];
|
$devices[] = ["PathOnHost" => $device, "PathInContainer" => $device, "CgroupPermissions" => "rwm"];
|
||||||
|
|
@ -510,7 +505,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmpfs = [];
|
$tmpfs = [];
|
||||||
foreach($container->GetTmpfs() as $tmp) {
|
foreach ($container->GetTmpfs() as $tmp) {
|
||||||
$mode = "";
|
$mode = "";
|
||||||
if (str_contains($tmp, ':')) {
|
if (str_contains($tmp, ':')) {
|
||||||
$mode = explode(':', $tmp)[1];
|
$mode = explode(':', $tmp)[1];
|
||||||
|
|
@ -519,7 +514,7 @@ readonly class DockerActionManager {
|
||||||
$tmpfs[$tmp] = $mode;
|
$tmpfs[$tmp] = $mode;
|
||||||
}
|
}
|
||||||
if (count($tmpfs) > 0) {
|
if (count($tmpfs) > 0) {
|
||||||
$requestBody['HostConfig']['Tmpfs'] = $tmpfs;
|
$requestBody['HostConfig']['Tmpfs'] = $tmpfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
$requestBody['HostConfig']['Init'] = $container->GetInit();
|
$requestBody['HostConfig']['Init'] = $container->GetInit();
|
||||||
|
|
@ -563,22 +558,22 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Special things for the talk container which should not be exposed in the containers.json
|
// Special things for the talk container which should not be exposed in the containers.json
|
||||||
} elseif ($container->GetIdentifier() === 'nextcloud-aio-talk') {
|
} elseif ($container->GetIdentifier() === 'nextcloud-aio-talk') {
|
||||||
// This is needed due to a bug in libwebsockets which cannot handle unlimited ulimits
|
// This is needed due to a bug in libwebsockets which cannot handle unlimited ulimits
|
||||||
$requestBody['HostConfig']['Ulimits'] = [["Name" => "nofile", "Hard" => 200000, "Soft" => 200000]];
|
$requestBody['HostConfig']['Ulimits'] = [["Name" => "nofile", "Hard" => 200000, "Soft" => 200000]];
|
||||||
// // Special things for the nextcloud container which should not be exposed in the containers.json
|
// // Special things for the nextcloud container which should not be exposed in the containers.json
|
||||||
// } elseif ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') {
|
// } elseif ($container->GetIdentifier() === 'nextcloud-aio-nextcloud') {
|
||||||
// foreach ($container->GetVolumes()->GetVolumes() as $volume) {
|
// foreach ($container->GetVolumes()->GetVolumes() as $volume) {
|
||||||
// if ($volume->name !== $this->configurationManager->GetNextcloudMount()) {
|
// if ($volume->name !== $this->configurationManager->GetNextcloudMount()) {
|
||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
// $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]];
|
// $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]];
|
||||||
// }
|
// }
|
||||||
// Special things for the caddy community container
|
// Special things for the caddy community container
|
||||||
} elseif ($container->GetIdentifier() === 'nextcloud-aio-caddy') {
|
} elseif ($container->GetIdentifier() === 'nextcloud-aio-caddy') {
|
||||||
$requestBody['HostConfig']['ExtraHosts'] = ['host.docker.internal:host-gateway'];
|
$requestBody['HostConfig']['ExtraHosts'] = ['host.docker.internal:host-gateway'];
|
||||||
// Special things for the collabora container which should not be exposed in the containers.json
|
// Special things for the collabora container which should not be exposed in the containers.json
|
||||||
} elseif ($container->GetIdentifier() === 'nextcloud-aio-collabora') {
|
} elseif ($container->GetIdentifier() === 'nextcloud-aio-collabora') {
|
||||||
if ($this->configurationManager->GetAdditionalCollaboraOptions() !== '') {
|
if ($this->configurationManager->GetAdditionalCollaboraOptions() !== '') {
|
||||||
$requestBody['Cmd'] = [$this->configurationManager->GetAdditionalCollaboraOptions()];
|
$requestBody['Cmd'] = [$this->configurationManager->GetAdditionalCollaboraOptions()];
|
||||||
|
|
@ -604,13 +599,13 @@ readonly class DockerActionManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isDockerHubReachable(Container $container) : bool {
|
public function isDockerHubReachable(Container $container): bool {
|
||||||
$tag = $container->GetImageTag();
|
$tag = $container->GetImageTag();
|
||||||
if ($tag === '%AIO_CHANNEL%') {
|
if ($tag === '%AIO_CHANNEL%') {
|
||||||
$tag = $this->GetCurrentChannel();
|
$tag = $this->GetCurrentChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
$remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($container->GetContainerName(), $tag);
|
$remoteDigest = $this->GetLatestDigestOfTag($container->GetContainerName(), $tag);
|
||||||
|
|
||||||
if ($remoteDigest === null) {
|
if ($remoteDigest === null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -619,8 +614,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function PullImage(Container $container) : void
|
public function PullImage(Container $container): void {
|
||||||
{
|
|
||||||
$imageName = $this->BuildImageName($container);
|
$imageName = $this->BuildImageName($container);
|
||||||
$encodedImageName = urlencode($imageName);
|
$encodedImageName = urlencode($imageName);
|
||||||
$url = $this->BuildApiUrl(sprintf('images/create?fromImage=%s', $encodedImageName));
|
$url = $this->BuildApiUrl(sprintf('images/create?fromImage=%s', $encodedImageName));
|
||||||
|
|
@ -643,8 +637,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isContainerUpdateAvailable(string $id) : string
|
private function isContainerUpdateAvailable(string $id): string {
|
||||||
{
|
|
||||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||||
|
|
||||||
$updateAvailable = "";
|
$updateAvailable = "";
|
||||||
|
|
@ -657,7 +650,7 @@ readonly class DockerActionManager {
|
||||||
return $updateAvailable;
|
return $updateAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAnyUpdateAvailable() : bool {
|
public function isAnyUpdateAvailable(): bool {
|
||||||
// return early if instance is not installed
|
// return early if instance is not installed
|
||||||
if (!$this->configurationManager->wasStartButtonClicked()) {
|
if (!$this->configurationManager->wasStartButtonClicked()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -671,8 +664,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getBackupVolumes(string $id) : string
|
private function getBackupVolumes(string $id): string {
|
||||||
{
|
|
||||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||||
|
|
||||||
$backupVolumes = '';
|
$backupVolumes = '';
|
||||||
|
|
@ -685,14 +677,13 @@ readonly class DockerActionManager {
|
||||||
return $backupVolumes;
|
return $backupVolumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAllBackupVolumes() : array {
|
private function getAllBackupVolumes(): array {
|
||||||
$id = 'nextcloud-aio-apache';
|
$id = 'nextcloud-aio-apache';
|
||||||
$backupVolumesArray = explode(' ', $this->getBackupVolumes($id));
|
$backupVolumesArray = explode(' ', $this->getBackupVolumes($id));
|
||||||
return array_unique($backupVolumesArray);
|
return array_unique($backupVolumesArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function GetNextcloudExecCommands(string $id) : string
|
private function GetNextcloudExecCommands(string $id): string {
|
||||||
{
|
|
||||||
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
$container = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||||
|
|
||||||
$nextcloudExecCommands = '';
|
$nextcloudExecCommands = '';
|
||||||
|
|
@ -705,13 +696,12 @@ readonly class DockerActionManager {
|
||||||
return $nextcloudExecCommands;
|
return $nextcloudExecCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function GetAllNextcloudExecCommands() : string
|
private function GetAllNextcloudExecCommands(): string {
|
||||||
{
|
|
||||||
$id = 'nextcloud-aio-apache';
|
$id = 'nextcloud-aio-apache';
|
||||||
return 'NEXTCLOUD_EXEC_COMMANDS=' . $this->GetNextcloudExecCommands($id);
|
return 'NEXTCLOUD_EXEC_COMMANDS=' . $this->GetNextcloudExecCommands($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function GetRepoDigestsOfContainer(string $containerName) : ?array {
|
private function GetRepoDigestsOfContainer(string $containerName): ?array {
|
||||||
try {
|
try {
|
||||||
$containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
$containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
||||||
$containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true);
|
$containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true);
|
||||||
|
|
@ -732,7 +722,7 @@ readonly class DockerActionManager {
|
||||||
|
|
||||||
$repoDigestArray = [];
|
$repoDigestArray = [];
|
||||||
$oneDigestGiven = false;
|
$oneDigestGiven = false;
|
||||||
foreach($imageOutput['RepoDigests'] as $repoDigest) {
|
foreach ($imageOutput['RepoDigests'] as $repoDigest) {
|
||||||
$digestPosition = strpos($repoDigest, '@');
|
$digestPosition = strpos($repoDigest, '@');
|
||||||
if ($digestPosition === false) {
|
if ($digestPosition === false) {
|
||||||
error_log('Somehow the RepoDigest of ' . $containerName . ' does not contain a @.');
|
error_log('Somehow the RepoDigest of ' . $containerName . ' does not contain a @.');
|
||||||
|
|
@ -752,10 +742,10 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetCurrentChannel() : string {
|
public function GetCurrentChannel(): string {
|
||||||
$cacheKey = 'aio-ChannelName';
|
$cacheKey = 'aio-ChannelName';
|
||||||
$channelName = apcu_fetch($cacheKey);
|
$channelName = apcu_fetch($cacheKey);
|
||||||
if($channelName !== false && is_string($channelName)) {
|
if ($channelName !== false && is_string($channelName)) {
|
||||||
return $channelName;
|
return $channelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -765,7 +755,7 @@ readonly class DockerActionManager {
|
||||||
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true);
|
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true);
|
||||||
$containerChecksum = $output['Image'];
|
$containerChecksum = $output['Image'];
|
||||||
$tagArray = explode(':', $output['Config']['Image']);
|
$tagArray = explode(':', $output['Config']['Image']);
|
||||||
if (count($tagArray) === 2) {
|
if (count($tagArray) === 2) {
|
||||||
$tag = $tagArray[1];
|
$tag = $tagArray[1];
|
||||||
} else {
|
} else {
|
||||||
error_log("No tag was found when getting the current channel. You probably did not follow the documentation correctly. Changing the channel to the default 'latest'.");
|
error_log("No tag was found when getting the current channel. You probably did not follow the documentation correctly. Changing the channel to the default 'latest'.");
|
||||||
|
|
@ -780,8 +770,7 @@ readonly class DockerActionManager {
|
||||||
return 'latest';
|
return 'latest';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function IsMastercontainerUpdateAvailable() : bool
|
public function IsMastercontainerUpdateAvailable(): bool {
|
||||||
{
|
|
||||||
$imageName = 'nextcloud/all-in-one';
|
$imageName = 'nextcloud/all-in-one';
|
||||||
$containerName = 'nextcloud-aio-mastercontainer';
|
$containerName = 'nextcloud-aio-mastercontainer';
|
||||||
|
|
||||||
|
|
@ -791,7 +780,7 @@ readonly class DockerActionManager {
|
||||||
if ($runningDigests === null) {
|
if ($runningDigests === null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$remoteDigest = $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag);
|
$remoteDigest = $this->GetLatestDigestOfTag($imageName, $tag);
|
||||||
if ($remoteDigest === null) {
|
if ($remoteDigest === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -804,8 +793,7 @@ readonly class DockerActionManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh') : void
|
public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh'): void {
|
||||||
{
|
|
||||||
if ($this->GetContainerStartingState($container) === ContainerState::Running) {
|
if ($this->GetContainerStartingState($container) === ContainerState::Running) {
|
||||||
|
|
||||||
$containerName = $container->GetIdentifier();
|
$containerName = $container->GetIdentifier();
|
||||||
|
|
@ -849,8 +837,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function DisconnectContainerFromBridgeNetwork(string $id) : void
|
private function DisconnectContainerFromBridgeNetwork(string $id): void {
|
||||||
{
|
|
||||||
|
|
||||||
$url = $this->BuildApiUrl(
|
$url = $this->BuildApiUrl(
|
||||||
sprintf('networks/%s/disconnect', 'bridge')
|
sprintf('networks/%s/disconnect', 'bridge')
|
||||||
|
|
@ -870,8 +857,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ConnectContainerIdToNetwork(string $id, string $internalPort, string $network = 'nextcloud-aio', bool $createNetwork = true, string $alias = '') : void
|
private function ConnectContainerIdToNetwork(string $id, string $internalPort, string $network = 'nextcloud-aio', bool $createNetwork = true, string $alias = ''): void {
|
||||||
{
|
|
||||||
if ($internalPort === 'host') {
|
if ($internalPort === 'host') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -902,9 +888,9 @@ readonly class DockerActionManager {
|
||||||
$url = $this->BuildApiUrl(
|
$url = $this->BuildApiUrl(
|
||||||
sprintf('networks/%s/connect', $network)
|
sprintf('networks/%s/connect', $network)
|
||||||
);
|
);
|
||||||
$jsonPayload = [ 'Container' => $id ];
|
$jsonPayload = ['Container' => $id];
|
||||||
if ($alias !== '' ) {
|
if ($alias !== '') {
|
||||||
$jsonPayload['EndpointConfig'] = ['Aliases' => [ $alias ]];
|
$jsonPayload['EndpointConfig'] = ['Aliases' => [$alias]];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -923,15 +909,13 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ConnectMasterContainerToNetwork() : void
|
public function ConnectMasterContainerToNetwork(): void {
|
||||||
{
|
|
||||||
$this->ConnectContainerIdToNetwork('nextcloud-aio-mastercontainer', '');
|
$this->ConnectContainerIdToNetwork('nextcloud-aio-mastercontainer', '');
|
||||||
// Don't disconnect here since it slows down the initial login by a lot. Is getting done during cron.sh instead.
|
// Don't disconnect here since it slows down the initial login by a lot. Is getting done during cron.sh instead.
|
||||||
// $this->DisconnectContainerFromBridgeNetwork('nextcloud-aio-mastercontainer');
|
// $this->DisconnectContainerFromBridgeNetwork('nextcloud-aio-mastercontainer');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ConnectContainerToNetwork(Container $container) : void
|
public function ConnectContainerToNetwork(Container $container): void {
|
||||||
{
|
|
||||||
// Add a secondary alias for domaincheck container, to keep it as similar to actual apache controller as possible.
|
// Add a secondary alias for domaincheck container, to keep it as similar to actual apache controller as possible.
|
||||||
// If a reverse-proxy is relying on container name as hostname this allows it to operate as usual and still validate the domain
|
// If a reverse-proxy is relying on container name as hostname this allows it to operate as usual and still validate the domain
|
||||||
// The domaincheck container and apache container are never supposed to be active at the same time because they use the same APACHE_PORT anyway, so this doesn't add any new constraints.
|
// The domaincheck container and apache container are never supposed to be active at the same time because they use the same APACHE_PORT anyway, so this doesn't add any new constraints.
|
||||||
|
|
@ -947,7 +931,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function StopContainer(Container $container) : void {
|
public function StopContainer(Container $container): void {
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->GetIdentifier()), $container->GetMaxShutdownTime()));
|
$url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->GetIdentifier()), $container->GetMaxShutdownTime()));
|
||||||
try {
|
try {
|
||||||
$this->guzzleClient->post($url);
|
$this->guzzleClient->post($url);
|
||||||
|
|
@ -958,8 +942,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetBackupcontainerExitCode() : int
|
public function GetBackupcontainerExitCode(): int {
|
||||||
{
|
|
||||||
$containerName = 'nextcloud-aio-borgbackup';
|
$containerName = 'nextcloud-aio-borgbackup';
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
||||||
try {
|
try {
|
||||||
|
|
@ -981,8 +964,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetDatabasecontainerExitCode() : int
|
public function GetDatabasecontainerExitCode(): int {
|
||||||
{
|
|
||||||
$containerName = 'nextcloud-aio-database';
|
$containerName = 'nextcloud-aio-database';
|
||||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
||||||
try {
|
try {
|
||||||
|
|
@ -1004,7 +986,7 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isLoginAllowed() : bool {
|
public function isLoginAllowed(): bool {
|
||||||
$id = 'nextcloud-aio-apache';
|
$id = 'nextcloud-aio-apache';
|
||||||
$apacheContainer = $this->containerDefinitionFetcher->GetContainerById($id);
|
$apacheContainer = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||||
if ($this->GetContainerStartingState($apacheContainer) === ContainerState::Running) {
|
if ($this->GetContainerStartingState($apacheContainer) === ContainerState::Running) {
|
||||||
|
|
@ -1013,7 +995,7 @@ readonly class DockerActionManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isBackupContainerRunning() : bool {
|
public function isBackupContainerRunning(): bool {
|
||||||
$id = 'nextcloud-aio-borgbackup';
|
$id = 'nextcloud-aio-borgbackup';
|
||||||
$backupContainer = $this->containerDefinitionFetcher->GetContainerById($id);
|
$backupContainer = $this->containerDefinitionFetcher->GetContainerById($id);
|
||||||
if ($this->GetContainerRunningState($backupContainer) === ContainerState::Running) {
|
if ($this->GetContainerRunningState($backupContainer) === ContainerState::Running) {
|
||||||
|
|
@ -1022,7 +1004,7 @@ readonly class DockerActionManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function GetCreatedTimeOfNextcloudImage() : ?string {
|
private function GetCreatedTimeOfNextcloudImage(): ?string {
|
||||||
$imageName = 'nextcloud/aio-nextcloud' . ':' . $this->GetCurrentChannel();
|
$imageName = 'nextcloud/aio-nextcloud' . ':' . $this->GetCurrentChannel();
|
||||||
try {
|
try {
|
||||||
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
|
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
|
||||||
|
|
@ -1039,11 +1021,11 @@ readonly class DockerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetAndGenerateSecretWrapper(string $secretId) : string {
|
public function GetAndGenerateSecretWrapper(string $secretId): string {
|
||||||
return $this->configurationManager->GetAndGenerateSecret($secretId);
|
return $this->configurationManager->GetAndGenerateSecret($secretId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNextcloudImageOutdated() : bool {
|
public function isNextcloudImageOutdated(): bool {
|
||||||
$createdTime = $this->GetCreatedTimeOfNextcloudImage();
|
$createdTime = $this->GetCreatedTimeOfNextcloudImage();
|
||||||
|
|
||||||
if ($createdTime === null) {
|
if ($createdTime === null) {
|
||||||
|
|
@ -1057,4 +1039,13 @@ readonly class DockerActionManager {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GetLatestDigestOfTag(string $imageName, string $tag): ?string {
|
||||||
|
$prefix = 'ghcr.io/';
|
||||||
|
if (str_starts_with($imageName, $prefix)) {
|
||||||
|
return $this->gitHubContainerRegistryManager->GetLatestDigestOfTag(str_replace($prefix, '', $imageName), $tag);
|
||||||
|
} else {
|
||||||
|
return $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
php/src/Docker/GitHubContainerRegistryManager.php
Normal file
62
php/src/Docker/GitHubContainerRegistryManager.php
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace AIO\Docker;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
|
readonly class GitHubContainerRegistryManager
|
||||||
|
{
|
||||||
|
private Client $guzzleClient;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->guzzleClient = new Client();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetLatestDigestOfTag(string $name, string $tag): ?string
|
||||||
|
{
|
||||||
|
$cacheKey = 'ghcr-manifest-' . $name . $tag;
|
||||||
|
|
||||||
|
$cachedVersion = apcu_fetch($cacheKey);
|
||||||
|
if ($cachedVersion !== false && is_string($cachedVersion)) {
|
||||||
|
return $cachedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one of the links below should ever become outdated, we can still upgrade the mastercontainer via the webinterface manually by opening '/api/docker/getwatchtower'
|
||||||
|
|
||||||
|
try {
|
||||||
|
$authTokenRequest = $this->guzzleClient->request(
|
||||||
|
'GET',
|
||||||
|
'https://ghcr.io/token?scope=repository:' . $name . ':pull'
|
||||||
|
);
|
||||||
|
$body = $authTokenRequest->getBody()->getContents();
|
||||||
|
$decodedBody = json_decode($body, true);
|
||||||
|
if (isset($decodedBody['token'])) {
|
||||||
|
$authToken = $decodedBody['token'];
|
||||||
|
$manifestRequest = $this->guzzleClient->request(
|
||||||
|
'HEAD',
|
||||||
|
'https://ghcr.io/v2/' . $name . '/manifests/' . $tag,
|
||||||
|
[
|
||||||
|
'headers' => [
|
||||||
|
'Accept' => 'application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log('Could not get digest of container ' . $name . ':' . $tag);
|
||||||
|
return null;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log('Could not get digest of container ' . $name . ':' . $tag . ' ' . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue