feat(boot.tar): initial commit

This commit is contained in:
Mikaël Cluseau 2018-07-03 18:35:52 +11:00
parent f91ae88876
commit d0148fd26f
5 changed files with 237 additions and 13 deletions

176
boot-tar.go Normal file
View File

@ -0,0 +1,176 @@
package main
import (
"archive/tar"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
)
func rmTempFile(f *os.File) {
f.Close()
if err := os.Remove(f.Name()); err != nil {
log.Print("failed to remove ", f.Name(), ": ", err)
}
}
func buildBootTar(out io.Writer, ctx *renderContext) (err error) {
grubCfg, err := ioutil.TempFile(os.TempDir(), "grub.cfg-")
if err != nil {
return
}
defer rmTempFile(grubCfg)
_, err = grubCfg.WriteString(`
search --no-floppy --set=root --part-label boot
insmod all_video
set timeout=3
set bootdev=PARTNAME=boot
menuentry "Direktil" {
linux /current/vmlinuz direktil.boot=$bootdev
initrd /current/initrd
}
`)
if err != nil {
return
}
grubCfg.Close()
// FIXME including in grub memdisk for now...
kernelPath, err := ctx.distFetch("kernels", ctx.Group.Kernel)
initrdPath, err := ctx.distFetch("initrd", ctx.Group.Initrd)
grubMk := func(format string) ([]byte, error) {
grubOut, err := ioutil.TempFile(os.TempDir(), "grub.img-")
if err != nil {
return nil, err
}
defer rmTempFile(grubOut)
if err := grubOut.Close(); err != nil {
return nil, err
}
cmd := exec.Command("grub-mkstandalone",
"--format="+format,
"--output="+grubOut.Name(),
"boot/grub/grub.cfg="+grubCfg.Name(),
"current/vmlinuz="+kernelPath,
"current/initrd="+initrdPath,
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, err
}
return ioutil.ReadFile(grubOut.Name())
}
arch := tar.NewWriter(out)
defer arch.Close()
archAdd := func(path string, ba []byte) (err error) {
err = arch.WriteHeader(&tar.Header{Name: path, Size: int64(len(ba))})
if err != nil {
return
}
_, err = arch.Write(ba)
return
}
ba, err := grubMk("x86_64-efi")
if err != nil {
return err
}
err = archAdd("EFI/boot/bootx64.efi", ba)
if err != nil {
return
}
if false {
// TODO
ba, err = grubMk("i386-pc")
if err != nil {
return err
}
arch.WriteHeader(&tar.Header{
Name: "grub.img",
Size: int64(len(ba)),
})
arch.Write(ba)
}
// config
cfgBytes, cfg, err := ctx.Config()
if err != nil {
return err
}
archAdd("config.yaml", cfgBytes)
// kernel and initrd
type distCopy struct {
Src []string
Dst string
}
copies := []distCopy{
// XXX {Src: []string{"kernels", ctx.Group.Kernel}, Dst: "current/vmlinuz"},
// XXX {Src: []string{"initrd", ctx.Group.Initrd}, Dst: "current/initrd"},
}
// layers
for _, layer := range cfg.Layers {
layerVersion := ctx.Group.Versions[layer]
if layerVersion == "" {
return fmt.Errorf("layer %q not mapped to a version", layer)
}
copies = append(copies,
distCopy{
Src: []string{"layers", layer, layerVersion},
Dst: filepath.Join("current", "layers", layer+".fs"),
})
}
for _, copy := range copies {
outPath, err := ctx.distFetch(copy.Src...)
if err != nil {
return err
}
f, err := os.Open(outPath)
if err != nil {
return err
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return err
}
if err = arch.WriteHeader(&tar.Header{
Name: copy.Dst,
Size: stat.Size(),
}); err != nil {
return err
}
_, err = io.Copy(arch, f)
if err != nil {
return err
}
}
return nil
}

View File

@ -31,7 +31,10 @@ func cleanCAS() error {
activeTags := make([]string, len(cfg.Hosts)) activeTags := make([]string, len(cfg.Hosts))
for i, host := range cfg.Hosts { for i, host := range cfg.Hosts {
ctx := newRenderContext(host, cfg) ctx, err := newRenderContext(host, cfg)
if err != nil {
return err
}
tag, err := ctx.Tag() tag, err := ctx.Tag()
if err != nil { if err != nil {

19
http.go
View File

@ -93,12 +93,7 @@ func serveHosts(w http.ResponseWriter, r *http.Request) {
return return
} }
hostNames := make([]string, len(cfg.Hosts)) renderJSON(w, cfg.Hosts)
for i, host := range cfg.Hosts {
hostNames[i] = host.Name
}
renderJSON(w, hostNames)
} }
func serveHost(w http.ResponseWriter, r *http.Request) { func serveHost(w http.ResponseWriter, r *http.Request) {
@ -137,7 +132,12 @@ func serveHost(w http.ResponseWriter, r *http.Request) {
} }
func renderHost(w http.ResponseWriter, r *http.Request, what string, host *clustersconfig.Host, cfg *clustersconfig.Config) { func renderHost(w http.ResponseWriter, r *http.Request, what string, host *clustersconfig.Host, cfg *clustersconfig.Config) {
ctx := newRenderContext(host, cfg) ctx, err := newRenderContext(host, cfg)
if err != nil {
log.Printf("host %s: %s: failed to render: %v", what, host.Name, err)
http.Error(w, "", http.StatusServiceUnavailable)
return
}
switch what { switch what {
case "ipxe": case "ipxe":
@ -148,8 +148,6 @@ func renderHost(w http.ResponseWriter, r *http.Request, what string, host *clust
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
} }
var err error
switch what { switch what {
case "ipxe": case "ipxe":
err = renderIPXE(w, ctx) err = renderIPXE(w, ctx)
@ -163,6 +161,9 @@ func renderHost(w http.ResponseWriter, r *http.Request, what string, host *clust
case "boot.iso": case "boot.iso":
err = renderCtx(w, r, ctx, "boot.iso", buildBootISO) err = renderCtx(w, r, ctx, "boot.iso", buildBootISO)
case "boot.tar":
err = renderCtx(w, r, ctx, "boot.tar", buildBootTar)
case "config": case "config":
err = renderConfig(w, r, ctx) err = renderConfig(w, r, ctx)

37
install-on-metal.sh Executable file
View File

@ -0,0 +1,37 @@
#! /bin/sh
if [ $# -ne 2 ]; then
echo "USAGE: $0 <device> <tar url>"
fi
dev=$1
tar_url=$2
: ${MP:=/mnt}
set -ex
[[ $dev =~ nvme ]] &&
devp=${dev}p ||
devp=${dev}
vgdisplay storage || {
sgdisk --clear $dev
sgdisk \
--new=0:4096:+2G --typecode=0:EF00 -c 0:boot \
--new=0:0:+2M --typecode=0:EF02 -c 0:BIOS-BOOT \
--new=0:0:0 --typecode=0:FFFF -c 0:data \
--hybrid=1:2 \
--print $dev
mkfs.vfat -n DKLBOOT ${devp}1
pvcreate ${devp}3
vgcreate storage ${devp}3
}
while umount $MP; do true; done
mount -t vfat ${devp}1 $MP
curl $tar_url |tar xv -C $MP
umount $MP

View File

@ -30,9 +30,16 @@ type renderContext struct {
clusterConfig *clustersconfig.Config clusterConfig *clustersconfig.Config
} }
func newRenderContext(host *clustersconfig.Host, cfg *clustersconfig.Config) *renderContext { func newRenderContext(host *clustersconfig.Host, cfg *clustersconfig.Config) (*renderContext, error) {
group := cfg.Group(host.Group)
cluster := cfg.Cluster(host.Cluster) cluster := cfg.Cluster(host.Cluster)
if cluster == nil {
return nil, fmt.Errorf("no cluster named %q", host.Cluster)
}
group := cfg.Group(host.Group)
if group == nil {
return nil, fmt.Errorf("no group named %q", host.Group)
}
vars := make(map[string]interface{}) vars := make(map[string]interface{})
@ -55,7 +62,7 @@ func newRenderContext(host *clustersconfig.Host, cfg *clustersconfig.Config) *re
StaticPodsTemplate: cfg.StaticPodsTemplate(group.StaticPods), StaticPodsTemplate: cfg.StaticPodsTemplate(group.StaticPods),
clusterConfig: cfg, clusterConfig: cfg,
} }, nil
} }
func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) { func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {