From aabb8e5fe7b394c3ebe2d50b95488b300748817c Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 11:40:56 +0100 Subject: [PATCH 01/12] improved spinning up swamrs --- src/bin/super/util.rs | 699 ++++++++++++++++++++++++++++++------------ 1 file changed, 511 insertions(+), 188 deletions(-) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index 967ee224b..7859b2fba 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -394,53 +394,44 @@ async fn create_ec2_instance( let region_provider = RegionProviderChain::first_try(Some(Region::new(region))); let stakwork_token = getenv("STAKWORK_ADD_NODE_TOKEN")?; - let lnd_macaroon = getenv("EXTERNAL_LND_MACAROON")?; - let lnd_address = getenv("EXTERNAL_LND_ADDRESS")?; - let lnd_cert = getenv("EXTERNAL_LND_CERT")?; - let youtube_token = getenv("YOUTUBE_API_TOKEN")?; - let twitter_token = getenv("TWITTER_BEARER")?; - let super_url = getenv("SUPER_URL")?; - let super_token = getenv("SUPER_TOKEN")?; + let swarm_updater_password = getenv("SWARM_UPDATER_PASSWORD")?; + let aws_s3_bucket_name = getenv("AWS_S3_BUCKET_NAME")?; + let github_pat = getenv("GITHUB_PAT")?; let swarm_name = format!("{}", swarm_name); - let swarm_number = rand::thread_rng().gen_range(100000..1000000); let device_name = getenv("AWS_DEVICE_NAME")?; - - let image_id = getenv("AWS_IMAGE_ID")?; - let security_group_id = getenv("AWS_SECURITY_GROUP_ID")?; - let subnet_id = getenv("AWS_SUBNET_ID")?; - let key_name = getenv("AWS_KEY_NAME")?; - let aws_role = getenv("AWS_USER_ROLE")?; - - let swarm_updater_password = getenv("SWARM_UPDATER_PASSWORD")?; - - let aws_s3_bucket_name = getenv("AWS_S3_BUCKET_NAME")?; - let custom_domain = vanity_address.unwrap_or_else(|| String::from("")); - let key = getenv("SWARM_TAG_KEY")?; - let value = getenv("SWARM_TAG_VALUE")?; - let github_pat = getenv("GITHUB_PAT")?; + // Check if we have a custom AMI ID, otherwise fall back to original + let use_custom_ami = getenv("AWS_CUSTOM_AMI_ID").is_ok(); + let image_id = if use_custom_ami { + getenv("AWS_CUSTOM_AMI_ID")? + } else { + getenv("AWS_IMAGE_ID")? + }; - let mut host = custom_domain.clone(); + log::info!("Using {} AMI: {}", + if use_custom_ami { "custom optimized" } else { "standard" }, + image_id + ); + let mut host = custom_domain.clone(); let mut docker_compose_start_script = r#"./restart-second-brain-2.sh"#.to_string(); - let mut setup_tls_cert = format!( r#"cd /home/admin && \ mkdir -p certs && \ @@ -463,7 +454,6 @@ async fn create_ec2_instance( } let mut env_lines = "".to_string(); - if let Some(env_map) = env { env_lines = env_map .iter() @@ -477,8 +467,8 @@ async fn create_ec2_instance( .collect::>() .join(""); } - let mut password = "password".to_string(); + let mut password = "password".to_string(); if let Some(provided_swarm_password) = swarm_password { if provided_swarm_password.is_empty() { return Err(anyhow!("password cannot be an empty string")); @@ -486,12 +476,59 @@ async fn create_ec2_instance( password = provided_swarm_password } + // Create different user data scripts based on AMI type + let user_data_script = if use_custom_ami { + create_optimized_user_data_script( + &setup_tls_cert, + &host, + &aws_s3_bucket_name, + &stakwork_token, + &lnd_macaroon, + &lnd_address, + &lnd_cert, + &youtube_token, + &swarm_updater_password, + &twitter_token, + &super_token, + &super_url, + &custom_domain, + swarm_number, + &password, + &github_pat, + &port_based_ssl, + &env_lines, + &docker_compose_start_script, + ) + } else { + create_legacy_user_data_script( + &setup_tls_cert, + &host, + &aws_s3_bucket_name, + &stakwork_token, + &lnd_macaroon, + &lnd_address, + &lnd_cert, + &youtube_token, + &swarm_updater_password, + &twitter_token, + &super_token, + &super_url, + &custom_domain, + swarm_number, + &password, + &github_pat, + &port_based_ssl, + &env_lines, + &docker_compose_start_script, + ) + }; + + // Rest of your EC2 creation code stays the same... let timeout_config = TimeoutConfig::builder() .connect_timeout(Duration::from_secs(5)) .read_timeout(Duration::from_secs(60)) .build(); - // Load the AWS configuration let config = aws_config::from_env() .region(region_provider) .retry_config(RetryConfig::standard().with_max_attempts(10)) @@ -500,114 +537,18 @@ async fn create_ec2_instance( .await; let client = Client::new(&config); - let user_data_script = format!( - r#"#!/bin/bash - su - admin -c ' - cd /home/admin && - pwd && - echo "INSTALLING DEPENDENCIES..." && \ - curl -fsSL https://get.docker.com/ -o get-docker.sh && \ - sh get-docker.sh && \ - sudo usermod -aG docker $USER && \ - sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \ - sudo chmod +x /usr/local/bin/docker-compose && \ - docker-compose version && \ - sudo apt update && \ - sudo apt install -y git unzip awscli && \ - - # Create Docker network - echo "Creating Docker network..." && \ - newgrp docker <> .env && \ - echo "NETWORK=bitcoin" >> .env && \ - echo "AWS_REGION=us-east-1" >> .env && \ - echo "AWS_S3_BUCKET_NAME={aws_s3_bucket_name}" >> .env && \ - echo "STAKWORK_ADD_NODE_TOKEN={stakwork_token}" >> .env && \ - echo "STAKWORK_RADAR_REQUEST_TOKEN={stakwork_token}" >> .env && \ - echo "NO_REMOTE_SIGNER=true" >> .env && \ - echo "EXTERNAL_LND_MACAROON={lnd_macaroon}" >> .env && \ - echo "EXTERNAL_LND_ADDRESS={lnd_address}" >> .env && \ - echo "EXTERNAL_LND_CERT={lnd_cert}" >> .env && \ - echo "YOUTUBE_API_TOKEN={youtube_token}" >> .env && \ - echo "SWARM_UPDATER_PASSWORD={swarm_updater_password}" >> .env && \ - echo "JARVIS_FEATURE_FLAG_SCHEMA=true" >> .env && \ - echo "BACKUP_KEY=" >> .env && \ - echo "FEATURE_FLAG_TEXT_EMBEDDINGS=true" >> .env && \ - echo "TWITTER_BEARER={twitter_token}" >> .env && \ - echo "SUPER_TOKEN={super_token}" >> .env && \ - echo "SUPER_URL={super_url}" >> .env && \ - echo "NAV_BOLTWALL_SHARED_HOST={custom_domain}" >> .env && \ - echo "SECOND_BRAIN_ONLY=true" >> .env && \ - echo "SWARM_NUMBER={swarm_number}" >> .env && \ - echo "PASSWORD={password}" >> .env && \ - echo "GITHUB_PAT={github_pat}" >> .env && \ - {port_based_ssl} - - sleep 60 && \ - - echo "Setting up Restarter server..." && \ - sudo apt install -y nodejs npm && \ - sudo npm install pm2 -g - ' - - echo 'module.exports = {{ - apps: [ - {{ - name: "restarter", - script: "./restarter.js", - env: {{ - SECOND_BRAIN: "true", - PASSWORD: "{swarm_updater_password}", - }}, - }}, - ], - }};' > /home/admin/sphinx-swarm/ecosystem.config.js - - su - admin -c ' - cd /home/admin && \ - wget https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_amd64/amazon-ssm-agent.deb && \ - sudo dpkg -i amazon-ssm-agent.deb - cd /home/admin/sphinx-swarm && \ - pm2 start ecosystem.config.js && \ - pm2 save && \ - startup_command=$(pm2 startup | grep "sudo" | tail -n 1) && \ - eval $startup_command && \ - pm2 save && \ - {docker_compose_start_script} - ' - "# - ); - let tags = vec![ Tag::builder().key("Name").value(swarm_name).build(), Tag::builder().key(key).value(value).build(), ]; - // Define the TagSpecification to apply the tags when the instance is created let tag_specification = TagSpecification::builder() .resource_type("instance".into()) .set_tags(Some(tags)) .build(); let block_device = BlockDeviceMapping::builder() - .device_name(device_name) // Valid for Debian + .device_name(device_name) .ebs(EbsBlockDevice::builder().volume_size(100).build()) .build(); @@ -642,10 +583,6 @@ async fn create_ec2_instance( .iam_instance_profile(instance_profile_spec) .metadata_options(metadata_options) .send() - // .map_err(|err| { - // log::error!("Error Creating instance instance: {}", err); - // anyhow!(err.to_string()) - // }) .await; match result { @@ -703,6 +640,231 @@ async fn create_ec2_instance( } } +fn create_optimized_user_data_script( + setup_tls_cert: &str, + host: &str, + aws_s3_bucket_name: &str, + stakwork_token: &str, + lnd_macaroon: &str, + lnd_address: &str, + lnd_cert: &str, + youtube_token: &str, + swarm_updater_password: &str, + twitter_token: &str, + super_token: &str, + super_url: &str, + custom_domain: &str, + swarm_number: i32, + password: &str, + github_pat: &str, + port_based_ssl: &str, + env_lines: &str, + docker_compose_start_script: &str, +) -> String { + format!( + r#"#!/bin/bash +exec > >(tee /var/log/sphinx-swarm/user-data.log) 2>&1 +set -e + +echo "Starting OPTIMIZED Sphinx Swarm deployment at $(date)" + +su - admin -c ' + cd /home/admin + + echo "Setting up TLS certificates..." + {setup_tls_cert} + + echo "Cloning sphinx-swarm repository..." + git clone https://github.com/stakwork/sphinx-swarm.git + cd sphinx-swarm + + echo "Creating environment configuration..." + cat > .env << "ENVEOF" +HOST={host} +NETWORK=bitcoin +AWS_REGION=us-east-1 +AWS_S3_BUCKET_NAME={aws_s3_bucket_name} +STAKWORK_ADD_NODE_TOKEN={stakwork_token} +STAKWORK_RADAR_REQUEST_TOKEN={stakwork_token} +NO_REMOTE_SIGNER=true +EXTERNAL_LND_MACAROON={lnd_macaroon} +EXTERNAL_LND_ADDRESS={lnd_address} +EXTERNAL_LND_CERT={lnd_cert} +YOUTUBE_API_TOKEN={youtube_token} +SWARM_UPDATER_PASSWORD={swarm_updater_password} +JARVIS_FEATURE_FLAG_SCHEMA=true +BACKUP_KEY= +FEATURE_FLAG_TEXT_EMBEDDINGS=true +TWITTER_BEARER={twitter_token} +SUPER_TOKEN={super_token} +SUPER_URL={super_url} +NAV_BOLTWALL_SHARED_HOST={custom_domain} +SECOND_BRAIN_ONLY=true +SWARM_NUMBER={swarm_number} +PASSWORD={password} +GITHUB_PAT={github_pat} +{port_based_ssl} +{env_lines} +ENVEOF + + echo "Creating PM2 configuration..." + cat > ecosystem.config.js << "PMEOF" +module.exports = {{ + apps: [ + {{ + name: "restarter", + script: "./restarter.js", + env: {{ + SECOND_BRAIN: "true", + PASSWORD: "{swarm_updater_password}", + }}, + }}, + ], +}}; +PMEOF + + echo "Starting PM2 services..." + pm2 start ecosystem.config.js + pm2 save + + # Setup PM2 to start on boot + startup_command=$(pm2 startup | grep "sudo" | tail -n 1) + if [ -n "$startup_command" ]; then + eval $startup_command 2>/dev/null || true + pm2 save + fi + + echo "Starting Sphinx Swarm application..." + {docker_compose_start_script} + + echo "OPTIMIZED deployment completed successfully at $(date)" +' + +echo "OPTIMIZED user data script completed at $(date)" +"# + ) +} + +fn create_legacy_user_data_script( + setup_tls_cert: &str, + host: &str, + aws_s3_bucket_name: &str, + stakwork_token: &str, + lnd_macaroon: &str, + lnd_address: &str, + lnd_cert: &str, + youtube_token: &str, + swarm_updater_password: &str, + twitter_token: &str, + super_token: &str, + super_url: &str, + custom_domain: &str, + swarm_number: i32, + password: &str, + github_pat: &str, + port_based_ssl: &str, + env_lines: &str, + docker_compose_start_script: &str, +) -> String { + // This is your original user data script + format!( + r#"#!/bin/bash + su - admin -c ' + cd /home/admin && + pwd && + echo "INSTALLING DEPENDENCIES..." && \ + curl -fsSL https://get.docker.com/ -o get-docker.sh && \ + sh get-docker.sh && \ + sudo usermod -aG docker $USER && \ + sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \ + sudo chmod +x /usr/local/bin/docker-compose && \ + docker-compose version && \ + sudo apt update && \ + sudo apt install -y git unzip awscli && \ + + # Create Docker network + echo "Creating Docker network..." && \ + newgrp docker <> .env && \ + echo "NETWORK=bitcoin" >> .env && \ + echo "AWS_REGION=us-east-1" >> .env && \ + echo "AWS_S3_BUCKET_NAME={aws_s3_bucket_name}" >> .env && \ + echo "STAKWORK_ADD_NODE_TOKEN={stakwork_token}" >> .env && \ + echo "STAKWORK_RADAR_REQUEST_TOKEN={stakwork_token}" >> .env && \ + echo "NO_REMOTE_SIGNER=true" >> .env && \ + echo "EXTERNAL_LND_MACAROON={lnd_macaroon}" >> .env && \ + echo "EXTERNAL_LND_ADDRESS={lnd_address}" >> .env && \ + echo "EXTERNAL_LND_CERT={lnd_cert}" >> .env && \ + echo "YOUTUBE_API_TOKEN={youtube_token}" >> .env && \ + echo "SWARM_UPDATER_PASSWORD={swarm_updater_password}" >> .env && \ + echo "JARVIS_FEATURE_FLAG_SCHEMA=true" >> .env && \ + echo "BACKUP_KEY=" >> .env && \ + echo "FEATURE_FLAG_TEXT_EMBEDDINGS=true" >> .env && \ + echo "TWITTER_BEARER={twitter_token}" >> .env && \ + echo "SUPER_TOKEN={super_token}" >> .env && \ + echo "SUPER_URL={super_url}" >> .env && \ + echo "NAV_BOLTWALL_SHARED_HOST={custom_domain}" >> .env && \ + echo "SECOND_BRAIN_ONLY=true" >> .env && \ + echo "SWARM_NUMBER={swarm_number}" >> .env && \ + echo "PASSWORD={password}" >> .env && \ + echo "GITHUB_PAT={github_pat}" >> .env && \ + {port_based_ssl} + + sleep 60 && \ + + echo "Setting up Restarter server..." && \ + sudo apt install -y nodejs npm && \ + sudo npm install pm2 -g + ' + + echo 'module.exports = {{ + apps: [ + {{ + name: "restarter", + script: "./restarter.js", + env: {{ + SECOND_BRAIN: "true", + PASSWORD: "{swarm_updater_password}", + }}, + }}, + ], + }};' > /home/admin/sphinx-swarm/ecosystem.config.js + + su - admin -c ' + cd /home/admin && \ + wget https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_amd64/amazon-ssm-agent.deb && \ + sudo dpkg -i amazon-ssm-agent.deb + cd /home/admin/sphinx-swarm && \ + pm2 start ecosystem.config.js && \ + pm2 save && \ + startup_command=$(pm2 startup | grep "sudo" | tail -n 1) && \ + eval $startup_command && \ + pm2 save && \ + {docker_compose_start_script} + + echo "User data script completed successfully at $(date)" + ' + "# + ) +} + async fn get_instance_ip(instance_id: &str) -> Result { let region = getenv("AWS_REGION")?; let region_provider = RegionProviderChain::first_try(Some(Region::new(region))); @@ -797,8 +959,8 @@ pub async fn create_swarm_ec2( state.ec2_limit.date = today_date; state.ec2_limit.count = 1; } - let mut actual_vanity_address: Option = None; + let mut actual_vanity_address: Option = None; let instance_type = get_instance(&info.instance_type); if instance_type.is_none() { @@ -840,7 +1002,13 @@ pub async fn create_swarm_ec2( )); } - let ec2_intance = create_ec2_instance( + // Detect if we're using custom AMI + let use_custom_ami = getenv("AWS_CUSTOM_AMI_ID").is_ok(); + log::info!("Creating instance with {} AMI", + if use_custom_ami { "optimized" } else { "standard" } + ); + + let ec2_instance = create_ec2_instance( info.name.clone(), actual_vanity_address.clone(), info.instance_type.clone(), @@ -850,97 +1018,252 @@ pub async fn create_swarm_ec2( ) .await?; - sleep(Duration::from_secs(40)).await; + let instance_id = &ec2_instance.0; + let swarm_number = ec2_instance.1; - let mut domain_names: Vec = Vec::new(); + // Create AWS client for waiting operations + let client = make_aws_client().await?; - let mut default_host = format!("swarm{}.sphinx.chat:8800", &ec2_intance.1); - let mut host = format!("swarm{}.sphinx.chat", &ec2_intance.1); + // Wait for instance to be running (replaces the sleep) + log::info!("Waiting for instance {} to be running...", instance_id); + client + .wait_until_instance_running() + .instance_ids(instance_id) + .wait(Duration::from_secs(180)) // 3 minutes max wait + .await + .map_err(|e| anyhow!("Instance failed to start: {}", e))?; + + log::info!("Instance is running, getting IP address..."); + + // Get IP address in parallel with domain setup + let ip_future = get_instance_ip(instance_id); + let domain_setup_future = async { + prepare_domain_configuration( + swarm_number, + &actual_vanity_address, + info.subdomain_ssl + ) + }; - if let Some(custom_domain) = &actual_vanity_address { - log::info!("vanity address is being set"); - if !custom_domain.is_empty() { - host = custom_domain.clone(); - default_host = format!("{}:8800", custom_domain.clone()); - } - } + let (ec2_ip_address, domain_config) = tokio::try_join!(ip_future, domain_setup_future)?; - let mut subdomain_ssl = false; + log::info!("Instance IP: {}, setting up domains...", ec2_ip_address); - if let Some(is_subdomain_ssl) = info.subdomain_ssl { - if is_subdomain_ssl == true { - subdomain_ssl = true - } - } + // Setup Route53 records + let _ = add_domain_name_to_route53(domain_config.domain_names.clone(), &ec2_ip_address).await?; - if subdomain_ssl == true { - default_host = format!("swarm{}.sphinx.chat", &ec2_intance.1); - let default_domain = format!("*.{}", default_host); - domain_names.push(default_domain); - if let Some(custom_domain) = &actual_vanity_address { - log::info!("vanity address is being set"); - if !custom_domain.is_empty() { - host = custom_domain.clone(); - domain_names.push(format!("*.{}", custom_domain)); - } - } + // If using custom AMI, wait for user data to complete + if use_custom_ami { + log::info!("Waiting for optimized deployment to complete..."); + wait_for_user_data_completion(instance_id, &client, Duration::from_secs(120)).await?; + } else { + log::info!("Waiting for standard deployment to complete..."); + wait_for_user_data_completion(instance_id, &client, Duration::from_secs(300)).await?; } - domain_names.push(host.clone()); - - let ec2_ip_address = get_instance_ip(&ec2_intance.0).await?; - - let _ = add_domain_name_to_route53(domain_names.clone(), &ec2_ip_address).await?; - - log::info!("Public_IP: {}", ec2_ip_address); + let swarm_id = format!("swarm{}", swarm_number); - let swarm_id = format!("swarm{}", ec2_intance.1); - - // add new ec2 to list of swarms + // Add new ec2 to list of swarms let new_swarm = RemoteStack { - host: host, + host: domain_config.host, ec2: Some(info.instance_type.clone()), - default_host: default_host.clone(), + default_host: domain_config.default_host, note: Some("".to_string()), user: Some("".to_string()), pass: Some("".to_string()), - ec2_instance_id: ec2_intance.0, - public_ip_address: Some("".to_string()), + ec2_instance_id: instance_id.clone(), + public_ip_address: Some(ec2_ip_address), private_ip_address: Some("".to_string()), id: Some(swarm_id.clone()), deleted: Some(false), - route53_domain_names: Some(domain_names), + route53_domain_names: Some(domain_config.domain_names), }; state.add_remote_stack(new_swarm); - log::info!("New Swarm added to stack"); - Ok(CreateEc2InstanceRes { swarm_id: swarm_id }) + log::info!("Swarm {} deployment completed successfully!", swarm_id); + Ok(CreateEc2InstanceRes { swarm_id }) +} + +// Helper struct for domain configuration +struct DomainConfig { + host: String, + default_host: String, + domain_names: Vec, } -pub fn is_valid_domain(domain: String) -> String { - let valid_chars = |c: char| c.is_ascii_alphanumeric() || c == '-'; +// Extract domain preparation logic +async fn prepare_domain_configuration( + swarm_number: i32, + actual_vanity_address: &Option, + subdomain_ssl: Option, +) -> Result { + let mut domain_names: Vec = Vec::new(); + let mut default_host = format!("swarm{}.sphinx.chat:8800", swarm_number); + let mut host = format!("swarm{}.sphinx.chat", swarm_number); - if domain.starts_with('-') || domain.ends_with('-') { - return "Hyphen cannot be the first or last character.".to_string(); + if let Some(custom_domain) = actual_vanity_address { + if !custom_domain.is_empty() { + host = custom_domain.clone(); + default_host = format!("{}:8800", custom_domain); + } } - let mut previous_char: Option = None; - for c in domain.chars() { - if !valid_chars(c) { - return "Domain can only contain letters, numbers, and hyphens.".to_string(); + let subdomain_ssl = subdomain_ssl.unwrap_or(false); + + if subdomain_ssl { + default_host = format!("swarm{}.sphinx.chat", swarm_number); + let default_domain = format!("*.{}", default_host); + domain_names.push(default_domain); + if let Some(custom_domain) = actual_vanity_address { + if !custom_domain.is_empty() { + host = custom_domain.clone(); + domain_names.push(format!("*.{}", custom_domain)); + } + } + } + + domain_names.push(host.clone()); + + Ok(DomainConfig { + host, + default_host, + domain_names, + }) +} + +// Smart waiting for user data completion +async fn wait_for_user_data_completion( + instance_id: &str, + client: &Client, + timeout: Duration, +) -> Result<(), Error> { + let start_time = std::time::Instant::now(); + + // First wait for status checks to pass + log::info!("Waiting for instance status checks to pass..."); + + // Wait for system status check (shorter timeout) + tokio::time::timeout( + Duration::from_secs(120), + client + .wait_until_instance_status_ok() + .instance_ids(instance_id) + .wait(Duration::from_secs(120)) + ) + .await + .map_err(|_| anyhow!("Timeout waiting for instance status checks"))? + .map_err(|e| anyhow!("Instance status check failed: {}", e))?; + + log::info!("Instance status checks passed, checking user data completion..."); + + // Check user data completion by looking for log markers + let mut attempts = 0; + let max_attempts = (timeout.as_secs() / 10) as u32; // Check every 10 seconds + + loop { + if start_time.elapsed() > timeout { + return Err(anyhow!("Timeout waiting for user data completion")); } - if let Some(prev) = previous_char { - if prev == '-' && c == '-' { - return "Hyphens cannot appear consecutively.".to_string(); + attempts += 1; + + // Try to get console output to check for completion markers + match get_console_output_and_check_completion(client, instance_id).await { + Ok(true) => { + log::info!("User data script completed successfully"); + return Ok(()); + } + Ok(false) => { + if attempts >= max_attempts { + log::warn!("Could not verify user data completion, but proceeding after timeout"); + return Ok(()); + } + log::info!("User data still running, waiting... (attempt {}/{})", attempts, max_attempts); + tokio::time::sleep(Duration::from_secs(10)).await; + } + Err(e) => { + log::warn!("Could not check console output: {}. Proceeding with time-based wait.", e); + // Fallback to a shorter time-based wait + let fallback_wait = if getenv("AWS_CUSTOM_AMI_ID").is_ok() { + Duration::from_secs(60) + } else { + Duration::from_secs(120) + }; + tokio::time::sleep(fallback_wait).await; + return Ok(()); } } + } +} - previous_char = Some(c); +// Check console output for completion markers +async fn get_console_output_and_check_completion( + client: &Client, + instance_id: &str, +) -> Result { + let output = client + .get_console_output() + .instance_id(instance_id) + .send() + .await?; + + if let Some(console_output) = output.output() { + // Decode base64 console output + let decoded = base64::decode(console_output) + .map_err(|e| anyhow!("Failed to decode console output: {}", e))?; + let output_text = String::from_utf8_lossy(&decoded); + + // Look for completion markers in the console output + let completion_markers = [ + "OPTIMIZED deployment completed successfully", + "User data script completed", + "deployment completed successfully", + "Deployment completed successfully", + ]; + + for marker in &completion_markers { + if output_text.contains(marker) { + return Ok(true); + } + } + + // Check for error markers + let error_markers = [ + "ERROR:", + "FAILED:", + "error:", + "failed:", + ]; + + for error in &error_markers { + if output_text.contains(error) && !output_text.contains("non-fatal") { + log::warn!("Potential error detected in user data script: check console output"); + } + } } - "".to_string() + Ok(false) +} + +// Helper function to create AWS client (if not already defined) +async fn make_aws_client() -> Result { + let region = getenv("AWS_REGION")?; + let region_provider = RegionProviderChain::first_try(Some(Region::new(region))); + + let timeout_config = TimeoutConfig::builder() + .connect_timeout(Duration::from_secs(5)) + .read_timeout(Duration::from_secs(60)) + .build(); + + let config = aws_config::from_env() + .region(region_provider) + .retry_config(RetryConfig::standard().with_max_attempts(5)) + .timeout_config(timeout_config) + .load() + .await; + + Ok(Client::new(&config)) } pub async fn update_aws_instance_type( From 1f767a0641e021c5bc431f6b0f2d03781a122d3c Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 12:07:01 +0100 Subject: [PATCH 02/12] testing --- .github/workflows/docker-build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 7181a2b0e..082b0ac14 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -3,8 +3,9 @@ env: DOCKER_CLI_EXPERIMENTAL: enabled on: - release: - types: [published] + push: + branches: + - feature/faster-swarm-spinup jobs: build: @@ -61,5 +62,5 @@ jobs: --cache-to "type=local,dest=/tmp/.buildx-cache" \ --platform linux/amd64 \ --tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-swarm:${{ env.RELEASE_TAG }}" \ - --tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-swarm:latest" \ + #--tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-swarm:latest" \ --output "type=registry" ./ From 6dec42649a5b794dd7d762f2d90f8e5e7d815c62 Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 12:10:32 +0100 Subject: [PATCH 03/12] testing --- .github/workflows/docker-build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 082b0ac14..4b187e573 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -62,5 +62,4 @@ jobs: --cache-to "type=local,dest=/tmp/.buildx-cache" \ --platform linux/amd64 \ --tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-swarm:${{ env.RELEASE_TAG }}" \ - #--tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-swarm:latest" \ --output "type=registry" ./ From 7fe617cb88a2fdbe13637b9c0be5e8041173eb96 Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 12:14:36 +0100 Subject: [PATCH 04/12] testing --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 4b187e573..503b29656 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -5,7 +5,7 @@ env: on: push: branches: - - feature/faster-swarm-spinup + - faster-swarm-spinup jobs: build: From 780f7c3527ea0e7254d0547349b2c1ab36a75fb4 Mon Sep 17 00:00:00 2001 From: Oluwatobi Bamidele Date: Tue, 9 Sep 2025 14:08:55 +0100 Subject: [PATCH 05/12] fix: spin up faster swarm --- src/bin/super/util.rs | 138 ++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 52 deletions(-) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index 7859b2fba..65f83f039 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -37,7 +37,7 @@ use crate::state::{self, AwsInstanceType, InstanceFromAws, RemoteStack, Super}; use aws_config::timeout::TimeoutConfig; use aws_sdk_ec2::types::IamInstanceProfileSpecification; use rand::Rng; -use tokio::time::{sleep, Duration}; +use tokio::time::Duration; pub fn add_new_swarm_details( state: &mut Super, @@ -425,8 +425,13 @@ async fn create_ec2_instance( getenv("AWS_IMAGE_ID")? }; - log::info!("Using {} AMI: {}", - if use_custom_ami { "custom optimized" } else { "standard" }, + log::info!( + "Using {} AMI: {}", + if use_custom_ami { + "custom optimized" + } else { + "standard" + }, image_id ); @@ -1004,8 +1009,13 @@ pub async fn create_swarm_ec2( // Detect if we're using custom AMI let use_custom_ami = getenv("AWS_CUSTOM_AMI_ID").is_ok(); - log::info!("Creating instance with {} AMI", - if use_custom_ami { "optimized" } else { "standard" } + log::info!( + "Creating instance with {} AMI", + if use_custom_ami { + "optimized" + } else { + "standard" + } ); let ec2_instance = create_ec2_instance( @@ -1037,13 +1047,8 @@ pub async fn create_swarm_ec2( // Get IP address in parallel with domain setup let ip_future = get_instance_ip(instance_id); - let domain_setup_future = async { - prepare_domain_configuration( - swarm_number, - &actual_vanity_address, - info.subdomain_ssl - ) - }; + let domain_setup_future = + prepare_domain_configuration(swarm_number, &actual_vanity_address, info.subdomain_ssl); let (ec2_ip_address, domain_config) = tokio::try_join!(ip_future, domain_setup_future)?; @@ -1139,17 +1144,17 @@ async fn wait_for_user_data_completion( timeout: Duration, ) -> Result<(), Error> { let start_time = std::time::Instant::now(); - + // First wait for status checks to pass log::info!("Waiting for instance status checks to pass..."); - + // Wait for system status check (shorter timeout) tokio::time::timeout( Duration::from_secs(120), client .wait_until_instance_status_ok() .instance_ids(instance_id) - .wait(Duration::from_secs(120)) + .wait(Duration::from_secs(120)), ) .await .map_err(|_| anyhow!("Timeout waiting for instance status checks"))? @@ -1160,14 +1165,14 @@ async fn wait_for_user_data_completion( // Check user data completion by looking for log markers let mut attempts = 0; let max_attempts = (timeout.as_secs() / 10) as u32; // Check every 10 seconds - + loop { if start_time.elapsed() > timeout { return Err(anyhow!("Timeout waiting for user data completion")); } attempts += 1; - + // Try to get console output to check for completion markers match get_console_output_and_check_completion(client, instance_id).await { Ok(true) => { @@ -1176,19 +1181,28 @@ async fn wait_for_user_data_completion( } Ok(false) => { if attempts >= max_attempts { - log::warn!("Could not verify user data completion, but proceeding after timeout"); + log::warn!( + "Could not verify user data completion, but proceeding after timeout" + ); return Ok(()); } - log::info!("User data still running, waiting... (attempt {}/{})", attempts, max_attempts); + log::info!( + "User data still running, waiting... (attempt {}/{})", + attempts, + max_attempts + ); tokio::time::sleep(Duration::from_secs(10)).await; } Err(e) => { - log::warn!("Could not check console output: {}. Proceeding with time-based wait.", e); + log::warn!( + "Could not check console output: {}. Proceeding with time-based wait.", + e + ); // Fallback to a shorter time-based wait - let fallback_wait = if getenv("AWS_CUSTOM_AMI_ID").is_ok() { - Duration::from_secs(60) - } else { - Duration::from_secs(120) + let fallback_wait = if getenv("AWS_CUSTOM_AMI_ID").is_ok() { + Duration::from_secs(60) + } else { + Duration::from_secs(120) }; tokio::time::sleep(fallback_wait).await; return Ok(()); @@ -1213,7 +1227,7 @@ async fn get_console_output_and_check_completion( let decoded = base64::decode(console_output) .map_err(|e| anyhow!("Failed to decode console output: {}", e))?; let output_text = String::from_utf8_lossy(&decoded); - + // Look for completion markers in the console output let completion_markers = [ "OPTIMIZED deployment completed successfully", @@ -1221,21 +1235,16 @@ async fn get_console_output_and_check_completion( "deployment completed successfully", "Deployment completed successfully", ]; - + for marker in &completion_markers { if output_text.contains(marker) { return Ok(true); } } - + // Check for error markers - let error_markers = [ - "ERROR:", - "FAILED:", - "error:", - "failed:", - ]; - + let error_markers = ["ERROR:", "FAILED:", "error:", "failed:"]; + for error in &error_markers { if output_text.contains(error) && !output_text.contains("non-fatal") { log::warn!("Potential error detected in user data script: check console output"); @@ -1246,25 +1255,25 @@ async fn get_console_output_and_check_completion( Ok(false) } -// Helper function to create AWS client (if not already defined) -async fn make_aws_client() -> Result { - let region = getenv("AWS_REGION")?; - let region_provider = RegionProviderChain::first_try(Some(Region::new(region))); - - let timeout_config = TimeoutConfig::builder() - .connect_timeout(Duration::from_secs(5)) - .read_timeout(Duration::from_secs(60)) - .build(); +// // Helper function to create AWS client (if not already defined) +// async fn make_aws_client() -> Result { +// let region = getenv("AWS_REGION")?; +// let region_provider = RegionProviderChain::first_try(Some(Region::new(region))); - let config = aws_config::from_env() - .region(region_provider) - .retry_config(RetryConfig::standard().with_max_attempts(5)) - .timeout_config(timeout_config) - .load() - .await; - - Ok(Client::new(&config)) -} +// let timeout_config = TimeoutConfig::builder() +// .connect_timeout(Duration::from_secs(5)) +// .read_timeout(Duration::from_secs(60)) +// .build(); + +// let config = aws_config::from_env() +// .region(region_provider) +// .retry_config(RetryConfig::standard().with_max_attempts(5)) +// .timeout_config(timeout_config) +// .load() +// .await; + +// Ok(Client::new(&config)) +// } pub async fn update_aws_instance_type( details: UpdateInstanceDetails, @@ -1694,6 +1703,31 @@ async fn handle_get_swarm_details_by_default_id( }) } +pub fn is_valid_domain(domain: String) -> String { + let valid_chars = |c: char| c.is_ascii_alphanumeric() || c == '-'; + + if domain.starts_with('-') || domain.ends_with('-') { + return "Hyphen cannot be the first or last character.".to_string(); + } + + let mut previous_char: Option = None; + for c in domain.chars() { + if !valid_chars(c) { + return "Domain can only contain letters, numbers, and hyphens.".to_string(); + } + + if let Some(prev) = previous_char { + if prev == '-' && c == '-' { + return "Hyphens cannot appear consecutively.".to_string(); + } + } + + previous_char = Some(c); + } + + "".to_string() +} + // async fn update_password_request( // token: String, // data: ChangeSwarmChildPasswordInfo, From 6c6af605e80b004befd4d39304924e85576fa4d8 Mon Sep 17 00:00:00 2001 From: Oluwatobi Bamidele Date: Tue, 9 Sep 2025 15:16:02 +0100 Subject: [PATCH 06/12] chore: add aws custom ami --- superadmin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/superadmin.yml b/superadmin.yml index e2223ee3a..1ea31910e 100644 --- a/superadmin.yml +++ b/superadmin.yml @@ -97,6 +97,7 @@ services: - SINGLE_AUDIO_OR_VIDEO_EPISODE_WORKFLOW_ID=$SINGLE_AUDIO_OR_VIDEO_EPISODE_WORKFLOW_ID - GITHUB_PAT=$GITHUB_PAT - LOG_TAIL_LENGTH=$LOG_TAIL_LENGTH + - AWS_CUSTOM_AMI_ID=$AWS_CUSTOM_AMI_ID prometheus: image: prom/prometheus container_name: prometheus From 2a622d603081508af2a99377eb90fd457819cf4e Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 15:42:33 +0100 Subject: [PATCH 07/12] testing --- src/bin/super/util.rs | 97 ++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 43 deletions(-) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index 65f83f039..2f6349b33 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -683,58 +683,68 @@ su - admin -c ' git clone https://github.com/stakwork/sphinx-swarm.git cd sphinx-swarm - echo "Creating environment configuration..." - cat > .env << "ENVEOF" -HOST={host} -NETWORK=bitcoin -AWS_REGION=us-east-1 -AWS_S3_BUCKET_NAME={aws_s3_bucket_name} -STAKWORK_ADD_NODE_TOKEN={stakwork_token} -STAKWORK_RADAR_REQUEST_TOKEN={stakwork_token} -NO_REMOTE_SIGNER=true -EXTERNAL_LND_MACAROON={lnd_macaroon} -EXTERNAL_LND_ADDRESS={lnd_address} -EXTERNAL_LND_CERT={lnd_cert} -YOUTUBE_API_TOKEN={youtube_token} -SWARM_UPDATER_PASSWORD={swarm_updater_password} -JARVIS_FEATURE_FLAG_SCHEMA=true -BACKUP_KEY= -FEATURE_FLAG_TEXT_EMBEDDINGS=true -TWITTER_BEARER={twitter_token} -SUPER_TOKEN={super_token} -SUPER_URL={super_url} -NAV_BOLTWALL_SHARED_HOST={custom_domain} -SECOND_BRAIN_ONLY=true -SWARM_NUMBER={swarm_number} -PASSWORD={password} -GITHUB_PAT={github_pat} -{port_based_ssl} -{env_lines} -ENVEOF + echo "Current working directory: $(pwd)" + ls -la + + echo "Creating environment configuration using echo commands..." + rm -f .env + echo "HOST={host}" >> .env + echo "NETWORK=bitcoin" >> .env + echo "AWS_REGION=us-east-1" >> .env + echo "AWS_S3_BUCKET_NAME={aws_s3_bucket_name}" >> .env + echo "STAKWORK_ADD_NODE_TOKEN={stakwork_token}" >> .env + echo "STAKWORK_RADAR_REQUEST_TOKEN={stakwork_token}" >> .env + echo "NO_REMOTE_SIGNER=true" >> .env + echo "EXTERNAL_LND_MACAROON={lnd_macaroon}" >> .env + echo "EXTERNAL_LND_ADDRESS={lnd_address}" >> .env + echo "EXTERNAL_LND_CERT={lnd_cert}" >> .env + echo "YOUTUBE_API_TOKEN={youtube_token}" >> .env + echo "SWARM_UPDATER_PASSWORD={swarm_updater_password}" >> .env + echo "JARVIS_FEATURE_FLAG_SCHEMA=true" >> .env + echo "BACKUP_KEY=" >> .env + echo "FEATURE_FLAG_TEXT_EMBEDDINGS=true" >> .env + echo "TWITTER_BEARER={twitter_token}" >> .env + echo "SUPER_TOKEN={super_token}" >> .env + echo "SUPER_URL={super_url}" >> .env + echo "NAV_BOLTWALL_SHARED_HOST={custom_domain}" >> .env + echo "SECOND_BRAIN_ONLY=true" >> .env + echo "SWARM_NUMBER={swarm_number}" >> .env + echo "PASSWORD={password}" >> .env + echo "GITHUB_PAT={github_pat}" >> .env + {port_based_ssl} + {env_lines} + + echo "Verifying .env file was created:" + ls -la .env + echo "File contains $(wc -l < .env) lines" + echo "First few lines:" + head -5 .env echo "Creating PM2 configuration..." - cat > ecosystem.config.js << "PMEOF" -module.exports = {{ - apps: [ - {{ - name: "restarter", - script: "./restarter.js", - env: {{ - SECOND_BRAIN: "true", - PASSWORD: "{swarm_updater_password}", - }}, - }}, - ], -}}; -PMEOF + rm -f ecosystem.config.js + echo "module.exports = {{" > ecosystem.config.js + echo " apps: [" >> ecosystem.config.js + echo " {{" >> ecosystem.config.js + echo " name: \"restarter\"," >> ecosystem.config.js + echo " script: \"./restarter.js\"," >> ecosystem.config.js + echo " env: {{" >> ecosystem.config.js + echo " SECOND_BRAIN: \"true\"," >> ecosystem.config.js + echo " PASSWORD: \"{swarm_updater_password}\"," >> ecosystem.config.js + echo " }}," >> ecosystem.config.js + echo " }}," >> ecosystem.config.js + echo " ]," >> ecosystem.config.js + echo "}};" >> ecosystem.config.js + + echo "PM2 config created:" + cat ecosystem.config.js echo "Starting PM2 services..." pm2 start ecosystem.config.js pm2 save - # Setup PM2 to start on boot startup_command=$(pm2 startup | grep "sudo" | tail -n 1) if [ -n "$startup_command" ]; then + echo "Setting up PM2 startup" eval $startup_command 2>/dev/null || true pm2 save fi @@ -750,6 +760,7 @@ echo "OPTIMIZED user data script completed at $(date)" ) } + fn create_legacy_user_data_script( setup_tls_cert: &str, host: &str, From 9d3694dc6040a46ef53c318c87758e9d9a8d6d34 Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 20:21:24 +0100 Subject: [PATCH 08/12] updated scripts --- restart-second-brain.sh => restart-swarm-no-ports.sh | 4 +++- restart-second-brain-2.sh => restart-swarm-with-ports.sh | 4 +++- scripts/cleanup.sh | 2 ++ scripts/pull_secondbrain.sh | 4 +++- 4 files changed, 11 insertions(+), 3 deletions(-) rename restart-second-brain.sh => restart-swarm-no-ports.sh (71%) rename restart-second-brain-2.sh => restart-swarm-with-ports.sh (71%) create mode 100755 scripts/cleanup.sh diff --git a/restart-second-brain.sh b/restart-swarm-no-ports.sh similarity index 71% rename from restart-second-brain.sh rename to restart-swarm-no-ports.sh index 5cb0816ab..dfd058f4c 100755 --- a/restart-second-brain.sh +++ b/restart-swarm-no-ports.sh @@ -4,4 +4,6 @@ docker stop sphinx-swarm && docker rm sphinx-swarm docker-compose -f second-brain.yml up sphinx-swarm -d -docker logs sphinx-swarm --follow \ No newline at end of file +docker logs sphinx-swarm --follow + +./scripts/cleanup.sh diff --git a/restart-second-brain-2.sh b/restart-swarm-with-ports.sh similarity index 71% rename from restart-second-brain-2.sh rename to restart-swarm-with-ports.sh index b6fc64a7b..cddfe92a6 100755 --- a/restart-second-brain-2.sh +++ b/restart-swarm-with-ports.sh @@ -4,4 +4,6 @@ docker stop sphinx-swarm && docker rm sphinx-swarm docker-compose -f second-brain-2.yml up sphinx-swarm -d -docker logs sphinx-swarm --follow \ No newline at end of file +docker logs sphinx-swarm --follow + +./scripts/cleanup.sh diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 000000000..8f4f4b53e --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,2 @@ +docker builder prune -f +docker image prune -f diff --git a/scripts/pull_secondbrain.sh b/scripts/pull_secondbrain.sh index 3c86bb831..f479b3c10 100755 --- a/scripts/pull_secondbrain.sh +++ b/scripts/pull_secondbrain.sh @@ -1,4 +1,6 @@ -docker image prune -af +docker builder prune -f +docker image prune -f + docker pull sphinxlightning/sphinx-swarm:latest docker pull sphinxlightning/sphinx-boltwall:latest # docker pull sphinxlightning/sphinx-jarvis-backend:latest From 12107d8efbaa4a0067c04bc889629e669d7e4888 Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 20:23:21 +0100 Subject: [PATCH 09/12] renamed scripts --- src/bin/super/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index 2f6349b33..b416ab564 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -436,7 +436,7 @@ async fn create_ec2_instance( ); let mut host = custom_domain.clone(); - let mut docker_compose_start_script = r#"./restart-second-brain-2.sh"#.to_string(); + let mut docker_compose_start_script = r#"./restart-swarm-with-ports.sh"#.to_string(); let mut setup_tls_cert = format!( r#"cd /home/admin && \ mkdir -p certs && \ @@ -452,7 +452,7 @@ async fn create_ec2_instance( if let Some(is_subdomain_ssl) = subdomain_ssl { if is_subdomain_ssl == true { host = format!("swarm{}.sphinx.chat", swarm_number); - docker_compose_start_script = format!(r#"./restart-second-brain.sh"#); + docker_compose_start_script = format!(r#"./restart-swarm-no-ports.sh"#); setup_tls_cert = "".to_string(); port_based_ssl = "".to_string() } From b6346d4c073fd4215eef624c4b51535b25722243 Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 20:54:29 +0100 Subject: [PATCH 10/12] updates --- src/bin/super/util.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index b416ab564..e585d46b4 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -679,8 +679,11 @@ su - admin -c ' echo "Setting up TLS certificates..." {setup_tls_cert} + cd /home/admin + echo "Cloning sphinx-swarm repository..." git clone https://github.com/stakwork/sphinx-swarm.git + git checkout faster-swarm-spinup cd sphinx-swarm echo "Current working directory: $(pwd)" From d212741854b05ede4892c0fe93244de911ab10ea Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Tue, 9 Sep 2025 21:33:30 +0100 Subject: [PATCH 11/12] updates --- src/bin/super/util.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index e585d46b4..85d486126 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -683,9 +683,11 @@ su - admin -c ' echo "Cloning sphinx-swarm repository..." git clone https://github.com/stakwork/sphinx-swarm.git - git checkout faster-swarm-spinup + cd sphinx-swarm + git checkout faster-swarm-spinup + echo "Current working directory: $(pwd)" ls -la From 1e2cc5d67962cc7b428e2c6e63e9e886f7f5dca3 Mon Sep 17 00:00:00 2001 From: Gonzalo Javier Aune Date: Wed, 10 Sep 2025 10:54:54 +0100 Subject: [PATCH 12/12] only cloning the latest since we don't care about history --- src/bin/super/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index 85d486126..1c9bb6252 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -682,7 +682,7 @@ su - admin -c ' cd /home/admin echo "Cloning sphinx-swarm repository..." - git clone https://github.com/stakwork/sphinx-swarm.git + git clone --depth=1 https://github.com/stakwork/sphinx-swarm.git cd sphinx-swarm