* [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 2/4] cloudinit: basic implementation Leo Nunner
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
Introduce configuration parameters for cloud-init. Like with VMs, it's
possible to specify:
- user
- password
- ssh keys
- enable/disable updates on first boot
It's also possible to pass through custom config files for the user and
vendor settings. We don't allow configuring the network through
cloud-init, since it will clash with whatever configuration we already
did for the container.
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
src/PVE/API2/LXC.pm | 3 ++
src/PVE/API2/LXC/Config.pm | 7 ++++-
src/PVE/LXC/Config.pm | 61 ++++++++++++++++++++++++++++++++++++++
3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 50c9eaf..e585509 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -2492,6 +2492,9 @@ __PACKAGE__->register_method({
my $pending_delete_hash = PVE::LXC::Config->parse_pending_delete($conf->{pending}->{delete});
+ $conf->{cipassword} = '**********' if defined($conf->{cipassword});
+ $conf->{pending}->{cipassword} = '********** ' if defined($conf->{pending}->{cipassword});
+
return PVE::GuestHelpers::config_with_pending_array($conf, $pending_delete_hash);
}});
diff --git a/src/PVE/API2/LXC/Config.pm b/src/PVE/API2/LXC/Config.pm
index e6c0980..0ff4115 100644
--- a/src/PVE/API2/LXC/Config.pm
+++ b/src/PVE/API2/LXC/Config.pm
@@ -79,7 +79,7 @@ __PACKAGE__->register_method({
} else {
$conf = PVE::LXC::Config->load_current_config($param->{vmid}, $param->{current});
}
-
+ $conf->{cipassword} = '**********' if $conf->{cipassword};
return $conf;
}});
@@ -148,6 +148,11 @@ __PACKAGE__->register_method({
$param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})
if defined($param->{cpuunits}); # clamp value depending on cgroup version
+ if (defined(my $cipassword = $param->{cipassword})) {
+ $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
+ if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
+ }
+
my $code = sub {
my $conf = PVE::LXC::Config->load_config($vmid);
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index ac9db94..f8bfb95 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -442,6 +442,63 @@ my $features_desc = {
},
};
+my $cicustom_fmt = {
+ user => {
+ type => 'string',
+ optional => 1,
+ description => 'To pass a custom file containing all user data to the container via cloud-init.',
+ format => 'pve-volume-id',
+ format_description => 'volume',
+ },
+ vendor => {
+ type => 'string',
+ optional => 1,
+ description => 'To pass a custom file containing all vendor data to the container via cloud-init.',
+ format => 'pve-volume-id',
+ format_description => 'volume',
+ },
+};
+PVE::JSONSchema::register_format('pve-pct-cicustom', $cicustom_fmt);
+
+my $confdesc_cloudinit = {
+ cienable => {
+ optional => 1,
+ type => 'boolean',
+ description => "cloud-init: provide cloud-init configuration to container.",
+ },
+ ciuser => {
+ optional => 1,
+ type => 'string',
+ description => "cloud-init: User name to change ssh keys and password for instead of the"
+ ." image's configured default user.",
+ },
+ cipassword => {
+ optional => 1,
+ type => 'string',
+ description => 'cloud-init: Password to assign the user. Using this is generally not'
+ .' recommended. Use ssh keys instead. Also note that older cloud-init versions do not'
+ .' support hashed passwords.',
+ },
+ ciupgrade => {
+ optional => 1,
+ type => 'boolean',
+ description => 'cloud-init: do an automatic package update on boot.'
+ },
+ cicustom => {
+ optional => 1,
+ type => 'string',
+ description => 'cloud-init: Specify custom files to replace the automatically generated'
+ .' ones at start.',
+ format => 'pve-pct-cicustom',
+ },
+ sshkeys => {
+ optional => 1,
+ type => 'string',
+ format => 'urlencoded',
+ description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).",
+ },
+};
+
my $confdesc = {
lock => {
optional => 1,
@@ -614,6 +671,10 @@ my $confdesc = {
},
};
+foreach my $key (keys %$confdesc_cloudinit) {
+ $confdesc->{$key} = $confdesc_cloudinit->{$key};
+}
+
my $valid_lxc_conf_keys = {
'lxc.apparmor.profile' => 1,
'lxc.apparmor.allow_incomplete' => 1,
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH container 2/4] cloudinit: basic implementation
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct Leo Nunner
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
The code to generate the actual configuration works pretty much the same
as with the VM system. We generate an instance ID by hashing the user
configuration, causing cloud-init to run every time said configuration
changes.
Instead of creating a config drive, we write files directly into the
volume of the container. We create a folder at
'/var/lib/cloud/seed/nocloud-net' and write the files 'user-data',
'vendor-data' and 'meta-data'. Cloud-init looks at the instance ID
inside 'meta-data' to decide whether it should run (again) or not.
Custom scripts need to be located inside the snippets directory, and
overwrite the default generated configuration file.
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
src/PVE/LXC.pm | 1 +
src/PVE/LXC/Cloudinit.pm | 114 ++++++++++++++++++++++++++++++++++++++
src/PVE/LXC/Makefile | 1 +
src/lxc-pve-prestart-hook | 5 ++
4 files changed, 121 insertions(+)
create mode 100644 src/PVE/LXC/Cloudinit.pm
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index fe1a5cf..f4391c8 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -39,6 +39,7 @@ use PVE::Tools qw(
use PVE::Syscall qw(:fsmount);
use PVE::LXC::CGroup;
+use PVE::LXC::Cloudinit;
use PVE::LXC::Config;
use PVE::LXC::Monitor;
use PVE::LXC::Tools;
diff --git a/src/PVE/LXC/Cloudinit.pm b/src/PVE/LXC/Cloudinit.pm
new file mode 100644
index 0000000..3e8617b
--- /dev/null
+++ b/src/PVE/LXC/Cloudinit.pm
@@ -0,0 +1,114 @@
+package PVE::LXC::Cloudinit;
+
+use strict;
+use warnings;
+
+use Digest::SHA;
+use File::Path;
+
+use PVE::LXC;
+
+sub gen_cloudinit_metadata {
+ my ($user) = @_;
+
+ my $uuid_str = Digest::SHA::sha1_hex($user);
+ return cloudinit_metadata($uuid_str);
+}
+
+sub cloudinit_metadata {
+ my ($uuid) = @_;
+ my $raw = "";
+
+ $raw .= "instance-id: $uuid\n";
+
+ return $raw;
+}
+
+sub cloudinit_userdata {
+ my ($conf) = @_;
+
+ my $content = "#cloud-config\n";
+
+ my $username = $conf->{ciuser};
+ my $password = $conf->{cipassword};
+
+ $content .= "user: $username\n" if defined($username);
+ $content .= "password: $password\n" if defined($password);
+
+ if (defined(my $keys = $conf->{sshkeys})) {
+ $keys = URI::Escape::uri_unescape($keys);
+ $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)];
+ $keys = [grep { /\S/ } @$keys];
+ $content .= "ssh_authorized_keys:\n";
+ foreach my $k (@$keys) {
+ $content .= " - $k\n";
+ }
+ }
+ $content .= "chpasswd:\n";
+ $content .= " expire: False\n";
+
+ if (!defined($username) || $username ne 'root') {
+ $content .= "users:\n";
+ $content .= " - default\n";
+ }
+
+ $content .= "package_upgrade: true\n" if $conf->{ciupgrade};
+
+ return $content;
+}
+
+sub read_cloudinit_snippets_file {
+ my ($storage_conf, $volid) = @_;
+
+ my ($full_path, undef, $type) = PVE::Storage::path($storage_conf, $volid);
+ die "$volid is not in the snippets directory\n" if $type ne 'snippets';
+ return PVE::Tools::file_get_contents($full_path, 1 * 1024 * 1024);
+}
+
+sub read_custom_cloudinit_files {
+ my ($conf) = @_;
+
+ my $cloudinit_conf = $conf->{cicustom};
+ my $files = $cloudinit_conf ? PVE::JSONSchema::parse_property_string('pve-pct-cicustom', $cloudinit_conf) : {};
+
+ my $user_volid = $files->{user};
+ my $vendor_volid = $files->{vendor};
+
+ my $storage_conf = PVE::Storage::config();
+
+ my $user_data;
+ if ($user_volid) {
+ $user_data = read_cloudinit_snippets_file($storage_conf, $user_volid);
+ }
+
+ my $vendor_data;
+ if ($vendor_volid) {
+ $user_data = read_cloudinit_snippets_file($storage_conf, $vendor_volid);
+ }
+
+ return ($user_data, $vendor_data);
+}
+
+sub create_cloudinit_files {
+ my ($conf, $setup) = @_;
+
+ my $cloudinit_dir = "/var/lib/cloud/seed/nocloud-net";
+
+ my ($user_data, $vendor_data) = read_custom_cloudinit_files($conf);
+ $user_data = cloudinit_userdata($conf) if !defined($user_data);
+ $vendor_data = '' if !defined($vendor_data);
+
+ my $meta_data = gen_cloudinit_metadata($user_data);
+
+ $setup->protected_call(sub {
+ my $plugin = $setup->{plugin};
+
+ $plugin->ct_make_path($cloudinit_dir);
+
+ $plugin->ct_file_set_contents("$cloudinit_dir/user-data", $user_data);
+ $plugin->ct_file_set_contents("$cloudinit_dir/vendor-data", $vendor_data);
+ $plugin->ct_file_set_contents("$cloudinit_dir/meta-data", $meta_data);
+ });
+}
+
+1;
diff --git a/src/PVE/LXC/Makefile b/src/PVE/LXC/Makefile
index a190260..5d595ba 100644
--- a/src/PVE/LXC/Makefile
+++ b/src/PVE/LXC/Makefile
@@ -1,5 +1,6 @@
SOURCES= \
CGroup.pm \
+ Cloudinit.pm \
Command.pm \
Config.pm \
Create.pm \
diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook
index 3bdf7e4..e5932d2 100755
--- a/src/lxc-pve-prestart-hook
+++ b/src/lxc-pve-prestart-hook
@@ -12,6 +12,7 @@ use POSIX;
use PVE::CGroup;
use PVE::Cluster;
use PVE::LXC::Config;
+use PVE::LXC::Cloudinit;
use PVE::LXC::Setup;
use PVE::LXC::Tools;
use PVE::LXC;
@@ -140,6 +141,10 @@ PVE::LXC::Tools::lxc_hook('pre-start', 'lxc', sub {
my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
$lxc_setup->pre_start_hook();
+ if ($conf->{cienable}) {
+ PVE::LXC::Cloudinit::create_cloudinit_files($conf, $lxc_setup)
+ }
+
if (PVE::CGroup::cgroup_mode() == 2) {
if (!$lxc_setup->unified_cgroupv2_support()) {
log_warn($vmid, "old systemd (< v232) detected, container won't run in a pure cgroupv2"
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 2/4] cloudinit: basic implementation Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs Leo Nunner
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
Introduce a 'pct cloudinit dump <vmid> <section>' command to dump the
generated cloudinit configuration for a section.
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
src/PVE/API2/LXC.pm | 33 +++++++++++++++++++++++++++++++++
src/PVE/CLI/pct.pm | 4 ++++
src/PVE/LXC/Cloudinit.pm | 11 +++++++++++
3 files changed, 48 insertions(+)
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index e585509..2cae727 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -2963,4 +2963,37 @@ __PACKAGE__->register_method({
return { socket => $socket };
}});
+
+__PACKAGE__->register_method({
+ name => 'cloudinit_generated_config_dump',
+ path => '{vmid}/cloudinit/dump',
+ method => 'GET',
+ proxyto => 'node',
+ description => "Get automatically generated cloudinit config.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
+ type => {
+ description => 'Config type.',
+ type => 'string',
+ enum => ['user', 'meta'],
+ },
+ },
+ },
+ returns => {
+ type => 'string',
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $conf = PVE::LXC::Config->load_config($param->{vmid});
+
+ return PVE::LXC::Cloudinit::dump_cloudinit_config($conf, $param->{type});
+ }});
+
1;
diff --git a/src/PVE/CLI/pct.pm b/src/PVE/CLI/pct.pm
index ff75d33..69f3560 100755
--- a/src/PVE/CLI/pct.pm
+++ b/src/PVE/CLI/pct.pm
@@ -1000,6 +1000,10 @@ our $cmddef = {
rescan => [ __PACKAGE__, 'rescan', []],
cpusets => [ __PACKAGE__, 'cpusets', []],
fstrim => [ __PACKAGE__, 'fstrim', ['vmid']],
+
+ cloudinit => {
+ dump => [ "PVE::API2::LXC", 'cloudinit_generated_config_dump', ['vmid', 'type'], { node => $nodename }, sub { print "$_[0]\n"; }],
+ },
};
1;
diff --git a/src/PVE/LXC/Cloudinit.pm b/src/PVE/LXC/Cloudinit.pm
index 3e8617b..b6fec2c 100644
--- a/src/PVE/LXC/Cloudinit.pm
+++ b/src/PVE/LXC/Cloudinit.pm
@@ -111,4 +111,15 @@ sub create_cloudinit_files {
});
}
+sub dump_cloudinit_config {
+ my ($conf, $type) = @_;
+
+ if ($type eq 'user') {
+ return cloudinit_userdata($conf);
+ } else { # metadata config
+ my $user = cloudinit_userdata($conf);
+ return gen_cloudinit_metadata($user);
+ }
+}
+
1;
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
` (2 preceding siblings ...)
2023-06-02 11:57 ` [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel Leo Nunner
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
Adds a cloudinit_config_properties function which dumps the config
parameters from the cloudinit config description. This is called
automatically when generating the docs.
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
src/PVE/LXC/Config.pm | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index f8bfb95..49d6479 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -1244,6 +1244,9 @@ sub check_type {
}
}
+sub cloudinit_config_properties {
+ return Storable::dclone($confdesc_cloudinit);
+}
# add JSON properties for create and set function
sub json_config_properties {
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
` (3 preceding siblings ...)
2023-06-02 11:57 ` [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs Leo Nunner
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
www/manager6/qemu/CloudInit.js | 4 ++--
www/manager6/qemu/Config.js | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/www/manager6/qemu/CloudInit.js b/www/manager6/qemu/CloudInit.js
index 77ff93d41..14117ff6b 100644
--- a/www/manager6/qemu/CloudInit.js
+++ b/www/manager6/qemu/CloudInit.js
@@ -1,6 +1,6 @@
Ext.define('PVE.qemu.CloudInit', {
extend: 'Proxmox.grid.PendingObjectGrid',
- xtype: 'pveCiPanel',
+ xtype: 'pveQemuCiPanel',
onlineHelp: 'qm_cloud_init',
@@ -66,7 +66,7 @@ Ext.define('PVE.qemu.CloudInit', {
xtype: 'proxmoxButton',
disabled: true,
enableFn: function(rec) {
- let view = this.up('pveCiPanel');
+ let view = this.up('pveQemuCiPanel');
return !!view.rows[rec.data.key].editor;
},
handler: function() {
diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js
index 94c540c59..03e1e6d8d 100644
--- a/www/manager6/qemu/Config.js
+++ b/www/manager6/qemu/Config.js
@@ -284,7 +284,7 @@ Ext.define('PVE.qemu.Config', {
title: 'Cloud-Init',
itemId: 'cloudinit',
iconCls: 'fa fa-cloud',
- xtype: 'pveCiPanel',
+ xtype: 'pveQemuCiPanel',
},
{
title: gettext('Options'),
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
` (4 preceding siblings ...)
2023-06-02 11:57 ` [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC Leo Nunner
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
based on the already existing panel for VMs. Some things have been
changed, there is no network configuration, and a separate "enable"
options toggles cloud-init (simillar to adding/removing a cloud-init
drive for VMs).
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
www/manager6/Makefile | 1 +
www/manager6/lxc/CloudInit.js | 237 ++++++++++++++++++++++++++++++++++
www/manager6/lxc/Config.js | 6 +
3 files changed, 244 insertions(+)
create mode 100644 www/manager6/lxc/CloudInit.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 336355abf..05a2fd558 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -168,6 +168,7 @@ JSSRC= \
dc/UserTagAccessEdit.js \
dc/RegisteredTagsEdit.js \
lxc/CmdMenu.js \
+ lxc/CloudInit.js \
lxc/Config.js \
lxc/CreateWizard.js \
lxc/DNS.js \
diff --git a/www/manager6/lxc/CloudInit.js b/www/manager6/lxc/CloudInit.js
new file mode 100644
index 000000000..11d5448de
--- /dev/null
+++ b/www/manager6/lxc/CloudInit.js
@@ -0,0 +1,237 @@
+Ext.define('PVE.lxc.CloudInit', {
+ extend: 'Proxmox.grid.PendingObjectGrid',
+ xtype: 'pveLxcCiPanel',
+
+ tbar: [
+ {
+ xtype: 'proxmoxButton',
+ disabled: true,
+ dangerous: true,
+ confirmMsg: function(rec) {
+ let view = this.up('grid');
+ var warn = gettext('Are you sure you want to remove entry {0}');
+
+ var entry = rec.data.key;
+ var msg = Ext.String.format(warn, "'"
+ + view.renderKey(entry, {}, rec) + "'");
+
+ return msg;
+ },
+ enableFn: function(record) {
+ let view = this.up('grid');
+ var caps = Ext.state.Manager.get('GuiCap');
+ if (view.rows[record.data.key].never_delete ||
+ !caps.vms['VM.Config.Network']) {
+ return false;
+ }
+
+ if (record.data.key === 'cipassword' && !record.data.value) {
+ return false;
+ }
+ return true;
+ },
+ handler: function() {
+ let view = this.up('grid');
+ let records = view.getSelection();
+ if (!records || !records.length) {
+ return;
+ }
+
+ var id = records[0].data.key;
+
+ var params = {};
+ params.delete = id;
+ Proxmox.Utils.API2Request({
+ url: view.baseurl + '/config',
+ waitMsgTarget: view,
+ method: 'PUT',
+ params: params,
+ failure: function(response, opts) {
+ Ext.Msg.alert('Error', response.htmlStatus);
+ },
+ callback: function() {
+ view.reload();
+ },
+ });
+ },
+ text: gettext('Remove'),
+ },
+ {
+ xtype: 'proxmoxButton',
+ disabled: true,
+ enableFn: function(rec) {
+ let view = this.up('pveLxcCiPanel');
+ return !!view.rows[rec.data.key].editor;
+ },
+ handler: function() {
+ let view = this.up('grid');
+ view.run_editor();
+ },
+ text: gettext('Edit'),
+ },
+ ],
+
+ border: false,
+
+ renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
+ var me = this;
+ var rows = me.rows;
+ var rowdef = rows[key] || {};
+
+ var icon = "";
+ if (rowdef.iconCls) {
+ icon = '<i class="' + rowdef.iconCls + '"></i> ';
+ }
+ return icon + (rowdef.header || key);
+ },
+
+ listeners: {
+ activate: function() {
+ var me = this;
+ me.rstore.startUpdate();
+ },
+ itemdblclick: function() {
+ var me = this;
+ me.run_editor();
+ },
+ },
+
+ initComponent: function() {
+ var me = this;
+
+ var nodename = me.pveSelNode.data.node;
+ if (!nodename) {
+ throw "no node name specified";
+ }
+
+ var vmid = me.pveSelNode.data.vmid;
+ if (!vmid) {
+ throw "no VM ID specified";
+ }
+ var caps = Ext.state.Manager.get('GuiCap');
+ me.baseurl = '/api2/extjs/nodes/' + nodename + '/lxc/' + vmid;
+ me.url = me.baseurl + '/pending';
+ me.editorConfig.url = me.baseurl + '/config';
+ me.editorConfig.pveSelNode = me.pveSelNode;
+
+ let caps_ci = caps.vms['VM.Config.Cloudinit'] || caps.vms['VM.Config.Network'];
+ /* editor is string and object */
+ me.rows = {
+ cienable: {
+ header: gettext('Enable'),
+ iconCls: 'fa fa-cloud',
+ never_delete: true,
+ defaultValue: false,
+ editor: caps_ci ? {
+ xtype: 'proxmoxWindowEdit',
+ subject: gettext('Enable Cloud-Init'),
+ items: [
+ {
+ xtype: 'proxmoxcheckbox',
+ deleteEmpty: true,
+ fieldLabel: gettext('Enable'),
+ name: 'cienable',
+ },
+ ],
+ } : undefined,
+ renderer: Proxmox.Utils.format_boolean,
+ },
+ ciuser: {
+ header: gettext('User'),
+ iconCls: 'fa fa-user',
+ never_delete: true,
+ defaultValue: '',
+ editor: caps_ci ? {
+ xtype: 'proxmoxWindowEdit',
+ subject: gettext('User'),
+ items: [
+ {
+ xtype: 'proxmoxtextfield',
+ deleteEmpty: true,
+ emptyText: Proxmox.Utils.defaultText,
+ fieldLabel: gettext('User'),
+ name: 'ciuser',
+ },
+ ],
+ } : undefined,
+ renderer: function(value) {
+ return value || Proxmox.Utils.defaultText;
+ },
+ },
+ cipassword: {
+ header: gettext('Password'),
+ iconCls: 'fa fa-unlock',
+ defaultValue: '',
+ editor: caps_ci ? {
+ xtype: 'proxmoxWindowEdit',
+ subject: gettext('Password'),
+ items: [
+ {
+ xtype: 'proxmoxtextfield',
+ inputType: 'password',
+ deleteEmpty: true,
+ emptyText: Proxmox.Utils.noneText,
+ fieldLabel: gettext('Password'),
+ name: 'cipassword',
+ },
+ ],
+ } : undefined,
+ renderer: function(value) {
+ return value || Proxmox.Utils.noneText;
+ },
+ },
+ sshkeys: {
+ header: gettext('SSH public key'),
+ iconCls: 'fa fa-key',
+ editor: caps_ci ? 'PVE.qemu.SSHKeyEdit' : undefined,
+ never_delete: true,
+ renderer: function(value) {
+ value = decodeURIComponent(value);
+ var keys = value.split('\n');
+ var text = [];
+ keys.forEach(function(key) {
+ if (key.length) {
+ let res = PVE.Parser.parseSSHKey(key);
+ if (res) {
+ key = Ext.String.htmlEncode(res.comment);
+ if (res.options) {
+ key += ' <span style="color:gray">(' + gettext('with options') + ')</span>';
+ }
+ text.push(key);
+ return;
+ }
+ // Most likely invalid at this point, so just stick to
+ // the old value.
+ text.push(Ext.String.htmlEncode(key));
+ }
+ });
+ if (text.length) {
+ return text.join('<br>');
+ } else {
+ return Proxmox.Utils.noneText;
+ }
+ },
+ defaultValue: '',
+ },
+ ciupgrade: {
+ header: gettext('Upgrade packages'),
+ iconCls: 'fa fa-archive',
+ renderer: Proxmox.Utils.format_boolean,
+ defaultValue: '',
+ editor: {
+ xtype: 'proxmoxWindowEdit',
+ subject: gettext('Upgrade packages on boot'),
+ items: {
+ xtype: 'proxmoxcheckbox',
+ name: 'ciupgrade',
+ uncheckedValue: 0,
+ defaultValue: 0,
+ fieldLabel: gettext('Upgrade packages'),
+ labelWidth: 140,
+ },
+ },
+ },
+ };
+ me.callParent();
+ },
+});
diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js
index 23c17d2ea..bd86b93b9 100644
--- a/www/manager6/lxc/Config.js
+++ b/www/manager6/lxc/Config.js
@@ -260,6 +260,12 @@ Ext.define('PVE.lxc.Config', {
itemId: 'dns',
xtype: 'pveLxcDNS',
},
+ {
+ title: 'Cloud-Init',
+ itemId: 'cloudinit',
+ iconCls: 'fa fa-cloud',
+ xtype: 'pveLxcCiPanel',
+ },
{
title: gettext('Options'),
itemId: 'options',
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
` (5 preceding siblings ...)
2023-06-02 11:57 ` [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC Leo Nunner
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
…the same way as it's already being done for VMs.
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
gen-pct-cloud-init-opts.pl | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100755 gen-pct-cloud-init-opts.pl
diff --git a/gen-pct-cloud-init-opts.pl b/gen-pct-cloud-init-opts.pl
new file mode 100755
index 0000000..c22c4d1
--- /dev/null
+++ b/gen-pct-cloud-init-opts.pl
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use lib '.';
+use strict;
+use warnings;
+use PVE::JSONSchema;
+use PVE::RESTHandler;
+use PVE::LXC::Config;
+
+my $prop = PVE::LXC::Config::cloudinit_config_properties();
+
+my $data = PVE::RESTHandler::dump_properties($prop, 'asciidoc', 'config');
+
+$data =~ s/cloud-init: //g;
+
+print $data;
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
` (6 preceding siblings ...)
2023-06-02 11:57 ` [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
To: pve-devel
adds documentation for Cloud-Init for containers. Most of it has been
taken from the VM documentation, since the configuration mostly works
the same. Added a script to extract the cloudinit parameters the same
way it's already done for VMs.
Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
Makefile | 1 +
pct-cloud-init.adoc | 114 ++++++++++++++++++++++++++++++++++++++++++++
pct.adoc | 4 ++
3 files changed, 119 insertions(+)
create mode 100644 pct-cloud-init.adoc
diff --git a/Makefile b/Makefile
index b8a0666..ec5319a 100644
--- a/Makefile
+++ b/Makefile
@@ -53,6 +53,7 @@ GEN_SCRIPTS= \
gen-pct.conf.5-opts.pl \
gen-pct-network-opts.pl \
gen-pct-mountpoint-opts.pl \
+ gen-pct-cloud-init-opts.pl \
gen-qm.conf.5-opts.pl \
gen-cpu-models.conf.5-opts.pl \
gen-qm-cloud-init-opts.pl \
diff --git a/pct-cloud-init.adoc b/pct-cloud-init.adoc
new file mode 100644
index 0000000..1398e7b
--- /dev/null
+++ b/pct-cloud-init.adoc
@@ -0,0 +1,114 @@
+[[pct_cloud_init]]
+Cloud-Init Support
+------------------
+ifdef::wiki[]
+:pve-toplevel:
+endif::wiki[]
+
+{pve} supports the Cloud-init 'nocloud' format for LXC.
+
+{pve} writes the Cloud-init configuration directly into the container.
+When the 'cienable' option is set to true, the configuration is updated
+directly before before every boot. Each configuration is identified by
+an 'instance id', which Cloud-Init uses to decide whether it should run
+again or not.
+
+Preparing Cloud-Init Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first step is to prepare your container. Any template will suffice.
+Simply install the Cloud-Init packages inside the CT that you want to
+prepare. On Debian/Ubuntu based systems this is as simple as:
+
+----
+apt install cloud-init
+----
+
+WARNING: This command is *not* intended to be executed on the {pve} host, but
+only inside the container.
+
+A simple preparation for a cloud-init capable container could look like this:
+
+----
+# download an image
+pveam download local ubuntu-22.10-standard_22.10-1_amd64.tar.zst
+
+# create a new container
+pct create 9000 local:vztmpl/ubuntu-22.10-standard_22.10-1_amd64.tar.zst \
+ --storage local-lvm --memory 512 \
+ --net0 name=eth0,bridge=vmbr0,ip=dhcp,type=veth
+----
+
+Now, the package can be installed inside the container:
+
+----
+# install the needed packages
+pct start 9000
+pct exec 9000 apt update
+pct exec 9000 apt install cloud-init
+pct stop 9000
+----
+
+Finally, it can be helpful to turn the container into a template, to be able
+to quickly create clones whenever needed.
+
+----
+pct template 9000
+----
+
+Deploying Cloud-Init Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A template can easily be deployed by cloning:
+
+----
+pct clone 9000 3000 --hostname ubuntu
+----
+
+Cloud-Init can now be enabled, and will run automatically on the next boot.
+
+----
+pct set 3000 --cienable=1
+----
+
+Custom Cloud-Init Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Cloud-Init integration also allows custom config files to be used instead
+of the automatically generated configs. This is done via the `cicustom`
+option on the command line:
+
+----
+pct set 9000 --cicustom "user=<volume>,meta=<volume>"
+----
+
+The custom config files have to be on a storage that supports snippets and have
+to be available on all nodes the container is going to be migrated to. Otherwise the
+container won't be able to start.
+For example:
+
+----
+qm set 9000 --cicustom "user=local:snippets/userconfig.yaml"
+----
+
+In contrast to the options for VMs, containers only support custom 'user'
+and 'vendor' scripts, but not 'network'. Network configuration is done through
+the already existing facilities integrated into {pve}. They can all be specified
+together or mixed and matched however needed.
+The automatically generated config will be used for any section that doesn't have a
+custom config file specified.
+
+The generated config can be dumped to serve as a base for custom configs:
+
+----
+pct cloudinit dump 9000 user
+----
+
+The same command exists for `meta`.
+
+
+Cloud-Init specific Options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+include::pct-cloud-init-opts.adoc[]
+
diff --git a/pct.adoc b/pct.adoc
index fdeb6dd..7cfb09e 100644
--- a/pct.adoc
+++ b/pct.adoc
@@ -622,6 +622,10 @@ It will be called during various phases of the guests lifetime. For an example
and documentation see the example script under
`/usr/share/pve-docs/examples/guest-example-hookscript.pl`.
+ifndef::wiki[]
+include::pct-cloud-init.adoc[]
+endif::wiki[]
+
Security Considerations
-----------------------
--
2.30.2
^ permalink raw reply [flat|nested] 9+ messages in thread