Compare commits
32 commits
v4.2.2-rc.
...
develop
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bdc9c413e2 | ||
![]() |
2b16841702 | ||
![]() |
1d6390ddd6 | ||
![]() |
3eb3c7e746 | ||
![]() |
b47adacb95 | ||
![]() |
bc6f9e4c42 | ||
![]() |
3643a717e9 | ||
![]() |
a9214aa820 | ||
![]() |
561aca99df | ||
![]() |
2841c09510 | ||
![]() |
bdaf07a0dc | ||
![]() |
b3b96ee361 | ||
![]() |
4558b31385 | ||
![]() |
98bf2ec95c | ||
![]() |
7957141128 | ||
![]() |
7380d50fc2 | ||
![]() |
c780ca0218 | ||
![]() |
63ee5f2a2b | ||
![]() |
75f81a9b4e | ||
![]() |
f1fda8ba0a | ||
![]() |
1bf95fa2bd | ||
![]() |
a14d754e79 | ||
![]() |
a19a5d5907 | ||
![]() |
a368951705 | ||
![]() |
93b641be86 | ||
![]() |
80672e249e | ||
![]() |
8fe890a5e1 | ||
![]() |
27e9e821b6 | ||
![]() |
31f93264a5 | ||
![]() |
290b1c34ca | ||
![]() |
db40a05daf | ||
![]() |
504db753dc |
10 changed files with 234 additions and 65 deletions
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
|
@ -25,8 +25,34 @@ jobs:
|
|||
|
||||
- name: Create Release Archive
|
||||
run: |
|
||||
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 *
|
||||
# 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[@]}"
|
||||
|
||||
- name: Extract Changelog
|
||||
id: extract_changelog
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -18,4 +18,5 @@ yarn-error.log
|
|||
_ide_*.php
|
||||
stats.html
|
||||
.fleet
|
||||
lang/php_*.json
|
||||
lang/php_*.json
|
||||
.phpunit.cache
|
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -4,6 +4,37 @@ 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
|
||||
|
|
|
@ -25,6 +25,6 @@ Please [visit this page](https://convoypanel.com/docs/project/about.html#acknowl
|
|||
|
||||
## License
|
||||
|
||||
Convoy is licensed under the Business Source License. Production use of Convoy without an active license from Performave is strictly disallowed.
|
||||
Convoy is licensed under our own proprietary license.
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FConvoyPanel%2Fpanel?ref=badge_large)
|
||||
|
|
|
@ -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 Webmozart\Assert\Assert;
|
||||
use Convoy\Enums\Server\BackupMode;
|
||||
use Convoy\Enums\Server\BackupCompressionType;
|
||||
use Convoy\Repositories\Proxmox\ProxmoxRepository;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
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 ? 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 ? (int)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);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
namespace Convoy\Repositories\Proxmox\Server;
|
||||
|
||||
use Convoy\Models\Server;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Convoy\Repositories\Proxmox\ProxmoxRepository;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class ProxmoxConfigRepository extends ProxmoxRepository
|
||||
{
|
||||
public function getConfig()
|
||||
public function getConfig(): array
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ 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
|
||||
{
|
||||
|
@ -23,9 +24,7 @@ class NetworkService
|
|||
private CloudinitService $cloudinitService,
|
||||
private ProxmoxCloudinitRepository $cloudinitRepository,
|
||||
private ProxmoxConfigRepository $allocationRepository,
|
||||
private ConnectionInterface $connection,
|
||||
)
|
||||
{
|
||||
) {
|
||||
}
|
||||
|
||||
public function deleteIpset(Server $server, string $name)
|
||||
|
@ -41,7 +40,7 @@ class NetworkService
|
|||
return $this->firewallRepository->deleteIpset($name);
|
||||
}
|
||||
|
||||
public function clearIpsets(Server $server)
|
||||
public function clearIpsets(Server $server): void
|
||||
{
|
||||
$this->firewallRepository->setServer($server);
|
||||
|
||||
|
@ -52,7 +51,7 @@ class NetworkService
|
|||
}
|
||||
}
|
||||
|
||||
public function lockIps(Server $server, array $addresses, string $ipsetName)
|
||||
public function lockIps(Server $server, array $addresses, string $ipsetName): void
|
||||
{
|
||||
$this->firewallRepository->setServer($server);
|
||||
|
||||
|
@ -63,7 +62,7 @@ class NetworkService
|
|||
}
|
||||
}
|
||||
|
||||
public function getMacAddresses(Server $server, bool $eloquent = true, bool $proxmox = false)
|
||||
public function getMacAddresses(Server $server, bool $eloquent = true, bool $proxmox = false): MacAddressData
|
||||
{
|
||||
if ($eloquent) {
|
||||
$addresses = $this->getAddresses($server);
|
||||
|
@ -78,7 +77,8 @@ 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)
|
||||
public function syncSettings(Server $server): void
|
||||
{
|
||||
$macAddresses = $this->getMacAddresses($server, true, true);
|
||||
$addresses = $this->getAddresses($server);
|
||||
|
@ -113,7 +113,8 @@ 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([
|
||||
|
@ -130,27 +131,119 @@ class NetworkService
|
|||
);
|
||||
}
|
||||
|
||||
public function updateRateLimit(Server $server, ?int $mebibytes = null)
|
||||
public function updateRateLimit(Server $server, ?int $mebibytes = null): void
|
||||
{
|
||||
$macAddresses = $this->getMacAddresses($server, true, true);
|
||||
$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;
|
||||
$rawConfig = $this->allocationRepository->setServer($server)->getConfig();
|
||||
$networkConfig = collect($rawConfig)->where('key', '=', 'net0')->first();
|
||||
|
||||
$payload = "virtio={$macAddress},bridge={$server->node->network},firewall=1";
|
||||
|
||||
if (!is_null($mebibytes)) {
|
||||
$payload .= ',rate=' . $mebibytes;
|
||||
if (is_null($networkConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->allocationRepository->setServer($server)->update(['net0' => $payload]);
|
||||
$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]);
|
||||
}
|
||||
|
||||
public function updateAddresses(Server $server, array $addressIds)
|
||||
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
|
||||
{
|
||||
$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)) {
|
||||
|
|
|
@ -31,7 +31,7 @@ class ServerSuspensionService
|
|||
]);
|
||||
|
||||
try {
|
||||
$this->powerRepository->setServer($server)->send(PowerAction::KILL);
|
||||
$this->powerRepository->setServer($server)->send($isSuspending ? PowerAction::KILL : PowerAction::START);
|
||||
} catch (Exception $exception) {
|
||||
$server->update([
|
||||
'status' => $isSuspending ? null : Status::SUSPENDED->value,
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "www",
|
||||
"name": "panel",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
@ -6,7 +6,25 @@ use Illuminate\Support\Facades\Http;
|
|||
|
||||
it('can rate limit servers if over limit', function () {
|
||||
Http::fake([
|
||||
'*' => Http::response(['data' => 'dummy-upid'], 200),
|
||||
'/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)
|
||||
|
||||
]);
|
||||
|
||||
[$_, $_, $node, $server] = createServerModel();
|
||||
|
|
Loading…
Add table
Reference in a new issue