feat: install bootloader to block device (#455)
Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
parent
6ae6118d2e
commit
31a00ef73a
@ -39,6 +39,9 @@ RUN ln -s /toolchain/etc/ssl/certs/ca-certificates /etc/ssl/certs/ca-certificate
|
||||
# fhs
|
||||
COPY hack/scripts/fhs.sh /bin
|
||||
RUN fhs.sh /rootfs
|
||||
# ca-certificates
|
||||
RUN mkdir -p /rootfs/etc/ssl/certs
|
||||
RUN curl -o /rootfs/etc/ssl/certs/ca-certificates.crt https://curl.haxx.se/ca/cacert.pem
|
||||
# xfsprogs
|
||||
WORKDIR /tmp/xfsprogs
|
||||
RUN curl -L https://www.kernel.org/pub/linux/utils/fs/xfs/xfsprogs/xfsprogs-4.18.0.tar.xz | tar -xJ --strip-components=1
|
||||
@ -66,7 +69,7 @@ RUN curl -L https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.t
|
||||
RUN ln -s /toolchain/bin/pwd /bin/pwd && \
|
||||
make installer && \
|
||||
cp /tmp/syslinux/bios/extlinux/extlinux /rootfs/bin && \
|
||||
cp /tmp/syslinux/bios/mbr/gptmbr.bin /rootfs/share
|
||||
cp /tmp/syslinux/efi64/mbr/gptmbr.bin /rootfs/share
|
||||
# golang
|
||||
ENV GOROOT /toolchain/usr/local/go
|
||||
ENV GOPATH /toolchain/go
|
||||
@ -157,9 +160,6 @@ RUN ../configure \
|
||||
RUN make -j $(($(nproc) / 2))
|
||||
RUN make install DESTDIR=/rootfs
|
||||
RUN make install DESTDIR=/toolchain
|
||||
# ca-certificates
|
||||
RUN mkdir -p /rootfs/etc/ssl/certs
|
||||
RUN curl -o /rootfs/etc/ssl/certs/ca-certificates.crt https://curl.haxx.se/ca/cacert.pem
|
||||
# crictl
|
||||
RUN curl -L https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.13.0/crictl-v1.13.0-linux-amd64.tar.gz | tar -xz -C /rootfs/bin
|
||||
# containerd
|
||||
|
@ -83,6 +83,15 @@ func (b *BareMetal) Prepare(data *userdata.UserData) (err error) {
|
||||
// Install provides the functionality to install talos by
|
||||
// download the necessary bits and write them to a target device
|
||||
// nolint: gocyclo, dupl
|
||||
func (b *BareMetal) Install(data *userdata.UserData) error {
|
||||
return install.Install(data)
|
||||
func (b *BareMetal) Install(data *userdata.UserData) (err error) {
|
||||
var cmdlineBytes []byte
|
||||
cmdlineBytes, err = kernel.ReadProcCmdline()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = install.Install(string(cmdlineBytes), data); err != nil {
|
||||
return errors.Wrap(err, "failed to install to bare metal")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package mount
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
@ -237,6 +238,7 @@ func mountpoints() (mountpoints *mount.Points, err error) {
|
||||
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
|
||||
if name == constants.BootPartitionLabel {
|
||||
// A bootloader is not always required.
|
||||
log.Println("WARNING: no ESP partition was found")
|
||||
continue
|
||||
}
|
||||
return nil, errors.Errorf("failed to find device with label %s: %v", name, err)
|
||||
|
@ -14,6 +14,10 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
kernelParams string
|
||||
)
|
||||
|
||||
// installCmd reads in a userData file and attempts to parse it
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
@ -38,7 +42,7 @@ var installCmd = &cobra.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = install.Install(ud)
|
||||
err = install.Install(kernelParams, ud)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -46,6 +50,7 @@ var installCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
installCmd.Flags().StringVarP(&kernelParams, "kernel-parameters", "k", "", "kernel parameter flags")
|
||||
installCmd.Flags().StringVarP(&userdataFile, "userdata", "u", "", "path or url of userdata file")
|
||||
rootCmd.AddCommand(installCmd)
|
||||
}
|
||||
|
11
internal/pkg/blockdevice/bootloader/bootloader.go
Normal file
11
internal/pkg/blockdevice/bootloader/bootloader.go
Normal file
@ -0,0 +1,11 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package bootloader
|
||||
|
||||
// Bootloader describes a bootloader.
|
||||
type Bootloader interface {
|
||||
Prepare(string) error
|
||||
Install(string) error
|
||||
}
|
93
internal/pkg/blockdevice/bootloader/syslinux/syslinux.go
Normal file
93
internal/pkg/blockdevice/bootloader/syslinux/syslinux.go
Normal file
@ -0,0 +1,93 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package syslinux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"text/template"
|
||||
|
||||
"github.com/autonomy/talos/internal/pkg/constants"
|
||||
"github.com/autonomy/talos/internal/pkg/version"
|
||||
)
|
||||
|
||||
const extlinuxConfig = `DEFAULT Talos
|
||||
SAY Talos ({{ .Version }}) by Autonomy
|
||||
LABEL Talos
|
||||
KERNEL /vmlinuz
|
||||
INITRD /initramfs.xz
|
||||
APPEND {{ .Append }}`
|
||||
|
||||
const gptmbrbin = "/usr/share/gptmbr.bin"
|
||||
|
||||
// Syslinux represents the syslinux bootloader.
|
||||
type Syslinux struct{}
|
||||
|
||||
// Prepare implements the Bootloader interface. It works by invoking writing
|
||||
// gptmbr.bin to a block device.
|
||||
func Prepare(dev string) (err error) {
|
||||
b, err := ioutil.ReadFile(gptmbrbin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(dev, os.O_WRONLY, os.ModeDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer f.Close()
|
||||
if _, err := f.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install implements the Bootloader interface. It sets up extlinux with the
|
||||
// specified kernel parameters.
|
||||
func Install(args string) (err error) {
|
||||
aux := struct {
|
||||
Version string
|
||||
Append string
|
||||
}{
|
||||
Version: version.Tag,
|
||||
Append: args,
|
||||
}
|
||||
|
||||
b := []byte{}
|
||||
wr := bytes.NewBuffer(b)
|
||||
t := template.Must(template.New("extlinux").Parse(extlinuxConfig))
|
||||
if err = t.Execute(wr, aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(constants.NewRoot+"/boot/extlinux", os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("writing extlinux.conf to disk")
|
||||
if err = ioutil.WriteFile(constants.NewRoot+"/boot/extlinux/extlinux.conf", wr.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = cmd("extlinux", "--install", constants.NewRoot+"/boot/extlinux"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmd(name string, args ...string) error {
|
||||
cmd := exec.Command(name, args...)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cmd.Wait()
|
||||
}
|
@ -57,5 +57,5 @@ func (sb *SuperBlock) Offset() int64 {
|
||||
|
||||
// Type implements the SuperBlocker interface.
|
||||
func (sb *SuperBlock) Type() string {
|
||||
return "fat32"
|
||||
return "vfat"
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ func MakeFS(partname string, setters ...Option) error {
|
||||
args := []string{}
|
||||
|
||||
if opts.Label != "" {
|
||||
args = append(args, "-n", opts.Label)
|
||||
args = append(args, "-F", "32", "-n", opts.Label)
|
||||
}
|
||||
|
||||
args = append(args, partname)
|
||||
|
@ -285,10 +285,9 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
|
||||
ID: uuid,
|
||||
FirstLBA: start,
|
||||
LastLBA: end,
|
||||
// TODO(andrewrynhard): Flags should be an option.
|
||||
Flags: 0,
|
||||
Name: opts.Name,
|
||||
Number: int32(len(gpt.partitions) + 1),
|
||||
Flags: opts.Flags,
|
||||
Name: opts.Name,
|
||||
Number: int32(len(gpt.partitions) + 1),
|
||||
}
|
||||
|
||||
gpt.partitions = append(gpt.partitions, partition)
|
||||
|
@ -10,9 +10,10 @@ import (
|
||||
|
||||
// Options is the functional options struct.
|
||||
type Options struct {
|
||||
Type uuid.UUID
|
||||
Name string
|
||||
Test bool
|
||||
Type uuid.UUID
|
||||
Name string
|
||||
Flags uint64
|
||||
Test bool
|
||||
}
|
||||
|
||||
// Option is the functional option func.
|
||||
@ -35,6 +36,15 @@ func WithPartitionName(o string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLegacyBIOSBootableAttribute marks the partition as bootable.
|
||||
func WithLegacyBIOSBootableAttribute(o bool) Option {
|
||||
return func(args *Options) {
|
||||
if o == true {
|
||||
args.Flags = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithPartitionTest allows us to disable the IsNew partition
|
||||
// check. This is only intended to be used for tests.
|
||||
func WithPartitionTest(t bool) Option {
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
||||
"github.com/autonomy/talos/internal/pkg/constants"
|
||||
"github.com/autonomy/talos/internal/pkg/userdata"
|
||||
"github.com/pkg/errors"
|
||||
@ -24,7 +25,7 @@ import (
|
||||
// Install fetches the necessary data locations and copies or extracts
|
||||
// to the target locations
|
||||
// nolint: gocyclo
|
||||
func Install(data *userdata.UserData) (err error) {
|
||||
func Install(args string, data *userdata.UserData) (err error) {
|
||||
if data.Install == nil {
|
||||
return nil
|
||||
}
|
||||
@ -46,6 +47,10 @@ func Install(data *userdata.UserData) (err error) {
|
||||
previousMountPoint = dest
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dest, os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract artifact if necessary, otherwise place at root of partition/filesystem
|
||||
for _, artifact := range urls {
|
||||
switch {
|
||||
@ -120,6 +125,11 @@ func Install(data *userdata.UserData) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = syslinux.Install(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice"
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/vfat"
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/xfs"
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
|
||||
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
|
||||
@ -178,6 +180,11 @@ func Prepare(data *userdata.UserData) (err error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if label == constants.BootPartitionLabel {
|
||||
if err = syslinux.Prepare(device); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var bd *blockdevice.BlockDevice
|
||||
|
||||
bd, err = blockdevice.Open(device, blockdevice.WithNewGPT(data.Install.Wipe))
|
||||
@ -241,7 +248,7 @@ func Prepare(data *userdata.UserData) (err error) {
|
||||
|
||||
for _, dev := range devices {
|
||||
// Create the filesystem
|
||||
log.Printf("Formatting Partition %s - %s\n", dev.Name, dev.Label)
|
||||
log.Printf("Formatting Partition %s - %s\n", dev.PartitionName, dev.Label)
|
||||
err = dev.Format()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -296,11 +303,15 @@ func NewDevice(name string, label string, size uint, force bool, test bool, data
|
||||
// Partition creates a new partition on the specified device
|
||||
// nolint: dupl
|
||||
func (d *Device) Partition() error {
|
||||
var typeID string
|
||||
var (
|
||||
typeID string
|
||||
legacyBIOSBootable bool
|
||||
)
|
||||
switch d.Label {
|
||||
case constants.BootPartitionLabel:
|
||||
// EFI System Partition
|
||||
typeID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
||||
legacyBIOSBootable = true
|
||||
case constants.RootPartitionLabel:
|
||||
// Root Partition
|
||||
switch runtime.GOARCH {
|
||||
@ -318,7 +329,13 @@ func (d *Device) Partition() error {
|
||||
return errors.Errorf("%s", "unknown partition label")
|
||||
}
|
||||
|
||||
part, err := d.PartitionTable.Add(uint64(d.Size), partition.WithPartitionType(typeID), partition.WithPartitionName(d.Label), partition.WithPartitionTest(d.Test))
|
||||
part, err := d.PartitionTable.Add(
|
||||
uint64(d.Size),
|
||||
partition.WithPartitionType(typeID),
|
||||
partition.WithPartitionName(d.Label),
|
||||
partition.WithLegacyBIOSBootableAttribute(legacyBIOSBootable),
|
||||
partition.WithPartitionTest(d.Test),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -330,5 +347,8 @@ func (d *Device) Partition() error {
|
||||
|
||||
// Format creates a xfs filesystem on the device/partition
|
||||
func (d *Device) Format() error {
|
||||
if d.Label == constants.BootPartitionLabel {
|
||||
return vfat.MakeFS(d.PartitionName, vfat.WithLabel(d.Label))
|
||||
}
|
||||
return xfs.MakeFS(d.PartitionName, xfs.WithLabel(d.Label), xfs.WithForce(d.Force))
|
||||
}
|
||||
|
@ -9,11 +9,21 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ReadProcCmdline reads /proc/cmdline.
|
||||
func ReadProcCmdline() (cmdlineBytes []byte, err error) {
|
||||
cmdlineBytes, err = ioutil.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmdlineBytes, nil
|
||||
}
|
||||
|
||||
// ParseProcCmdline parses /proc/cmdline and returns a map reprentation of the
|
||||
// kernel parameters.
|
||||
func ParseProcCmdline() (cmdline map[string]string, err error) {
|
||||
var cmdlineBytes []byte
|
||||
cmdlineBytes, err = ioutil.ReadFile("/proc/cmdline")
|
||||
cmdlineBytes, err = ReadProcCmdline()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user