cleanup
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
use crate::cloudhv::{vm_create, vm_start, CloudHvError, VmSpec};
|
||||
use crate::image_prep::{image_prepare, Flavor as ImgFlavor, ImagePrepOptions, NetPlanOpts};
|
||||
use sal_process;
|
||||
use crate::cloudhv::net::{NetworkingProfileSpec, DefaultNatOptions, BridgeOptions};
|
||||
|
||||
/// Cloud Hypervisor VM Builder focused on Rhai ergonomics.
|
||||
|
@@ -5,8 +5,6 @@ use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use sal_os;
|
||||
use sal_process;
|
||||
@@ -416,7 +414,7 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
||||
|
||||
if let Some(profile) = profile_effective {
|
||||
match profile {
|
||||
NetworkingProfileSpec::DefaultNat(mut nat) => {
|
||||
NetworkingProfileSpec::DefaultNat(nat) => {
|
||||
// IPv6 handling (auto via Mycelium unless disabled)
|
||||
let mut ipv6_bridge_cidr: Option<String> = None;
|
||||
if nat.ipv6_enable {
|
||||
@@ -579,8 +577,8 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
||||
std::env::var("HERO_VIRT_DHCP_LEASE_FILE")
|
||||
.unwrap_or_else(|_| format!("/var/lib/misc/dnsmasq-hero-{}.leases", bridge_name))
|
||||
});
|
||||
let ipv4 = net::discover_ipv4_from_leases(&lease_path, &mac_lower, 12);
|
||||
let ipv6 = net::discover_ipv6_on_bridge(&bridge_name, &mac_lower);
|
||||
let _ipv4 = net::discover_ipv4_from_leases(&lease_path, &mac_lower, 12);
|
||||
let _ipv6 = net::discover_ipv6_on_bridge(&bridge_name, &mac_lower);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -713,253 +711,14 @@ pub fn vm_list() -> Result<Vec<VmRecord>, CloudHvError> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn tap_name_for_id(id: &str) -> String {
|
||||
// Linux IFNAMSIZ is typically 15; keep "tap-" + 10 hex = 14 chars
|
||||
let mut h = DefaultHasher::new();
|
||||
id.hash(&mut h);
|
||||
let v = h.finish();
|
||||
let hex = format!("{:016x}", v);
|
||||
format!("tap-{}", &hex[..10])
|
||||
}
|
||||
|
||||
fn ensure_tap_for_vm(bridge_name: &str, id: &str) -> Result<String, CloudHvError> {
|
||||
let tap = tap_name_for_id(id);
|
||||
|
||||
let body = format!(
|
||||
"BR={br}
|
||||
TAP={tap}
|
||||
UIDX=$(id -u)
|
||||
GIDX=$(id -g)
|
||||
|
||||
# Create TAP if missing and assign to current user/group
|
||||
ip link show \"$TAP\" >/dev/null 2>&1 || ip tuntap add dev \"$TAP\" mode tap user \"$UIDX\" group \"$GIDX\"
|
||||
|
||||
# Enslave to bridge and bring up (idempotent)
|
||||
ip link set \"$TAP\" master \"$BR\" 2>/dev/null || true
|
||||
ip link set \"$TAP\" up
|
||||
",
|
||||
br = shell_escape(bridge_name),
|
||||
tap = shell_escape(&tap),
|
||||
);
|
||||
let heredoc_tap = format!("bash -e -s <<'EOF'\n{}\nEOF\n", body);
|
||||
|
||||
match sal_process::run(&heredoc_tap).silent(true).execute() {
|
||||
Ok(res) if res.success => Ok(tap),
|
||||
Ok(res) => Err(CloudHvError::CommandFailed(format!(
|
||||
"Failed to ensure TAP '{}': {}",
|
||||
tap, res.stderr
|
||||
))),
|
||||
Err(e) => Err(CloudHvError::CommandFailed(format!(
|
||||
"Failed to ensure TAP '{}': {}",
|
||||
tap, e
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn stable_mac_from_id(id: &str) -> String {
|
||||
let mut h = DefaultHasher::new();
|
||||
id.hash(&mut h);
|
||||
let v = h.finish();
|
||||
let b0 = (((v >> 40) & 0xff) as u8 & 0xfe) | 0x02; // locally administered, unicast
|
||||
let b1 = ((v >> 32) & 0xff) as u8;
|
||||
let b2 = ((v >> 24) & 0xff) as u8;
|
||||
let b3 = ((v >> 16) & 0xff) as u8;
|
||||
let b4 = ((v >> 8) & 0xff) as u8;
|
||||
let b5 = (v & 0xff) as u8;
|
||||
format!("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", b0, b1, b2, b3, b4, b5)
|
||||
}
|
||||
|
||||
/// Discover the mycelium IPv6 address by inspecting the interface itself (no CLI dependency).
|
||||
/// Returns (interface_name, first global IPv6 address found on the interface).
|
||||
fn get_mycelium_ipv6_addr(iface_hint: &str) -> Result<(String, String), CloudHvError> {
|
||||
let iface = std::env::var("HERO_VIRT_MYCELIUM_IF").unwrap_or_else(|_| iface_hint.to_string());
|
||||
|
||||
// Query IPv6 addresses on the interface
|
||||
let cmd = format!("ip -6 addr show dev {}", shell_escape(&iface));
|
||||
let res = sal_process::run(&cmd).silent(true).die(false).execute();
|
||||
let out = match res {
|
||||
Ok(r) if r.success => r.stdout,
|
||||
_ => {
|
||||
return Err(CloudHvError::DependencyMissing(format!(
|
||||
"mycelium interface '{}' not found or no IPv6 configured",
|
||||
iface
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// Extract the first global IPv6 address present on the interface.
|
||||
for line in out.lines() {
|
||||
let lt = line.trim();
|
||||
// Example line: "inet6 578:9fcf:.../7 scope global"
|
||||
if lt.starts_with("inet6 ") && lt.contains("scope global") {
|
||||
let parts: Vec<&str> = lt.split_whitespace().collect();
|
||||
if let Some(addr_cidr) = parts.get(1) {
|
||||
let addr_only = addr_cidr.split('/').next().unwrap_or("").trim();
|
||||
if !addr_only.is_empty() && addr_only.parse::<std::net::Ipv6Addr>().is_ok() {
|
||||
return Ok((iface, addr_only.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(CloudHvError::DependencyMissing(format!(
|
||||
"no global IPv6 found on interface '{}'",
|
||||
iface
|
||||
)))
|
||||
}
|
||||
|
||||
/// Derive a /64 prefix P from the mycelium IPv6 and return (P/64, P::2/64).
|
||||
fn derive_ipv6_prefix_from_mycelium(m: &str) -> Result<(String, String), CloudHvError> {
|
||||
let ip = m
|
||||
.parse::<std::net::Ipv6Addr>()
|
||||
.map_err(|e| CloudHvError::InvalidSpec(format!("invalid mycelium IPv6 address '{}': {}", m, e)))?;
|
||||
let seg = ip.segments(); // [u16; 8]
|
||||
// Take the top /64 from the mycelium address; zero the host half
|
||||
let pfx = std::net::Ipv6Addr::new(seg[0], seg[1], seg[2], seg[3], 0, 0, 0, 0);
|
||||
// Router address for the bridge = P::2
|
||||
let router = std::net::Ipv6Addr::new(seg[0], seg[1], seg[2], seg[3], 0, 0, 0, 2);
|
||||
let pfx_str = format!("{}/64", pfx);
|
||||
let router_cidr = format!("{}/64", router);
|
||||
Ok((pfx_str, router_cidr))
|
||||
}
|
||||
|
||||
fn ensure_host_net_prereq_dnsmasq_nftables(
|
||||
bridge_name: &str,
|
||||
bridge_addr_cidr: &str,
|
||||
subnet_cidr: &str,
|
||||
dhcp_start: &str,
|
||||
dhcp_end: &str,
|
||||
ipv6_bridge_cidr: Option<&str>,
|
||||
mycelium_if: Option<&str>,
|
||||
) -> Result<(), CloudHvError> {
|
||||
// Dependencies
|
||||
for bin in ["ip", "nft", "dnsmasq", "systemctl"] {
|
||||
if sal_process::which(bin).is_none() {
|
||||
return Err(CloudHvError::DependencyMissing(format!(
|
||||
"{} not found on PATH; required for VM networking",
|
||||
bin
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare optional IPv6 value (empty string when disabled)
|
||||
let ipv6_cidr = ipv6_bridge_cidr.unwrap_or("");
|
||||
|
||||
// Build idempotent setup script
|
||||
let body = format!(
|
||||
"set -e
|
||||
BR={br}
|
||||
BR_ADDR={br_addr}
|
||||
SUBNET={subnet}
|
||||
DHCP_START={dstart}
|
||||
DHCP_END={dend}
|
||||
IPV6_CIDR={v6cidr}
|
||||
LEASE_FILE=/var/lib/misc/dnsmasq-hero-$BR.leases
|
||||
|
||||
# Determine default WAN interface
|
||||
WAN_IF=$(ip -o route show default | awk '{{print $5}}' | head -n1)
|
||||
if [ -z \"$WAN_IF\" ]; then
|
||||
echo \"No default WAN interface detected (required for IPv4 NAT)\" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Bridge creation (idempotent)
|
||||
ip link show \"$BR\" >/dev/null 2>&1 || ip link add name \"$BR\" type bridge
|
||||
ip addr replace \"$BR_ADDR\" dev \"$BR\"
|
||||
ip link set \"$BR\" up
|
||||
|
||||
# IPv6 bridge address and forwarding (optional)
|
||||
if [ -n \"$IPV6_CIDR\" ]; then
|
||||
ip -6 addr replace \"$IPV6_CIDR\" dev \"$BR\"
|
||||
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null || true
|
||||
fi
|
||||
|
||||
# IPv4 forwarding
|
||||
sysctl -w net.ipv4.ip_forward=1 >/dev/null
|
||||
|
||||
# nftables NAT (idempotent) for IPv4
|
||||
nft list table ip hero >/dev/null 2>&1 || nft add table ip hero
|
||||
nft list chain ip hero postrouting >/dev/null 2>&1 || nft add chain ip hero postrouting {{ type nat hook postrouting priority 100 \\; }}
|
||||
nft list chain ip hero postrouting | grep -q \"ip saddr $SUBNET oifname \\\"$WAN_IF\\\" masquerade\" \
|
||||
|| nft add rule ip hero postrouting ip saddr $SUBNET oifname \"$WAN_IF\" masquerade
|
||||
|
||||
# dnsmasq DHCPv4 + RA/DHCPv6 config (idempotent)
|
||||
mkdir -p /etc/dnsmasq.d
|
||||
mkdir -p /var/lib/misc
|
||||
CFG=/etc/dnsmasq.d/hero-$BR.conf
|
||||
TMP=/etc/dnsmasq.d/.hero-$BR.conf.new
|
||||
|
||||
RELOAD=0
|
||||
CONF=/etc/dnsmasq.conf
|
||||
# Ensure conf-dir includes /etc/dnsmasq.d (simple fixed-string check to avoid regex escapes in Rust)
|
||||
if ! grep -qF \"conf-dir=/etc/dnsmasq.d\" \"$CONF\"; then
|
||||
printf '%s\n' 'conf-dir=/etc/dnsmasq.d,*.conf' >> \"$CONF\"
|
||||
RELOAD=1
|
||||
fi
|
||||
|
||||
|
||||
# Ensure lease file exists and is writable by dnsmasq user
|
||||
touch \"$LEASE_FILE\" || true
|
||||
chown dnsmasq:dnsmasq \"$LEASE_FILE\" 2>/dev/null || true
|
||||
|
||||
# Always include IPv4 section
|
||||
printf '%s\n' \
|
||||
\"interface=$BR\" \
|
||||
\"bind-interfaces\" \
|
||||
\"dhcp-authoritative\" \
|
||||
\"dhcp-range=$DHCP_START,$DHCP_END,12h\" \
|
||||
\"dhcp-option=option:dns-server,1.1.1.1,8.8.8.8\" \
|
||||
\"dhcp-leasefile=$LEASE_FILE\" >\"$TMP\"
|
||||
|
||||
# Optionally append IPv6 RA/DHCPv6
|
||||
if [ -n \"$IPV6_CIDR\" ]; then
|
||||
printf '%s\n' \
|
||||
\"enable-ra\" \
|
||||
\"dhcp-range=::,constructor:BR_PLACEHOLDER,ra-names,64,12h\" \
|
||||
\"dhcp-option=option6:dns-server,[2001:4860:4860::8888],[2606:4700:4700::1111]\" >>\"$TMP\"
|
||||
sed -i \"s/BR_PLACEHOLDER/$BR/g\" \"$TMP\"
|
||||
fi
|
||||
|
||||
if [ ! -f \"$CFG\" ] || ! cmp -s \"$CFG\" \"$TMP\"; then
|
||||
mv \"$TMP\" \"$CFG\"
|
||||
if systemctl is-active --quiet dnsmasq; then
|
||||
systemctl reload dnsmasq || systemctl restart dnsmasq || true
|
||||
else
|
||||
systemctl enable --now dnsmasq || true
|
||||
fi
|
||||
else
|
||||
rm -f \"$TMP\"
|
||||
systemctl enable --now dnsmasq || true
|
||||
fi
|
||||
|
||||
# Reload if main conf was updated to include conf-dir
|
||||
if [ \"$RELOAD\" = \"1\" ]; then
|
||||
systemctl reload dnsmasq || systemctl restart dnsmasq || true
|
||||
fi
|
||||
",
|
||||
br = shell_escape(bridge_name),
|
||||
br_addr = shell_escape(bridge_addr_cidr),
|
||||
subnet = shell_escape(subnet_cidr),
|
||||
dstart = shell_escape(dhcp_start),
|
||||
dend = shell_escape(dhcp_end),
|
||||
v6cidr = shell_escape(ipv6_cidr),
|
||||
);
|
||||
|
||||
// Use a unique heredoc delimiter to avoid clashing with inner <<EOF blocks
|
||||
let heredoc_net = format!("bash -e -s <<'HERONET'\n{}\nHERONET\n", body);
|
||||
|
||||
match sal_process::run(&heredoc_net).silent(true).execute() {
|
||||
Ok(res) if res.success => Ok(()),
|
||||
Ok(res) => Err(CloudHvError::CommandFailed(format!(
|
||||
"Host networking setup failed: {}",
|
||||
res.stderr
|
||||
))),
|
||||
Err(e) => Err(CloudHvError::CommandFailed(format!(
|
||||
"Host networking setup failed: {}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a shell-safe command string from vector of tokens
|
||||
fn shell_join(parts: &Vec<String>) -> String {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use sal_process;
|
||||
|
||||
use crate::cloudhv::CloudHvError;
|
||||
|
@@ -42,7 +42,7 @@ fn bin_missing(name: &str) -> bool {
|
||||
/// Returns a structured report that Rhai can consume easily.
|
||||
pub fn host_check_deps() -> Result<HostCheckReport, HostCheckError> {
|
||||
let mut critical: Vec<String> = Vec::new();
|
||||
let mut optional: Vec<String> = Vec::new();
|
||||
let optional: Vec<String> = Vec::new();
|
||||
let mut notes: Vec<String> = Vec::new();
|
||||
|
||||
// Must run as root
|
||||
|
@@ -1,5 +1,4 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use sal_os;
|
||||
@@ -443,7 +442,7 @@ if [ -f "$MNT_ROOT/etc/ssh/sshd_config" ]; then
|
||||
sed -i -E 's/^[[:space:]]*AuthenticationMethods[[:space:]].*$/# hero: removed AuthenticationMethods/' "$MNT_ROOT/etc/ssh/sshd_config" 2>/dev/null || true
|
||||
fi
|
||||
if [ -d "$MNT_ROOT/etc/ssh/sshd_config.d" ]; then
|
||||
find "$MNT_ROOT/etc/ssh/sshd_config.d" -type f -name '*.conf' -exec sed -i -E 's/^[[:space:]]*AuthenticationMethods[[:space:]].*$/# hero: removed AuthenticationMethods/' {} + 2>/dev/null \; || true
|
||||
find "$MNT_ROOT/etc/ssh/sshd_config.d" -type f -name '*.conf' -exec sed -i -E 's/^[[:space:]]*AuthenticationMethods[[:space:]].*$/# hero: removed AuthenticationMethods/' {{}} + 2>/dev/null \; || true
|
||||
fi
|
||||
|
||||
# Set password for default user 'ubuntu'
|
||||
@@ -653,7 +652,7 @@ if [ -f /etc/ssh/sshd_config ]; then
|
||||
sed -i -E 's/^[[:space:]]*AuthenticationMethods[[:space:]].*$/# hero: removed AuthenticationMethods/' /etc/ssh/sshd_config 2>/dev/null || true
|
||||
fi
|
||||
if [ -d /etc/ssh/sshd_config.d ]; then
|
||||
find /etc/ssh/sshd_config.d -type f -name '*.conf' -exec sed -i -E 's/^[[:space:]]*AuthenticationMethods[[:space:]].*$/# hero: removed AuthenticationMethods/' {} + 2>/dev/null \; || true
|
||||
find /etc/ssh/sshd_config.d -type f -name '*.conf' -exec sed -i -E 's/^[[:space:]]*AuthenticationMethods[[:space:]].*$/# hero: removed AuthenticationMethods/' {{}} + 2>/dev/null \; || true
|
||||
fi
|
||||
|
||||
# Ensure Include covers drop-ins
|
||||
|
@@ -171,6 +171,13 @@ pub fn cloudhv_vm_info(id: &str) -> Result<Map, Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
pub fn cloudhv_discover_ipv4_from_leases(lease_path: &str, mac_lower: &str, timeout_secs: i64) -> Dynamic {
|
||||
// Check verbosity from environment variable, default to verbose
|
||||
let verbose = std::env::var("VIRT_VERBOSE").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||
|
||||
if verbose {
|
||||
println!("🔍 Discovering VM network addresses...");
|
||||
}
|
||||
|
||||
match crate::cloudhv::net::discover_ipv4_from_leases(lease_path, mac_lower, timeout_secs as u64) {
|
||||
Some(ip) => ip.into(),
|
||||
None => Dynamic::UNIT,
|
||||
@@ -184,6 +191,45 @@ pub fn cloudhv_discover_ipv6_on_bridge(bridge_name: &str, mac_lower: &str) -> Dy
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cloudhv_display_network_info(vm_id: &str, ipv4: Dynamic, ipv6: Dynamic) {
|
||||
// Check verbosity from environment variable, default to verbose
|
||||
let verbose = std::env::var("VIRT_VERBOSE").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||
|
||||
if !verbose {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("✅ VM {} is ready!", vm_id);
|
||||
println!("");
|
||||
println!("🌐 Network Information:");
|
||||
|
||||
if ipv4.is_string() && !ipv4.clone().cast::<String>().is_empty() {
|
||||
println!(" IPv4: {}", ipv4.clone().cast::<String>());
|
||||
} else {
|
||||
println!(" IPv4: Not assigned yet (VM may still be configuring)");
|
||||
}
|
||||
|
||||
if ipv6.is_string() && !ipv6.clone().cast::<String>().is_empty() {
|
||||
println!(" IPv6: {}", ipv6.clone().cast::<String>());
|
||||
} else {
|
||||
println!(" IPv6: Not available");
|
||||
}
|
||||
|
||||
println!("");
|
||||
println!("💡 VM is running in the background. To connect:");
|
||||
|
||||
let ssh_addr = if ipv4.is_string() && !ipv4.clone().cast::<String>().is_empty() {
|
||||
ipv4.cast::<String>()
|
||||
} else {
|
||||
"<IPv4>".to_string()
|
||||
};
|
||||
println!(" SSH: ssh ubuntu@{}", ssh_addr);
|
||||
println!("");
|
||||
println!("🛑 To stop the VM later:");
|
||||
println!(" cloudhv_vm_stop(\"{}\", false);", vm_id);
|
||||
println!(" cloudhv_vm_delete(\"{}\", true);", vm_id);
|
||||
}
|
||||
|
||||
// Module registration
|
||||
|
||||
pub fn register_cloudhv_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
@@ -195,5 +241,6 @@ pub fn register_cloudhv_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
|
||||
engine.register_fn("cloudhv_vm_info", cloudhv_vm_info);
|
||||
engine.register_fn("cloudhv_discover_ipv4_from_leases", cloudhv_discover_ipv4_from_leases);
|
||||
engine.register_fn("cloudhv_discover_ipv6_on_bridge", cloudhv_discover_ipv6_on_bridge);
|
||||
engine.register_fn("cloudhv_display_network_info", cloudhv_display_network_info);
|
||||
Ok(())
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
use crate::cloudhv::builder::CloudHvBuilder;
|
||||
use crate::hostcheck::host_check_deps;
|
||||
use crate::image_prep::{image_prepare, Flavor as ImgFlavor, ImagePrepOptions, NetPlanOpts};
|
||||
use rhai::{Engine, EvalAltResult, Map, Array};
|
||||
use rhai::{Engine, EvalAltResult, Array};
|
||||
|
||||
// Improved functional-style builder with better method names for fluent feel
|
||||
fn cloudhv_builder(id: &str) -> CloudHvBuilder {
|
||||
@@ -85,14 +85,37 @@ fn network_custom(b: CloudHvBuilder, args: Array) -> CloudHvBuilder {
|
||||
}
|
||||
|
||||
fn launch(mut b: CloudHvBuilder) -> Result<String, Box<EvalAltResult>> {
|
||||
// Check verbosity from environment variable, default to verbose
|
||||
let verbose = std::env::var("VIRT_VERBOSE").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||
|
||||
if verbose {
|
||||
println!("Preparing Ubuntu image and configuring VM...");
|
||||
}
|
||||
|
||||
b.launch().map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
format!("cloudhv builder launch failed: {}", e).into(),
|
||||
rhai::Position::NONE,
|
||||
))
|
||||
}).map(|vm_id| {
|
||||
if verbose {
|
||||
println!("✅ VM launched successfully");
|
||||
}
|
||||
vm_id
|
||||
})
|
||||
}
|
||||
|
||||
fn wait_for_vm_boot(seconds: i64) {
|
||||
// Check verbosity from environment variable, default to verbose
|
||||
let verbose = std::env::var("VIRT_VERBOSE").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||
|
||||
if verbose {
|
||||
println!("⏳ Waiting {} seconds for VM to boot and configure network...", seconds);
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(seconds as u64));
|
||||
}
|
||||
|
||||
// Noob-friendly one-shot wrapper
|
||||
fn vm_easy_launch(flavor: &str, id: &str, memory_mb: i64, vcpus: i64) -> Result<String, Box<EvalAltResult>> {
|
||||
// Preflight
|
||||
@@ -172,6 +195,7 @@ pub fn register_cloudhv_builder_module(engine: &mut Engine) -> Result<(), Box<Ev
|
||||
|
||||
// Action
|
||||
engine.register_fn("launch", launch);
|
||||
engine.register_fn("wait_for_vm_boot", wait_for_vm_boot);
|
||||
|
||||
// One-shot wrapper
|
||||
engine.register_fn("vm_easy_launch", vm_easy_launch);
|
||||
|
@@ -27,9 +27,42 @@ fn report_to_map(r: &HostCheckReport) -> Map {
|
||||
}
|
||||
|
||||
fn host_check() -> Result<Map, Box<EvalAltResult>> {
|
||||
// Check verbosity from environment variable, default to verbose
|
||||
let verbose = std::env::var("VIRT_VERBOSE").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||
|
||||
if verbose {
|
||||
println!("Checking system requirements...");
|
||||
}
|
||||
|
||||
match host_check_deps() {
|
||||
Ok(rep) => Ok(report_to_map(&rep)),
|
||||
Ok(rep) => {
|
||||
if verbose {
|
||||
if rep.ok {
|
||||
println!("✅ System requirements met");
|
||||
} else {
|
||||
println!("❌ System check failed - missing dependencies:");
|
||||
if !rep.critical.is_empty() {
|
||||
println!("Critical:");
|
||||
for dep in &rep.critical {
|
||||
println!(" - {}", dep);
|
||||
}
|
||||
}
|
||||
if !rep.optional.is_empty() {
|
||||
println!("Optional:");
|
||||
for dep in &rep.optional {
|
||||
println!(" - {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(report_to_map(&rep))
|
||||
},
|
||||
Err(e) => {
|
||||
if verbose {
|
||||
println!("❌ System check failed - missing dependencies:");
|
||||
println!("Critical:");
|
||||
println!(" - host_check failed: {}", e);
|
||||
}
|
||||
let mut m = Map::new();
|
||||
m.insert("ok".into(), Dynamic::FALSE);
|
||||
let mut crit = Array::new();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
use crate::image_prep::{image_prepare, Flavor, ImagePrepOptions, NetPlanOpts};
|
||||
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
|
||||
use rhai::{Engine, EvalAltResult, Map};
|
||||
|
||||
fn parse_flavor(s: &str) -> Result<Flavor, Box<EvalAltResult>> {
|
||||
match s {
|
||||
|
@@ -4,28 +4,12 @@
|
||||
let vm_id = "vm-clean-test";
|
||||
|
||||
// Phase 1: Host check
|
||||
print("Checking system requirements...");
|
||||
let hc = host_check();
|
||||
if !(hc.ok == true) {
|
||||
print("❌ System check failed - missing dependencies:");
|
||||
if hc.critical != () && hc.critical.len() > 0 {
|
||||
print("Critical:");
|
||||
for dep in hc.critical {
|
||||
print(" - " + dep);
|
||||
}
|
||||
}
|
||||
if hc.optional != () && hc.optional.len() > 0 {
|
||||
print("Optional:");
|
||||
for dep in hc.optional {
|
||||
print(" - " + dep);
|
||||
}
|
||||
}
|
||||
throw "Host check failed: missing dependencies";
|
||||
}
|
||||
print("✅ System requirements met");
|
||||
|
||||
// Phase 2: Create VM using fluent builder pattern
|
||||
print("Preparing Ubuntu image and configuring VM...");
|
||||
let vm_id_actual = "";
|
||||
try {
|
||||
vm_id_actual = cloudhv_builder(vm_id)
|
||||
@@ -39,37 +23,15 @@ try {
|
||||
}
|
||||
|
||||
// Phase 3: Wait for VM to boot and get network configuration
|
||||
print("✅ VM launched successfully");
|
||||
print("⏳ Waiting 10 seconds for VM to boot and configure network...");
|
||||
sleep(10);
|
||||
wait_for_vm_boot(10);
|
||||
|
||||
// Phase 4: Discover VM IP addresses
|
||||
print("🔍 Discovering VM network addresses...");
|
||||
let mac_addr = "a2:26:1e:ac:96:3a"; // This should be derived from vm_id_actual
|
||||
let ipv4 = cloudhv_discover_ipv4_from_leases("/var/lib/misc/dnsmasq-hero-br-hero.leases", mac_addr, 30);
|
||||
let ipv6 = cloudhv_discover_ipv6_on_bridge("br-hero", mac_addr);
|
||||
|
||||
// Phase 5: Display connection info
|
||||
print("✅ VM " + vm_id_actual + " is ready!");
|
||||
print("");
|
||||
print("🌐 Network Information:");
|
||||
if ipv4 != () && ipv4 != "" {
|
||||
print(" IPv4: " + ipv4);
|
||||
} else {
|
||||
print(" IPv4: Not assigned yet (VM may still be configuring)");
|
||||
}
|
||||
if ipv6 != () && ipv6 != "" {
|
||||
print(" IPv6: " + ipv6);
|
||||
} else {
|
||||
print(" IPv6: Not available");
|
||||
}
|
||||
print("");
|
||||
print("💡 VM is running in the background. To connect:");
|
||||
print(" SSH: ssh ubuntu@" + (if ipv4 != () && ipv4 != "" { ipv4 } else { "<IPv4>" }));
|
||||
print("");
|
||||
print("🛑 To stop the VM later:");
|
||||
print(" cloudhv_vm_stop(\"" + vm_id_actual + "\", false);");
|
||||
print(" cloudhv_vm_delete(\"" + vm_id_actual + "\", true);");
|
||||
cloudhv_display_network_info(vm_id_actual, ipv4, ipv6);
|
||||
|
||||
/*
|
||||
try {
|
||||
|
Reference in New Issue
Block a user