Some work on storage
This commit is contained in:
parent
4706e25644
commit
e09ad7d1c9
4 changed files with 175 additions and 2 deletions
1
go.mod
1
go.mod
|
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/alessio/shellescape v1.4.2
|
||||
github.com/bramvdbogaerde/go-scp v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sethvargo/go-password v0.2.0
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -7,6 +7,8 @@ github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yP
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
|
||||
|
|
|
|||
171
storage/storage.go
Normal file
171
storage/storage.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const imageURL = "http://localhost:8000/linsk-base.qcow2"
|
||||
|
||||
var imageHash = [64]byte{96, 134, 26, 122, 43, 140, 212, 78, 44, 123, 103, 209, 21, 36, 81, 152, 9, 177, 47, 114, 225, 117, 64, 198, 50, 151, 71, 100, 1, 92, 106, 24, 224, 254, 157, 125, 188, 118, 84, 200, 47, 11, 215, 252, 100, 173, 64, 202, 132, 110, 15, 240, 234, 223, 56, 125, 94, 94, 179, 39, 193, 215, 41, 109}
|
||||
|
||||
type Storage struct {
|
||||
logger *slog.Logger
|
||||
|
||||
path string
|
||||
}
|
||||
|
||||
func NewStorage(logger *slog.Logger, dataDir string) (*Storage, error) {
|
||||
dataDir = filepath.Clean(dataDir)
|
||||
|
||||
err := os.MkdirAll(dataDir, 0700)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mkdir all data dir")
|
||||
}
|
||||
|
||||
return &Storage{
|
||||
logger: logger,
|
||||
|
||||
path: dataDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Storage) getLocalImagePath() string {
|
||||
return filepath.Join(s.path, hex.EncodeToString(imageHash[:])+".qcow2")
|
||||
}
|
||||
|
||||
func (s *Storage) DownloadImage() error {
|
||||
localImagePath := s.getLocalImagePath()
|
||||
|
||||
var created, success bool
|
||||
|
||||
defer func() {
|
||||
if created && !success {
|
||||
_ = os.Remove(localImagePath)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err := os.Stat(localImagePath)
|
||||
if err == nil {
|
||||
err = os.Remove(localImagePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "remove existing image")
|
||||
}
|
||||
} else {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return errors.Wrap(err, "stat local image path")
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(localImagePath, os.O_CREATE|os.O_WRONLY, 0400)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
|
||||
created = true
|
||||
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
resp, err := http.Get(imageURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "http get image")
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
_, err = copyWithProgress(f, resp.Body, 1024, resp.ContentLength, func(i int, f float64) {
|
||||
s.logger.Info("Downloading image", "url", imageURL, "percent", math.Round(f*100*100)/100, "content-length", humanize.Bytes(uint64(resp.ContentLength)))
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "copy resp to file")
|
||||
}
|
||||
|
||||
err = s.ValidateImageHash()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "validate image hash")
|
||||
}
|
||||
|
||||
success = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) ValidateImageHash() error {
|
||||
localImagePath := s.getLocalImagePath()
|
||||
|
||||
f, err := os.OpenFile(localImagePath, os.O_RDONLY, 0400)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
h := sha512.New()
|
||||
block := make([]byte, 1024)
|
||||
for {
|
||||
read, err := f.Read(block)
|
||||
if read > 0 {
|
||||
h.Write(block[:read])
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "read file block")
|
||||
}
|
||||
}
|
||||
|
||||
sum := h.Sum(nil)
|
||||
|
||||
if !bytes.Equal(sum, imageHash[:]) {
|
||||
return fmt.Errorf("hash mismatch: want '%v', have '%v'", hex.EncodeToString(imageHash[:]), hex.EncodeToString(sum))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyWithProgress(dst io.Writer, src io.Reader, blockSize int, length int64, report func(int, float64)) (int, error) {
|
||||
block := make([]byte, blockSize)
|
||||
|
||||
var progress int
|
||||
|
||||
for {
|
||||
read, err := src.Read(block)
|
||||
if read > 0 {
|
||||
written, err := dst.Write(block[:read])
|
||||
if err != nil {
|
||||
return progress, errors.Wrap(err, "write")
|
||||
}
|
||||
progress += written
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return progress, errors.Wrap(err, "read")
|
||||
}
|
||||
|
||||
if progress%1000000 == 0 {
|
||||
var percent float64
|
||||
if length != 0 {
|
||||
percent = float64(progress) / float64(length)
|
||||
}
|
||||
report(progress, percent)
|
||||
}
|
||||
}
|
||||
|
||||
return progress, nil
|
||||
}
|
||||
3
vm/vm.go
3
vm/vm.go
|
|
@ -144,7 +144,6 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
|
|||
|
||||
if !cfg.ShowDisplay {
|
||||
cmdArgs = append(cmdArgs, "-display", "none")
|
||||
|
||||
} else if runtime.GOARCH == "arm64" {
|
||||
// No video is configured by default in ARM. This will enable it.
|
||||
// TODO: This doesn't really work on arm64. It just shows a blank viewer.
|
||||
|
|
@ -174,7 +173,7 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
|
|||
}
|
||||
|
||||
if len(cfg.PassthroughConfig.Block) != 0 {
|
||||
logger.Warn("Detected raw block device passthrough, please note that it's YOUR responsibility to ensure that no device is mounted in your OS and the VM at the same time")
|
||||
logger.Warn("Detected raw block device passthrough. Please note that it's YOUR responsibility to ensure that no device is mounted in your OS and the VM at the same time. Otherwise, you run serious risks. No further warnings will be issued.")
|
||||
}
|
||||
|
||||
for _, dev := range cfg.PassthroughConfig.Block {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue