diff --git a/TODO.md b/TODO.md
index 3aab8730b..1e2f20092 100644
--- a/TODO.md
+++ b/TODO.md
@@ -24,6 +24,11 @@
- Confirm cloud-init will resize volume on boot and configured correctly.
- Attach additional volumes
- Attach to VPC / Security group (required Open vSwitch implementation)
+- only allow hive to use x% of cpu and memory. dont allow allocating 100% of resources.
+- placement group instances, allow spreading instance group between nodes.
+- capabilities flag
+- more instance types - per cpu gen
+- multi part uploads on frontend
## EC2 describe-instances
diff --git a/hive/daemon/daemon.go b/hive/daemon/daemon.go
index bb9267623..33143f776 100644
--- a/hive/daemon/daemon.go
+++ b/hive/daemon/daemon.go
@@ -1420,7 +1420,7 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) {
var successCount int
for _, instance := range instances {
// Prepare the root volume, cloud-init, EFI drives via NBD (AMI clone to new volume)
- err = d.instanceService.GenerateVolumes(runInstancesInput, instance)
+ volumeInfos, err := d.instanceService.GenerateVolumes(runInstancesInput, instance)
if err != nil {
slog.Error("handleEC2RunInstances GenerateVolumes failed", "instanceId", instance.ID, "err", err)
d.resourceMgr.deallocate(instanceType)
@@ -1428,6 +1428,19 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) {
continue
}
+ // Populate BlockDeviceMappings on the ec2.Instance
+ instance.Instance.BlockDeviceMappings = make([]*ec2.InstanceBlockDeviceMapping, 0, len(volumeInfos))
+ for _, vi := range volumeInfos {
+ mapping := &ec2.InstanceBlockDeviceMapping{}
+ mapping.SetDeviceName(vi.DeviceName)
+ mapping.Ebs = &ec2.EbsInstanceBlockDevice{}
+ mapping.Ebs.SetVolumeId(vi.VolumeId)
+ mapping.Ebs.SetAttachTime(vi.AttachTime)
+ mapping.Ebs.SetDeleteOnTermination(vi.DeleteOnTermination)
+ mapping.Ebs.SetStatus("attached")
+ instance.Instance.BlockDeviceMappings = append(instance.Instance.BlockDeviceMappings, mapping)
+ }
+
// Launch the instance infrastructure (QEMU, QMP, NATS subscriptions)
err = d.LaunchInstance(instance)
if err != nil {
diff --git a/hive/handlers/ec2/instance/service_impl.go b/hive/handlers/ec2/instance/service_impl.go
index 57cdb6c40..e71cffebf 100644
--- a/hive/handlers/ec2/instance/service_impl.go
+++ b/hive/handlers/ec2/instance/service_impl.go
@@ -75,6 +75,15 @@ type CloudInitMetaData struct {
Hostname string
}
+// VolumeInfo holds volume information returned from GenerateVolumes
+// for populating BlockDeviceMappings in the EC2 API response
+type VolumeInfo struct {
+ VolumeId string
+ DeviceName string
+ AttachTime time.Time
+ DeleteOnTermination bool
+}
+
// InstanceServiceImpl handles daemon-side EC2 instance operations
type InstanceServiceImpl struct {
config *config.Config
@@ -132,28 +141,43 @@ func (s *InstanceServiceImpl) RunInstance(input *ec2.RunInstancesInput) (*vm.VM,
return instance, ec2Instance, nil
}
-func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, instance *vm.VM) (err error) {
+func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, instance *vm.VM) ([]VolumeInfo, error) {
var size int = 4 * 1024 * 1024 * 1024 // 4GB default size
- var deviceName string
+ var deviceName string = "/dev/xvda" // Default device name
var volumeType string
var iops int
var imageId string
var snapshotId string
+ var deleteOnTermination bool = true // Default to true
// Handle block device mappings
if len(input.BlockDeviceMappings) > 0 {
- size = int(*input.BlockDeviceMappings[0].Ebs.VolumeSize)
- deviceName = *input.BlockDeviceMappings[0].DeviceName
- volumeType = *input.BlockDeviceMappings[0].Ebs.VolumeType
- iops = int(*input.BlockDeviceMappings[0].Ebs.Iops)
+ bdm := input.BlockDeviceMappings[0]
+ if bdm.DeviceName != nil {
+ deviceName = *bdm.DeviceName
+ }
+ if bdm.Ebs != nil {
+ if bdm.Ebs.VolumeSize != nil {
+ size = int(*bdm.Ebs.VolumeSize)
+ }
+ if bdm.Ebs.VolumeType != nil {
+ volumeType = *bdm.Ebs.VolumeType
+ }
+ if bdm.Ebs.Iops != nil {
+ iops = int(*bdm.Ebs.Iops)
+ }
+ if bdm.Ebs.DeleteOnTermination != nil {
+ deleteOnTermination = *bdm.Ebs.DeleteOnTermination
+ }
+ }
}
// Determine image ID and snapshot ID
if strings.HasPrefix(*input.ImageId, "ami-") {
randomNumber, err := rand.Int(rand.Reader, big.NewInt(100_000_000))
if err != nil {
- return err
+ return nil, err
}
imageId = viperblock.GenerateVolumeID("vol", fmt.Sprintf("%d-%s", randomNumber, *input.ImageId), "predastore", time.Now().Unix())
snapshotId = *input.ImageId
@@ -161,11 +185,14 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
imageId = *input.ImageId
}
+ // Capture attach time for the root volume
+ attachTime := time.Now()
+
volumeConfig := viperblock.VolumeConfig{
VolumeMetadata: viperblock.VolumeMetadata{
VolumeID: imageId,
SizeGiB: utils.SafeIntToUint64(size / 1024 / 1024 / 1024),
- CreatedAt: time.Now(),
+ CreatedAt: attachTime,
DeviceName: deviceName,
VolumeType: volumeType,
IOPS: iops,
@@ -174,26 +201,36 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
}
// Step 1: Create or validate root volume
- err = s.prepareRootVolume(input, imageId, size, volumeConfig, instance)
+ err := s.prepareRootVolume(input, imageId, size, volumeConfig, instance)
if err != nil {
- return err
+ return nil, err
}
// Step 2: Create EFI partition
err = s.prepareEFIVolume(imageId, volumeConfig, instance)
if err != nil {
- return err
+ return nil, err
}
// Step 3: Create cloud-init volume if needed
if input.KeyName != nil && *input.KeyName != "" || (input.UserData != nil && *input.UserData != "") {
err = s.prepareCloudInitVolume(input, imageId, volumeConfig, instance)
if err != nil {
- return err
+ return nil, err
}
}
- return nil
+ // Return volume info for the root volume only (EFI and cloud-init are internal)
+ volumeInfos := []VolumeInfo{
+ {
+ VolumeId: imageId,
+ DeviceName: deviceName,
+ AttachTime: attachTime,
+ DeleteOnTermination: deleteOnTermination,
+ },
+ }
+
+ return volumeInfos, nil
}
diff --git a/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx b/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx
index 971420391..e7a3a27dc 100644
--- a/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx
+++ b/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx
@@ -114,6 +114,42 @@ function InstanceDetail() {
*/}
+ {/* Block Device Mappings */}
+ {instance.BlockDeviceMappings &&
+ instance.BlockDeviceMappings.length > 0 && (
+
+ Block Device Mappings
+
+
+
+
+
+
+
+
+ )}
+
{/* AMI Details */}
{image && }
diff --git a/scripts/reset-dev-env.sh b/scripts/reset-dev-env.sh
index 3b214a68d..660bfdc8c 100755
--- a/scripts/reset-dev-env.sh
+++ b/scripts/reset-dev-env.sh
@@ -16,6 +16,8 @@ rm -rf ~/hive/viperblock/*
rm -rf ~/hive/nats/*
+rm -rf ~/hive/images/*
+
# Enable pprof for development
PPROF_ENABLED=1 PPROF_OUTPUT=/tmp/hive-vm.prof ./scripts/start-dev.sh
diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh
index 797a208a0..b6ea47192 100755
--- a/scripts/start-dev.sh
+++ b/scripts/start-dev.sh
@@ -4,6 +4,10 @@
# This script starts all required services in the correct order using Hive service commands
# Usage: ./scripts/start-dev.sh [data-dir]
# data-dir: Optional data directory path (default: ~/hive)
+#
+# Environment variables:
+# UI=false Skip starting Hive UI (e.g., UI=false ./scripts/start-dev.sh)
+# HIVE_SKIP_BUILD=true Skip building binaries before starting
set -e
@@ -270,18 +274,25 @@ start_service "awsgw" "$AWSGW_CMD"
#check_service "awsgw" "9999"
-# 6️⃣ Start Hive UI
-echo ""
-echo "6️⃣. Starting Hive UI..."
+# 6️⃣ Start Hive UI (skip with UI=false)
+if [ "${UI}" != "false" ]; then
+ echo ""
+ echo "6️⃣. Starting Hive UI..."
-HIVEUI_CMD="./bin/hive service hive-ui start"
+ HIVEUI_CMD="./bin/hive service hive-ui start"
-start_service "hive-ui" "$HIVEUI_CMD"
+ start_service "hive-ui" "$HIVEUI_CMD"
+else
+ echo ""
+ echo "6️⃣. Skipping Hive UI (UI=false)"
+fi
echo ""
echo "🔗 Service endpoints will be:"
-echo " - Hive UI: https://localhost:3000"
+if [ "${UI}" != "false" ]; then
+ echo " - Hive UI: https://localhost:3000"
+fi
echo " - NATS: nats://localhost:4222"
echo " - Predastore: https://localhost:8443"
echo " - AWS Gateway: https://localhost:9999"