diff --git a/Dockerfile b/Dockerfile index 7de918204..a9dde3c94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -106,7 +106,8 @@ ENV LANG="en_US.UTF-8" \ PROFILING_EXTENSION_ENABLED=0 \ PROFILING_EXTENSION_ALWAYS_ON=0 \ PROFILING_EXTENSION_HTTP_KEY=dev \ - PROFILING_EXTENSION_HTTP_IP_WHITELIST=* + PROFILING_EXTENSION_HTTP_IP_WHITELIST=* \ + ENABLE_AUTO_UPDATER="false" # Entrypoint and default command ENTRYPOINT ["tini", "--", "/usr/local/bin/my_init"] diff --git a/docker-compose.sample.yml b/docker-compose.sample.yml index c9921dc87..d04abc744 100644 --- a/docker-compose.sample.yml +++ b/docker-compose.sample.yml @@ -14,6 +14,8 @@ services: web: container_name: azuracast image: "azuracast.docker.scarf.sh/azuracast/azuracast:${AZURACAST_VERSION:-latest}" + labels: + - "com.centurylinklabs.watchtower.scope=azuracast" # Want to customize the HTTP/S ports? Follow the instructions here: # https://docs.azuracast.com/en/administration/docker#using-non-standard-ports ports: @@ -202,6 +204,16 @@ services: max-size: "1m" max-file: "5" + updater: + name: azuracast_updater + image: ghcr.io/azuracast/updater:latest + volumes: + - /var/run/docker.sock:/var/run/docker.sock + logging: + options: + max-size: "1m" + max-file: "5" + volumes: db_data: { } acme: { } diff --git a/frontend/vue/components/Admin/Index.vue b/frontend/vue/components/Admin/Index.vue index c5e6f573c..617e6bfe0 100644 --- a/frontend/vue/components/Admin/Index.vue +++ b/frontend/vue/components/Admin/Index.vue @@ -446,151 +446,159 @@ - - + + - diff --git a/frontend/vue/components/Admin/Updates.vue b/frontend/vue/components/Admin/Updates.vue new file mode 100644 index 000000000..8c5f421ff --- /dev/null +++ b/frontend/vue/components/Admin/Updates.vue @@ -0,0 +1,180 @@ + + + diff --git a/src/Controller/Admin/UpdatesAction.php b/src/Controller/Admin/UpdatesAction.php new file mode 100644 index 000000000..7198f2102 --- /dev/null +++ b/src/Controller/Admin/UpdatesAction.php @@ -0,0 +1,42 @@ +settingsRepo->readSettings(); + + $router = $request->getRouter(); + + return $request->getView()->renderVuePage( + response: $response, + component: 'Vue_AdminUpdates', + id: 'admin-updates', + title: __('Update AzuraCast'), + props: [ + 'releaseChannel' => $this->version->getReleaseChannelEnum()->value, + 'initialUpdateInfo' => $settings->getUpdateResults(), + 'updatesApiUrl' => $router->fromHere('api:admin:updates'), + 'enableWebUpdates' => $this->environment->enableWebUpdater(), + ], + ); + } +} diff --git a/src/Controller/Api/Admin/Updates/GetUpdatesAction.php b/src/Controller/Api/Admin/Updates/GetUpdatesAction.php new file mode 100644 index 000000000..ade40679a --- /dev/null +++ b/src/Controller/Api/Admin/Updates/GetUpdatesAction.php @@ -0,0 +1,46 @@ +settingsRepo->readSettings(); + + try { + $updates = $this->azuracastCentral->checkForUpdates(); + + if (!empty($updates)) { + $settings->setUpdateResults($updates); + $settings->updateUpdateLastRun(); + $this->settingsRepo->writeSettings($settings); + + return $response->withJson($updates); + } + + throw new \RuntimeException('Error parsing update data response from AzuraCast central.'); + } catch (TransferException $e) { + throw new \RuntimeException( + sprintf('Error from AzuraCast Central (%d): %s', $e->getCode(), $e->getMessage()) + ); + } + } +} diff --git a/src/Controller/Api/Admin/Updates/PutUpdatesAction.php b/src/Controller/Api/Admin/Updates/PutUpdatesAction.php new file mode 100644 index 000000000..7813818b5 --- /dev/null +++ b/src/Controller/Api/Admin/Updates/PutUpdatesAction.php @@ -0,0 +1,28 @@ +webUpdater->triggerUpdate(); + + return $response->withJson(Status::success()); + } +} diff --git a/src/Environment.php b/src/Environment.php index 6a1390fc2..05168f528 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -51,6 +51,8 @@ final class Environment public const PROFILING_EXTENSION_ALWAYS_ON = 'PROFILING_EXTENSION_ALWAYS_ON'; public const PROFILING_EXTENSION_HTTP_KEY = 'PROFILING_EXTENSION_HTTP_KEY'; + public const ENABLE_WEB_UPDATER = 'ENABLE_WEB_UPDATER'; + // Database and Cache Configuration Variables public const DB_HOST = 'MYSQL_HOST'; public const DB_PORT = 'MYSQL_PORT'; @@ -84,6 +86,8 @@ final class Environment self::PROFILING_EXTENSION_ENABLED => 0, self::PROFILING_EXTENSION_ALWAYS_ON => 0, self::PROFILING_EXTENSION_HTTP_KEY => 'dev', + + self::ENABLE_WEB_UPDATER => false, ]; public function __construct(array $elements = []) @@ -348,6 +352,11 @@ final class Environment return $this->data[self::PROFILING_EXTENSION_HTTP_KEY] ?? 'dev'; } + public function enableWebUpdater(): bool + { + return $this->isDocker() && self::envToBool($this->data[self::ENABLE_WEB_UPDATER] ?? false); + } + public static function getDefaultsForEnvironment(Environment $existingEnv): self { return new self([ diff --git a/src/Installer/Command/InstallCommand.php b/src/Installer/Command/InstallCommand.php index 17b983125..45bca8a59 100644 --- a/src/Installer/Command/InstallCommand.php +++ b/src/Installer/Command/InstallCommand.php @@ -245,6 +245,11 @@ final class InstallCommand extends Command $azuracastEnvConfig['COMPOSER_PLUGIN_MODE']['name'], $azuracastEnv->getAsBool('COMPOSER_PLUGIN_MODE', false) ); + + $azuracastEnv[Environment::ENABLE_WEB_UPDATER] = $io->confirm( + $azuracastEnvConfig[Environment::ENABLE_WEB_UPDATER]['name'], + $azuracastEnv->getAsBool(Environment::ENABLE_WEB_UPDATER, true) + ); } $io->writeln( @@ -354,6 +359,11 @@ final class InstallCommand extends Command unset($service); } + // Remove web updater if disabled + if (!$env->getAsBool(Environment::ENABLE_WEB_UPDATER, true)) { + unset($yaml['services']['updater']); + } + $yamlRaw = Yaml::dump($yaml, PHP_INT_MAX); file_put_contents($dockerComposePath, $yamlRaw); diff --git a/src/Installer/EnvFiles/AzuraCastEnvFile.php b/src/Installer/EnvFiles/AzuraCastEnvFile.php index d9a3fade9..f78fcc5cd 100644 --- a/src/Installer/EnvFiles/AzuraCastEnvFile.php +++ b/src/Installer/EnvFiles/AzuraCastEnvFile.php @@ -227,6 +227,10 @@ final class AzuraCastEnvFile extends AbstractEnvFile 'options' => ['127.0.0.1', '*'], 'default' => '*', ], + Environment::ENABLE_WEB_UPDATER => [ + 'name' => __('Enable web-based Docker image updates'), + 'default' => true, + ], ]; foreach ($config as $key => &$keyInfo) { diff --git a/src/Service/WebUpdater.php b/src/Service/WebUpdater.php new file mode 100644 index 000000000..c329a74b4 --- /dev/null +++ b/src/Service/WebUpdater.php @@ -0,0 +1,40 @@ +environment->enableWebUpdater(); + } + + public function triggerUpdate(): void + { + if (!$this->isSupported()) { + throw new \RuntimeException('Web updates are not supported on this installation.'); + } + + $client = $this->guzzleFactory->buildClient(); + + $client->post( + 'http://updater/v1/update', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . self::WATCHTOWER_TOKEN, + ], + ] + ); + } +}