Compare commits

..

No commits in common. "develop" and "v4.2.2-rc.2" have entirely different histories.

10 changed files with 65 additions and 234 deletions

View file

@ -25,34 +25,8 @@ jobs:
- name: Create Release Archive
run: |
# Array of files and directories to remove
files_to_remove=(
"node_modules/"
"tests/"
"CODE_OF_CONDUCT.md"
"CONTRIBUTOR_LICENSE_AGREEMENT"
"crowdin.yml"
"docker-compose.ci.yml"
"phpstan.neon"
"phpunit.xml"
"stats.html"
)
# Loop over the files to remove and delete them
rm -rf "${files_to_remove[@]}"
# Array of specific dot files to include
files_to_include=(
".editorconfig"
".env.example"
".gitattributes"
".gitignore"
".prettierignore"
".prettierrc.json"
)
# Archive files, using * directly outside the array for proper expansion
tar --exclude=panel.tar.gz -czf panel.tar.gz * "${files_to_include[@]}"
rm -rf node_modules/ tests/ CODE_OF_CONDUCT.md CONTRIBUTOR_LICENSE_AGREEMENT crowdin.yml docker-compose.ci.yml phpstan.neon phpunit.xml
tar -czf panel.tar.gz *
- name: Extract Changelog
id: extract_changelog

3
.gitignore vendored
View file

@ -18,5 +18,4 @@ yarn-error.log
_ide_*.php
stats.html
.fleet
lang/php_*.json
.phpunit.cache
lang/php_*.json

View file

@ -4,37 +4,6 @@ This file is a running track of new features and fixes to each version of the pa
This project follows [Semantic Versioning](http://semver.org) guidelines.
## v4.2.4
### Changes
- Fixed a bug where initiating a backup with no compression fails. #87
## v4.2.3
### Changes
- Updated code for applying rate limits to NIC. Convoy will no longer override settings other than ratelimit, NIC
model (e.g., e1000, vmxnet3, virtio, etc.), and firewall status.
#### From v4.2.2-rc.2
- Fix US keyboard characters validation #80
- Fixed a visual bug on the bandwidth usage card where the text wasn't centered.
#### From v4.2.2-rc.1
- Fix special character support in environment file.
- Added checks in server creation to use unique VMID. #78
- Add error messages instead of generic server error messages. #49
- Scope route model binding by default to prevent unauthorized access of related resources.
- Removed a lot of dead code.
- Added more tests (getting closer to full release! 😁😩).
#### From v4.2.1-rc.1
- Potential fix for disk resize timeout?
## v4.2.2-rc.2
### Changes

View file

@ -25,6 +25,6 @@ Please [visit this page](https://convoypanel.com/docs/project/about.html#acknowl
## License
Convoy is licensed under our own proprietary license.
Convoy is licensed under the Business Source License. Production use of Convoy without an active license from Performave is strictly disallowed.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FConvoyPanel%2Fpanel.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FConvoyPanel%2Fpanel?ref=badge_large)

View file

@ -2,12 +2,12 @@
namespace Convoy\Repositories\Proxmox\Server;
use Convoy\Enums\Server\BackupCompressionType;
use Convoy\Enums\Server\BackupMode;
use Convoy\Models\Backup;
use Convoy\Models\Server;
use Convoy\Repositories\Proxmox\ProxmoxRepository;
use Webmozart\Assert\Assert;
use Convoy\Enums\Server\BackupMode;
use Convoy\Enums\Server\BackupCompressionType;
use Convoy\Repositories\Proxmox\ProxmoxRepository;
class ProxmoxBackupRepository extends ProxmoxRepository
{
@ -16,15 +16,15 @@ class ProxmoxBackupRepository extends ProxmoxRepository
Assert::isInstanceOf($this->server, Server::class);
$response = $this->getHttpClient()
->withUrlParameters([
'node' => $this->node->cluster,
'storage' => $this->node->backup_storage,
])
->get('/api2/json/nodes/{node}/storage/{storage}/content', [
'content' => 'backup',
'vmid' => $this->server->vmid,
])
->json();
->withUrlParameters([
'node' => $this->node->cluster,
'storage' => $this->node->backup_storage,
])
->get('/api2/json/nodes/{node}/storage/{storage}/content', [
'content' => 'backup',
'vmid' => $this->server->vmid,
])
->json();
return $this->getData($response);
}
@ -43,16 +43,16 @@ class ProxmoxBackupRepository extends ProxmoxRepository
}
$response = $this->getHttpClient()
->withUrlParameters([
'node' => $this->node->cluster,
])
->post('/api2/json/nodes/{node}/vzdump', [
'vmid' => $this->server->vmid,
'storage' => $this->node->backup_storage,
'mode' => $parsedMode,
'compress' => $compressionType === BackupCompressionType::NONE ? (int)false : $compressionType->value,
])
->json();
->withUrlParameters([
'node' => $this->node->cluster,
])
->post('/api2/json/nodes/{node}/vzdump', [
'vmid' => $this->server->vmid,
'storage' => $this->node->backup_storage,
'mode' => $parsedMode,
'compress' => $compressionType === BackupCompressionType::NONE ? false : $compressionType->value,
])
->json();
return $this->getData($response);
}
@ -62,15 +62,15 @@ class ProxmoxBackupRepository extends ProxmoxRepository
Assert::isInstanceOf($this->server, Server::class);
$response = $this->getHttpClient()
->withUrlParameters([
'node' => $this->node->cluster,
])
->post('/api2/json/nodes/{node}/qemu', [
'vmid' => $this->server->vmid,
'force' => true,
'archive' => "{$this->node->backup_storage}:backup/{$backup->file_name}",
])
->json();
->withUrlParameters([
'node' => $this->node->cluster,
])
->post('/api2/json/nodes/{node}/qemu', [
'vmid' => $this->server->vmid,
'force' => true,
'archive' => "{$this->node->backup_storage}:backup/{$backup->file_name}",
])
->json();
return $this->getData($response);
}
@ -80,13 +80,13 @@ class ProxmoxBackupRepository extends ProxmoxRepository
Assert::isInstanceOf($this->server, Server::class);
$response = $this->getHttpClient()
->withUrlParameters([
'node' => $this->node->cluster,
'storage' => $this->node->backup_storage,
'backup' => "{$this->node->backup_storage}:backup/{$backup->file_name}",
])
->delete('/api2/json/nodes/{node}/storage/{storage}/content/{backup}')
->json();
->withUrlParameters([
'node' => $this->node->cluster,
'storage' => $this->node->backup_storage,
'backup' => "{$this->node->backup_storage}:backup/{$backup->file_name}",
])
->delete('/api2/json/nodes/{node}/storage/{storage}/content/{backup}')
->json();
return $this->getData($response);
}

View file

@ -3,12 +3,12 @@
namespace Convoy\Repositories\Proxmox\Server;
use Convoy\Models\Server;
use Convoy\Repositories\Proxmox\ProxmoxRepository;
use Webmozart\Assert\Assert;
use Convoy\Repositories\Proxmox\ProxmoxRepository;
class ProxmoxConfigRepository extends ProxmoxRepository
{
public function getConfig(): array
public function getConfig()
{
Assert::isInstanceOf($this->server, Server::class);

View file

@ -12,9 +12,8 @@ use Convoy\Repositories\Eloquent\AddressRepository;
use Convoy\Repositories\Proxmox\Server\ProxmoxCloudinitRepository;
use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository;
use Convoy\Repositories\Proxmox\Server\ProxmoxFirewallRepository;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Arr;
use function collect;
use function is_null;
class NetworkService
{
@ -24,7 +23,9 @@ class NetworkService
private CloudinitService $cloudinitService,
private ProxmoxCloudinitRepository $cloudinitRepository,
private ProxmoxConfigRepository $allocationRepository,
) {
private ConnectionInterface $connection,
)
{
}
public function deleteIpset(Server $server, string $name)
@ -40,7 +41,7 @@ class NetworkService
return $this->firewallRepository->deleteIpset($name);
}
public function clearIpsets(Server $server): void
public function clearIpsets(Server $server)
{
$this->firewallRepository->setServer($server);
@ -51,7 +52,7 @@ class NetworkService
}
}
public function lockIps(Server $server, array $addresses, string $ipsetName): void
public function lockIps(Server $server, array $addresses, string $ipsetName)
{
$this->firewallRepository->setServer($server);
@ -62,7 +63,7 @@ class NetworkService
}
}
public function getMacAddresses(Server $server, bool $eloquent = true, bool $proxmox = false): MacAddressData
public function getMacAddresses(Server $server, bool $eloquent = true, bool $proxmox = false)
{
if ($eloquent) {
$addresses = $this->getAddresses($server);
@ -77,8 +78,7 @@ class NetworkService
$proxmoxMacAddress = null;
if (preg_match(
"/\b[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\b/su",
Arr::get($config, 'net0', ''),
$matches,
Arr::get($config, 'net0', ''), $matches,
)) {
$proxmoxMacAddress = $matches[0];
}
@ -102,7 +102,7 @@ class NetworkService
]);
}
public function syncSettings(Server $server): void
public function syncSettings(Server $server)
{
$macAddresses = $this->getMacAddresses($server, true, true);
$addresses = $this->getAddresses($server);
@ -113,8 +113,7 @@ class NetworkService
'ipv6' => $addresses->ipv6->first()?->toArray(),
]));
$this->lockIps(
$server,
array_unique(Arr::flatten($server->addresses()->get(['address'])->toArray())),
$server, array_unique(Arr::flatten($server->addresses()->get(['address'])->toArray())),
'ipfilter-net0',
);
$this->firewallRepository->setServer($server)->updateOptions([
@ -131,119 +130,27 @@ class NetworkService
);
}
public function updateRateLimit(Server $server, ?int $mebibytes = null): void
public function updateRateLimit(Server $server, ?int $mebibytes = null)
{
$macAddresses = $this->getMacAddresses($server, true, true);
$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;
$rawConfig = $this->allocationRepository->setServer($server)->getConfig();
$networkConfig = collect($rawConfig)->where('key', '=', 'net0')->first();
if (is_null($networkConfig)) {
return;
$payload = "virtio={$macAddress},bridge={$server->node->network},firewall=1";
if (!is_null($mebibytes)) {
$payload .= ',rate=' . $mebibytes;
}
$parsedConfig = $this->parseConfig($networkConfig['value']);
// List of possible models
$models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'e1000e', 'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3'];
// Update the model with the new MAC address
$modelFound = false;
foreach ($parsedConfig as $item) {
if (in_array($item->key, $models)) {
$item->value = $macAddress;
$modelFound = true;
break;
}
}
// If no model key exists, add the default model with the MAC address
if (!$modelFound) {
$parsedConfig[] = (object) ['key' => 'virtio', 'value' => $macAddress];
}
// Update or create the bridge value
$bridgeFound = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'bridge') {
$item->value = $server->node->network;
$bridgeFound = true;
break;
}
}
if (!$bridgeFound) {
$parsedConfig[] = (object) ['key' => 'bridge', 'value' => $server->node->network];
}
// Update or create the firewall key
$firewallFound = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'firewall') {
$item->value = 1;
$firewallFound = true;
break;
}
}
if (!$firewallFound) {
$parsedConfig[] = (object) ['key' => 'firewall', 'value' => 1];
}
// Handle the rate limit
if (is_null($mebibytes)) {
// Remove the 'rate' key if $mebibytes is null
$parsedConfig = array_filter($parsedConfig, fn ($item) => $item->key !== 'rate');
} else {
// Add or update the 'rate' key
$rateUpdated = false;
foreach ($parsedConfig as $item) {
if ($item->key === 'rate') {
$item->value = $mebibytes;
$rateUpdated = true;
break;
}
}
if (!$rateUpdated) {
$parsedConfig[] = (object) ['key' => 'rate', 'value' => $mebibytes];
}
}
// Rebuild the configuration string
$newConfig = implode(',', array_map(fn ($item) => "{$item->key}={$item->value}", $parsedConfig));
// Update the Proxmox configuration
$this->allocationRepository->setServer($server)->update(['net0' => $newConfig]);
$this->allocationRepository->setServer($server)->update(['net0' => $payload]);
}
private function parseConfig(string $config): array
{
// Split components by commas
$components = explode(',', $config);
// Array to hold the parsed objects
$parsedObjects = [];
foreach ($components as $component) {
// Split each component into key and value
[$key, $value] = explode('=', $component);
// Create an associative array (or object) for key-value pairs
$parsedObjects[] = (object) ['key' => $key, 'value' => $value];
}
return $parsedObjects;
}
public function updateAddresses(Server $server, array $addressIds): void
public function updateAddresses(Server $server, array $addressIds)
{
$currentAddresses = $server->addresses()->get()->pluck('id')->toArray();
$addressesToAdd = array_diff($addressIds, $currentAddresses);
$addressesToRemove = array_filter(
$currentAddresses,
fn ($id) => !in_array($id, $addressIds),
$currentAddresses, fn ($id) => !in_array($id, $addressIds),
);
if (!empty($addressesToAdd)) {

View file

@ -31,7 +31,7 @@ class ServerSuspensionService
]);
try {
$this->powerRepository->setServer($server)->send($isSuspending ? PowerAction::KILL : PowerAction::START);
$this->powerRepository->setServer($server)->send(PowerAction::KILL);
} catch (Exception $exception) {
$server->update([
'status' => $isSuspending ? null : Status::SUSPENDED->value,

2
package-lock.json generated
View file

@ -1,5 +1,5 @@
{
"name": "panel",
"name": "www",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -6,25 +6,7 @@ use Illuminate\Support\Facades\Http;
it('can rate limit servers if over limit', function () {
Http::fake([
'/api2/json/nodes/*/qemu/*/config' => Http::sequence()
->push(
file_get_contents(
base_path(
'tests/Fixtures/Repositories/Server/GetServerConfigData.json',
),
),
200
)
->push(
file_get_contents(
base_path(
'tests/Fixtures/Repositories/Server/GetServerConfigData.json',
),
),
200,
)
->push(['data' => 'dummy-upid'], 200)
'*' => Http::response(['data' => 'dummy-upid'], 200),
]);
[$_, $_, $node, $server] = createServerModel();