diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index 61280f3..e95536d 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -230,6 +230,8 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst Volumes: volumes, Hypervisor: hvType, GPU: gpuConfig, + SkipKernelHeaders: request.Body.SkipKernelHeaders != nil && *request.Body.SkipKernelHeaders, + SkipGuestAgent: request.Body.SkipGuestAgent != nil && *request.Body.SkipGuestAgent, } inst, err := s.InstanceManager.CreateInstance(ctx, domainReq) diff --git a/lib/instances/configdisk.go b/lib/instances/configdisk.go index cc8b49e..7d3f73c 100644 --- a/lib/instances/configdisk.go +++ b/lib/instances/configdisk.go @@ -97,6 +97,10 @@ func (m *manager) buildGuestConfig(ctx context.Context, inst *Instance, imageInf cfg.InitMode = "systemd" } + // Boot optimizations + cfg.SkipKernelHeaders = inst.SkipKernelHeaders + cfg.SkipGuestAgent = inst.SkipGuestAgent + return cfg } diff --git a/lib/instances/create.go b/lib/instances/create.go index d165f3b..904e589 100644 --- a/lib/instances/create.go +++ b/lib/instances/create.go @@ -335,6 +335,8 @@ func (m *manager) createInstance( Devices: resolvedDeviceIDs, GPUProfile: gpuProfile, GPUMdevUUID: gpuMdevUUID, + SkipKernelHeaders: req.SkipKernelHeaders, + SkipGuestAgent: req.SkipGuestAgent, } // 12. Ensure directories diff --git a/lib/instances/types.go b/lib/instances/types.go index b27f6e2..8c3b468 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -80,6 +80,10 @@ type StoredMetadata struct { // GPU configuration (vGPU mode) GPUProfile string // vGPU profile name (e.g., "L40S-1Q") GPUMdevUUID string // mdev device UUID + + // Boot optimizations + SkipKernelHeaders bool // Skip kernel headers installation (disables DKMS) + SkipGuestAgent bool // Skip guest-agent installation (disables exec/stat API) } // Instance represents a virtual machine instance with derived runtime state @@ -120,6 +124,8 @@ type CreateInstanceRequest struct { Volumes []VolumeAttachment // Volumes to attach at creation time Hypervisor hypervisor.Type // Optional: hypervisor type (defaults to config) GPU *GPUConfig // Optional: vGPU configuration + SkipKernelHeaders bool // Skip kernel headers installation (disables DKMS) + SkipGuestAgent bool // Skip guest-agent installation (disables exec/stat API) } // AttachVolumeRequest is the domain request for attaching a volume (used for API compatibility) diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 5b43965..c2cead2 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -276,6 +276,17 @@ type CreateInstanceRequest struct { // Size Base memory size (human-readable format like "1GB", "512MB", "2G") Size *string `json:"size,omitempty"` + // SkipGuestAgent Skip guest-agent installation during boot. + // When true, the exec and stat APIs will not work for this instance. + // The instance will still run, but remote command execution will be unavailable. + SkipGuestAgent *bool `json:"skip_guest_agent,omitempty"` + + // SkipKernelHeaders Skip kernel headers installation during boot for faster startup. + // When true, DKMS (Dynamic Kernel Module Support) will not work, + // preventing compilation of out-of-tree kernel modules (e.g., NVIDIA vGPU drivers). + // Recommended for workloads that don't need kernel module compilation. + SkipKernelHeaders *bool `json:"skip_kernel_headers,omitempty"` + // Vcpus Number of virtual CPUs Vcpus *int `json:"vcpus,omitempty"` @@ -10226,159 +10237,163 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/lib/system/init/config.go b/lib/system/init/config.go index bf6533b..35c9786 100644 --- a/lib/system/init/config.go +++ b/lib/system/init/config.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/exec" + "time" "github.com/kernel/hypeman/lib/vmconfig" ) @@ -19,6 +20,11 @@ func readConfig(log *Logger) (*vmconfig.Config, error) { return nil, fmt.Errorf("mkdir config mount: %w", err) } + // Wait for config disk to be ready (polls every 10ms, 2s timeout) + if err := waitForDevice("/dev/vdc", 2*time.Second); err != nil { + return nil, fmt.Errorf("wait for config device: %w", err) + } + // Mount config disk (/dev/vdc) read-only cmd := exec.Command("/bin/mount", "-o", "ro", "/dev/vdc", configMount) if output, err := cmd.CombinedOutput(); err != nil { diff --git a/lib/system/init/main.go b/lib/system/init/main.go index d07e05f..43d2241 100644 --- a/lib/system/init/main.go +++ b/lib/system/init/main.go @@ -60,16 +60,20 @@ func main() { dropToShell() } - // Phase 7: Copy guest-agent to target location - if err := copyGuestAgent(log); err != nil { + // Phase 7: Copy guest-agent to target location (skips if already exists or skip_guest_agent=true) + if err := copyGuestAgent(log, cfg.SkipGuestAgent); err != nil { log.Error("agent", "failed to copy guest-agent", err) // Continue anyway - exec will still work, just no remote access } - // Phase 8: Setup kernel headers for DKMS - if err := setupKernelHeaders(log); err != nil { - log.Error("headers", "failed to setup kernel headers", err) - // Continue anyway - only needed for DKMS module building + // Phase 8: Setup kernel headers for DKMS (can be skipped via config) + if cfg.SkipKernelHeaders { + log.Info("headers", "skipping kernel headers setup (skip_kernel_headers=true)") + } else { + if err := setupKernelHeaders(log); err != nil { + log.Error("headers", "failed to setup kernel headers", err) + // Continue anyway - only needed for DKMS module building + } } // Phase 9: Mode-specific execution diff --git a/lib/system/init/mode_exec.go b/lib/system/init/mode_exec.go index 5a9e19a..549bb59 100644 --- a/lib/system/init/mode_exec.go +++ b/lib/system/init/mode_exec.go @@ -36,13 +36,18 @@ func runExecMode(log *Logger, cfg *vmconfig.Config) { os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") os.Setenv("HOME", "/root") - // Start guest-agent in background - log.Info("exec", "starting guest-agent in background") - agentCmd := exec.Command("/opt/hypeman/guest-agent") - agentCmd.Stdout = os.Stdout - agentCmd.Stderr = os.Stderr - if err := agentCmd.Start(); err != nil { - log.Error("exec", "failed to start guest-agent", err) + // Start guest-agent in background (skip if guest-agent was not copied) + var agentCmd *exec.Cmd + if cfg.SkipGuestAgent { + log.Info("exec", "skipping guest-agent (skip_guest_agent=true)") + } else { + log.Info("exec", "starting guest-agent in background") + agentCmd = exec.Command("/opt/hypeman/guest-agent") + agentCmd.Stdout = os.Stdout + agentCmd.Stderr = os.Stderr + if err := agentCmd.Start(); err != nil { + log.Error("exec", "failed to start guest-agent", err) + } } // Build the entrypoint command @@ -94,7 +99,7 @@ func runExecMode(log *Logger, cfg *vmconfig.Config) { // Wait for guest-agent (keeps init alive, prevents kernel panic) // The guest-agent runs forever, so this effectively keeps the VM alive // until it's explicitly terminated - if agentCmd.Process != nil { + if agentCmd != nil && agentCmd.Process != nil { agentCmd.Wait() } diff --git a/lib/system/init/mode_systemd.go b/lib/system/init/mode_systemd.go index cc6e59f..744d7ed 100644 --- a/lib/system/init/mode_systemd.go +++ b/lib/system/init/mode_systemd.go @@ -17,11 +17,15 @@ import ( func runSystemdMode(log *Logger, cfg *vmconfig.Config) { const newroot = "/overlay/newroot" - // Inject hypeman-agent.service - log.Info("systemd", "injecting hypeman-agent.service") - if err := injectAgentService(newroot); err != nil { - log.Error("systemd", "failed to inject service", err) - // Continue anyway - VM will work, just without agent + // Inject hypeman-agent.service (skip if guest-agent was not copied) + if cfg.SkipGuestAgent { + log.Info("systemd", "skipping agent service injection (skip_guest_agent=true)") + } else { + log.Info("systemd", "injecting hypeman-agent.service") + if err := injectAgentService(newroot); err != nil { + log.Error("systemd", "failed to inject service", err) + // Continue anyway - VM will work, just without agent + } } // Change root to the new filesystem using chroot diff --git a/lib/system/init/mount.go b/lib/system/init/mount.go index fa1fc89..50ebc07 100644 --- a/lib/system/init/mount.go +++ b/lib/system/init/mount.go @@ -63,13 +63,36 @@ func mountEssentials(log *Logger) error { return nil } +// waitForDevice polls for a block device to become available. +// It polls every 10ms until the device exists or the timeout is reached. +func waitForDevice(device string, timeout time.Duration) error { + const pollInterval = 10 * time.Millisecond + deadline := time.Now().Add(timeout) + + for { + if _, err := os.Stat(device); err == nil { + return nil + } + if time.Now().After(deadline) { + return fmt.Errorf("timeout waiting for device %s", device) + } + time.Sleep(pollInterval) + } +} + // setupOverlay sets up the overlay filesystem: // - /dev/vda: readonly rootfs (ext4) // - /dev/vdb: writable overlay disk (ext4) // - /overlay/newroot: merged overlay filesystem func setupOverlay(log *Logger) error { - // Wait for block devices to be ready - time.Sleep(500 * time.Millisecond) + // Wait for block devices to be ready (polls every 10ms, 2s timeout) + // This typically completes in 10-50ms instead of always waiting 500ms + if err := waitForDevice("/dev/vda", 2*time.Second); err != nil { + return fmt.Errorf("wait for rootfs device: %w", err) + } + if err := waitForDevice("/dev/vdb", 2*time.Second); err != nil { + return fmt.Errorf("wait for overlay device: %w", err) + } // Create mount points for _, dir := range []string{"/lower", "/overlay"} { @@ -197,12 +220,27 @@ func redirectToConsole(device string) { } // copyGuestAgent copies the guest-agent binary to the target location in the new root. -func copyGuestAgent(log *Logger) error { +// It skips copying if: +// - skipGuestAgent config option is true +// - The destination file already exists (lazy copy optimization) +func copyGuestAgent(log *Logger, skipGuestAgent bool) error { const ( src = "/usr/local/bin/guest-agent" dst = "/overlay/newroot/opt/hypeman/guest-agent" ) + // Check for skip via config + if skipGuestAgent { + log.Info("agent", "skipping guest-agent copy (skip_guest_agent=true)") + return nil + } + + // Check if destination already exists (lazy copy - skip if already present) + if _, err := os.Stat(dst); err == nil { + log.Info("agent", "guest-agent already exists, skipping copy") + return nil + } + // Create target directory if err := os.MkdirAll("/overlay/newroot/opt/hypeman", 0755); err != nil { return fmt.Errorf("mkdir: %w", err) diff --git a/lib/vmconfig/config.go b/lib/vmconfig/config.go index bb7b9a0..df1bf6b 100644 --- a/lib/vmconfig/config.go +++ b/lib/vmconfig/config.go @@ -25,6 +25,10 @@ type Config struct { // Init mode: "exec" (default) or "systemd" InitMode string `json:"init_mode"` + + // Boot optimizations + SkipKernelHeaders bool `json:"skip_kernel_headers,omitempty"` + SkipGuestAgent bool `json:"skip_guest_agent,omitempty"` } // VolumeMount represents a volume mount configuration. diff --git a/openapi.yaml b/openapi.yaml index ee637a4..dce1ca3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -179,6 +179,23 @@ components: enum: [cloud-hypervisor, qemu] description: Hypervisor to use for this instance. Defaults to server configuration. example: cloud-hypervisor + skip_kernel_headers: + type: boolean + description: | + Skip kernel headers installation during boot for faster startup. + When true, DKMS (Dynamic Kernel Module Support) will not work, + preventing compilation of out-of-tree kernel modules (e.g., NVIDIA vGPU drivers). + Recommended for workloads that don't need kernel module compilation. + default: false + example: true + skip_guest_agent: + type: boolean + description: | + Skip guest-agent installation during boot. + When true, the exec and stat APIs will not work for this instance. + The instance will still run, but remote command execution will be unavailable. + default: false + example: false # Future: port_mappings, timeout_seconds Instance: