Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions app/Actions/Server/CheckUpdates.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public function handle(Server $server)
case 'fedora-asahi-remix':
$osType = 'fedora';
break;
case 'nixos':
$osType = 'nixos';
break;
default:
$osType = $osId;
}
Expand All @@ -67,6 +70,7 @@ public function handle(Server $server)
'ubuntu', 'debian', 'raspbian' => 'apt',
'centos', 'fedora', 'rhel', 'ol', 'rocky', 'almalinux', 'amzn' => 'dnf',
'sles', 'opensuse-leap', 'opensuse-tumbleweed' => 'zypper',
'nixos' => 'nixos',
default => null
};

Expand All @@ -93,6 +97,15 @@ public function handle(Server $server)
$out['osId'] = $osId;
$out['package_manager'] = $packageManager;

return $out;
case 'nixos':
instant_remote_process(['nix-channel --update nixos'], $server);
$output = instant_remote_process(['nixos-rebuild dry-build 2>&1'], $server);

$out = $this->parseNixosOutput($output);
$out['osId'] = $osId;
$out['package_manager'] = $packageManager;

return $out;
default:
return [
Expand Down Expand Up @@ -219,4 +232,59 @@ private function parseAptOutput(string $output): array
'updates' => $updates,
];
}

private function parseNixosOutput(string $output): array
{
$updates = [];
$lines = explode("\n", $output);

foreach ($lines as $line) {
if (str_contains($line, 'these') && str_contains($line, 'paths will be fetched')) {
if (preg_match('/these (\d+) paths will be fetched/', $line, $matches)) {
$packageCount = (int) $matches[1];

$updates[] = [
'package' => 'nixos-system',
'new_version' => 'latest-channel',
'current_version' => 'current-channel',
'architecture' => 'system',
'repository' => 'nixos-channel',
'is_system_update' => true,
'package_count' => $packageCount,
'description' => 'NixOS system rebuild with '.$packageCount.' updated packages',
];
}
break;
}
}

if (empty($updates)) {
$hasChanges = false;
foreach ($lines as $line) {
if (str_contains($line, 'building') || str_contains($line, 'fetching') || str_contains($line, 'unpacking')) {
$hasChanges = true;
break;
}
}

if ($hasChanges) {
$updates[] = [
'package' => 'nixos-system',
'new_version' => 'latest-channel',
'current_version' => 'current-channel',
'architecture' => 'system',
'repository' => 'nixos-channel',
'is_system_update' => true,
'package_count' => 'unknown',
'description' => 'NixOS system rebuild with updates available',
];
}
}

return [
'total_updates' => count($updates),
'updates' => $updates,
'is_nixos' => true,
];
}
}
28 changes: 28 additions & 0 deletions app/Actions/Server/InstallDocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public function handle(Server $server)
$command = $command->merge([$this->getRhelDockerInstallCommand()]);
} elseif ($supported_os_type->contains('sles')) {
$command = $command->merge([$this->getSuseDockerInstallCommand()]);
} elseif ($supported_os_type->contains('nixos')) {
$command = $command->merge([$this->getNixosDockerInstallCommand()]);
} else {
$command = $command->merge([$this->getGenericDockerInstallCommand()]);
}
Expand Down Expand Up @@ -150,4 +152,30 @@ private function getGenericDockerInstallCommand(): string
{
return "curl https://releases.rancher.com/install-docker/{$this->dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$this->dockerVersion}";
}

private function getNixosDockerInstallCommand(): string
{
return "echo 'NixOS Docker Configuration Guide:' && ".
"echo '' && ".
"echo 'To use Coolify with NixOS, you need to add Docker to your configuration.nix file:' && ".
"echo '' && ".
"echo 'virtualisation.docker = {' && ".
"echo ' enable = true;' && ".
"echo ' enableOnBoot = true;' && ".
"echo ' autoPrune.enable = true;' && ".
"echo '};' && ".
"echo '' && ".
"echo 'Also add these packages to your environment.systemPackages:' && ".
"echo ' [' && ".
"echo ' docker' && ".
"echo ' docker-compose' && ".
"echo ' git' && ".
"echo ' jq' && ".
"echo ' ]' && ".
"echo '' && ".
"echo 'After updating your configuration.nix, run:' && ".
"echo ' sudo nixos-rebuild switch' && ".
"echo '' && ".
"echo 'Then click the \"Retry\" button in Coolify to continue validation.'";
}
}
4 changes: 4 additions & 0 deletions app/Actions/Server/UpdatePackage.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public function handle(Server $server, string $osId, ?string $package = null, ?s
$commandAll = 'apt update && apt upgrade -y';
$commandInstall = 'apt install -y '.$package;
break;
case 'nixos':
$commandAll = 'nix-channel --update nixos && nixos-rebuild switch';
$commandInstall = 'nix-channel --update nixos && nixos-rebuild switch';
break;
default:
return [
'error' => 'OS not supported',
Expand Down
20 changes: 18 additions & 2 deletions app/Livewire/Server/ValidateAndInstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ public function validateOS()

return;
}

if ($this->supported_os_type->contains('nixos')) {
$this->error = 'NixOS detected! Please ensure Docker is configured in your NixOS configuration before continuing. See the Docker installation step for detailed instructions.';
$this->server->update([
'validation_logs' => $this->error,
]);
}

$this->dispatch('validatePrerequisites');
}

Expand Down Expand Up @@ -157,7 +165,11 @@ public function validateDockerEngine()
if (! $this->docker_installed || ! $this->docker_compose_installed) {
if ($this->install) {
if ($this->number_of_tries == $this->max_tries) {
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
if ($this->supported_os_type && $this->supported_os_type->contains('nixos')) {
$this->error = 'Docker is not installed on your NixOS system. Please follow the NixOS Docker configuration guide shown in the installation logs, then run `sudo nixos-rebuild switch` and click "Retry".';
} else {
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
}
$this->server->update([
'validation_logs' => $this->error,
]);
Expand All @@ -174,7 +186,11 @@ public function validateDockerEngine()
return;
}
} else {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
if ($this->supported_os_type && $this->supported_os_type->contains('nixos')) {
$this->error = 'Docker is not installed on your NixOS system. Please configure Docker in your configuration.nix file and run `sudo nixos-rebuild switch`.';
} else {
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
}
$this->server->update([
'validation_logs' => $this->error,
]);
Expand Down
1 change: 1 addition & 0 deletions bootstrap/helpers/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
'sles opensuse-leap opensuse-tumbleweed',
'arch',
'alpine',
'nixos',
];

const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
47 changes: 40 additions & 7 deletions resources/views/livewire/server/security/patches.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<h2>Server Patching</h2>
<span class="text-xs text-neutral-500">(experimental)</span>
<x-helper
helper="Only available for apt, dnf and zypper package managers atm, more coming
helper="Only available for apt, dnf, zypper, and nixos package managers atm, more coming
soon.<br/>Status notifications sent every week.<br/>You can disable notifications in the <a class='dark:text-white underline' href='{{ route('notifications.email') }}'>notification settings</a>." />
@if (isDev())
<x-forms.button type="button" wire:click="sendTestEmail">
Expand All @@ -43,13 +43,33 @@
@endif
@if (isset($updates) && count($updates) > 0)
<div class="pb-2">
@if ($packageManager === 'nixos')
<div class="mb-4 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<div class="flex items-center gap-2 mb-2">
<svg class="w-5 h-5 text-yellow-600 dark:text-yellow-400" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"/>
</svg>
<h3 class="font-medium text-yellow-800 dark:text-yellow-200">NixOS System Update Notice</h3>
</div>
<div class="text-sm text-yellow-700 dark:text-yellow-300">
<p class="mb-2">NixOS uses atomic system-wide updates. This will:</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>Update the entire system configuration atomically</li>
<li>Rebuild the system with latest channel packages</li>
<li>May require system reboot</li>
<li>Could temporarily interrupt running services</li>
</ul>
<p class="mt-2 font-medium">Ensure you have backups and maintenance window available.</p>
</div>
</div>
@endif
<x-modal-confirmation title="Confirm package update?"
buttonTitle="Update All
Packages"
isHighlightedButton submitAction="updateAllPackages" dispatchAction
:actions="[
'All packages will be updated to the latest version.',
'This action could restart your currently running containers if docker will be updated.',
$packageManager === 'nixos' ? 'NixOS will perform atomic system rebuild - services may be interrupted.' : 'This action could restart your currently running containers if docker will be updated.',
]" confirmationText="Update All Packages"
confirmationLabel="Please confirm the execution of the actions by entering the name below"
shortConfirmationLabel="Name" :confirmWithPassword=false
Expand All @@ -70,8 +90,8 @@
<tr>
<td>
<div class="flex gap-2 items-center">
@if (data_get_str($update, 'package')->contains('docker') || data_get_str($update, 'package')->contains('kernel'))
<x-helper :helper="'This package will restart your currently running containers'">
@if (data_get_str($update, 'package')->contains('docker') || data_get_str($update, 'package')->contains('kernel') || data_get($update, 'is_system_update'))
<x-helper :helper="data_get($update, 'is_system_update') ? 'This is a system-wide update that may interrupt services' : 'This package will restart your currently running containers'">
<x-slot:icon>
<svg class="w-4 h-4 text-red-500 block flex-shrink-0"
viewBox="0 0 256 256"
Expand All @@ -83,7 +103,12 @@
</x-slot:icon>
</x-helper>
@endif
<span class="break-all">{{ data_get($update, 'package') }}</span>
<div>
<span class="break-all">{{ data_get($update, 'package') }}</span>
@if (data_get($update, 'description'))
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ data_get($update, 'description') }}</div>
@endif
</div>
</div>
</td>
<td class="whitespace-nowrap">
Expand All @@ -92,11 +117,19 @@
@if ($packageManager !== 'dnf' && data_get($update, 'current_version'))
<x-helper helper="Current: {{ data_get($update, 'current_version') }}" />
@endif
@if (data_get($update, 'package_count') && data_get($update, 'package_count') !== 'unknown')
<span class="text-xs text-gray-500">({{ data_get($update, 'package_count') }} packages)</span>
@endif
</div>
</td>
<td class="whitespace-nowrap">
<x-forms.button type="button"
wire:click="$dispatch('updatePackage', { package: '{{ data_get($update, 'package') }}' })">Update</x-forms.button>
@if ($packageManager === 'nixos')
<x-forms.button type="button"
wire:click="$dispatch('updateAllPackages')">Update System</x-forms.button>
@else
<x-forms.button type="button"
wire:click="$dispatch('updatePackage', { package: '{{ data_get($update, 'package') }}' })">Update</x-forms.button>
@endif
</td>
</tr>
@endforeach
Expand Down
Loading