mirror of
https://gitlab.com/ArkHost/WHMCS-ArkHost-HetznerVPS.git
synced 2026-07-24 07:45:53 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc9d8a2e1c | ||
|
|
0f96849032 | ||
|
|
cc60ca7103 |
@@ -12,6 +12,7 @@ WHMCS server module for Hetzner Cloud VPS management.
|
||||
|
||||
**Advanced**
|
||||
|
||||
- Cloud-init support for automated server configuration
|
||||
- Floating IP management with reverse DNS
|
||||
- Backup creation and restoration
|
||||
- Firewall rule management
|
||||
@@ -55,11 +56,12 @@ WHMCS server module for Hetzner Cloud VPS management.
|
||||
|
||||
**Module Settings**
|
||||
|
||||
- Server Type: `cx11`, `cx21`, `cx31`, `cx41`, `cx51` , etc.
|
||||
- Operating System: `ubuntu-20.04`, `debian-11`, etc.
|
||||
- Datacenter: `fsn1`, `nbg1`, `hel1`, `ash`, `hil`
|
||||
- Server Type: `cx23`, `cx33`, `cx43`, `cx53` (Cost-Optimized), `cpx11`, `cpx21`, `cax11`, `ccx13`, etc.
|
||||
- Operating System: `ubuntu-22.04`, `debian-11`, `rocky-9`, etc.
|
||||
- Datacenter: `fsn1`, `nbg1`, `hel1`, `ash`, `hil`, `sin`
|
||||
- Backups: On/Off
|
||||
- Create Floating IP: On/Off
|
||||
- Cloud-Init YAML: Optional custom cloud-init configuration
|
||||
|
||||
**Custom Field (Required)**
|
||||
|
||||
@@ -98,6 +100,10 @@ Create in Setup → Products/Services → Configurable Options:
|
||||
- Option Name: `planid`
|
||||
- Option Type: `Dropdown`
|
||||
- Options:
|
||||
- `cx23|CX23 - 2 vCPU, 4 GB RAM, 40 GB Disk, shared (Cost-Optimized)`
|
||||
- `cx33|CX33 - 4 vCPU, 8 GB RAM, 80 GB Disk, shared (Cost-Optimized)`
|
||||
- `cx43|CX43 - 8 vCPU, 16 GB RAM, 160 GB Disk, shared (Cost-Optimized)`
|
||||
- `cx53|CX53 - 16 vCPU, 32 GB RAM, 320 GB Disk, shared (Cost-Optimized)`
|
||||
- `cpx11|CPX 11 - 2 vCPU, 2 GB RAM, 40 GB Disk, shared`
|
||||
- `cpx21|CPX 21 - 3 vCPU, 4 GB RAM, 80 GB Disk, shared`
|
||||
- `cpx31|CPX 31 - 4 vCPU, 8 GB RAM, 160 GB Disk, shared`
|
||||
@@ -121,34 +127,36 @@ Create in Setup → Products/Services → Configurable Options:
|
||||
|
||||
**4\. Operating System**
|
||||
|
||||
- Option Name: `osid`
|
||||
- Option Type: `Dropdown`
|
||||
- Option Name: `osid`
|
||||
- Option Type: `Dropdown`
|
||||
- Options:
|
||||
- `lamp|lamp`
|
||||
-`wordpress|wordpress`
|
||||
-`jitsi|jitsi`
|
||||
-`nextcloud|nextcloud`
|
||||
- `docker-ce|docker-ce`
|
||||
- `gitlab|gitlab`
|
||||
- `debian-11|Debian 11`
|
||||
- `rocky-8|Rocky Linux 8`
|
||||
- `centos-stream-9|CentOS Stream 9`
|
||||
- `debian-12|Debian 12`
|
||||
- `debian-13|Debian 13`
|
||||
- `ubuntu-22.04|Ubuntu 22.04`
|
||||
- `prometheus-grafana|prometheus-grafana`
|
||||
- `rocky-9|Rocky Linux 9`
|
||||
- `wireguard|wireguard`
|
||||
- `owncast|owncast`
|
||||
- `photoprism|photoprism`
|
||||
- `rustdesk|rustdesk`
|
||||
- `centos-stream-9|CentOS Stream 9`
|
||||
- `debian-11|Debian 11`
|
||||
- `ubuntu-24.04|Ubuntu 24.04`
|
||||
- `rocky-8|Rocky Linux 8`
|
||||
- `rocky-9|Rocky Linux 9`
|
||||
- `ubuntu-22.04|Ubuntu 22.04`
|
||||
- `rocky-10|Rocky Linux 10`
|
||||
- `alma-8|AlmaLinux 8`
|
||||
- `alma-9|AlmaLinux 9`
|
||||
- `jitsi|jitsi`
|
||||
- `owncast|owncast`
|
||||
- `alma-10|AlmaLinux 10`
|
||||
- `centos-stream-9|CentOS Stream 9`
|
||||
- `centos-stream-10|CentOS Stream 10`
|
||||
- `opensuse-15|openSUSE 15`
|
||||
- `fedora-41|Fedora 41`
|
||||
- `fedora-42|Fedora 42`
|
||||
- `docker-ce|Docker CE`
|
||||
- `lamp|LAMP Stack`
|
||||
- `wordpress|WordPress`
|
||||
- `nextcloud|Nextcloud`
|
||||
- `gitlab|GitLab`
|
||||
- `jitsi|Jitsi`
|
||||
- `wireguard|WireGuard`
|
||||
- `prometheus-grafana|Prometheus + Grafana`
|
||||
- `owncast|Owncast`
|
||||
- `photoprism|PhotoPrism`
|
||||
- `rustdesk|RustDesk`
|
||||
|
||||
|
||||
|
||||
@@ -183,6 +191,102 @@ This ensures proper key isolation and security for each customer.
|
||||
|
||||
We're exploring secure implementations using cloud-init or per-server user data to enable SSH key management while maintaining proper isolation.
|
||||
|
||||
## Cloud-Init Support
|
||||
|
||||
Cloud-init allows automatic server configuration during first boot. Unlike project-level SSH keys, cloud-init user_data is per-server, ensuring proper isolation in multi-tenant environments.
|
||||
|
||||
**Configuration:**
|
||||
|
||||
1. Navigate to Setup → Products/Services → Products/Services
|
||||
2. Edit your product → Module Settings tab
|
||||
3. Find "Cloud-Init YAML (Optional)" textarea
|
||||
4. Enter your cloud-init configuration in YAML format
|
||||
5. Leave empty to skip cloud-init
|
||||
|
||||
**Common Use Cases:**
|
||||
|
||||
**1. Change APT Mirrors** (original use case - avoid Hetzner mirrors):
|
||||
```yaml
|
||||
#cloud-config
|
||||
apt:
|
||||
primary:
|
||||
- arches: [default]
|
||||
uri: http://de.archive.ubuntu.com/ubuntu/
|
||||
```
|
||||
|
||||
**2. Add SSH Keys** (secure alternative to project-level keys):
|
||||
```yaml
|
||||
#cloud-config
|
||||
users:
|
||||
- name: admin
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2E... user@laptop
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
shell: /bin/bash
|
||||
```
|
||||
|
||||
**3. Install Docker:**
|
||||
```yaml
|
||||
#cloud-config
|
||||
packages:
|
||||
- docker.io
|
||||
- docker-compose
|
||||
|
||||
runcmd:
|
||||
- systemctl enable docker
|
||||
- systemctl start docker
|
||||
```
|
||||
|
||||
**4. Security Hardening:**
|
||||
```yaml
|
||||
#cloud-config
|
||||
packages:
|
||||
- fail2ban
|
||||
- ufw
|
||||
|
||||
runcmd:
|
||||
- ufw default deny incoming
|
||||
- ufw default allow outgoing
|
||||
- ufw allow 22/tcp
|
||||
- ufw --force enable
|
||||
- systemctl enable fail2ban
|
||||
- systemctl start fail2ban
|
||||
```
|
||||
|
||||
**5. Combined Configuration:**
|
||||
```yaml
|
||||
#cloud-config
|
||||
apt:
|
||||
primary:
|
||||
- arches: [default]
|
||||
uri: http://mirror.example.com/ubuntu/
|
||||
|
||||
users:
|
||||
- name: admin
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3... admin@company
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
|
||||
packages:
|
||||
- fail2ban
|
||||
- ufw
|
||||
- docker.io
|
||||
|
||||
runcmd:
|
||||
- ufw allow 22/tcp
|
||||
- ufw --force enable
|
||||
- systemctl enable docker fail2ban
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
|
||||
- YAML must be valid syntax (use a YAML validator if unsure)
|
||||
- If you don't start with `#cloud-config`, the module will add it automatically
|
||||
- Cloud-init runs ONCE on first boot only
|
||||
- Invalid YAML will cause provisioning to fail silently
|
||||
- Maximum size: 32 KiB (Hetzner API limit)
|
||||
- Documentation: https://docs.hetzner.cloud/#servers-create-a-server
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||
@@ -234,5 +338,3 @@ Free and open source. Commercial support available.
|
||||
Check out our other WHMCS modules at [arkhost.com/whmcs-modules.php](https://arkhost.com/whmcs-modules.php "https://arkhost.com/whmcs-modules.php")
|
||||
|
||||
© 2025 ArkHost
|
||||
|
||||
<br>
|
||||
|
||||
@@ -116,12 +116,12 @@ function ArkHostHetznerVPS_API(array $params) {
|
||||
break;
|
||||
|
||||
case 'Packages':
|
||||
$url .= 'server_types';
|
||||
$url .= 'server_types?per_page=50';
|
||||
$method = 'GET';
|
||||
break;
|
||||
|
||||
case 'Operating Systems':
|
||||
$url .= 'images?type=system';
|
||||
$url .= 'images?type=system&per_page=50';
|
||||
$method = 'GET';
|
||||
break;
|
||||
|
||||
@@ -177,6 +177,20 @@ function ArkHostHetznerVPS_API(array $params) {
|
||||
$data['enable_ipv6'] = false;
|
||||
}
|
||||
|
||||
// Handle Cloud-Init user_data if provided
|
||||
$cloudInitYaml = ArkHostHetznerVPS_GetOption($params, 'cloud_init_yaml');
|
||||
if ($cloudInitYaml && trim($cloudInitYaml) !== '') {
|
||||
// Basic YAML validation - check if it starts with #cloud-config
|
||||
$trimmedYaml = trim($cloudInitYaml);
|
||||
if (strpos($trimmedYaml, '#cloud-config') !== 0) {
|
||||
// Auto-prepend #cloud-config if missing
|
||||
$cloudInitYaml = "#cloud-config\n" . $cloudInitYaml;
|
||||
}
|
||||
|
||||
// Pass as plain text - Hetzner API accepts user_data as plain string (max 32KiB)
|
||||
$data['user_data'] = $cloudInitYaml;
|
||||
}
|
||||
|
||||
// Handle SSH keys from custom field if needed
|
||||
// This would need to be implemented with a custom field
|
||||
break;
|
||||
@@ -239,7 +253,7 @@ function ArkHostHetznerVPS_API(array $params) {
|
||||
break;
|
||||
|
||||
case 'Operating Systems - Server':
|
||||
$url .= 'images?type=system';
|
||||
$url .= 'images?type=system&per_page=50';
|
||||
$method = 'GET';
|
||||
break;
|
||||
|
||||
@@ -988,6 +1002,13 @@ function ArkHostHetznerVPS_ConfigOptions() {
|
||||
'Description' => 'Automatically create a floating IP when provisioning this server (additional cost - billed by Hetzner).',
|
||||
'Type' => 'yesno',
|
||||
),
|
||||
'cloud_init_yaml' => array(
|
||||
'FriendlyName' => 'Cloud-Init YAML (Optional)',
|
||||
'Description' => 'Custom cloud-init configuration in YAML format (max 32KiB). Passed as user_data to Hetzner API during server creation. Leave empty to skip cloud-init. <a href="https://docs.hetzner.cloud/#servers-create-a-server" target="_blank">Documentation</a>',
|
||||
'Type' => 'textarea',
|
||||
'Rows' => '10',
|
||||
'Cols' => '60',
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -1160,6 +1181,11 @@ function ArkHostHetznerVPS_CreateAccount(array $params) {
|
||||
Capsule::table('tblhosting')->where('id', $params['serviceid'])->update([
|
||||
'password' => encrypt($create['root_password'])
|
||||
]);
|
||||
|
||||
// Save the timestamp when password was set for expiration tracking (72 hours)
|
||||
$params['model']->serviceProperties->save([
|
||||
'ArkHostHetznerVPS|Password Set Time' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
// Handle floating IP creation if requested via Configurable Options or Module Settings
|
||||
@@ -1905,6 +1931,11 @@ function ArkHostHetznerVPS_ClientAreaAPI(array $params) {
|
||||
->where('id', $params['serviceid'])
|
||||
->update(['password' => encrypt($result['root_password'])]);
|
||||
|
||||
// Save the timestamp when password was set for expiration tracking (72 hours)
|
||||
$params['model']->serviceProperties->save([
|
||||
'ArkHostHetznerVPS|Password Set Time' => time()
|
||||
]);
|
||||
|
||||
$results['root_password'] = $result['root_password'];
|
||||
$results['message'] = 'Server rebuild initiated. New root password has been saved.';
|
||||
} else {
|
||||
@@ -1941,6 +1972,21 @@ function ArkHostHetznerVPS_ClientAreaAPI(array $params) {
|
||||
$results['result'] = 'error';
|
||||
$results['message'] = 'Invalid floating IP ID for this service';
|
||||
}
|
||||
} else if ($action === 'Reset root') {
|
||||
// Handle password reset from client area
|
||||
$results = array_merge($results, is_array($result) ? $result : array('data' => $result));
|
||||
|
||||
// Store the new root password and timestamp if provided
|
||||
if (isset($result['root_password'])) {
|
||||
Capsule::table('tblhosting')->where('id', $params['serviceid'])->update([
|
||||
'password' => encrypt($result['root_password'])
|
||||
]);
|
||||
|
||||
// Save the timestamp when password was set for expiration tracking (72 hours)
|
||||
$params['model']->serviceProperties->save([
|
||||
'ArkHostHetznerVPS|Password Set Time' => time()
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$results = array_merge($results, is_array($result) ? $result : array('data' => $result));
|
||||
}
|
||||
@@ -2347,6 +2393,26 @@ function ArkHostHetznerVPS_ClientArea(array $params) {
|
||||
$serverInfo['datacenter'] = isset($serverInfo['datacenter']['description']) ? $serverInfo['datacenter']['description'] : 'N/A';
|
||||
$serverInfo['location'] = isset($serverInfo['datacenter']['location']['city']) ? $serverInfo['datacenter']['location']['city'] : 'N/A';
|
||||
|
||||
// Get root password with expiration check (72 hours)
|
||||
$passwordSetTime = $params['model']->serviceProperties->get('ArkHostHetznerVPS|Password Set Time');
|
||||
$currentTime = time();
|
||||
$expirationPeriod = 72 * 3600; // 72 hours in seconds
|
||||
|
||||
if (!empty($params['password']) && $params['password'] !== 'managed-via-api') {
|
||||
// If no timestamp exists, show the password (backward compatibility or initial setup)
|
||||
// If timestamp exists, check if it's within the 72-hour window
|
||||
if (!$passwordSetTime || ($currentTime - $passwordSetTime) < $expirationPeriod) {
|
||||
// Password is still valid - WHMCS already decrypts it for us in $params
|
||||
// No need to call decrypt() as $params['password'] is already plain text
|
||||
$serverInfo['install_root'] = $params['password'];
|
||||
} else {
|
||||
// Password has expired (timestamp exists and is older than 72 hours)
|
||||
$serverInfo['install_root'] = '';
|
||||
}
|
||||
} else {
|
||||
$serverInfo['install_root'] = '';
|
||||
}
|
||||
|
||||
// For now, always show backups tab - the API will handle permissions
|
||||
$backupsEnabled = true;
|
||||
|
||||
@@ -2442,6 +2508,11 @@ function ArkHostHetznerVPS_ResetRoot(array $params) {
|
||||
Capsule::table('tblhosting')->where('id', $params['serviceid'])->update([
|
||||
'password' => encrypt($result['root_password'])
|
||||
]);
|
||||
|
||||
// Save the timestamp when password was set for expiration tracking (72 hours)
|
||||
$params['model']->serviceProperties->save([
|
||||
'ArkHostHetznerVPS|Password Set Time' => time()
|
||||
]);
|
||||
}
|
||||
} catch (Exception $err) {
|
||||
ArkHostHetznerVPS_Error(__FUNCTION__, $params, $err);
|
||||
|
||||
@@ -224,6 +224,12 @@ function resetRootPassword() {
|
||||
function() {
|
||||
ArkHostHetznerVPS_API('Reset root', true, {}, function(data) {
|
||||
if (data.root_password) {
|
||||
// Update the password field on the page
|
||||
var passwordField = document.getElementById('vpsPassword');
|
||||
if (passwordField) {
|
||||
passwordField.value = data.root_password;
|
||||
}
|
||||
|
||||
// Show password in a special modal or notification
|
||||
var passwordHtml = '<div class="alert alert-success">' +
|
||||
'<h5>' + lang.newRootPassword + '</h5>' +
|
||||
|
||||
Reference in New Issue
Block a user