Skip to content

Commit 3b00d2c

Browse files
committed
feat: SSH keys support
Closes #35
1 parent 0fe9ef1 commit 3b00d2c

3 files changed

Lines changed: 148 additions & 0 deletions

File tree

apis/upcloudvps_api.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ public function getTemplate()
235235
return $this->get('storage/template');
236236
}
237237

238+
/**
239+
* Gets details for a specific storage.
240+
*
241+
* @param string $storage_uuid The UUID of the storage
242+
* @return array The API response
243+
*/
244+
public function getStorage($storage_uuid)
245+
{
246+
return $this->get('storage/' . rawurlencode($storage_uuid));
247+
}
248+
238249
/**
239250
* Creates a new server.
240251
*
@@ -243,6 +254,7 @@ public function getTemplate()
243254
* - title (string) The server title (hostname)
244255
* - plan (string) The plan name
245256
* - template (string) The template UUID
257+
* - ssh_keys (array) Array of SSH public keys to add to the server (optional)
246258
* @return array The API response
247259
*/
248260
public function createServer($params)
@@ -287,6 +299,12 @@ public function createServer($params)
287299
]
288300
]
289301
];
302+
303+
// Add SSH keys if provided
304+
if (!empty($params['ssh_keys']) && is_array($params['ssh_keys'])) {
305+
$postData['server']['login_user']['ssh_keys']['ssh_key'] = $params['ssh_keys'];
306+
}
307+
290308
return $this->post('server', $postData);
291309
}
292310

language/en_us/upcloud.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
$lang['Upcloudvps.!error.upcloudvps_hostname.format'] = 'Please enter a valid hostname, e.g. domain.com.';
5757
$lang['Upcloudvps.!error.upcloudvps_location.valid'] = 'Please select a valid location.';
5858
$lang['Upcloudvps.!error.upcloudvps_template.valid'] = 'Please select a valid template.';
59+
$lang['Upcloudvps.!error.upcloudvps_ssh_keys.valid'] = 'Please enter valid SSH public keys.';
60+
$lang['Upcloudvps.!error.upcloudvps_ssh_keys.required'] = 'SSH public keys are required in this configuration.';
5961
$lang['Upcloudvps.!error.upcloudvps_vmid.valid'] = 'Please provide a valid vm id.';
6062
$lang['Upcloudvps.!error.api.internal'] = 'An internal error occurred, or the server did not respond to the request.';
6163
$lang['Upcloudvps.!error.api.server_locked'] = 'Unable to complete action. Server is currently locked.';
@@ -76,12 +78,14 @@
7678
$lang['Upcloudvps.service_field.hostname'] = 'Hostname';
7779
$lang['Upcloudvps.service_field.location'] = 'Location';
7880
$lang['Upcloudvps.service_field.template'] = 'Template';
81+
$lang['Upcloudvps.service_field.ssh_keys'] = 'SSH Keys';
7982
$lang['Upcloudvps.service_field.ipv6'] = 'IPv6 Networking';
8083
$lang['Upcloudvps.service_field.enable_vnc'] = 'Enable VNC';
8184
$lang['Upcloudvps.service_field.disable_vnc'] = 'Disable VNC';
8285

8386
// Tooltips
8487
$lang['Upcloudvps.service_field.tooltip.vmid'] = 'The unique identifier for this subscription.';
88+
$lang['Upcloudvps.service_field.tooltip.ssh_keys'] = 'Enter one or more SSH public keys (comma-separated).';
8589

8690
// Service info
8791
$lang['Upcloudvps.service_info.hostname'] = 'Hostname';

upcloud.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,23 @@ private function getFieldsFromInput(array $vars, $package)
601601
'template' => $osid,
602602
'title' => isset($vars['upcloudvps_hostname']) ? strtolower($vars['upcloudvps_hostname']) : null
603603
];
604+
605+
// Add SSH keys if provided
606+
if (!empty($vars['upcloudvps_ssh_keys'])) {
607+
$ssh_keys = [];
608+
// Handle both single key and multiple keys (comma-separated or array)
609+
if (is_array($vars['upcloudvps_ssh_keys'])) {
610+
$ssh_keys = array_filter(array_map('trim', $vars['upcloudvps_ssh_keys']));
611+
} else {
612+
// Split and clean up
613+
$keys = preg_split('/[,\r\n]+/', $vars['upcloudvps_ssh_keys'], -1, PREG_SPLIT_NO_EMPTY);
614+
$ssh_keys = array_filter(array_map('trim', $keys));
615+
}
616+
if (!empty($ssh_keys)) {
617+
$fields['ssh_keys'] = $ssh_keys;
618+
}
619+
}
620+
604621
return $fields;
605622
}
606623

@@ -652,6 +669,22 @@ private function getServiceRules(array $vars = null, $package = null, $edit = fa
652669
'rule' => [[$this, 'validateTemplate']],
653670
'message' => Language::_('Upcloudvps.!error.upcloudvps_template.valid', true)
654671
]
672+
],
673+
'upcloudvps_ssh_keys' => [
674+
'required' => [
675+
'if_set' => $edit,
676+
'rule' => [
677+
[$this, 'validateSshKeysRequired'],
678+
$package,
679+
$vars['upcloudvps_template'] ?? null,
680+
],
681+
'message' => Language::_('Upcloudvps.!error.upcloudvps_ssh_keys.required', true)
682+
],
683+
'valid' => [
684+
'if_set' => $edit,
685+
'rule' => [[$this, 'validateSshKeys']],
686+
'message' => Language::_('Upcloudvps.!error.upcloudvps_ssh_keys.valid', true)
687+
]
655688
]
656689
];
657690

@@ -706,6 +739,54 @@ public function validateTemplate($template)
706739
return array_key_exists(trim($template), $valid_templates);
707740
}
708741

742+
/**
743+
* Validates SSH keys' requiredness with the given package and template.
744+
*
745+
* @param string $ssh_keys The SSH keys set
746+
* @param stdClass $package The package to validate with
747+
* @param string $template_uuid The template UUID to validate with (ignored if admin-set)
748+
*/
749+
public function validateSshKeysRequired($ssh_keys, $package, $template_uuid)
750+
{
751+
if (!empty($ssh_keys)) {
752+
return true;
753+
}
754+
755+
$module_row = null;
756+
$rows = $this->getModuleRows();
757+
if (isset($rows[0])) {
758+
$module_row = $rows[0];
759+
}
760+
unset($rows);
761+
762+
$api = $this->getApi($module_row->meta->api_token, $module_row->meta->api_base_url);
763+
if ($package->meta->set_template !== 'client') {
764+
$template_uuid = $package->meta->template;
765+
}
766+
$template = $api->getStorage($template_uuid)['response']['storage'];
767+
768+
if ($template['type'] == 'template' && $template['template_type'] == 'cloud-init') {
769+
return false;
770+
}
771+
return true;
772+
}
773+
774+
/**
775+
* Validates SSH public keys.
776+
*
777+
* @param string $ssh_keys The SSH public keys to validate
778+
* @return bool True if the SSH key is valid, false otherwise
779+
*/
780+
public function validateSshKeys($ssh_keys)
781+
{
782+
foreach (preg_split('/[,\r\n]+/', $ssh_keys, -1, PREG_SPLIT_NO_EMPTY) as $ssh_key) {
783+
if (!preg_match('/^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp(256|384|521)|sk-(ssh-ed25519|ecdsa-sha2-nistp256)@openssh\.com) AAAA[0-9A-Za-z+\/]+={0,3}( .*)?$/', $ssh_key)) {
784+
return false;
785+
}
786+
}
787+
return true;
788+
}
789+
709790

710791
/**
711792
* Adds a new service. Creates the server via the API if 'use_module' is true.
@@ -780,6 +861,11 @@ public function addService($package, array $vars = null, $parent_package = null,
780861
'value' => $vars['upcloudvps_location'] ?? null,
781862
'encrypted' => 0
782863
],
864+
[
865+
'key' => 'upcloudvps_ssh_keys',
866+
'value' => $vars['upcloudvps_ssh_keys'] ?? null,
867+
'encrypted' => 0
868+
],
783869
[
784870
'key' => 'upcloudvps_password',
785871
'value' => $server['response']['server']['password'] ?? ($server['response']['server']['password'] ?? null),
@@ -865,6 +951,7 @@ public function editService($package, $service, array $vars = null, $parent_pack
865951
$fields = [
866952
'upcloudvps_vmid',
867953
'upcloudvps_template',
954+
'upcloudvps_ssh_keys',
868955
];
869956
foreach ($fields as $field) {
870957
if (property_exists($service_fields, $field) && isset($vars[$field])) {
@@ -944,6 +1031,19 @@ public function getAdminAddFields($package, $vars = null)
9441031
$fields->setField($template);
9451032
}
9461033

1034+
// Add SSH keys field
1035+
$ssh_keys = $fields->label(Language::_('Upcloudvps.service_field.ssh_keys', true), 'upcloudvps_ssh_keys');
1036+
$ssh_keys->attach(
1037+
$fields->fieldTextarea(
1038+
'upcloudvps_ssh_keys',
1039+
$vars->upcloudvps_ssh_keys ?? null,
1040+
['id' => 'upcloudvps_ssh_keys', 'rows' => 4, 'placeholder' => 'ssh-rsa AAAAB3NzaC1yc2E...']
1041+
)
1042+
);
1043+
$ssh_keys_tooltip = $fields->tooltip(Language::_('Upcloudvps.service_field.tooltip.ssh_keys', true));
1044+
$ssh_keys->attach($ssh_keys_tooltip);
1045+
$fields->setField($ssh_keys);
1046+
9471047
return $fields;
9481048
}
9491049

@@ -988,6 +1088,19 @@ public function getAdminEditFields($package, $vars = null)
9881088
$fields->setField($template);
9891089
}
9901090

1091+
// Add SSH keys field
1092+
$ssh_keys = $fields->label(Language::_('Upcloudvps.service_field.ssh_keys', true), 'upcloudvps_ssh_keys');
1093+
$ssh_keys->attach(
1094+
$fields->fieldTextarea(
1095+
'upcloudvps_ssh_keys',
1096+
$vars->upcloudvps_ssh_keys ?? null,
1097+
['id' => 'upcloudvps_ssh_keys', 'rows' => 4, 'placeholder' => 'ssh-rsa AAAAB3NzaC1yc2E...']
1098+
)
1099+
);
1100+
$ssh_keys_tooltip = $fields->tooltip(Language::_('Upcloudvps.service_field.tooltip.ssh_keys', true));
1101+
$ssh_keys->attach($ssh_keys_tooltip);
1102+
$fields->setField($ssh_keys);
1103+
9911104
return $fields;
9921105
}
9931106

@@ -1043,6 +1156,19 @@ public function getClientAddFields($package, $vars = null)
10431156
$fields->setField($template);
10441157
}
10451158

1159+
// Add SSH keys field
1160+
$ssh_keys = $fields->label(Language::_('Upcloudvps.service_field.ssh_keys', true), 'upcloudvps_ssh_keys');
1161+
$ssh_keys->attach(
1162+
$fields->fieldTextarea(
1163+
'upcloudvps_ssh_keys',
1164+
$vars->upcloudvps_ssh_keys ?? null,
1165+
['id' => 'upcloudvps_ssh_keys', 'rows' => 4, 'placeholder' => 'ssh-rsa AAAAB3NzaC1yc2E...']
1166+
)
1167+
);
1168+
$ssh_keys_tooltip = $fields->tooltip(Language::_('Upcloudvps.service_field.tooltip.ssh_keys', true));
1169+
$ssh_keys->attach($ssh_keys_tooltip);
1170+
$fields->setField($ssh_keys);
1171+
10461172
return $fields;
10471173
}
10481174

0 commit comments

Comments
 (0)