Skip to content

Commit

Permalink
feat: install bootloader to block device (#455)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
  • Loading branch information
andrewrynhard committed Mar 18, 2019
1 parent 6ae6118 commit 31a00ef
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 21 deletions.
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions internal/app/init/internal/platform/baremetal/baremetal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 2 additions & 0 deletions internal/app/init/internal/rootfs/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package mount

import (
"log"
"os"
"path"

Expand Down Expand Up @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion internal/app/osinstall/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -38,14 +42,15 @@ var installCmd = &cobra.Command{
log.Fatal(err)
}

err = install.Install(ud)
err = install.Install(kernelParams, ud)
if err != nil {
log.Fatal(err)
}
},
}

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 changes: 11 additions & 0 deletions internal/pkg/blockdevice/bootloader/bootloader.go
Original file line number Diff line number Diff line change
@@ -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 changes: 93 additions & 0 deletions internal/pkg/blockdevice/bootloader/syslinux/syslinux.go
Original file line number Diff line number Diff line change
@@ -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()
}
2 changes: 1 addition & 1 deletion internal/pkg/blockdevice/filesystem/vfat/superblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ func (sb *SuperBlock) Offset() int64 {

// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "fat32"
return "vfat"
}
2 changes: 1 addition & 1 deletion internal/pkg/blockdevice/filesystem/vfat/vfat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 3 additions & 4 deletions internal/pkg/blockdevice/table/gpt/gpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 13 additions & 3 deletions internal/pkg/blockdevice/table/gpt/partition/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion internal/pkg/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -120,6 +125,11 @@ func Install(data *userdata.UserData) (err error) {
}
}
}

if err = syslinux.Install(args); err != nil {
return err
}

return nil
}

Expand Down
26 changes: 23 additions & 3 deletions internal/pkg/install/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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))
}
12 changes: 11 additions & 1 deletion internal/pkg/kernel/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 31a00ef

Please sign in to comment.