-cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
-
-my $rootfs_desc = {
- volume => {
- type => 'string',
- default_key => 1,
- format_description => 'volume',
- description => 'Volume, device or directory to mount into the container.',
- },
- backup => {
- type => 'boolean',
- format_description => '[1|0]',
- description => 'Whether to include the mountpoint in backups.',
- optional => 1,
- },
- size => {
- type => 'string',
- format => 'disk-size',
- format_description => 'DiskSize',
- description => 'Volume size (read only value).',
- optional => 1,
- },
-};
-
-PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
- type => 'string', format => $rootfs_desc,
- description => "Use volume as container root.",
- optional => 1,
-});
-
-PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
- description => "The name of the snapshot.",
- type => 'string', format => 'pve-configid',
- maxLength => 40,
-});
-
-my $confdesc = {
- lock => {
- optional => 1,
- type => 'string',
- description => "Lock/unlock the VM.",
- enum => [qw(migrate backup snapshot rollback)],
- },
- onboot => {
- optional => 1,
- type => 'boolean',
- description => "Specifies whether a VM will be started during system bootup.",
- default => 0,
- },
- startup => get_standard_option('pve-startup-order'),
- template => {
- optional => 1,
- type => 'boolean',
- description => "Enable/disable Template.",
- default => 0,
- },
- arch => {
- optional => 1,
- type => 'string',
- enum => ['amd64', 'i386'],
- description => "OS architecture type.",
- default => 'amd64',
- },
- ostype => {
- optional => 1,
- type => 'string',
- enum => ['debian', 'ubuntu', 'centos', 'archlinux'],
- description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
- },
- console => {
- optional => 1,
- type => 'boolean',
- description => "Attach a console device (/dev/console) to the container.",
- default => 1,
- },
- tty => {
- optional => 1,
- type => 'integer',
- description => "Specify the number of tty available to the container",
- minimum => 0,
- maximum => 6,
- default => 2,
- },
- cpulimit => {
- optional => 1,
- type => 'number',
- description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
- minimum => 0,
- maximum => 128,
- default => 0,
- },
- cpuunits => {
- optional => 1,
- type => 'integer',
- description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
- minimum => 0,
- maximum => 500000,
- default => 1024,
- },
- memory => {
- optional => 1,
- type => 'integer',
- description => "Amount of RAM for the VM in MB.",
- minimum => 16,
- default => 512,
- },
- swap => {
- optional => 1,
- type => 'integer',
- description => "Amount of SWAP for the VM in MB.",
- minimum => 0,
- default => 512,
- },
- hostname => {
- optional => 1,
- description => "Set a host name for the container.",
- type => 'string', format => 'dns-name',
- maxLength => 255,
- },
- description => {
- optional => 1,
- type => 'string',
- description => "Container description. Only used on the configuration web interface.",
- },
- searchdomain => {
- optional => 1,
- type => 'string', format => 'dns-name-list',
- description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
- },
- nameserver => {
- optional => 1,
- type => 'string', format => 'address-list',
- description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
- },
- rootfs => get_standard_option('pve-ct-rootfs'),
- parent => {
- optional => 1,
- type => 'string', format => 'pve-configid',
- maxLength => 40,
- description => "Parent snapshot name. This is used internally, and should not be modified.",
- },
- snaptime => {
- optional => 1,
- description => "Timestamp for snapshots.",
- type => 'integer',
- minimum => 0,
- },
- cmode => {
- optional => 1,
- description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
- type => 'string',
- enum => ['shell', 'console', 'tty'],
- default => 'tty',
- },
- protection => {
- optional => 1,
- type => 'boolean',
- description => "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
- default => 0,
- },
- unprivileged => {
- optional => 1,
- type => 'boolean',
- description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
- default => 0,
- },
-};
-
-my $valid_lxc_conf_keys = {
- 'lxc.include' => 1,
- 'lxc.arch' => 1,
- 'lxc.utsname' => 1,
- 'lxc.haltsignal' => 1,
- 'lxc.rebootsignal' => 1,
- 'lxc.stopsignal' => 1,
- 'lxc.init_cmd' => 1,
- 'lxc.network.type' => 1,
- 'lxc.network.flags' => 1,
- 'lxc.network.link' => 1,
- 'lxc.network.mtu' => 1,
- 'lxc.network.name' => 1,
- 'lxc.network.hwaddr' => 1,
- 'lxc.network.ipv4' => 1,
- 'lxc.network.ipv4.gateway' => 1,
- 'lxc.network.ipv6' => 1,
- 'lxc.network.ipv6.gateway' => 1,
- 'lxc.network.script.up' => 1,
- 'lxc.network.script.down' => 1,
- 'lxc.pts' => 1,
- 'lxc.console.logfile' => 1,
- 'lxc.console' => 1,
- 'lxc.tty' => 1,
- 'lxc.devttydir' => 1,
- 'lxc.hook.autodev' => 1,
- 'lxc.autodev' => 1,
- 'lxc.kmsg' => 1,
- 'lxc.mount' => 1,
- 'lxc.mount.entry' => 1,
- 'lxc.mount.auto' => 1,
- 'lxc.rootfs' => 1,
- 'lxc.rootfs.mount' => 1,
- 'lxc.rootfs.options' => 1,
- # lxc.cgroup.*
- 'lxc.cap.drop' => 1,
- 'lxc.cap.keep' => 1,
- 'lxc.aa_profile' => 1,
- 'lxc.aa_allow_incomplete' => 1,
- 'lxc.se_context' => 1,
- 'lxc.seccomp' => 1,
- 'lxc.id_map' => 1,
- 'lxc.hook.pre-start' => 1,
- 'lxc.hook.pre-mount' => 1,
- 'lxc.hook.mount' => 1,
- 'lxc.hook.start' => 1,
- 'lxc.hook.stop' => 1,
- 'lxc.hook.post-stop' => 1,
- 'lxc.hook.clone' => 1,
- 'lxc.hook.destroy' => 1,
- 'lxc.loglevel' => 1,
- 'lxc.logfile' => 1,
- 'lxc.start.auto' => 1,
- 'lxc.start.delay' => 1,
- 'lxc.start.order' => 1,
- 'lxc.group' => 1,
- 'lxc.environment' => 1,
- 'lxc.' => 1,
- 'lxc.' => 1,
- 'lxc.' => 1,
- 'lxc.' => 1,
-};
-
-my $netconf_desc = {
- type => {
- type => 'string',
- optional => 1,
- description => "Network interface type.",
- enum => [qw(veth)],
- },
- name => {
- type => 'string',
- format_description => 'String',
- description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
- pattern => '[-_.\w\d]+',
- },
- bridge => {
- type => 'string',
- format_description => 'vmbr<Number>',
- description => 'Bridge to attach the network device to.',
- pattern => '[-_.\w\d]+',
- optional => 1,
- },
- hwaddr => {
- type => 'string',
- format_description => 'MAC',
- description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
- pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
- optional => 1,
- },
- mtu => {
- type => 'integer',
- format_description => 'Number',
- description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
- minimum => 64, # minimum ethernet frame is 64 bytes
- optional => 1,
- },
- ip => {
- type => 'string',
- format => 'pve-ipv4-config',
- format_description => 'IPv4Format/CIDR',
- description => 'IPv4 address in CIDR format.',
- optional => 1,
- },
- gw => {
- type => 'string',
- format => 'ipv4',
- format_description => 'GatewayIPv4',
- description => 'Default gateway for IPv4 traffic.',
- optional => 1,
- },
- ip6 => {
- type => 'string',
- format => 'pve-ipv6-config',
- format_description => 'IPv6Format/CIDR',
- description => 'IPv6 address in CIDR format.',
- optional => 1,
- },
- gw6 => {
- type => 'string',
- format => 'ipv6',
- format_description => 'GatewayIPv6',
- description => 'Default gateway for IPv6 traffic.',
- optional => 1,
- },
- firewall => {
- type => 'boolean',
- format_description => '[1|0]',
- description => "Controls whether this interface's firewall rules should be used.",
- optional => 1,
- },
- tag => {
- type => 'integer',
- format_description => 'VlanNo',
- minimum => '2',
- maximum => '4094',
- description => "VLAN tag foro this interface.",
- optional => 1,
- },
-};
-PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
-
-my $MAX_LXC_NETWORKS = 10;
-for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
- $confdesc->{"net$i"} = {
- optional => 1,
- type => 'string', format => $netconf_desc,
- description => "Specifies network interfaces for the container.",
- };
-}
-
-my $mp_desc = {
- %$rootfs_desc,
- mp => {
- type => 'string',
- format_description => 'Path',
- description => 'Path to the mountpoint as seen from inside the container.',
- optional => 1,
- },
-};
-PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
-
-my $unuseddesc = {
- optional => 1,
- type => 'string', format => 'pve-volume-id',
- description => "Reference to unused volumes.",
-};
-
-my $MAX_MOUNT_POINTS = 10;
-for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
- $confdesc->{"mp$i"} = {
- optional => 1,
- type => 'string', format => $mp_desc,
- description => "Use volume as container mount point (experimental feature).",
- optional => 1,
- };
-}
-
-my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
-for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
- $confdesc->{"unused$i"} = $unuseddesc;
-}
-
-sub write_pct_config {
- my ($filename, $conf) = @_;
-
- delete $conf->{snapstate}; # just to be sure
-
- my $generate_raw_config = sub {
- my ($conf) = @_;
-
- my $raw = '';
-
- # add description as comment to top of file
- my $descr = $conf->{description} || '';
- foreach my $cl (split(/\n/, $descr)) {
- $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
- }
-
- foreach my $key (sort keys %$conf) {
- next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
- $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
- my $value = $conf->{$key};
- die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
- $raw .= "$key: $value\n";
- }
-
- if (my $lxcconf = $conf->{lxc}) {
- foreach my $entry (@$lxcconf) {
- my ($k, $v) = @$entry;
- $raw .= "$k: $v\n";
- }
- }
-
- return $raw;
- };
-
- my $raw = &$generate_raw_config($conf);
-
- foreach my $snapname (sort keys %{$conf->{snapshots}}) {
- $raw .= "\n[$snapname]\n";
- $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
- }
-
- return $raw;
-}
-
-sub check_type {
- my ($key, $value) = @_;
-
- die "unknown setting '$key'\n" if !$confdesc->{$key};
-
- my $type = $confdesc->{$key}->{type};
-
- if (!defined($value)) {
- die "got undefined value\n";
- }
-
- if ($value =~ m/[\n\r]/) {
- die "property contains a line feed\n";
- }
-
- if ($type eq 'boolean') {
- return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
- return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
- die "type check ('boolean') failed - got '$value'\n";
- } elsif ($type eq 'integer') {
- return int($1) if $value =~ m/^(\d+)$/;
- die "type check ('integer') failed - got '$value'\n";
- } elsif ($type eq 'number') {
- return $value if $value =~ m/^(\d+)(\.\d+)?$/;
- die "type check ('number') failed - got '$value'\n";
- } elsif ($type eq 'string') {
- if (my $fmt = $confdesc->{$key}->{format}) {
- PVE::JSONSchema::check_format($fmt, $value);
- return $value;
- }
- return $value;
- } else {
- die "internal error"
- }
-}
-
-sub parse_pct_config {
- my ($filename, $raw) = @_;
-
- return undef if !defined($raw);
-
- my $res = {
- digest => Digest::SHA::sha1_hex($raw),
- snapshots => {},
- };
-
- $filename =~ m|/lxc/(\d+).conf$|
- || die "got strange filename '$filename'";
-
- my $vmid = $1;
-
- my $conf = $res;
- my $descr = '';
- my $section = '';
-
- my @lines = split(/\n/, $raw);
- foreach my $line (@lines) {
- next if $line =~ m/^\s*$/;
-
- if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
- $section = $1;
- $conf->{description} = $descr if $descr;
- $descr = '';
- $conf = $res->{snapshots}->{$section} = {};
- next;
- }
-
- if ($line =~ m/^\#(.*)\s*$/) {
- $descr .= PVE::Tools::decode_text($1) . "\n";
- next;
- }
-
- if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
- my $key = $1;
- my $value = $3;
- if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
- push @{$conf->{lxc}}, [$key, $value];
- } else {
- warn "vm $vmid - unable to parse config: $line\n";
- }
- } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
- $descr .= PVE::Tools::decode_text($2);
- } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
- $conf->{snapstate} = $1;
- } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
- my $key = $1;
- my $value = $2;
- eval { $value = check_type($key, $value); };
- warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
- $conf->{$key} = $value;
- } else {
- warn "vm $vmid - unable to parse config: $line\n";
- }
- }
-
- $conf->{description} = $descr if $descr;
-
- delete $res->{snapstate}; # just to be sure
-
- return $res;
-}
-