mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-19 13:41:13 +02:00
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
|
# fhs
|
||||||
COPY hack/scripts/fhs.sh /bin
|
COPY hack/scripts/fhs.sh /bin
|
||||||
RUN fhs.sh /rootfs
|
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
|
# xfsprogs
|
||||||
WORKDIR /tmp/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
|
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 && \
|
RUN ln -s /toolchain/bin/pwd /bin/pwd && \
|
||||||
make installer && \
|
make installer && \
|
||||||
cp /tmp/syslinux/bios/extlinux/extlinux /rootfs/bin && \
|
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
|
# golang
|
||||||
ENV GOROOT /toolchain/usr/local/go
|
ENV GOROOT /toolchain/usr/local/go
|
||||||
ENV GOPATH /toolchain/go
|
ENV GOPATH /toolchain/go
|
||||||
@ -157,9 +160,6 @@ RUN ../configure \
|
|||||||
RUN make -j $(($(nproc) / 2))
|
RUN make -j $(($(nproc) / 2))
|
||||||
RUN make install DESTDIR=/rootfs
|
RUN make install DESTDIR=/rootfs
|
||||||
RUN make install DESTDIR=/toolchain
|
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
|
# 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
|
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
|
# containerd
|
||||||
|
@ -83,6 +83,15 @@ func (b *BareMetal) Prepare(data *userdata.UserData) (err error) {
|
|||||||
// Install provides the functionality to install talos by
|
// Install provides the functionality to install talos by
|
||||||
// download the necessary bits and write them to a target device
|
// download the necessary bits and write them to a target device
|
||||||
// nolint: gocyclo, dupl
|
// nolint: gocyclo, dupl
|
||||||
func (b *BareMetal) Install(data *userdata.UserData) error {
|
func (b *BareMetal) Install(data *userdata.UserData) (err error) {
|
||||||
return install.Install(data)
|
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
|
package mount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
@ -237,6 +238,7 @@ func mountpoints() (mountpoints *mount.Points, err error) {
|
|||||||
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
|
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
|
||||||
if name == constants.BootPartitionLabel {
|
if name == constants.BootPartitionLabel {
|
||||||
// A bootloader is not always required.
|
// A bootloader is not always required.
|
||||||
|
log.Println("WARNING: no ESP partition was found")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, errors.Errorf("failed to find device with label %s: %v", name, err)
|
return nil, errors.Errorf("failed to find device with label %s: %v", name, err)
|
||||||
|
@ -14,6 +14,10 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernelParams string
|
||||||
|
)
|
||||||
|
|
||||||
// installCmd reads in a userData file and attempts to parse it
|
// installCmd reads in a userData file and attempts to parse it
|
||||||
var installCmd = &cobra.Command{
|
var installCmd = &cobra.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
@ -38,7 +42,7 @@ var installCmd = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = install.Install(ud)
|
err = install.Install(kernelParams, ud)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -46,6 +50,7 @@ var installCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
installCmd.Flags().StringVarP(&kernelParams, "kernel-parameters", "k", "", "kernel parameter flags")
|
||||||
installCmd.Flags().StringVarP(&userdataFile, "userdata", "u", "", "path or url of userdata file")
|
installCmd.Flags().StringVarP(&userdataFile, "userdata", "u", "", "path or url of userdata file")
|
||||||
rootCmd.AddCommand(installCmd)
|
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.
|
// Type implements the SuperBlocker interface.
|
||||||
func (sb *SuperBlock) Type() string {
|
func (sb *SuperBlock) Type() string {
|
||||||
return "fat32"
|
return "vfat"
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ func MakeFS(partname string, setters ...Option) error {
|
|||||||
args := []string{}
|
args := []string{}
|
||||||
|
|
||||||
if opts.Label != "" {
|
if opts.Label != "" {
|
||||||
args = append(args, "-n", opts.Label)
|
args = append(args, "-F", "32", "-n", opts.Label)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, partname)
|
args = append(args, partname)
|
||||||
|
@ -285,10 +285,9 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
|
|||||||
ID: uuid,
|
ID: uuid,
|
||||||
FirstLBA: start,
|
FirstLBA: start,
|
||||||
LastLBA: end,
|
LastLBA: end,
|
||||||
// TODO(andrewrynhard): Flags should be an option.
|
Flags: opts.Flags,
|
||||||
Flags: 0,
|
Name: opts.Name,
|
||||||
Name: opts.Name,
|
Number: int32(len(gpt.partitions) + 1),
|
||||||
Number: int32(len(gpt.partitions) + 1),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gpt.partitions = append(gpt.partitions, partition)
|
gpt.partitions = append(gpt.partitions, partition)
|
||||||
|
@ -10,9 +10,10 @@ import (
|
|||||||
|
|
||||||
// Options is the functional options struct.
|
// Options is the functional options struct.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Type uuid.UUID
|
Type uuid.UUID
|
||||||
Name string
|
Name string
|
||||||
Test bool
|
Flags uint64
|
||||||
|
Test bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is the functional option func.
|
// 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
|
// WithPartitionTest allows us to disable the IsNew partition
|
||||||
// check. This is only intended to be used for tests.
|
// check. This is only intended to be used for tests.
|
||||||
func WithPartitionTest(t bool) Option {
|
func WithPartitionTest(t bool) Option {
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/autonomy/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
||||||
"github.com/autonomy/talos/internal/pkg/constants"
|
"github.com/autonomy/talos/internal/pkg/constants"
|
||||||
"github.com/autonomy/talos/internal/pkg/userdata"
|
"github.com/autonomy/talos/internal/pkg/userdata"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -24,7 +25,7 @@ import (
|
|||||||
// Install fetches the necessary data locations and copies or extracts
|
// Install fetches the necessary data locations and copies or extracts
|
||||||
// to the target locations
|
// to the target locations
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func Install(data *userdata.UserData) (err error) {
|
func Install(args string, data *userdata.UserData) (err error) {
|
||||||
if data.Install == nil {
|
if data.Install == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -46,6 +47,10 @@ func Install(data *userdata.UserData) (err error) {
|
|||||||
previousMountPoint = dest
|
previousMountPoint = dest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(dest, os.ModeDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Extract artifact if necessary, otherwise place at root of partition/filesystem
|
// Extract artifact if necessary, otherwise place at root of partition/filesystem
|
||||||
for _, artifact := range urls {
|
for _, artifact := range urls {
|
||||||
switch {
|
switch {
|
||||||
@ -120,6 +125,11 @@ func Install(data *userdata.UserData) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = syslinux.Install(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/autonomy/talos/internal/pkg/blockdevice"
|
"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/filesystem/xfs"
|
||||||
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
|
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
|
||||||
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
|
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
|
||||||
@ -178,6 +180,11 @@ func Prepare(data *userdata.UserData) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if label == constants.BootPartitionLabel {
|
||||||
|
if err = syslinux.Prepare(device); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
var bd *blockdevice.BlockDevice
|
var bd *blockdevice.BlockDevice
|
||||||
|
|
||||||
bd, err = blockdevice.Open(device, blockdevice.WithNewGPT(data.Install.Wipe))
|
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 {
|
for _, dev := range devices {
|
||||||
// Create the filesystem
|
// 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()
|
err = dev.Format()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Partition creates a new partition on the specified device
|
||||||
// nolint: dupl
|
// nolint: dupl
|
||||||
func (d *Device) Partition() error {
|
func (d *Device) Partition() error {
|
||||||
var typeID string
|
var (
|
||||||
|
typeID string
|
||||||
|
legacyBIOSBootable bool
|
||||||
|
)
|
||||||
switch d.Label {
|
switch d.Label {
|
||||||
case constants.BootPartitionLabel:
|
case constants.BootPartitionLabel:
|
||||||
// EFI System Partition
|
// EFI System Partition
|
||||||
typeID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
typeID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
||||||
|
legacyBIOSBootable = true
|
||||||
case constants.RootPartitionLabel:
|
case constants.RootPartitionLabel:
|
||||||
// Root Partition
|
// Root Partition
|
||||||
switch runtime.GOARCH {
|
switch runtime.GOARCH {
|
||||||
@ -318,7 +329,13 @@ func (d *Device) Partition() error {
|
|||||||
return errors.Errorf("%s", "unknown partition label")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -330,5 +347,8 @@ func (d *Device) Partition() error {
|
|||||||
|
|
||||||
// Format creates a xfs filesystem on the device/partition
|
// Format creates a xfs filesystem on the device/partition
|
||||||
func (d *Device) Format() error {
|
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))
|
return xfs.MakeFS(d.PartitionName, xfs.WithLabel(d.Label), xfs.WithForce(d.Force))
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,21 @@ import (
|
|||||||
"strings"
|
"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
|
// ParseProcCmdline parses /proc/cmdline and returns a map reprentation of the
|
||||||
// kernel parameters.
|
// kernel parameters.
|
||||||
func ParseProcCmdline() (cmdline map[string]string, err error) {
|
func ParseProcCmdline() (cmdline map[string]string, err error) {
|
||||||
var cmdlineBytes []byte
|
var cmdlineBytes []byte
|
||||||
cmdlineBytes, err = ioutil.ReadFile("/proc/cmdline")
|
cmdlineBytes, err = ReadProcCmdline()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user