diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7adbe9e1..d5c20e04 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -128,6 +128,7 @@ jobs: - { name: "partitioning", case: "partitioning" } - { name: "msdos partitioning", case: "msdos" } - { name: "debian (amd64)", case: "debian", variables: "-t architecture:amd64" } + - { name: "debian with mmdebstrap (amd64)", case: "debian", variables: "-t architecture:amd64 -t tool:mmdebstrap" } exclude: - backend: nofakemachine test: { name: "partitioning", case: "partitioning" } diff --git a/actions/mmdebstrap_action.go b/actions/mmdebstrap_action.go new file mode 100644 index 00000000..bc392ed4 --- /dev/null +++ b/actions/mmdebstrap_action.go @@ -0,0 +1,186 @@ +/* +mmdebstrap Action + +Construct the target rootfs with mmdebstrap tool. + +Please keep in mind -- file `/etc/resolv.conf` will be removed after execution. +Most of the OS scripts used by `mmdebstrap` copy `resolv.conf` from the host, +and this may lead to incorrect configuration when becoming part of the created rootfs. + + # Yaml syntax: + - action: mmdebstrap + mirrors: + suite: "name" + components: + variant: "name" + keyring-packages: + keyring-files: + +Mandatory properties: + +- suite -- release code name or symbolic name (e.g. "stable") + +Optional properties: + +- mirrors -- list of URLs with Debian-compatible repository + If no mirror is specified debos will use http://deb.debian.org/debian as default. + +- variant -- name of the bootstrap script variant to use + +- components -- list of components to use for packages selection. + If no components are specified debos will use main as default. + +Example: + components: [ main, contrib ] + +- keyring-packages -- list of keyrings for package validation. + +- keyring-files -- list keyring files for repository validation. + +- merged-usr -- use merged '/usr' filesystem, true by default. + +*/ +package actions + +import ( + "fmt" + "os" + "path" + "strings" + "runtime" + + "github.com/go-debos/debos" + "github.com/go-debos/fakemachine" +) + +type MmdebstrapAction struct { + debos.BaseAction `yaml:",inline"` + Suite string + Mirrors []string + Variant string + KeyringPackages []string `yaml:"keyring-packages"` + KeyringFiles []string `yaml:"keyring-files"` + Components []string + MergedUsr *bool `yaml:"merged-usr"` +} + +func NewMmdebstrapAction() *MmdebstrapAction { + d := MmdebstrapAction{} + // Be secure by default + // Use main as default component + d.Components = []string{"main"} + + return &d +} + +func (d *MmdebstrapAction) listOptionFiles(context *debos.DebosContext) []string { + files := []string{} + + if d.KeyringFiles != nil { + for _, file := range d.KeyringFiles { + file = debos.CleanPathAt(file, context.RecipeDir) + files = append(files, file) + } + } + + return files +} + +func (d *MmdebstrapAction) Verify(context *debos.DebosContext) error { + if len(d.Suite) == 0 { + return fmt.Errorf("suite property not specified") + } + + files := d.listOptionFiles(context) + + // Check if all needed files exists + for _, f := range files { + if _, err := os.Stat(f); os.IsNotExist(err) { + return err + } + } + return nil +} + +func (d *MmdebstrapAction) PreMachine(context *debos.DebosContext, m *fakemachine.Machine, args *[]string) error { + + mounts := d.listOptionFiles(context) + + // Mount configuration files outside of recipes directory + for _, mount := range mounts { + m.AddVolume(path.Dir(mount)) + } + + return nil +} + +func (d *MmdebstrapAction) Run(context *debos.DebosContext) error { + cmdline := []string{"mmdebstrap"} + + if d.MergedUsr != nil { + if *d.MergedUsr { + cmdline = append(cmdline, "--include=usrmerge") + } else { + cmdline = append(cmdline, "--hook-dir=/usr/share/mmdebstrap/hooks/no-merged-usr") + } + } + + if d.KeyringFiles != nil { + s := strings.Join(d.KeyringFiles, ",") + cmdline = append(cmdline, fmt.Sprintf("--keyring=%s", s)) + } + + if d.KeyringPackages != nil { + s := strings.Join(d.KeyringPackages, ",") + cmdline = append(cmdline, fmt.Sprintf("--include=%s", s)) + } + + if d.Components != nil { + s := strings.Join(d.Components, ",") + cmdline = append(cmdline, fmt.Sprintf("--components=%s", s)) + } + + /* Only works for amd64, arm64 and riscv64 hosts, which should be enough */ + foreign := context.Architecture != runtime.GOARCH + + if foreign { + cmdline = append(cmdline, fmt.Sprintf("--architectures=%s", context.Architecture)) + + } + + if d.Variant != "" { + cmdline = append(cmdline, fmt.Sprintf("--variant=%s", d.Variant)) + } + + cmdline = append(cmdline, d.Suite) + cmdline = append(cmdline, context.Rootdir) + + if d.Mirrors != nil { + cmdline = append(cmdline, d.Mirrors...) + } + + /* Make sure files in /etc/apt/ exist inside the fakemachine otherwise + mmdebstrap prints a warning about the path not existing. */ + if fakemachine.InMachine() { + if err := os.MkdirAll(path.Join("/etc/apt/apt.conf.d"), os.ModePerm); err != nil { + return err + } + if err := os.MkdirAll(path.Join("/etc/apt/trusted.gpg.d"), os.ModePerm); err != nil { + return err + } + } + + err := debos.Command{}.Run("Mmdebstrap", cmdline...) + + /* Cleanup resolv.conf after mmdebstrap */ + resolvconf := path.Join(context.Rootdir, "/etc/resolv.conf") + if _, err = os.Stat(resolvconf); !os.IsNotExist(err) { + if err = os.Remove(resolvconf); err != nil { + return err + } + } + + c := debos.NewChrootCommandForContext(*context) + + return c.Run("apt clean", "/usr/bin/apt-get", "clean") +} diff --git a/actions/recipe.go b/actions/recipe.go index 0909117a..c1ae3cff 100644 --- a/actions/recipe.go +++ b/actions/recipe.go @@ -49,6 +49,8 @@ Supported actions - debootstrap -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Debootstrap_Action +- debootstrap -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Mmdebstrap_Action + - download -- https://godoc.org/github.com/go-debos/debos/actions#hdr-Download_Action - filesystem-deploy -- https://godoc.org/github.com/go-debos/debos/actions#hdr-FilesystemDeploy_Action @@ -115,6 +117,8 @@ func (y *YamlAction) UnmarshalYAML(unmarshal func(interface{}) error) error { switch aux.Action { case "debootstrap": y.Action = NewDebootstrapAction() + case "mmdebstrap": + y.Action = NewMmdebstrapAction() case "pacstrap": y.Action = &PacstrapAction{} case "pack": diff --git a/docker/Dockerfile b/docker/Dockerfile index dd4d3ab7..817c210e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -68,6 +68,7 @@ RUN apt-get update && \ ca-certificates \ debian-ports-archive-keyring \ debootstrap \ + mmdebstrap \ dosfstools \ e2fsprogs \ equivs \ diff --git a/tests/debian/test.yaml b/tests/debian/test.yaml index bf4adb2a..eeb650e2 100644 --- a/tests/debian/test.yaml +++ b/tests/debian/test.yaml @@ -1,9 +1,10 @@ --- {{- $architecture := or .architecture "amd64"}} +{{- $tool := or .tool "debootstrap" }} architecture: {{$architecture}} actions: - - action: debootstrap + - action: {{ $tool }} suite: bullseye variant: minbase merged-usr: true