Web updater initial WIP.
This commit is contained in:
parent
c8e18a7324
commit
d76ff450c8
|
@ -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"]
|
||||
|
|
|
@ -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: { }
|
||||
|
|
|
@ -446,151 +446,159 @@
|
|||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<cpu-stats-help-modal ref="cpuStatsHelpModal" />
|
||||
<memory-stats-help-modal ref="memoryStatsHelpModal" />
|
||||
<cpu-stats-help-modal ref="$cpuStatsHelpModal" />
|
||||
<memory-stats-help-modal ref="$memoryStatsHelpModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import Icon from '~/components/Common/Icon';
|
||||
import CpuStatsHelpModal from "./Index/CpuStatsHelpModal";
|
||||
import MemoryStatsHelpModal from "./Index/MemoryStatsHelpModal";
|
||||
import {isObject, upperFirst} from 'lodash';
|
||||
import RunningBadge from "~/components/Common/Badges/RunningBadge.vue";
|
||||
import {onMounted, ref, shallowRef} from "vue";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
import {useNotify} from "~/vendor/bootstrapVue";
|
||||
|
||||
export default {
|
||||
name: 'AdminIndex',
|
||||
components: {RunningBadge, CpuStatsHelpModal, MemoryStatsHelpModal, Icon},
|
||||
props: {
|
||||
adminPanels: {
|
||||
type: Object,
|
||||
required: true
|
||||
const props = defineProps({
|
||||
adminPanels: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
statsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
servicesUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const stats = shallowRef({
|
||||
cpu: {
|
||||
total: {
|
||||
name: 'Total',
|
||||
steal: 0,
|
||||
io_wait: 0,
|
||||
usage: 0
|
||||
},
|
||||
statsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
cores: [],
|
||||
load: [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
memory: {
|
||||
bytes: {
|
||||
total: 0,
|
||||
used: 0,
|
||||
cached: 0
|
||||
},
|
||||
servicesUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
readable: {
|
||||
total: '',
|
||||
used: '',
|
||||
cached: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stats: {
|
||||
cpu: {
|
||||
total: {
|
||||
name: 'Total',
|
||||
steal: 0,
|
||||
io_wait: 0,
|
||||
usage: 0
|
||||
},
|
||||
cores: [],
|
||||
load: [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
memory: {
|
||||
bytes: {
|
||||
total: 0,
|
||||
used: 0,
|
||||
cached: 0
|
||||
},
|
||||
readable: {
|
||||
total: '',
|
||||
used: '',
|
||||
cached: ''
|
||||
}
|
||||
},
|
||||
disk: {
|
||||
bytes: {
|
||||
total: 0,
|
||||
used: 0
|
||||
},
|
||||
readable: {
|
||||
total: '',
|
||||
used: ''
|
||||
}
|
||||
},
|
||||
network: []
|
||||
},
|
||||
services: []
|
||||
};
|
||||
disk: {
|
||||
bytes: {
|
||||
total: 0,
|
||||
used: 0
|
||||
},
|
||||
readable: {
|
||||
total: '',
|
||||
used: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateStats();
|
||||
this.updateServices();
|
||||
},
|
||||
methods: {
|
||||
formatCpuName(cpuName) {
|
||||
return upperFirst(cpuName);
|
||||
},
|
||||
formatPercentageString(value) {
|
||||
return value + '%';
|
||||
},
|
||||
getNetworkInterfaceTableFields(interfaceData) {
|
||||
let fields = [];
|
||||
network: []
|
||||
});
|
||||
|
||||
Object.keys(interfaceData).forEach((key) => {
|
||||
fields.push({
|
||||
key: key,
|
||||
sortable: false
|
||||
});
|
||||
});
|
||||
const services = ref([]);
|
||||
|
||||
return fields;
|
||||
},
|
||||
getNetworkInterfaceTableItems(interfaceData) {
|
||||
let item = {};
|
||||
const formatCpuName = (cpuName) => upperFirst(cpuName);
|
||||
|
||||
Object.entries(interfaceData).forEach((data) => {
|
||||
let key = data[0];
|
||||
let value = data[1];
|
||||
const formatPercentageString = (value) => value + '%';
|
||||
|
||||
if (isObject(value)) {
|
||||
value = value.readable + '/s';
|
||||
}
|
||||
const getNetworkInterfaceTableFields = (interfaceData) => {
|
||||
let fields = [];
|
||||
|
||||
item[key] = value;
|
||||
});
|
||||
Object.keys(interfaceData).forEach((key) => {
|
||||
fields.push({
|
||||
key: key,
|
||||
sortable: false
|
||||
});
|
||||
});
|
||||
|
||||
return [item];
|
||||
},
|
||||
updateStats() {
|
||||
this.axios.get(this.statsUrl).then((response) => {
|
||||
this.stats = response.data;
|
||||
return fields;
|
||||
};
|
||||
|
||||
setTimeout(this.updateStats, (!document.hidden) ? 1000 : 5000);
|
||||
}).catch((error) => {
|
||||
if (!error.response || error.response.data.code !== 403) {
|
||||
setTimeout(this.updateStats, (!document.hidden) ? 5000 : 10000);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateServices() {
|
||||
this.axios.get(this.servicesUrl).then((response) => {
|
||||
this.services = response.data;
|
||||
const getNetworkInterfaceTableItems = (interfaceData) => {
|
||||
let item = {};
|
||||
|
||||
setTimeout(this.updateServices, (!document.hidden) ? 5000 : 15000);
|
||||
}).catch((error) => {
|
||||
if (!error.response || error.response.data.code !== 403) {
|
||||
setTimeout(this.updateServices, (!document.hidden) ? 15000 : 30000);
|
||||
}
|
||||
});
|
||||
},
|
||||
doRestart(serviceUrl) {
|
||||
this.axios.post(serviceUrl).then((resp) => {
|
||||
this.$notifySuccess(resp.data.message);
|
||||
});
|
||||
},
|
||||
showCpuStatsHelpModal() {
|
||||
this.$refs.cpuStatsHelpModal.create();
|
||||
},
|
||||
showMemoryStatsHelpModal() {
|
||||
this.$refs.memoryStatsHelpModal.create();
|
||||
},
|
||||
}
|
||||
Object.entries(interfaceData).forEach((data) => {
|
||||
let key = data[0];
|
||||
let value = data[1];
|
||||
|
||||
if (isObject(value)) {
|
||||
value = value.readable + '/s';
|
||||
}
|
||||
|
||||
item[key] = value;
|
||||
});
|
||||
|
||||
return [item];
|
||||
};
|
||||
|
||||
const {axios} = useAxios();
|
||||
|
||||
const updateStats = () => {
|
||||
axios.get(props.statsUrl).then((response) => {
|
||||
stats.value = response.data;
|
||||
|
||||
setTimeout(updateStats, (!document.hidden) ? 1000 : 5000);
|
||||
}).catch((error) => {
|
||||
if (!error.response || error.response.data.code !== 403) {
|
||||
setTimeout(updateStats, (!document.hidden) ? 5000 : 10000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(updateStats);
|
||||
|
||||
const updateServices = () => {
|
||||
axios.get(props.servicesUrl).then((response) => {
|
||||
services.value = response.data;
|
||||
|
||||
setTimeout(updateServices, (!document.hidden) ? 5000 : 15000);
|
||||
}).catch((error) => {
|
||||
if (!error.response || error.response.data.code !== 403) {
|
||||
setTimeout(updateServices, (!document.hidden) ? 15000 : 30000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(updateServices);
|
||||
|
||||
const {notifySuccess} = useNotify();
|
||||
|
||||
const doRestart = (serviceUrl) => {
|
||||
axios.post(serviceUrl).then((resp) => {
|
||||
notifySuccess(resp.data.message);
|
||||
});
|
||||
};
|
||||
|
||||
const $cpuStatsHelpModal = ref(); // Template Ref
|
||||
|
||||
const showCpuStatsHelpModal = () => {
|
||||
$cpuStatsHelpModal.value.create();
|
||||
};
|
||||
|
||||
const $memoryStatsHelpModal = ref(); // Template Ref
|
||||
|
||||
const showMemoryStatsHelpModal = () => {
|
||||
$memoryStatsHelpModal.value.create();
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="col col-md-8">
|
||||
<section class="card mb-4" role="region">
|
||||
<div class="card-header bg-primary-dark">
|
||||
<h3 class="card-title">
|
||||
{{ $gettext('Update Details') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
|
||||
</div>
|
||||
<div class="card-actions buttons">
|
||||
<a
|
||||
class="btn btn-outline-info"
|
||||
href="#"
|
||||
@click.prevent="checkForUpdates()"
|
||||
>
|
||||
<icon icon="sync"></icon>
|
||||
{{ $gettext('Check for Updates') }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col col-md-4">
|
||||
<section class="card mb-4" role="region">
|
||||
<div class="card-header bg-primary-dark">
|
||||
<h3 class="card-title">
|
||||
{{ $gettext('Release Channel') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-body">
|
||||
{{ $gettext('Your installation is currently on this release channel:') }}
|
||||
</p>
|
||||
<p class="card-body typography-subheading">
|
||||
{{ langReleaseChannel }}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="card-actions buttons">
|
||||
<a
|
||||
class="btn btn-outline-info"
|
||||
href="https://docs.azuracast.com/en/getting-started/updates/release-channel"
|
||||
>
|
||||
<icon icon="info"></icon>
|
||||
{{ $gettext('About Release Channels') }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<section class="card mb-4" role="region">
|
||||
<div class="card-header bg-primary-dark">
|
||||
<h3 class="card-title">
|
||||
{{ $gettext('Update AzuraCast via Web') }}
|
||||
</h3>
|
||||
</div>
|
||||
<template v-if="enableWebUpdates">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="card-actions buttons">
|
||||
<a
|
||||
class="btn btn-outline-success"
|
||||
:data-confirm-title="$gettext('Update AzuraCast? Your installation will restart.')"
|
||||
href="#"
|
||||
@click.prevent="doUpdate()"
|
||||
>
|
||||
<icon icon="update"></icon>
|
||||
{{ $gettext('Update AzuraCast via Web') }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
{{
|
||||
$gettext('Web updates are not available for your installation. To update your installation, perform the manual update process instead.')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<section class="card mb-4" role="region">
|
||||
<div class="card-header bg-primary-dark">
|
||||
<h3 class="card-title">
|
||||
{{ $gettext('Manual Updates') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
{{
|
||||
$gettext('To customize installation settings, or if automatic updates are disabled, you can follow our standard update instructions to update via your SSH console.')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions buttons">
|
||||
<a
|
||||
class="btn btn-outline-info"
|
||||
href="https://docs.azuracast.com/en/getting-started/updates"
|
||||
>
|
||||
<icon icon="info"></icon>
|
||||
{{ $gettext('Update Instructions') }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, ref} from "vue";
|
||||
import Icon from "~/components/InlinePlayer.vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {useNotify} from "~/vendor/bootstrapVue";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
|
||||
const props = defineProps({
|
||||
releaseChannel: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
initialUpdateInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
updatesApiUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
enableWebUpdates: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const updateInfo = ref(props.initialUpdateInfo);
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const langReleaseChannel = computed(() => {
|
||||
return (props.releaseChannel === 'stable')
|
||||
? $gettext('Stable')
|
||||
: $gettext('Rolling Release');
|
||||
});
|
||||
|
||||
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||
const {axios} = useAxios();
|
||||
|
||||
const checkForUpdates = () => {
|
||||
wrapWithLoading(
|
||||
axios.get(props.restartStatusUrl)
|
||||
).then((resp) => {
|
||||
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
const doUpdate = () => {
|
||||
wrapWithLoading(
|
||||
axios.put(props.restartStatusUrl)
|
||||
).then(() => {
|
||||
notifySuccess(
|
||||
$gettext('Update started. Your installation will restart shortly.')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\Repository\SettingsRepository;
|
||||
use App\Environment;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Version;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class UpdatesAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SettingsRepository $settingsRepo,
|
||||
private readonly Environment $environment,
|
||||
private readonly Version $version
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response
|
||||
): ResponseInterface {
|
||||
$settings = $this->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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Admin\Updates;
|
||||
|
||||
use App\Entity\Repository\SettingsRepository;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Service\AzuraCastCentral;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class GetUpdatesAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SettingsRepository $settingsRepo,
|
||||
private readonly AzuraCastCentral $azuracastCentral
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response
|
||||
): ResponseInterface {
|
||||
$settings = $this->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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Admin\Updates;
|
||||
|
||||
use App\Entity\Api\Status;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Service\WebUpdater;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class PutUpdatesAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WebUpdater $webUpdater
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response
|
||||
): ResponseInterface {
|
||||
$this->webUpdater->triggerUpdate();
|
||||
|
||||
return $response->withJson(Status::success());
|
||||
}
|
||||
}
|
|
@ -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([
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Environment;
|
||||
|
||||
final class WebUpdater
|
||||
{
|
||||
// Don't worry that this is insecure; it's only ever used for internal communications.
|
||||
public const WATCHTOWER_TOKEN = 'azur4c457';
|
||||
|
||||
public function __construct(
|
||||
private readonly Environment $environment,
|
||||
private readonly GuzzleFactory $guzzleFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return $this->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,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue