pct.1.pod: pct
perl -I. -T ./pct printmanpod >$@
-lxc-pve.1.pod: lxc-pve
- perl -I. -T ./lxc-pve printmanpod >$@
-
lxc-pve-mount-hook.1.pod: lxc-pve-mount-hook
perl -I. -T ./lxc-pve-mount-hook printmanpod >$@
.PHONY: install
-install: pct lxc-pve lxc-pve-mount-hook lxcnetaddbr lxc-pve-mount-hook.1.pod lxc-pve-mount-hook.1.gz pct.1.pod pct.1.gz lxc-pve.1.pod lxc-pve.1.gz
+install: pct lxc-pve-mount-hook lxcnetaddbr lxc-pve-mount-hook.1.pod lxc-pve-mount-hook.1.gz pct.1.pod pct.1.gz
perl -T -I. ./pct verifyapi
- perl -T -I. ./lxc-pve verifyapi
install -d ${SBINDIR}
install -m 0755 pct ${SBINDIR}
install -d ${LXCSCRIPTDIR}
install -m 0755 lxcnetaddbr ${LXCSCRIPTDIR}
- install -d ${LXCTMPLDIR}
- install -m 0755 lxc-pve ${LXCTMPLDIR}
install -d ${LXCHOOKDIR}
install -m 0755 lxc-pve-mount-hook ${LXCHOOKDIR}
make -C PVE install
install -d ${MAN1DIR}
install -d ${PODDIR}
install -m 0644 pct.1.gz ${MAN1DIR}
- install -m 0644 lxc-pve.1.gz ${MAN1DIR}
install -m 0644 pct.1.pod ${PODDIR}
- install -m 0644 lxc-pve.1.pod ${PODDIR}
.PHONY: test
test:
use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::LXC;
+use PVE::LXCCreate;
use PVE::HA::Config;
use PVE::JSONSchema qw(get_standard_option);
use base qw(PVE::RESTHandler);
$param->{hostname} ||= "CT$vmid";
$param->{memory} ||= 512;
$param->{swap} = 512 if !defined($param->{swap});
-
- PVE::LXC::update_lxc_config($vmid, $conf, 0, $param);
+
+ PVE::LXC::update_lxc_config($vmid, $conf, 0, $param);
# assigng default names, so that we can configure network with LXCSetup
foreach my $k (keys %$conf) {
next if $k !~ m/^net(\d+)$/;
my $d = $conf->{$k};
my $ind = $1;
- $d->{name} = "eth$ind";
+ $d->{name} = "eth$ind"; # fixme: do not overwrite settings!
}
-
- my $code = sub {
- my $temp_conf_fn = PVE::LXC::write_temp_config($vmid, $conf);
-
- my $cmd = ['lxc-create', '-f', $temp_conf_fn, '-t', 'pve', '-n', $vmid,
- '--', '--archive', $archive];
- if (defined($password)) {
- push $cmd, '--password', $password
- }
-
- eval { PVE::Tools::run_command($cmd); };
- my $err = $@;
+ $conf->{'lxc.hook.mount'} = "/usr/share/lxc/hooks/lxc-pve-mount-hook";
- unlink $temp_conf_fn;
+ # use user namespace ?
+ # disable for now, because kernel 3.10.0 does not support it
+ #$conf->{'lxc.id_map'} = ["u 0 100000 65536", "g 0 100000 65536"];
- die $err if $err;
+ my $code = sub {
+ my $size = 4*1024*1024*1024; # fixme
+ PVE::LXCCreate::create_rootfs($storage_cfg, $storage, $size, $vmid, $conf, $archive, $password);
};
my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
my $running = PVE::LXC::check_running($vmid);
- PVE::LXC::update_lxc_config($vmid, $conf, $running, $param, \@delete);
+ PVE::LXC::update_lxc_config($vmid, $conf, $running, $param, \@delete);
PVE::LXC::write_config($vmid, $conf);
};
my $sslcert;
__PACKAGE__->register_method ({
- name => 'vncproxy',
- path => '{vmid}/vncproxy',
+ name => 'vncproxy',
+ path => '{vmid}/vncproxy',
method => 'POST',
protected => 1,
permissions => {
},
},
},
- returns => {
+ returns => {
additionalProperties => 0,
properties => {
user => { type => 'string' },
my $port = PVE::Tools::next_vnc_port();
my $remip;
-
+
if ($node ne PVE::INotify::nodename()) {
$remip = PVE::Cluster::remote_node_ip($node);
}
# NOTE: vncterm VNC traffic is already TLS encrypted,
# so we select the fastest chipher here (or 'none'?)
- my $remcmd = $remip ?
+ my $remcmd = $remip ?
['/usr/bin/ssh', '-t', $remip] : [];
- my $shcmd = [ '/usr/bin/dtach', '-A',
- "/var/run/dtach/vzctlconsole$vmid",
- '-r', 'winch', '-z',
+ my $shcmd = [ '/usr/bin/dtach', '-A',
+ "/var/run/dtach/vzctlconsole$vmid",
+ '-r', 'winch', '-z',
'lxc-console', '-n', $vmid ];
my $realcmd = sub {
my $upid = shift;
- syslog ('info', "starting openvz vnc proxy $upid\n");
+ syslog ('info', "starting lxc vnc proxy $upid\n");
- my $timeout = 10;
+ my $timeout = 10;
my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
- '-timeout', $timeout, '-authpath', $authpath,
+ '-timeout', $timeout, '-authpath', $authpath,
'-perm', 'VM.Console'];
if ($param->{websocket}) {
- $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
+ $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
push @$cmd, '-notls', '-listen', 'localhost';
}
return {
user => $authuser,
ticket => $ticket,
- port => $port,
- upid => $upid,
- cert => $sslcert,
+ port => $port,
+ upid => $upid,
+ cert => $sslcert,
};
}});
name => 'vncwebsocket',
path => '{vmid}/vncwebsocket',
method => 'GET',
- permissions => {
+ permissions => {
description => "You also need to pass a valid ticket (vncticket).",
check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
},
PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
my $port = $param->{port};
-
+
return { port => $port };
}});
__PACKAGE__->register_method ({
- name => 'spiceproxy',
- path => '{vmid}/spiceproxy',
+ name => 'spiceproxy',
+ path => '{vmid}/spiceproxy',
method => 'POST',
protected => 1,
proxyto => 'node',
my $authpath = "/vms/$vmid";
my $permissions = 'VM.Console';
- my $shcmd = ['/usr/bin/dtach', '-A',
- "/var/run/dtach/vzctlconsole$vmid",
- '-r', 'winch', '-z',
+ my $shcmd = ['/usr/bin/dtach', '-A',
+ "/var/run/dtach/vzctlconsole$vmid",
+ '-r', 'winch', '-z',
'lxc-console', '-n', $vmid];
my $title = "CT $vmid";
'lxc.network' => 1,
'lxc.mount' => 1,
'lxc.include' => 1,
+ 'lxc.id_map' => 1,
};
sub write_lxc_config {
foreach my $k (sort keys %$data) {
next if $k !~ m/^lxc\./;
$done_hash->{$k} = 1;
- $raw .= "$k = $data->{$k}\n";
+ if (ref($data->{$k})) {
+ die "got unexpected reference for '$k'" if !$lxc_array_configs->{$k};
+ foreach my $v (@{$data->{$k}}) {
+ $raw .= "$k = $v\n";
+ }
+ } else {
+ $raw .= "$k = $data->{$k}\n";
+ }
}
foreach my $k (sort keys %$data) {
if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
my ($name, $value) = ($1, $2);
- die "multiple definitions for $name\n" if defined($data->{$name});
-
- $data->{$name} = parse_lxc_option($name, $value);
+ if ($lxc_array_configs->{$name}) {
+ $data->{$name} = [] if !defined($data->{$name});
+ push @{$data->{$name}}, parse_lxc_option($name, $value);
+ } else {
+ die "multiple definitions for $name\n" if defined($data->{$name});
+ $data->{$name} = parse_lxc_option($name, $value);
+ }
+
next;
}
return $conf;
}
+sub create_config {
+ my ($vmid, $conf) = @_;
+
+ my $dir = "/etc/pve/nodes/$nodename/lxc";
+ mkdir $dir;
+
+ $dir .= "/$vmid";
+ mkdir($dir) || die "unable to create container configuration directory - $!\n";
+
+ write_config($vmid, $conf);
+}
+
+sub destroy_config {
+ my ($vmid) = @_;
+
+ my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
+ File::Path::rmtree($dir);
+}
+
sub write_config {
my ($vmid, $conf) = @_;
--- /dev/null
+package PVE::LXCCreate;
+
+use strict;
+use warnings;
+use File::Basename;
+use File::Path;
+use Data::Dumper;
+
+use PVE::Storage;
+use PVE::LXC;
+use PVE::LXCSetup;
+
+sub restore_archive {
+ my ($archive, $rootdir, $conf) = @_;
+
+ # we always use the same mapping: 'b:0:100000:65536'
+ my $userns_cmd;
+
+ if ($conf->{'lxc.id_map'}) {
+ $userns_cmd = ['lxc-usernsexec', '-m', 'b:0:100000:65536', '--'];
+ } else {
+ $userns_cmd = [];
+ }
+
+ my $cmd;
+
+ if ($conf->{'lxc.id_map'}) {
+ PVE::Tools::run_command(['chown', '-R', '100000:100000', $rootdir]);
+ }
+
+ $cmd = [@$userns_cmd, 'tar', 'xpf', $archive, '--numeric-owner', '--totals',
+ '--sparse', '-C', $rootdir];
+
+ push @$cmd, '--anchored';
+ push @$cmd, '--exclude' , './dev/*';
+
+ print "extracting archive '$archive'\n";
+ PVE::Tools::run_command($cmd);
+
+ # is this really required? what for?
+ #$cmd = [@$userns_cmd, 'mkdir', '-p', "$rootdir/dev/pts"];
+ #PVE::Tools::run_command($cmd);
+
+ # template/OS specific configuration
+ $conf->{'lxc.arch'} = 'i386'; #fixme: || x86_64
+}
+
+sub restore_and_configure {
+ my ($vmid, $archive, $rootdir, $conf, $password) = @_;
+
+ restore_archive($archive, $rootdir, $conf);
+
+ PVE::LXC::write_config($vmid, $conf);
+
+ my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir); # detect OS
+
+ PVE::LXC::write_config($vmid, $conf); # safe config (after OS detection)
+
+ $lxc_setup->post_create_hook($password);
+}
+
+# directly use a storage directory
+sub create_rootfs_dir {
+ my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password) = @_;
+
+ # fixme: size is ignored here!
+
+ my $private = PVE::Storage::get_private_dir($storage_conf, $storage, $vmid);
+ mkdir($private) || die "unable to create container private dir '$private' - $!\n";
+
+ push @{$cleanup->{files}}, $private;
+ $conf->{'lxc.rootfs'} = $private;
+
+ restore_and_configure($vmid, $archive, $private, $conf, $password);
+}
+
+# create a raw file, then loop mount
+sub create_rootfs_dir_loop {
+ my ($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password) = @_;
+
+ my $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'raw', "vm-$vmid-rootfs.raw", $size);
+
+ push @{$cleanup->{volids}}, $volid;
+
+ my $image_path = PVE::Storage::path($storage_conf, $volid);
+ $conf->{'lxc.rootfs'} = "loop:${image_path}";
+
+ my $cmd = ['mkfs.ext4', $image_path];
+ PVE::Tools::run_command($cmd);
+
+ print "allocated image: $image_path\n";
+
+ my $mountpoint;
+
+ my $loopdev;
+ eval {
+ my $parser = sub {
+ my $line = shift;
+ $loopdev = $line if $line =~m|^/dev/loop\d+$|;
+ };
+ PVE::Tools::run_command(['losetup', '--find', '--show', $image_path], outfunc => $parser);
+
+ my $tmp = "/var/lib/lxc/$vmid/rootfs";
+ File::Path::mkpath($tmp);
+ PVE::Tools::run_command(['mount', '-t', 'ext4', $loopdev, $tmp]);
+ $mountpoint = $tmp;
+
+ restore_and_configure($vmid, $archive, $mountpoint, $conf, $password);
+ };
+ if (my $err = $@) {
+ if ($mountpoint) {
+ eval { PVE::Tools::run_command(['umount', '-d', $mountpoint]) };
+ warn $@ if $@;
+ } else {
+ eval { PVE::Tools::run_command(['losetup', '-d', $loopdev]) if $loopdev; };
+ warn $@ if $@;
+ }
+ die $err;
+ }
+
+ PVE::Tools::run_command(['umount', '-l', '-d', $mountpoint]);
+}
+
+sub create_rootfs {
+ my ($storage_conf, $storage, $size, $vmid, $conf, $archive, $password) = @_;
+
+ PVE::LXC::create_config($vmid, $conf);
+
+ my $cleanup = { files => [], volids => [] };
+
+ eval {
+ my $scfg = PVE::Storage::storage_config($storage_conf, $storage);
+ if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+ if (1) {
+ create_rootfs_dir_loop($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password);
+ } else {
+ create_rootfs_dir($cleanup, $storage_conf, $storage, $size, $vmid, $conf, $archive, $password);
+ }
+ } else {
+ die "unable to create containers on storage type '$scfg->{type}'\n";
+ }
+ };
+ if (my $err = $@) {
+ # cleanup
+ File::Path::rmtree($cleanup->{files});
+ foreach my $volid (@{$cleanup->{volids}}) {
+ eval { PVE::Storage::vdisk_free($storage_conf, $volid); };
+ warn $@ if $@;
+ }
+
+ PVE::LXC::destroy_config($vmid);
+
+ die $err;
+ }
+}
+
+1;
};
my $autodetect_type = sub {
- my ($conf) = @_;
+ my ($rootdir) = @_;
- my $rootfs = $conf->{'lxc.rootfs'};
- if (-f "$rootfs/etc/debian_version") {
+ if (-f "$rootdir/etc/debian_version") {
return "debian";
}
die "unable to detect OS disribution\n";
};
sub new {
- my ($class, $conf, $type) = @_;
+ my ($class, $conf, $rootdir, $type) = @_;
- my $self = bless { conf => $conf };
+ die "no root directory\n" if !$rootdir || $rootdir eq '/';
+
+ my $self = bless { conf => $conf, $rootdir => $rootdir};
- my $rootfs = $conf->{'lxc.rootfs'};
- die "unable to find root filesystem\n" if !$rootfs || $rootfs eq '/';
-
if (!defined($type)) {
# try to autodetect type
- $type = &$autodetect_type($conf);
+ $type = &$autodetect_type($rootdir);
}
my $plugin_class = $plugins->{$type} ||
"no such OS type '$type'\n";
- $self->{plugin} = $plugin_class->new($conf);
+ $self->{plugin} = $plugin_class->new($conf, $rootdir);
return $self;
}
use PVE::Tools;
sub new {
- my ($class, $conf) = @_;
+ my ($class, $conf, $rootdir) = @_;
- return bless { conf => $conf }, $class;
+ return bless { conf => $conf, rootdir => $rootdir }, $class;
}
my $lookup_dns_conf = sub {
my ($searchdomains, $nameserver) = &$lookup_dns_conf($conf);
- my $rootfs = $conf->{'lxc.rootfs'};
+ my $rootdir = $self->{rootdir};
- my $filename = "$rootfs/etc/resolv.conf";
+ my $filename = "$rootdir/etc/resolv.conf";
my $data = '';
$hostname =~ s/\..*$//;
- my $rootfs = $conf->{'lxc.rootfs'};
+ my $rootdir = $self->{rootdir};
- my $hostname_fn = "$rootfs/etc/hostname";
+ my $hostname_fn = "$rootdir/etc/hostname";
my $oldname = PVE::Tools::file_read_firstline($hostname_fn) || 'localhost';
- my $hosts_fn = "$rootfs/etc/hosts";
+ my $hosts_fn = "$rootdir/etc/hosts";
my $etc_hosts_data = '';
if (-f $hosts_fn) {
sub set_user_password {
my ($self, $conf, $user, $opt_password) = @_;
- my $rootfs = $conf->{'lxc.rootfs'};
+ my $rootdir = $self->{rootdir};
- my $pwfile = "$rootfs/etc/passwd";
+ my $pwfile = "$rootdir/etc/passwd";
return if ! -f $pwfile;
- my $shadow = "$rootfs/etc/shadow";
+ my $shadow = "$rootdir/etc/shadow";
if (defined($opt_password)) {
if ($opt_password !~ m/^\$/) {
use base qw(PVE::LXCSetup::Base);
sub new {
- my ($class, $conf) = @_;
+ my ($class, $conf, $rootdir) = @_;
- my $rootfs = $conf->{'lxc.rootfs'};
-
- my $version = PVE::Tools::file_read_firstline("$rootfs/etc/debian_version");
+ my $version = PVE::Tools::file_read_firstline("$rootdir/etc/debian_version");
die "unable to read version info\n" if !defined($version);
die "unsupported debian version '$version'\n" if $version < 6;
- my $self = { conf => $conf, version => $version };
+ my $self = { conf => $conf, rootdir => $rootdir, version => $version };
+
+ $conf->{'lxc.include'} = "/usr/share/lxc/config/debian.common.conf";
return bless $self, $class;
}
sub setup_init {
my ($self, $conf) = @_;
- my $rootfs = $conf->{'lxc.rootfs'};
+ my $rootdir = $self->{rootdir};
- my $filename = "$rootfs/etc/inittab";
+ my $filename = "$rootdir/etc/inittab";
if (-f $filename) {
my $inittab = $default_inittab;
sub setup_network {
my ($self, $conf) = @_;
- my $rootfs = $conf->{'lxc.rootfs'};
+ my $rootdir = $self->{rootdir};
my $networks = {};
foreach my $k (keys %$conf) {
return if !scalar(keys %$networks);
- my $filename = "$rootfs/etc/network/interfaces";
+ my $filename = "$rootdir/etc/network/interfaces";
my $data = {};
my $order = [];
my $interfaces = "";
-SOURCES=LXC.pm LXCSetup.pm
+SOURCES=LXC.pm LXCSetup.pm LXCCreate.pm
.PHONY: install
install:
#!/usr/bin/perl
+
+# NOTE: we do not use this at all
+
use strict;
use warnings;
use POSIX;
my $raw = PVE::Tools::file_get_contents($param->{path});
my $conf = PVE::LXC::parse_lxc_config($param->{path}, $raw);
- my $lxc_setup = PVE::LXCSetup->new($conf);
+ my $mountpoint = $ENV{LXC_ROOTFS_MOUNT};
+
+ my $lxc_setup = PVE::LXCSetup->new($conf, $mountpoint);
$lxc_setup->pre_start_hook();
-
+
return undef;
}});
} elsif ((scalar(@ARGV) == 3) && ($ARGV[1] eq 'lxc') && ($ARGV[2] eq 'mount')) {
$param->{name} = $ENV{'LXC_NAME'};
die "got wrong name" if $param->{name} ne $ARGV[0];
-
+
$param->{path} = $ENV{'LXC_CONFIG_FILE'};
$param->{rootfs} = $ENV{'LXC_ROOTFS_PATH'};
@ARGV = ();
my $conf = PVE::LXC::parse_lxc_config("/lxc/100/config", $raw);
- $conf->{'lxc.rootfs'} = $rootfs;
$conf->{'pve.test_mode'} = 1;
- my $lxc_setup = PVE::LXCSetup->new($conf);
+ my $lxc_setup = PVE::LXCSetup->new($conf, $rootfs);
for (my $i = 0; $i < 2; $i++) {
# run tests twice, to make sure scripts are idempotent