From bc9d8a2e1c7b5406fa2540eda2d5e844b4a0232a Mon Sep 17 00:00:00 2001 From: Yuri Date: Mon, 3 Nov 2025 15:07:02 +0100 Subject: [PATCH] per product cloud-init optional support --- README.md | 98 +++++++++++++++++++ .../ArkHostHetznerVPS/ArkHostHetznerVPS.php | 23 ++++- 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f299622..da0bdcc 100644 --- a/README.md +++ b/README.md @@ -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 @@ -60,6 +61,7 @@ WHMCS server module for Hetzner Cloud VPS management. - 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)** @@ -189,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 ![Screenshot 1](screenshots/1.png) ![Screenshot 2](screenshots/2.png) diff --git a/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php b/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php index 3fd3dbe..4fa5e78 100644 --- a/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php +++ b/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php @@ -176,7 +176,21 @@ function ArkHostHetznerVPS_API(array $params) { if (ArkHostHetznerVPS_GetOption($params, 'ipv6') === 'on') { $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; @@ -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. Documentation', + 'Type' => 'textarea', + 'Rows' => '10', + 'Cols' => '60', + ), ); try {