sbuild-createchroot: add --chroot-mode=unshare which allows running...

sbuild-createchroot: add --chroot-mode=unshare which allows running debootstrap and creating a chroot tarball without needing sudo
parent 7c0cb4a0
......@@ -28,6 +28,7 @@ bin_SCRIPTS = \
sbuild-abort \
sbuild-apt \
sbuild-checkpackages \
sbuild-createchroot \
sbuild-debian-developer-setup \
sbuild-update \
sbuild-upgrade \
......@@ -45,7 +46,6 @@ bin_SCRIPTS = \
sbin_SCRIPTS = \
sbuild-adduser \
sbuild-createchroot \
sbuild-destroychroot
sbuilddata_SCRIPTS = \
......
......@@ -24,6 +24,8 @@
use strict;
use warnings;
use English;
use Sbuild::AptResolver;
package Conf;
......@@ -181,9 +183,11 @@ use POSIX;
use Getopt::Long qw(:config no_ignore_case auto_abbrev gnu_getopt);
use Sbuild qw(dump_file help_text version_text usage_error check_packages);
use Sbuild::ChrootPlain;
use Sbuild::ChrootUnshare;
use Sbuild::ChrootRoot;
use Sbuild::Sysconfig;
use Sbuild::Conf qw();
use Sbuild::Utility;
use File::Basename qw(dirname);
use File::Path qw(mkpath rmtree);
use File::Temp qw(tempfile);
......@@ -254,6 +258,18 @@ if ($conf->get('CHROOT_MODE') eq 'schroot') {
waitpid($pid, 0);
}
my $target = $ARGV[1];
if (-e $target) {
if (!-d $target) {
die "$target exists and is not a directory";
}
# attempt to remove target. If the directory was not empty, then this
# operation will fail and we can die. If the directory was empty, then we
# will recreate it anyways
if (!rmdir($target)) {
die "$target is not empty";
}
}
# Create the target directory in advance so abs_path (which is buggy)
# won't fail. Remove if abs_path is replaced by something better.
makedir($ARGV[1], 0755);
......@@ -298,17 +314,86 @@ if ($conf->get('VERBOSE')) {
print "I: Running $debootstrap_bin " . join(' ',@args) . "\n";
}
my @idmap;
if ($conf->get('CHROOT_MODE') eq 'unshare') {
@idmap = read_subuid_subgid;
# sanity check
if (scalar(@idmap) != 2 || $idmap[0][0] ne 'u' || $idmap[1][0] ne 'g') {
printf STDERR "invalid idmap\n";
return 0;
}
}
# Run debootstrap with specified options.
if (!$conf->get('SETUP_ONLY')) {
if ($conf->get('CHROOT_MODE') eq 'unshare') {
exit 42;
makedir($target, 0755);
my $outer_gid = $REAL_GROUP_ID+0;
system(get_unshare_cmd({
IDMAP => [['u', '0', $REAL_USER_ID, '1'],
['g', '0', $outer_gid, '1'],
['u', '1', $idmap[0][2], '1'],
['g', '1', $idmap[1][2], '1'],
]
}), 'chown', '1:1', $target);
my @cmd = ('env', 'PATH=/usr/sbin:/usr/bin:/sbin:/bin',
get_unshare_cmd({UNSHARE_FLAGS => CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC, FORK => 1, IDMAP => \@idmap}), 'sh', '-c', "
rootdir=\"\$1\"; shift;
hostname sbuild;
mkdir -p \"\$rootdir/dev\";
for f in null zero full random urandom tty; do
touch \"\$rootdir/dev/\$f\";
chmod -rwx \"\$rootdir/dev/\$f\";
mount -o bind \"/dev/\$f\" \"\$rootdir/dev/\$f\";
done;
mkdir -p \"\$rootdir/sys\";
mount -o rbind /sys \"\$rootdir/sys\";
mkdir -p \"\$rootdir/proc\";
mount -t proc proc \"\$rootdir/proc\";
mkdir -p \"\$rootdir/fakebin\";
ln -sf /bin/true \"\$rootdir/fakebin/mknod\";
ln -sf /bin/true \"\$rootdir/fakebin/mount\";
export PATH=\"\$rootdir/fakebin:/fakebin:\$PATH\"
\"\$@\";
exit_status=\$?;
rm \"\$rootdir/fakebin/mknod\" \"\$rootdir/fakebin/mount\";
rm -d \"\$rootdir/fakebin\";
exit \$exit_status;
", '--', $target, $debootstrap, @args
);
!system(@cmd) or die "E: Error running @cmd";
} else {
!system($debootstrap, @args) or die "E: Error running $debootstrap_bin";
}
}
if (!($conf->get('SETUP_ONLY') && $conf->get('MAKE_SBUILD_TARBALL'))) {
my $sources_list = "";
# Add deb-src to /etc/apt/sources.list.
if ($conf->get('DEB_SRC')) {
my $comps = join(' ',split(/,/,$conf->get('COMPONENTS')));
$sources_list .= "deb-src $mirror $suite $comps\n";
}
# Add extra repositories to /etc/apt/sources.list
for my $repo (@{$conf->get('EXTRA_REPOSITORIES')}) {
$sources_list .= "$repo\n";
}
my $passwd_sbuild = `getent passwd sbuild`;
my $group_sbuild = `getent group sbuild`;
my $setup_script = <<"EOF";
open (my \$passwd_fd, ">>", "\$target/etc/passwd") or die "cannot open /etc/passwd";
print \$passwd_fd \$passwd_sbuild;
close(\$passwd_fd);
open (my \$group_fd, ">>", "\$target/etc/group") or die "cannot open /etc/group";
print \$group_fd \$group_sbuild;
close(\$group_fd);
# Set up minimal /etc/hosts if it didn't exist yet. Normally, the package
# netbase would create the file.
my \$hosts = "\${target}/etc/hosts";
......@@ -351,31 +436,47 @@ chmod(0775, \$policy_rc_d) == 1
# Display /usr/sbin/policy-rc.d.
print "I: Configured /usr/sbin/policy-rc.d:\n";
dump_file("\$policy_rc_d");
EOF
if (\$conf->get('DEB_SRC') || scalar \@{\$conf->get('EXTRA_REPOSITORIES')} > 0) {
my \$sources = "\${target}/etc/apt/sources.list";
open(SOURCES, ">>\$sources")
or die "E: Can't open \$sources for writing";
# Add deb-src to /etc/apt/sources.list.
if (\$conf->get('DEB_SRC')) {
my \$comps = join(' ',split(/,/,\$conf->get('COMPONENTS')));
print SOURCES "deb-src \$mirror \$suite \$comps\n";
}
if ($conf->get('DEB_SRC') || scalar @{$conf->get('EXTRA_REPOSITORIES')} > 0) {
$setup_script .= <<"EOF";
my \$sources = "\${target}/etc/apt/sources.list";
open(SOURCES, ">>\$sources")
or die "E: Can't open \$sources for writing";
# Add extra repositories to /etc/apt/sources.list
for my \$repo (\@{\$conf->get('EXTRA_REPOSITORIES')}) {
print SOURCES "\$repo\n";
print SOURCES <<"EOF2";
$sources_list
EOF2
close SOURCES or die "E: Can't close \$sources";
EOF
}
close SOURCES or die "E: Can't close \$sources";
}
$setup_script .= <<"EOF";
# Display /etc/apt/sources.list.
print "I: Configured APT /etc/apt/sources.list:\n";
dump_file("\${target}/etc/apt/sources.list");
print "I: Please add any additional APT sources to \${target}/etc/apt/sources.list\n";
EOF
eval $setup_script;
if ($conf->get('CHROOT_MODE') eq 'unshare') {
my $group_sbuild = `getent group sbuild`;
$setup_script = <<"EOF";
use strict;
use warnings;
use Sbuild qw(dump_file);
my \$target = \$ARGV[0];
my \$passwd_sbuild = \$ARGV[1];
my \$group_sbuild = \$ARGV[2];
$setup_script
EOF
!system(get_unshare_cmd(
{IDMAP => \@idmap}), 'perl', '-e', $setup_script, $target, $passwd_sbuild, $group_sbuild
) or die "E: failed running setup script";
} else {
eval $setup_script;
if ($@) {
die "E: failed running setup script: $@\n";
}
}
}
if ($conf->get('CHROOT_MODE') eq 'schroot') {
......@@ -502,12 +603,8 @@ if ($conf->get('CHROOT_MODE') eq 'schroot' || $conf->get('CHROOT_MODE') eq 'sudo
}
if (!$conf->get('SETUP_ONLY') || !$conf->get('MAKE_SBUILD_TARBALL')) {
# FIXME: adapt for unshare
# Add sbuild user and group to chroot
system("getent passwd sbuild >> '$target/etc/passwd'");
system("getent group sbuild >> '$target/etc/group'");
if ($conf->get('ARCH') eq $conf->get('HOST_ARCH')) {
# FIXME: also update packages with the unshare backend
if ($conf->get('ARCH') eq $conf->get('HOST_ARCH') && $conf->get('CHROOT_MODE') ne 'unshare') {
my $session = Sbuild::ChrootPlain->new($conf, $target);
my $host = Sbuild::ChrootRoot->new($conf);
if (defined($session)) {
......@@ -540,7 +637,7 @@ if (!$conf->get('SETUP_ONLY') || !$conf->get('MAKE_SBUILD_TARBALL')) {
$session = undef;
}
}
} else {
} elsif ($conf->get('ARCH') ne $conf->get('HOST_ARCH')) {
print "W: The selected architecture and the current architecture do not match\n";
print "W: (" . $conf->get('BUILD_ARCH') . " versus " . $conf->get('ARCH') . ").\n";
print "W: Not automatically updating APT package lists.\n";
......@@ -553,25 +650,39 @@ if (!$conf->get('SETUP_ONLY') || !$conf->get('MAKE_SBUILD_TARBALL')) {
# the sbuild chroot directory created, unless it's been requested to keep the
# directory.
if ($conf->get('MAKE_SBUILD_TARBALL') && !$conf->get('SETUP_ONLY')) {
# FIXME: adapt for unshare
my ($tmpfh, $tmpfile) = tempfile("XXXXXX");
my @program_list;
# Change program arguments accordingly
if ($conf->get('MAKE_SBUILD_TARBALL') =~ /\.tar$/) {
@program_list = ("/bin/tar", "-cf", "$tmpfile", "-C", "$target", "./");
@program_list = ("/bin/tar", "-c", "-C", "$target");
} elsif ($conf->get('MAKE_SBUILD_TARBALL') =~ /(\.tar\.gz|\.tgz)$/) {
@program_list = ("/bin/tar", "-czf", "$tmpfile", "-C", "$target", "./");
@program_list = ("/bin/tar", "-cz", "-C", "$target");
} elsif ($conf->get('MAKE_SBUILD_TARBALL') =~ /(\.tar\.bz2|\.tbz)$/) {
@program_list = ("/bin/tar", "-cjf", "$tmpfile", "-C", "$target", "./");
@program_list = ("/bin/tar", "-cj", "-C", "$target");
} elsif ($conf->get('MAKE_SBUILD_TARBALL') =~ /(\.tar\.lz|\.tlz)$/) {
@program_list = ("/bin/tar", "--lzma", "-cf", "$tmpfile", "-C", "$target", "./");
@program_list = ("/bin/tar", "--lzma", "-c", "-C", "$target");
} elsif ($conf->get('MAKE_SBUILD_TARBALL') =~ /(\.tar\.xz|\.txz)$/) {
@program_list = ("/bin/tar", "-cJf", "$tmpfile", "-C", "$target", "./");
@program_list = ("/bin/tar", "-cJ", "-C", "$target");
}
if ($conf->get('CHROOT_MODE') ne 'unshare') {
push @program_list, '-f', $tmpfile;
}
push @program_list, './';
print "I: Creating tarball...\n";
system(@program_list) == 0 or die "Could not create chroot tarball: $?\n";
if ($conf->get('CHROOT_MODE') eq 'unshare') {
open(my $in, '-|', get_unshare_cmd(
{IDMAP => \@idmap}), @program_list
) or die "could not exec tar";
open(my $out, '>', $tmpfile);
copy($in, $out);
close($out) or die "Failed to close $tmpfile";
close($in) or die "Could not create chroot tarball: $?\n";
} else {
system(@program_list) == 0 or die "Could not create chroot tarball: $?\n";
}
makedir(dirname($conf->get('MAKE_SBUILD_TARBALL')), 0755);
move("$tmpfile", $conf->get('MAKE_SBUILD_TARBALL'));
......@@ -580,7 +691,15 @@ if ($conf->get('MAKE_SBUILD_TARBALL') && !$conf->get('SETUP_ONLY')) {
print "I: Done creating " . $conf->get('MAKE_SBUILD_TARBALL') . "\n";
if (! $conf->get('KEEP_SBUILD_CHROOT_DIR')) {
rmtree("$target");
if ($conf->get('CHROOT_MODE') eq 'unshare') {
# this looks like a recipe for disaster, but since we execute "rm -rf" with
# lxc-usernsexec, we only have permission to delete the files that were
# created with the fake root user
system(get_unshare_cmd({IDMAP => \@idmap}), 'rm', '-rf', $target);
rmdir($target) or die "Unable to remove $target";
} else {
rmtree("$target");
}
print "I: chroot $target has been removed.\n";
} else {
print "I: chroot $target has been kept.\n";
......
......@@ -24,6 +24,7 @@ sbuild\-createchroot \- create sbuild chroot
.RB [ \-\-arch=\fIarch\fP ]
.RB [ \-\-chroot-prefix=\fIprefix\fP ]
.RB [ \-\-chroot-suffix=\fIsuffix\fP ]
.RB [ \-\-chroot-mode=\fIschroot|sudo|unshare\fP ]
.RB [ \-\-foreign ]
.RB [ \-\-resolve-deps " \[or] " \-\-no-resolve-deps ]
.RB [ \-\-keep-debootstrap-dir ]
......@@ -108,6 +109,20 @@ also be used to create chroots with any other type of customization. It is a
shortcut for creating a base chroot and then having to manually copy it and
edit the configuration files.
.TP
.BR \-\-chroot-mode=\fIschroot|sudo|unshare\fP
The sbuild chroot backend to generate the chroot for. The autopkgtest backend
is not supported by this method because of the diversity of container types it
supports. To generate a chroot for schroot and sudo, sbuild-createchroot must
be executed with superuser privileges (for example by using sudo). Because of
backwards compatibility, choosing schroot implies sudo (but not the other way
round). Choosing unshare requires the \fI\-\-make\-sbuild\-tarball\fP option.
Creating a chroot for the unshare backend can be done by a normal user (without
sudo) but it requires Linux user namespaces to be enabled (via "sysctl -w
kernel.unprivileged_userns_clone=1"). Chroot tarballs created for the unshare
backend are also compatible with the schroot backend if a respective schroot
config file is manually created by the user or by running sbuild-createchroot
with the \fI--setup-only\fP option. Defaults to 'schroot'.
.TP
.BR \-\-foreign
Only perform the initial unpack phase of bootstrapping. This is required if
the target and host architectures do not match. Note that debootstrap requires
......@@ -167,6 +182,8 @@ debootstrap script to run. Not typically required.
Don't run debootstrap. Only perform the setup tasks on an already existing
chroot. This is useful for converting an existing chroot for use with sbuild
which has already been created using a tool such as debootstrap.
Some configuration steps can only be carried out on a directory chroot and
thus, this option is not useful with the unshare backend.
.TP
.BR \-\-make\-sbuild\-tarball=\fIfile\fP
Create a bootstrapped file type chroot ready for use with sbuild and save it as
......@@ -194,6 +211,9 @@ Debian unstable is also known as sid. Additionally, sbuild chooses the
distribution by the latest changelog entry which could list \fIUNRELEASED\fP
for packages that the maintainer is currently working on. For Debian it thus
makes sense to add \fIUNRELEASED\fP as an alias for a Debian unstable chroot.
This option is only allowed when choosing \fI--chroot-mode=schroot\fP.
For similar functionality with \fI--chroot-mode=unshare\fP you can work with
symlinks.
See the EXAMPLES section for how to use this option in practice.
.TP
.BR \-\-extra\-repository=\fIspec\fP
......@@ -212,7 +232,8 @@ this repository. See the EXAMPLES section for how to combine this option with
.BR \-\-command\-prefix=\fIprefix\fP
Set the chroot \fIcommand-prefix\fP option as specified. A common use-case is
to specify eatmydata, thereby preventing all commands executed in the chroot
from syncing data to disk. See
from syncing data to disk. This option is only allowed when choosing
\fI--chroot-mode=schroot\fP. See
.BR schroot.conf (5)
for more details.
.SH TARBALL FILE
......@@ -240,7 +261,8 @@ If no extension is specified, sbuild\-createchroot will rename \fIfile\fP to
\fIfile.tar.gz\fP and generate \fIfile.tar.gz\fP as a compressed tarball using gzip.
.SH EXAMPLES
To create a bootstrapped file type sid (unstable) chroot ready for use with
sbuild and saved in \fI/srv/chroot/unstable-amd64.tar.gz\fP using the
sbuild and schroot (the default backend), saved in
\fI/srv/chroot/unstable-amd64.tar.gz\fP using the
\fIhttpredir.debian.org\fP Debian http mirror redirector service and using a
temporary directory as the target:
.PP
......@@ -252,9 +274,10 @@ temporary directory as the target:
.br
\f[CB] http://httpredir.debian.org/debian\fP\[CR]
.PP
To create a plain type sid (unstable) chroot in \fI/srv/chroot/unstable\fP using
the \fIhttpredir.debian.org\fP Debian http mirror redirector service and with
aliases for \fIunstable\fP and \fIUNRELEASED\fP:
To create a plain type sid (unstable) schroot chroot in
\fI/srv/chroot/unstable\fP using the \fIhttpredir.debian.org\fP Debian http
mirror redirector service and with aliases for \fIunstable\fP and
\fIUNRELEASED\fP:
.PP
\f[CR]% \f[CB]sudo sbuild\-createchroot sid /srv/chroot/unstable-amd64 \fP\fP\\
.br
......@@ -262,7 +285,7 @@ aliases for \fIunstable\fP and \fIUNRELEASED\fP:
.br
\f[CB] http://httpredir.debian.org/debian\fP\[CR]
.PP
To create a chroot to build for Jessie backports:
To create a schroot chroot to build for Jessie backports:
.PP
\f[CR]% \f[CB]sudo sbuild\-createchroot \fP\fP\\
.br
......@@ -274,7 +297,7 @@ To create a chroot to build for Jessie backports:
.br
\f[CB] http://httpredir.debian.org/debian\fP\[CR]
.PP
To create a chroot to build for experimental using a custom chroot prefix:
To create a schroot chroot to build for experimental using a custom chroot prefix:
.PP
\f[CR]% \f[CB]sudo sbuild\-createchroot \fP\fP\\
.br
......@@ -285,6 +308,17 @@ To create a chroot to build for experimental using a custom chroot prefix:
unstable /srv/chroot/unstable-experimental-amd64 \fP\fP\\
.br
\f[CB] http://httpredir.debian.org/debian\fP\[CR]
.PP
Creating a chroot for the unshare backend does not require superuser privileges
(no sudo). The path for the tarball is the default path expected by sbuild for
the unshare backend:
.PP
\f[CR]% \f[CB]sbuild-createchroot --chroot-mode=unshare \\
.br
--make-sbuild-tarball=~/.local/share/sbuild/unstable-amd64.tar.gz \\
.br
sid `mktemp -d` http://httpredir.debian.org/debian/ \\
.PP
.SH HISTORY
sbuild\-createchroot was previously known as \fBbuildd.chroot\fP.
buildd.chroot performed exactly the same tasks, but additionally created a
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment