mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-19 05:31:14 +02:00
refactor: improve installation reliability
This change aims to make installations more unified and reliable. It introduces the concept of a mountpoint manager that is capable of mounting, unmounting, and moving a set of mountpoints in the correct order. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
parent
9c63f4ed0a
commit
ca35b85300
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
// "github.com/talos-systems/talos/cmd/osctl/internal/userdata"
|
// "github.com/talos-systems/talos/cmd/osctl/internal/userdata"
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/install"
|
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
"github.com/talos-systems/talos/internal/pkg/version"
|
"github.com/talos-systems/talos/internal/pkg/version"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
@ -55,14 +55,6 @@ var installCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = install.Prepare(data); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = install.Mount(data); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdline := kernel.NewDefaultCmdline()
|
cmdline := kernel.NewDefaultCmdline()
|
||||||
cmdline.Append("initrd", filepath.Join("/", "default", "initramfs.xz"))
|
cmdline.Append("initrd", filepath.Join("/", "default", "initramfs.xz"))
|
||||||
cmdline.Append(constants.KernelParamPlatform, platform)
|
cmdline.Append(constants.KernelParamPlatform, platform)
|
||||||
@ -71,7 +63,8 @@ var installCmd = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = install.Install(cmdline.String(), data); err != nil {
|
i := installer.NewInstaller(cmdline, data)
|
||||||
|
if err = i.Install(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
/* 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 mount
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"gopkg.in/freddierice/go-losetup.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Initializer represents the early boot initialization control.
|
|
||||||
type Initializer struct {
|
|
||||||
prefix string
|
|
||||||
|
|
||||||
special *mount.Points
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInitializer initializes and returns an Initializer struct.
|
|
||||||
func NewInitializer(prefix string) (initializer *Initializer, err error) {
|
|
||||||
special := mount.NewMountPoints()
|
|
||||||
special.Set("dev", mount.NewMountPoint("devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"))
|
|
||||||
special.Set("proc", mount.NewMountPoint("proc", "/proc", "proc", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""))
|
|
||||||
special.Set("sys", mount.NewMountPoint("sysfs", "/sys", "sysfs", 0, ""))
|
|
||||||
special.Set("run", mount.NewMountPoint("tmpfs", "/run", "tmpfs", 0, ""))
|
|
||||||
special.Set("tmp", mount.NewMountPoint("tmpfs", "/tmp", "tmpfs", 0, ""))
|
|
||||||
|
|
||||||
initializer = &Initializer{
|
|
||||||
prefix: prefix,
|
|
||||||
special: special,
|
|
||||||
}
|
|
||||||
|
|
||||||
return initializer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special returns the special devices.
|
|
||||||
func (i *Initializer) Special() *mount.Points {
|
|
||||||
return i.special
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitSpecial initializes and mounts the special devices in the early boot
|
|
||||||
// tasks.
|
|
||||||
func (i *Initializer) InitSpecial() (err error) {
|
|
||||||
iter := i.special.Iter()
|
|
||||||
for iter.Next() {
|
|
||||||
if err = mount.WithRetry(iter.Value()); err != nil {
|
|
||||||
return errors.Errorf("error initializing special device at %s: %v", iter.Value().Target(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if iter.Err() != nil {
|
|
||||||
return iter.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rootfs initializes and mounts the OS owned block devices in the early boot
|
|
||||||
// tasks.
|
|
||||||
func (i *Initializer) Rootfs() (err error) {
|
|
||||||
var dev losetup.Device
|
|
||||||
dev, err = losetup.Attach("/"+constants.RootfsAsset, 0, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := mount.NewMountPoint(dev.Path(), "/", "squashfs", unix.MS_RDONLY, "")
|
|
||||||
if err = mount.WithRetry(m, mount.WithPrefix(i.prefix), mount.WithReadOnly(true), mount.WithShared(true)); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to mount squashfs")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveSpecial moves the special device mount points to the new root.
|
|
||||||
func (i *Initializer) MoveSpecial() (err error) {
|
|
||||||
iter := i.special.Iter()
|
|
||||||
for iter.Next() {
|
|
||||||
mountpoint := mount.NewMountPoint(iter.Value().Target(), iter.Value().Target(), "", unix.MS_MOVE, "")
|
|
||||||
if err = mount.WithRetry(mountpoint, mount.WithPrefix(i.prefix)); err != nil {
|
|
||||||
return errors.Errorf("error moving mount point %s: %v", iter.Value().Target(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if iter.Err() != nil {
|
|
||||||
return iter.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = mount.WithRetry(mount.NewMountPoint("tmpfs", "/dev/shm", "tmpfs", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME, ""), mount.WithPrefix(i.prefix)); err != nil {
|
|
||||||
return errors.Errorf("error mounting mount point %s: %v", "/dev/shm", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = mount.WithRetry(mount.NewMountPoint("devpts", "/dev/pts", "devpts", unix.MS_NOSUID|unix.MS_NOEXEC, "ptmxmode=000,mode=620,gid=5"), mount.WithPrefix(i.prefix)); err != nil {
|
|
||||||
return errors.Errorf("error mounting mount point %s: %v", "/dev/pts", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch moves the root to a specified directory. See
|
|
||||||
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c.
|
|
||||||
// nolint: gocyclo
|
|
||||||
func (i *Initializer) Switch() (err error) {
|
|
||||||
if err = i.MoveSpecial(); err != nil {
|
|
||||||
return errors.Wrap(err, "error moving special devices")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = unix.Chdir(i.prefix); err != nil {
|
|
||||||
return errors.Wrapf(err, "error changing working directory to %s", i.prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
var old *os.File
|
|
||||||
if old, err = os.Open("/"); err != nil {
|
|
||||||
return errors.Wrap(err, "error opening /")
|
|
||||||
}
|
|
||||||
// nolint: errcheck
|
|
||||||
defer old.Close()
|
|
||||||
|
|
||||||
if err = unix.Mount(i.prefix, "/", "", unix.MS_MOVE, ""); err != nil {
|
|
||||||
return errors.Wrap(err, "error moving /")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = unix.Chroot("."); err != nil {
|
|
||||||
return errors.Wrap(err, "error chroot")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = recursiveDelete(int(old.Fd())); err != nil {
|
|
||||||
return errors.Wrap(err, "error deleting initramfs")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that /sbin/init is machined. We call it init since this is the
|
|
||||||
// convention.
|
|
||||||
if err = unix.Exec("/sbin/init", []string{"/sbin/init"}, []string{}); err != nil {
|
|
||||||
return errors.Wrap(err, "error executing /sbin/init")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func recursiveDelete(fd int) error {
|
|
||||||
parentDev, err := getDev(fd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := os.NewFile(uintptr(fd), "__ignored__")
|
|
||||||
// nolint: errcheck
|
|
||||||
defer dir.Close()
|
|
||||||
names, err := dir.Readdirnames(-1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range names {
|
|
||||||
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
|
|
||||||
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
|
|
||||||
if err != nil {
|
|
||||||
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// nolint: errcheck
|
|
||||||
defer unix.Close(childFd)
|
|
||||||
|
|
||||||
if childFdDev, err := getDev(childFd); err != nil {
|
|
||||||
return err
|
|
||||||
} else if childFdDev != parentDev {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := recursiveDelete(childFd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDev(fd int) (dev uint64, err error) {
|
|
||||||
var stat unix.Stat_t
|
|
||||||
|
|
||||||
if err := unix.Fstat(fd, &stat); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stat.Dev, nil
|
|
||||||
}
|
|
@ -6,22 +6,27 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/app/init/internal/mount"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kmsg"
|
"github.com/talos-systems/talos/internal/pkg/kmsg"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager/squashfs"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager/virtual"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/switchroot"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func initram() (err error) {
|
func run() (err error) {
|
||||||
var initializer *mount.Initializer
|
// Mount the virtual devices.
|
||||||
if initializer, err = mount.NewInitializer(constants.NewRoot); err != nil {
|
mountpoints, err := virtual.MountPoints()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
virtual := manager.NewManager(mountpoints)
|
||||||
// Mount the special devices.
|
if err = virtual.MountAll(); err != nil {
|
||||||
if err = initializer.InitSpecial(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,23 +36,43 @@ func initram() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the equivalent of switch_root.
|
// Mount the rootfs.
|
||||||
log.Println("mounting the rootfs")
|
log.Println("mounting the rootfs")
|
||||||
if err = initializer.Rootfs(); err != nil {
|
mountpoints, err = squashfs.MountPoints(constants.NewRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
squashfs := manager.NewManager(mountpoints)
|
||||||
|
if err = squashfs.MountAll(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the equivalent of switch_root.
|
// Switch into the new rootfs.
|
||||||
log.Println("entering the rootfs")
|
log.Println("entering the rootfs")
|
||||||
if err = initializer.Switch(); err != nil {
|
if err = switchroot.Switch(constants.NewRoot, virtual); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recovery() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("recovered from: %+v\n", r)
|
||||||
|
for i := 10; i >= 0; i-- {
|
||||||
|
log.Printf("rebooting in %d seconds\n", i)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: errcheck
|
||||||
|
unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := initram(); err != nil {
|
defer recovery()
|
||||||
|
|
||||||
|
if err := run(); err != nil {
|
||||||
panic(errors.Wrap(err, "early boot failed"))
|
panic(errors.Wrap(err, "early boot failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
/* 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 cgroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
memoryCgroup = "memory"
|
|
||||||
memoryUseHierarchy = "memory.use_hierarchy"
|
|
||||||
memoryUseHierarchyPermissions = os.FileMode(400)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
memoryUseHierarchyContents = []byte(strconv.Itoa(1))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mount creates the cgroup mount points.
|
|
||||||
func Mount() error {
|
|
||||||
target := "/sys/fs/cgroup"
|
|
||||||
if err := os.MkdirAll(target, os.ModeDir); err != nil {
|
|
||||||
return errors.Errorf("failed to create %s: %+v", target, err)
|
|
||||||
}
|
|
||||||
if err := unix.Mount("tmpfs", target, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, "mode=755"); err != nil {
|
|
||||||
return errors.Errorf("failed to mount %s: %+v", target, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cgroups := []string{
|
|
||||||
"blkio",
|
|
||||||
"cpu",
|
|
||||||
"cpuacct",
|
|
||||||
"cpuset",
|
|
||||||
"devices",
|
|
||||||
"freezer",
|
|
||||||
"hugetlb",
|
|
||||||
"memory",
|
|
||||||
"net_cls",
|
|
||||||
"net_prio",
|
|
||||||
"perf_event",
|
|
||||||
"pids",
|
|
||||||
}
|
|
||||||
for _, c := range cgroups {
|
|
||||||
p := path.Join("/sys/fs/cgroup", c)
|
|
||||||
if err := os.MkdirAll(p, os.ModeDir); err != nil {
|
|
||||||
return errors.Errorf("failed to create %s: %+v", p, err)
|
|
||||||
}
|
|
||||||
if err := unix.Mount(c, p, "cgroup", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, c); err != nil {
|
|
||||||
return errors.Errorf("failed to mount %s: %+v", p, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
|
|
||||||
target = path.Join("/sys/fs/cgroup", memoryCgroup, memoryUseHierarchy)
|
|
||||||
err := ioutil.WriteFile(target, memoryUseHierarchyContents, memoryUseHierarchyPermissions)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
/* 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 mount
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
|
||||||
gptpartition "github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/util"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Initializer represents the early boot initialization control.
|
|
||||||
type Initializer struct {
|
|
||||||
prefix string
|
|
||||||
|
|
||||||
owned *mount.Points
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInitializer initializes and returns an Initializer struct.
|
|
||||||
func NewInitializer(prefix string) (initializer *Initializer, err error) {
|
|
||||||
initializer = &Initializer{
|
|
||||||
prefix: prefix,
|
|
||||||
}
|
|
||||||
|
|
||||||
return initializer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Owned returns the OS owned block devices.
|
|
||||||
func (i *Initializer) Owned() *mount.Points {
|
|
||||||
return i.owned
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitOwned initializes and mounts the OS owned block devices in the early boot
|
|
||||||
// tasks.
|
|
||||||
func (i *Initializer) InitOwned() (err error) {
|
|
||||||
var owned *mount.Points
|
|
||||||
if owned, err = mountpoints(); err != nil {
|
|
||||||
return errors.Errorf("error initializing owned block devices: %v", err)
|
|
||||||
}
|
|
||||||
i.owned = owned
|
|
||||||
if mountpoint, ok := i.owned.Get(constants.DataPartitionLabel); ok {
|
|
||||||
if err = repair(mountpoint); err != nil {
|
|
||||||
return errors.Errorf("error fixing data partition: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := i.owned.Iter()
|
|
||||||
for iter.Next() {
|
|
||||||
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
|
|
||||||
return errors.Errorf("error mounting partitions: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if iter.Err() != nil {
|
|
||||||
return iter.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
if mountpoint, ok := i.owned.Get(constants.DataPartitionLabel); ok {
|
|
||||||
// NB: The XFS partition MUST be mounted, or this will fail.
|
|
||||||
log.Printf("growing the %s partition", constants.DataPartitionLabel)
|
|
||||||
if err = xfs.GrowFS(path.Join(i.prefix, mountpoint.Target())); err != nil {
|
|
||||||
return errors.Errorf("error growing data partition file system: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtraDevices mounts the extra devices.
|
|
||||||
func ExtraDevices(data *userdata.UserData) (err error) {
|
|
||||||
if data.Install == nil || data.Install.ExtraDevices == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, extra := range data.Install.ExtraDevices {
|
|
||||||
for i, part := range extra.Partitions {
|
|
||||||
devname := fmt.Sprintf("%s%d", extra.Device, i+1)
|
|
||||||
mountpoint := mount.NewMountPoint(devname, part.MountPoint, "xfs", unix.MS_NOATIME, "")
|
|
||||||
if err = mount.WithRetry(mountpoint); err != nil {
|
|
||||||
return errors.Errorf("failed to mount %s at %s: %v", devname, part.MountPoint, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountOwned mounts the OS owned block devices.
|
|
||||||
func (i *Initializer) MountOwned() (err error) {
|
|
||||||
iter := i.owned.Iter()
|
|
||||||
for iter.Next() {
|
|
||||||
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
|
|
||||||
return errors.Errorf("error mounting partitions: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if iter.Err() != nil {
|
|
||||||
return iter.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmountOwned unmounts the OS owned block devices.
|
|
||||||
func (i *Initializer) UnmountOwned() (err error) {
|
|
||||||
iter := i.owned.IterRev()
|
|
||||||
for iter.Next() {
|
|
||||||
if err = mount.UnWithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
|
|
||||||
return errors.Errorf("error mounting partitions: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if iter.Err() != nil {
|
|
||||||
return iter.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: dupl
|
|
||||||
func mountpoints() (mountpoints *mount.Points, err error) {
|
|
||||||
mountpoints = mount.NewMountPoints()
|
|
||||||
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
|
||||||
var target string
|
|
||||||
switch name {
|
|
||||||
case constants.DataPartitionLabel:
|
|
||||||
target = constants.DataMountPoint
|
|
||||||
case constants.BootPartitionLabel:
|
|
||||||
target = constants.BootMountPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
var dev *probe.ProbedBlockDevice
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
|
||||||
|
|
||||||
mountpoints.Set(name, mountpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mountpoints, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func repair(mountpoint *mount.Point) (err error) {
|
|
||||||
var devname string
|
|
||||||
if devname, err = util.DevnameFromPartname(mountpoint.Source()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bd, err := blockdevice.Open("/dev/" + devname)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("error opening block device %q: %v", devname, err)
|
|
||||||
}
|
|
||||||
// nolint: errcheck
|
|
||||||
defer bd.Close()
|
|
||||||
|
|
||||||
pt, err := bd.PartitionTable(true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pt.Repair(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, partition := range pt.Partitions() {
|
|
||||||
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
|
|
||||||
if err := pt.Resize(partition); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pt.Write(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rereading the partition table requires that all partitions be unmounted
|
|
||||||
// or it will fail with EBUSY.
|
|
||||||
if err := bd.RereadPartitionTable(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
/* 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 install
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/mount"
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Install represents the Install task.
|
|
||||||
type Install struct{}
|
|
||||||
|
|
||||||
// NewInstallTask initializes and returns an Install task.
|
|
||||||
func NewInstallTask() phase.Task {
|
|
||||||
return &Install{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuntimeFunc returns the runtime function.
|
|
||||||
func (task *Install) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
|
||||||
switch mode {
|
|
||||||
case runtime.Standard:
|
|
||||||
return task.runtime
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (task *Install) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
|
||||||
// Perform any tasks required by a particular platform.
|
|
||||||
log.Printf("performing platform specific tasks")
|
|
||||||
if err = platform.Prepare(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var initializer *mount.Initializer
|
|
||||||
if initializer, err = mount.NewInitializer(""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount the owned partitions.
|
|
||||||
log.Printf("mounting the owned partitions")
|
|
||||||
if err = initializer.InitOwned(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install handles additional system setup
|
|
||||||
if err = platform.Install(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
33
internal/app/machined/internal/phase/platform/platform.go
Normal file
33
internal/app/machined/internal/phase/platform/platform.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/* 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 platform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||||
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Platform represents the Platform task.
|
||||||
|
type Platform struct{}
|
||||||
|
|
||||||
|
// NewPlatformTask initializes and returns an Platform task.
|
||||||
|
func NewPlatformTask() phase.Task {
|
||||||
|
return &Platform{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeFunc returns the runtime function.
|
||||||
|
func (task *Platform) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||||
|
return task.runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (task *Platform) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||||
|
if err = platform.Initialize(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package install_test
|
package platform_test
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
@ -42,7 +42,7 @@ BUG_REPORT_URL="https://github.com/talos-systems/talos/issues"
|
|||||||
// Hosts renders a valid /etc/hosts file and writes it to disk.
|
// Hosts renders a valid /etc/hosts file and writes it to disk.
|
||||||
func Hosts() (err error) {
|
func Hosts() (err error) {
|
||||||
var h *string
|
var h *string
|
||||||
if h = kernel.Cmdline().Get(constants.KernelParamHostname).First(); h != nil {
|
if h = kernel.ProcCmdline().Get(constants.KernelParamHostname).First(); h != nil {
|
||||||
if err = unix.Sethostname([]byte(*h)); err != nil {
|
if err = unix.Sethostname([]byte(*h)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,32 @@
|
|||||||
package rootfs
|
package rootfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// "github.com/pkg/errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/mount/cgroups"
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager/cgroups"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
memoryCgroup = "memory"
|
||||||
|
memoryUseHierarchy = "memory.use_hierarchy"
|
||||||
|
memoryUseHierarchyPermissions = os.FileMode(400)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
memoryUseHierarchyContents = []byte(strconv.Itoa(1))
|
||||||
|
)
|
||||||
|
|
||||||
// MountCgroups represents the MountCgroups task.
|
// MountCgroups represents the MountCgroups task.
|
||||||
type MountCgroups struct{}
|
type MountCgroups struct{}
|
||||||
|
|
||||||
@ -32,8 +50,21 @@ func (task *MountCgroups) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (task *MountCgroups) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
func (task *MountCgroups) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||||
if err = cgroups.Mount(); err != nil {
|
var mountpoints *mount.Points
|
||||||
return errors.Wrap(err, "error mounting cgroups")
|
mountpoints, err = cgroups.MountPoints()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := manager.NewManager(mountpoints)
|
||||||
|
if err = m.MountAll(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
|
||||||
|
target := path.Join("/sys/fs/cgroup", memoryCgroup, memoryUseHierarchy)
|
||||||
|
if err = ioutil.WriteFile(target, memoryUseHierarchyContents, memoryUseHierarchyPermissions); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to enable memory hierarchy support")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/* 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 rootfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager/virtual"
|
||||||
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MountSubDevices represents the MountSubDevices task.
|
||||||
|
type MountSubDevices struct{}
|
||||||
|
|
||||||
|
// NewMountSubDevicesTask initializes and returns an MountSubDevices task.
|
||||||
|
func NewMountSubDevicesTask() phase.Task {
|
||||||
|
return &MountSubDevices{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeFunc returns the runtime function.
|
||||||
|
func (task *MountSubDevices) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||||
|
switch mode {
|
||||||
|
case runtime.Standard:
|
||||||
|
return task.runtime
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (task *MountSubDevices) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||||
|
var mountpoints *mount.Points
|
||||||
|
mountpoints, err = virtual.SubMountPoints()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := manager.NewManager(mountpoints)
|
||||||
|
if err = m.MountAll(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||||
"github.com/talos-systems/talos/internal/pkg/proc"
|
"github.com/talos-systems/talos/internal/pkg/kernel/sysctl"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,5 +26,5 @@ func (task *Sysctls) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (task *Sysctls) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
func (task *Sysctls) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||||
return proc.WriteSystemProperty(&proc.SystemProperty{Key: "net.ipv4.ip_forward", Value: "1"})
|
return sysctl.WriteSystemProperty(&sysctl.SystemProperty{Key: "net.ipv4.ip_forward", Value: "1"})
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,15 @@
|
|||||||
package userdata
|
package userdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/mount"
|
"fmt"
|
||||||
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtraDevices represents the ExtraDevices task.
|
// ExtraDevices represents the ExtraDevices task.
|
||||||
@ -26,8 +30,20 @@ func (task *ExtraDevices) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (task *ExtraDevices) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
func (task *ExtraDevices) runtime(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||||
// Mount the extra devices.
|
if data.Install == nil || data.Install.ExtraDevices == nil {
|
||||||
if err = mount.ExtraDevices(data); err != nil {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mountpoints := mount.NewMountPoints()
|
||||||
|
for _, extra := range data.Install.ExtraDevices {
|
||||||
|
for i, part := range extra.Partitions {
|
||||||
|
devname := fmt.Sprintf("%s%d", extra.Device, i+1)
|
||||||
|
mountpoints.Set(devname, mount.NewMountPoint(devname, part.MountPoint, "xfs", unix.MS_NOATIME, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extras := manager.NewManager(mountpoints)
|
||||||
|
if err = extras.MountAll(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/install"
|
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ func (b *BareMetal) Name() string {
|
|||||||
// UserData implements the platform.Platform interface.
|
// UserData implements the platform.Platform interface.
|
||||||
func (b *BareMetal) UserData() (data *userdata.UserData, err error) {
|
func (b *BareMetal) UserData() (data *userdata.UserData, err error) {
|
||||||
var option *string
|
var option *string
|
||||||
if option = kernel.Cmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
if option = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
||||||
return data, errors.Errorf("no user data option was found")
|
return data, errors.Errorf("no user data option was found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,17 +71,12 @@ func (b *BareMetal) UserData() (data *userdata.UserData, err error) {
|
|||||||
return userdata.Download(*option)
|
return userdata.Download(*option)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface.
|
// Initialize provides the functionality to install talos by downloading the
|
||||||
func (b *BareMetal) Prepare(data *userdata.UserData) (err error) {
|
|
||||||
return install.Prepare(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install provides the functionality to install talos by downloading the
|
|
||||||
// required artifacts and writing them to a target device.
|
// required artifacts and writing them to a target device.
|
||||||
// nolint: dupl
|
// nolint: dupl
|
||||||
func (b *BareMetal) Install(data *userdata.UserData) (err error) {
|
func (b *BareMetal) Initialize(data *userdata.UserData) (err error) {
|
||||||
var endpoint *string
|
var endpoint *string
|
||||||
if endpoint = kernel.Cmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
if endpoint = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
||||||
return errors.Errorf("failed to find %s in kernel parameters", constants.KernelParamUserData)
|
return errors.Errorf("failed to find %s in kernel parameters", constants.KernelParamUserData)
|
||||||
}
|
}
|
||||||
cmdline := kernel.NewDefaultCmdline()
|
cmdline := kernel.NewDefaultCmdline()
|
||||||
@ -93,7 +88,8 @@ func (b *BareMetal) Install(data *userdata.UserData) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = install.Install(cmdline.String(), data); err != nil {
|
i := installer.NewInstaller(cmdline, data)
|
||||||
|
if err = i.Install(); err != nil {
|
||||||
return errors.Wrap(err, "failed to install")
|
return errors.Wrap(err, "failed to install")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,9 +121,9 @@ func (a *AWS) UserData() (*userdata.UserData, error) {
|
|||||||
return userdata.Download(AWSUserDataEndpoint)
|
return userdata.Download(AWSUserDataEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||||
func (a *AWS) Prepare(data *userdata.UserData) (err error) {
|
func (a *AWS) Initialize(data *userdata.UserData) (err error) {
|
||||||
return nil
|
return hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostname() (err error) {
|
func hostname() (err error) {
|
||||||
@ -149,8 +149,3 @@ func hostname() (err error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install implements the platform.Platform interface and handles additional system setup.
|
|
||||||
func (a *AWS) Install(data *userdata.UserData) (err error) {
|
|
||||||
return hostname()
|
|
||||||
}
|
|
||||||
|
@ -37,11 +37,6 @@ func (a *Azure) UserData() (*userdata.UserData, error) {
|
|||||||
return userdata.Download(AzureUserDataEndpoint, userdata.WithHeaders(map[string]string{"Metadata": "true"}), userdata.WithFormat("base64"))
|
return userdata.Download(AzureUserDataEndpoint, userdata.WithHeaders(map[string]string{"Metadata": "true"}), userdata.WithFormat("base64"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
|
||||||
func (a *Azure) Prepare(data *userdata.UserData) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostname() (err error) {
|
func hostname() (err error) {
|
||||||
|
|
||||||
// TODO get this sorted; assuming we need to set appropriate headers
|
// TODO get this sorted; assuming we need to set appropriate headers
|
||||||
@ -72,7 +67,7 @@ func hostname() (err error) {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install implements the platform.Platform interface and handles additional system setup.
|
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||||
func (a *Azure) Install(data *userdata.UserData) (err error) {
|
func (a *Azure) Initialize(data *userdata.UserData) (err error) {
|
||||||
return hostname()
|
return hostname()
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,7 @@ func (gc *GoogleCloud) UserData() (data *userdata.UserData, err error) {
|
|||||||
return ud, nil
|
return ud, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||||
func (gc *GoogleCloud) Prepare(data *userdata.UserData) (err error) {
|
func (gc *GoogleCloud) Initialize(data *userdata.UserData) (err error) {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install implements the platform.Platform interface and handles additional system setup.
|
|
||||||
func (gc *GoogleCloud) Install(data *userdata.UserData) (err error) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/install"
|
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
)
|
)
|
||||||
@ -32,19 +32,14 @@ func (p *Packet) UserData() (data *userdata.UserData, err error) {
|
|||||||
return userdata.Download(PacketUserDataEndpoint)
|
return userdata.Download(PacketUserDataEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface.
|
// Initialize implements the platform.Platform interface.
|
||||||
func (p *Packet) Prepare(data *userdata.UserData) (err error) {
|
|
||||||
return install.Prepare(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install provides the functionality to install talos by downloading the
|
|
||||||
// required artifacts and writing them to a target device.
|
|
||||||
// nolint: dupl
|
// nolint: dupl
|
||||||
func (p *Packet) Install(data *userdata.UserData) (err error) {
|
func (p *Packet) Initialize(data *userdata.UserData) (err error) {
|
||||||
var endpoint *string
|
var endpoint *string
|
||||||
if endpoint = kernel.Cmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
if endpoint = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); endpoint == nil {
|
||||||
return errors.Errorf("failed to find %s in kernel parameters", constants.KernelParamUserData)
|
return errors.Errorf("failed to find %s in kernel parameters", constants.KernelParamUserData)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdline := kernel.NewDefaultCmdline()
|
cmdline := kernel.NewDefaultCmdline()
|
||||||
cmdline.Append("initrd", filepath.Join("/", "default", "initramfs.xz"))
|
cmdline.Append("initrd", filepath.Join("/", "default", "initramfs.xz"))
|
||||||
cmdline.Append(constants.KernelParamPlatform, "packet")
|
cmdline.Append(constants.KernelParamPlatform, "packet")
|
||||||
@ -54,7 +49,8 @@ func (p *Packet) Install(data *userdata.UserData) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = install.Install(cmdline.String(), data); err != nil {
|
i := installer.NewInstaller(cmdline, data)
|
||||||
|
if err = i.Install(); err != nil {
|
||||||
return errors.Wrap(err, "failed to install")
|
return errors.Wrap(err, "failed to install")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ func (vmw *VMware) Name() string {
|
|||||||
// UserData implements the platform.Platform interface.
|
// UserData implements the platform.Platform interface.
|
||||||
func (vmw *VMware) UserData() (data *userdata.UserData, err error) {
|
func (vmw *VMware) UserData() (data *userdata.UserData, err error) {
|
||||||
var option *string
|
var option *string
|
||||||
if option = kernel.Cmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
if option = kernel.ProcCmdline().Get(constants.KernelParamUserData).First(); option == nil {
|
||||||
return data, fmt.Errorf("no user data option was found")
|
return data, fmt.Errorf("no user data option was found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +65,7 @@ func (vmw *VMware) UserData() (data *userdata.UserData, err error) {
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface and handles initial host preparation.
|
// Initialize implements the platform.Platform interface and handles additional system setup.
|
||||||
func (vmw *VMware) Prepare(data *userdata.UserData) (err error) {
|
func (vmw *VMware) Initialize(data *userdata.UserData) (err error) {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install implements the platform.Platform interface and handles additional system setup.
|
|
||||||
func (vmw *VMware) Install(data *userdata.UserData) (err error) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,7 @@ func (c *Container) UserData() (data *userdata.UserData, err error) {
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface.
|
// Initialize implements the platform.Platform interface.
|
||||||
func (c *Container) Prepare(data *userdata.UserData) (err error) {
|
func (c *Container) Initialize(data *userdata.UserData) error {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install implements the platform.Platform interface.
|
|
||||||
func (c *Container) Install(data *userdata.UserData) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,8 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/install"
|
"github.com/talos-systems/talos/internal/pkg/installer"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
|
||||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -62,21 +61,21 @@ func (i *ISO) UserData() (data *userdata.UserData, err error) {
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare implements the platform.Platform interface.
|
// Initialize implements the platform.Platform interface.
|
||||||
func (i *ISO) Prepare(data *userdata.UserData) (err error) {
|
func (i *ISO) Initialize(data *userdata.UserData) (err error) {
|
||||||
var dev *probe.ProbedBlockDevice
|
var dev *probe.ProbedBlockDevice
|
||||||
dev, err = probe.GetDevWithFileSystemLabel(constants.ISOFilesystemLabel)
|
dev, err = probe.GetDevWithFileSystemLabel(constants.ISOFilesystemLabel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("failed to find %s iso: %v", constants.ISOFilesystemLabel, err)
|
return errors.Errorf("failed to find %s iso: %v", constants.ISOFilesystemLabel, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), unix.MS_RDONLY, "")
|
if err = unix.Mount(dev.Path, "/tmp", dev.SuperBlock.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||||
if err = mount.WithRetry(mountpoint); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range []string{"/tmp/usr/install/vmlinuz", "/tmp/usr/install/initramfs.xz"} {
|
for _, f := range []string{"/tmp/usr/install/vmlinuz", "/tmp/usr/install/initramfs.xz"} {
|
||||||
source, err := ioutil.ReadFile(f)
|
var source []byte
|
||||||
|
source, err = ioutil.ReadFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -85,11 +84,6 @@ func (i *ISO) Prepare(data *userdata.UserData) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return install.Prepare(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install implements the platform.Platform interface.
|
|
||||||
func (i *ISO) Install(data *userdata.UserData) error {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
fmt.Print("Talos configuration URL: ")
|
fmt.Print("Talos configuration URL: ")
|
||||||
endpoint, err := reader.ReadString('\n')
|
endpoint, err := reader.ReadString('\n')
|
||||||
@ -102,7 +96,8 @@ func (i *ISO) Install(data *userdata.UserData) error {
|
|||||||
cmdline.Append(constants.KernelParamPlatform, "bare-metal")
|
cmdline.Append(constants.KernelParamPlatform, "bare-metal")
|
||||||
cmdline.Append(constants.KernelParamUserData, endpoint)
|
cmdline.Append(constants.KernelParamUserData, endpoint)
|
||||||
|
|
||||||
if err = install.Install(cmdline.String(), data); err != nil {
|
inst := installer.NewInstaller(cmdline, data)
|
||||||
|
if err = inst.Install(); err != nil {
|
||||||
return errors.Wrap(err, "failed to install")
|
return errors.Wrap(err, "failed to install")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +25,7 @@ import (
|
|||||||
type Platform interface {
|
type Platform interface {
|
||||||
Name() string
|
Name() string
|
||||||
UserData() (*userdata.UserData, error)
|
UserData() (*userdata.UserData, error)
|
||||||
Prepare(*userdata.UserData) error
|
Initialize(*userdata.UserData) error
|
||||||
Install(*userdata.UserData) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlatform is a helper func for discovering the current platform.
|
// NewPlatform is a helper func for discovering the current platform.
|
||||||
@ -34,7 +33,7 @@ type Platform interface {
|
|||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func NewPlatform() (p Platform, err error) {
|
func NewPlatform() (p Platform, err error) {
|
||||||
var platform string
|
var platform string
|
||||||
if p := kernel.Cmdline().Get(constants.KernelParamPlatform).First(); p != nil {
|
if p := kernel.ProcCmdline().Get(constants.KernelParamPlatform).First(); p != nil {
|
||||||
platform = *p
|
platform = *p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||||
apiptask "github.com/talos-systems/talos/internal/app/machined/internal/phase/api"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase/api"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/install"
|
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/network"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase/network"
|
||||||
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase/platform"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/rootfs"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase/rootfs"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/security"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase/security"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase/services"
|
"github.com/talos-systems/talos/internal/app/machined/internal/phase/services"
|
||||||
@ -45,6 +45,7 @@ func run() (err error) {
|
|||||||
security.NewSecurityTask(),
|
security.NewSecurityTask(),
|
||||||
rootfs.NewSystemDirectoryTask(),
|
rootfs.NewSystemDirectoryTask(),
|
||||||
rootfs.NewMountCgroupsTask(),
|
rootfs.NewMountCgroupsTask(),
|
||||||
|
rootfs.NewMountSubDevicesTask(),
|
||||||
sysctls.NewSysctlsTask(),
|
sysctls.NewSysctlsTask(),
|
||||||
),
|
),
|
||||||
phase.NewPhase(
|
phase.NewPhase(
|
||||||
@ -68,11 +69,11 @@ func run() (err error) {
|
|||||||
userdatatask.NewExtraFilesTask(),
|
userdatatask.NewExtraFilesTask(),
|
||||||
),
|
),
|
||||||
phase.NewPhase(
|
phase.NewPhase(
|
||||||
"installation",
|
"platform tasks",
|
||||||
install.NewInstallTask(),
|
platform.NewPlatformTask(),
|
||||||
),
|
),
|
||||||
phase.NewPhase(
|
phase.NewPhase(
|
||||||
"overlay",
|
"overlay mounts",
|
||||||
rootfs.NewMountOverlayTask(),
|
rootfs.NewMountOverlayTask(),
|
||||||
),
|
),
|
||||||
phase.NewPhase(
|
phase.NewPhase(
|
||||||
@ -85,7 +86,7 @@ func run() (err error) {
|
|||||||
),
|
),
|
||||||
phase.NewPhase(
|
phase.NewPhase(
|
||||||
"service setup",
|
"service setup",
|
||||||
apiptask.NewAPITask(),
|
api.NewAPITask(),
|
||||||
services.NewServicesTask(),
|
services.NewServicesTask(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
/* 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 install
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Exists checks if Talos has already been installed to a block device.
|
|
||||||
// It works by searching for a filesystem with a well known label, and then,
|
|
||||||
// if the filesystem exists, checking for the existence of a file.
|
|
||||||
func Exists(devpath string) (bool, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
dev *probe.ProbedBlockDevice
|
|
||||||
)
|
|
||||||
|
|
||||||
if dev, err = probe.DevForFileSystemLabel(devpath, constants.BootPartitionLabel); err == nil {
|
|
||||||
// nolint: errcheck
|
|
||||||
defer dev.Close()
|
|
||||||
if dev.SuperBlock != nil {
|
|
||||||
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), 0, "")
|
|
||||||
if err = mount.WithRetry(mountpoint); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err = mount.UnWithRetry(mountpoint); err != nil {
|
|
||||||
log.Printf("WARNING: failed to unmount %s from /tmp", dev.Path)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err = os.Stat(filepath.Join("tmp", "installed"))
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
return true, nil
|
|
||||||
case os.IsNotExist(err):
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
@ -1,205 +0,0 @@
|
|||||||
/* 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 install
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"compress/gzip"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Install fetches the necessary data locations and copies or extracts
|
|
||||||
// to the target locations
|
|
||||||
// nolint: gocyclo
|
|
||||||
func Install(args string, data *userdata.UserData) (err error) {
|
|
||||||
if data.Install == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
if exists, err = Exists(data.Install.Boot.InstallDevice.Device); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
log.Println("found existing installation, skipping install step")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest := NewManifest(data)
|
|
||||||
for _, targets := range manifest.Targets {
|
|
||||||
for _, target := range targets {
|
|
||||||
// Handle any pre-setup work that's required
|
|
||||||
switch target.Label {
|
|
||||||
case constants.BootPartitionLabel:
|
|
||||||
// Install/Update the bootloader.
|
|
||||||
if err = syslinux.Prepare(target.Device); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case constants.DataPartitionLabel:
|
|
||||||
// Do nothing
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles the download and extraction of assets
|
|
||||||
if err = target.Install(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Install.Boot == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
syslinuxcfg := &syslinux.Cfg{
|
|
||||||
Default: "default",
|
|
||||||
Labels: []*syslinux.Label{
|
|
||||||
{
|
|
||||||
Root: "default",
|
|
||||||
Kernel: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Kernel)),
|
|
||||||
Initrd: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Initramfs)),
|
|
||||||
Append: args,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err = syslinux.Install(filepath.Join(constants.NewRoot, constants.BootMountPoint), syslinuxcfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = ioutil.WriteFile(filepath.Join(constants.NewRoot, constants.BootMountPoint, "installed"), []byte{}, 0400); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple extract function
|
|
||||||
// nolint: gocyclo, dupl
|
|
||||||
func untar(tarball *os.File, dst string) error {
|
|
||||||
|
|
||||||
var input io.Reader
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.HasSuffix(tarball.Name(), ".tar.gz") {
|
|
||||||
input, err = gzip.NewReader(tarball)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: errcheck
|
|
||||||
defer input.(*gzip.Reader).Close()
|
|
||||||
} else {
|
|
||||||
input = tarball
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := tar.NewReader(input)
|
|
||||||
|
|
||||||
for {
|
|
||||||
var header *tar.Header
|
|
||||||
|
|
||||||
header, err = tr.Next()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case err == io.EOF:
|
|
||||||
err = nil
|
|
||||||
return err
|
|
||||||
case err != nil:
|
|
||||||
return err
|
|
||||||
case header == nil:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// the target location where the dir/file should be created
|
|
||||||
target := filepath.Join(dst, header.Name)
|
|
||||||
|
|
||||||
// May need to add in support for these
|
|
||||||
/*
|
|
||||||
// Type '1' to '6' are header-only flags and may not have a data body.
|
|
||||||
TypeLink = '1' // Hard link
|
|
||||||
TypeSymlink = '2' // Symbolic link
|
|
||||||
TypeChar = '3' // Character device node
|
|
||||||
TypeBlock = '4' // Block device node
|
|
||||||
TypeDir = '5' // Directory
|
|
||||||
TypeFifo = '6' // FIFO node
|
|
||||||
*/
|
|
||||||
switch header.Typeflag {
|
|
||||||
case tar.TypeDir:
|
|
||||||
if err = os.MkdirAll(target, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case tar.TypeReg:
|
|
||||||
var downloadedFileput *os.File
|
|
||||||
|
|
||||||
downloadedFileput, err = os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = io.Copy(downloadedFileput, tr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = downloadedFileput.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case tar.TypeSymlink:
|
|
||||||
dest := filepath.Join(dst, header.Name)
|
|
||||||
source := header.Linkname
|
|
||||||
if err := os.Symlink(source, dest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func download(artifact, dest string) (*os.File, error) {
|
|
||||||
if err := os.MkdirAll(filepath.Dir(dest), 0700); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
downloadedFile, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the data
|
|
||||||
resp, err := http.Get(artifact)
|
|
||||||
if err != nil {
|
|
||||||
return downloadedFile, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: errcheck
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
// nolint: errcheck
|
|
||||||
downloadedFile.Close()
|
|
||||||
return nil, errors.Errorf("failed to download %s, got %d", artifact, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the body to file
|
|
||||||
_, err = io.Copy(downloadedFile, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return downloadedFile, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset downloadedFile file position to 0 so we can immediately read from it
|
|
||||||
_, err = downloadedFile.Seek(0, 0)
|
|
||||||
|
|
||||||
// TODO add support for checksum validation of downloaded file
|
|
||||||
|
|
||||||
return downloadedFile, err
|
|
||||||
}
|
|
252
internal/pkg/installer/installer.go
Normal file
252
internal/pkg/installer/installer.go
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
/* 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 installer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/installer/bootloader/syslinux"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/installer/manifest"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/version"
|
||||||
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultSizeBootDevice is the default size of the boot partition.
|
||||||
|
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
|
||||||
|
DefaultSizeBootDevice = 512 * 1000 * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultURLBase is the base URL for all default artifacts.
|
||||||
|
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
|
||||||
|
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
|
||||||
|
|
||||||
|
// DefaultKernelURL is the URL to the kernel.
|
||||||
|
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
|
||||||
|
|
||||||
|
// DefaultInitramfsURL is the URL to the initramfs.
|
||||||
|
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Installer represents the installer logic. It serves as the entrypoint to all
|
||||||
|
// installation methods.
|
||||||
|
type Installer struct {
|
||||||
|
cmdline *kernel.Cmdline
|
||||||
|
data *userdata.UserData
|
||||||
|
manifest *manifest.Manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInstaller initializes and returns an Installer.
|
||||||
|
func NewInstaller(cmdline *kernel.Cmdline, data *userdata.UserData) *Installer {
|
||||||
|
i := &Installer{
|
||||||
|
cmdline: cmdline,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
i.manifest = manifest.NewManifest(data)
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install fetches the necessary data locations and copies or extracts
|
||||||
|
// to the target locations.
|
||||||
|
// nolint: gocyclo
|
||||||
|
func (i *Installer) Install() (err error) {
|
||||||
|
if i.data.Install == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.data.Install.Boot != nil {
|
||||||
|
var ok bool
|
||||||
|
if ok, err = exists(i.data.Install.Boot.InstallDevice.Device); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
log.Println("found existing installation")
|
||||||
|
var mountpoints *mount.Points
|
||||||
|
mountpoints, err = owned.MountPointsFromLabels()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := manager.NewManager(mountpoints)
|
||||||
|
if err = m.MountAll(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = VerifyBootDevice(i.data); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to prepare boot device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the target device(s) can satisify the requested options.
|
||||||
|
|
||||||
|
if err = VerifyDataDevice(i.data); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to prepare data device")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.data.Install.Wipe {
|
||||||
|
if err = wipe(i.manifest); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to wipe device(s)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition and format the block device(s).
|
||||||
|
|
||||||
|
if err = i.manifest.ExecuteManifest(i.data, i.manifest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount the partitions.
|
||||||
|
|
||||||
|
var mountpoints *mount.Points
|
||||||
|
if i.data.Install.Boot != nil {
|
||||||
|
mountpoints, err = owned.MountPointsForDevice(i.data.Install.Boot.InstallDevice.Device)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mountpoints, err = owned.MountPointsFromLabels()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := manager.NewManager(mountpoints)
|
||||||
|
if err = m.MountAll(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the assets.
|
||||||
|
|
||||||
|
for _, targets := range i.manifest.Targets {
|
||||||
|
for _, target := range targets {
|
||||||
|
switch target.Label {
|
||||||
|
case constants.BootPartitionLabel:
|
||||||
|
// Install the bootloader.
|
||||||
|
if err = syslinux.Prepare(target.Device); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case constants.DataPartitionLabel:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the download and extraction of assets.
|
||||||
|
if err = target.Save(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the bootloader.
|
||||||
|
|
||||||
|
if i.data.Install.Boot == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syslinuxcfg := &syslinux.Cfg{
|
||||||
|
Default: "default",
|
||||||
|
Labels: []*syslinux.Label{
|
||||||
|
{
|
||||||
|
Root: "default",
|
||||||
|
Kernel: filepath.Join("/", "default", filepath.Base(i.data.Install.Boot.Kernel)),
|
||||||
|
Initrd: filepath.Join("/", "default", filepath.Base(i.data.Install.Boot.Initramfs)),
|
||||||
|
Append: i.cmdline.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syslinux.Install(filepath.Join(constants.BootMountPoint), syslinuxcfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(filepath.Join(constants.BootMountPoint, "installed"), []byte{}, 0400); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wipe(manifest *manifest.Manifest) (err error) {
|
||||||
|
var zero *os.File
|
||||||
|
if zero, err = os.Open("/dev/zero"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for dev := range manifest.Targets {
|
||||||
|
var f *os.File
|
||||||
|
if f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var size uint64
|
||||||
|
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); ret != 0 {
|
||||||
|
return errors.Errorf("failed to got block device size: %v", ret)
|
||||||
|
}
|
||||||
|
if _, err = io.CopyN(f, zero, int64(size)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = f.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = zero.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(devpath string) (bool, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
dev *probe.ProbedBlockDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
if dev, err = probe.DevForFileSystemLabel(devpath, constants.BootPartitionLabel); err == nil {
|
||||||
|
// nolint: errcheck
|
||||||
|
defer dev.Close()
|
||||||
|
if dev.SuperBlock != nil {
|
||||||
|
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), 0, "")
|
||||||
|
if err = mountpoint.Mount(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = mountpoint.Unmount(); err != nil {
|
||||||
|
log.Printf("WARNING: failed to unmount %s from /tmp", dev.Path)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err = os.Stat(filepath.Join("tmp", "installed"))
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return true, nil
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
107
internal/pkg/installer/installer_test.go
Normal file
107
internal/pkg/installer/installer_test.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/* 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 installer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type validateSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(validateSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *validateSuite) TestVerifyDevice() {
|
||||||
|
// Start off with success and then remove bits
|
||||||
|
data := &userdata.UserData{}
|
||||||
|
err := yaml.Unmarshal([]byte(testConfig), data)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
suite.Require().NoError(VerifyBootDevice(data))
|
||||||
|
suite.Require().NoError(VerifyDataDevice(data))
|
||||||
|
|
||||||
|
// No impact because we can infer all data from the data device and
|
||||||
|
// defaults.
|
||||||
|
data.Install.Boot = nil
|
||||||
|
suite.Require().NoError(VerifyBootDevice(data))
|
||||||
|
data.Install.Data = &userdata.InstallDevice{
|
||||||
|
Device: "/dev/sda",
|
||||||
|
}
|
||||||
|
suite.Require().NoError(VerifyDataDevice(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO we should move this to a well defined location
|
||||||
|
// Copied from userdata_test.go
|
||||||
|
const testConfig = `version: "1"
|
||||||
|
security:
|
||||||
|
os:
|
||||||
|
ca:
|
||||||
|
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
identity:
|
||||||
|
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
kubernetes:
|
||||||
|
ca:
|
||||||
|
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
sa:
|
||||||
|
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
frontproxy:
|
||||||
|
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
etcd:
|
||||||
|
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||||
|
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||||
|
networking:
|
||||||
|
os: {}
|
||||||
|
kubernetes: {}
|
||||||
|
services:
|
||||||
|
init:
|
||||||
|
cni: flannel
|
||||||
|
kubeadm:
|
||||||
|
certificateKey: 'test'
|
||||||
|
configuration: |
|
||||||
|
apiVersion: kubeadm.k8s.io/v1beta1
|
||||||
|
kind: InitConfiguration
|
||||||
|
localAPIEndpoint:
|
||||||
|
bindPort: 6443
|
||||||
|
bootstrapTokens:
|
||||||
|
- token: '1qbsj9.3oz5hsk6grdfp98b'
|
||||||
|
ttl: 0s
|
||||||
|
---
|
||||||
|
apiVersion: kubeadm.k8s.io/v1beta1
|
||||||
|
kind: ClusterConfiguration
|
||||||
|
clusterName: test
|
||||||
|
kubernetesVersion: v1.14.1
|
||||||
|
---
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
mode: ipvs
|
||||||
|
ipvs:
|
||||||
|
scheduler: lc
|
||||||
|
trustd:
|
||||||
|
username: 'test'
|
||||||
|
password: 'test'
|
||||||
|
endpoints: [ "1.2.3.4" ]
|
||||||
|
certSANs: []
|
||||||
|
install:
|
||||||
|
wipe: true
|
||||||
|
force: true
|
||||||
|
boot:
|
||||||
|
device: /dev/sda
|
||||||
|
size: 1024000000
|
||||||
|
data:
|
||||||
|
device: /dev/sda
|
||||||
|
size: 1024000000
|
||||||
|
`
|
@ -2,230 +2,55 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package install
|
package manifest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/vfat"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/vfat"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/table"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/table"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/version"
|
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// Manifest represents the instructions for preparing all block devices
|
||||||
// DefaultSizeRootDevice is the default size of the root partition.
|
// for an installation.
|
||||||
// TODO(andrewrynhard): We should inspect the tarball's uncompressed size and dynamically set the root partition's size.
|
type Manifest struct {
|
||||||
DefaultSizeRootDevice = 2048 * 1000 * 1000
|
Targets map[string][]*Target
|
||||||
|
|
||||||
// DefaultSizeBootDevice is the default size of the boot partition.
|
|
||||||
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
|
|
||||||
DefaultSizeBootDevice = 512 * 1000 * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultURLBase is the base URL for all default artifacts.
|
|
||||||
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
|
|
||||||
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
|
|
||||||
|
|
||||||
// DefaultKernelURL is the URL to the kernel.
|
|
||||||
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
|
|
||||||
|
|
||||||
// DefaultInitramfsURL is the URL to the initramfs.
|
|
||||||
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Prepare handles setting/consolidating/defaulting userdata pieces specific to
|
|
||||||
// installation
|
|
||||||
// TODO: See if this would be more appropriate in userdata
|
|
||||||
// nolint: dupl, gocyclo
|
|
||||||
func Prepare(data *userdata.UserData) (err error) {
|
|
||||||
if data.Install == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var exists bool
|
// Target represents an installation partition.
|
||||||
if exists, err = Exists(data.Install.Boot.InstallDevice.Device); err != nil {
|
type Target struct {
|
||||||
return err
|
Label string
|
||||||
|
MountPoint string
|
||||||
|
Device string
|
||||||
|
FileSystemType string
|
||||||
|
PartitionName string
|
||||||
|
Size uint
|
||||||
|
Force bool
|
||||||
|
Test bool
|
||||||
|
Assets []*Asset
|
||||||
|
BlockDevice *blockdevice.BlockDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
// Asset represents a file required by a target.
|
||||||
log.Println("found existing installation, skipping prepare step")
|
type Asset struct {
|
||||||
return nil
|
Source string
|
||||||
}
|
Destination string
|
||||||
|
|
||||||
// Verify that the target device(s) can satisify the requested options.
|
|
||||||
|
|
||||||
if err = VerifyDataDevice(data); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to prepare data device")
|
|
||||||
}
|
|
||||||
if err = VerifyBootDevice(data); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to prepare boot device")
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest := NewManifest(data)
|
|
||||||
|
|
||||||
if data.Install.Wipe {
|
|
||||||
if err = WipeDevices(manifest); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to wipe device(s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and format all partitions.
|
|
||||||
|
|
||||||
if err = ExecuteManifest(data, manifest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteManifest partitions and formats all disks in a manifest.
|
|
||||||
func ExecuteManifest(data *userdata.UserData, manifest *Manifest) (err error) {
|
|
||||||
for dev, targets := range manifest.Targets {
|
|
||||||
var bd *blockdevice.BlockDevice
|
|
||||||
if bd, err = blockdevice.Open(dev, blockdevice.WithNewGPT(data.Install.Force)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// nolint: errcheck
|
|
||||||
defer bd.Close()
|
|
||||||
|
|
||||||
for _, target := range targets {
|
|
||||||
if err = target.Partition(bd); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to partition device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = bd.RereadPartitionTable(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, target := range targets {
|
|
||||||
if err = target.Format(); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to format device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDataDevice verifies the supplied data device options.
|
|
||||||
func VerifyDataDevice(data *userdata.UserData) (err error) {
|
|
||||||
// Set data device to root device if not specified
|
|
||||||
if data.Install.Data == nil {
|
|
||||||
data.Install.Data = &userdata.InstallDevice{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Install.Data.Device == "" {
|
|
||||||
return errors.New("a data device is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !data.Install.Force {
|
|
||||||
if err = VerifyDiskAvailability(constants.DataPartitionLabel); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to verify disk availability")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyBootDevice verifies the supplied boot device options.
|
|
||||||
func VerifyBootDevice(data *userdata.UserData) (err error) {
|
|
||||||
if data.Install.Boot == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Install.Boot.Device == "" {
|
|
||||||
// We can safely assume data device is defined at this point
|
|
||||||
// because VerifyDataDevice should have been called first in
|
|
||||||
// in the chain
|
|
||||||
data.Install.Boot.Device = data.Install.Data.Device
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Install.Boot.Size == 0 {
|
|
||||||
data.Install.Boot.Size = DefaultSizeBootDevice
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Install.Boot.Kernel == "" {
|
|
||||||
data.Install.Boot.Kernel = DefaultKernelURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Install.Boot.Initramfs == "" {
|
|
||||||
data.Install.Boot.Initramfs = DefaultInitramfsURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if !data.Install.Force {
|
|
||||||
if err = VerifyDiskAvailability(constants.BootPartitionLabel); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to verify disk availability")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDiskAvailability verifies that no filesystems currently exist with
|
|
||||||
// the labels used by the OS.
|
|
||||||
func VerifyDiskAvailability(label string) (err error) {
|
|
||||||
var dev *probe.ProbedBlockDevice
|
|
||||||
if dev, err = probe.GetDevWithFileSystemLabel(label); err != nil {
|
|
||||||
// We return here because we only care if we can discover the
|
|
||||||
// device successfully and confirm that the disk is not in use.
|
|
||||||
// TODO(andrewrynhard): We should return a custom error type here
|
|
||||||
// that we can use to confirm the device was not found.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if dev.SuperBlock != nil {
|
|
||||||
return errors.Errorf("target install device %s is not empty, found existing %s file system", label, dev.SuperBlock.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WipeDevices writes zeros to each block device in the preparation manifest.
|
|
||||||
func WipeDevices(manifest *Manifest) (err error) {
|
|
||||||
var zero *os.File
|
|
||||||
if zero, err = os.Open("/dev/zero"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for dev := range manifest.Targets {
|
|
||||||
var f *os.File
|
|
||||||
if f, err = os.OpenFile(dev, os.O_RDWR, os.ModeDevice); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var size uint64
|
|
||||||
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); ret != 0 {
|
|
||||||
return errors.Errorf("failed to got block device size: %v", ret)
|
|
||||||
}
|
|
||||||
if _, err = io.CopyN(f, zero, int64(size)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = f.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = zero.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManifest initializes and returns a Manifest.
|
// NewManifest initializes and returns a Manifest.
|
||||||
@ -259,7 +84,7 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
|
|||||||
Destination: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Initramfs)),
|
Destination: filepath.Join("/", "default", filepath.Base(data.Install.Boot.Initramfs)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MountPoint: filepath.Join(constants.NewRoot, constants.BootMountPoint),
|
MountPoint: constants.BootMountPoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +94,7 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
|
|||||||
Size: data.Install.Data.Size,
|
Size: data.Install.Data.Size,
|
||||||
Force: data.Install.Force,
|
Force: data.Install.Force,
|
||||||
Test: false,
|
Test: false,
|
||||||
MountPoint: filepath.Join(constants.NewRoot, constants.DataMountPoint),
|
MountPoint: constants.DataMountPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range []*Target{bootTarget, dataTarget} {
|
for _, target := range []*Target{bootTarget, dataTarget} {
|
||||||
@ -299,33 +124,37 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
|
|||||||
return manifest
|
return manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manifest represents the instructions for preparing all block devices
|
// ExecuteManifest partitions and formats all disks in a manifest.
|
||||||
// for an installation.
|
func (m *Manifest) ExecuteManifest(data *userdata.UserData, manifest *Manifest) (err error) {
|
||||||
type Manifest struct {
|
for dev, targets := range manifest.Targets {
|
||||||
Targets map[string][]*Target
|
var bd *blockdevice.BlockDevice
|
||||||
|
if bd, err = blockdevice.Open(dev, blockdevice.WithNewGPT(data.Install.Force)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// nolint: errcheck
|
||||||
|
defer bd.Close()
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
if err = target.Partition(bd); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to partition device")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target represents an installation partition.
|
if err = bd.RereadPartitionTable(); err != nil {
|
||||||
type Target struct {
|
return err
|
||||||
Label string
|
|
||||||
MountPoint string
|
|
||||||
Device string
|
|
||||||
FileSystemType string
|
|
||||||
PartitionName string
|
|
||||||
Size uint
|
|
||||||
Force bool
|
|
||||||
Test bool
|
|
||||||
Assets []*Asset
|
|
||||||
BlockDevice *blockdevice.BlockDevice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asset represents a file required by a target.
|
for _, target := range targets {
|
||||||
type Asset struct {
|
if err = target.Format(); err != nil {
|
||||||
Source string
|
return errors.Wrap(err, "failed to format device")
|
||||||
Destination string
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Partition creates a new partition on the specified device
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition creates a new partition on the specified device.
|
||||||
// nolint: dupl, gocyclo
|
// nolint: dupl, gocyclo
|
||||||
func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
|
func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
|
||||||
log.Printf("partitioning %s - %s\n", t.Device, t.Label)
|
log.Printf("partitioning %s - %s\n", t.Device, t.Label)
|
||||||
@ -374,7 +203,7 @@ func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format creates a xfs filesystem on the device/partition
|
// Format creates a filesystem on the device/partition.
|
||||||
func (t *Target) Format() error {
|
func (t *Target) Format() error {
|
||||||
log.Printf("formatting partition %s - %s\n", t.PartitionName, t.Label)
|
log.Printf("formatting partition %s - %s\n", t.PartitionName, t.Label)
|
||||||
if t.Label == constants.BootPartitionLabel {
|
if t.Label == constants.BootPartitionLabel {
|
||||||
@ -387,10 +216,10 @@ func (t *Target) Format() error {
|
|||||||
return xfs.MakeFS(t.PartitionName, opts...)
|
return xfs.MakeFS(t.PartitionName, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install handles downloading the necessary assets and extracting them to
|
// Save handles downloading the necessary assets and extracting them to
|
||||||
// the appropriate locations
|
// the appropriate location.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func (t *Target) Install() error {
|
func (t *Target) Save() error {
|
||||||
// Download and extract all artifacts.
|
// Download and extract all artifacts.
|
||||||
var sourceFile *os.File
|
var sourceFile *os.File
|
||||||
var destFile *os.File
|
var destFile *os.File
|
||||||
@ -425,7 +254,6 @@ func (t *Target) Install() error {
|
|||||||
source := u.Path
|
source := u.Path
|
||||||
dest := filepath.Join(t.MountPoint, asset.Destination)
|
dest := filepath.Join(t.MountPoint, asset.Destination)
|
||||||
|
|
||||||
log.Printf("copying %s to %s\n", asset.Source, dest)
|
|
||||||
sourceFile, err = os.Open(source)
|
sourceFile, err = os.Open(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -492,3 +320,120 @@ func (t *Target) Install() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint: gocyclo, dupl
|
||||||
|
func untar(tarball *os.File, dst string) error {
|
||||||
|
|
||||||
|
var input io.Reader
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if strings.HasSuffix(tarball.Name(), ".tar.gz") {
|
||||||
|
input, err = gzip.NewReader(tarball)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: errcheck
|
||||||
|
defer input.(*gzip.Reader).Close()
|
||||||
|
} else {
|
||||||
|
input = tarball
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := tar.NewReader(input)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var header *tar.Header
|
||||||
|
|
||||||
|
header, err = tr.Next()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
return err
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case header == nil:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// the target location where the dir/file should be created
|
||||||
|
target := filepath.Join(dst, header.Name)
|
||||||
|
|
||||||
|
// May need to add in support for these
|
||||||
|
/*
|
||||||
|
// Type '1' to '6' are header-only flags and may not have a data body.
|
||||||
|
TypeLink = '1' // Hard link
|
||||||
|
TypeSymlink = '2' // Symbolic link
|
||||||
|
TypeChar = '3' // Character device node
|
||||||
|
TypeBlock = '4' // Block device node
|
||||||
|
TypeDir = '5' // Directory
|
||||||
|
TypeFifo = '6' // FIFO node
|
||||||
|
*/
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if err = os.MkdirAll(target, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
var downloadedFileput *os.File
|
||||||
|
|
||||||
|
downloadedFileput, err = os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = io.Copy(downloadedFileput, tr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = downloadedFileput.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeSymlink:
|
||||||
|
dest := filepath.Join(dst, header.Name)
|
||||||
|
source := header.Linkname
|
||||||
|
if err := os.Symlink(source, dest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func download(artifact, dest string) (*os.File, error) {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dest), 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
downloadedFile, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data
|
||||||
|
resp, err := http.Get(artifact)
|
||||||
|
if err != nil {
|
||||||
|
return downloadedFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: errcheck
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
// nolint: errcheck
|
||||||
|
downloadedFile.Close()
|
||||||
|
return nil, errors.Errorf("failed to download %s, got %d", artifact, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the body to file
|
||||||
|
_, err = io.Copy(downloadedFile, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return downloadedFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset downloadedFile file position to 0 so we can immediately read from it
|
||||||
|
_, err = downloadedFile.Seek(0, 0)
|
||||||
|
|
||||||
|
// TODO add support for checksum validation of downloaded file
|
||||||
|
|
||||||
|
return downloadedFile, err
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package install
|
package manifest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -18,15 +18,15 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type validateSuite struct {
|
type manifestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateSuite(t *testing.T) {
|
func TestManifestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(validateSuite))
|
suite.Run(t, new(manifestSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *validateSuite) TestNewManifest() {
|
func (suite *manifestSuite) TestNewManifest() {
|
||||||
// Test with whole data
|
// Test with whole data
|
||||||
data := &userdata.UserData{}
|
data := &userdata.UserData{}
|
||||||
err := yaml.Unmarshal([]byte(testConfig), data)
|
err := yaml.Unmarshal([]byte(testConfig), data)
|
||||||
@ -36,26 +36,7 @@ func (suite *validateSuite) TestNewManifest() {
|
|||||||
assert.Equal(suite.T(), 2, len(manifests.Targets["/dev/sda"]))
|
assert.Equal(suite.T(), 2, len(manifests.Targets["/dev/sda"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *validateSuite) TestVerifyDevice() {
|
func (suite *manifestSuite) TestTargetInstall() {
|
||||||
// Start off with success and then remove bits
|
|
||||||
data := &userdata.UserData{}
|
|
||||||
err := yaml.Unmarshal([]byte(testConfig), data)
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
suite.Require().NoError(VerifyBootDevice(data))
|
|
||||||
suite.Require().NoError(VerifyDataDevice(data))
|
|
||||||
|
|
||||||
// No impact because we can infer all data from the data device and
|
|
||||||
// defaults.
|
|
||||||
data.Install.Boot = nil
|
|
||||||
suite.Require().NoError(VerifyBootDevice(data))
|
|
||||||
data.Install.Data = &userdata.InstallDevice{
|
|
||||||
Device: "/dev/sda",
|
|
||||||
}
|
|
||||||
suite.Require().NoError(VerifyDataDevice(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *validateSuite) TestTargetInstall() {
|
|
||||||
// Create Temp dirname for mountpoint
|
// Create Temp dirname for mountpoint
|
||||||
dir, err := ioutil.TempDir("", "talostest")
|
dir, err := ioutil.TempDir("", "talostest")
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
@ -95,7 +76,7 @@ func (suite *validateSuite) TestTargetInstall() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.Require().NoError(target.Install())
|
suite.Require().NoError(target.Save())
|
||||||
|
|
||||||
for _, expectedFile := range target.Assets {
|
for _, expectedFile := range target.Assets {
|
||||||
// Verify downloaded/copied file is at the appropriate location
|
// Verify downloaded/copied file is at the appropriate location
|
83
internal/pkg/installer/verify.go
Normal file
83
internal/pkg/installer/verify.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/* 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 installer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VerifyDataDevice verifies the supplied data device options.
|
||||||
|
func VerifyDataDevice(data *userdata.UserData) (err error) {
|
||||||
|
// Set data device to root device if not specified
|
||||||
|
if data.Install.Data == nil {
|
||||||
|
data.Install.Data = &userdata.InstallDevice{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Install.Data.Device == "" {
|
||||||
|
return errors.New("a data device is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.Install.Force {
|
||||||
|
if err = VerifyDiskAvailability(constants.DataPartitionLabel); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to verify disk availability")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyBootDevice verifies the supplied boot device options.
|
||||||
|
func VerifyBootDevice(data *userdata.UserData) (err error) {
|
||||||
|
if data.Install.Boot == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Install.Boot.Device == "" {
|
||||||
|
// We can safely assume data device is defined at this point
|
||||||
|
// because VerifyDataDevice should have been called first in
|
||||||
|
// in the chain
|
||||||
|
data.Install.Boot.Device = data.Install.Data.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Install.Boot.Size == 0 {
|
||||||
|
data.Install.Boot.Size = DefaultSizeBootDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Install.Boot.Kernel == "" {
|
||||||
|
data.Install.Boot.Kernel = DefaultKernelURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Install.Boot.Initramfs == "" {
|
||||||
|
data.Install.Boot.Initramfs = DefaultInitramfsURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.Install.Force {
|
||||||
|
if err = VerifyDiskAvailability(constants.BootPartitionLabel); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to verify disk availability")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyDiskAvailability verifies that no filesystems currently exist with
|
||||||
|
// the labels used by the OS.
|
||||||
|
func VerifyDiskAvailability(label string) (err error) {
|
||||||
|
var dev *probe.ProbedBlockDevice
|
||||||
|
if dev, err = probe.GetDevWithFileSystemLabel(label); err != nil {
|
||||||
|
// We return here because we only care if we can discover the
|
||||||
|
// device successfully and confirm that the disk is not in use.
|
||||||
|
// TODO(andrewrynhard): We should return a custom error type here
|
||||||
|
// that we can use to confirm the device was not found.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if dev.SuperBlock != nil {
|
||||||
|
return errors.Errorf("target install device %s is not empty, found existing %s file system", label, dev.SuperBlock.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -13,7 +13,7 @@ import (
|
|||||||
// NewDefaultCmdline returns a set of kernel parameters that serve as the base
|
// NewDefaultCmdline returns a set of kernel parameters that serve as the base
|
||||||
// for all Talos installations.
|
// for all Talos installations.
|
||||||
// nolint: golint
|
// nolint: golint
|
||||||
func NewDefaultCmdline() *cmdline {
|
func NewDefaultCmdline() *Cmdline {
|
||||||
cmdline := NewCmdline("")
|
cmdline := NewCmdline("")
|
||||||
cmdline.Append("page_poison", "1")
|
cmdline.Append("page_poison", "1")
|
||||||
cmdline.Append("slab_nomerge", "")
|
cmdline.Append("slab_nomerge", "")
|
||||||
@ -122,17 +122,18 @@ func (p Parameters) String() string {
|
|||||||
return strings.TrimRight(s, " ")
|
return strings.TrimRight(s, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
type cmdline struct {
|
// Cmdline represents a set of kernel parameters.
|
||||||
|
type Cmdline struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
Parameters
|
Parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance *cmdline
|
var instance *Cmdline
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
|
|
||||||
// Cmdline returns a representation of /proc/cmdline.
|
// Cmdline returns a representation of /proc/cmdline.
|
||||||
// nolint: golint
|
// nolint: golint
|
||||||
func Cmdline() *cmdline {
|
func ProcCmdline() *Cmdline {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
var err error
|
var err error
|
||||||
if instance, err = read(); err != nil {
|
if instance, err = read(); err != nil {
|
||||||
@ -146,15 +147,15 @@ func Cmdline() *cmdline {
|
|||||||
// NewCmdline initializes and returns a representation of the cmdline values
|
// NewCmdline initializes and returns a representation of the cmdline values
|
||||||
// specified by `parameters`.
|
// specified by `parameters`.
|
||||||
// nolint: golint
|
// nolint: golint
|
||||||
func NewCmdline(parameters string) *cmdline {
|
func NewCmdline(parameters string) *Cmdline {
|
||||||
parsed := parse(parameters)
|
parsed := parse(parameters)
|
||||||
c := &cmdline{sync.Mutex{}, parsed}
|
c := &Cmdline{sync.Mutex{}, parsed}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a kernel parameter.
|
// Get gets a kernel parameter.
|
||||||
func (c *cmdline) Get(k string) (value *Parameter) {
|
func (c *Cmdline) Get(k string) (value *Parameter) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
for _, value := range c.Parameters {
|
for _, value := range c.Parameters {
|
||||||
@ -167,7 +168,7 @@ func (c *cmdline) Get(k string) (value *Parameter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set sets a kernel parameter.
|
// Set sets a kernel parameter.
|
||||||
func (c *cmdline) Set(k string, v *Parameter) {
|
func (c *Cmdline) Set(k string, v *Parameter) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
for i, value := range c.Parameters {
|
for i, value := range c.Parameters {
|
||||||
@ -179,7 +180,7 @@ func (c *cmdline) Set(k string, v *Parameter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append appends a kernel parameter.
|
// Append appends a kernel parameter.
|
||||||
func (c *cmdline) Append(k string, v string) {
|
func (c *Cmdline) Append(k string, v string) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
for _, value := range c.Parameters {
|
for _, value := range c.Parameters {
|
||||||
@ -192,7 +193,7 @@ func (c *cmdline) Append(k string, v string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AppendAll appends a set of kernel parameters.
|
// AppendAll appends a set of kernel parameters.
|
||||||
func (c *cmdline) AppendAll(args []string) error {
|
func (c *Cmdline) AppendAll(args []string) error {
|
||||||
parameters := parse(strings.Join(args, " "))
|
parameters := parse(strings.Join(args, " "))
|
||||||
c.Parameters = append(c.Parameters, parameters...)
|
c.Parameters = append(c.Parameters, parameters...)
|
||||||
|
|
||||||
@ -200,7 +201,7 @@ func (c *cmdline) AppendAll(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the byte slice representation of the cmdline struct.
|
// Bytes returns the byte slice representation of the cmdline struct.
|
||||||
func (c *cmdline) Bytes() []byte {
|
func (c *Cmdline) Bytes() []byte {
|
||||||
return []byte(c.String())
|
return []byte(c.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +232,7 @@ func parse(parameters string) (parsed Parameters) {
|
|||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
func read() (c *cmdline, err error) {
|
func read() (c *Cmdline, err error) {
|
||||||
var parameters []byte
|
var parameters []byte
|
||||||
parameters, err = ioutil.ReadFile("/proc/cmdline")
|
parameters, err = ioutil.ReadFile("/proc/cmdline")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -239,7 +240,7 @@ func read() (c *cmdline, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parsed := parse(string(parameters))
|
parsed := parse(string(parameters))
|
||||||
c = &cmdline{sync.Mutex{}, parsed}
|
c = &Cmdline{sync.Mutex{}, parsed}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
32
internal/pkg/kernel/sysctl/sysctl.go
Normal file
32
internal/pkg/kernel/sysctl/sysctl.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* 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 sysctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SystemProperty represents a kernel system property.
|
||||||
|
type SystemProperty struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSystemProperty writes a value to a key under /proc/sys.
|
||||||
|
func WriteSystemProperty(prop *SystemProperty) error {
|
||||||
|
return ioutil.WriteFile(prop.Path(), []byte(prop.Value), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSystemProperty reads a value from a key under /proc/sys.
|
||||||
|
func ReadSystemProperty(prop *SystemProperty) ([]byte, error) {
|
||||||
|
return ioutil.ReadFile(prop.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path to the systctl file under /proc/sys.
|
||||||
|
func (prop *SystemProperty) Path() string {
|
||||||
|
return path.Join("/proc/sys", strings.Replace(prop.Key, ".", "/", -1))
|
||||||
|
}
|
41
internal/pkg/mount/manager/cgroups/cgroups.go
Normal file
41
internal/pkg/mount/manager/cgroups/cgroups.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* 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 cgroups
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MountPoints returns the cgroup mount points
|
||||||
|
func MountPoints() (mountpoints *mount.Points, err error) {
|
||||||
|
base := "/sys/fs/cgroup"
|
||||||
|
cgroups := mount.NewMountPoints()
|
||||||
|
cgroups.Set("dev", mount.NewMountPoint("tmpfs", base, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, "mode=755"))
|
||||||
|
|
||||||
|
controllers := []string{
|
||||||
|
"blkio",
|
||||||
|
"cpu",
|
||||||
|
"cpuacct",
|
||||||
|
"cpuset",
|
||||||
|
"devices",
|
||||||
|
"freezer",
|
||||||
|
"hugetlb",
|
||||||
|
"memory",
|
||||||
|
"net_cls",
|
||||||
|
"net_prio",
|
||||||
|
"perf_event",
|
||||||
|
"pids",
|
||||||
|
}
|
||||||
|
for _, c := range controllers {
|
||||||
|
p := path.Join(base, c)
|
||||||
|
cgroups.Set(c, mount.NewMountPoint(c, p, "cgroup", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_RELATIME, c))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cgroups, nil
|
||||||
|
}
|
92
internal/pkg/mount/manager/manager.go
Normal file
92
internal/pkg/mount/manager/manager.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/* 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 manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager represents a management layer for a set of mountpoints.
|
||||||
|
type Manager struct {
|
||||||
|
mountpoints *mount.Points
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager initializes and returns a Manager.
|
||||||
|
func NewManager(mountpoints *mount.Points) *Manager {
|
||||||
|
m := &Manager{
|
||||||
|
mountpoints: mountpoints,
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// MountAll mounts the device(s).
|
||||||
|
func (m *Manager) MountAll() (err error) {
|
||||||
|
iter := m.mountpoints.Iter()
|
||||||
|
|
||||||
|
// Mount the device(s).
|
||||||
|
|
||||||
|
for iter.Next() {
|
||||||
|
mountpoint := iter.Value()
|
||||||
|
// Repair the disk's partition table.
|
||||||
|
if mountpoint.Resize {
|
||||||
|
if err = mountpoint.ResizePartition(); err != nil {
|
||||||
|
return errors.Wrap(err, "resize")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mountpoint.Mount(); err != nil {
|
||||||
|
return errors.Wrap(err, "mount")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow the filesystem to the maximum allowed size.
|
||||||
|
if mountpoint.Resize {
|
||||||
|
if err = mountpoint.GrowFilesystem(); err != nil {
|
||||||
|
return errors.Wrap(err, "grow")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.Err() != nil {
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmountAll unmounts the device(s).
|
||||||
|
func (m *Manager) UnmountAll() (err error) {
|
||||||
|
iter := m.mountpoints.IterRev()
|
||||||
|
for iter.Next() {
|
||||||
|
mountpoint := iter.Value()
|
||||||
|
if err = mountpoint.Unmount(); err != nil {
|
||||||
|
return errors.Wrap(err, "unomunt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.Err() != nil {
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveAll moves the device(s).
|
||||||
|
// TODO(andrewrynhard): We need to skip calling the move method on mountpoints
|
||||||
|
// that are a child of another mountpoint. The kernel will handle moving the
|
||||||
|
// child mountpoints for us.
|
||||||
|
func (m *Manager) MoveAll(prefix string) (err error) {
|
||||||
|
iter := m.mountpoints.Iter()
|
||||||
|
for iter.Next() {
|
||||||
|
mountpoint := iter.Value()
|
||||||
|
if err = mountpoint.Move(prefix); err != nil {
|
||||||
|
return errors.Wrapf(err, "move")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.Err() != nil {
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package mount_test
|
package manager_test
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
@ -2,7 +2,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package install
|
package owned
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
@ -11,34 +11,11 @@ import (
|
|||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/probe"
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mount discovers the appropriate partitions by label and mounts them up
|
// MountPointsForDevice returns the mountpoints required to boot the system.
|
||||||
// to the appropriate mountpoint.
|
func MountPointsForDevice(devpath string) (mountpoints *mount.Points, err error) {
|
||||||
// TODO: See if we can consolidate this with rootfs/mount
|
|
||||||
func Mount(data *userdata.UserData) (err error) {
|
|
||||||
var mp *mount.Points
|
|
||||||
if mp, err = mountpoints(data.Install.Boot.InstallDevice.Device); err != nil {
|
|
||||||
return errors.Errorf("error initializing block devices: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := mp.Iter()
|
|
||||||
for iter.Next() {
|
|
||||||
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(constants.NewRoot)); err != nil {
|
|
||||||
return errors.Errorf("error mounting partitions: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if iter.Err() != nil {
|
|
||||||
return iter.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: dupl
|
|
||||||
func mountpoints(devpath string) (mountpoints *mount.Points, err error) {
|
|
||||||
mountpoints = mount.NewMountPoints()
|
mountpoints = mount.NewMountPoints()
|
||||||
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
||||||
var target string
|
var target string
|
||||||
@ -56,13 +33,38 @@ func mountpoints(devpath string) (mountpoints *mount.Points, err error) {
|
|||||||
log.Println("WARNING: no ESP partition was found")
|
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("probe device for filesystem %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
||||||
|
|
||||||
mountpoints.Set(name, mountpoint)
|
mountpoints.Set(name, mountpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mountpoints, nil
|
return mountpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MountPointsFromLabels returns the mountpoints required to boot the system.
|
||||||
|
func MountPointsFromLabels() (mountpoints *mount.Points, err error) {
|
||||||
|
mountpoints = mount.NewMountPoints()
|
||||||
|
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
|
||||||
|
var target string
|
||||||
|
switch name {
|
||||||
|
case constants.DataPartitionLabel:
|
||||||
|
target = constants.DataMountPoint
|
||||||
|
case constants.BootPartitionLabel:
|
||||||
|
target = constants.BootMountPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
var dev *probe.ProbedBlockDevice
|
||||||
|
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("find device with label %s: %v", name, err)
|
||||||
|
}
|
||||||
|
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
|
||||||
|
mountpoints.Set(name, mountpoint)
|
||||||
|
}
|
||||||
|
return mountpoints, nil
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package mount_test
|
package owned_test
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
25
internal/pkg/mount/manager/squashfs/squashfs.go
Normal file
25
internal/pkg/mount/manager/squashfs/squashfs.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* 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 squashfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gopkg.in/freddierice/go-losetup.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MountPoints returns the mountpoints required to boot the system.
|
||||||
|
func MountPoints(prefix string) (mountpoints *mount.Points, err error) {
|
||||||
|
var dev losetup.Device
|
||||||
|
dev, err = losetup.Attach("/"+constants.RootfsAsset, 0, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
squashfs := mount.NewMountPoints()
|
||||||
|
squashfs.Set("squashfs", mount.NewMountPoint(dev.Path(), "/", "squashfs", unix.MS_RDONLY, "", mount.WithPrefix(prefix), mount.WithReadOnly(true), mount.WithShared(true)))
|
||||||
|
|
||||||
|
return squashfs, nil
|
||||||
|
}
|
14
internal/pkg/mount/manager/squashfs/squashfs_test.go
Normal file
14
internal/pkg/mount/manager/squashfs/squashfs_test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* 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 squashfs_test
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
// added for accurate coverage estimation
|
||||||
|
//
|
||||||
|
// please remove it once any unit-test is added
|
||||||
|
// for this package
|
||||||
|
}
|
31
internal/pkg/mount/manager/virtual/virtual.go
Normal file
31
internal/pkg/mount/manager/virtual/virtual.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/* 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 virtual
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MountPoints returns the mountpoints required to boot the system.
|
||||||
|
func MountPoints() (mountpoints *mount.Points, err error) {
|
||||||
|
virtual := mount.NewMountPoints()
|
||||||
|
virtual.Set("dev", mount.NewMountPoint("devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"))
|
||||||
|
virtual.Set("proc", mount.NewMountPoint("proc", "/proc", "proc", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""))
|
||||||
|
virtual.Set("sys", mount.NewMountPoint("sysfs", "/sys", "sysfs", 0, ""))
|
||||||
|
virtual.Set("run", mount.NewMountPoint("tmpfs", "/run", "tmpfs", 0, ""))
|
||||||
|
virtual.Set("tmp", mount.NewMountPoint("tmpfs", "/tmp", "tmpfs", 0, ""))
|
||||||
|
|
||||||
|
return virtual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubMountPoints returns the mountpoints required to boot the system.
|
||||||
|
func SubMountPoints() (mountpoints *mount.Points, err error) {
|
||||||
|
virtual := mount.NewMountPoints()
|
||||||
|
virtual.Set("devshm", mount.NewMountPoint("tmpfs", "/dev/shm", "tmpfs", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_RELATIME, ""))
|
||||||
|
virtual.Set("devpts", mount.NewMountPoint("devpts", "/dev/pts", "devpts", unix.MS_NOSUID|unix.MS_NOEXEC, "ptmxmode=000,mode=620,gid=5"))
|
||||||
|
|
||||||
|
return virtual, nil
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package install_test
|
package virtual_test
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
@ -10,6 +10,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/blockdevice"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/filesystem/xfs"
|
||||||
|
gptpartition "github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt/partition"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/blockdevice/util"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +25,7 @@ type Point struct {
|
|||||||
fstype string
|
fstype string
|
||||||
flags uintptr
|
flags uintptr
|
||||||
data string
|
data string
|
||||||
|
*Options
|
||||||
}
|
}
|
||||||
|
|
||||||
// PointMap represents a unique set of mount points.
|
// PointMap represents a unique set of mount points.
|
||||||
@ -32,13 +38,15 @@ type Points struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMountPoint initializes and returns a Point struct.
|
// NewMountPoint initializes and returns a Point struct.
|
||||||
func NewMountPoint(source string, target string, fstype string, flags uintptr, data string) *Point {
|
func NewMountPoint(source string, target string, fstype string, flags uintptr, data string, setters ...Option) *Point {
|
||||||
|
opts := NewDefaultOptions(setters...)
|
||||||
return &Point{
|
return &Point{
|
||||||
source: source,
|
source: source,
|
||||||
target: target,
|
target: target,
|
||||||
fstype: fstype,
|
fstype: fstype,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
data: data,
|
data: data,
|
||||||
|
Options: opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,22 +82,20 @@ func (p *Point) Data() string {
|
|||||||
return p.data
|
return p.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithRetry attempts to retry a mount on EBUSY. It will attempt a retry
|
// Mount attempts to retry a mount on EBUSY. It will attempt a retry
|
||||||
// every 100 milliseconds over the course of 5 seconds.
|
// every 100 milliseconds over the course of 5 seconds.
|
||||||
func WithRetry(mountpoint *Point, setters ...Option) (err error) {
|
func (p *Point) Mount() (err error) {
|
||||||
opts := NewDefaultOptions(setters...)
|
if p.ReadOnly {
|
||||||
|
p.flags |= unix.MS_RDONLY
|
||||||
if opts.ReadOnly {
|
|
||||||
mountpoint.flags |= unix.MS_RDONLY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target := path.Join(opts.Prefix, mountpoint.target)
|
target := path.Join(p.Prefix, p.target)
|
||||||
|
|
||||||
if err = os.MkdirAll(target, os.ModeDir); err != nil {
|
if err = os.MkdirAll(target, os.ModeDir); err != nil {
|
||||||
return errors.Errorf("error creating mount point directory %s: %v", target, err)
|
return errors.Errorf("error creating mount point directory %s: %v", target, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
retry := func(source string, target string, fstype string, flags uintptr, data string) error {
|
retry := func(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
if err = unix.Mount(source, target, fstype, flags, data); err != nil {
|
if err = unix.Mount(source, target, fstype, flags, data); err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
@ -103,14 +109,15 @@ func WithRetry(mountpoint *Point, setters ...Option) (err error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Errorf("mount timeout: %v", err)
|
return errors.Errorf("mount timeout: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = retry(mountpoint.source, target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
|
if err = retry(p.source, target, p.fstype, p.flags, p.data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Shared {
|
if p.Shared {
|
||||||
if err = retry("", target, "", unix.MS_SHARED, ""); err != nil {
|
if err = retry("", target, "", unix.MS_SHARED, ""); err != nil {
|
||||||
return errors.Errorf("error mounting shared mount point %s: %v", target, err)
|
return errors.Errorf("error mounting shared mount point %s: %v", target, err)
|
||||||
}
|
}
|
||||||
@ -120,11 +127,9 @@ func WithRetry(mountpoint *Point, setters ...Option) (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnWithRetry attempts to retry an unmount on EBUSY. It will attempt a
|
// Unmount attempts to retry an unmount on EBUSY. It will attempt a
|
||||||
// retry every 100 milliseconds over the course of 5 seconds.
|
// retry every 100 milliseconds over the course of 5 seconds.
|
||||||
func UnWithRetry(mountpoint *Point, setters ...Option) (err error) {
|
func (p *Point) Unmount() (err error) {
|
||||||
opts := NewDefaultOptions(setters...)
|
|
||||||
|
|
||||||
retry := func(target string, flags int) error {
|
retry := func(target string, flags int) error {
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
if err = unix.Unmount(target, flags); err != nil {
|
if err = unix.Unmount(target, flags); err != nil {
|
||||||
@ -141,10 +146,74 @@ func UnWithRetry(mountpoint *Point, setters ...Option) (err error) {
|
|||||||
return errors.Errorf("mount timeout: %v", err)
|
return errors.Errorf("mount timeout: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
target := path.Join(opts.Prefix, mountpoint.target)
|
target := path.Join(p.Prefix, p.target)
|
||||||
if err := retry(target, 0); err != nil {
|
if err := retry(target, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move moves a mountpoint to a new location with a prefix.
|
||||||
|
func (p *Point) Move(prefix string) (err error) {
|
||||||
|
target := p.Target()
|
||||||
|
mountpoint := NewMountPoint(target, target, "", unix.MS_MOVE, "", WithPrefix(prefix))
|
||||||
|
if err = mountpoint.Mount(); err != nil {
|
||||||
|
return errors.Errorf("error moving mount point %s: %v", target, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizePartition resizes a partition to the maximum size allowed.
|
||||||
|
func (p *Point) ResizePartition() (err error) {
|
||||||
|
var devname string
|
||||||
|
if devname, err = util.DevnameFromPartname(p.Source()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bd, err := blockdevice.Open("/dev/" + devname)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("error opening block device %q: %v", devname, err)
|
||||||
|
}
|
||||||
|
// nolint: errcheck
|
||||||
|
defer bd.Close()
|
||||||
|
|
||||||
|
pt, err := bd.PartitionTable(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pt.Repair(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, partition := range pt.Partitions() {
|
||||||
|
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
|
||||||
|
if err := pt.Resize(partition); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pt.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: Rereading the partition table requires that all partitions be
|
||||||
|
// unmounted or it will fail with EBUSY.
|
||||||
|
if err := bd.RereadPartitionTable(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrowFilesystem grows a partition's filesystem to the maximum size allowed.
|
||||||
|
// NB: An XFS partition MUST be mounted, or this will fail.
|
||||||
|
func (p *Point) GrowFilesystem() (err error) {
|
||||||
|
if err = xfs.GrowFS(p.Target()); err != nil {
|
||||||
|
return errors.Wrap(err, "xfs_growfs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -6,9 +6,11 @@ package mount
|
|||||||
|
|
||||||
// Options is the functional options struct.
|
// Options is the functional options struct.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
Loopback string
|
||||||
Prefix string
|
Prefix string
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
Shared bool
|
Shared bool
|
||||||
|
Resize bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is the functional option func.
|
// Option is the functional option func.
|
||||||
@ -35,9 +37,18 @@ func WithShared(o bool) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithResize indicates that a the partition for a given mount point should be
|
||||||
|
// resized to the maximum allowed.
|
||||||
|
func WithResize(o bool) Option {
|
||||||
|
return func(args *Options) {
|
||||||
|
args.Resize = o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewDefaultOptions initializes a Options struct with default values.
|
// NewDefaultOptions initializes a Options struct with default values.
|
||||||
func NewDefaultOptions(setters ...Option) *Options {
|
func NewDefaultOptions(setters ...Option) *Options {
|
||||||
opts := &Options{
|
opts := &Options{
|
||||||
|
Loopback: "",
|
||||||
Prefix: "",
|
Prefix: "",
|
||||||
ReadOnly: false,
|
ReadOnly: false,
|
||||||
Shared: false,
|
Shared: false,
|
||||||
|
118
internal/pkg/mount/switchroot/switchroot.go
Normal file
118
internal/pkg/mount/switchroot/switchroot.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/* 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 switchroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Switch moves the rootfs to a specified directory. See
|
||||||
|
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c.
|
||||||
|
// nolint: gocyclo
|
||||||
|
func Switch(prefix string, virtual *manager.Manager) (err error) {
|
||||||
|
log.Println("moving virtual devices to the new rootfs")
|
||||||
|
if err = virtual.MoveAll(prefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("changing working directory into %s", prefix)
|
||||||
|
if err = unix.Chdir(prefix); err != nil {
|
||||||
|
return errors.Wrapf(err, "error changing working directory to %s", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
var old *os.File
|
||||||
|
if old, err = os.Open("/"); err != nil {
|
||||||
|
return errors.Wrap(err, "error opening /")
|
||||||
|
}
|
||||||
|
// nolint: errcheck
|
||||||
|
defer old.Close()
|
||||||
|
|
||||||
|
log.Printf("moving %s to /", prefix)
|
||||||
|
if err = unix.Mount(prefix, "/", "", unix.MS_MOVE, ""); err != nil {
|
||||||
|
return errors.Wrap(err, "error moving /")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("changing root directory")
|
||||||
|
if err = unix.Chroot("."); err != nil {
|
||||||
|
return errors.Wrap(err, "error chroot")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("cleaning up initramfs")
|
||||||
|
if err = recursiveDelete(int(old.Fd())); err != nil {
|
||||||
|
return errors.Wrap(err, "error deleting initramfs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that /sbin/init is machined. We call it init since this is the
|
||||||
|
// convention.
|
||||||
|
log.Println("executing /sbin/init")
|
||||||
|
if err = unix.Exec("/sbin/init", []string{"/sbin/init"}, []string{}); err != nil {
|
||||||
|
return errors.Wrap(err, "error executing /sbin/init")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recursiveDelete(fd int) error {
|
||||||
|
parentDev, err := getDev(fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := os.NewFile(uintptr(fd), "__ignored__")
|
||||||
|
// nolint: errcheck
|
||||||
|
defer dir.Close()
|
||||||
|
names, err := dir.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
|
||||||
|
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
|
||||||
|
if err != nil {
|
||||||
|
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// nolint: errcheck
|
||||||
|
defer unix.Close(childFd)
|
||||||
|
|
||||||
|
if childFdDev, err := getDev(childFd); err != nil {
|
||||||
|
return err
|
||||||
|
} else if childFdDev != parentDev {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := recursiveDelete(childFd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDev(fd int) (dev uint64, err error) {
|
||||||
|
var stat unix.Stat_t
|
||||||
|
|
||||||
|
if err := unix.Fstat(fd, &stat); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat.Dev, nil
|
||||||
|
}
|
14
internal/pkg/mount/switchroot/switchroot_test.go
Normal file
14
internal/pkg/mount/switchroot/switchroot_test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* 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 switchroot_test
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
// added for accurate coverage estimation
|
||||||
|
//
|
||||||
|
// please remove it once any unit-test is added
|
||||||
|
// for this package
|
||||||
|
}
|
@ -116,7 +116,7 @@ func (service *Service) dhclient4(ctx context.Context, ifname string, modifiers
|
|||||||
|
|
||||||
// Ignore DHCP-offered hostname if the kernel parameter is set
|
// Ignore DHCP-offered hostname if the kernel parameter is set
|
||||||
var kernHostname *string
|
var kernHostname *string
|
||||||
if kernHostname = kernel.Cmdline().Get(constants.KernelParamHostname).First(); kernHostname != nil {
|
if kernHostname = kernel.ProcCmdline().Get(constants.KernelParamHostname).First(); kernHostname != nil {
|
||||||
hostname = *kernHostname
|
hostname = *kernHostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ func ifup(ifname string, mtu int) (err error) {
|
|||||||
|
|
||||||
func defaultInterface() string {
|
func defaultInterface() string {
|
||||||
netif := DefaultInterface
|
netif := DefaultInterface
|
||||||
if option := kernel.Cmdline().Get(constants.KernelParamDefaultInterface).First(); option != nil {
|
if option := kernel.ProcCmdline().Get(constants.KernelParamDefaultInterface).First(); option != nil {
|
||||||
netif = *option
|
netif = *option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,26 +6,12 @@ package proc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.cloudfoundry.org/bytefmt"
|
"code.cloudfoundry.org/bytefmt"
|
||||||
"github.com/prometheus/procfs"
|
"github.com/prometheus/procfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemProperty represents a kernel system property.
|
|
||||||
type SystemProperty struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteSystemProperty writes a value to a key under /proc/sys.
|
|
||||||
func WriteSystemProperty(prop *SystemProperty) error {
|
|
||||||
keyPath := path.Join("/proc/sys", strings.Replace(prop.Key, ".", "/", -1))
|
|
||||||
return ioutil.WriteFile(keyPath, []byte(prop.Value), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessList contains all of the process stats we want
|
// ProcessList contains all of the process stats we want
|
||||||
// to display via top
|
// to display via top
|
||||||
type ProcessList struct {
|
type ProcessList struct {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
"github.com/talos-systems/talos/internal/pkg/proc"
|
"github.com/talos-systems/talos/internal/pkg/kernel/sysctl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +28,7 @@ func EnforceKSPPKernelParameters() error {
|
|||||||
var result *multierror.Error
|
var result *multierror.Error
|
||||||
for _, values := range RequiredKSPPKernelParameters {
|
for _, values := range RequiredKSPPKernelParameters {
|
||||||
var val *string
|
var val *string
|
||||||
if val = kernel.Cmdline().Get(values.Key()).First(); val == nil {
|
if val = kernel.ProcCmdline().Get(values.Key()).First(); val == nil {
|
||||||
result = multierror.Append(result, errors.Errorf("KSPP kernel parameter %s is required", values.Key()))
|
result = multierror.Append(result, errors.Errorf("KSPP kernel parameter %s is required", values.Key()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ func EnforceKSPPKernelParameters() error {
|
|||||||
// EnforceKSPPSysctls verifies that all required KSPP kernel sysctls are set
|
// EnforceKSPPSysctls verifies that all required KSPP kernel sysctls are set
|
||||||
// with the right value.
|
// with the right value.
|
||||||
func EnforceKSPPSysctls() (err error) {
|
func EnforceKSPPSysctls() (err error) {
|
||||||
props := []*proc.SystemProperty{
|
props := []*sysctl.SystemProperty{
|
||||||
{
|
{
|
||||||
Key: "kernel.kptr_restrict",
|
Key: "kernel.kptr_restrict",
|
||||||
Value: "1",
|
Value: "1",
|
||||||
@ -82,7 +82,7 @@ func EnforceKSPPSysctls() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, prop := range props {
|
for _, prop := range props {
|
||||||
if err = proc.WriteSystemProperty(prop); err != nil {
|
if err = sysctl.WriteSystemProperty(prop); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/talos-systems/talos/internal/pkg/blockdevice/bootloader/syslinux"
|
|
||||||
"github.com/talos-systems/talos/internal/pkg/constants"
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
||||||
"github.com/talos-systems/talos/internal/pkg/install"
|
"github.com/talos-systems/talos/internal/pkg/installer/bootloader/syslinux"
|
||||||
|
"github.com/talos-systems/talos/internal/pkg/installer/manifest"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kernel"
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kubernetes"
|
"github.com/talos-systems/talos/internal/pkg/kubernetes"
|
||||||
"github.com/talos-systems/talos/pkg/userdata"
|
"github.com/talos-systems/talos/pkg/userdata"
|
||||||
@ -83,31 +83,31 @@ func NewUpgrade(url string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func upgradeBoot(url string) error {
|
func upgradeBoot(url string) error {
|
||||||
bootTarget := install.Target{
|
bootTarget := manifest.Target{
|
||||||
Label: constants.BootPartitionLabel,
|
Label: constants.BootPartitionLabel,
|
||||||
MountPoint: constants.BootMountPoint,
|
MountPoint: constants.BootMountPoint,
|
||||||
Assets: []*install.Asset{},
|
Assets: []*manifest.Asset{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kernel
|
// Kernel
|
||||||
bootTarget.Assets = append(bootTarget.Assets, &install.Asset{
|
bootTarget.Assets = append(bootTarget.Assets, &manifest.Asset{
|
||||||
Source: url + "/" + constants.KernelAsset,
|
Source: url + "/" + constants.KernelAsset,
|
||||||
Destination: filepath.Join("/", "default", constants.KernelAsset),
|
Destination: filepath.Join("/", "default", constants.KernelAsset),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initramfs
|
// Initramfs
|
||||||
bootTarget.Assets = append(bootTarget.Assets, &install.Asset{
|
bootTarget.Assets = append(bootTarget.Assets, &manifest.Asset{
|
||||||
Source: url + "/" + constants.InitramfsAsset,
|
Source: url + "/" + constants.InitramfsAsset,
|
||||||
Destination: filepath.Join("/", "default", constants.InitramfsAsset),
|
Destination: filepath.Join("/", "default", constants.InitramfsAsset),
|
||||||
})
|
})
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if err = bootTarget.Install(); err != nil {
|
if err = bootTarget.Save(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out a method to update kernel args
|
// TODO: Figure out a method to update kernel args
|
||||||
nextCmdline := kernel.NewCmdline(kernel.Cmdline().String())
|
nextCmdline := kernel.NewCmdline(kernel.ProcCmdline().String())
|
||||||
|
|
||||||
// Set the initrd kernel paramaeter.
|
// Set the initrd kernel paramaeter.
|
||||||
initParam := kernel.NewParameter("initrd")
|
initParam := kernel.NewParameter("initrd")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user