package daemon import ( "context" "fmt" "os" "path/filepath" "strings" "banger/internal/api" "banger/internal/model" "banger/internal/vmdns" ) func (d *Daemon) CreateVM(ctx context.Context, params api.VMCreateParams) (vm model.VMRecord, err error) { d.mu.Lock() defer d.mu.Unlock() op := d.beginOperation("vm.create") defer func() { if err != nil { op.fail(err) return } op.done(vmLogAttrs(vm)...) }() if err := validateOptionalPositiveSetting("vcpu", params.VCPUCount); err != nil { return model.VMRecord{}, err } if err := validateOptionalPositiveSetting("memory", params.MemoryMiB); err != nil { return model.VMRecord{}, err } imageName := params.ImageName if imageName == "" { imageName = d.config.DefaultImageName } vmCreateStage(ctx, "resolve_image", "resolving image") image, err := d.FindImage(ctx, imageName) if err != nil { return model.VMRecord{}, err } vmCreateStage(ctx, "resolve_image", "using image "+image.Name) op.stage("image_resolved", imageLogAttrs(image)...) name := strings.TrimSpace(params.Name) if name == "" { name, err = d.generateName(ctx) if err != nil { return model.VMRecord{}, err } } if _, err := d.FindVM(ctx, name); err == nil { return model.VMRecord{}, fmt.Errorf("vm name already exists: %s", name) } id, err := model.NewID() if err != nil { return model.VMRecord{}, err } unlockVM := d.lockVMID(id) defer unlockVM() guestIP, err := d.store.NextGuestIP(ctx, bridgePrefix(d.config.BridgeIP)) if err != nil { return model.VMRecord{}, err } vmDir := filepath.Join(d.layout.VMsDir, id) if err := os.MkdirAll(vmDir, 0o755); err != nil { return model.VMRecord{}, err } vsockCID, err := defaultVSockCID(guestIP) if err != nil { return model.VMRecord{}, err } systemOverlaySize := int64(model.DefaultSystemOverlaySize) if params.SystemOverlaySize != "" { systemOverlaySize, err = model.ParseSize(params.SystemOverlaySize) if err != nil { return model.VMRecord{}, err } } workDiskSize := int64(model.DefaultWorkDiskSize) if params.WorkDiskSize != "" { workDiskSize, err = model.ParseSize(params.WorkDiskSize) if err != nil { return model.VMRecord{}, err } } now := model.Now() spec := model.VMSpec{ VCPUCount: optionalIntOrDefault(params.VCPUCount, model.DefaultVCPUCount), MemoryMiB: optionalIntOrDefault(params.MemoryMiB, model.DefaultMemoryMiB), SystemOverlaySizeByte: systemOverlaySize, WorkDiskSizeBytes: workDiskSize, NATEnabled: params.NATEnabled, } vm = model.VMRecord{ ID: id, Name: name, ImageID: image.ID, State: model.VMStateCreated, CreatedAt: now, UpdatedAt: now, LastTouchedAt: now, Spec: spec, Runtime: model.VMRuntime{ State: model.VMStateCreated, GuestIP: guestIP, DNSName: vmdns.RecordName(name), VMDir: vmDir, VSockPath: defaultVSockPath(d.layout.RuntimeDir, id), VSockCID: vsockCID, SystemOverlay: filepath.Join(vmDir, "system.cow"), WorkDiskPath: filepath.Join(vmDir, "root.ext4"), LogPath: filepath.Join(vmDir, "firecracker.log"), MetricsPath: filepath.Join(vmDir, "metrics.json"), }, } vmCreateBindVM(ctx, vm) vmCreateStage(ctx, "reserve_vm", fmt.Sprintf("allocated %s (%s)", vm.Name, vm.Runtime.GuestIP)) if err := d.store.UpsertVM(ctx, vm); err != nil { return model.VMRecord{}, err } op.stage("persisted", vmLogAttrs(vm)...) if params.NoStart { vm.State = model.VMStateStopped vm.Runtime.State = model.VMStateStopped if err := d.store.UpsertVM(ctx, vm); err != nil { return model.VMRecord{}, err } return vm, nil } return d.startVMLocked(ctx, vm, image) }