32 |
33 | {%- if show_copyright %}
34 |
35 | {%- if hasdoc('copyright') %}
36 | {% trans path=pathto('copyright'), copyright=copyright|e -%}
37 |
Copyright © {{ copyright }}
38 | {%- endtrans %}
39 | {%- else %}
40 | {% trans copyright=copyright|e -%}
41 | Copyright © {{ copyright }}
42 | {%- endtrans %}
43 | {%- endif %}
44 |
45 | {%- endif %}
46 |
47 | {# ru-fu: removed "Made with" #}
48 |
49 | {%- if last_updated -%}
50 |
51 | {% trans last_updated=last_updated|e -%}
52 | Last updated on {{ last_updated }}
53 | {%- endtrans -%}
54 |
55 | {%- endif %}
56 |
57 |
58 |
59 | {# ru-fu: replaced RTD icons with our links #}
60 |
61 | {%- if show_source and has_source and sourcename %}
62 |
66 | {%- endif %}
67 | {% if github_url and github_version and github_folder and github_filetype and has_source and sourcename %}
68 |
71 | {% endif %}
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/sources/funtoo-http.go:
--------------------------------------------------------------------------------
1 | package sources
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/url"
7 | "path/filepath"
8 |
9 | "github.com/lxc/distrobuilder/shared"
10 | )
11 |
12 | type funtoo struct {
13 | common
14 | }
15 |
16 | // Run downloads a Funtoo stage3 tarball.
17 | func (s *funtoo) Run() error {
18 | topLevelArch := s.definition.Image.ArchitectureMapped
19 |
20 | switch topLevelArch {
21 | case "generic_32":
22 | topLevelArch = "x86-32bit"
23 | case "generic_64":
24 | topLevelArch = "x86-64bit"
25 | case "armv7a_vfpv3_hardfp":
26 | topLevelArch = "arm-32bit"
27 | case "arm64_generic":
28 | topLevelArch = "arm-64bit"
29 | }
30 |
31 | // Keep release backward compatible to old implementation
32 | // and to permit to have yet the funtoo/1.4 alias.
33 | if s.definition.Image.Release == "1.4" {
34 | s.definition.Image.Release = "1.4-release-std"
35 | }
36 |
37 | baseURL := fmt.Sprintf("%s/%s/%s/%s",
38 | s.definition.Source.URL, s.definition.Image.Release,
39 | topLevelArch, s.definition.Image.ArchitectureMapped)
40 |
41 | // Get the latest release tarball.
42 | fname := "stage3-latest.tar.xz"
43 | tarball := fmt.Sprintf("%s/%s", baseURL, fname)
44 |
45 | url, err := url.Parse(tarball)
46 | if err != nil {
47 | return fmt.Errorf("Failed to parse URL %q: %w", tarball, err)
48 | }
49 |
50 | if !s.definition.Source.SkipVerification && url.Scheme != "https" &&
51 | len(s.definition.Source.Keys) == 0 {
52 | return errors.New("GPG keys are required if downloading from HTTP")
53 | }
54 |
55 | var fpath string
56 |
57 | fpath, err = s.DownloadHash(s.definition.Image, tarball, "", nil)
58 | if err != nil {
59 | return fmt.Errorf("Failed to download %q: %w", tarball, err)
60 | }
61 |
62 | // Force gpg checks when using http
63 | if !s.definition.Source.SkipVerification && url.Scheme != "https" {
64 | _, err = s.DownloadHash(s.definition.Image, tarball+".gpg", "", nil)
65 | if err != nil {
66 | return fmt.Errorf("Failed to download %q: %w", tarball+".gpg", err)
67 | }
68 |
69 | valid, err := s.VerifyFile(
70 | filepath.Join(fpath, fname),
71 | filepath.Join(fpath, fname+".gpg"))
72 | if err != nil {
73 | return fmt.Errorf("Failed to verify file: %w", err)
74 | }
75 |
76 | if !valid {
77 | return fmt.Errorf("Invalid signature for %q", filepath.Join(fpath, fname))
78 | }
79 | }
80 |
81 | s.logger.WithField("file", filepath.Join(fpath, fname)).Info("Unpacking image")
82 |
83 | // Unpack
84 | err = shared.Unpack(filepath.Join(fpath, fname), s.rootfsDir)
85 | if err != nil {
86 | return fmt.Errorf("Failed to unpack %q: %w", filepath.Join(fpath, fname), err)
87 | }
88 |
89 | return nil
90 | }
91 |
--------------------------------------------------------------------------------
/generators/hosts_test.go:
--------------------------------------------------------------------------------
1 | package generators
2 |
3 | import (
4 | "context"
5 | "os"
6 | "path/filepath"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 |
11 | "github.com/lxc/distrobuilder/image"
12 | "github.com/lxc/distrobuilder/shared"
13 | )
14 |
15 | func TestHostsGeneratorRunLXC(t *testing.T) {
16 | cacheDir, err := os.MkdirTemp(os.TempDir(), "distrobuilder-test-")
17 | require.NoError(t, err)
18 |
19 | rootfsDir := filepath.Join(cacheDir, "rootfs")
20 |
21 | setup(t, cacheDir)
22 | defer teardown(cacheDir)
23 |
24 | generator, err := Load("hosts", nil, cacheDir, rootfsDir, shared.DefinitionFile{Path: "/etc/hosts"}, shared.Definition{})
25 | require.IsType(t, &hosts{}, generator)
26 | require.NoError(t, err)
27 |
28 | definition := shared.Definition{
29 | Image: shared.DefinitionImage{
30 | Distribution: "ubuntu",
31 | Release: "artful",
32 | },
33 | }
34 |
35 | image := image.NewLXCImage(context.TODO(), cacheDir, "", cacheDir, definition)
36 |
37 | err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0o755)
38 | require.NoError(t, err)
39 |
40 | createTestFile(t, filepath.Join(cacheDir, "rootfs", "etc", "hosts"),
41 | "127.0.0.1\tlocalhost\n127.0.0.1\tdistrobuilder\n")
42 |
43 | err = generator.RunLXC(image, shared.DefinitionTargetLXC{})
44 | require.NoError(t, err)
45 |
46 | validateTestFile(t, filepath.Join(cacheDir, "rootfs", "etc", "hosts"),
47 | "127.0.0.1\tlocalhost\n127.0.0.1\tLXC_NAME\n")
48 | }
49 |
50 | func TestHostsGeneratorRunIncus(t *testing.T) {
51 | cacheDir, err := os.MkdirTemp(os.TempDir(), "distrobuilder-test-")
52 | require.NoError(t, err)
53 |
54 | rootfsDir := filepath.Join(cacheDir, "rootfs")
55 |
56 | setup(t, cacheDir)
57 | defer teardown(cacheDir)
58 |
59 | generator, err := Load("hosts", nil, cacheDir, rootfsDir, shared.DefinitionFile{Path: "/etc/hosts"}, shared.Definition{})
60 | require.IsType(t, &hosts{}, generator)
61 | require.NoError(t, err)
62 |
63 | definition := shared.Definition{
64 | Image: shared.DefinitionImage{
65 | Distribution: "ubuntu",
66 | Release: "artful",
67 | },
68 | }
69 |
70 | image := image.NewIncusImage(context.TODO(), cacheDir, "", cacheDir, definition)
71 |
72 | err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0o755)
73 | require.NoError(t, err)
74 |
75 | createTestFile(t, filepath.Join(cacheDir, "rootfs", "etc", "hosts"),
76 | "127.0.0.1\tlocalhost\n127.0.0.1\tdistrobuilder\n")
77 |
78 | err = generator.RunIncus(image, shared.DefinitionTargetIncus{})
79 | require.NoError(t, err)
80 |
81 | validateTestFile(t, filepath.Join(cacheDir, "templates", "hosts.tpl"),
82 | "127.0.0.1\tlocalhost\n127.0.0.1\t{{ container.name }}\n")
83 | }
84 |
--------------------------------------------------------------------------------
/generators/hostname.go:
--------------------------------------------------------------------------------
1 | package generators
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/lxc/incus/v6/shared/api"
9 | incus "github.com/lxc/incus/v6/shared/util"
10 |
11 | "github.com/lxc/distrobuilder/image"
12 | "github.com/lxc/distrobuilder/shared"
13 | )
14 |
15 | type hostname struct {
16 | common
17 | }
18 |
19 | // RunLXC creates a hostname template.
20 | func (g *hostname) RunLXC(img *image.LXCImage, target shared.DefinitionTargetLXC) error {
21 | // Skip if the file doesn't exist
22 | if !incus.PathExists(filepath.Join(g.sourceDir, g.defFile.Path)) {
23 | return nil
24 | }
25 |
26 | // Create new hostname file
27 | file, err := os.Create(filepath.Join(g.sourceDir, g.defFile.Path))
28 | if err != nil {
29 | return fmt.Errorf("Failed to create file %q: %w", filepath.Join(g.sourceDir, g.defFile.Path), err)
30 | }
31 |
32 | defer file.Close()
33 |
34 | // Write LXC specific string to the hostname file
35 | _, err = file.WriteString("LXC_NAME\n")
36 | if err != nil {
37 | return fmt.Errorf("Failed to write to hostname file: %w", err)
38 | }
39 |
40 | // Add hostname path to LXC's templates file
41 | err = img.AddTemplate(g.defFile.Path)
42 | if err != nil {
43 | return fmt.Errorf("Failed to add template: %w", err)
44 | }
45 |
46 | return nil
47 | }
48 |
49 | // RunIncus creates a hostname template.
50 | func (g *hostname) RunIncus(img *image.IncusImage, target shared.DefinitionTargetIncus) error {
51 | // Skip if the file doesn't exist
52 | if !incus.PathExists(filepath.Join(g.sourceDir, g.defFile.Path)) {
53 | return nil
54 | }
55 |
56 | templateDir := filepath.Join(g.cacheDir, "templates")
57 |
58 | err := os.MkdirAll(templateDir, 0o755)
59 | if err != nil {
60 | return fmt.Errorf("Failed to create directory %q: %w", templateDir, err)
61 | }
62 |
63 | file, err := os.Create(filepath.Join(templateDir, "hostname.tpl"))
64 | if err != nil {
65 | return fmt.Errorf("Failed to create file %q: %w", filepath.Join(templateDir, "hostname.tpl"), err)
66 | }
67 |
68 | defer file.Close()
69 |
70 | _, err = file.WriteString("{{ container.name }}\n")
71 | if err != nil {
72 | return fmt.Errorf("Failed to write to hostname file: %w", err)
73 | }
74 |
75 | // Add to Incus templates
76 | img.Metadata.Templates[g.defFile.Path] = &api.ImageMetadataTemplate{
77 | Template: "hostname.tpl",
78 | Properties: g.defFile.Template.Properties,
79 | When: g.defFile.Template.When,
80 | }
81 |
82 | if len(g.defFile.Template.When) == 0 {
83 | img.Metadata.Templates[g.defFile.Path].When = []string{
84 | "create",
85 | "copy",
86 | }
87 | }
88 |
89 | return nil
90 | }
91 |
92 | // Run does nothing.
93 | func (g *hostname) Run() error {
94 | return nil
95 | }
96 |
--------------------------------------------------------------------------------
/managers/common.go:
--------------------------------------------------------------------------------
1 | package managers
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/sirupsen/logrus"
7 |
8 | "github.com/lxc/distrobuilder/shared"
9 | )
10 |
11 | type common struct {
12 | commands managerCommands
13 | flags managerFlags
14 | hooks managerHooks
15 | logger *logrus.Logger
16 | definition shared.Definition
17 | ctx context.Context
18 | }
19 |
20 | func (c *common) init(ctx context.Context, logger *logrus.Logger, definition shared.Definition) {
21 | c.logger = logger
22 | c.definition = definition
23 | c.ctx = ctx
24 | }
25 |
26 | // Install installs packages to the rootfs.
27 | func (c *common) install(pkgs, flags []string) error {
28 | if len(c.flags.install) == 0 || pkgs == nil || len(pkgs) == 0 {
29 | return nil
30 | }
31 |
32 | args := append(c.flags.global, c.flags.install...)
33 | args = append(args, flags...)
34 | args = append(args, pkgs...)
35 |
36 | return shared.RunCommand(c.ctx, nil, nil, c.commands.install, args...)
37 | }
38 |
39 | // Remove removes packages from the rootfs.
40 | func (c *common) remove(pkgs, flags []string) error {
41 | if len(c.flags.remove) == 0 || pkgs == nil || len(pkgs) == 0 {
42 | return nil
43 | }
44 |
45 | args := append(c.flags.global, c.flags.remove...)
46 | args = append(args, flags...)
47 | args = append(args, pkgs...)
48 |
49 | return shared.RunCommand(c.ctx, nil, nil, c.commands.remove, args...)
50 | }
51 |
52 | // Clean cleans up cached files used by the package managers.
53 | func (c *common) clean() error {
54 | var err error
55 |
56 | if len(c.flags.clean) == 0 {
57 | return nil
58 | }
59 |
60 | args := append(c.flags.global, c.flags.clean...)
61 |
62 | err = shared.RunCommand(c.ctx, nil, nil, c.commands.clean, args...)
63 | if err != nil {
64 | return err
65 | }
66 |
67 | if c.hooks.clean != nil {
68 | err = c.hooks.clean()
69 | }
70 |
71 | return err
72 | }
73 |
74 | // Refresh refreshes the local package database.
75 | func (c *common) refresh() error {
76 | if len(c.flags.refresh) == 0 {
77 | return nil
78 | }
79 |
80 | if c.hooks.preRefresh != nil {
81 | err := c.hooks.preRefresh()
82 | if err != nil {
83 | return err
84 | }
85 | }
86 |
87 | args := append(c.flags.global, c.flags.refresh...)
88 |
89 | return shared.RunCommand(c.ctx, nil, nil, c.commands.refresh, args...)
90 | }
91 |
92 | // Update updates all packages.
93 | func (c *common) update() error {
94 | if len(c.flags.update) == 0 {
95 | return nil
96 | }
97 |
98 | args := append(c.flags.global, c.flags.update...)
99 |
100 | return shared.RunCommand(c.ctx, nil, nil, c.commands.update, args...)
101 | }
102 |
103 | func (c *common) manageRepository(repo shared.DefinitionPackagesRepository) error {
104 | return nil
105 | }
106 |
--------------------------------------------------------------------------------
/shared/archive_linux.go:
--------------------------------------------------------------------------------
1 | package shared
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "os"
9 | "strings"
10 |
11 | "github.com/lxc/incus/v6/shared/archive"
12 | "github.com/lxc/incus/v6/shared/subprocess"
13 | "golang.org/x/sys/unix"
14 | )
15 |
16 | // Unpack unpacks a tarball.
17 | func Unpack(file string, path string) error {
18 | extractArgs, extension, _, err := archive.DetectCompression(file)
19 | if err != nil {
20 | return err
21 | }
22 |
23 | command := ""
24 | args := []string{}
25 | var reader io.Reader
26 | if strings.HasPrefix(extension, ".tar") {
27 | command = "tar"
28 | args = append(args, "--restrict", "--force-local")
29 | args = append(args, "-C", path, "--numeric-owner", "--xattrs-include=*")
30 | args = append(args, extractArgs...)
31 | args = append(args, "-")
32 |
33 | f, err := os.Open(file)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | defer f.Close()
39 |
40 | reader = f
41 | } else if strings.HasPrefix(extension, ".squashfs") {
42 | // unsquashfs does not support reading from stdin,
43 | // so ProgressTracker is not possible.
44 | command = "unsquashfs"
45 | args = append(args, "-f", "-d", path, "-n", file)
46 | } else {
47 | return fmt.Errorf("Unsupported image format: %s", extension)
48 | }
49 |
50 | err = subprocess.RunCommandWithFds(context.TODO(), reader, nil, command, args...)
51 | if err != nil {
52 | // We can't create char/block devices in unpriv containers so ignore related errors.
53 | if command == "unsquashfs" {
54 | var runError *subprocess.RunError
55 |
56 | ok := errors.As(err, &runError)
57 | if !ok || runError.StdErr().String() == "" {
58 | return err
59 | }
60 |
61 | // Confirm that all errors are related to character or block devices.
62 | found := false
63 | for _, line := range strings.Split(runError.StdErr().String(), "\n") {
64 | line = strings.TrimSpace(line)
65 | if line == "" {
66 | continue
67 | }
68 |
69 | if !strings.Contains(line, "failed to create block device") {
70 | continue
71 | }
72 |
73 | if !strings.Contains(line, "failed to create character device") {
74 | continue
75 | }
76 |
77 | // We found an actual error.
78 | found = true
79 | }
80 |
81 | if !found {
82 | // All good, assume everything unpacked.
83 | return nil
84 | }
85 | }
86 |
87 | // Check if we ran out of space
88 | fs := unix.Statfs_t{}
89 |
90 | err1 := unix.Statfs(path, &fs)
91 | if err1 != nil {
92 | return err1
93 | }
94 |
95 | // Check if we're running out of space
96 | if int64(fs.Bfree) < 10 {
97 | return fmt.Errorf("Unable to unpack image, run out of disk space")
98 | }
99 |
100 | return fmt.Errorf("Unpack failed: %w", err)
101 | }
102 |
103 | return nil
104 | }
105 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Pull requests:
4 |
5 | Changes to this project should be proposed as pull requests on Github
6 | at: