dist & cache cleaning + preseeding
This commit is contained in:
+191
-183
@@ -132,15 +132,19 @@ func buildInitrd(out io.Writer, ctx *renderContext) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func getSigner(clusterName string) (signer crypto.Signer, err error) {
|
||||
ca, err := getUsableClusterCA(clusterName, "boot-signer")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ca.ParseKey()
|
||||
}
|
||||
|
||||
func buildBootstrap(out io.Writer, ctx *renderContext) (err error) {
|
||||
arch := tar.NewWriter(out)
|
||||
defer arch.Close()
|
||||
|
||||
ca, err := getUsableClusterCA(ctx.Host.ClusterName, "boot-signer")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signer, err := ca.ParseKey()
|
||||
signer, err := getSigner(ctx.Host.ClusterName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -245,7 +249,7 @@ func buildBootstrap(out io.Writer, ctx *renderContext) (err error) {
|
||||
}
|
||||
|
||||
if allErofs {
|
||||
layerPath, e := layersCombo(ctx, cfg, signer)
|
||||
layerPath, e := layersCombo(ctx.Host, cfg, signer)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
@@ -275,184 +279,10 @@ func buildBootstrap(out io.Writer, ctx *renderContext) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func layersCombo(ctx *renderContext, cfg *config.Config, signer crypto.Signer) (path string, err error) {
|
||||
key := layersComboKey(ctx.Host, cfg)
|
||||
|
||||
func layersCombo(host *localconfig.Host, cfg *config.Config, signer crypto.Signer) (path string, err error) {
|
||||
key := layersComboKey(host, cfg)
|
||||
return opMutex(key, func() (path string, err error) {
|
||||
path = filepath.Join(*dataDir, "cache")
|
||||
if err = os.MkdirAll(path, 0o700); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path = filepath.Join(path, key) + ".fs"
|
||||
|
||||
if _, statErr := os.Stat(path); statErr == nil {
|
||||
return // exists -> already done
|
||||
}
|
||||
|
||||
workdir, err := os.MkdirTemp("/tmp", "layers")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
tmpTar := filepath.Join(workdir, "output.tar")
|
||||
|
||||
layers := slices.Clone(cfg.Layers)
|
||||
slices.Reverse(layers)
|
||||
|
||||
cmdOut := new(bytes.Buffer)
|
||||
|
||||
run := func(prog string, arg ...string) bool {
|
||||
cmdOut.Reset()
|
||||
|
||||
cmd := exec.Command(prog, arg...)
|
||||
cmd.Stdout = cmdOut // os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if e := cmd.Run(); e != nil {
|
||||
err = fmt.Errorf("%s %q failed: %w", cmd.Path, cmd.Args, e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for i, layer := range layers {
|
||||
if layer == "modules" {
|
||||
continue // modules are in the initrd with boot v2
|
||||
}
|
||||
|
||||
layerFile, e := fetchHostLayer(ctx.Host, layer)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
mountPoint := filepath.Join(workdir, layer)
|
||||
os.MkdirAll(mountPoint, 0700)
|
||||
|
||||
if e := exec.Command("erofsfuse", layerFile, mountPoint).Run(); e != nil {
|
||||
err = fmt.Errorf("erofsfuse %s %s failed: %w", layerFile, mountPoint, e)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := exec.Command("umount", mountPoint).Run(); err != nil {
|
||||
log.Printf("umount %s failed: %v", mountPoint, err)
|
||||
}
|
||||
}()
|
||||
|
||||
mode := "--append"
|
||||
if i == 0 {
|
||||
mode = "--create"
|
||||
}
|
||||
|
||||
if !run("tar", mode, "-p", "-f", tmpTar, "-C", mountPoint, ".") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fsOut := filepath.Join(workdir, "output.fs")
|
||||
if !run("mkfs.erofs", "-z", "lzma", "-C131072", "-Efragments,ztailpacking",
|
||||
"-T0", "--all-time", "--ignore-mtime", "--tar=f", fsOut, tmpTar) {
|
||||
return
|
||||
}
|
||||
|
||||
hashOut := filepath.Join(workdir, "output.hash")
|
||||
|
||||
if !run("veritysetup", "format", fsOut, hashOut) {
|
||||
return
|
||||
}
|
||||
|
||||
var rootHash []byte
|
||||
for line := range strings.SplitSeq(cmdOut.String(), "\n") {
|
||||
v, ok := strings.CutPrefix(line, "Root hash:")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
|
||||
b, e := hex.DecodeString(v)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("invalid root hash: %w", e)
|
||||
return
|
||||
}
|
||||
|
||||
rootHash = b
|
||||
break
|
||||
}
|
||||
|
||||
if len(rootHash) == 0 {
|
||||
err = fmt.Errorf("root hash not found in output")
|
||||
return
|
||||
}
|
||||
|
||||
sigBytes, err := signer.Sign(nil, rootHash, crypto.SHA256)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("root hash signature failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
outPath := path + ".tmp"
|
||||
err = func() (err error) {
|
||||
fsRd, e := os.Open(fsOut)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer fsRd.Close()
|
||||
hashRd, e := os.Open(hashOut)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer hashRd.Close()
|
||||
|
||||
fsStat, e := fsRd.Stat()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
hashStat, e := hashRd.Stat()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
out, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
append := func(sz uint64, rd io.Reader) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
szB := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(szB, sz)
|
||||
_, err = out.Write(szB)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, rd)
|
||||
}
|
||||
|
||||
append(uint64(len(sigBytes)), bytes.NewBuffer(sigBytes))
|
||||
append(uint64(len(rootHash)), bytes.NewBuffer(rootHash))
|
||||
append(uint64(fsStat.Size()), fsRd)
|
||||
append(uint64(hashStat.Size()), hashRd)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = out.Close()
|
||||
return
|
||||
}()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("assembly failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Rename(outPath, path)
|
||||
return
|
||||
return buildLayersCombo(host, cfg, signer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -474,6 +304,184 @@ func layersComboKey(host *localconfig.Host, cfg *config.Config) string {
|
||||
return key.String()
|
||||
}
|
||||
|
||||
func buildLayersCombo(host *localconfig.Host, cfg *config.Config, signer crypto.Signer) (path string, err error) {
|
||||
path = filepath.Join(*dataDir, "cache")
|
||||
if err = os.MkdirAll(path, 0o700); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
key := layersComboKey(host, cfg)
|
||||
path = filepath.Join(path, key) + ".fs"
|
||||
|
||||
if _, statErr := os.Stat(path); statErr == nil {
|
||||
return // exists -> already done
|
||||
}
|
||||
|
||||
workdir, err := os.MkdirTemp("/tmp", "layers")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
tmpTar := filepath.Join(workdir, "output.tar")
|
||||
|
||||
layers := slices.Clone(cfg.Layers)
|
||||
slices.Reverse(layers)
|
||||
|
||||
cmdOut := new(bytes.Buffer)
|
||||
|
||||
run := func(prog string, arg ...string) bool {
|
||||
cmdOut.Reset()
|
||||
|
||||
cmd := exec.Command(prog, arg...)
|
||||
cmd.Stdout = cmdOut // os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if e := cmd.Run(); e != nil {
|
||||
err = fmt.Errorf("%s %q failed: %w", cmd.Path, cmd.Args, e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for i, layer := range layers {
|
||||
if layer == "modules" {
|
||||
continue // modules are in the initrd with boot v2
|
||||
}
|
||||
|
||||
layerFile, e := fetchHostLayer(host, layer)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
mountPoint := filepath.Join(workdir, layer)
|
||||
os.MkdirAll(mountPoint, 0700)
|
||||
|
||||
if e := exec.Command("erofsfuse", layerFile, mountPoint).Run(); e != nil {
|
||||
err = fmt.Errorf("erofsfuse %s %s failed: %w", layerFile, mountPoint, e)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := exec.Command("umount", mountPoint).Run(); err != nil {
|
||||
log.Printf("umount %s failed: %v", mountPoint, err)
|
||||
}
|
||||
}()
|
||||
|
||||
mode := "--append"
|
||||
if i == 0 {
|
||||
mode = "--create"
|
||||
}
|
||||
|
||||
if !run("tar", mode, "-p", "-f", tmpTar, "-C", mountPoint, ".") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fsOut := filepath.Join(workdir, "output.fs")
|
||||
if !run("mkfs.erofs", "-z", "lzma", "-C131072", "-Efragments,ztailpacking",
|
||||
"-T0", "--all-time", "--ignore-mtime", "--tar=f", fsOut, tmpTar) {
|
||||
return
|
||||
}
|
||||
|
||||
hashOut := filepath.Join(workdir, "output.hash")
|
||||
|
||||
if !run("veritysetup", "format", fsOut, hashOut) {
|
||||
return
|
||||
}
|
||||
|
||||
var rootHash []byte
|
||||
for line := range strings.SplitSeq(cmdOut.String(), "\n") {
|
||||
v, ok := strings.CutPrefix(line, "Root hash:")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
|
||||
b, e := hex.DecodeString(v)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("invalid root hash: %w", e)
|
||||
return
|
||||
}
|
||||
|
||||
rootHash = b
|
||||
break
|
||||
}
|
||||
|
||||
if len(rootHash) == 0 {
|
||||
err = fmt.Errorf("root hash not found in output")
|
||||
return
|
||||
}
|
||||
|
||||
sigBytes, err := signer.Sign(nil, rootHash, crypto.SHA256)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("root hash signature failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
outPath := path + ".tmp"
|
||||
err = func() (err error) {
|
||||
fsRd, e := os.Open(fsOut)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer fsRd.Close()
|
||||
hashRd, e := os.Open(hashOut)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer hashRd.Close()
|
||||
|
||||
fsStat, e := fsRd.Stat()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
hashStat, e := hashRd.Stat()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
out, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
append := func(sz uint64, rd io.Reader) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
szB := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(szB, sz)
|
||||
_, err = out.Write(szB)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, rd)
|
||||
}
|
||||
|
||||
append(uint64(len(sigBytes)), bytes.NewBuffer(sigBytes))
|
||||
append(uint64(len(rootHash)), bytes.NewBuffer(rootHash))
|
||||
append(uint64(fsStat.Size()), fsRd)
|
||||
append(uint64(hashStat.Size()), hashRd)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = out.Close()
|
||||
return
|
||||
}()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("assembly failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Rename(outPath, path)
|
||||
return
|
||||
}
|
||||
|
||||
func fetchHostLayer(host *localconfig.Host, layer string) (path string, err error) {
|
||||
layerVersion := host.Versions[layer]
|
||||
if layerVersion == "" {
|
||||
|
||||
Reference in New Issue
Block a user