summaryrefslogtreecommitdiff
path: root/lib/hyperstack
diff options
context:
space:
mode:
Diffstat (limited to 'lib/hyperstack')
-rw-r--r--lib/hyperstack/cli.rb17
-rw-r--r--lib/hyperstack/provisioning.rb3
-rw-r--r--lib/hyperstack/provisioning_orchestrator.rb7
-rw-r--r--lib/hyperstack/vm_lifecycle.rb7
4 files changed, 24 insertions, 10 deletions
diff --git a/lib/hyperstack/cli.rb b/lib/hyperstack/cli.rb
index b5bcaff..b466105 100644
--- a/lib/hyperstack/cli.rb
+++ b/lib/hyperstack/cli.rb
@@ -110,30 +110,27 @@ module HyperstackVM
end
end
- # Returns only the config loaders whose state files exist, i.e. VMs that have
- # been provisioned at least once. Used by watch/status/test when the user
- # wants to see whatever is currently up without specifying --vm explicitly.
- # VMs that have an active (live) state file: state exists, has a public IP,
- # and status is ACTIVE. Used by watch/status/test when falling back from
- # a dead or unprovisioned default VM.
+ # Returns only the config loaders whose state files exist and have a tracked VM.
+ # Used by watch/status/test when the user wants to see whatever is currently
+ # up without specifying --vm explicitly.
def active_config_loaders
pair_config_loaders.filter_map do |loader|
next unless File.exist?(loader.config.state_file)
state = JSON.parse(File.read(loader.config.state_file))
- state['public_ip'] && state['status'] == 'ACTIVE' ? loader : nil
+ state['public_ip'] && state['vm_id'] ? loader : nil
rescue JSON::ParserError, Errno::ENOENT
nil
end
end
- # True when VM1 has a state file that actually points to a running VM.
+ # True when VM1 has a state file with a tracked VM ID and public IP.
def vm1_alive?
path = ConfigLoader.load(vm_config_path('1')).config.state_file
return false unless File.exist?(path)
state = JSON.parse(File.read(path))
- state['public_ip'] && state['status'] == 'ACTIVE'
+ state['public_ip'] && state['vm_id']
rescue JSON::ParserError, Errno::ENOENT
false
end
@@ -263,7 +260,7 @@ module HyperstackVM
def run_status
loaders = default_or_active_loaders
if loaders.empty?
- puts 'No active VMs found.'
+ puts 'No active VMs found. Run `create --vm 1|2|both` first.'
puts
puts '[local-wireguard]'
build_manager(ConfigLoader.load(vm_config_path('1')).config).show_local_wireguard(nil)
diff --git a/lib/hyperstack/provisioning.rb b/lib/hyperstack/provisioning.rb
index 19a3d33..dfe5b8d 100644
--- a/lib/hyperstack/provisioning.rb
+++ b/lib/hyperstack/provisioning.rb
@@ -162,6 +162,9 @@ module HyperstackVM
# When set, --entrypoint bash is used so the command can patch dependencies at runtime
# (e.g. upgrading transformers for Gemma 4, which requires transformers>=5.x).
pre_cmd = (cfg.key?('pre_start_cmd') ? cfg['pre_start_cmd'] : nil) || @config.vllm_pre_start_cmd
+ # vLLM nightly images may be missing pytest which cupy imports during engine init.
+ # Prepend a quiet install so any pre_start_cmd also satisfies this dependency.
+ pre_cmd = "pip install -q pytest 2>/dev/null; #{pre_cmd}" if pre_cmd
port = @config.ollama_port
docker_args = [
diff --git a/lib/hyperstack/provisioning_orchestrator.rb b/lib/hyperstack/provisioning_orchestrator.rb
index 7dbb10e..b9f9e3d 100644
--- a/lib/hyperstack/provisioning_orchestrator.rb
+++ b/lib/hyperstack/provisioning_orchestrator.rb
@@ -29,6 +29,7 @@ module HyperstackVM
state['security_rules'] = Array(vm['security_rules']).map { |r| normalize_rule(r) }
@state_store.save(state)
+ wait_for_ssh(state['public_ip'])
@ssh_runner.ensure_trusted_host(state['public_ip'])
if @config.guest_bootstrap_enabled? && state['bootstrapped_at'].nil?
@@ -78,6 +79,12 @@ module HyperstackVM
state
end
+ def wait_for_ssh(host)
+ with_polling("SSH on #{host}:#{@config.ssh_port} to become reachable", timeout: 300) do
+ @ssh_runner.tcp_open?(host, @config.ssh_port)
+ end
+ end
+
def wait_for_ready(vm_id)
with_polling("VM #{vm_id} to become ready for firewall updates") do
vm = @client.get_vm(vm_id)
diff --git a/lib/hyperstack/vm_lifecycle.rb b/lib/hyperstack/vm_lifecycle.rb
index cc52880..22fbe62 100644
--- a/lib/hyperstack/vm_lifecycle.rb
+++ b/lib/hyperstack/vm_lifecycle.rb
@@ -110,9 +110,16 @@ module HyperstackVM
info "Tracked VM: #{state['vm_id']} #{vm['name']}"
info "Status: #{vm['status']} / #{vm['vm_state']}"
info "Public IP: #{connect_host_for(vm) || 'none'}"
+ unless state['provisioned_at']
+ info "Provisioning: incomplete — run `create` to resume"
+ end
info "Service mode: #{service_summary(vllm: vllm_e, ollama: ollama_e)}"
info "Active model: #{state['vllm_model'] || @config.vllm_model}" if vllm_e
info "Missing firewall rules: #{missing.empty? ? 'none' : missing.size}"
+ state['status'] = vm['status']
+ state['vm_state'] = vm['vm_state']
+ state['public_ip'] = connect_host_for(vm) || state['public_ip']
+ @state_store.save(state)
rescue Error => e
warn_out "Unable to load VM #{state['vm_id']}: #{e.message}"
return state&.dig('public_ip')