File: archlinux-http.go

package info (click to toggle)
distrobuilder 3.2-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,384 kB
  • sloc: sh: 204; makefile: 75
file content (144 lines) | stat: -rw-r--r-- 3,803 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package sources

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"github.com/antchfx/htmlquery"

	"github.com/lxc/distrobuilder/shared"
)

type archlinux struct {
	common
}

// Run downloads an Arch Linux tarball.
func (s *archlinux) Run() error {
	release := s.definition.Image.Release

	// Releases are only available for the x86_64 architecture. ARM only has
	// a "latest" tarball.
	if s.definition.Image.ArchitectureMapped == "x86_64" && release == "" {
		var err error

		// Get latest release
		release, err = s.getLatestRelease(s.definition.Source.URL, s.definition.Image.ArchitectureMapped)
		if err != nil {
			return fmt.Errorf("Failed to get latest release: %w", err)
		}
	}

	var fname string
	var tarball string

	if s.definition.Image.ArchitectureMapped == "x86_64" {
		fname = fmt.Sprintf("archlinux-bootstrap-%s-%s.tar.zst",
			release, s.definition.Image.ArchitectureMapped)
		tarball = fmt.Sprintf("%s/%s/%s", s.definition.Source.URL,
			release, fname)
	} else {
		fname = fmt.Sprintf("ArchLinuxARM-%s-latest.tar.gz",
			s.definition.Image.ArchitectureMapped)
		tarball = fmt.Sprintf("%s/os/%s", s.definition.Source.URL, fname)
	}

	url, err := url.Parse(tarball)
	if err != nil {
		return fmt.Errorf("Failed to parse URL %q: %w", tarball, err)
	}

	if !s.definition.Source.SkipVerification && url.Scheme != "https" &&
		len(s.definition.Source.Keys) == 0 {
		return errors.New("GPG keys are required if downloading from HTTP")
	}

	fpath, err := s.DownloadHash(s.definition.Image, tarball, "", nil)
	if err != nil {
		return fmt.Errorf("Failed to download %q: %w", tarball, err)
	}

	// Force gpg checks when using http
	if !s.definition.Source.SkipVerification && url.Scheme != "https" {
		_, err = s.DownloadHash(s.definition.Image, tarball+".sig", "", nil)
		if err != nil {
			return fmt.Errorf("Failed downloading %q: %w", tarball+".sig", err)
		}

		valid, err := s.VerifyFile(
			filepath.Join(fpath, fname),
			filepath.Join(fpath, fname+".sig"))
		if err != nil {
			return fmt.Errorf("Failed to verify %q: %w", fname, err)
		}

		if !valid {
			return fmt.Errorf("Invalid signature for %q", fname)
		}
	}

	s.logger.WithField("file", filepath.Join(fpath, fname)).Info("Unpacking image")

	// Unpack
	err = shared.Unpack(filepath.Join(fpath, fname), s.rootfsDir)
	if err != nil {
		return fmt.Errorf("Failed to unpack file %q: %w", filepath.Join(fpath, fname), err)
	}

	// Move everything inside 'root.<architecture>' (which was is the tarball) to its
	// parent directory
	files, err := filepath.Glob(fmt.Sprintf("%s/*", filepath.Join(s.rootfsDir,
		"root."+s.definition.Image.ArchitectureMapped)))
	if err != nil {
		return fmt.Errorf("Failed to get files: %w", err)
	}

	for _, file := range files {
		err = os.Rename(file, filepath.Join(s.rootfsDir, path.Base(file)))
		if err != nil {
			return fmt.Errorf("Failed to rename file %q: %w", file, err)
		}
	}

	path := filepath.Join(s.rootfsDir, "root."+s.definition.Image.ArchitectureMapped)

	err = os.RemoveAll(path)
	if err != nil {
		return fmt.Errorf("Failed to remove %q: %w", path, err)
	}

	return nil
}

func (s *archlinux) getLatestRelease(URL string, arch string) (string, error) {
	doc, err := htmlquery.LoadURL(URL)
	if err != nil {
		return "", fmt.Errorf("Failed to load URL %q: %w", URL, err)
	}

	re := regexp.MustCompile(`^\d{4}\.\d{2}\.\d{2}/?$`)

	var releases []string

	for _, node := range htmlquery.Find(doc, `//a[@href]/text()`) {
		if re.MatchString(node.Data) {
			releases = append(releases, strings.TrimSuffix(node.Data, "/"))
		}
	}

	if len(releases) == 0 {
		return "", errors.New("Failed to determine latest release")
	}

	// Sort releases in case they're out-of-order
	sort.Strings(releases)

	return releases[len(releases)-1], nil
}