Files
ladill-hosting/app/Services/Hosting/ServerAgentInstallerService.php
isaaccladandCursor e251a4cf60
Deploy Ladill Hosting / deploy (push) Failing after 17s
Initial Ladill Hosting app with Gitea deploy pipeline.
Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 16:24:20 +00:00

230 lines
7.2 KiB
PHP

<?php
namespace App\Services\Hosting;
use App\Models\CustomerHostingOrder;
use App\Services\Hosting\Providers\ContaboProvider;
class ServerAgentInstallerService
{
public function __construct(
private ContaboProvider $contaboProvider,
private ServerAgentReleaseService $releaseService,
) {}
public function augmentProvisioningConfig(CustomerHostingOrder $order, array $provisioning, array $bootstrap): array
{
if (! (bool) ($provisioning['panel_snapshot']['managed_stack_candidate'] ?? false)) {
return $provisioning;
}
$provisioning['user_data'] = $this->buildCloudInit(
$order,
$bootstrap,
(string) ($provisioning['user_data'] ?? '')
);
return $provisioning;
}
public function buildCloudInit(CustomerHostingOrder $order, array $bootstrap, string $existingCloudInit = ''): string
{
$parsed = $this->parseGeneratedCloudInit($existingCloudInit);
$release = $this->releaseService->ensureRelease();
$releaseUrl = $this->releaseService->temporaryDownloadUrl($release['version']);
$envFile = implode("\n", [
'ORDER_ID='.$order->id,
'BOOTSTRAP_TOKEN='.$bootstrap['bootstrap_token'],
'REGISTRATION_URL='.$bootstrap['registration_url'],
'HEARTBEAT_URL='.$bootstrap['heartbeat_url'],
'AGENT_RELEASE_VERSION='.$release['version'],
'',
]);
$packages = array_values(array_unique(array_merge($parsed['packages'], [
'curl',
'wget',
'jq',
'ca-certificates',
'git',
'unzip',
'zip',
])));
$runCommands = array_merge([
$this->managedStackInstallCommand($releaseUrl, $release['checksum_sha256']),
'bash -lc \'mkdir -p /etc/ladill /var/lib/ladill-server-agent /var/www /tmp/ladill-server-agent-release\'',
'bash -lc \'cd /tmp/ladill-server-agent-release && bash install.sh\'',
'bash -lc \'chmod 0600 /etc/ladill/server-agent.env\'',
$this->managedStackEnableServicesCommand(),
], $parsed['run_commands']);
return $this->contaboProvider->generateCloudInit([
'packages' => $packages,
'write_files' => [
[
'path' => '/etc/ladill/server-agent.env',
'permissions' => '0600',
'owner' => 'root:root',
'encoding' => 'b64',
'content' => base64_encode($envFile),
],
],
'run_commands' => $runCommands,
]);
}
/**
* @return array{packages: array<int, string>, run_commands: array<int, string>}
*/
private function parseGeneratedCloudInit(string $cloudInit): array
{
$packages = [];
$runCommands = [];
$section = null;
foreach (preg_split("/\r\n|\n|\r/", $cloudInit) ?: [] as $line) {
$trimmed = trim($line);
if ($trimmed === '' || $trimmed === '#cloud-config') {
continue;
}
if (! str_starts_with($line, ' ')) {
$section = match ($trimmed) {
'packages:' => 'packages',
'runcmd:' => 'runcmd',
default => null,
};
continue;
}
if ($section === 'packages' && str_starts_with($trimmed, '- ')) {
$packages[] = trim(substr($trimmed, 2));
}
if ($section === 'runcmd' && str_starts_with($trimmed, '- ')) {
$runCommands[] = trim(substr($trimmed, 2));
}
}
return [
'packages' => array_values(array_filter($packages)),
'run_commands' => array_values(array_filter($runCommands)),
];
}
private function managedStackInstallCommand(string $releaseUrl, string $checksum): string
{
$script = str_replace(
['__RELEASE_URL__', '__CHECKSUM__'],
[$releaseUrl, $checksum],
<<<'BASH'
set -e
detect_pm() {
if command -v apt-get >/dev/null 2>&1; then
echo apt-get
return 0
fi
if command -v dnf >/dev/null 2>&1; then
echo dnf
return 0
fi
if command -v yum >/dev/null 2>&1; then
echo yum
return 0
fi
if command -v zypper >/dev/null 2>&1; then
echo zypper
return 0
fi
return 1
}
install_composer_if_missing() {
if command -v composer >/dev/null 2>&1; then
return 0
fi
curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
rm -f /tmp/composer-setup.php
}
pm="$(detect_pm || true)"
if [ -z "${pm}" ]; then
echo "Ladill managed stack currently supports apt, dnf, yum, or zypper based Linux distributions only." >&2
exit 1
fi
case "${pm}" in
apt-get)
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y nginx mariadb-server php-fpm php-cli php-mysql php-curl php-xml php-zip php-mbstring php-gd php-intl php-bcmath certbot jq curl wget ca-certificates cron composer git unzip zip bubblewrap
;;
dnf)
dnf install -y epel-release || true
dnf install -y nginx mariadb-server php-fpm php-cli php-mysqlnd certbot jq curl wget ca-certificates cronie git unzip zip bubblewrap
dnf install -y php-curl php-xml php-zip php-mbstring php-gd php-intl php-bcmath || true
;;
yum)
yum install -y epel-release || true
yum install -y nginx mariadb-server php-fpm php-cli php-mysqlnd certbot jq curl wget ca-certificates cronie git unzip zip bubblewrap
yum install -y php-curl php-xml php-zip php-mbstring php-gd php-intl php-bcmath || true
;;
zypper)
zypper --non-interactive refresh
zypper --non-interactive install -y nginx mariadb php8 php8-fpm php8-mysql certbot jq curl wget ca-certificates cron git unzip zip bubblewrap
zypper --non-interactive install -y php8-curl php8-xmlreader php8-xmlwriter php8-zip php8-mbstring php8-gd php8-intl php8-bcmath || true
;;
esac
install_composer_if_missing
mkdir -p /etc/ladill /var/lib/ladill-server-agent /var/www /tmp/ladill-server-agent-release
curl -fsSL "__RELEASE_URL__" -o /tmp/ladill-server-agent-release/agent.tar.gz
echo "__CHECKSUM__ /tmp/ladill-server-agent-release/agent.tar.gz" | sha256sum -c -
tar -xzf /tmp/ladill-server-agent-release/agent.tar.gz -C /tmp/ladill-server-agent-release
BASH
);
return 'bash -lc '.escapeshellarg($script);
}
private function managedStackEnableServicesCommand(): string
{
return 'bash -lc '.escapeshellarg(<<<'BASH'
set -e
service_exists() {
systemctl list-unit-files "$1.service" --no-legend 2>/dev/null | grep -q "^$1\.service"
}
enable_if_present() {
for service in "$@"; do
if [ -n "$service" ] && service_exists "$service"; then
systemctl enable --now "$service" >/dev/null 2>&1 || true
return 0
fi
done
return 1
}
chmod 0600 /etc/ladill/server-agent.env
systemctl daemon-reload
enable_if_present nginx
enable_if_present mariadb mysql
enable_if_present cron crond
enable_if_present php-fpm php8-fpm php8.4-fpm php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm
enable_if_present ladill-server-agent
BASH
);
}
}